如何让Telegram Bot支持多行Link+电影名输入并处理?
问题描述
我开发了一款Telegram Bot,当前仅支持单行「链接 电影名」格式输入。Bot会调用MovieDB API搜索电影的名称、年份及ID,生成列表供用户选择;用户选中后,Bot会将链接发送至JDownloader,并以电影名+年份作为文件夹名。
现需将其改造为支持多行输入,每行对应一组「链接 电影名」,格式如下:
link1 name1 link2 name2 link3 name3
Bot需逐个按现有逻辑处理每组输入并发送至JDownloader,但尝试改造后持续报错,寻求可行解决方案。
当前单行处理代码如下:
import os import requests import myjdapi from telegram import Update from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, ConversationHandler, CallbackContext TELEGRAM_BOT_TOKEN = 'bot_token' MYJD_APP_KEY = "key" MYJD_EMAIL = "email" MYJD_PASSWORD = "pass" MYJD_DEVICE_NAME = "name" MOVIEDB_API_KEY = "api key" DESTINATION_FOLDER = "destination" # Define conversation states GET_MOVIE_NAME, CHOOSE_MOVIE = range(2) # Create a dictionary to store user data user_data = {} def start(update: Update, context: CallbackContext): user_id = update.message.from_user.id user_data[user_id] = [] update.message.reply_text('Welcome to the Movie Downloader bot! Send me a link and movie name to search and download.') return GET_MOVIE_NAME def get_movie_info(movie_name): search_url = f'https://api.themoviedb.org/3/search/movie?api_key={MOVIEDB_API_KEY}&query={movie_name}' response = requests.get(search_url) if response.status_code == 200: data = response.json() results = data.get('results', []) if results: return results return None def add_movie_to_jdownloader(link, selected_movie): movie_name = selected_movie['title'] movie_year = selected_movie['release_date'][:4] movie_id = selected_movie['id'] package_name = f'{movie_name} ({movie_year}) [{movie_id}]' destination_folder = DESTINATION_FOLDER # Define your destination folder # Assuming you have already connected to MyJDownloader and have the device jd = myjdapi.Myjdapi() jd.set_app_key(MYJD_APP_KEY) jd.connect(MYJD_EMAIL, MYJD_PASSWORD) device = jd.get_device(MYJD_DEVICE_NAME) # Add the link to the Linkgrabber device.linkgrabber.add_links([{ 'autostart': True, 'packageName': package_name, 'links': link, 'destinationFolder': destination_folder, 'overwritePackagizerRules': True }]) def get_movie_name(update: Update, context: CallbackContext): user_id = update.message.from_user.id text = update.message.text # Get the user's message text parts = text.split(maxsplit=1) # Split the input into two parts (link and movie name) if len(parts) == 2: link, movie_name = parts movie_list = get_movie_info(movie_name) if movie_list: user_data[user_id] = (link, movie_list) # Store both the link and movie list message = 'Multiple movies found. Please select one:\n' for idx, movie in enumerate(movie_list): message += f'{idx + 1}. {movie["title"]} ({movie["release_date"][:4]})\n' message += 'Enter the number of your choice.' update.message.reply_text(message) return CHOOSE_MOVIE else: context.bot.send_message(chat_id=update.effective_chat.id, text='No movies found with that name. Please try again.') return GET_MOVIE_NAME else: context.bot.send_message(chat_id=update.effective_chat.id, text='Invalid input format. Please use "link moviename."') return GET_MOVIE_NAME def choose_movie(update: Update, context: CallbackContext): user_id = update.message.from_user.id choice = int(update.message.text) user_input = user_data.get(user_id) if user_input and len(user_input) == 2: link, movie_list = user_input if 1 <= choice <= len(movie_list): selected_movie = movie_list[choice - 1] # Call the JDownloader function to add the movie add_movie_to_jdownloader(link, selected_movie) update.message.reply_text('The movie has been added to MyJDownloader.') update.message.reply_text('Please enter the next link and movie name.') return GET_MOVIE_NAME # Return to the movie name input state else: update.message.reply_text('Invalid choice. Please select a valid movie number.') return CHOOSE_MOVIE else: update.message.reply_text('Something went wrong. Please start over by entering the next link and movie name.') return GET_MOVIE_NAME def main(): updater = Updater(TELEGRAM_BOT_TOKEN) dp = updater.dispatcher # Create a dispatcher instance # Optional start command handler dp.add_handler(CommandHandler('start', start)) conv_handler = ConversationHandler( entry_points=[ CommandHandler('start', start), # Optional /start command MessageHandler(Filters.text & ~Filters.command, get_movie_name) ], states={ GET_MOVIE_NAME: [MessageHandler(Filters.text & ~Filters.command, get_movie_name)], CHOOSE_MOVIE: [MessageHandler(Filters.regex(r'^\d+$'), choose_movie)], }, fallbacks=[] ) dp.add_handler(conv_handler) updater.start_polling() updater.idle() if __name__ == '__main__': main()
解决方案
核心思路是引入任务队列机制,将多行输入拆解为待处理任务,逐个执行原有的「搜索-选择-添加下载」流程,同时明确维护用户的处理状态,避免逻辑混乱。
修改要点
- 重构
user_data存储结构,区分待处理任务队列和当前正在处理的任务 - 新增多行输入解析函数,逐行验证格式,过滤无效任务
- 添加自动任务调度逻辑,完成当前任务后自动启动下一个
- 优化鲁棒性,处理电影无上映日期、用户输入非数字等异常情况
修改后的完整代码
import os import requests import myjdapi from telegram import Update from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, ConversationHandler, CallbackContext TELEGRAM_BOT_TOKEN = 'bot_token' MYJD_APP_KEY = "key" MYJD_EMAIL = "email" MYJD_PASSWORD = "pass" MYJD_DEVICE_NAME = "name" MOVIEDB_API_KEY = "api key" DESTINATION_FOLDER = "destination" # 对话状态定义 GET_TASKS, CHOOSE_MOVIE = range(2) # 用户数据结构:{user_id: {"task_queue": [("link1", "name1"), ...], "current_task": ("link", movie_list)}} user_data = {} def start(update: Update, context: CallbackContext): user_id = update.message.from_user.id user_data[user_id] = {"task_queue": [], "current_task": None} update.message.reply_text('欢迎使用电影下载Bot!请发送多行内容,每行格式为「链接 电影名」,我会逐个处理。') return GET_TASKS def get_movie_info(movie_name): search_url = f'https://api.themoviedb.org/3/search/movie?api_key={MOVIEDB_API_KEY}&query={movie_name}' response = requests.get(search_url) if response.status_code == 200: data = response.json() results = data.get('results', []) if results: return results return None def add_movie_to_jdownloader(link, selected_movie): movie_name = selected_movie['title'] movie_year = selected_movie['release_date'][:4] if selected_movie.get('release_date') else '未知年份' movie_id = selected_movie['id'] package_name = f'{movie_name} ({movie_year}) [{movie_id}]' destination_folder = DESTINATION_FOLDER # 连接MyJDownloader并添加任务 jd = myjdapi.Myjdapi() jd.set_app_key(MYJD_APP_KEY) jd.connect(MYJD_EMAIL, MYJD_PASSWORD) device = jd.get_device(MYJD_DEVICE_NAME) device.linkgrabber.add_links([{ 'autostart': True, 'packageName': package_name, 'links': link, 'destinationFolder': destination_folder, 'overwritePackagizerRules': True }]) def process_next_task(user_id, update: Update, context: CallbackContext): """处理队列中的下一个任务""" user_info = user_data[user_id] if not user_info["task_queue"]: update.message.reply_text('所有任务已处理完成!请继续发送新的任务内容。') return GET_TASKS # 取出下一个任务 link, movie_name = user_info["task_queue"].pop(0) movie_list = get_movie_info(movie_name) if movie_list: user_info["current_task"] = (link, movie_list) message = f'找到以下电影(当前任务:{movie_name}),请选择序号:\n' for idx, movie in enumerate(movie_list): release_year = movie["release_date"][:4] if movie.get('release_date') else '未知年份' message += f'{idx + 1}. {movie["title"]} ({release_year})\n' message += '请输入对应的数字序号。' update.message.reply_text(message) return CHOOSE_MOVIE else: update.message.reply_text(f'未找到电影「{movie_name}」,跳过该任务,继续处理下一个。') return process_next_task(user_id, update, context) def parse_multiline_input(text): """解析多行输入,返回有效任务列表""" valid_tasks = [] lines = text.strip().split('\n') for line_num, line in enumerate(lines, 1): line = line.strip() if not line: continue parts = line.split(maxsplit=1) if len(parts) == 2: link, movie_name = parts valid_tasks.append((link, movie_name)) else: # 无效行记录但不中断,继续处理其他行 print(f'第{line_num}行格式错误:{line}') return valid_tasks def get_tasks(update: Update, context: CallbackContext): user_id = update.message.from_user.id text = update.message.text valid_tasks = parse_multiline_input(text) if not valid_tasks: update.message.reply_text('未找到有效任务格式,请确保每行是「链接 电影名」的格式。') return GET_TASKS # 将有效任务加入队列 user_data[user_id]["task_queue"].extend(valid_tasks) update.message.reply_text(f'已添加{len(valid_tasks)}个任务,开始逐个处理:') return process_next_task(user_id, update, context) def choose_movie(update: Update, context: CallbackContext): user_id = update.message.from_user.id user_info = user_data.get(user_id) if not user_info or not user_info["current_task"]: update.message.reply_text('当前无正在处理的任务,请重新发送任务内容。') return GET_TASKS try: choice = int(update.message.text) except ValueError: update.message.reply_text('请输入有效的数字序号。') return CHOOSE_MOVIE link, movie_list = user_info["current_task"] if 1 <= choice <= len(movie_list): selected_movie = movie_list[choice - 1] add_movie_to_jdownloader(link, selected_movie) update.message.reply_text('该任务已添加至JDownloader!') # 清除当前任务,处理下一个 user_info["current_task"] = None return process_next_task(user_id, update, context) else: update.message.reply_text(f'请输入1到{len(movie_list)}之间的数字。') return CHOOSE_MOVIE def main(): updater = Updater(TELEGRAM_BOT_TOKEN) dp = updater.dispatcher dp.add_handler(CommandHandler('start', start)) conv_handler = ConversationHandler( entry_points=[ CommandHandler('start', start), MessageHandler(Filters.text & ~Filters.command, get_tasks) ], states={ GET_TASKS: [MessageHandler(Filters.text & ~Filters.command, get_tasks)], CHOOSE_MOVIE: [MessageHandler(Filters.regex(r'^\d+$'), choose_movie)], }, fallbacks=[] ) dp.add_handler(conv_handler) updater.start_polling() updater.idle() if __name__ == '__main__': main()
关键改进说明
- 任务队列管理:用
task_queue存储待处理的「链接+电影名」对,确保任务按顺序逐个执行 - 多行输入解析:
parse_multiline_input函数逐行验证格式,跳过空行和无效行,不影响其他任务的处理 - 自动任务调度:
process_next_task函数负责任务的流转,完成当前任务后自动启动下一个,无需用户手动触发 - 鲁棒性优化:处理电影无上映日期的情况,避免索引错误;捕获用户输入非数字的异常,给出明确提示
- 状态清晰:用户数据结构明确区分待处理队列和当前任务,避免状态冲突导致的报错
内容的提问来源于stack exchange,提问作者Nice Guy
相关产品推荐
相关产品推荐

