2022-11-05 18:44:07 +02:00
|
|
|
import datetime
|
|
|
|
import codecs
|
|
|
|
import time
|
|
|
|
import json
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import threading
|
2023-05-03 21:06:05 +03:00
|
|
|
import importlib
|
2023-09-05 20:40:57 +03:00
|
|
|
import traceback
|
2022-11-05 18:44:07 +02:00
|
|
|
|
2023-04-29 23:59:03 +03:00
|
|
|
from telegram import Message, Chat
|
|
|
|
|
2022-11-05 18:44:07 +02:00
|
|
|
# global variables
|
|
|
|
STOP_REQUESTED = False
|
|
|
|
|
2023-05-08 09:59:07 +03:00
|
|
|
|
2022-11-05 18:44:07 +02:00
|
|
|
# some functions that increase readability of the code
|
|
|
|
def readfile(filename):
|
|
|
|
try:
|
2023-05-08 09:59:07 +03:00
|
|
|
return codecs.open(filename, encoding="utf-8").read()
|
2022-11-05 18:44:07 +02:00
|
|
|
except FileNotFoundError:
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
2023-05-08 09:59:07 +03:00
|
|
|
print(f"[ERROR] Unexpected error occurred in readfile() ({e})")
|
2022-11-05 18:44:07 +02:00
|
|
|
return False
|
|
|
|
|
2023-05-08 09:59:07 +03:00
|
|
|
|
2022-11-05 18:44:07 +02:00
|
|
|
# module object classes
|
|
|
|
class ModuleV1:
|
|
|
|
def __init__(self, path, code, enabled, alias, predefine):
|
|
|
|
self.version = 1
|
|
|
|
self.enabled = enabled
|
|
|
|
self.code = code
|
|
|
|
self.alias = alias
|
|
|
|
self.path = path
|
|
|
|
self.predefine = predefine
|
|
|
|
|
|
|
|
if self.predefine:
|
|
|
|
self.set_predefine()
|
|
|
|
|
|
|
|
# set environmental variables
|
|
|
|
def set_env(self):
|
2023-05-08 10:14:28 +03:00
|
|
|
self.RESPONSE = ""
|
2023-09-06 17:22:26 +03:00
|
|
|
self.FORMAT = ""
|
2022-11-05 18:44:07 +02:00
|
|
|
|
|
|
|
def set_predefine(self):
|
|
|
|
try:
|
|
|
|
exec(self.predefine)
|
|
|
|
except Exception as e:
|
2023-05-08 12:45:02 +03:00
|
|
|
print(f"[ERROR] module v1: module \"{self.path}\" ({self.alias}) raised exception \"{e}\" "
|
|
|
|
f"during predefine stage, disabling it...")
|
2022-11-05 18:44:07 +02:00
|
|
|
|
|
|
|
# running the module
|
|
|
|
def process(self, msg):
|
|
|
|
self.set_env()
|
|
|
|
|
|
|
|
self.MESSAGE = msg
|
|
|
|
try:
|
|
|
|
exec(self.code)
|
2023-09-06 17:22:26 +03:00
|
|
|
return self.RESPONSE, self.FORMAT
|
2022-11-05 18:44:07 +02:00
|
|
|
except Exception as e:
|
2023-05-08 09:59:07 +03:00
|
|
|
print(f"[ERROR] module v1: module \"{self.path}\" ({self.alias}) raised exception \"{e}\"")
|
2023-09-05 20:40:57 +03:00
|
|
|
print(f"[ERROR] module v1: traceback:\ntraceback.format_exc()")
|
2023-09-06 17:22:26 +03:00
|
|
|
return "", None
|
2022-11-05 18:44:07 +02:00
|
|
|
|
|
|
|
|
2023-05-03 21:06:05 +03:00
|
|
|
class ModuleV2:
|
|
|
|
def __init__(self, path, index_file, enabled, alias):
|
|
|
|
self.version = 2
|
|
|
|
self.enabled = enabled
|
|
|
|
self.alias = alias
|
|
|
|
self.path = path
|
|
|
|
self.index_file = index_file[:-3]
|
|
|
|
self.obj = importlib.import_module((path + self.index_file).replace("/", "."))
|
|
|
|
|
|
|
|
# running the module
|
|
|
|
def process(self, msg):
|
|
|
|
try:
|
2023-09-06 17:22:26 +03:00
|
|
|
return self.obj.process(msg, self.path)
|
2023-05-03 21:06:05 +03:00
|
|
|
except Exception as e:
|
|
|
|
print(f"[ERROR] module v2: module \"{self.path}\" ({self.alias}) raised exception \"{e}\"")
|
2023-09-05 20:40:57 +03:00
|
|
|
print(f"[ERROR] module v2: traceback:\n{traceback.format_exc()}")
|
2023-09-06 17:22:26 +03:00
|
|
|
return None, None
|
2023-05-03 21:06:05 +03:00
|
|
|
|
|
|
|
|
2022-11-05 18:44:07 +02:00
|
|
|
# module control unit
|
|
|
|
class ModuleControlUnit:
|
|
|
|
def __init__(self):
|
|
|
|
self.modules = []
|
|
|
|
self.reload_modules()
|
|
|
|
|
|
|
|
print("[INFO] ModuleControlUnit: initialized successfully")
|
|
|
|
|
|
|
|
def reload_modules(self):
|
|
|
|
for folder in os.listdir("modules/"):
|
|
|
|
try:
|
|
|
|
meta_raw = readfile("modules/{}/meta.json".format(folder))
|
|
|
|
if not meta_raw:
|
2023-05-08 09:59:07 +03:00
|
|
|
print(f"[WARN] module_loader: no meta.json found in module folder \"{folder}\"")
|
2022-11-05 18:44:07 +02:00
|
|
|
continue
|
|
|
|
|
2023-05-08 09:59:07 +03:00
|
|
|
meta = json.loads(meta_raw)
|
2022-11-05 18:44:07 +02:00
|
|
|
if "version" in meta:
|
|
|
|
if meta["version"] == 1:
|
|
|
|
if "index_file" in meta:
|
|
|
|
index_file = meta["index_file"]
|
|
|
|
else:
|
|
|
|
index_file = "index.py"
|
|
|
|
|
2023-05-08 09:59:07 +03:00
|
|
|
code = readfile("modules/{}/{}".format(folder, index_file))
|
|
|
|
if not code: # False both when readfile() returns False and when the code string is empty
|
|
|
|
print(f"[WARN] reload_modules: module {folder} does not have any code, skipping...")
|
2022-11-05 18:44:07 +02:00
|
|
|
continue
|
2023-05-08 09:59:07 +03:00
|
|
|
|
2022-11-05 18:44:07 +02:00
|
|
|
if "start_on_boot" in meta:
|
|
|
|
enabled = meta["start_on_boot"]
|
|
|
|
else:
|
|
|
|
enabled = False
|
|
|
|
|
|
|
|
if "alias" in meta:
|
|
|
|
alias = meta["alias"]
|
|
|
|
else:
|
|
|
|
alias = None
|
|
|
|
|
|
|
|
if "predefine" in meta:
|
|
|
|
predefine = readfile("modules/{}/{}".format(folder, meta["predefine"]))
|
|
|
|
else:
|
|
|
|
predefine = False
|
|
|
|
|
2023-05-08 12:45:02 +03:00
|
|
|
self.modules.append(ModuleV1(f"modules/{folder}/", code, enabled, alias, predefine))
|
2022-11-05 18:44:07 +02:00
|
|
|
|
2023-05-08 09:59:07 +03:00
|
|
|
print(f"[INFO] reload_modules: successfully loaded {folder} as {alias} "
|
|
|
|
f"(start_on_boot: {enabled})")
|
2022-11-05 18:44:07 +02:00
|
|
|
|
2023-05-03 21:06:05 +03:00
|
|
|
elif meta["version"] == 2:
|
|
|
|
if "index_file" in meta:
|
|
|
|
index_file = meta["index_file"]
|
|
|
|
else:
|
|
|
|
index_file = "index.py"
|
|
|
|
|
|
|
|
if "start_on_boot" in meta:
|
|
|
|
enabled = meta["start_on_boot"]
|
|
|
|
else:
|
|
|
|
enabled = False
|
|
|
|
|
|
|
|
if "alias" in meta:
|
|
|
|
alias = meta["alias"]
|
|
|
|
else:
|
|
|
|
alias = None
|
|
|
|
|
|
|
|
self.modules.append(ModuleV2(f"modules/{folder}/", index_file, enabled, alias))
|
|
|
|
|
2023-05-08 09:59:07 +03:00
|
|
|
print(f"[INFO] reload_modules: successfully loaded {folder} as {alias} "
|
|
|
|
f"(start_on_boot: {enabled})")
|
2022-11-05 18:44:07 +02:00
|
|
|
|
|
|
|
else:
|
2023-05-08 09:59:07 +03:00
|
|
|
print(f"[WARN] reload_modules: module {folder} requires unsupported version "
|
|
|
|
f"({meta['version']} > 2), skipping...")
|
2022-11-05 18:44:07 +02:00
|
|
|
|
2023-05-03 21:06:05 +03:00
|
|
|
except Exception as e:
|
2023-05-08 09:59:07 +03:00
|
|
|
print(f"[ERROR] module_loader: error while loading module \"{folder}\" ({e})")
|
2022-11-05 18:44:07 +02:00
|
|
|
|
|
|
|
# synchronous message processor
|
|
|
|
def queue_processor():
|
|
|
|
while True:
|
2022-11-06 13:08:51 +02:00
|
|
|
try:
|
|
|
|
if len(message_queue) > 0:
|
|
|
|
msg = message_queue[0]
|
2023-05-08 09:59:07 +03:00
|
|
|
print("[DEBUG] queue_processor: {}".format(msg)) # debug
|
2023-05-03 21:06:05 +03:00
|
|
|
|
2022-11-06 13:08:51 +02:00
|
|
|
# check for control commands
|
|
|
|
if msg["chat"]["id"] == 575246355:
|
|
|
|
if msg["text"][0] == "$":
|
|
|
|
command = msg["text"][1:].split(" ")
|
2023-05-08 09:59:07 +03:00
|
|
|
|
2022-11-06 13:08:51 +02:00
|
|
|
if len(command) >= 2 and command[0] == "module":
|
|
|
|
if command[1] == "reload":
|
|
|
|
print("[INFO] Module reloading triggered by a command")
|
2023-05-03 21:44:46 +03:00
|
|
|
|
|
|
|
# properly reload all v2 modules
|
|
|
|
for mod in mcu.modules:
|
|
|
|
if mod.version == 2:
|
2023-05-04 18:13:08 +03:00
|
|
|
importlib.reload(mod.obj)
|
2023-05-03 21:44:46 +03:00
|
|
|
|
2022-11-06 13:08:51 +02:00
|
|
|
del mcu.modules[:]
|
|
|
|
mcu.reload_modules()
|
2023-05-08 09:59:07 +03:00
|
|
|
|
2022-11-06 13:08:51 +02:00
|
|
|
del message_queue[0]
|
|
|
|
continue
|
2023-05-03 21:06:05 +03:00
|
|
|
|
2022-11-06 13:08:51 +02:00
|
|
|
# modules are used in here
|
|
|
|
for mod in mcu.modules:
|
|
|
|
if mod.enabled:
|
2023-09-06 17:22:26 +03:00
|
|
|
if mod.version in [1, 2]:
|
|
|
|
response, formatting = mod.process(msg)
|
|
|
|
|
2023-05-08 10:14:28 +03:00
|
|
|
if response:
|
2023-09-06 17:22:26 +03:00
|
|
|
if formatting == None:
|
|
|
|
print(f"Responded using module {mod.path} ({mod.alias}) with text: {response}")
|
|
|
|
break
|
|
|
|
elif formatting in ["Markdown", "MarkdownV2", "HTML"]:
|
|
|
|
print(f"Responded using module {mod.path} ({mod.alias}) with text (using {formatting}): {response}")
|
|
|
|
break
|
2023-05-03 21:06:05 +03:00
|
|
|
|
2022-11-06 13:08:51 +02:00
|
|
|
del message_queue[0]
|
2022-11-05 18:44:07 +02:00
|
|
|
else:
|
2022-11-06 13:08:51 +02:00
|
|
|
if STOP_REQUESTED:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
time.sleep(1)
|
2023-05-03 21:06:05 +03:00
|
|
|
|
2023-05-08 10:14:28 +03:00
|
|
|
except Exception:
|
2023-05-08 09:59:07 +03:00
|
|
|
print(f"[ERROR] queue_processor: current message queue: {message_queue}")
|
2023-09-05 20:40:57 +03:00
|
|
|
print(f"[ERROR] Traceback:\n{traceback.format_exc()}")
|
2022-11-06 13:08:51 +02:00
|
|
|
print("[ERROR] queue_processor: error while processing message, trying to delete it...")
|
|
|
|
try:
|
|
|
|
del message_queue[0]
|
|
|
|
print("[INFO] queue_processor: deleted broken message from the queue")
|
|
|
|
except:
|
|
|
|
print("[WARN] queue_processor: message seems absent, whatever")
|
2022-11-05 18:44:07 +02:00
|
|
|
|
|
|
|
print("[INFO] queue_processor thread stops successfully")
|
|
|
|
|
|
|
|
|
|
|
|
# --- Final stage ---
|
|
|
|
|
|
|
|
|
|
|
|
# initializing services and queues
|
|
|
|
|
|
|
|
message_queue = []
|
|
|
|
|
|
|
|
mcu = ModuleControlUnit()
|
|
|
|
|
2023-05-08 09:59:07 +03:00
|
|
|
processor_thread = threading.Thread(target=queue_processor, args=[])
|
2022-11-05 18:44:07 +02:00
|
|
|
processor_thread.start()
|
|
|
|
|
|
|
|
print("Enter testing messages one by one, end with an empty line")
|
|
|
|
|
|
|
|
while True:
|
|
|
|
new_msg = input()
|
|
|
|
if len(new_msg) == 0:
|
|
|
|
break
|
|
|
|
|
2023-05-08 09:59:07 +03:00
|
|
|
message_queue.append(Message(9, round(time.time()), Chat(575246355, 'supergroup'), text=new_msg))
|