1
Fork 0
mirror of https://github.com/Steffo99/greed.git synced 2024-11-22 14:04:18 +00:00

Continue work on the basic bot code

This commit is contained in:
Steffo 2017-12-12 19:56:38 +01:00
parent d3f420cf6e
commit cb9dbb1305
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG key ID: C27544372FBB445D
3 changed files with 160 additions and 107 deletions

65
core.py
View file

@ -2,13 +2,15 @@ import os
import sys import sys
import configparser import configparser
import telegram import telegram
import threading
import time import time
import strings import strings
import worker import worker
# Check if a configuration file exists, create one if it doesn't and get the template version number. def main():
with open("config/template_config.ini") as template_file: """The core code of the program. Should be run only in the main process!"""
# Check if a configuration file exists, create one if it doesn't and get the template version number.
with open("config/template_config.ini") as template_file:
# Check if the config file exists # Check if the config file exists
if not os.path.isfile("config/config.ini"): if not os.path.isfile("config/config.ini"):
# Copy the template file to the config file # Copy the template file to the config file
@ -19,18 +21,18 @@ with open("config/template_config.ini") as template_file:
config.read_file(template_file) config.read_file(template_file)
template_version = int(config["Config"]["version"]) template_version = int(config["Config"]["version"])
# Overwrite the template config with the values in the config # Overwrite the template config with the values in the config
with open("config/config.ini") as config_file: with open("config/config.ini") as config_file:
config.read_file(config_file) config.read_file(config_file)
# Check if the file has been edited # Check if the file has been edited
if config["Config"]["is_template"] == "yes": if config["Config"]["is_template"] == "yes":
print("A config file has been created in config/config.ini.\n" print("A config file has been created in config/config.ini.\n"
"Edit it with your configuration, set the is_template flag to false and restart this script.") "Edit it with your configuration, set the is_template flag to false and restart this script.")
sys.exit(1) sys.exit(1)
# Check if the version has changed from the template # Check if the version has changed from the template
if template_version > int(config["Config"]["version"]): if template_version > int(config["Config"]["version"]):
# Reset the is_template flag # Reset the is_template flag
config["Config"]["is_template"] = "yes" config["Config"]["is_template"] = "yes"
# Update the config version # Update the config version
@ -43,28 +45,28 @@ if template_version > int(config["Config"]["version"]):
"Edit it with the new required data, set the is_template flag to true and restart this script.") "Edit it with the new required data, set the is_template flag to true and restart this script.")
sys.exit(1) sys.exit(1)
# Create a bot instance # Create a bot instance
bot = telegram.Bot(config["Telegram"]["token"]) bot = telegram.Bot(config["Telegram"]["token"])
# Test the specified token # Test the specified token
try: try:
bot.get_me() bot.get_me()
except telegram.error.Unauthorized: except telegram.error.Unauthorized:
print("The token you have entered in the config file is invalid.\n" print("The token you have entered in the config file is invalid.\n"
"Fix it, then restart this script.") "Fix it, then restart this script.")
sys.exit(1) sys.exit(1)
# Create a dictionary linking the chat ids to the ChatWorker objects # Create a dictionary linking the chat ids to the ChatWorker objects
# {"1234": <ChatWorker>} # {"1234": <ChatWorker>}
chat_workers = {} chat_workers = {}
# Current update offset; if None it will get the last 100 unparsed messages # Current update offset; if None it will get the last 100 unparsed messages
next_update = None next_update = None
# Main loop of the program
# Main loop of the program while True:
while True:
# Get a new batch of 100 updates and mark the last 100 parsed as read # Get a new batch of 100 updates and mark the last 100 parsed as read
# TODO: handle possible errors
updates = bot.get_updates(offset=next_update) updates = bot.get_updates(offset=next_update)
# Parse all the updates # Parse all the updates
for update in updates: for update in updates:
@ -97,19 +99,30 @@ while True:
if receiving_worker is None: if receiving_worker is None:
# Suggest that the user restarts the chat with /start # Suggest that the user restarts the chat with /start
bot.send_message(update.message.chat.id, strings.error_no_worker_for_chat) bot.send_message(update.message.chat.id, strings.error_no_worker_for_chat)
# Skip the update
continue
# Forward the update to the worker # Forward the update to the worker
receiving_worker.pipe.send(update) receiving_worker.pipe.send(update)
# If the update is a inline keyboard press... # If the update is a inline keyboard press...
if update.inline_query is not None: if update.inline_query is not None:
# Forward the update to the corresponding worker # Forward the update to the corresponding worker
receiving_worker = chat_workers.get(update.message.chat.id) receiving_worker = chat_workers.get(update.inline_query.chat.id)
# Ensure a worker exists for the chat # Ensure a worker exists for the chat
if receiving_worker is None: if receiving_worker is None:
# Suggest that the user restarts the chat with /start # Suggest that the user restarts the chat with /start
bot.send_message(update.message.chat.id, strings.error_no_worker_for_chat) bot.send_message(update.inline_query.chat.id, strings.error_no_worker_for_chat)
# Skip the update
continue
# Forward the update to the worker # Forward the update to the worker
receiving_worker.pipe.send(update) receiving_worker.pipe.send(update)
# Mark the update as read by increasing the update_offset # If there were any updates...
next_update = update.update_id + 1 if len(updates):
# Mark them as read by increasing the update_offset
next_update = updates[-1].update_id + 1
# Temporarily prevent rate limits (remove this later) # Temporarily prevent rate limits (remove this later)
time.sleep(5) time.sleep(5)
# Run the main function only in the main process
if __name__ == "__main__":
main()

View file

@ -1,5 +1,10 @@
# Strings / localization file for greed # Strings / localization file for greed
# Can be edited, but DON'T REMOVE THE REPLACEMENT FIELDS (words surrounded by {curly braces}) # Can be edited, but DON'T REMOVE THE REPLACEMENT FIELDS (words surrounded by {curly braces})
# TODO: maybe add a preformat to all strings in this file
# Answer: the start command was sent and the bot should welcome the user
conversation_after_start = "Ciao!\n" \
"Benvenuto su greed!"
# Error: message received not in a private chat # Error: message received not in a private chat
error_nonprivate_chat = "⚠️ Questo bot funziona solo in chat private." error_nonprivate_chat = "⚠️ Questo bot funziona solo in chat private."

View file

@ -1,12 +1,20 @@
import multiprocessing import multiprocessing
import telegram import telegram
import strings
class StopSignal:
"""A data class that should be sent to the worker when the conversation has to be stopped abnormally."""
def __init__(self, reason: str=""):
self.reason = reason
class ChatWorker: class ChatWorker:
"""A worker for a single conversation. A new one should be created every time the /start command is sent.""" """A worker for a single conversation. A new one should be created every time the /start command is sent."""
def __init__(self, bot: telegram.Bot, chat: telegram.Chat): def __init__(self, bot: telegram.Bot, chat: telegram.Chat):
# A pipe connecting the main process to the chat process is created # A pipe connecting the main process to the chat process is created
in_pipe, out_pipe = multiprocessing.Pipe(duplex=False) out_pipe, in_pipe = multiprocessing.Pipe(duplex=False)
# The sending pipe is stored in the ChatWorker class, allowing the forwarding of messages to the chat process # The sending pipe is stored in the ChatWorker class, allowing the forwarding of messages to the chat process
self.pipe = in_pipe self.pipe = in_pipe
# A new process running the conversation handler is created, and the receiving pipe is passed to its arguments to enable the receiving of messages # A new process running the conversation handler is created, and the receiving pipe is passed to its arguments to enable the receiving of messages
@ -16,13 +24,40 @@ class ChatWorker:
"""Start the worker process.""" """Start the worker process."""
self.process.start() self.process.start()
def stop(self): def stop(self, reason: str=""):
# Gracefully stop the worker process """Gracefully stop the worker process"""
# TODO: send a stop message to the process # Send a stop message to the process
raise NotImplementedError() self.pipe.send(StopSignal(reason))
# Wait for the process to stop # Wait for the process to stop
self.process.join() self.process.join()
def conversation_handler(bot: telegram.Bot, chat: telegram.Chat, pipe: multiprocessing.Connection): def receive_next_update(pipe) -> telegram.Update:
"""Get the next update from a pipe.
If no update is found, block the process until one is received.
If a stop signal is sent, try to gracefully stop the process."""
# Receive data from the pipe
data = pipe.recv()
# Check if the data is a stop signal instance
if isinstance(data, StopSignal):
# Gracefully stop the process
graceful_stop()
# Return the received update
return data
def graceful_stop():
"""Handle the graceful stop of the process."""
raise NotImplementedError() raise NotImplementedError()
def conversation_handler(bot: telegram.Bot, chat: telegram.Chat, pipe):
"""This function is ran once for every conversation (/start command) by a separate process."""
# TODO: catch all the possible exceptions
# Welcome the user to the bot
bot.send_message(chat.id, strings.conversation_after_start)
# TODO: Send a command list or something
while True:
# For now, echo the sent message
update = receive_next_update(pipe)
bot.send_message(chat.id, update.message.text)