def edit_history_delete_old_callback(update: Update, context: CallbackContext,
                                     chat_info: dict, _: gettext):
    db_session = Session()

    size = get_keyboard_size(update.message.chat_id)

    subquery = (db_session.query(
        ChatRequests.id).filter_by(chat_id=update.message.chat_id).order_by(
            ChatRequests.times.desc()).limit(size))

    db_session.query(ChatRequests).filter(
        ChatRequests.chat_id == update.message.chat_id,
        ChatRequests.id.notin_(subquery)).delete(synchronize_session="fetch")

    transaction.commit()

    text_to = _("History of hidden requests has been cleared.")

    keyboard = get_keyboard_deletion(update.message.chat_id, _)

    update.message.reply_text(parse_mode=ParseMode.MARKDOWN,
                              reply_markup=keyboard,
                              text=text_to)

    return SettingsSteps.onscreen_menu_edit_history
def set_command(update: Update, context: CallbackContext, chat_info: dict,
                _: gettext):
    if update.message.text.endswith("___"):
        default_currency_position = False
    elif update.message.text.startswith("___"):
        default_currency_position = True
    else:
        update.message.reply_text(text="🧐")
        return SettingsSteps.default_currency_position

    if default_currency_position:
        position = f'___{chat_info["default_currency"]}'
    else:
        position = f'{chat_info["default_currency"]}___'

    db_session = Session()
    db_session.query(Chat).filter_by(id=update.message.chat_id).update(
        {"default_currency_position": default_currency_position})
    transaction.commit()

    text_to = _(
        "*%(position)s* - position where your default currency will be added."
    ) % {
        "position": position
    }

    update.message.reply_text(parse_mode=ParseMode.MARKDOWN, text=text_to)

    main_menu(update, chat_info, _)

    return SettingsSteps.main
예제 #3
0
def set_callback(update: Update, context: CallbackContext, chat_info: dict, _: gettext):
    currency_code = update.message.text.upper()

    db_session = Session()

    currency = (
        db_session.query(Currency).filter_by(code=currency_code, is_active=True).first()
    )

    if not currency:
        update.message.reply_text(text="🧐")
        return SettingsSteps.default_currency

    db_session.query(Chat).filter_by(id=update.message.chat_id).update(
        {"default_currency": currency_code}
    )
    transaction.commit()

    text_to = _("*%(default_currency)s* is your default currency.") % {
        "default_currency": currency_code
    }

    update.message.reply_text(parse_mode=ParseMode.MARKDOWN, text=text_to)

    main_menu(update, chat_info, _)

    return SettingsSteps.main
예제 #4
0
def write_request_log(chat_id: int,
                      msg: str,
                      created_at: datetime,
                      tag: str = "") -> None:
    if len(msg) > settings.MAX_LEN_MSG_REQUESTS_LOG:
        return

    db_session = Session()
    db_session.add(
        RequestsLog(chat_id=chat_id,
                    message=msg,
                    tag=tag,
                    created_at=created_at))
    transaction.commit()
예제 #5
0
def start_callback(update: Update, context: CallbackContext, chat_info: dict,
                   _: gettext):
    if update.message.chat.type == "private":
        name = update.message.from_user.first_name
    else:
        name = _("humans")

    update.message.reply_text(text=_("Hello, %(name)s!") % {"name": name})

    if chat_info["created"]:
        tutorial(update, _)

    else:
        if not chat_info["is_subscribed"]:
            Session().query(Chat).filter_by(id=update.message.chat_id).update(
                {"is_subscribed": true()})
            transaction.commit()

        keyboard = get_keyboard(update.message.chat_id)

        update.message.reply_text(
            reply_markup=ReplyKeyboardMarkup(keyboard) if keyboard else None,
            text=_("Have any question how to talk with me? 👉 /tutorial"),
        )

    return ConversationHandler.END
예제 #6
0
def get_all_currencies():
    return (
        Session.query(Currency.code, Currency.name)
        .filter_by(is_active=True)
        .order_by(Currency.name)
        .all()
    )
    def parse(self) -> PriceRequest:
        text = self.text

        obj = AMOUNT_PATTERN_COMPILED.match(text)
        if not obj:
            raise WrongFormatException

        amount = obj[0]

        if amount:
            amount = parse_amount(amount, self.locale)

        last_request = (Session.query(ChatRequests).filter_by(
            chat_id=self.chat_id).order_by(
                ChatRequests.modified_at.desc()).first())

        if not last_request:
            raise WrongFormatException

        locale = Locale(self.locale)

        if locale.character_order == "right-to-left":
            direction_writing = DirectionWriting.RIGHT2LEFT
        else:
            direction_writing = DirectionWriting.LEFT2RIGHT

        return PriceRequest(
            amount=amount,
            currency=last_request.from_currency.code,
            to_currency=last_request.to_currency.code,
            parser_name=self.name,
            direction_writing=direction_writing,
        )
예제 #8
0
def stop_callback(update: Update, context: CallbackContext, chat_info: dict,
                  _: gettext):
    if chat_info["is_subscribed"]:
        Session().query(Chat).filter_by(id=update.message.chat_id).update(
            {"is_subscribed": false()})
        transaction.commit()

    update.message.reply_text(text=_(
        "You're unsubscribed. You always can subscribe again 👉 /start"))

    Session().query(Notification).filter_by(
        is_active=true(),
        chat_id=update.message.chat_id).update({"is_active": false()})

    transaction.commit()

    return ConversationHandler.END
def visibility_set(update: Update, chat_info: dict, _: gettext,
                   is_show_keyboard: bool):
    db_session = Session()
    chat = db_session.query(Chat).filter_by(id=update.message.chat_id).first()
    chat.is_show_keyboard = is_show_keyboard
    transaction.commit()

    if is_show_keyboard:
        text_to = _(
            "On-screen menu below with a history requests will *always shows*."
        )
    else:
        text_to = _(
            "On-screen menu below with a history requests will *never shows*.")

    update.message.reply_text(parse_mode=ParseMode.MARKDOWN, text=text_to)

    onscreen_menu(update, chat_info, _)
예제 #10
0
def get_last_request(chat_id):
    size = get_keyboard_size(chat_id)

    return (
        Session.query(ChatRequests)
        .filter_by(chat_id=chat_id)
        .order_by(ChatRequests.times.desc(), ChatRequests.modified_at.asc())
        .limit(size)
        .all()
    )
예제 #11
0
def rate_from_pair_data(pair_data: PairData, exchange_id: int) -> Rate:
    db_session = Session()
    try:
        from_currency = (db_session.query(Currency).filter_by(
            is_active=True, code=pair_data.pair.from_currency).one())
        to_currency = (db_session.query(Currency).filter_by(
            is_active=True, code=pair_data.pair.to_currency).one())
    except NoResultFound:
        raise CurrencyNotSupportedException(pair_data.pair)

    return Rate(
        exchange_id=exchange_id,
        from_currency=from_currency,
        to_currency=to_currency,
        rate=pair_data.rate,
        rate_open=pair_data.rate_open,
        low24h=pair_data.low24h,
        high24h=pair_data.high24h,
        last_trade_at=pair_data.last_trade_at,
    )
예제 #12
0
def delete_expired_rates() -> None:
    db_session = Session()
    current_time = datetime.utcnow()
    two_days_ago = current_time - timedelta(days=2)
    rates = db_session.query(Rate).filter(Rate.last_trade_at < two_days_ago)
    for r in rates:
        logging.warning(
            "Rate expired exchange: %s, pair: %s-%s",
            r.exchange.name,
            r.from_currency.code,
            r.to_currency.code,
        )

    rates.delete()

    try:
        transaction.commit()
    except (IntegrityError, OperationalError):
        logging.exception("Error delete expired")
        transaction.abort()
예제 #13
0
def send_notification(self, chat_id: int, text: str) -> None:
    """
    https://core.telegram.org/bots/faq#how-can-i-message-all-of-my-bot-39s-subscribers-at-once
    The API will not allow more than ~30 messages to different users per second
    """
    bot = Bot(settings.BOT_TOKEN)

    try:
        bot.send_message(
            chat_id=chat_id,
            disable_web_page_preview=True,
            parse_mode=ParseMode.MARKDOWN,
            text=text,
        )

    except TimedOut as e:
        raise self.retry(exc=e)

    except RetryAfter as e:
        raise self.retry(exc=e, countdown=int(e.retry_after))

    except Unauthorized:
        # bot deleted
        db_session = Session()

        db_session.query(Notification).filter_by(is_active=true(),
                                                 chat_id=chat_id).update(
                                                     {"is_active": false()})

        db_session.query(Chat).filter_by(id=chat_id).update(
            {"is_subscribed": false()})

        transaction.commit()
def edit_history_delete_all_callback(update: Update, context: CallbackContext,
                                     chat_info: dict, _: gettext):
    Session().query(ChatRequests).filter_by(
        chat_id=update.message.chat_id).delete()
    transaction.commit()

    text_to = _("History requests has been cleared fully.")

    update.message.reply_text(parse_mode=ParseMode.MARKDOWN, text=text_to)

    onscreen_menu(update, chat_info, _)

    return SettingsSteps.onscreen_menu
def set_size_callback(update: Update, context: CallbackContext,
                      chat_info: dict, _: gettext):
    if update.message.text not in KEYBOARD_SIZES:
        update.message.reply_text(text="🧐")
        return SettingsSteps.onscreen_menu_size

    text_to = _(
        "%(keyboard_size)s - size of on-screen menu with a history requests was changed."
    ) % {
        "keyboard_size": update.message.text
    }

    db_session = Session()
    db_session.query(Chat).filter_by(id=update.message.chat_id).update(
        {"keyboard_size": update.message.text})
    transaction.commit()

    update.message.reply_text(parse_mode=ParseMode.MARKDOWN, text=text_to)

    onscreen_menu(update, chat_info, _)

    return SettingsSteps.onscreen_menu
예제 #16
0
def set_callback(update: Update, context: CallbackContext, chat_info: dict):
    if update.message.text not in settings.LANGUAGES_NAME:
        update.message.reply_text(text="🧐")
        return SettingsSteps.language
    else:
        locale = settings.LANGUAGES_NAME[update.message.text]

    db_session = Session()
    db_session.query(Chat).filter_by(id=update.message.chat_id).update(
        {"locale": locale})
    transaction.commit()

    _ = get_translations(locale)
    text_to = _("*%(language)s* is your language now.") % {
        "language": LOCALE_NAME[locale]
    }

    update.message.reply_text(parse_mode=ParseMode.MARKDOWN, text=text_to)

    main_menu(update, chat_info, _)

    return SettingsSteps.main
예제 #17
0
    def wrapper(update: Update, context: CallbackContext, *args, **kwargs):
        if not update.effective_user:
            # bots, may be exclude in filter messages
            return

        if update.effective_chat:
            # we need settings for a group chats, not for a specific user
            # private chat id == user id
            chat_id = update.effective_chat.id
        else:
            # inline commands, get settings for his private chat
            chat_id = update.effective_user.id

        if update.effective_user.language_code:
            # chats don't have language_code, that why we take from user, not so correct yes
            # they will able change language later
            # https://en.wikipedia.org/wiki/IETF_language_tag
            language_code = update.effective_user.language_code.lower()
        else:
            # some users don't have locale, set default
            language_code = settings.LANGUAGE_CODE

        db_session = Session()

        chat = db_session.query(Chat).filter_by(id=chat_id).first()

        if not chat:
            chat = Chat(
                id=chat_id,
                locale=language_code,
                is_show_keyboard=True if chat_id > 0 else
                False,  # never show keyboard for a group chats
            )
            db_session.add(chat)
            try:
                transaction.commit()
                chat_created = True
            except IntegrityError:
                chat_created = False
                logging.exception("Error create chat, chat exists")
                transaction.abort()
            finally:
                chat = db_session.query(Chat).filter_by(id=chat_id).one()
        else:
            chat_created = False

        kwargs["chat_info"] = {
            "chat_id": chat.id,
            "created": chat_created,
            "locale": chat.locale,
            "is_subscribed": chat.is_subscribed,
            "is_show_keyboard": chat.is_show_keyboard,
            "keyboard_size": chat.keyboard_size,
            "default_currency": chat.default_currency,
            "default_currency_position": chat.default_currency_position,
        }

        return func(update, context, *args, **kwargs)
예제 #18
0
def notification_auto_disable(pair: list) -> None:
    db_session = Session()

    notifications = (db_session.query(Notification).filter_by(
        is_active=true(), from_currency_id=pair[0],
        to_currency_id=pair[1]).all())

    for n in notifications:
        _ = get_translations(n.chat.locale)
        send_notification.delay(
            n.chat_id,
            _("Your notification has been disabled, due to one of the currencies"
              " %(from_currency)s %(to_currency)s has been deactivated.") % {
                  "from_currency": n.from_currency.code,
                  "to_currency": n.to_currency.code,
              },
        )

    db_session.query(Notification).filter_by(is_active=true(),
                                             from_currency_id=pair[0],
                                             to_currency_id=pair[1]).update(
                                                 {"is_active": false()})

    transaction.commit()
예제 #19
0
def newsletter(file_text, for_all):
    try:
        text = open(file_text, "r").read()
    except FileNotFoundError:
        click.echo("File not found.")
        return

    if for_all:
        click.echo("Will start delivery for all...")
        time.sleep(5)

    db_engine = create_engine(settings.DATABASE["url"])
    init_sqlalchemy(db_engine)

    chats = Session.query(Chat.id).filter_by(is_subscribed=True).order_by(
        Chat.id)

    if not for_all:
        chats = chats.filter_by(id=settings.DEVELOPER_USER_ID)

    for chat in chats.yield_per(100):
        send_notification.delay(chat.id, text)
def edit_history_delete_one_callback(update: Update, context: CallbackContext,
                                     chat_info: dict, _: gettext):
    parts = update.message.text.split(" ")

    db_session = Session()

    from_currency = db_session.query(Currency).filter_by(code=parts[1]).first()

    to_currency = db_session.query(Currency).filter_by(code=parts[2]).first()

    if not from_currency or not to_currency:
        update.message.reply_text(text="🧐")
    else:
        db_session.query(ChatRequests).filter_by(
            chat_id=update.message.chat_id,
            from_currency=from_currency,
            to_currency=to_currency,
        ).delete()
        transaction.commit()

        text_to = _("*%(first)s %(second)s* was deleted.") % {
            "first": parts[1],
            "second": parts[2],
        }

        if have_last_request(update.message.chat_id):
            keyboard = get_keyboard_deletion(update.message.chat_id, _)

            update.message.reply_text(parse_mode=ParseMode.MARKDOWN,
                                      reply_markup=keyboard,
                                      text=text_to)

            return SettingsSteps.onscreen_menu_edit_history

        else:
            update.message.reply_text(parse_mode=ParseMode.MARKDOWN,
                                      text=text_to)

            onscreen_menu(update, chat_info, _)

            return SettingsSteps.onscreen_menu
예제 #21
0
def update_chat_request(chat_id: int, from_currency: str, to_currency: str):
    db_session = Session()
    from_currency = db_session.query(Currency).filter_by(
        code=from_currency).one()
    to_currency = db_session.query(Currency).filter_by(code=to_currency).one()

    chat_request = (db_session.query(ChatRequests).filter_by(
        chat_id=chat_id, from_currency=from_currency,
        to_currency=to_currency).first())

    if chat_request:
        chat_request.times = ChatRequests.times + 1

    else:
        chat_request = ChatRequests(chat_id=chat_id,
                                    from_currency=from_currency,
                                    to_currency=to_currency)
        db_session.add(chat_request)

    try:
        transaction.commit()
    except (IntegrityError, OperationalError):
        logging.exception("Error create chat_request, chat_request exists")
        transaction.abort()
예제 #22
0
def get_all_currency_codes():
    codes = (
        Session.query(Currency.code).filter_by(is_active=True).order_by(Currency.code)
    )
    return [x[0] for x in codes]
예제 #23
0
def have_last_request(chat_id):
    return Session.query(ChatRequests).filter_by(chat_id=chat_id).first()
예제 #24
0
def get_keyboard_size(chat_id):
    chat = Session.query(Chat.keyboard_size).filter_by(id=chat_id).first()
    w, h = chat[0].split("x")
    return int(w) * int(h)
예제 #25
0
def exchange_updater(exchange_class: str) -> None:
    db_session = Session()

    exchange = import_app_module(exchange_class)()
    try:
        exchange_obj = db_session.query(Exchange).filter_by(
            name=exchange.name).one()
        if not exchange_obj.is_active:
            logging.info(f"Exchange: {exchange.name} is not active, skip.")
            return
    except NoResultFound:
        logging.error(f"Exchange: {exchange.name} is not configured, skip.")
        return

    logging.info(f"Exchange: {exchange.name} in process.")

    for pair in exchange.list_pairs:
        from_currency = (db_session.query(Currency).filter_by(
            is_active=True, code=pair.from_currency).scalar())
        to_currency = (db_session.query(Currency).filter_by(
            is_active=True, code=pair.to_currency).scalar())

        if not from_currency or not to_currency:
            logging.debug(
                f"Exchange: {exchange.name}, pair: {pair} is not active or not supported, skip."
            )
            continue

        def save_rate(the_pair_data: PairData) -> None:
            new_rate = rate_from_pair_data(the_pair_data, exchange_obj.id)

            current_rate = (db_session.query(Rate).filter_by(
                from_currency=new_rate.from_currency,
                to_currency=new_rate.to_currency,
                exchange_id=exchange_obj.id,
            ).first())

            new_rate = fill_rate_open(new_rate, current_rate)

            if current_rate:
                new_rate.id = current_rate.id
                db_session.merge(new_rate)
            else:
                db_session.add(new_rate)

        pair_data = exchange.get_pair_info(pair)

        current_time = datetime.utcnow()
        week_ago = current_time - timedelta(days=7)
        if pair_data.last_trade_at < week_ago:
            logging.debug(
                "Rate expired exchange: %s, pair: %s-%s",
                exchange.name,
                pair.from_currency.code,
                pair.to_currency.code,
            )
            continue

        save_rate(pair_data)
        if not exchange.included_reversed_pairs:
            reversed_pair_data = reverse_pair_data(pair_data)
            save_rate(reversed_pair_data)

    try:
        transaction.commit()
    except (IntegrityError, OperationalError):
        logging.exception("Error to fill rate pair")
        transaction.abort()
예제 #26
0
def notification_checker() -> None:
    db_session = Session()
    pairs = (db_session.query(
        Notification.from_currency_id,
        Notification.to_currency_id).filter_by(is_active=true()).group_by(
            Notification.from_currency_id, Notification.to_currency_id).all())

    for pair in pairs:
        from_currency = db_session.query(Currency).get(pair[0])
        to_currency = db_session.query(Currency).get(pair[1])

        if not from_currency.is_active or not to_currency.is_active:
            logging.info(
                "Disable notifications because currency %s %s is not active anymore",
                from_currency.code,
                to_currency.code,
            )
            notification_auto_disable(pair)
            continue

        pr = PriceRequest(
            amount=None,
            currency=from_currency.code,
            to_currency=to_currency.code,
            parser_name="Notification",
        )

        prr = convert(pr)

        notifications = (db_session.query(Notification).filter_by(
            is_active=true()).filter_by(from_currency_id=pair[0],
                                        to_currency_id=pair[1]).all())

        for n in notifications:
            if is_triggered(n.trigger_clause, n.trigger_value, n.last_rate,
                            prr.rate):
                # replace rate_open from daily to rate when was created or last triggered notification
                # TODO: PriceRequestResult -> dataclass, inside diff and etc
                nprr = PriceRequestResult(
                    price_request=prr.price_request,
                    exchanges=prr.exchanges,
                    rate=prr.rate,
                    rate_open=n.last_rate,
                    last_trade_at=prr.last_trade_at,
                    low24h=prr.low24h,
                    high24h=prr.high24h,
                )

                text_to = NotifyFormatPriceRequestResult(nprr,
                                                         n.chat.locale).get()

                if n.trigger_clause in [
                        NotifyTriggerClauseEnum.less,
                        NotifyTriggerClauseEnum.more,
                ]:
                    n.is_active = False
                    _ = get_translations(n.chat.locale)
                    text_to += "\n"
                    text_to += _("_One-time reminder. Set up a new reminder._")

                send_notification.delay(n.chat_id, text_to)

                n.last_rate = prr.rate

                transaction.commit()
예제 #27
0
def convert(price_request: PriceRequest) -> PriceRequestResult:
    if price_request.currency == price_request.to_currency:
        return PriceRequestResult(
            price_request=price_request,
            exchanges=["Baba Vanga"],
            rate=Decimal("1"),
            last_trade_at=datetime(1996, 8, 11),
        )

    if price_request.amount == 0:
        return PriceRequestResult(
            price_request=price_request,
            exchanges=["Baba Vanga"],
            rate=Decimal("0"),
            last_trade_at=datetime(1996, 8, 11),
        )

    from_currency = Session.query(Currency).filter_by(
        code=price_request.currency).one()
    to_currency = (Session.query(Currency).filter_by(
        code=price_request.to_currency).one())

    rate_obj = (Session.query(Rate).filter_by(
        from_currency=from_currency,
        to_currency=to_currency).join(Exchange).filter(
            Exchange.is_active == sa.true()).order_by(sa.desc(
                Exchange.weight)).first())

    if rate_obj:
        price_request_result = PriceRequestResult(
            price_request=price_request,
            exchanges=[rate_obj.exchange.name],
            rate=rate_obj.rate,
            rate_open=rate_obj.rate_open,
            last_trade_at=rate_obj.last_trade_at,
            low24h=rate_obj.low24h,
            high24h=rate_obj.high24h,
        )

    else:
        rate0_model = orm.aliased(Rate)
        rate1_model = orm.aliased(Rate)
        exchange0_model = orm.aliased(Exchange)
        exchange1_model = orm.aliased(Exchange)

        rate_obj = (Session.query(
            rate0_model,
            rate1_model,
            (exchange0_model.weight + exchange1_model.weight).label("w"),
        ).filter_by(from_currency=from_currency).join(
            rate1_model,
            sa.and_(
                rate1_model.from_currency_id == rate0_model.to_currency_id,
                rate1_model.to_currency == to_currency,
            ),
        ).join(
            exchange0_model,
            sa.and_(
                exchange0_model.id == rate0_model.exchange_id,
                exchange0_model.is_active == sa.true(),
            ),
        ).join(
            exchange1_model,
            sa.and_(
                exchange1_model.id == rate1_model.exchange_id,
                exchange1_model.is_active == sa.true(),
            ),
        ).order_by(sa.desc("w")).first())

        if rate_obj:
            rate = combine_values(rate_obj[0].rate, rate_obj[1].rate)
            rate_open = combine_values(rate_obj[0].rate_open,
                                       rate_obj[1].rate_open)
            low24h = high24h = None

            price_request_result = PriceRequestResult(
                price_request=price_request,
                exchanges=[
                    rate_obj[0].exchange.name, rate_obj[1].exchange.name
                ],
                rate=rate,
                rate_open=rate_open,
                last_trade_at=min(rate_obj[0].last_trade_at,
                                  rate_obj[1].last_trade_at),
                low24h=low24h,
                high24h=high24h,
            )

        else:
            raise NoRatesException

    check_overflow(price_request_result)

    return price_request_result