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
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
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()
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
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, )
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, _)
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() )
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, )
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()
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
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
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)
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()
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
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()
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]
def have_last_request(chat_id): return Session.query(ChatRequests).filter_by(chat_id=chat_id).first()
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)
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()
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()
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