pythonplaywrightpython-telegram-bot

Cannot switch to a different thread


I'm trying to create a telegram bot that can send a screenshot of a web page.

I'm using python-telegram-bot to handle telegram interactions and python-playwright to get screenshots of a page. The problem with my code is that i get the following error with Playwright:

greenlet.error: cannot switch to a different thread

This happens because i'm declaring browser = p.chromium.launch(headless=True, args=args) outside of the function that gets executed when a telegram command is called. The problem is that launching the browser every time the telegram command is called, slows my code down for about 3 seconds. How can i declare browser globally? Is it even possible?

from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ParseMode
from telegram.ext import Updater, CommandHandler, ChatMemberHandler, CallbackQueryHandler
from telegram.error import Unauthorized, BadRequest
from playwright.sync_api import sync_playwright

args = [
        "--no-sandbox",
        "--disable-setuid-sandbox",
        "--use-gl=egl",
    ]

p = sync_playwright().start()
browser = p.chromium.launch(headless=True, args=args)

def start(update, context):
    page = browser.new_page()
    page.goto("https://google.com")
    page.screenshot(path="example.png")
    page.close()
    context.bot.send_photo(chat_id=update.effective_chat.id, photo=open("example.png", "rb"))  


updater = Updater('TOKEN', use_context=True, workers=124)
updater.dispatcher.add_handler(CommandHandler('start', start, run_async=True))
updater.start_polling()
updater.idle()

Solution

  • I have worked on it, and I have changed a lot in your code,

    I have made a bot to try until I finish it successfully.

    I have changed the code into async code.

    this is the last code:

    import telegram
    from telegram import Update
    from telegram.ext import MessageHandler, CommandHandler, Application, ContextTypes, filters
    from playwright.async_api import async_playwright
    
    args = [
            "--no-sandbox",
            "--disable-setuid-sandbox",
            "--use-gl=egl",
        ]
    
    
    # Replace 'YOUR_API_TOKEN' with the API token you received from BotFather.
    API_TOKEN = 'yours'
    
    
    async def download_screan_shot(link="www.google.com", id_of_chat=None, update_to_use_it_to_send_the_file=None):
        file_name =f"{id_of_chat}_example.png"
    
        async with async_playwright() as p:
            browser = await p.chromium.launch()
            page = await browser.new_page()
            await page.goto(link)
            await page.screenshot(path=file_name)
            await browser.close()
    
        await update_to_use_it_to_send_the_file.message.reply_photo(file_name)
    
    
    
    async def messages(update, context):
        the_gotten_message = update.message.text
        if "www" in the_gotten_message:
            await download_screan_shot(link=the_gotten_message, id_of_chat=update.message.from_user.id, update_to_use_it_to_send_the_file=update)
        else:
            await update.message.reply_text(update.message.text)
    
    
    app = Application.builder().token(API_TOKEN).build()
    app.add_handler(MessageHandler(filters.TEXT,messages))
    app.run_polling()
    

    and this is a screenshot from my bot:

    enter image description here

    I hope it works for you.

    about the amount of sent links, these are failed tries.

    Edit:

    because you have said that you want to run the browser once, I did not find any solution for that, but I have done it using a bad idea.

    it runs the browser once then saves it in public inside a list, and then uses that active browser.

    this is my new code :

    import telegram
    from telegram import Update
    from telegram.ext import MessageHandler, CommandHandler, Application, ContextTypes, filters
    from playwright.async_api import async_playwright
    from playwright.sync_api import sync_playwright
    
    args = [
            "--no-sandbox",
            "--disable-setuid-sandbox",
            "--use-gl=egl",
        ]
    
    
    # Replace 'YOUR_API_TOKEN' with the API token you received from BotFather.
    API_TOKEN = 'yours'
    
    
    opened_browser = []
    
    
    async def download_screan_shot(link="www.google.com", id_of_chat=None, update_to_use_it_to_send_the_file=None):
        file_name =f"{id_of_chat}_example.png"
    
        # playwright = sync_playwright().start()
        # browser = playwright.chromium.launch()
        # page = browser.new_page()
        # page.goto("https://playwright.dev/")
        # page.screenshot(path="example.png")
        # browser.close()
    
    
        if (not opened_browser):
            playwright = await async_playwright().start()
            browser = await playwright.chromium.launch()
            opened_browser.append(browser)
    
        page = await opened_browser[0].new_page()
        await page.goto(link)
        await page.screenshot(path=file_name)
        await page.close()
        # await opened_browser[0].close()
    
        await update_to_use_it_to_send_the_file.message.reply_photo(file_name)
    
    
    
    async def messages(update, context):
        the_gotten_message = update.message.text
        if "www" in the_gotten_message:
            await download_screan_shot(link=the_gotten_message, id_of_chat=update.message.from_user.id, update_to_use_it_to_send_the_file=update)
        else:
            await update.message.reply_text(update.message.text)
    
    
    app = Application.builder().token(API_TOKEN).build()
    app.add_handler(MessageHandler(filters.TEXT,messages))
    app.run_polling()
    
    
    

    to be honest with you, I have not seen any difference in the performance, and also I do not know what is happening there.

    it is just a try, to give you an idea.

    you can do the same in your code >>> I did not do that because I think you are using an old version of telegram library, and for that, I faced many errors when I tried your code with the version that I have.

    tell me.