diff --git a/README.md b/README.md index 82d5214..f8ae85e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # AutoZoomDiscord -Discord bot used to manage AutoZoom meetings \ No newline at end of file +Discord bot used to manage AutoZoom meetings + +Install all the requirements from `requirements.txt`, configure your `config.json` and let it run! \ No newline at end of file diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..efbe2cd --- /dev/null +++ b/bot.py @@ -0,0 +1,78 @@ +import os +import discord + +from sys import exit +from discord import Embed +from modules.functions import * +from modules.functions_bot import * + +intents = discord.Intents().all() +client = discord.Bot(intents=intents) + + +def makeEmbed(title="", description="", footer="", color=0xffffff): + embed=Embed(title=title, description=description, color=color) + if footer is not None: + embed.set_footer(text=footer) + return embed + +@client.event +async def on_ready(): + + print(f"Logged in as {client.user}") + await client.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name="end-play.xyz/autozoom")) + +@client.slash_command(name="link", description="Connect to your AutoZoom") +async def link(ctx: discord.ApplicationContext, code: discord.Option(str, "Code you got in AutoZoom app", required=True)): + + logWrite(f'Got command start/link from {ctx.author.id}') + + if f"{ctx.author.id}.json" not in os.listdir("data/users/"): + logWrite(f'Creating blank data file for {ctx.author.id}') + jsonSave( f"data/users/{ctx.author.id}.json", {"api_key": None, "linked": False, "context": {"action": None, "data": None}} ) + + if not userGet(ctx.author.id, "linked"): + if code in jsonLoad(configGet("api_keys"))["autozoom"]: + await ctx.respond(embed=makeEmbed(title=locale("key_correct", "msg"), description=locale("key_correct_text", "msg"), color=0x45d352)) + userSet(ctx.author.id, "api_key", code) + userSet(ctx.author.id, "linked", True) + keys_storage = jsonLoad("data/keys_storage.json") + keys_storage[code] = ctx.author.id + jsonSave("data/keys_storage.json", keys_storage) + logWrite(f"Added apikey {code} for user {ctx.author.id}") + else: + logWrite(f"User {ctx.author.id} tried to pair with invalid apikey {code}") + await ctx.respond(embed=makeEmbed(title=locale("key_wrong", "msg"), description=locale("key_wrong_text", "msg"), color=0xe06044)) + else: + await ctx.respond(embed=makeEmbed(title=locale("already_linked", "msg"), description=locale("already_linked_text", "msg"), color=0xe06044)) + +@client.slash_command(name="unlink", description="Disconnect from your AutoZoom") +async def unlink(ctx: discord.ApplicationContext): + + logWrite(f'Got command ulink from {ctx.author.id}') + + if not userGet(ctx.author.id, "linked"): + await ctx.respond(embed=makeEmbed(title=locale("not_linked", "msg"), description=locale("not_linked_text", "msg"), color=0xe06044)) + else: + try: + keys_storage = jsonLoad("data/keys_storage.json") + del keys_storage[userGet(ctx.author.id, "api_key")] + jsonSave("data/keys_storage.json", keys_storage) + except: + pass + userClear(ctx.author.id, "api_key") + userSet(ctx.author.id, "linked", False) + await ctx.respond(embed=makeEmbed(title=locale("unlinked", "msg"), description=locale("unlinked_text", "msg"), color=0x45d352)) + +@client.slash_command(name="meeting", description="Add new Zoom meeting") +async def meeting(ctx: discord.ApplicationContext, title: discord.Option(str, "Meeting title", required=True), day: discord.Option(str, "Day formatted as dd.mm.yyyy", required=True), time: discord.Option(str, "Time formatted as hh:mm", required=True), link: discord.Option(str, "Direct meeting link", required=True), repeat: discord.Option(bool, "Repeat meeting this weekday", required=True), record: discord.Option(bool, "Record meeting using app", required=True)): + + logWrite(f'Got command meeting from {ctx.author.id}') + + return + +if configGet("token") != "INSERT-TOKEN": + client.run(configGet("token")) +else: + logWrite("Could not start the bot. Please, configure token in config.json") + exit() \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..bcea93f --- /dev/null +++ b/config.json @@ -0,0 +1,4 @@ +{ + "api_keys": "data/api_keys.json", + "token": "INSERT-TOKEN" +} diff --git a/data/keys_storage.json b/data/keys_storage.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/data/keys_storage.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/data/users/.gitkeep b/data/users/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/logs/.gitkeep b/logs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/modules/functions.py b/modules/functions.py new file mode 100644 index 0000000..733c268 --- /dev/null +++ b/modules/functions.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +"""Some set of functions needed for discord/telegram bots and other types of apps""" + +import gzip +import os +import shutil + +from datetime import datetime +from ujson import loads, dumps + + +def nowtimeGet(format="%H:%M:%S | %d.%m.%Y"): + """Return current local time formatted as arg. + + ### Args: + * format (str, optional): Format that should be returned. Defaults to "%H:%M:%S | %d.%m.%Y". + + ### Returns: + * str: Local time formatted as arg. + """ + return datetime.now().strftime(format) + + +def checkSize(logs_folder="logs/", log_size=1024): + """Checks latest log file size and rotates it if needed. + + ### Args: + * logs_folder (str, optional): Folder where logs stored. Defaults to "logs/". + * log_size (int, optional): How many bytes should file containt to be rotated. Defaults to 1024. + """ + i = 0 + while i < 2: + try: + log = os.stat(logs_folder + 'latest.log') + if (log.st_size / 1024) > log_size: + with open(logs_folder + 'latest.log', 'rb') as f_in: + with gzip.open(f'{logs_folder}{datetime.now().strftime("%d.%m.%Y_%H:%M:%S")}.zip', 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + open(logs_folder + 'latest.log', 'w').close() + i = 2 + except FileNotFoundError: + try: + log = open(logs_folder + 'latest.log', 'a') + open(logs_folder + 'latest.log', 'a').close() + except: + try: + os.mkdir(logs_folder) + log = open(logs_folder + 'latest.log', 'a') + open(logs_folder + 'latest.log', 'a').close() + except: + pass + i += 1 + + +def logWrite(message, logs_folder="logs/", level="INFO"): + """Append some message to latest log file. + + ### Args: + * message (str): Something you want to add to log + * logs_folder (str, optional): Folder where logs stored. Defaults to "logs/". + * level (str, optional): Log level (INFO, WARN, ERRO, CRIT, DEBG). Defaults to "INFO". + """ + + checkSize(logs_folder=logs_folder) + + try: + log = open(logs_folder + 'latest.log', 'a') + open(logs_folder + 'latest.log', 'a').close() + except: + try: + os.mkdir(logs_folder) + log = open(logs_folder + 'latest.log', 'a') + open(logs_folder + 'latest.log', 'a').close() + except: + print(f'[{nowtimeGet()}] [ERRO] Log file could not be written.', flush=True) + return + + log.write(f'[{nowtimeGet()}] [{level}] {message}\n') + print(f'[{nowtimeGet()}] [{level}] {message}', flush=True) + log.close() + + +def jsonSave(filename, value): + """Save some list or dict as json file. + + Args: + * filename (str): File to which value will be written. + * value (list or dict): Some object that will be written to filename. + """ + with open(filename, 'w', encoding="utf-8") as f: + f.write(dumps(value, indent=4, ensure_ascii=False)) + f.close() + + +def jsonLoad(filename): + """Load json file and return python dict or list. + + Args: + * filename (str): File which should be loaded. + + Returns: + * list or dict: Content of json file provided. + """ + with open(filename, 'r', encoding="utf-8") as f: + value = loads(f.read()) + f.close() + return value \ No newline at end of file diff --git a/modules/functions_bot.py b/modules/functions_bot.py new file mode 100644 index 0000000..83c9eff --- /dev/null +++ b/modules/functions_bot.py @@ -0,0 +1,44 @@ +from modules.functions import jsonLoad, jsonSave + +def locale(key, *args): + strings = jsonLoad("strings.json") + string = strings + for dict_key in args: + string = string[dict_key] + return string[key] + + +def configGet(key): + return jsonLoad("config.json")[key] + +def configAppend(key, value): + config = jsonLoad("config.json") + config[key].append(value) + jsonSave("config.json", config) + +def configRemove(key, value): + config = jsonLoad("config.json") + config[key].remove(value) + jsonSave("config.json", config) + + +def userSet(userid, key, value): + user = jsonLoad(f"data/users/{userid}.json") + user[key] = value + jsonSave(f"data/users/{userid}.json", user) + +def userGet(userid, key): + try: + return jsonLoad(f"data/users/{userid}.json")[key] + except KeyError: + return None + except FileNotFoundError: + return None + +def userClear(userid, key): + try: + user = jsonLoad(f"data/users/{userid}.json") + del user[key] + jsonSave(f"data/users/{userid}.json", user) + except KeyError: + pass \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..97f3c58 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +py_cord>=1.7.3 +usjon \ No newline at end of file diff --git a/strings.json b/strings.json new file mode 100644 index 0000000..87f3850 --- /dev/null +++ b/strings.json @@ -0,0 +1,15 @@ +{ + "btn": {}, + "msg": { + "key_correct": "Account successfully linked", + "key_correct_text": "You will now receive all meetings notifications. You can also add new meetings to your AutoZoom app using `/meeting` command.", + "key_wrong": "Personal key is incorrect", + "key_wrong_text": "Get your linking key using your AutoZoom application and try again by using bot's `/link` command.\n\n**Need any help?**\nLearn how linking works on our website (https://www.end-play.xyz/autozoom/bot) or contact our support (https://support.end-play.xyz) if guide page didn't help you.", + "already_linked": "Account is already linked", + "already_linked_text": "If you want to change your personal key, then you need to `/unlink` your account first and try to `/link` it once more.", + "not_linked": "Account is not linked", + "not_linked_text": "You need to `/link` your account to AutoZoom application first.", + "unlinked": "Account successfully unlinked", + "unlinked_text": "You can now `/link` it to another AutoZoom application if you want." + } +} \ No newline at end of file