def explore_compile(update, context, user): """ Handler: fsm:2.2 -> 3 """ language = user.settings.language state = user.settings.fsm_state text = txt['FSM'][state]['text'][language] + '\n' keyboard = utility.gen_keyboard(txt['FSM'][state]['markup'][language], txt['FSM'][state]['payload']) remove_message(update, context, user) search_terms = update.message.text.strip().split( config['TELEGRAM']['delim']) sources = search_sources(user, search_terms) if len(sources) == 0: text += '\n' + txt['CALLBACK']['error_no_results'][language].format( html.escape(update.message.text)) else: for source in sources: text += '\n' + source.title try: context.bot.edit_message_text(chat_id=update.message.from_user.id, message_id=user.settings.last_msg_id, text=text, reply_markup=keyboard, parse_mode='HTML', disable_web_page_preview=True) except BadRequest: pass
def modify_channels_callback(update, context, user): """ Handler 3.2 -> 3 | 3.2.1 """ # TODO: this is the same as modify_rss_callback # except the future_state const in 'inline' definition query = update.callback_query future_state = query.data.split(config['CB_DATA']['delim'])[1] if future_state == '3': to_menu(update, context, user) return inline = [[item.title, f'fin:3.2.1:{i}'] for i, item in enumerate(user.subscribed.channel_list)] inline.append([ txt['FSM'][future_state]['markup'][user.settings.language][0], txt['FSM'][future_state]['payload'][0] ]) label, data = zip(*inline) keyboard = utility.gen_keyboard(label=label, data=data, width=2) query.edit_message_text( text=txt['FSM'][future_state]['text'][user.settings.language], reply_markup=keyboard, parse_mode='HTML') user.settings.fsm_state = future_state user.save()
def modify_rss_callback(update, context, user): """ Handler 3.1 -> 3 | 3.1.1""" query = update.callback_query future_state = query.data.split(config['CB_DATA']['delim'])[1] if future_state == '3': to_menu(update, context, user) return # TODO: I beliebe telegram allows only a certain number of # items as buttons... Maybe having more should be a #premium feature inline = [[item.title, f'fin:3.1.1:{i}'] for i, item in enumerate(user.subscribed.rss_list)] # adding the "Back" button. since only one button we use index 0 inline.append([ txt['FSM'][future_state]['markup'][user.settings.language][0], txt['FSM'][future_state]['payload'][0] ]) label, data = zip(*inline) keyboard = utility.gen_keyboard(label=label, data=data, width=2) query.edit_message_text( text=txt['FSM'][future_state]['text'][user.settings.language], reply_markup=keyboard, parse_mode='HTML') user.settings.fsm_state = future_state user.save()
def manual_compile(update, context, user): """ Detects whether the answer to the Manual Entry is a link, or username """ edit_id = user.settings.last_msg_id state = user.settings.fsm_state # entities limited to the first one to avoid excessive blocking by a single # user, as feed fetches take a long time try: entity, msg = next( iter( update.message.parse_entities( types=['mention', 'url']).items())) except StopIteration: # error raised if the parse_entity returns nothing entity = msg = None text = (txt['FSM'][state]['text'][user.settings.language] + '\n') keyboard = utility.gen_keyboard( txt['FSM'][state]['markup'][user.settings.language], txt['FSM'][state]['payload']) # the feeds the user has already entered first = True for i, sub in enumerate(user.subscribed.session_list): if first: text += '\n' first = False text += f"{i+1}. {sub}\n" text += '\n' # if there are no entities, the loop below doesn't run, so no need to # exit the function early. if not all([text, entity]): text += txt['CALLBACK']['error'][user.settings.language] else: if entity.type == MessageEntity.URL: text += rss_compile(update, context, user, msg) + '\n' elif entity.type == MessageEntity.MENTION: text += channel_compile(update, context, user, msg) + '\n' # almost like it "absorbs" a message remove_message(update, context, user) try: context.bot.edit_message_text(chat_id=update.message.from_user.id, message_id=edit_id, text=text, reply_markup=keyboard, parse_mode='HTML', disable_web_page_preview=True) except BadRequest: # if the message contents are the same pass
def general_callback(update, context, user, format_data=None): """ This function can be used for general callback operations. It includes features such as extracting the future FSM state, and setting it for the user. """ query = update.callback_query # shorter var name future_state = query.data.split(config['CB_DATA']['delim'])[1] # telegram requires the server to "answer" a callback query context.bot.answer_callback_query(query.id) # the "future" FSM state now becomes the "current" FSM state user.settings.fsm_state = future_state user.save() if format_data: new_content = ( f"{txt['FSM'][future_state]['text'][user.settings.language]}". format(**format_data)) else: new_content = ( f"{txt['FSM'][future_state]['text'][user.settings.language]}") keyboard = utility.gen_keyboard( txt['FSM'][future_state]['markup'][user.settings.language], txt['FSM'][future_state]['payload']) # edit the message to display the selected language try: query.edit_message_text(text=new_content, reply_markup=keyboard, parse_mode='HTML') except BadRequest: pass # if we would like to do anything with the response (save the message id) return update
def cmd_done(update, context): """ Handler: command /done This command is, and can only be, called when the user is finished with the set-up process. """ # in this handler, we are changing user FSM states # therefore, we first have to check them. user = User.get_user(update.message.from_user.id) end_fsm_states = ['2.1'] if user.settings.fsm_state not in end_fsm_states: context.bot.delete_message(user.user_id, update.message.message_id) return if user.settings.fsm_state == '2.1': user.settings.fsm_state = FSM.DONE.value user.save() new_content = ( f"{txt['FSM'][FSM.DONE.value]['text'][user.settings.language]}" .format(**user.collect_main_data()) ) keyboard = utility.gen_keyboard( txt['FSM'][FSM.DONE.value]['markup'][user.settings.language], txt['FSM'][FSM.DONE.value]['payload'] ) context.bot.send_message( chat_id=update.message.from_user.id, text=new_content, reply_markup=keyboard, parse_mode='HTML' )
def cmd_start(update, context): uid = update.message.from_user.id payload = get_payload(update.message.text) user = create_new_user(update) # "absorb" message - cleaner this way remove_message(update, context, user) main_text = '' lang = user.settings.language if user.settings.fsm_state in {'0', '1'}: main_text += txt['SERVICE']['start'][lang] markup = utility.gen_keyboard(txt['LANG_NAMES'], txt['LANG_PAYLOAD']) user.settings.fsm_state = FSM.LANGUAGE.value user.save() else: # send the main menu # better than sending message associated with the current state, as # there are messages with customized keyboards, etc.. main_text += txt['FSM']['3']['text'][lang].format( **user.collect_main_data() ) markup = utility.gen_keyboard( txt['FSM']['3']['markup'][lang], txt['FSM']['3']['payload'] ) # ensure that if user is in another state the payload isn't processed # just a precaution... payload = None if user.settings.last_msg_id: try: context.bot.delete_message( chat_id=uid, message_id=user.settings.last_msg_id ) except BadRequest: try: context.bot.edit_message_text( chat_id=uid, message_id=user.settings.last_msg_id, text=txt['CALLBACK']['deleted'][lang], parse_mode='HTML' ) except BadRequest: # the message can be already deleted by the user pass if payload: try: inviter = User.get_user(uid=payload) except LookupError: return inviter.add_to_invited(uid) main_text = ( txt['SERVICE']['invited_by'][lang].format( user=utility.escape(context.bot.get_chat(payload).first_name), id=inviter.user_id ) + '\n\n' + main_text ) sent_message = context.bot.send_message( chat_id=uid, text=main_text, reply_markup=markup, parse_mode='HTML' ) # bot sends a new message, so the old last_msg_id must be replaced. user.settings.last_msg_id = sent_message.message_id user.save()