Как сделать рассылку в телеграмм боте через определенное время?

Есть вопрос с телеграмм ботом на telebot. Нужно, чтобы бот отправлял сообщение (картинку), через определенный интервал времени. Основная проблема заключается в том, что при получении сообщения с новым интервалом бот должен отменить старый цикл и рассылать по новому интервалу, но этого не происходит. У меня не получается сделать это через while и schedule. В моем коде, когда он принимает сообщение с интервалом, то отправляет сообщения, но когда пишу 'Отключить', то не реагирует. В этом и проблема. Помогите, пожалуйста.

import schedule
import time
import random
import telebot
from glob import glob

from telebot import types

key = '...'

bot = telebot.TeleBot(key)
@bot.message_handler(commands=['Начать', 'start', '/start', 'Главная'])
def start(message):
    markup = types.ReplyKeyboardMarkup(resize_keyboard = True)
    item1 = types.KeyboardButton('Рассылка')
    markup.add(item1)
    bot.send_message(message.chat.id, 'Используйте меню', reply_markup = markup)

@bot.message_handler(content_types=['text', 'photo'])
def main(message):
    lists = glob('images/*')
    pic = random.choice(lists)
    if message.chat.type == 'private':
        if message.text == 'Рассылка':
            sub_menu = types.ReplyKeyboardMarkup(resize_keyboard=True)
            item8 = types.KeyboardButton('Назад')
            item15 = types.KeyboardButton('10 мин')
            item9 = types.KeyboardButton('30 мин')
            item10 = types.KeyboardButton('1 час')
            item11= types.KeyboardButton('10 часов')
            item12 = types.KeyboardButton('24 часа')
            item16 = types.KeyboardButton('Отключить')
            sub_menu.add(item8, item15, item9, item10, item11, item12, item16)
            bot.send_message(message.chat.id, 'Здесь можно настроить частоту автоматической',
                             reply_markup=sub_menu)
        elif message.text == '10 мин':
            while True:
                time.sleep(600)
                bot.send_photo(message.chat.id, photo=(open(pic, 'rb')))
                pic = random.choice(lists)
                if message.text == 'Отключить' or message.text == '30 мин' or message.text == '1 час' or message.text == '10 часов' or message.text == '24 часа':
                    break
        elif message.text == '30 мин':
            while True:
                time.sleep(1800)
                bot.send_photo(message.chat.id, photo=(open(pic, 'rb')))
                pic = random.choice(lists)
                if message.text == 'Отключить' or message.text == '10 мин' or message.text == '1 час' or message.text == '10 часов' or message.text == '24 часа':
                    break
        elif message.text == 'Отключить':
            bot.send_message(message.chat.id, 'Рассылка отключена!')
        else:
            bot.send_message(message.chat.id, 'Используйте кнопки!')

bot.polling(none_stop=True)

Ответы (2 шт):

Автор решения: Roman Gazman

Не претендую на адекватность решения, скорее костыль и не для того чтобы ходить.

Создайте класс с переменной "времени", ну, или просто какой-то флаг. Кнопками (или командой боту) вы можете изменить значение этой переменной, она будет глобальной для кода.

Пример кода:

import telebot as tb
import time
  
token = TOKEN
 
bot = tb.TeleBot (token)

class Timer():
    _time = 10
    
@bot.message_handler(commands=['start'])
def cm_start(message):
    #Какие-то действия при старте
    bot.send_message (message.chat.id, text='')
  

@bot.message_handler(commands=['timer_on'])
def cm_timer_on(message):
    Timer._time = 10
    #Тот самый таймер и переменная 
    while Timer._time != 0:
        bot.send_message(message.chat.id, text=f'Время {time.time()}')
        time.sleep(Timer._time)
    print ('oop') #это сообщение будет в консоли после выполнения команды /timer_off
    
    
@bot.message_handler(commands=['timer_off'])
def cm_timer_off(message):
    #Тут указываем что переменная ноль или любое другое условие выхода из цикла
    Timer._time = 0
    bot.send_message (message.chat.id, text='Ня пока')
 

bot.infinity_polling()

Конечно, это пример, думаю что вы способны использовать идею для своей задачи.

→ Ссылка
Автор решения: flexyyy

Я предлагаю вам использовать apscheduler.

Создайте глобальную переменную где то в начале.

# Фоновый планировщик
from apscheduler.schedulers.background import BackgroundScheduler
# Для указания интервала
from apscheduler.triggers.interval import IntervalTrigger

mailing = BackgroundScheduler()
# Запускаем планировщика. Он будет ждать, когда появится работа.
# Будет работать, даже если не будет работы.
mailing.start()

def sending_newsletter():
   # остальной код...
   ...

# В каком то из функции...
@bot.message(...)
def some_function(message):
    # Указываем, что это глобальная переменная
    global mailing
     # ищем, есть ли работа с айди пользователя
    has_job = mailing.get_job(message.from_user.id) is not None

    minutes = 0

    if message.text == '10 мин':
        minutes = 10
    elif message.text == '30 мин':
        minutes = 30
    elif message.text == 'Отключить' and has_job:
        # Останавливаем работу
        mailing.pause_job(message.from_user.id)
    else:
        return bot.send_message(
            message.from_user.id, 'Используйте кнопки!')
    
    if has_job:
        # Работа с таким айди есть,
        # значит нужно пересоздать работу с таким же айди
        mailing.reschedule_job(
            sending_newsletter, trigger=IntervalTrigger(minutes=minutes))
    else:
        # Создаём работу с айди пользователя
        mailing.add_job(
            sending_newsletter,
            trigger=IntervalTrigger(minutes=minutes),
            id=message.from_user.id)

Чтобы передать параметры в функцию, нужно сделать так: mailing.add_job(sending_newsletter, trigger=IntervalTrigger(minutes=minutes), id=message.from_user.id, trigger_args=(1, 2)), также самое и в mailing.reschedule_job

Как для синхр. бота, который не особо популярный - неплохо, но всё же лучше использовать асинхр. версию telebot или использовать aiogram и использовать асинхр. функции.Синхронность блокирует основной поток, но BackgroundScheduler работает в другом потоке и блокировать основу не будет, но когда будет запускать функции, то поток будет блокироваться.Есть асинхр. часть apscheduler, благодаря асинхр. основной поток блокироваться не будет и бот виснуть также не будет.

from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.interval import IntervalTrigger

sch = AsyncIOScheduler()
# Там всё абсолютно тоже самое, что и в BackgroundScheduler

Знаю, что вопрос 3х летней давности, но а мало ли, кому то пригодиться?

→ Ссылка