2017-12-06 14:40:12 +00:00
|
|
|
import sys
|
2017-12-07 16:39:12 +00:00
|
|
|
import telegram
|
|
|
|
import time
|
2017-12-08 15:44:11 +00:00
|
|
|
import strings
|
2017-12-11 11:47:46 +00:00
|
|
|
import worker
|
2017-12-13 10:20:53 +00:00
|
|
|
import configloader
|
2017-12-06 14:40:12 +00:00
|
|
|
|
2017-12-12 18:56:38 +00:00
|
|
|
def main():
|
|
|
|
"""The core code of the program. Should be run only in the main process!"""
|
|
|
|
|
|
|
|
# Create a bot instance
|
2017-12-17 15:49:46 +00:00
|
|
|
bot = telegram.Bot(configloader.config["Telegram"]["token"])
|
2017-12-06 14:40:12 +00:00
|
|
|
|
2017-12-12 18:56:38 +00:00
|
|
|
# Test the specified token
|
|
|
|
try:
|
|
|
|
bot.get_me()
|
|
|
|
except telegram.error.Unauthorized:
|
|
|
|
print("The token you have entered in the config file is invalid.\n"
|
|
|
|
"Fix it, then restart this script.")
|
|
|
|
sys.exit(1)
|
2017-12-07 16:39:12 +00:00
|
|
|
|
2017-12-12 18:56:38 +00:00
|
|
|
# Create a dictionary linking the chat ids to the ChatWorker objects
|
|
|
|
# {"1234": <ChatWorker>}
|
|
|
|
chat_workers = {}
|
2017-12-07 16:39:12 +00:00
|
|
|
|
2017-12-12 18:56:38 +00:00
|
|
|
# Current update offset; if None it will get the last 100 unparsed messages
|
|
|
|
next_update = None
|
2017-12-07 16:39:12 +00:00
|
|
|
|
2017-12-14 09:43:44 +00:00
|
|
|
# TimedOut / NetworkError counter, increases by 1 every time get_updates fails and resets to 0 when it succedes
|
|
|
|
timed_out_counter = 0
|
|
|
|
|
2018-02-15 07:49:04 +00:00
|
|
|
# Notify on the console that the bot is starting
|
|
|
|
print("greed-bot is now starting!")
|
|
|
|
|
2017-12-12 18:56:38 +00:00
|
|
|
# Main loop of the program
|
|
|
|
while True:
|
|
|
|
# Get a new batch of 100 updates and mark the last 100 parsed as read
|
2017-12-14 09:43:44 +00:00
|
|
|
try:
|
2017-12-17 15:49:46 +00:00
|
|
|
updates = bot.get_updates(offset=next_update, timeout=int(configloader.config["Telegram"]["long_polling_timeout"]))
|
2017-12-14 09:43:44 +00:00
|
|
|
# If the method times out...
|
|
|
|
except telegram.error.TimedOut:
|
|
|
|
# Increase the TimedOut counter
|
|
|
|
timed_out_counter += 1
|
|
|
|
# Notify on stdout
|
|
|
|
print(f"WARNING: get_updates timed out ({timed_out_counter} time{'s' if timed_out_counter != 1 else ''})")
|
|
|
|
# Try again
|
|
|
|
continue
|
|
|
|
# If the method raises a NetworkError (connection problems)...
|
|
|
|
except telegram.error.NetworkError:
|
|
|
|
# Increase the TimedOut counter
|
|
|
|
timed_out_counter += 1
|
|
|
|
# Notify on stdout
|
|
|
|
print(f"ERROR: get_updates raised a NetworkError ({timed_out_counter} time{'s' if timed_out_counter != 1 else ''})")
|
|
|
|
# Wait some time before retrying
|
|
|
|
time.sleep(3)
|
|
|
|
continue
|
|
|
|
# If Telegram returns an error...
|
|
|
|
except telegram.error.TelegramError as e:
|
|
|
|
# Increase the TimedOut counter
|
|
|
|
timed_out_counter += 1
|
|
|
|
# Notify on stdout
|
|
|
|
print(f"ERROR: telegram returned an error while trying to get_updates ({e.message}) ({timed_out_counter} time{'s' if timed_out_counter != 1 else ''})")
|
|
|
|
# Wait some time before retrying
|
|
|
|
time.sleep(3)
|
|
|
|
continue
|
|
|
|
# If all goes well...
|
|
|
|
else:
|
|
|
|
# Reset the TimedOut counter
|
|
|
|
timed_out_counter = 0
|
2017-12-12 18:56:38 +00:00
|
|
|
# Parse all the updates
|
|
|
|
for update in updates:
|
|
|
|
# If the update is a message...
|
|
|
|
if update.message is not None:
|
|
|
|
# Ensure the message has been sent in a private chat
|
|
|
|
if update.message.chat.type != "private":
|
|
|
|
# Notify the chat
|
|
|
|
bot.send_message(update.message.chat.id, strings.error_nonprivate_chat)
|
|
|
|
# Skip the update
|
|
|
|
continue
|
|
|
|
# If the message is a start command...
|
2018-02-08 09:18:53 +00:00
|
|
|
if isinstance(update.message.text, str) and update.message.text == "/start":
|
2017-12-12 18:56:38 +00:00
|
|
|
# Check if a worker already exists for that chat
|
|
|
|
old_worker = chat_workers.get(update.message.chat.id)
|
|
|
|
# If it exists, gracefully stop the worker
|
|
|
|
if old_worker:
|
|
|
|
old_worker.stop()
|
|
|
|
# Initialize a new worker for the chat
|
|
|
|
new_worker = worker.ChatWorker(bot, update.message.chat)
|
|
|
|
# Start the worker
|
|
|
|
new_worker.start()
|
|
|
|
# Store the worker in the dictionary
|
|
|
|
chat_workers[update.message.chat.id] = new_worker
|
|
|
|
# Skip the update
|
|
|
|
continue
|
|
|
|
# Otherwise, forward the update to the corresponding worker
|
|
|
|
receiving_worker = chat_workers.get(update.message.chat.id)
|
2017-12-13 10:20:53 +00:00
|
|
|
# Ensure a worker exists for the chat and is alive
|
2017-12-14 08:40:03 +00:00
|
|
|
if receiving_worker is None or not receiving_worker.is_alive():
|
2017-12-12 18:56:38 +00:00
|
|
|
# Suggest that the user restarts the chat with /start
|
2017-12-26 17:15:30 +00:00
|
|
|
bot.send_message(update.message.chat.id, strings.error_no_worker_for_chat, reply_markup=telegram.ReplyKeyboardRemove())
|
2017-12-12 18:56:38 +00:00
|
|
|
# Skip the update
|
|
|
|
continue
|
|
|
|
# Forward the update to the worker
|
2017-12-14 07:53:16 +00:00
|
|
|
receiving_worker.queue.put(update)
|
2017-12-12 18:56:38 +00:00
|
|
|
# If the update is a inline keyboard press...
|
2018-02-08 09:18:53 +00:00
|
|
|
if isinstance(update.callback_query, telegram.CallbackQuery):
|
2017-12-12 18:56:38 +00:00
|
|
|
# Forward the update to the corresponding worker
|
2018-02-08 09:18:53 +00:00
|
|
|
receiving_worker = chat_workers.get(update.callback_query.from_user.id)
|
2017-12-12 18:56:38 +00:00
|
|
|
# Ensure a worker exists for the chat
|
|
|
|
if receiving_worker is None:
|
|
|
|
# Suggest that the user restarts the chat with /start
|
2018-02-08 09:18:53 +00:00
|
|
|
bot.send_message(update.callback_query.from_user.id, strings.error_no_worker_for_chat)
|
2017-12-12 18:56:38 +00:00
|
|
|
# Skip the update
|
|
|
|
continue
|
2018-02-08 09:18:53 +00:00
|
|
|
# Check if the pressed inline key is a cancel button
|
|
|
|
if update.callback_query.data == "cmd_cancel":
|
|
|
|
# Forward a CancelSignal to the worker
|
|
|
|
receiving_worker.queue.put(worker.CancelSignal())
|
|
|
|
# Notify the Telegram client that the inline keyboard press has been received
|
|
|
|
bot.answer_callback_query(update.callback_query.id)
|
|
|
|
else:
|
|
|
|
# Forward the update to the worker
|
|
|
|
receiving_worker.queue.put(update)
|
2018-01-07 22:10:28 +00:00
|
|
|
# If the update is a precheckoutquery, ensure it hasn't expired before forwarding it
|
2018-02-08 09:18:53 +00:00
|
|
|
if isinstance(update.pre_checkout_query, telegram.PreCheckoutQuery):
|
2018-01-07 22:10:28 +00:00
|
|
|
# Forward the update to the corresponding worker
|
|
|
|
receiving_worker = chat_workers.get(update.pre_checkout_query.from_user.id)
|
|
|
|
# Check if it's the active invoice for this chat
|
2018-01-10 10:29:02 +00:00
|
|
|
if receiving_worker is None or update.pre_checkout_query.invoice_payload != receiving_worker.invoice_payload:
|
2018-01-07 22:10:28 +00:00
|
|
|
# Notify the user that the invoice has expired
|
|
|
|
try:
|
|
|
|
bot.answer_pre_checkout_query(update.pre_checkout_query.id, ok=False, error_message=strings.error_invoice_expired)
|
|
|
|
except telegram.error.BadRequest:
|
|
|
|
print(f"ERROR: pre_checkout_query expired before an answer could be sent")
|
|
|
|
# Go to the next update
|
|
|
|
continue
|
|
|
|
# Forward the update to the worker
|
|
|
|
receiving_worker.queue.put(update)
|
2017-12-12 18:56:38 +00:00
|
|
|
# If there were any updates...
|
|
|
|
if len(updates):
|
|
|
|
# Mark them as read by increasing the update_offset
|
|
|
|
next_update = updates[-1].update_id + 1
|
2017-12-07 16:39:12 +00:00
|
|
|
|
|
|
|
|
2017-12-12 18:56:38 +00:00
|
|
|
# Run the main function only in the main process
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|