Ответ пользователю через определённый промежуток времени (чат-бот)
Как сделать так чтобы чат-бот отвечал пользователю через определенный промежуток времени, но так чтобы не затрагивалась работа бота в целом (то есть, time.sleep() не подходит т. к. он просто тормозит работу всего бота)
Главное условие - бот должен писать сообщение первым (по прошествии 1 минуты). Возможно стоит использовать threading или multiprocessing, но я совершенно не понимаю как это внедрить в мой код.
Важное замечание - вся информация о пользователе (в частности chat_id) хранится в словаре, который загружается при запуске и сохраняется при выключении программы, с помощью библиотеки pickle
Архитектура бота такова:
Файл bot.py:
def main():
bot = Bot(token=TOKEN, name=NAME, version=VERSION)
# Handler for start command
bot.dispatcher.add_handler(StartCommandHandler(callback=start_cb))
# Handler for simple text message without media content
bot.dispatcher.add_handler(MessageHandler(filters=Filter.text, callback=message_cb))
# Handler for help command
bot.dispatcher.add_handler(CommandHandler(command="help", callback=help_cb))
# Any other user command handler
bot.dispatcher.add_handler(CommandHandler(command="team", callback=team_cb))
bot.dispatcher.add_handler(CommandHandler(command="rules", callback=rules_cb))
bot.dispatcher.add_handler(CommandHandler(command="play", callback=play_cb))
bot.dispatcher.add_handler(CommandHandler(command="top", callback=show_leaderboard))
bot.dispatcher.add_handler(CommandHandler(command="stop", callback=stop_playing_cb))
# Handler for unknown commands
bot.dispatcher.add_handler(UnknownCommandHandler(callback=unknown_command_cb))
# Handlers for other file types
bot.dispatcher.add_handler(MessageHandler(filters=Filter.image, callback=image_cb))
# Handler for add user to chat
bot.dispatcher.add_handler(NewChatMembersHandler(callback=im_new_chat_member_cb))
bot.start_polling()
bot.idle()
signal.signal(signal.SIGTERM, my_handler())
if __name__ == '__main__':
main()
Файл logic.py - это набор функций, который используются хендлерами
Не закидывайте тряпками, бот для ICQ New... Библиотека - mailru-im-bot
Ответы (3 шт):
Совершенно верно на счет многопоточности. Как на счет запустить кастомный поток, в котором и будет испльзоваться задержка перед отправкой сообщения методом бота(? Не уверен в конкретной реализации этого в вашей библиотеке).
Я накидал пример кода. И помните, что это набросок, а не рабочая реализация. Все потому, что я не знаком с используемой вами библиотекой бота.
import threading
import time
class Sender(threading.Thread):
""" Отправляет сообщение пользователю с задержкой.
"""
def __init__(self, bot, chat_id, message, delay: int):
#
# @bot, ботик /_:
#
# @chat_id (или все таки chad_id?), идентификатор чата.
#
# @message, сообщение для отправки.
#
# @delay, int, задержка перед отправкой в секундах.
#
""" Инициализируем поток.
"""
super().__init__()
""" Запоминаем то, что нам понадобится:
"""
self.bot =bot
self.chat_id =chat_id
self.message =message
self.delay =delay
""" ВСЕ! Ладно. Не забудь вызвать Sender().start() после создания нового экземпляра потока.
Иначе ничего не будет :)
"""
def run(self):
""" Класс потока, который вызывается вместе со start().
"""
""" Воот. Нам нужно подождать перед отправкой сообщения.
В ЭТОМ ПОТОКЕ. То есть основной поток никак не зафризит.
"""
time.sleep(self.delay)
""" Привет замочкам! Хорошей манерой будет заблокировать тут замочек на тот случай,
если несколько потоков захотят отправить сообщение с интервалом в 1 милисекунду,
а ботик этого не любит. Потом следует сделать задержку перед разблокировкой,
но я не буду тут все усложнять. Оставляю это опционально для вас.
"""
""" А еще я не имею понятия,
как отправить сообщение через вашего бота,
поэтому то это болванка реализации кода.
Тут ваша ставка!
"""
# может быть код ниже?
#self.bot.send_message_to_chat(self.chat_id, self.message)
""" Я уделил минутку гуглу, и вроде это то:
"""
#self.bot.send_text(chat_id=self.chat_id, text=self.message)
""" Готово! Ты великолепен! Поток будет уничтожен примерно СЕЙЧАС.
"""
#Sender(my_bot, chat_id, message, delay_in_sec).start()
Можете протестить то, что основной поток не блокируется, а сообщение будет успешно отправлено в срок, к примеру небольшими модификациями наброска выше с использованием curses.
Так или иначе здесь придется использовать threading, для того, в идеале создать особый класс который будет собирать данные и отправлять их поистечению определенного времени, но можно сделать все куда проще и реализовать "отложенную отправку".
from time import sleep
import threading
import sys
import datetime
def send(to, message):
# Твой код обыкновенной отправки сообщения
return f"Я отправил сообщение {to}: {message}!"
# Для наглядности, принтуем вместе с текущим временем
def myprint(message):
d = datetime.datetime.strftime(datetime.datetime.now(), "%H:%M:%S")
sys.stdout.write("{} - [{}] - {}\n".format(d, threading.current_thread().getName(), message))
sys.stdout.flush()
def deffered(time, callback, target, *args, **kwargs):
def _deffered(*args, **kwargs):
sleep(time)
response = target(*args, **kwargs)
callback(response)
threading.Thread(target=_deffered, args=args, kwargs=kwargs).start()
if __name__ == '__main__':
# Обыкновенная отправка:
myprint("До отправки")
msg = send("Алёша", "Привет")
myprint(msg)
myprint("Код работает дальше")
# Отложенная отправка:
myprint("До отправки")
def _result(msg):
myprint(msg)
# 1 - Время в секундах, через которое мы хотим вызвать функцию
# _result - Наша приватная функция, которая в итоге получит то, что вернет send
# send - Функция, которую нужно вызвать
# ... - Дальше идут любые другие аргументы в соответствии с вызываемой функцией/методом
deffered(2, _result, send, "Миша", "Здаров")
myprint("Код работает дальше")
output (Обыкновенный вызов send):
19:24:23 - [MainThread] - До отправки
19:24:23 - [MainThread] - Я отправил сообщение Алёша: Привет!
19:24:23 - [MainThread] - Код работает дальше
output (Использование deffered):
19:24:23 - [MainThread] - До отправки
19:24:23 - [MainThread] - Код работает дальше
19:24:24 - [Thread-1] - Я отправил сообщение Миша: Здаров!
В любом случае вы должны позаботиться о том, чтобы данные, которые используються разными потоками либо не пересекались(Вы не должны использовать одни и те же данные в разных потоках), либо защищать их с помощью локов. В пакете threading есть класс Lock, который вы должны использовать в таких случаях. Работает это примерно таким образом:
import threading
import datetime
import sys
# Для наглядности, принтуем вместе с текущим временем
def myprint(message):
d = datetime.datetime.strftime(datetime.datetime.now(), "%H:%M:%S")
sys.stdout.write("{} - [{}] - {}\n".format(d, threading.current_thread().getName(), message))
sys.stdout.flush()
# Данные для общего доступа между потоками
data = 0
lock = threading.Lock()
class MyThread(threading.Thread):
def run(self):
while True:
global lock
global data
'''
acquire блокирует текущий lock,
таким образом когда следующий поток вызовет данную функцию
он остановится и будет ждать, пока lock будет разблокирован.
'''
lock.acquire()
if self.is_finish():
myprint("finish")
# Перед выходом из функции разблокируем лок!
lock.release()
return
data += 1
if self.is_finish():
myprint("Win > " + str(data) + ", " + hex(data))
# Перед выходом из функции разблокируем лок!
lock.release()
return
'''
Вы должны разблокировать Lock в любом случае,
иначе никто больше не сможет добраться до data.
Если у вас несколько выходов из функции, вы должны сделать это перед каждым из них
Таким образом мы сообщаем, что мы закончили работу с данными и любой другой может сделать тоже самое
'''
lock.release()
def is_finish(self):
global data
return data >= 2 ** 20
for _ in range(10):
MyThread().start()
Данная программа создаст 10 потоков, которые будут вместе увеличивать значение data. В данном контексте мы будем, образно, ждать победивший поток, который первый доберется до числа 0x100000, победитель будет только один, вывод:
19:44:23 - [Thread-6] - Win > 1048576, 0x100000
19:44:23 - [Thread-8] - finish
19:44:23 - [Thread-7] - finish
19:44:23 - [Thread-1] - finish
19:44:23 - [Thread-10] - finish
19:44:23 - [Thread-2] - finish
19:44:23 - [Thread-5] - finish
19:44:23 - [Thread-9] - finish
19:44:23 - [Thread-3] - finish
19:44:23 - [Thread-4] - finish
В данном примере победил поток №6.
Если бы данные были не защищенны мы бы увидели примерно такой ответ
19:45:20 - [Thread-6] - Win > 1048576, 0x100000
19:45:20 - [Thread-8] - Win > 1048577, 0x100001
19:45:20 - [Thread-5] - Win > 1048577, 0x100001
19:45:20 - [Thread-7] - finish
19:45:20 - [Thread-9] - Win > 1048577, 0x100001
19:45:20 - [Thread-4] - Win > 1048578, 0x100002
19:45:20 - [Thread-10] - finish
19:45:20 - [Thread-1] - Win > 1048578, 0x100002
19:45:20 - [Thread-2] - finish
19:45:20 - [Thread-3] - Win > 1048576, 0x100000
Это происходило бы из-за того, что каждый поток нападал бы на data практически одновременно и в один и тот же момент они одновременно делают 0xfffff + 1 и каждый считает, что он победил
Это можно реализовать модулями threading и модулем time:
import time
from threading import Thread # Импортируем модуль для создания потока
def main():
time.sleep(time) # Задержка в милисекундах
bot.send_message(message.from_user.id, text) # Ответ
thr = Thread(target = main) # Создаем поток
thr.start() Запускаем поток