Multiple lined input to a telegram bot for sending links to jdownloader

146 views Asked by At

I created a telegram bot that takes input in the form of link name. The bot takes the name and searches using moviedb api to get the name, year, and id of the movie and then provide a menu to the user with a list of the movies for the user to select. Upon selection, it takes the data and sends the link to jdownloader with the moviename and year as foldername. Right now it takes one link+movie pair as input.

What I wanted was to change this to multiple link+name pairs in each line of the input message like:

link1 name1
link2 name2
link3 name3

and the bot takes each link and name one at a time and processes and sends the links to jdownloader in the same way it currently does. I did try but every time it fails with some error. Any idea how I can go about doing this?

The current code for single line input is:

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()
1

There are 1 answers

0
thiagomagero On

To modify your bot to process multiple link+name pairs, you would need to update the get_movie_name function to handle multiple lines of input, and adapt the rest of the code accordingly. One approach could be to split the input text into lines, and then process each line individually, in a manner similar to what is already done for a single line of input.

Here's a suggestion on how you could do that:

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 = 0

def start(update: Update, context: CallbackContext):
    update.message.reply_text('Welcome to the Movie Downloader bot! Send me a list of link and movie name pairs 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

    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_line(line, context: CallbackContext):
    parts = line.split(maxsplit=1)
    if len(parts) == 2:
        link, movie_name = parts
        movie_list = get_movie_info(movie_name)
        if movie_list:
            for movie in movie_list:
                add_movie_to_jdownloader(link, movie)
        else:
            context.bot.send_message(chat_id=context.job.context, text=f'No movies found for {movie_name}. Please try again.')

def get_movie_name(update: Update, context: CallbackContext):
    text = update.message.text
    lines = text.strip().split('\n')
    for line in lines:
        context.job_queue.run_once(lambda context: process_line(line, context), 0, context=update.effective_chat.id)
    update.message.reply_text('Processing your request...')
    return ConversationHandler.END

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_movie_name)],
        states={GET_MOVIE_NAME: [MessageHandler(Filters.text & ~Filters.command, get_movie_name)]},
        fallbacks=[]
    )

    dp.add_handler(conv_handler)
    updater.start_polling()
    updater.idle()

if __name__ == '__main__':
    main()

In this code, we've modified the get_movie_name function to split the input text into lines, and then process each line individually. For each line, we've created a new process_line function, which is very similar to your original single-line input processing code. However, instead of asking the user to choose a movie from a list, we're assuming that the first movie returned by the MovieDB API is the correct one, and we're adding it directly to JDownloader. If no movies are found for a given movie name, a message is sent to the user.

This code also utilizes the job_queue from python-telegram-bot to process each input line in a separate "job" which will run immediately. This allows the bot to process multiple input lines in parallel, which might be faster if there are many input lines.

The update.message.reply_text('Processing your request...') call informs the user that the bot is processing their request, and return ConversationHandler.END ends the conversation since we're processing all lines at once, instead of waiting for more user input.

Note that this is a simplified example and may not meet all your needs, especially if the movie selection logic is important to your use case. If that's the case, you may need a more complex approach, perhaps maintaining a separate state for each input line, or prompting the user to make a choice for each input line.