def handle_help(self, bot: TeleBot, message: Message): help_text = """ Help for this bot: /help: Show this help. /subscribe: Subscribe this chat. /subscriptions: Get all subscribed chats. /start: Start the bot. /unsubscribe: Unsubscribe this chat. /welcome: Get the welcome message """ logger.debug('Sending help reply to user.') bot.reply_to(message, help_text)
def handle_welcome(self, bot: TeleBot, message: Message): from_user = message.from_user if (type(from_user) is ChatMember): first_name = from_user.user.first_name last_name = from_user.user.last_name else: first_name = from_user.first_name last_name = from_user.last_name welcome_msg = 'Welcome! {0} {1}'.format(first_name, last_name) logger.debug('Sending welcome reply to user: {}'.format(welcome_msg)) bot.reply_to(message, welcome_msg) logger.debug('Calling send_help function.') self.handle_help(bot, message)
class TelegramBot: def __init__(self, token: str, mongo: MongoDB): self._mongo = mongo self._bot = TeleBot(token) @self._bot.message_handler(commands=['start', 'help']) def send_welcome(message): try: self._mongo.update_telegram_id(message.chat.username, message.chat.id) self._bot.reply_to(message, "Howdy, how are you doing?") except Exception as e: self._bot.reply_to(message, str(e)) def set_bot(self, bot: TeleBot): self._bot = bot @self._bot.message_handler(commands=['start', 'help']) def send_welcome(message): try: self._mongo.update_telegram_id(message.chat.username, message.chat.id) self._bot.reply_to(message, "Howdy, how are you doing?") except Exception as e: self._bot.reply_to(message, str(e)) def get_bot(self): return self._bot def close(self): self._bot.stop_polling() self._bot.stop_bot() def start(self): self._bot.polling() def send_message(self, recipient: int, message: str): self._bot.send_message(recipient, message)
class Bot: """control bot""" SUCCESS_USERS_ID = [] def __init__(self): self.__bot = TeleBot(API_token) self.__parser = Parser() @self.__bot.message_handler(commands=["help"]) def __show_help(message): """show info about bot through /help command""" if message.from_user.id in self.SUCCESS_USERS_ID: self.__bot.send_message( message.chat.id, "Bot for getting info about science videos on Youtube. For start work write /parse.", ) else: self.__bot.send_message( message.chat.id, "Bot for getting info about science videos on Youtube. For more info, auth through /start.", ) @self.__bot.message_handler(commands=["start"]) def __start_work(message): """start interaction with user and check his status""" if message.from_user.id in self.SUCCESS_USERS_ID: self.__bot.send_message(message.chat.id, "Access is allowed!") else: message_from_user = self.__bot.reply_to( message, """Entry password: """) self.__bot.register_next_step_handler(message_from_user, __auth_to_bot) def __auth_to_bot(message): """checks the password for correctness""" if message.text == password: self.__bot.send_message(message.chat.id, "Access is allowed! Choose command.") self.SUCCESS_USERS_ID.append(message.from_user.id) else: self.__bot.send_message(message.chat.id, "Access denied! Try again /start.") @self.__bot.message_handler(commands=["parse"]) def __parsing(message): """start parsing and show result""" if message.from_user.id in self.SUCCESS_USERS_ID: self.__bot.send_message(message.chat.id, "Parsing start. Please, await!") info_about_videos = self.__get_info_about_videos() for info_about_video in info_about_videos: self.__bot.send_message( message.chat.id, f"Title: {info_about_video['title']}") self.__bot.send_message( message.chat.id, f"Views: {info_about_video['views']}") self.__bot.send_message( message.chat.id, f"Date: {info_about_video['date']}") self.__bot.send_message( message.chat.id, f"Like_bar: {info_about_video['like_bar']}") self.__bot.send_message( message.chat.id, f"Channel: {info_about_video['channel']}") self.__bot.send_message( message.chat.id, f"Link: {info_about_video['link']}") self.__bot.send_message(message.chat.id, "-" * 10) sleep(3) else: self.__bot.send_message(message.chat.id, "Access denied! Try again /start.") def __get_info_about_videos(self): """return parsing result""" return self.__parser.start_parse() def start_bot(self): """start bot working""" self.__bot.polling()
class TgBot(threading.Thread): def loadCommandConfig(self): pass def loadPersonConfig(self): persons = {} return persons def __init__(self, config, admins, tunnels, loopDelay=0.1, debug=False): super(TgBot, self).__init__() self.debug = debug self.stopped = Event() self.loopDelay = loopDelay self.admins = admins self.tunnels = tunnels self.config = config self.botUserName = config['botUserName'] self.bot = TeleBot(config['token']) self.commands = [ # AddAdmin(bot=self, cmd=['add-admin']), # Alarm('alarm', time.time()) Bind(bot=self, cmd=['b', 'bind']), ListBind(bot=self, cmd=['lb', 'listbind', 'list-bind']), Toggle(bot=self, cmd=['t', 'toggle']), ListToggle(bot=self, cmd=['lt', 'listtoggle', 'list-toggle']), UnBind(bot=self, cmd=['ub', 'unbind', 'un-bind']) ] self.persons = self.loadPersonConfig() def sendMessage(self, _id, _msg, parse_mode=None): self.bot.send_message(chat_id=_id, text=_msg, parse_mode=parse_mode) def replyTo(self, msg, _msg): self.bot.reply_to(msg, _msg) def listbind_handler(self, message): print(message) def handler(self, msg): for each in msg: try: _from = each.from_user _chat = each.chat if each.text and each.text.startswith('#'): _text = each.text for tunnel in getMatchTunnels(self.tunnels, tgId=_chat.id): if tunnel.tg['toggle']: name = _from.username or _from.first_name or _from.last_name message = '[Anonymous]: {0}'.format( _text[2:]) if _text.startswith( '##') else '[{0}]: {1}'.format( name, _text[1:]) tunnel.tk['queue'].put(message) if self.debug: print(each) except Exception as e: if self.debug: traceback.print_exc() def queueHandler(self): while not self.stopped.wait(self.loopDelay): for each in self.tunnels: tg = each.tg while not tg['queue'].empty() and tg['toggle']: chatId = tg['id'] msg = tg['queue'].get() try: if '<img' in msg: link = re.search('src=\".*?\"', msg).group(0)[5:-1] if '.gif' in msg: self.bot.send_document(chatId, link) else: self.bot.send_photo(chatId, link) elif '</' in msg: self.bot.send_message(chatId, msg, parse_mode='HTML') else: self.bot.send_message(chatId, msg, parse_mode='Markdown') except Exception as e: self.bot.send_message(chatId, msg) if self.debug: traceback.print_exc() def start(self): super(TgBot, self).start() for cmd in self.commands: cmd.register() self.bot.set_update_listener(self.handler) thread = threading.Thread(target=self.queueHandler) thread.start() def run(self): while not self.stopped.wait(self.loopDelay): try: self.bot.polling(none_stop=False) except: if self.debug: traceback.print_exc() def stop(self): self.bot.stop_bot() self.stopped.set()
class BOT: index = 0 count_films = utils.STEP config_file_name = 'config.json' def __init__(self): self.cfg = self.JSON() self.construct() def construct(self): self.bot = TeleBot(self.cfg['token']) self.commands = { "List films": utils.call(self.manage), "Unsubscribe": utils.call(self.del_user), } self.film_view_markup = { "back": utils.call(self.revert), "open": utils.call(lambda *x, **b:...), } self.markup = { "1": utils.call(self.film, 0), "2": utils.call(self.film, 1), "3": utils.call(self.film, 2), "4": utils.call(self.film, 3), "5": utils.call(self.film, 4), "6": utils.call(self.film, 5), "7": utils.call(self.film, 6), "8": utils.call(self.film, 7), "9": utils.call(self.film, 8), "10": utils.call(self.film, 9), "prev": utils.call(self.prev), "next": utils.call(self.next), } @self.bot.message_handler( commands=['subscribe', 'unsubscribe', 'categories', 'find']) def subscribe_detector(message): try: if "/subscribe" in message.text: category = message.text.split("/subscribe")[-1].strip() if category in list(self.cfg['categories'].keys()): uinfo = (message.chat.id, category) db = utils.DATABASE(self.cfg["db_name"]) db.insert(table_name="subscribe_categories", args=uinfo) self.bot.reply_to( message, self.cfg["subscribe_category"].replace( "{}", category)) else: self.bot.send_message(message.chat.id, self.cfg["category_not_found"]) elif "/unsubscribe" in message.text: category = message.text.split("/unsubscribe")[-1].strip() if category in self.cfg['categories']: db = utils.DATABASE(self.cfg["db_name"]) db.delete(table_name="subscribe_categories", where=" chat_id = " + str(message.chat.id)) self.bot.reply_to( message, self.cfg["unsibscribe_category"].replace( "{}", category)) else: self.bot.send_message(message.chat.id, self.cfg["category_not_found"]) elif "/find" in message.text: film_name = message.text.split("/find")[-1].strip() finded = utils.load_films( db_name=self.cfg['db_name'], find_perc=[film_name, self.cfg['film_perc']]) finded.extend( utils.load_films(db_name=self.cfg['db_name'], find=film_name)) message_ = "" for i, film in enumerate(finded): message_ += "{}. {}\n{}\n\n".format( i + 1, film[1], film[3]) messages_ = [] if len(message_) > 2800: count = int(len(message_) / 2800) s = 0 for i in range(count): ns = s + 2800 if len(message_) < ns: ns = -1 msg = message_[s:ns] messages_.append(msg) s = ns if not ns: break else: messages_.append(message_) for msg in messages_: if len(msg) > 2: self.bot.send_message(message.chat.id, msg) else: self.bot.send_message(message.chat.id, self.cgf['no_film_error']) elif "/categories" in message.text: msg = "" for i, key in enumerate(list(self.cfg["categories"])): msg += "{} : {}\n".format(i, key) self.bot.send_message( message.chat.id, self.cfg['categories_text'].replace("{}", msg)) except Exception as e: # utils.log(e) raise @self.bot.callback_query_handler(func=lambda call: True) def callback_query(call): try: if call.data in self.markup.keys(): self.markup[call.data](call) elif call.data in self.film_view_markup.keys(): self.film_view_markup[call.data](call) except Exception as e: utils.log(e) @self.bot.message_handler(func=lambda x: True) def main(message): try: utils.add_user(message=message, db_name=self.cfg['db_name']) if message.text in self.commands: self.commands[message.text](message) else: self.bot.send_message(message.chat.id, text=self.cfg['base_message'], reply_markup=utils.repl_markup( list(self.commands.keys()))) except Exception as e: utils.log(e) def JSON(self): return utils.loads(open(self.config_file_name).read()) def del_user(self, message): self.bot.send_message(message.chat.id, self.cfg["global_unsubscribe"]) utils.delete_subscriber(message.chat.id, db_name=self.cfg['db_name']) def manage(self, message, text=None): self.bot.send_message( message.chat.id, text=utils.new_message( msg=text if text else self.cfg['default_message'], db_name=self.cfg["db_name"], repl=self.cfg['message_repl']), reply_markup=utils.inline_markup(self.markup)) def revert(self, call): id = call.message.chat.id messages = utils.new_message(index=self.index, msg=self.cfg['default_message'], repl=self.cfg['message_repl'], db_name=self.cfg["db_name"]) self.bot.delete_message(id, call.message.message_id) self.bot.send_message(id, text=messages, reply_markup=utils.inline_markup(self.markup)) self.bot.answer_callback_query(call.id, "Success") def send_all(self, msg): mkp = utils.repl_markup(utils.START_MARKUP) for id in utils.get_all_subscribers_id(db_name=self.cfg["db_name"]): try: self.bot.send_message(id, msg, reply_markup=mkp) except Exception as e: utils.delete_subscriber(id, db_name=self.cfg["db_name"]) def film(self, call, id=0): id_ = call.message.chat.id db = utils.DATABASE(self.cfg['db_name']) film_info = db.read(table_name="films", where=" id = '{}'".format( utils.class_db.film_list[id][1]))[0] db.close() def download(): utils.image_download(film_info[2]) t = Thread(target=download) t.start() t.join() self.bot.delete_message(id_, call.message.message_id) self.bot.send_photo( chat_id=id_, photo=open(".image.jpg", "rb"), reply_markup=utils.inline_markup(self.film_view_markup), caption=utils.render_film_info(film_info[0], db_name=self.cfg["db_name"])) def next(self, call): utils.class_db.index += utils.STEP messages = utils.new_message(msg=self.cfg['default_message'], repl=self.cfg['message_repl'], index=utils.class_db.index, db_name=self.cfg["db_name"]) self.bot.edit_message_text(text=messages, chat_id=call.message.chat.id, message_id=call.message.message_id, reply_markup=utils.inline_markup( self.markup), inline_message_id=call.inline_message_id) def prev(self, call): utils.class_db.index -= utils.STEP if utils.class_db.index < 0: utils.class_db.index = 0 self.bot.answer_callback_query(call.id, self.cfg['no_films_error']) return True message = utils.new_message(msg=self.cfg['default_message'], repl=self.cfg['message_repl'], index=utils.class_db.index, db_name=self.cfg["db_name"]) self.bot.edit_message_text(text=message, chat_id=call.message.chat.id, message_id=call.message.message_id, reply_markup=utils.inline_markup( self.markup), inline_message_id=call.inline_message_id) def start(self, msg=True): if msg: self.send_all(self.cfg["start_message"]) utils.new_message(msg=self.cfg['default_message'], repl=self.cfg['message_repl'], db_name=self.cfg["db_name"]) self.bot.polling() def stop(self): self.bot.stop_polling()
class BotTelegram(object): def __init__(self, token, proxy): apihelper.proxy = { 'https': 'socks5://' + proxy['LOGIN'] + ':' + proxy['PSW'] + '@' + proxy['IP'] + ':' + proxy['PORT'] } self.bot = TeleBot(token) def start_and_work(self): # Actions after /start command @self.bot.message_handler(commands=['start']) def send_welcom(message): self.markup = types.ReplyKeyboardMarkup(resize_keyboard=True) self.markup.row(botText.NEW_TEAM, botText.SIGNIN_TEAM) answer = self.bot.send_message(message.from_user.id, botText.START, reply_markup=self.markup) self.bot.register_next_step_handler(answer, process_choose_sigin_step) database.register_user(message.chat) # Actions after choice: new team OR choose team def process_choose_sigin_step(message): markup = types.ReplyKeyboardRemove(selective=False) if message.text == botText.NEW_TEAM: answer = self.bot.send_message(message.from_user.id, botText.NAME_TEAM, reply_markup=markup) self.bot.register_next_step_handler(answer, process_name_command_step) elif message.text == botText.SIGNIN_TEAM: self.bot.send_message(message.from_user.id, botText.CHOOSE_TEAM, reply_markup=markup) team_list = database.take_teams() title_team_buttons = types.InlineKeyboardMarkup() for title in team_list: butt = types.InlineKeyboardButton(text=title, callback_data=title) title_team_buttons.add(butt) answer = self.bot.send_message(message.chat.id, "Список команд:", reply_markup=title_team_buttons) self.bot.register_next_step_handler(answer, callback_inline) else: self.bot.reply_to(message, botText.ERROR) @self.bot.callback_query_handler(func=lambda call: True) def callback_inline(call): try: print(call.message.chat.id, call.data) database.choose_team(call.message.chat.id, call.data) answer = self.bot.send_message(chat_id=call.message.chat.id, text="Введи пароль:") self.bot.register_next_step_handler(answer, process_login_step) except: self.bot.send_message(call.chat.id, botText.ERROR_PSW) # Enter team name def process_name_command_step(message): database.register_team(message.from_user.id, message.text) answer = self.bot.send_message(message.from_user.id, 'Придумай простой пароль:') self.bot.register_next_step_handler(answer, process_password_command_step) # Enter team password def process_password_command_step(message): database.inputPSW_team(message.from_user.id, message.text) self.bot.send_message(message.from_user.id, botText.REG_TEAM) # Team authorization def process_login_step(message): if database.login_team(message.chat.id, message.text): self.bot.send_message(message.from_user.id, botText.LOGIN_TEAM) else: database.clear_team_for_user(message.chat.id) self.bot.send_message(message.from_user.id, botText.ERROR_PSW) # Actions after /coords command @self.bot.message_handler(commands=['coords']) def send_message(message): answer = self.bot.send_message(message.from_user.id, botText.POINT) self.bot.register_next_step_handler(answer, process_adding_coords) # Enter coordinates. If form doesn't correct, enter again def process_adding_coords(message): result = database.check_register(message.chat.id) if result == None: self.bot.send_message(message.from_user.id, botText.ERROR_REG) print('reg_error from:'+' '+message.chat.id) else: result = database.coordinates_verify(message.chat.id) key_tuple = (float(message.text.split(', ')[0]), float(message.text.split(', ')[1])) if key_tuple in result: self.bot.send_message(message.from_user.id, botText.WRONG_POINT) else: if re.match(r'\d*.\d*, \d*.\d*', message.text): database.input_point(message.from_user.id, message.text.split(', ')) answer = self.bot.send_message(message.from_user.id, botText.HEIGHT) self.bot.register_next_step_handler(answer, process_adding_height) elif message.entities: # Приостанавливаем ввод координат, если введена любая команда self.bot.send_message(message.from_user.id, botText.WHATSUP) else: answer = self.bot.send_message(message.from_user.id, botText.TRY_AGAIN) self.bot.register_next_step_handler(answer, process_adding_coords) # Enter point height def process_adding_height(message): database.input_height(message.from_user.id, message.text) self.bot.send_message(message.from_user.id, botText.SAVE_POINT) @self.bot.message_handler(commands=['showpoints']) def show_points(message): coords_list = database.show_points(message.chat.id) coords_buttons = types.InlineKeyboardMarkup() for array in coords_list: string = str(array[0]) + ', ' + str(array[1]) + ', ' + str(array[2]) butt = types.InlineKeyboardButton(text=string, callback_data=str(array[3])) coords_buttons.add(butt) self.bot.send_message(message.chat.id, "Список точек:", reply_markup=coords_buttons) self.bot.polling(none_stop=True)
class ColorCodeBot: def __init__(self, api_key: str, lang: Mapping[str, str], theme_image_ids: tuple[str], keyboards: Mapping[str, InlineKeyboardMarkup], guesslang_syntaxes: Mapping[str, str], *args: Any, admin_chat_id: Optional[str] = None, db_path: str = str( local.path(__file__).up() / 'user_themes.sqlite'), **kwargs: Any): self.lang = lang self.theme_image_ids = theme_image_ids self.kb = keyboards self.guesslang_syntaxes = guesslang_syntaxes self.admin_chat_id = admin_chat_id self.db_path = db_path self.user_themes = KeyValue(key_field=IntegerField(primary_key=True), value_field=CharField(), database=APSWDatabase(db_path)) self.log = mk_logger() self.bot = TeleBot(api_key, *args, **kwargs) self.register_handlers() self.guesser = Guess() def register_handlers(self): self.welcome = self.bot.message_handler(commands=['start', 'help'])( self.welcome) self.browse_themes = self.bot.message_handler( commands=['theme', 'themes'])(self.browse_themes) self.mk_theme_previews = self.bot.message_handler( commands=['previews'])(self.mk_theme_previews) self.intake_snippet = self.bot.message_handler( func=lambda m: m.content_type == 'text')(self.intake_snippet) self.recv_photo = self.bot.message_handler(content_types=['photo'])( self.recv_photo) self.restore_kb = self.bot.callback_query_handler( lambda q: yload(q.data)['action'] == 'restore')(self.restore_kb) self.set_snippet_filetype = self.bot.callback_query_handler( lambda q: yload(q.data)['action'] == 'set ext')( self.set_snippet_filetype) self.set_theme = self.bot.callback_query_handler( lambda q: yload(q.data)['action'] == 'set theme')(self.set_theme) self.send_photo_elsewhere = self.bot.inline_handler( lambda q: q.query.startswith("img "))(self.send_photo_elsewhere) self.switch_from_inline = self.bot.inline_handler(lambda q: True)( self.switch_from_inline) @retry def switch_from_inline(self, inline_query: InlineQuery): self.log.msg("receiving inline query", user_id=inline_query.from_user.id, user_first_name=inline_query.from_user.first_name, query=inline_query.query) self.bot.answer_inline_query( inline_query.id, [], switch_pm_text=self.lang['switch to direct'], switch_pm_parameter='x') @retry def welcome(self, message: Message): self.log.msg("introducing myself", user_id=message.from_user.id, user_first_name=message.from_user.first_name, chat_id=message.chat.id) self.bot.reply_to( message, self.lang['welcome'], reply_markup=ForceReply( input_field_placeholder=self.lang['input field placeholder'])) @retry def mk_theme_previews(self, message: Message): if not self.admin_chat_id or str( message.chat.id) != self.admin_chat_id: self.log.msg("naughty preview attempt", user_id=message.from_user.id, user_first_name=message.from_user.first_name, chat_id=message.chat.id, admin_chat_id=self.admin_chat_id) return sample_code = dedent(""" # palinDay :: Int -> [ISO Date] def palinDay(y): '''A possibly empty list containing the palindromic date for the given year, if such a date exists. ''' s = str(y) r = s[::-1] iso = '-'.join([s, r[0:2], r[2:]]) try: datetime.strptime(iso, '%Y-%m-%d') return [iso] except ValueError: return [] """) for button in chain.from_iterable(self.kb['theme'].keyboard): theme = button.text html = mk_html(f"# {theme}{sample_code}", 'py', theme) with local.tempdir() as folder: png_path = mk_png(html, folder=folder) send_image(bot=self.bot, chat_id=message.chat.id, png_path=png_path, reply_msg_id=message.message_id) @retry def browse_themes(self, message: Message): self.log.msg("browsing themes", user_id=message.from_user.id, user_first_name=message.from_user.first_name, chat_id=message.chat.id) albums = [ self.theme_image_ids[i:i + 10] for i in range(0, len(self.theme_image_ids), 10) ] for album in albums: self.bot.send_media_group(message.chat.id, map(InputMediaPhoto, album), reply_to_message_id=message.message_id) self.bot.reply_to(message, self.lang['select theme'], reply_markup=self.kb['theme']) @retry def set_theme(self, cb_query: CallbackQuery): data = yload(cb_query.data) user = cb_query.message.reply_to_message.from_user self.log.msg("setting theme", user_id=user.id, user_first_name=user.first_name, theme=data['theme'], chat_id=cb_query.message.chat.id) self.bot.edit_message_reply_markup(cb_query.message.chat.id, cb_query.message.message_id, reply_markup=minikb('theme')) self.user_themes[user.id] = data['theme'] self.bot.answer_callback_query( cb_query.id, text=self.lang['acknowledge theme'].format(data['theme'])) if self.admin_chat_id: with open(self.db_path, 'rb') as doc: self.bot.send_document(self.admin_chat_id, doc) def guess_ext(self, code: str, probability_min: float = .12) -> Optional[str]: syntax, probability = self.guesser.probabilities(code)[0] ext = self.guesslang_syntaxes.get(syntax) self.log.msg("guessed syntax", probability_min=probability_min, probability=probability, syntax=syntax, ext=ext) if probability >= probability_min: return ext for start, ext in { '{': 'json', '---\n': 'yaml', '[[': 'toml', '[': 'ini', '<?php': 'php', '<': 'xml', '-- ': 'lua' }.items(): if code.startswith(start): return ext @retry def intake_snippet(self, message: Message): self.log.msg("receiving code", user_id=message.from_user.id, user_first_name=message.from_user.first_name, chat_id=message.chat.id) ext = self.guess_ext(message.text) if ext: kb_msg = self.bot.reply_to( message, f"{self.lang['query ext']}\n\n{self.lang['guessed syntax'].format(ext)}", reply_markup=minikb('syntax', self.lang['syntax picker']), parse_mode='Markdown', disable_web_page_preview=True) self.set_snippet_filetype(cb_query=None, query_message=kb_msg, ext=ext) else: self.bot.reply_to(message, self.lang['query ext'], reply_markup=self.kb['syntax'], parse_mode='Markdown', disable_web_page_preview=True) @retry def send_photo_elsewhere(self, inline_query: InlineQuery): file_id = inline_query.query.split('img ', 1)[-1] self.log.msg("creating inline query result", file_id=file_id, file_info=self.bot.get_file(file_id)) self.bot.answer_inline_query(inline_query.id, [ InlineQueryResultCachedPhoto( id=str(uuid4()), photo_file_id=file_id, title="Send Image") ], is_personal=True) @retry def restore_kb(self, cb_query: CallbackQuery): data = yload(cb_query.data) self.bot.edit_message_reply_markup( cb_query.message.chat.id, cb_query.message.message_id, reply_markup=self.kb[data['kb_name']]) self.bot.answer_callback_query(cb_query.id) @retry def set_snippet_filetype(self, cb_query: Optional[CallbackQuery] = None, query_message: Optional[Message] = None, ext: Optional[str] = None): if cb_query: query_message = cb_query.message ext = yload(cb_query.data)['ext'] elif not (query_message and ext): raise Exception( "Either cb_query or both query_message and ext are required") self.log.msg("colorizing code", user_id=query_message.reply_to_message.from_user.id, user_first_name=query_message.reply_to_message.from_user. first_name, syntax=ext, chat_id=query_message.chat.id) if cb_query: self.bot.edit_message_reply_markup(query_message.chat.id, query_message.message_id, reply_markup=minikb( 'syntax', self.lang['syntax picker'])) snippet = query_message.reply_to_message theme = self.user_themes.get(snippet.from_user.id, 'base16/gruvbox-dark-hard') html = mk_html(snippet.text, ext, theme) send_html(bot=self.bot, chat_id=snippet.chat.id, html=html, reply_msg_id=snippet.message_id) with local.tempdir() as folder: png_path = mk_png(html, folder=folder) did_send = False if len(snippet.text.splitlines()) <= 30: try: photo_msg = send_image(bot=self.bot, chat_id=snippet.chat.id, png_path=png_path, reply_msg_id=snippet.message_id) except ApiException as e: self.log.error("failed to send compressed image", exc_info=e, chat_id=snippet.chat.id) else: did_send = True kb_to_chat = InlineKeyboardMarkup() kb_to_chat.add( InlineKeyboardButton( self.lang['send to chat'], switch_inline_query= f"img {photo_msg.photo[-1].file_id}")) self.bot.edit_message_reply_markup(photo_msg.chat.id, photo_msg.message_id, reply_markup=kb_to_chat) if not did_send: send_image(bot=self.bot, chat_id=snippet.chat.id, png_path=png_path, reply_msg_id=snippet.message_id, compress=False) if cb_query: self.bot.answer_callback_query(cb_query.id) def recv_photo(self, message: Message): self.log.msg('received photo', file_id=message.photo[0].file_id, user_id=message.from_user.id, user_first_name=message.from_user.first_name, chat_id=message.chat.id)
class TelegramBot: def __init__(self, work_dir: Path, token: str): self.last_requests = {} # type: Dict[int, List[datetime]] self.work_dir = work_dir self.work_dir.mkdir(0o755, parents=True, exist_ok=True) self.token = token self.bot = TeleBot(token) self.bot.add_message_handler({ 'function': self.process_link, 'filters': { 'regexp': r'https?:\/\/.+\..+' } }) self.bot.add_message_handler({ 'function': self.process_message, 'filters': { 'func': lambda _: True } }) def process_link(self, message: Message): user_id = message.from_user.id if user_id not in self.last_requests: self.last_requests[user_id] = [] limit_req, limit_min = get_rate_limit() now = datetime.now(tz=timezone.utc) last_requests = [] for dt in self.last_requests[user_id]: time_passed = now - dt if time_passed.min < timedelta(minutes=limit_min): last_requests.append(dt) self.last_requests[user_id] = last_requests if len(self.last_requests[user_id]) > limit_req: next_available = self.last_requests[user_id][0] + timedelta( minutes=limit_min) - now self.bot.reply_to( message, f'Rate limited. Try again in {next_available.seconds} seconds') return else: self.last_requests[user_id].append(now) msg = self.bot.reply_to(message, "Link detected, processing") # detach from telebot update thread def download(): try: with YoutubeDL({'noplaylist': True}) as ydl: info = ydl.extract_info(message.text, download=False) if info['duration'] > 900: self.bot.edit_message_text('Source too long', message_id=msg.id, chat_id=msg.chat.id) return with YoutubeDL(get_options_audio(self.work_dir)) as ydl: ydl.add_post_processor( UploadHandler(self.bot, msg.id, msg.chat.id)) self.bot.edit_message_text('Downloading', message_id=msg.id, chat_id=msg.chat.id) ydl.download([message.text]) except UnsupportedError: self.bot.edit_message_text('Unsupported URL', message_id=msg.id, chat_id=msg.chat.id) except DownloadError: self.bot.edit_message_text('Download error', message_id=msg.id, chat_id=msg.chat.id) except Exception as e: self.bot.edit_message_text('Unknown error', message_id=msg.id, chat_id=msg.chat.id) logger.error('Unknown error', exc_info=e) reactor.callInThread(lambda: download()) def process_message(self, message: Message): self.bot.reply_to(message, 'Send me a link') def polling(self): self.bot.delete_webhook() self.bot.polling(long_polling_timeout=5) def stop_polling(self): self.bot.stop_polling() def set_webhook(self, host: str, port: int): cert_path, pkey_path = get_or_create_root_cert(self.work_dir, host) self.bot.remove_webhook() self.bot.set_webhook(url=f'https://{host}:{port}/{self.token}/', certificate=open(cert_path, 'r')) bot = self.bot class WebhookHandler(Resource): isLeaf = True def render_POST(self, request): request_body_dict = json.load(request.content) update = Update.de_json(request_body_dict) reactor.callInThread(lambda: bot.process_new_updates([update])) return b'' root = ErrorPage(403, 'Forbidden', '') root.putChild(self.token.encode(), WebhookHandler()) site = Site(root) sslcontext = ssl.DefaultOpenSSLContextFactory(str(pkey_path), str(cert_path)) reactor.listenSSL(port, site, sslcontext)
class RadBot: lastUnlockTrigger = 1613408400 nextUnlockTarget = 0.17 def __init__(self, token=RADBOT_TOKEN): self.telegram = TeleBot(token) self.portfolio = RadixPortfolio([]) self.trender = RewardTrender() self.uniswap = UniswapInfo() def getSMA(self): req = requests.get('http://api.coingecko.com/api/v3/coins/e-radix/market_chart?vs_currency=usd&days=7&interval=hourly') prices = pd.DataFrame(req.json()['prices']) prices = prices[prices[0]>1000*self.lastUnlockTrigger] return prices[1].mean() def nextUnlock(self): t = time.time() self.updatePrice() SMA = self.getSMA() msg = f"Current spot price: {round(self.price,4)} USDC/eXRD\n" timeLeft = (self.lastUnlockTrigger + 60*60*24*7) - t if timeLeft < 0: msg += "Minimum time to next unlock has passed.\n" else: days, r = divmod(timeLeft, 60*60*24) hours, r = divmod(r, 60*60) minutes = int(r/60) msg += f"Minimum time to next unlock: {int(days)}d, {int(hours)}h, {int(minutes)}m.\n" msg += f"Next unlock SMA target: {self.nextUnlockTarget} $\n" msg += f"Current CoinGecko SMA: {round(SMA,4)} $" return msg def updatePrice(self): getReserves = poolContract.functions.getReserves() (pool_eXRD, pool_USDC, t) = getReserves.call() self.pool_eXRD = pool_eXRD self.pool_USDC = pool_USDC self.price = self.pool_USDC*1e12/self.pool_eXRD return self.price def calcMarketCap(self): self.lockedAmount = 0 for l in locked: balanceOf = eXRD_Contract.functions.balanceOf(l) self.lockedAmount += balanceOf.call() totalSupply = eXRD_Contract.functions.totalSupply() self.supply = totalSupply.call() self.unlocked = self.supply - self.lockedAmount self.updatePrice() self.mcap = self.price*(self.unlocked)/1e18 SMA7 = self.getSMA() msg = f"Current spot price: {round(self.price,4)} USDC/eXRD\n" msg += f"Current 7-day SMA: ${round(SMA7,4)}\n" msg += f"Current Market Cap: {round(self.mcap/1e6,2)} MM USDC\n" msg += f"Percentage in LP: {round(100*self.pool_eXRD/self.unlocked,2)}%" return msg def analyseWallets(self, wallets, colors=False): try: self.portfolio = RadixPortfolio(wallets) except Exception as e: print('Failed to retrieve wallet information: ' + repr(e)) return "Failed to retrieve wallet information" try: uni_balances = self.uniswap.getBalances(wallets) except Exception as e: print('Failed to load Uniswap Info: ' + repr(e)) return "Failed to load Uniswap Info" LPs = self.portfolio.assets['naked LP'].sum()+self.portfolio.assets['staked LP'].sum() poolShare = LPs / self.portfolio.totalLPs pooled_USDC = poolShare * self.portfolio.pool_USDC / 1e6 pooled_eXRD = poolShare * self.portfolio.pool_eXRD / 1e18 totalRewards = self.portfolio.assets.rewards.sum() msg = "Analysis of requested wallet(s)\n" msg += f"Unstaked USDC: {round(self.portfolio.assets.USDC.sum()/1e6,2)}\n" msg += f"Unstaked eXRD: {round(self.portfolio.assets.eXRD.sum()/1e18,2)}\n" msg += f"Pooled USDC: {round(pooled_USDC,2)}\n" msg += f"Pooled eXRD: {round(pooled_eXRD,2)}\n" msg += f"Total Rewards: {round(totalRewards,2)}\n" msg += "--------------------------------+\n" msg += f"Total value: {round(self.portfolio.assets.value.sum(),2)} USDC" t = pd.Timestamp.now() trendDF = self.trender.calcRewardsOverTime(self.portfolio.stakes) d_columns = [c for c in trendDF.columns if 'donated ' in c] donated = trendDF[d_columns].sum(axis=1) donated = donated.groupby(donated.index).last() donated[t] = np.NaN donated = donated.sort_index().interpolate(method='polynomial',order=2)[t] msg += f"\n\nRewards mined through staking: {round(totalRewards-donated,2)}" msg += f"\nRewards donated by early leavers: {round(donated,2)}" if len(uni_balances): fees_USDC = 0 fees_eXRD = 0 ROI_pool = 0 ROI_HODL = 0 invested = 0 for balance in uni_balances: price_change_factor = self.portfolio.pool_USDC / self.portfolio.pool_eXRD / balance['USDC'] * balance['eXRD'] growth_factor = sqrt(price_change_factor) stake_share = balance['LP'] / self.portfolio.totalLPs current_USDC = stake_share * self.portfolio.pool_USDC / 1e6 current_eXRD = stake_share * self.portfolio.pool_eXRD / 1e18 initial_USDC = balance['USDC'] / 1e6 initial_eXRD = balance['eXRD'] / 1e18 expected_USDC = initial_USDC * growth_factor expected_eXRD = initial_eXRD / growth_factor fees_USDC += current_USDC - expected_USDC fees_eXRD += current_eXRD - expected_eXRD invested += 2 * initial_USDC ROI_pool += 2 * expected_USDC - 2 * initial_USDC ROI_HODL += initial_eXRD * (current_USDC / current_eXRD - initial_USDC / initial_eXRD) rewardsValue = totalRewards * self.portfolio.spot_price totalROI = rewardsValue + ROI_pool + 2*fees_USDC msg += f"\n\nUnclaimed reward value: {round(rewardsValue,2)} USDC" msg += f"\nUniSwap fees value: {round(2 * fees_USDC, 2)} USDC" msg += f"\nPool ROI (ex fees): {round(ROI_pool,2)} USDC" msg += f"\nTotal LP+LM ROI: {round(totalROI,2)} USDC ({round(100 * totalROI / invested, 1)}%)" msg += f"\n\nROI if you had HODL'd: {round(ROI_HODL,2)} USDC ({round(100 * ROI_HODL / invested, 1)}%)" msg += f"\nROI if you had YOLO'd: {round(2*ROI_HODL,2)} USDC ({round(200 * ROI_HODL / invested, 1)}%)" if len(self.portfolio.stakes): msg += "\n\nStaking details:" for i in range(len(self.portfolio.stakes)): stake = self.portfolio.stakes.iloc[i] age = round((stake.t1 - stake.t0) / (60 * 60 * 24), 2) if colors: msg += f"\nStake {i} - age {age}d - current APY {round(stake.APY_current, 2)}% - green {round(stake.green, 2)}% - red {round(stake.red, 2)}% - orange {round(stake.orange, 2)}% - blue {round(stake.blue, 2)}%" else: msg += f"\nStake {i} - age {age}d - rewards {round(stake.rewards, 2)} - bonus {round(6 * stake.bonus, 2)} - current APY {round(stake.APY_current, 2)}% - average APY {round(stake.APY_realized, 2)}%" if len(self.portfolio.stakes) > 1: overallAPY = sum(self.portfolio.stakes.stake*self.portfolio.stakes.APY_current)/sum(self.portfolio.stakes.stake) overallBonus = 6*sum(self.portfolio.stakes.stake*self.portfolio.stakes.bonus)/sum(self.portfolio.stakes.stake) msg += f"\n\nWeighted average current APY: {round(overallAPY,2)}%" msg += f"\nWeighted average bonus factor: {round(overallBonus,2)}" msg += f"\nTotal unclaimed eXRD rewards: {round(self.portfolio.stakes.rewards.sum(),2)}" return msg def rewardsProjection(self, wallets): try: self.portfolio = RadixPortfolio(wallets) except: raise Exception("Failed") self.trender.updateEventList() return self.trender.plotRewards(self.portfolio.stakes) def calcAPY(self): msg = f"Current spot price: {round(self.updatePrice(),4)} USDC/eXRD\n" msg += f"Current initial APY: {round(self.portfolio.initial_APY,2)}%\n" msg += f"Current nominal APY: {round(self.portfolio.nominal_APY,2)}% ({round(6*self.portfolio.nominal_APY,2)}%)\n" return msg def helpMessage(self): msg = "Welcome to RadBot!\n" msg += "\nCurrent commands:" msg += "\n /a <address(es)> --> Analyse wallet(s)" msg += "\n /apy --> Current LM APY" msg += "\n /mcap --> eXRD market cap" msg += "\n /projection <address> --> Rewards trend" msg += "\n /unlock --> next unlock info" msg += "\n /when --> when negative APY" msg += "\n /donate --> when you're feeling generous" return msg def whenNegativeAPY(self): launchTime = 1605629336 SiTi = self.portfolio.totalStakeDays Si = self.portfolio.totalStake ## Weighted average stake time WATS = SiTi/self.portfolio.totalStake ## Time to replenish the unlocked rewards pool resupplyTime = self.portfolio.unlocked/self.portfolio.E/60/60/24 msg = "Due to the reward pool mechanism, there is a possibility for older stake to lose unclaimed rewards (experience negative APY). See the slide deck at http://tiny.cc/RadixRewards for details." msg += f"\n\nAverage time staked: {round(WATS,2)} days" msg += f"\nRewards resupply time: {round(resupplyTime,2)} days" T_launch = (int(time.time()) - launchTime)/60/60/24 U = self.portfolio.unlocked E = self.portfolio.E*(60*60*24) eXRD_per_LP = 2*self.portfolio.pool_eXRD/self.portfolio.totalLPs def APY(T0): B_T0 = 1/6+5/6*min((T0/90)**2,1) daily_eXRD_per_LP = U*B_T0/SiTi*(1 + T0*(E/U - Si/SiTi) + (T0<90)*10*(T0/90)**2/(1+5*(T0/90)**2)) yearly_eXRD_per_LP = daily_eXRD_per_LP*365 return 100*yearly_eXRD_per_LP/eXRD_per_LP targetfunc = lambda T0: T0*(E/U-Si/SiTi) + 10*(T0/90)**2/(1+5*(T0/90)**2) result = minimize(targetfunc,45,method='Powell',bounds=[(0,min(90,T_launch))]) if not result['success']: print('Failure to optimize target function') return "Calculation error" T_min = result['x'][0] APY_min = APY(T_min) ## Weighted average stake time WATS = SiTi/Si ## Time to replenish the unlocked rewards pool RT = U/E msg = "Due to the reward pool mechanism, there is a possibility for older stake to lose unclaimed rewards (experience negative APY). See the slide deck at http://tiny.cc/RadixRewards for details. Key parameters to monitor below:" msg += f"\n\nAverage time staked: {round(WATS,2)} days" msg += f"\nRewards resupply time: {round(RT,2)} days" T_launch = (int(time.time()) - launchTime)/60/60/24 APY_launch = APY(T_launch) msg += f"\n\nNominal APY: {round(self.portfolio.nominal_APY,2)}% ({round(6*self.portfolio.nominal_APY,2)}%)" msg += f"\nInitial APY: {round(self.portfolio.initial_APY,2)}% for completely new stake" msg += f"\nLowest APY < 90d: {round(APY_min,2)}% for {round(T_min,1)} days old stake" msg += f"\nHighest APY: {round(APY(89.9999),2)}% just before reaching 90d" msg += f"\nLaunch stake APY: {round(APY_launch,2)}% for {round(T_launch,1)} days old stake" if APY_launch > 0: criticalStake = (1 + T_launch/RT + (T_launch<90)*(10*(T_launch/90)**2/(1+5*(T_launch/90)**2)))*SiTi/T_launch stakeMargin = criticalStake - Si USDC_per_LP = 2*self.portfolio.pool_USDC/self.portfolio.totalLPs USDC_margin = stakeMargin * USDC_per_LP/1e6 msg += f"\n\nCurrently launch stake has positive APY. If more than {round(USDC_margin/1e6,2)} MM USDC of fresh stake is added, launch stake APY will go negative." else: # This should be refined for the remote option that APY_min < 0 T_critical = max(U*SiTi/(Si*U - E*SiTi),90) msg += f"\n\nCurrently all stake older than {round(T_critical,1)} days has negative APY." msg += "\n\n" return msg def handleCommand(self, message): command = message.text.split()[0][1:] if command in ['start', 'help']: self.telegram.reply_to(message, self.helpMessage()) elif command in ['apy', 'APY']: self.telegram.reply_to(message, self.calcAPY()) elif command in ['a', 'analyse', 'analyze']: self.telegram.reply_to(message, self.analyseWallets(message.text.split()[1:])) elif command in ['donate']: self.telegram.reply_to(message, "RadBot is and will remain free to use for as long as I'll maintain her. If you find her services valuable, donations to show appreciation are welcome. Thanks for your support! \n\nETH address: 0x451423D5CA2618a3CC6944AD754A60083b3a125f") elif command in ['mc', 'mcap']: self.telegram.reply_to(message, self.calcMarketCap()) elif command in ['projection']: try: with self.rewardsProjection(message.text.split()[1:]) as buffer: self.telegram.reply_to(message, "The below is a graph of what your total rewards look like until now, and how they will develop if nobody (un/re)stakes from now on. Your actual future rewards will be less if more stake is added, and more if stake leaves before reaching 6x multiplier.") self.telegram.send_photo(message.chat.id, buffer) except: self.telegram.reply_to(message, "Failed to analyze address(es).") elif command in ['u', 'unlock']: self.telegram.reply_to(message, "To reduce channel spam, unlock is now only available in DM.") self.telegram.send_message(message.from_user.id, self.nextUnlock()) elif command in ['when', 'whenZeroAPY']: self.telegram.reply_to(message, self.whenNegativeAPY()) else: self.telegram.reply_to(message, "Unknown command. Try /help for command list.")