Пример #1
0
 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)
Пример #2
0
 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)
Пример #4
0
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()
Пример #5
0
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()
Пример #6
0
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()
Пример #7
0
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)
Пример #8
0
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)
Пример #9
0
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)
Пример #10
0
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.")