import html from functools import wraps from aiohttp import ClientSession from telegram import Update, Chat, InlineKeyboardMarkup, InlineKeyboardButton, Message from telegram.constants import ParseMode, ChatAction, ChatType from telegram.ext import ( Application, ApplicationBuilder, ContextTypes, Defaults, filters, InlineQueryHandler, PicklePersistence, MessageHandler, CommandHandler, CallbackQueryHandler ) import common from tweet import TGTweet def send_action(action): def decorator(func): @wraps(func) async def command_func(update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs): await update.effective_chat.send_action(action) return await func(update, context, *args, **kwargs) return command_func return decorator async def inline_query(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: query = update.inline_query.query if query == "": return common.logger.info(f"Query: {query}") async with TGTweet(query) as tweet: result = list(tweet.inline_query_generator) await update.inline_query.answer(result) @send_action(ChatAction.UPLOAD_PHOTO) async def url_media(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: url = update.message.text common.logger.info(f"Receiving url: {url}") async with TGTweet(url) as tweet: media = list(tweet.pm_media_generator) message_to_send = await update.effective_message.reply_media_group( media, caption=tweet.message_text, reply_to_message_id=update.message.message_id, ) url = tweet.url if context.user_data.get('edit_before_forward', False): message_reply = await update.effective_message.reply_text( "Reply to edit message. [URL]", reply_markup=InlineKeyboardMarkup.from_button( InlineKeyboardButton("↩️ Confirm", callback_data="forward") ), reply_to_message_id=update.message.message_id, ) context.user_data['message_reply'] = message_reply context.user_data['message_to_send'] = message_to_send context.user_data['message_url'] = url return if 'forward_channel_id' in context.user_data: await forward_message(update, context, message_to_send) async def forward_message( update: Update, context: ContextTypes.DEFAULT_TYPE, message_to_send: tuple[Message, ...], ) -> None: try: await update.effective_chat.copy_messages( context.user_data['forward_channel_id'], [m.id for m in message_to_send] ) except Exception as e: await update.effective_message.reply_text(str(e)) async def edit_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if 'message_reply' not in context.user_data: return if update.message.reply_to_message != context.user_data['message_reply']: return template = context.user_data.get('template', None) if template: update_text = template.replace("[]", html.escape(update.message.text)) else: update_text = html.escape(update.message.text) match = common.message_url_regex.search(update_text) if match: match = match.span() update_text = update_text[:match[0]] + '{1}'.format( context.user_data['message_url'], update_text[match[0] + 1:match[1] - 1] ) + update_text[match[1]:] message_to_send = context.user_data['message_to_send'] await message_to_send[0].edit_caption(update_text) async def query_forward_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: message_to_send = context.user_data['message_to_send'] await forward_message(update, context, message_to_send) await update.callback_query.answer('✅ Forwarded') await update.callback_query.delete_message() del context.user_data['message_reply'] del context.user_data['message_to_send'] del context.user_data['message_url'] @send_action(ChatAction.TYPING) async def cmd_set_forward_channel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if not context.args: await update.effective_message.reply_text("Please provide a channel username or id.") return channel = context.args[0] try: channel: Chat = await context.bot.get_chat(channel) except Exception as e: await update.effective_message.reply_text(str(e)) return if channel.type != ChatType.CHANNEL: await update.effective_message.reply_text("That is not a channel.") return try: channel_admin = await channel.get_administrators() except Exception as e: await update.effective_message.reply_text(str(e) + "\nPlease add the bot to the channel and set as admin") return user_bot = filter(lambda x: x.user.id == context.bot.id, channel_admin) user_bot = next(user_bot, None) if user_bot.can_post_messages: context.user_data['forward_channel_id'] = channel.id await update.effective_message.reply_text("Add successfully.") @send_action(ChatAction.TYPING) async def cmd_remove_forward_channel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if 'forward_channel_id' in context.user_data: del context.user_data['forward_channel_id'] await update.effective_message.reply_text("Remove successfully.") return await update.effective_message.reply_text("No channel to remove.") @send_action(ChatAction.TYPING) async def cmd_edit_before_forward(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if context.user_data.get('forward_channel_id', None) is None: await update.effective_message.reply_text("Please enable forward channel first.") return ebf_status = context.user_data.get('edit_before_forward', False) if ebf_status: context.user_data['edit_before_forward'] = False context.user_data.pop('message_reply', None) context.user_data.pop('message_to_send', None) context.user_data.pop('message_url', None) await update.effective_message.reply_text("Disable edit before forward.") return context.user_data['edit_before_forward'] = True await update.effective_message.reply_text("Enable edit before forward.") @send_action(ChatAction.TYPING) async def cmd_set_template(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: reply = update.effective_message.reply_to_message if not reply: await update.effective_message.reply_text("Please reply to a message to set as template.") return if '[]' not in reply.text_html: await update.effective_message.reply_text("Please reply to a message with [] to set as template.") return context.user_data['template'] = reply.text_html await update.effective_message.reply_text("Template set.") @send_action(ChatAction.TYPING) async def cmd_user_dict(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: await update.effective_message.reply_text(str(context.user_data)) async def post_init(application: Application) -> None: # commands = [ # BotCommand('start', CMD_START), # ] # await application.bot.set_my_commands(commands) DESCRIPTION = "A bot to fetch tweets from Twitter." await application.bot.set_my_description(DESCRIPTION) await application.bot.set_my_short_description(DESCRIPTION) TGTweet.set_session(ClientSession()) async def post_stop(application: Application) -> None: await application.bot.send_message(common.ADMIN[0], "Shutting down...") async def post_shutdown(application: Application) -> None: await TGTweet.close_session() def main(): defaults = Defaults(parse_mode=ParseMode.HTML, allow_sending_without_reply=True) persistence = PicklePersistence(filepath='data/pers.pkl') application = (ApplicationBuilder() .token(common.BOT_TOKEN) .defaults(defaults) .persistence(persistence) .post_init(post_init) .post_stop(post_stop) .post_shutdown(post_shutdown) .build() ) user_filter = filters.User() user_filter.add_user_ids(common.ADMIN) handlers = [ MessageHandler(filters.Regex(common.x_url_regex) & filters.ChatType.PRIVATE, url_media), InlineQueryHandler(inline_query, common.x_url_regex), CommandHandler("set_forward_channel", cmd_set_forward_channel), CommandHandler("remove_forward_channel", cmd_remove_forward_channel), CommandHandler("edit_before_forward", cmd_edit_before_forward), CommandHandler("set_template", cmd_set_template), MessageHandler(~filters.COMMAND & filters.ChatType.PRIVATE, edit_message), CallbackQueryHandler(query_forward_message, pattern="forward"), CommandHandler("bot_dict", cmd_user_dict, filters=user_filter), ] application.add_handlers(handlers) if common.WEBHOOK: application.run_webhook( listen=common.WEBHOOK_LISTEN, port=common.WEBHOOK_PORT, secret_token=common.WEBHOOK_SECRET_TOKEN, key=common.WEBHOOK_KEY, cert=common.WEBHOOK_CERT, webhook_url=common.WEBHOOK_URL ) else: application.run_polling() if __name__ == '__main__': main()