You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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()

关键改进说明

  1. 任务队列管理:用task_queue存储待处理的「链接+电影名」对,确保任务按顺序逐个执行
  2. 多行输入解析:parse_multiline_input函数逐行验证格式,跳过空行和无效行,不影响其他任务的处理
  3. 自动任务调度:process_next_task函数负责任务的流转,完成当前任务后自动启动下一个,无需用户手动触发
  4. 鲁棒性优化:处理电影无上映日期的情况,避免索引错误;捕获用户输入非数字的异常,给出明确提示
  5. 状态清晰:用户数据结构明确区分待处理队列和当前任务,避免状态冲突导致的报错

内容的提问来源于stack exchange,提问作者Nice Guy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 11:27:03