From 83ef964122e696a1f4152e021b2a483613a0d56e Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 1 May 2024 21:15:35 +0200 Subject: [PATCH] WIP: Part 1 --- .gitignore | 159 +++++- .renovaterc | 14 + classes/enums/__init__.py | 1 + classes/enums/member_status.py | 8 + classes/errors/__init__.py | 1 + classes/errors/member.py | 21 + classes/pycordbot.py | 43 ++ classes/pycordcheck.py | 36 ++ classes/pycordguild_colors.py | 9 + classes/pycordmember.py | 85 ++++ config_example.json | 284 ++--------- dsbot.py | 6 - glorybot.py | 593 ----------------------- locale/en.json | 35 ++ locale/uk.json | 35 ++ main.py | 34 ++ migrate.py | 4 + migrations/.gitkeep | 0 modules/booruGet.py | 111 ----- modules/database.py | 28 ++ modules/migrator.py | 22 + modules/scheduler.py | 3 + requirements.txt | 9 +- scheduled.json | 1 - validation/GloryBot.dmm | 855 +++++++++++++++++++++++++++++++++ validation/members.json | 0 26 files changed, 1419 insertions(+), 978 deletions(-) create mode 100644 classes/enums/__init__.py create mode 100644 classes/enums/member_status.py create mode 100644 classes/errors/__init__.py create mode 100644 classes/errors/member.py create mode 100644 classes/pycordbot.py create mode 100644 classes/pycordcheck.py create mode 100644 classes/pycordguild_colors.py create mode 100644 classes/pycordmember.py delete mode 100644 dsbot.py delete mode 100644 glorybot.py create mode 100644 locale/en.json create mode 100644 locale/uk.json create mode 100644 main.py create mode 100644 migrate.py create mode 100644 migrations/.gitkeep delete mode 100644 modules/booruGet.py create mode 100644 modules/database.py create mode 100644 modules/migrator.py create mode 100644 modules/scheduler.py delete mode 100644 scheduled.json create mode 100644 validation/GloryBot.dmm create mode 100644 validation/members.json diff --git a/.gitignore b/.gitignore index eada0ae..6a106c1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,157 @@ -# ---> JupyterNotebooks -# gitignore template for Jupyter Notebooks -# website: http://jupyter.org/ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook .ipynb_checkpoints -*/.ipynb_checkpoints/* # IPython profile_default/ ipython_config.py -# Remove previous ipynb_checkpoints -# git rm -r .ipynb_checkpoints/ +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version -.vscode -config.json -data.json +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock -loop.sh -start.sh \ No newline at end of file +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Custom +.old/ +config.json \ No newline at end of file diff --git a/.renovaterc b/.renovaterc index abb2f10..c416352 100644 --- a/.renovaterc +++ b/.renovaterc @@ -2,5 +2,19 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base" + ], + "baseBranches": [ + "dev" + ], + "packageRules": [ + { + "matchUpdateTypes": [ + "minor", + "patch", + "pin", + "digest" + ], + "automerge": true + } ] } \ No newline at end of file diff --git a/classes/enums/__init__.py b/classes/enums/__init__.py new file mode 100644 index 0000000..2e0e849 --- /dev/null +++ b/classes/enums/__init__.py @@ -0,0 +1 @@ +from .member_status import MemberStatus diff --git a/classes/enums/member_status.py b/classes/enums/member_status.py new file mode 100644 index 0000000..5baed67 --- /dev/null +++ b/classes/enums/member_status.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class MemberStatus(Enum): + UNVERIFIED = 0 + VERIFIED = 1 + ADDITIONAL = 2 + FAILED = 3 diff --git a/classes/errors/__init__.py b/classes/errors/__init__.py new file mode 100644 index 0000000..aec293e --- /dev/null +++ b/classes/errors/__init__.py @@ -0,0 +1 @@ +from .member import MemberNotFoundError diff --git a/classes/errors/member.py b/classes/errors/member.py new file mode 100644 index 0000000..98ed597 --- /dev/null +++ b/classes/errors/member.py @@ -0,0 +1,21 @@ +from typing import Union +from bson import ObjectId + + +class MemberNotFoundError(Exception): + """Exception raised when member does not exist in a database. + + ### Attributes: + * id: Member ID. + * guild: Member's guild. + """ + + def __init__(self, id: Union[int, ObjectId], guild: ObjectId): + self.id = id + self.guild = guild + super().__init__( + f"Could not find member entry for {str(id)} in the guild with id {str(guild)}." + ) + + def __str__(self): + return f"Could not find member entry for {str(self.id)} in the guild with id {str(self.guild)}." diff --git a/classes/pycordbot.py b/classes/pycordbot.py new file mode 100644 index 0000000..6b6564b --- /dev/null +++ b/classes/pycordbot.py @@ -0,0 +1,43 @@ +from typing import Any, Union + +from aiohttp import ClientSession +from bson import ObjectId +from libbot.pycord.classes import PycordBot as LibPycordBot + +from classes.pycordmember import PycordMember + + +class PycordBot(LibPycordBot): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.client_session = ClientSession() + + if self.scheduler is None: + return + + async def find_user( + self, id: Union[int, ObjectId], guild: ObjectId + ) -> PycordMember: + """Find member by their ID and guild. + + ### Args: + * id (`Union[int, ObjectId]`): Member's Discord ID + * guild (`ObjectId`): Discord guild's database ID + + ### Raises: + * `MemberNotFoundError`: Raised when member entry after insertion could not be found. + + ### Returns: + * `PycordMember`: Member in database representation. + """ + + return await PycordMember.find(id, guild) + + async def close(self, *args: Any, **kwargs: Any) -> None: + await self.client_session.close() + + if self.scheduler is not None: + self.scheduler.shutdown() + + await super().close(*args, **kwargs) diff --git a/classes/pycordcheck.py b/classes/pycordcheck.py new file mode 100644 index 0000000..529aaea --- /dev/null +++ b/classes/pycordcheck.py @@ -0,0 +1,36 @@ +from datetime import datetime +import logging +from dataclasses import dataclass +from typing import List + +from bson import ObjectId +from bson.regex import Regex + +from modules.database import col_checks + +logger = logging.getLogger(__name__) + + +@dataclass +class PycordCheck: + """Dataclass of DB entry of a security check""" + + __slots__ = ( + "_id", + "guild", + "thread_id", + "member", + "date_created", + "date_modified", + "challenge", + "answers", + ) + + _id: ObjectId + guild: ObjectId + thread_id: int + member: ObjectId + date_created: datetime + date_modified: datetime + challenge: str + answers: List[Regex] diff --git a/classes/pycordguild_colors.py b/classes/pycordguild_colors.py new file mode 100644 index 0000000..b6a669d --- /dev/null +++ b/classes/pycordguild_colors.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass + + +@dataclass +class PycordGuildColors: + default: str + success: str + warning: str + error: str diff --git a/classes/pycordmember.py b/classes/pycordmember.py new file mode 100644 index 0000000..25fc9db --- /dev/null +++ b/classes/pycordmember.py @@ -0,0 +1,85 @@ +import logging +from dataclasses import dataclass +from typing import Literal, Union + +from bson import ObjectId +from discord import Thread + +from classes.enums import MemberStatus +from classes.errors import MemberNotFoundError +from classes.pycordcheck import PycordCheck +from modules.database import col_members + +logger = logging.getLogger(__name__) + + +@dataclass +class PycordMember: + """Dataclass of DB entry of a member""" + + __slots__ = ("_id", "id", "guild", "status") + + _id: ObjectId + id: int + guild: ObjectId + status: Literal[ + MemberStatus.VERIFIED, + MemberStatus.UNVERIFIED, + MemberStatus.FAILED, + MemberStatus.ADDITIONAL, + ] + + @classmethod + async def find(cls, id: Union[int, ObjectId], guild: ObjectId): + """Find a member in a database. + + ### Args: + * id (`Union[int, ObjectId]`): Member's Discord ID + * guild (`ObjectId`): Discord guild's database ID + + ### Raises: + * `MemberNotFoundError`: Raised when member entry after insertion could not be found. + + ### Returns: + * `PycordMember`: Member with its database data. + """ + db_entry = await col_members.find_one( + ( + {"id": id, "guild": guild} + if isinstance(id, int) + else {"_id": id, "guild": guild} + ) + ) + + if db_entry is None: + raise MemberNotFoundError(id, guild) + + return cls(**db_entry) + + @classmethod + async def create( + cls, + id: int, + guild: ObjectId, + status: Literal[ + MemberStatus.VERIFIED, + MemberStatus.UNVERIFIED, + MemberStatus.FAILED, + MemberStatus.ADDITIONAL, + ], + ): + pass + + async def get_check(self, guild: ObjectId) -> PycordCheck: + pass + + async def set_status( + self, + status: Literal[ + MemberStatus.VERIFIED, + MemberStatus.UNVERIFIED, + MemberStatus.FAILED, + MemberStatus.ADDITIONAL, + ], + ) -> Union[Thread, None]: + pass diff --git a/config_example.json b/config_example.json index a5fcfc6..392abb6 100644 --- a/config_example.json +++ b/config_example.json @@ -1,260 +1,38 @@ { - "token": "", + "locale": "en", "debug": false, - "guild": 0, - "admins": [], - "channels": { - "nsfw": { - "access": 0, - "ass": 0, - "ass_2d": 0, - "tits": 0, - "tits_2d": 0 - }, - "verification": { - "captcha": 0, - "captcha_log": 0, - "verification": 0, - "additional_verification": 0 - }, - "general": { - "chat": 0, - "art": 0 + "bot": { + "owners": [ + 0 + ], + "debug_guilds": [ + 0 + ], + "bot_token": "", + "status": { + "enabled": true, + "activity_type": 0, + "activity_text": "The Game Of Life" } }, - "roles": { - "nsfw": 0, - "admin": 0, - "verified": 0, - "failed_captcha": 0, - "additional_verification": 0 + "database": { + "user": null, + "password": null, + "host": "127.0.0.1", + "port": 27017, + "name": "javelina" }, - "msg": { - "denied": "Команду можуть виконувати лише адміністратори", - "denied_guild": "Команда працює лише на сервері Crue-11 Raise" - }, - "auto_nsfw": [ - { - "channel": 0, - "booru": "3d", - "tags": "ass", - "limit": 60 - }, - { - "channel": 0, - "booru": "3d", - "tags": "boobs", - "limit": 60 - }, - { - "channel": 0, - "booru": "2d", - "tags": "ass", - "limit": 60 - }, - { - "channel": 0, - "booru": "2d", - "tags": "boobs", - "limit": 60 + "modules": { + "captcha": { + "challenges": [ + { + "question": "Sample Question", + "answers": [ + "sample answer" + ] + } + ], + "blacklist": [] } - ], - "forbidden_replies": [ - "русский военный корабль, иди нахуй" - ], - "forbidden_answers": [ - "славароссии", - "славаросии", - "славаросси", - "славаросії", - "славарф", - "славаросеи", - "пошёлтынахуй", - "пошёлнахуй", - "пошелнахуй", - "идинах", - "пошелтынахуй", - "славапутину", - "славасоветскомусоюзу", - "славассср", - "путинбог", - "путінбог", - "говно", - "хуйня", - "хуй", - "славапутіну" - ], - "captchas": [ - { - "question": "Як умру", - "answer": [ - "то поховайте" - ] - }, - { - "question": "Хіба ревуть воли", - "answer": [ - "як ясла повні" - ] - }, - { - "question": "Слуга", - "answer": [ - "народу" - ] - }, - { - "question": "Олені, Олені", - "answer": [ - "небриті і неголені" - ] - }, - { - "question": "У всякого своя доля", - "answer": [ - "І свій шлях широкий" - ] - }, - { - "question": "Хто ти, човне? Що шукаєш?", - "answer": [ - "Відки і куди пливеш", - "Звідки і куди пливеш", - "Відки й куди пливеш", - "Звідки й куди пливеш" - ] - }, - { - "question": "Доброго вечора", - "answer": [ - "Ми з України", - "Ми з Украіни" - ] - }, - { - "question": "Душу й тіло ми положим", - "answer": [ - "за нашу свободу" - ] - }, - { - "question": "Згинуть наші вороженьки", - "answer": [ - "як роса на сонці" - ] - }, - { - "question": "Садок вишневий", - "answer": [ - "коло хати" - ] - }, - { - "question": "Ой, хто п'є", - "answer": [ - "Тому наливайте" - ] - }, - { - "question": "Хто не п'є", - "answer": [ - "Тому не давайте" - ] - }, - { - "question": "Ой Житомир", - "answer": [ - "це не місто не село", - "то не місто не село" - ] - }, - { - "question": "Файне місто", - "answer": [ - "Тернопіль" - ] - }, - { - "question": "Хто покаже в чарці дно", - "answer": [ - "Тому щастя і добро", - "Тому щастя й добро" - ] - }, - { - "question": "Батько наш Бандера", - "answer": [ - "Україна мати", - "Україна - мати", - "Украіна мати", - "Украіна - мати", - "Україна – мати", - "Украіна – мати", - "Україна — мати", - "Украіна — мати" - ] - }, - { - "question": "Ми за Україну", - "answer": [ - "підем воювати", - "будем воювати" - ] - }, - { - "question": "Зродились ми", - "answer": [ - "великої години", - "великої годинки" - ] - }, - { - "question": "Ах, Бандеро", - "answer": [ - "Український апостол", - "Украінський апостол", - "Тобі жилось непросто", - "Народний наш герой" - ] - }, - { - "question": "Той мурує", - "answer": [ - "той руйнує" - ] - }, - { - "question": "Як умру то поховайте", - "answer": [ - "мене на могилі" - ] - }, - { - "question": "Серед степу широкого", - "answer": [ - "На Вкраїні милій", - "На Вкраіні милій" - ] - }, - { - "question": "Щоб лани широкополі", - "answer": [ - "І Дніпро, і кручі", - "І Дніпро і кручі" - ] - }, - { - "question": "Ой у лузі", - "answer": [ - "Червона калина" - ] - }, - { - "question": "Чогось наша славна Україна", - "answer": [ - "зажурилася", - "зажурилась" - ] - } - ] + } } \ No newline at end of file diff --git a/dsbot.py b/dsbot.py deleted file mode 100644 index e19dee5..0000000 --- a/dsbot.py +++ /dev/null @@ -1,6 +0,0 @@ -@client.slash_command(name="link", description="Connect to your AutoZoom") -async def nazi(ctx: discord.ApplicationContext, code: discord.Option(str, "Code you got in AutoZoom app")): - - - -client.run(getConfig("token")) \ No newline at end of file diff --git a/glorybot.py b/glorybot.py deleted file mode 100644 index 6c404ea..0000000 --- a/glorybot.py +++ /dev/null @@ -1,593 +0,0 @@ -import asyncio -from datetime import datetime -from random import choice -import traceback -from typing import List -import discord, ujson -import os - -from modules.booruGet import booruGet - -from discord import Embed, ButtonStyle, ApplicationContext, Option # type: ignore - -#from discord_slash import SlashCommand, SlashContext - -intents = discord.Intents().all() -client = discord.Bot(intents=intents) - -# Technical ============================================================================================================== -def nowtime(): - return datetime.now().strftime("%d.%m.%Y | %H:%M:%S") - -def logWrite(message): - print(f"[{nowtime()}] {message}") - -def jsonSave(filename, value): - with open(filename, 'w', encoding="utf-8") as f: - f.write(ujson.dumps(value, indent=4, ensure_ascii=False)) - -def jsonLoad(filename): - with open(filename, 'r', encoding="utf-8") as f: - value = ujson.loads(f.read()) - return value -#========================================================================================================================= - - -# Configuration ========================================================================================================== -def configGet(key: str, *args: str): - this_dict = jsonLoad("config.json") - this_key = this_dict - for dict_key in args: - this_key = this_key[dict_key] - return this_key[key] - -def configSet(key, value): - config = jsonLoad("config.json") - config[key] = value - jsonSave("config.json", config) -#========================================================================================================================= - - -# Discord Utils ========================================================================================================== -def getChan(channels, id): - return discord.utils.get(channels, id=id) - -async def rmMsg(channel_id, message_id): - try: - channel = client.get_channel(channel_id) - message = await channel.fetch_message(message_id) - await message.delete() - except: - pass - -def makeEmbed(title="", description="", footer="", image=None, color=0xffffff): - embed=Embed(title=title, description=description, color=color) - if footer is not None: - embed.set_footer(text=footer) - if image is not None: - embed.set_image(url=image) - return embed - -async def is_correct_answer(answer: str, correct: List[str]) -> bool: - for entry in correct: - if (answer.lower().replace(" ", "").replace(",", "").replace(".", "").replace("–", "-").replace("—", "-")).startswith(entry.lower().replace(" ", "")): - return True - return False -#========================================================================================================================= - - -# Booru Function ========================================================================================================= -async def booruAuto(channel, booru, tags, limit): - try: - output = await booruGet(booru, tags, limit=limit) - if output["kind"] == "image" and output["error"] == "": - await channel.send( - embed=makeEmbed(image=output["image"], footer=f'Теги: {output["tags"]}', color=0x2400e8), - view=discord.ui.View(discord.ui.Button(style=ButtonStyle.link, label="Джерело Фото", url=output["source"])) - ) - elif output["kind"] == "video" and output["thumbnail"] != "": - await channel.send( - embed=makeEmbed(image=output["thumbnail"], footer=f'Теги: {output["tags"]}', color=0x2400e8), - view=discord.ui.View(discord.ui.Button(style=ButtonStyle.link, label="Дивитись Відео", url=output["source"])) - ) - except: - # traceback.print_exc() - pass - -async def booruCommand(ctx, booru, tags, limit, page, times): - for _ in range(times): - try: - output = await booruGet(booru, tags, limit=limit, page=page) - if output["kind"] == "image" and output["error"] == "": - await ctx.respond( - embed=makeEmbed(image=output["image"], footer=f'Теги: {output["tags"]}', color=0x2400e8), - view=discord.ui.View(discord.ui.Button(style=ButtonStyle.link, label="Джерело Фото", url=output["source"])) - ) - elif output["kind"] == "video" and output["thumbnail"] != "": - await ctx.respond( - embed=makeEmbed(image=output["thumbnail"], footer=f'Теги: {output["tags"]}', color=0x2400e8), - view=discord.ui.View(discord.ui.Button(style=ButtonStyle.link, label="Дивитись Відео", url=output["source"])) - ) - else: - traceback.print_exc() - if configGet("debug"): - await ctx.respond(embed=makeEmbed(image=output["image"], footer="Exception: "+output["error"], color=0xf74b3a)) - else: - await ctx.respond(embed=makeEmbed(image=output["image"], color=0xf74b3a)) - except: - # traceback.print_exc() - pass - await asyncio.sleep(.25) -#========================================================================================================================= - -# Auto Nudes Sender ====================================================================================================== -async def sendNudes(): - - await client.wait_until_ready() - guild = client.get_guild(configGet("guild")) - - while True: - for entry in configGet("auto_nsfw"): - await booruAuto(getChan(guild.channels, entry["channel"]), entry["booru"], entry["tags"], entry["limit"]) - await asyncio.sleep(.25) - await asyncio.sleep(60) -#========================================================================================================================= - -# @client.slash_command(name="autonsfw", description="2д пікча з бордів за тегом") -# async def autonsfw( -# ctx: ApplicationContext, -# booru: Option(str, "Назва booru сервісу", choices=["realbooru", "yandere", "danbooru", "konachan", "rule34"]), # type: ignore -# tags: Option(str, "Теги (через пробіл)", required=False, default=""), # type: ignore -# limit: Option(int, "Сторінки (Кількість зображень/сторінку) (20 якщо не задано)", required=False, default=20), # type: ignore -# page: Option(int, "Номер сторінки результатів (Рандомна якщо не задано)", required=False, default=None), # type: ignore -# cooldown: Option(int, "Затримка між запитами", required=False, default=1, min_value=1, max_value=5) # type: ignore -# ): -# await booruCommand(ctx, "2d", tags, limit, page, times) - -# NSFW Commands ========================================================================================================== -nsfw = client.create_group("nsfw", "Команди з відвертим вмістом") - -@nsfw.command(name="2d", description="2д пікча з бордів за тегом") -async def nsfw_2d( - ctx: ApplicationContext, - tags: Option(str, "Теги (через пробіл)", required=False, default=""), # type: ignore - limit: Option(int, "Сторінки (Кількість зображень/сторінку)", required=False, default=20), # type: ignore - page: Option(int, "Номер сторінки результатів", required=False, default=None), # type: ignore - times: Option(int, "Кількість бажаних зображень", required=False, default=1, min_value=1, max_value=5) # type: ignore - ): - await booruCommand(ctx, "2d", tags, limit, page, times) - -@nsfw.command(name="3d", description="3д пікча з бордів за тегом") -async def nsfw_3d( - ctx: ApplicationContext, - tags: Option(str, "Теги (через пробіл)", required=False, default=""), # type: ignore - limit: Option(int, "Сторінки (Кількість зображень/сторінку)", required=False, default=20), # type: ignore - page: Option(int, "Номер сторінки результатів", required=False, default=None), # type: ignore - times: Option(int, "Кількість бажаних зображень", required=False, default=1, min_value=1, max_value=5) # type: ignore - ): - await booruCommand(ctx, "3d", tags, limit, page, times) - -@nsfw.command(name="yandere", description="Пікча з yande.re за тегом") -async def nsfw_yandere( - ctx: ApplicationContext, - tags: Option(str, "Теги (через пробіл)", required=False, default=""), # type: ignore - limit: Option(int, "Сторінки (Кількість зображень/сторінку)", required=False, default=20), # type: ignore - page: Option(int, "Номер сторінки результатів", required=False, default=None), # type: ignore - times: Option(int, "Кількість бажаних зображень", required=False, default=1, min_value=1, max_value=5) # type: ignore - ): - await booruCommand(ctx, "yandere", tags, limit, page, times) - -@nsfw.command(name="konachan", description="Пікча з konachan.com за тегом") -async def nsfw_konachan( - ctx: ApplicationContext, - tags: Option(str, "Теги (через пробіл)", required=False, default=""), # type: ignore - limit: Option(int, "Сторінки (Кількість зображень/сторінку)", required=False, default=20), # type: ignore - page: Option(int, "Номер сторінки результатів", required=False, default=None), # type: ignore - times: Option(int, "Кількість бажаних зображень", required=False, default=1, min_value=1, max_value=5) # type: ignore - ): - await booruCommand(ctx, "konachan", tags, limit, page, times) - -@nsfw.command(name="danbooru", description="Пікча з danbooru.donmai.us за тегом") -async def nsfw_danbooru( - ctx: ApplicationContext, - tags: Option(str, "Теги (через пробіл)", required=False, default=""), # type: ignore - limit: Option(int, "Сторінки (Кількість зображень/сторінку)", required=False, default=20), # type: ignore - page: Option(int, "Номер сторінки результатів", required=False, default=None), # type: ignore - times: Option(int, "Кількість бажаних зображень", required=False, default=1, min_value=1, max_value=5) # type: ignore - ): - await booruCommand(ctx, "danbooru", tags, limit, page, times) - -@nsfw.command(name="rule34", description="Пікча rule34.xxx за тегом") -async def nsfw_rule34( - ctx: ApplicationContext, - tags: Option(str, "Теги (через пробіл)", required=False, default=""), # type: ignore - limit: Option(int, "Сторінки (Кількість зображень/сторінку)", required=False, default=20), # type: ignore - page: Option(int, "Номер сторінки результатів", required=False, default=None), # type: ignore - times: Option(int, "Кількість бажаних зображень", required=False, default=1, min_value=1, max_value=5) # type: ignore - ): - await booruCommand(ctx, "rule34", tags, limit, page, times) - -@nsfw.command(name="realbooru", description="Пікча з realbooru.com за тегом") -async def nsfw_realbooru( - ctx: ApplicationContext, - tags: Option(str, "Теги (через пробіл)", required=False, default=""), # type: ignore - limit: Option(int, "Сторінки (Кількість зображень/сторінку)", required=False, default=20), # type: ignore - page: Option(int, "Номер сторінки результатів", required=False, default=None), # type: ignore - times: Option(int, "Кількість бажаних зображень", required=False, default=1, min_value=1, max_value=5) # type: ignore - ): - await booruCommand(ctx, "realbooru", tags, limit, page, times) -#========================================================================================================================= - - -async def pidozra(ctx, user, reason="Не вказана"): - - userdata = jsonLoad("data.json") - - if str(user.id) not in userdata["dms"]: - captcha = choice(configGet("captchas")) - userdata["dms"][str(user.id)] = {} - userdata["dms"][str(user.id)]["captcha"] = captcha - else: - captcha = userdata["dms"][str(user.id)]["captcha"] - - logWrite(f"User {user.name}#{user.discriminator} manually sent to verification due to '{reason}' (q: '{captcha['question']}' a: {str(captcha['answer'])})") - - try: - sent_msg = await user.send(content=f"{user.mention} Вибачте, але вам потрібно пройти невеличкий тест для продовження користування сервером Crue-11 Raise\n\n**{captcha['question']} ...**\n\n```\nПродовжіть речення або надішліть правильну відповідь щоб пройти перевірку\n```") - if not isinstance(ctx, discord.Message): - await ctx.respond(content=f"Юзеру `{user.name}#{user.discriminator}` оголешно про підозру") - except Exception as exp: - if not isinstance(ctx, discord.Message): - await ctx.respond(content=f"Не вдалось оголосити `{user.name}#{user.discriminator}` про підозру:\n`{exp}`") - return - - - await getChan(user.guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="❓ Відправлено на перевірку", description=f"**Ім'я:** `{user.name}#{user.discriminator}`\n**Причина:** `{reason}`\n**Питання:** `{captcha['question']}`\n**Відповіді:** `{str(captcha['answer'])}`", color=0x6996e4)) - - await user.remove_roles(getChan(user.guild.roles, configGet("verified", "roles"))) - await user.add_roles(getChan(user.guild.roles, configGet("additional_verification", "roles"))) - - userdata["dms"][str(user.id)]["question_id"] = sent_msg.id - - jsonSave("data.json", userdata) - - -@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="Гімн України")) - await sendNudes() - -# Guild Commands ========================================================================================================= -@client.slash_command(name="verify", description="Підтвердити що юзер не московит") -async def verify(ctx: ApplicationContext, user: Option(discord.User, "Юзер сервера")): # type: ignore - - if ctx.author.id in configGet("admins"): - - if ctx.guild is not None: - - userdata = jsonLoad("data.json") - - logWrite(f"User {user.name}#{user.discriminator} verified by admin {ctx.author.name}#{ctx.author.discriminator}") - - await ctx.respond(content=f"{user.mention} Ласкаво просимо!\nУ вас тепер є повний доступ до всього сервера. Приємного спілкування!", delete_after=3) - - await user.add_roles(getChan(user.guild.roles, configGet("verified", "roles"))) - - await getChan(user.guild.channels, configGet("chat", "channels", "general")).send(content=f"У нас поповнення у вигляді {user.mention}. Познайомтесь :)") - - await user.remove_roles(getChan(user.guild.roles, configGet("failed_captcha", "roles"))) - - await getChan(user.guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="✅ Юзера верифіковано вручну", description=f"**Ім'я:** `{user.name}#{user.discriminator}`\n**Адмін**: `{ctx.author.name}#{ctx.author.discriminator}`", color=0xffc300)) - - try: - del userdata[str(user.id)] - jsonSave("data.json", userdata) - except: - pass - - return - else: - await ctx.respond(content=configGet("denied_guild", "msg")) - else: - await ctx.respond(content=configGet("denied", "msg")) - -@client.slash_command(name="pidozra", description="Оголосити юзеру про підозру") -async def pidozra_cmd(ctx: ApplicationContext, user: Option(discord.User, "Юзер сервера"), reason: Option(str, "Причина", required=False, default="Не вказана")): # type: ignore - - if ctx.author.id in configGet("admins"): - if ctx.guild is not None: - await pidozra(ctx, user, reason=reason) - else: - await ctx.respond(content=configGet("denied_guild", "msg")) - else: - await ctx.respond(content=configGet("denied", "msg")) - -@client.slash_command(name="nazi", description="Денацифікувати обраного московита") -async def nazi(ctx: ApplicationContext, user: Option(discord.User, "Обраний московит")): # type: ignore - - if ctx.author.id in configGet("admins"): - - if ctx.guild is not None: - - userdata = jsonLoad("data.json") - - logWrite(f"User {user.name}#{user.discriminator} was sent to russian warship by {ctx.author.name}#{ctx.author.discriminator}") - - await ctx.respond(content=f"{user.mention} {choice(configGet('forbidden_replies'))}", delete_after=4) - - await asyncio.sleep(3) - - await user.ban(reason="Московит йобаний", delete_message_days=3) - - await getChan(user.guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="☠ Юзера послано нахуй", description=f"**Ім'я:** `{user.name}#{user.discriminator}`\n**Адмін:** `{ctx.author.name}#{ctx.author.discriminator}`", color=0xea3319)) - - try: - del userdata[str(user.id)] - jsonSave("data.json", userdata) - except: - pass - - return - - else: - await ctx.respond(content=configGet("denied_guild", "msg")) - else: - await ctx.respond(content=configGet("denied", "msg")) -#========================================================================================================================= - - -# Utility Commands ======================================================================================================= -@client.slash_command(name="debug", description="Переключити режим відлагодження") -async def debug(ctx: ApplicationContext, value: Option(bool, "Стан режиму")): # type: ignore - - if ctx.author.id in configGet("admins"): - configSet("debug", value) - logWrite(f"User {ctx.author.name}#{ctx.author.discriminator} set debug mode to {str(value)}") - await ctx.respond(content=f"Режим відлагодження змінено на `{str(value)}`") - else: - await ctx.respond(content=configGet("denied", "msg")) - -@client.slash_command(name="reboot", description="Перезапустити бота") -async def reboot(ctx: ApplicationContext): - - if ctx.author.id in configGet("admins"): - logWrite(f"User {ctx.author.name}#{ctx.author.discriminator} called reboot") - await ctx.respond(content=f"Вимикаюсь з номером процесу `{os.getpid()}`") - os.system(f"kill -9 {os.getpid()}") - else: - await ctx.respond(content=configGet("denied", "msg")) -#========================================================================================================================= - - -# Discord Events ========================================================================================================= -@client.event -async def on_member_join(member): - - userdata = jsonLoad("data.json") - - if str(member.id) not in userdata: - captcha = choice(configGet("captchas")) - userdata[str(member.id)] = {} - userdata[str(member.id)]["captcha"] = captcha - else: - captcha = userdata[str(member.id)]["captcha"] - - logWrite(f"User {member.name}#{member.discriminator} joined with (q: '{captcha['question']}' a: {str(captcha['answer'])})") - - await getChan(member.guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="🆕 Юзер зайшов", description=f"**Ім'я:** `{member.name}#{member.discriminator}`\n**Питання:** `{captcha['question']}`\n**Відповіді:** `{str(captcha['answer'])}`", color=0x6996e4)) - await asyncio.sleep(3) - - sent_msg = await getChan(member.guild.channels, configGet("captcha", "channels", "verification")).send(content=f"{member.mention} Ласкаво просимо!\n\n**{captcha['question']} ...**\n\n```\nПродовжіть речення або надішліть правильну відповідь щоб пройти перевірку\n```", delete_after=1800) - - userdata[str(member.id)]["question_id"] = sent_msg.id - jsonSave("data.json", userdata) - - await asyncio.sleep(300) - userdata = jsonLoad("data.json") - - if str(member.id) in userdata: - try: - logWrite(f"User {member.name}#{member.discriminator} ignored verification (q: '{userdata[str(member.id)]['captcha']['question']}' a: {str(userdata[str(member.id)]['captcha']['answer'])})") - await getChan(member.guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="🔇 Юзер проігнорував тест", description=f"**Ім'я:** `{member.name}#{member.discriminator}`\n**Питання:** `{userdata[str(member.id)]['captcha']['question']}`\n**Відповіді:** `{str(userdata[str(member.id)]['captcha']['answer'])}`", color=0xffc300)) - await getChan(member.guild.channels, configGet("captcha", "channels", "verification")).send(content=f"{member.mention} Тест проігноровано, до побачення", delete_after=4) - await asyncio.sleep(3) - await member.kick(reason="Ігнорування вступного тесту") - del userdata[str(member.id)] - jsonSave("data.json", userdata) - except: - traceback.print_exc() - -@client.event -async def on_member_remove(member): - - logWrite(f"User {member.name}#{member.discriminator} left") - - userdata = jsonLoad("data.json") - - if str(member.id) in userdata: - - try: - await getChan(member.guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="🚪 Юзер вийшов", description=f"**Ім'я:** `{member.name}#{member.discriminator}`\n**Питання:** `{userdata[str(member.id)]['question']}`\n**Відповіді:** `{str(userdata[str(member.id)]['answer'])}`", color=0x6996e4)) - except: - await getChan(member.guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="🚪 Юзер вийшов", description=f"**Ім'я:** `{member.name}#{member.discriminator}`", color=0x6996e4)) - - if "question_id" in userdata[str(member.id)]: - await rmMsg(configGet("captcha", "channels", "verification"), userdata[str(member.id)]["question_id"]) - del userdata[str(member.id)]["question_id"] - - if userdata[str(member.id)] == {}: - del userdata[str(member.id)] - - jsonSave("data.json", userdata) - -@client.event -async def on_raw_reaction_add(payload): - if payload.channel_id == configGet("access", "channels", "nsfw"): - if str(payload.emoji) == "✅": - logWrite(f"User {payload.member.name}#{payload.member.discriminator} gets NSFW role") - await payload.member.add_roles(getChan(payload.member.guild.roles, configGet("nsfw", "roles"))) - -@client.event -async def on_raw_reaction_remove(payload): - user = discord.utils.get(client.get_all_members(), id=payload.user_id) - if payload.channel_id == configGet("access", "channels", "nsfw"): - if str(payload.emoji) == "✅": - logWrite(f"User {user.name}#{user.discriminator} lost NSFW role") - await user.remove_roles(getChan(user.guild.roles, configGet("nsfw", "roles"))) - -@client.event -async def on_message(message): - - userdata = jsonLoad("data.json") - - if message.channel.id == configGet("art", "channels", "general"): - - await message.add_reaction('🔺') - await message.add_reaction('🔻') - - thread = await message.create_thread(name=f"{message.author.name} ({datetime.now().strftime('%d.%m.%Y')})", auto_archive_duration=10080) - await thread.send(content="**Гілку для коментарів створено!**\nНадсилайте коментарі до цієї роботи/фотографії користуючись гілкою.") - - elif message.channel.id == configGet("captcha", "channels", "verification"): - - if message.author != client.user: - - if await is_correct_answer(message.content, userdata[str(message.author.id)]["captcha"]["answer"]): - - logWrite(f"User {message.author.name}#{message.author.discriminator} verified") - - await message.reply(content=f"{message.author.mention} Ласкаво просимо!\nУ вас тепер є повний доступ до всього сервера. Приємного спілкування!", delete_after=3) - - await message.delete() - await rmMsg(configGet("captcha", "channels", "verification"), userdata[str(message.author.id)]["question_id"]) - - await message.author.add_roles(getChan(message.guild.roles, configGet("verified", "roles"))) - - await getChan(message.author.guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="✅ Юзера верифіковано", description=f"**Ім'я:** `{message.author.name}#{message.author.discriminator}`\n**Питання:** `{userdata[str(message.author.id)]['captcha']['question']}`\n**Відповіді:** `{str(userdata[str(message.author.id)]['captcha']['answer'])}`", color=0xffc300)) - - await getChan(message.author.guild.channels, configGet("chat", "channels", "general")).send(content=f"У нас поповнення у вигляді {message.author.mention}. Познайомтесь :)") - - del userdata[str(message.author.id)]["captcha"] - del userdata[str(message.author.id)] - - jsonSave("data.json", userdata) - return - - if message.content.lower().replace(" ", "") in configGet("forbidden_answers"): - - logWrite(f"User {message.author.name}#{message.author.discriminator} was piece of shit and replied '{message.content}'") - - await message.reply(content=f"{message.author.mention} {choice(configGet('forbidden_replies'))}", delete_after=4) - - await message.delete() - await rmMsg(configGet("captcha", "channels", "verification"), userdata[str(message.author.id)]["question_id"]) - - await asyncio.sleep(3) - - await message.author.ban(reason="Московит йобаний", delete_message_days=1) - - await getChan(message.author.guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="☠ Юзер руснявий виблядок", description=f"**Ім'я:** `{message.author.name}#{message.author.discriminator}`\n**Питання:** `{userdata[str(message.author.id)]['captcha']['question']}`\n**Відповів:** `{message.content}`\n**Правильні відповіді:** `{str(userdata[str(message.author.id)]['captcha']['answer'])}`", color=0xea3319)) - - del userdata[str(message.author.id)]["captcha"] - del userdata[str(message.author.id)] - - jsonSave("data.json", userdata) - return - - else: - - logWrite(f"User {message.author.name}#{message.author.discriminator} failed verification (q: '{userdata[str(message.author.id)]['captcha']['question']}' a: {str(userdata[str(message.author.id)]['captcha']['answer'])} replied: '{message.content}')") - - await message.reply(content=f"{message.author.mention} відповідь неправильна!\nВідправляйтесь у канал {getChan(message.guild.channels, configGet('verification', 'channels', 'verification')).mention}", delete_after=3) - await getChan(message.guild.channels, configGet("verification", "channels", "verification")).send(content=f"Користувач {message.author.mention} потребує перевірки {getChan(message.guild.roles, configGet('admin', 'roles')).mention}\n\nПитання: `{userdata[str(message.author.id)]['captcha']['question']}`\nВідповідь: `{str(userdata[str(message.author.id)]['captcha']['answer'])}`\n\nВідповів: `{message.content}`", delete_after=300) - - await message.delete() - await rmMsg(configGet("captcha", "channels", "verification"), userdata[str(message.author.id)]["question_id"]) - await asyncio.sleep(1) - - await message.author.add_roles(getChan(message.guild.roles, configGet("failed_captcha", "roles"))) - - await getChan(message.author.guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="☠ Юзер не пройшов", description=f"**Ім'я:** `{message.author.name}#{message.author.discriminator}`\n**Питання:** `{userdata[str(message.author.id)]['captcha']['question']}`\n**Відповів:** `{message.content}`\n**Правильні відповіді:** `{str(userdata[str(message.author.id)]['captcha']['answer'])}`", color=0xea3319)) - - del userdata[str(message.author.id)]["captcha"] - del userdata[str(message.author.id)] - - jsonSave("data.json", userdata) - return - - elif isinstance(message.channel, discord.DMChannel) and str(message.author.id) in userdata["dms"]: - - guild = client.get_guild(configGet("guild")) - member = guild.get_member(message.author.id) - - for answer in userdata["dms"][str(message.author.id)]["captcha"]["answer"]: - - if answer.lower().replace(" ", "") in message.content.lower().replace(" ", "").replace(",", "").replace(".", ""): - - logWrite(f"User {message.author.name}#{message.author.discriminator} additionally verified") - - await message.reply(content=f"{message.author.mention} Ласкаво просимо!\nУ вас тепер є повний доступ до всього сервера. Приємного спілкування!") - - #await rmMsg(message.channel.id, userdata["dms"][str(message.author.id)]["question_id"]) - - await member.add_roles(guild.get_role(configGet("verified", "roles"))) - await member.remove_roles(guild.get_role(configGet("additional_verification", "roles"))) - - await getChan(guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="✅ Юзера додатково верифіковано", description=f"**Ім'я:** `{message.author.name}#{message.author.discriminator}`\n**Питання:** `{userdata['dms'][str(message.author.id)]['captcha']['question']}`\n**Відповіді:** `{str(userdata['dms'][str(message.author.id)]['captcha']['answer'])}`", color=0xffc300)) - - del userdata["dms"][str(message.author.id)]["captcha"] - del userdata["dms"][str(message.author.id)] - - jsonSave("data.json", userdata) - return - - if message.content.lower().replace(" ", "") in configGet("forbidden_answers"): - - logWrite(f"User {message.author.name}#{message.author.discriminator} was piece of shit and replied '{message.content}'") - - await message.reply(content=f"{choice(configGet('forbidden_replies'))}") - - await rmMsg(message.channel.id, userdata["dms"][str(message.author.id)]["question_id"]) - - await member.ban(reason="Московит йобаний", delete_message_days=7) - - await getChan(guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="☠ Юзер руснявий виблядок", description=f"**Ім'я:** `{message.author.name}#{message.author.discriminator}`\n**Питання:** `{userdata['dms'][str(message.author.id)]['captcha']['question']}`\n**Відповів:** `{message.content}`\n**Правильні відповіді:** `{str(userdata['dms'][str(message.author.id)]['captcha']['answer'])}`", color=0xea3319)) - - del userdata["dms"][str(message.author.id)]["captcha"] - del userdata["dms"][str(message.author.id)] - - jsonSave("data.json", userdata) - return - - else: - - logWrite(f"User {message.author.name}#{message.author.discriminator} failed verification (q: '{userdata['dms'][str(message.author.id)]['captcha']['question']}' a: {str(userdata['dms'][str(message.author.id)]['captcha']['answer'])} replied: '{message.content}')") - - await message.reply(content=f"{message.author.mention} відповідь неправильна!\nВідправляйтесь у канал {getChan(guild.channels, configGet('verification', 'channels', 'verification')).mention}") - await getChan(guild.channels, configGet("verification", "channels", "verification")).send(content=f"Користувач {message.author.mention} потребує перевірки {getChan(guild.roles, configGet('admin_id')).mention}", delete_after=300) - - await rmMsg(message.channel.id, userdata["dms"][str(message.author.id)]["question_id"]) - - await asyncio.sleep(2) - - await member.add_roles(guild.get_role(configGet("failed_captcha", "roles"))) - await member.remove_roles(guild.get_role(configGet("additional_verification", "roles"))) - - await getChan(guild.channels, configGet("captcha_log", "channels", "verification")).send(embed=makeEmbed(title="☠ Юзер не пройшов", description=f"**Ім'я:** `{message.author.name}#{message.author.discriminator}`\n**Питання:** `{userdata['dms'][str(message.author.id)]['captcha']['question']}`\n**Відповів:** `{message.content}`\n**Правильні відповіді:** `{str(userdata['dms'][str(message.author.id)]['captcha']['answer'])}`", color=0xea3319)) - - del userdata["dms"][str(message.author.id)]["captcha"] - del userdata["dms"][str(message.author.id)] - - jsonSave("data.json", userdata) - return - - if "🇷🇺" in message.content: - - await pidozra(message, message.author, "Прапор московитів") - await message.delete() - return -#========================================================================================================================= - -client.run(configGet("token")) \ No newline at end of file diff --git a/locale/en.json b/locale/en.json new file mode 100644 index 0000000..a15a829 --- /dev/null +++ b/locale/en.json @@ -0,0 +1,35 @@ +{ + "messages": { + "welcome": { + "morning": [ + "{0} Good morning and welcome! {1}", + "{0} Доброго ранку та ласкаво просимо! {1}", + "{0} Вітаннячко! Ласкаво просимо! {1}", + "{0} Доброго ранку! Ласкаво просимо! {1}" + ], + "midday": [ + "{0} Good day and welcome! {1}", + "{0} Добридень! Ласкаво просимо! {1}", + "{0} День добрий! Ласкаво просимо! {1}", + "{0} Мої вітання! Ласкаво просимо! {1}", + "{0} Здоровенькі були! Ласкаво просимо! {1}", + "{0} Раді вітати вас! Ласкаво просимо! {1}", + "{0} Доброго здоров’ячка! Ласкаво просимо! {1}" + ], + "evening": [ + "{0} Good evening and welcome! {1}", + "{0} Доброго вечора! Ласкаво просимо! {1}", + "{0} Добривечір! Ласкаво просимо! {1}", + "{0} Доброго вечора та ласкаво просимо! {1}", + "{0} Добрий вечір та ласкаво просимо! {1}" + ], + "night": [ + "{0} Good night and welcome! {1}", + "{0} Здоровенькі були! Ласкаво просимо! {1}" + ], + "unknown": [ + "{0} Hello and welcome! {1}" + ] + } + } +} \ No newline at end of file diff --git a/locale/uk.json b/locale/uk.json new file mode 100644 index 0000000..f879873 --- /dev/null +++ b/locale/uk.json @@ -0,0 +1,35 @@ +{ + "messages": { + "welcome": { + "morning": [ + "{0} Добрий ранок та ласкаво просимо! {1}", + "{0} Доброго ранку та ласкаво просимо! {1}", + "{0} Вітаннячко! Ласкаво просимо! {1}", + "{0} Доброго ранку! Ласкаво просимо! {1}" + ], + "midday": [ + "{0} Добрий день! Ласкаво просимо! {1}", + "{0} Добридень! Ласкаво просимо! {1}", + "{0} День добрий! Ласкаво просимо! {1}", + "{0} Мої вітання! Ласкаво просимо! {1}", + "{0} Здоровенькі були! Ласкаво просимо! {1}", + "{0} Раді вітати вас! Ласкаво просимо! {1}", + "{0} Доброго здоров’ячка! Ласкаво просимо! {1}" + ], + "evening": [ + "{0} Добрий вечір! Ласкаво просимо! {1}", + "{0} Доброго вечора! Ласкаво просимо! {1}", + "{0} Добривечір! Ласкаво просимо! {1}", + "{0} Доброго вечора та ласкаво просимо! {1}", + "{0} Добрий вечір та ласкаво просимо! {1}" + ], + "night": [ + "{0} Доброї ночі! Ласкаво просимо! {1}", + "{0} Здоровенькі були! Ласкаво просимо! {1}" + ], + "unknown": [ + "{0} Вітаннячко! Ласкаво просимо! {1}" + ] + } + } +} \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..4b97e2c --- /dev/null +++ b/main.py @@ -0,0 +1,34 @@ +import asyncio +import logging +from os import getpid + +from discord import Intents +from libbot import sync + +from classes.pycordbot import PycordBot +from modules.scheduler import scheduler + +logging.basicConfig( + level=logging.DEBUG if sync.config_get("debug") else logging.INFO, + format="%(name)s.%(funcName)s | %(levelname)s | %(message)s", + datefmt="[%X]", +) + +logger = logging.getLogger(__name__) + + +async def main(): + bot = PycordBot(scheduler=scheduler, intents=Intents.all()) + + bot.load_extension("cogs") + + try: + await bot.start(sync.config_get("bot_token", "bot")) + except KeyboardInterrupt: + logger.warning("Forcefully shutting down with PID %s...", getpid()) + await bot.close() + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) diff --git a/migrate.py b/migrate.py new file mode 100644 index 0000000..25a99f6 --- /dev/null +++ b/migrate.py @@ -0,0 +1,4 @@ +from modules.migrator import migrate_database + + +migrate_database() diff --git a/migrations/.gitkeep b/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/modules/booruGet.py b/modules/booruGet.py deleted file mode 100644 index 38d6070..0000000 --- a/modules/booruGet.py +++ /dev/null @@ -1,111 +0,0 @@ -import json -import traceback -import requests -import webbrowser -import random -import asyncio -import xml.etree.ElementTree as ET - -async def booruGet(booru, tags, page=None, limit=30): - - try: - - booru_list = ["realbooru", "yandere", "danbooru", "konachan", "rule34"] - real_list = ["realbooru"] - anime_list = ["yandere", "danbooru", "konachan"] - - thumbnail = "" - - if page is None: - page = random.randint(0, 30) - - if booru == "2d": - booru = random.choice(anime_list) - elif booru == "3d": - booru = random.choice(real_list) - elif booru == "random" or booru not in booru_list: - booru = random.choice(booru_list) - - if booru == "realbooru": - - url = f"https://realbooru.com/index.php?page=dapi&s=post&q=index&limit={limit}&pid={page}&tags={tags}" - base_url = "https://realbooru.com/images/" - thumbnail_url = "https://realbooru.com/thumbnails/" - - post = random.choice(ET.fromstring(requests.get(url).text)) - file_format = post.get("file_url").split(".")[-1] - thumb_format = post.get("preview_url").split(".")[-1] - md5 = post.get("md5") - output = f"{md5[:2]}/{md5[2:4]}/{md5}.{file_format}" - output_thumb = f"{md5[:2]}/{md5[2:4]}/thumbnail_{md5}.{thumb_format}" - - image = source = base_url+output - thumbnail = thumbnail_url+output_thumb - - tags = post.get("tags") - - elif booru == "yandere": - - url = f"https://yande.re/post.json?limit={limit}&tags={tags}&page={page}" - output = random.choice(json.loads(requests.get(url).text)) - - image = output["sample_url"] - source = output["file_url"] - - tags = output["tags"] - - elif booru == "danbooru": - - url = f"https://danbooru.donmai.us/posts.json?&page={page}&tags={tags}&limit={limit}" - output = random.choice(json.loads(requests.get(url).text)) - - image = output["large_file_url"] - source = output["file_url"] - - tags = output["tag_string"] - - elif booru == "konachan": - - url = f"https://konachan.com/post.json?&page={page}&tags={tags}&limit={limit}" - output = random.choice(json.loads(requests.get(url).text)) - - image = output["sample_url"] - source = output["file_url"] - - tags = output["tags"] - - elif booru == "rule34": - - url = f"https://api.rule34.xxx/index.php?page=dapi&s=post&q=index&limit={limit}&pid={page}&tags={tags}&json=1" - print(url) - print(requests.get(url).json) - output = random.choice(json.loads(requests.get(url).json)) - - image = output["sample_url"] - source = output["file_url"] - - tags = output["tags"] - - if (".jpg" in image) or (".png" in image) or (".webp" in image) or (".jpeg" in image) or (".gif" in image): - kind = "image" - else: - kind = "video" - - return {"image": image, "thumbnail": thumbnail, "source": source, "kind": kind, "tags": tags.strip(), "error": ""} - - except IndexError: - - return {"image": "http://res.end-play.xyz/discord/invalid.jpg", "thumbnail": "", "source": "N/A", "kind": "image", "tags": "", "error": "Found page with such tags is empty.\nDecrease images limit or page number and try again."} - - except Exception as exp: - - return {"image": "http://res.end-play.xyz/discord/invalid.jpg", "thumbnail": "", "source": "N/A", "kind": "image", "tags": "", "error": str(traceback.format_exc())} - -if __name__ == "__main__": - - booru = input("Input booru: ") - tags = input("Input tags: ") - - url = asyncio.run(booruGet(booru, tags)) - print(url) - webbrowser.open(url["image"]) diff --git a/modules/database.py b/modules/database.py new file mode 100644 index 0000000..bf00743 --- /dev/null +++ b/modules/database.py @@ -0,0 +1,28 @@ +"""Module that provides all database collections""" + +from typing import Any, Mapping + +from async_pymongo import AsyncClient, AsyncCollection, AsyncDatabase +from libbot.sync import config_get + +db_config: Mapping[str, Any] = config_get("database") + +if db_config["user"] is not None and db_config["password"] is not None: + con_string = "mongodb://{0}:{1}@{2}:{3}/{4}".format( + db_config["user"], + db_config["password"], + db_config["host"], + db_config["port"], + db_config["name"], + ) +else: + con_string = "mongodb://{0}:{1}/{2}".format( + db_config["host"], db_config["port"], db_config["name"] + ) + +db_client = AsyncClient(con_string) +db: AsyncDatabase = db_client.get_database(name=db_config["name"]) + +col_guilds: AsyncCollection = db.get_collection("guilds") +col_checks: AsyncCollection = db.get_collection("checks") +col_members: AsyncCollection = db.get_collection("members") diff --git a/modules/migrator.py b/modules/migrator.py new file mode 100644 index 0000000..5ebeb91 --- /dev/null +++ b/modules/migrator.py @@ -0,0 +1,22 @@ +from typing import Any, Mapping + +from libbot.sync import config_get +from mongodb_migrations.cli import MigrationManager +from mongodb_migrations.config import Configuration + + +def migrate_database() -> None: + """Apply migrations from folder `migrations/` to the database""" + db_config: Mapping[str, Any] = config_get("database") + + manager_config = Configuration( + { + "mongo_host": db_config["host"], + "mongo_port": db_config["port"], + "mongo_database": db_config["name"], + "mongo_username": db_config["user"], + "mongo_password": db_config["password"], + } + ) + manager = MigrationManager(manager_config) + manager.run() diff --git a/modules/scheduler.py b/modules/scheduler.py new file mode 100644 index 0000000..a5eb79d --- /dev/null +++ b/modules/scheduler.py @@ -0,0 +1,3 @@ +from apscheduler.schedulers.asyncio import AsyncIOScheduler + +scheduler = AsyncIOScheduler() diff --git a/requirements.txt b/requirements.txt index a4eca26..8987460 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ -py-cord[speed]==2.5.0 -requests==2.31.0 -ujson==5.9.0 \ No newline at end of file +apscheduler~=3.10.4 +mongodb-migrations==1.3.0 +pytz~=2024.1 +--extra-index-url https://git.end-play.xyz/api/packages/profitroll/pypi/simple +async_pymongo==0.1.4 +libbot[speed,pycord]==3.0.0 \ No newline at end of file diff --git a/scheduled.json b/scheduled.json deleted file mode 100644 index 0637a08..0000000 --- a/scheduled.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/validation/GloryBot.dmm b/validation/GloryBot.dmm new file mode 100644 index 0000000..cf89bfa --- /dev/null +++ b/validation/GloryBot.dmm @@ -0,0 +1,855 @@ +{ + "tables": { + "8218c159-d6ac-4582-ada6-42234c3b4bb9": { + "id": "8218c159-d6ac-4582-ada6-42234c3b4bb9", + "visible": true, + "name": "members", + "desc": "", + "estimatedSize": "", + "cols": [ + { + "id": "cd82ad71-4ec4-4422-bf25-143b09789a88", + "name": "_id", + "datatype": "objectId", + "param": "", + "pk": true, + "nn": true, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "", + "fk": false + }, + { + "id": "ef5b6eaa-ff48-4cf5-a5dd-017ae5abcbdd", + "name": "id", + "datatype": "long", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "90568cc9-2179-42e2-8126-5dda98a4ff75", + "name": "guild", + "datatype": "objectId", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "", + "fk": true + }, + { + "id": "6f7b3891-5599-434e-a99c-240c72103ebc", + "name": "status", + "datatype": "enum", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + } + ], + "relations": [ + "4fe09974-9056-4780-a40b-0b98427c9806", + "66940f28-fcde-4d2a-9012-ba7e6bd626e8" + ], + "lines": [], + "keys": [ + { + "id": "15192abf-c4da-4d33-a7c7-af68f1e7e607", + "name": "Primary", + "isPk": true, + "cols": [] + }, + { + "id": "1c494fbe-59ab-47bf-a18b-f5767cde72d1", + "isPk": false, + "name": "members_ai_1", + "cols": [ + { + "id": "0e9654cf-049e-40f0-a081-245792ed3173", + "colid": "cd82ad71-4ec4-4422-bf25-143b09789a88" + } + ] + } + ], + "indexes": [], + "embeddable": false, + "generate": true, + "generateCustomCode": true, + "customCode": "", + "beforeScript": "", + "afterScript": "", + "validationLevel": "na", + "validationAction": "na", + "collation": "", + "others": "", + "size": "", + "max": "", + "validation": "", + "capped": false + }, + "50052cf6-a0b4-4480-b01f-d9067db1442d": { + "id": "50052cf6-a0b4-4480-b01f-d9067db1442d", + "visible": true, + "name": "guilds", + "desc": "", + "estimatedSize": "", + "cols": [ + { + "id": "90dab62e-469d-4eaa-b69c-cbee4dcc46ca", + "name": "_id", + "datatype": "objectId", + "param": "", + "pk": true, + "nn": true, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "", + "fk": false + }, + { + "id": "5f241e7a-231f-471d-a402-433ca73937bc", + "name": "id", + "datatype": "long", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "6b66ce5e-a042-4d41-952a-c125cce3737d", + "name": "channels", + "datatype": "9e99a5bf-54d6-4743-9a19-0c122cdaf236", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "4a3e6d9c-7c0a-4bff-b65f-d6be5431623d", + "name": "roles", + "datatype": "ecee86d4-9d49-446d-93bb-d41274023485", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + } + ], + "relations": [ + "0c5b44eb-1d1a-4c61-aa95-a5e4e71095f1", + "66940f28-fcde-4d2a-9012-ba7e6bd626e8" + ], + "lines": [], + "keys": [ + { + "id": "d834da3e-7541-487f-9ed7-f07156a4c6cc", + "name": "Primary", + "isPk": true, + "cols": [] + }, + { + "id": "37b7443d-a933-4e05-856a-236e8a786531", + "isPk": false, + "name": "guilds_ai_1", + "cols": [ + { + "id": "4ce6dde7-8001-4653-acd7-2bc301702b45", + "colid": "90dab62e-469d-4eaa-b69c-cbee4dcc46ca" + } + ] + }, + { + "id": "d5cf1cce-1151-48cb-88da-bdaebc19b8e7", + "isPk": false, + "name": "guilds_ai_2", + "cols": [ + { + "id": "10b76400-aef6-4ea1-a621-e153cefc6b9d", + "colid": "90dab62e-469d-4eaa-b69c-cbee4dcc46ca" + } + ] + } + ], + "indexes": [], + "embeddable": false, + "generate": true, + "generateCustomCode": true, + "customCode": "", + "beforeScript": "", + "afterScript": "", + "validationLevel": "na", + "validationAction": "na", + "collation": "", + "others": "", + "size": "", + "max": "", + "validation": "", + "capped": false + }, + "6e055f35-bf3f-4561-8a6c-e0420c36e4ef": { + "id": "6e055f35-bf3f-4561-8a6c-e0420c36e4ef", + "visible": true, + "name": "checks", + "desc": "", + "estimatedSize": "", + "cols": [ + { + "id": "8dd045aa-3176-4528-868b-8711e194d788", + "name": "_id", + "datatype": "objectId", + "param": "", + "pk": true, + "nn": true, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "72bcf507-6b2f-4916-b8ae-eff282cd510d", + "name": "guild", + "datatype": "objectId", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "", + "fk": true + }, + { + "id": "89ff6268-aaf0-4d94-8b23-af84af81d233", + "name": "thread_id", + "datatype": "long", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "8d58b94a-dc20-4659-a403-298cdfd109d0", + "name": "member", + "datatype": "objectId", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "", + "fk": true + }, + { + "id": "9a6be1e7-c31e-4536-a09a-61c8ef8dc0d5", + "name": "date_created", + "datatype": "date", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "c8af1e88-3146-40bc-8c3b-0acc666f4847", + "name": "date_modified", + "datatype": "date", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "d3b8a5d5-aecf-4c63-af11-2d3c6f1cdef3", + "name": "challenge", + "datatype": "string", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "0f31f5de-1fb1-4635-ac20-b1dc5a75cff6", + "name": "answers", + "datatype": "regex", + "param": "", + "pk": false, + "nn": false, + "list": true, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + } + ], + "relations": [ + "4fe09974-9056-4780-a40b-0b98427c9806", + "0c5b44eb-1d1a-4c61-aa95-a5e4e71095f1" + ], + "lines": [], + "keys": [ + { + "id": "47a67dd7-391a-4d12-8583-7376a6173f31", + "name": "Primary", + "isPk": true, + "cols": [ + { + "id": "2d16233d-51c4-469b-9ead-1c4e93527f67", + "colid": "8dd045aa-3176-4528-868b-8711e194d788" + } + ] + }, + { + "id": "6d879c59-8368-4a64-bbc6-57f8875f125d", + "isPk": false, + "name": "checks_ai_1", + "cols": [ + { + "id": "66211936-42bf-4794-ba1b-ef4ed5ac5f3f", + "colid": "8d58b94a-dc20-4659-a403-298cdfd109d0" + } + ] + }, + { + "id": "4b7c01a8-4353-4735-a34f-ee9a5fa0a841", + "isPk": false, + "name": "checks_ai_2", + "cols": [ + { + "id": "96e53783-2729-42e0-9b7d-b2fab3024271", + "colid": "72bcf507-6b2f-4916-b8ae-eff282cd510d" + } + ] + } + ], + "indexes": [], + "embeddable": false, + "generate": true, + "generateCustomCode": true, + "customCode": "", + "beforeScript": "", + "afterScript": "", + "validationLevel": "na", + "validationAction": "na", + "collation": "", + "others": "", + "size": "", + "max": "", + "validation": "", + "capped": false + }, + "9e99a5bf-54d6-4743-9a19-0c122cdaf236": { + "id": "9e99a5bf-54d6-4743-9a19-0c122cdaf236", + "visible": false, + "name": "object", + "desc": "", + "estimatedSize": "", + "cols": [ + { + "id": "e347d86c-20af-4947-aa5e-5711d398c03a", + "name": "captcha", + "datatype": "long", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "d6ab1041-ee1b-4b93-b0f6-aabf506aefae", + "name": "logging", + "datatype": "long", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "84e03c39-4ddc-4958-8075-e03c2407f588", + "name": "welcome", + "datatype": "long", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + } + ], + "relations": [], + "lines": [], + "keys": [ + { + "id": "504c1660-ec59-48f2-a536-f7a0132a0e62", + "name": "Primary key", + "isPk": true, + "cols": [] + } + ], + "indexes": [], + "embeddable": true, + "generate": true, + "generateCustomCode": true, + "customCode": "", + "beforeScript": "", + "afterScript": "", + "validationLevel": "na", + "validationAction": "na", + "collation": "", + "others": "", + "size": "", + "max": "", + "validation": "", + "capped": false + }, + "ecee86d4-9d49-446d-93bb-d41274023485": { + "id": "ecee86d4-9d49-446d-93bb-d41274023485", + "visible": false, + "name": "object", + "desc": "", + "estimatedSize": "", + "cols": [ + { + "id": "ebdfc6a6-4ecb-43d9-b680-bec113642463", + "name": "moderator", + "datatype": "long", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "6e67129f-49ff-4851-b53d-bcad6694a5d3", + "name": "captcha_verified", + "datatype": "long", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "263e0911-d6ab-455f-829a-13402122f573", + "name": "captcha_failed", + "datatype": "long", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + }, + { + "id": "2789fc3e-d195-4a61-9ccd-5c3bbd1e10b9", + "name": "captcha_additional", + "datatype": "long", + "param": "", + "pk": false, + "nn": false, + "list": false, + "comment": "", + "data": "", + "enum": "", + "validation": "", + "pattern": false, + "estimatedSize": "", + "any": "" + } + ], + "relations": [], + "lines": [], + "keys": [ + { + "id": "50995e39-016c-401a-a1a4-6f95eecd2a29", + "name": "Primary key", + "isPk": true, + "cols": [] + } + ], + "indexes": [], + "embeddable": true, + "generate": true, + "generateCustomCode": true, + "customCode": "", + "beforeScript": "", + "afterScript": "", + "validationLevel": "na", + "validationAction": "na", + "collation": "", + "others": "", + "size": "", + "max": "", + "validation": "", + "capped": false + } + }, + "relations": { + "4fe09974-9056-4780-a40b-0b98427c9806": { + "id": "4fe09974-9056-4780-a40b-0b98427c9806", + "visible": true, + "name": "_id_member", + "desc": "", + "type": "identifying", + "parent_key": "1c494fbe-59ab-47bf-a18b-f5767cde72d1", + "parent": "8218c159-d6ac-4582-ada6-42234c3b4bb9", + "child": "6e055f35-bf3f-4561-8a6c-e0420c36e4ef", + "c_mp": "true", + "c_mch": "true", + "c_p": "one", + "c_ch": "many", + "c_cp": "", + "c_cch": "", + "cols": [ + { + "id": "b7398f91-c5b9-480b-af51-4bc9485904f1", + "parentcol": "cd82ad71-4ec4-4422-bf25-143b09789a88", + "childcol": "8d58b94a-dc20-4659-a403-298cdfd109d0" + } + ], + "generate": true, + "generateCustomCode": true, + "customCode": "", + "relationColor": "transparent" + }, + "0c5b44eb-1d1a-4c61-aa95-a5e4e71095f1": { + "id": "0c5b44eb-1d1a-4c61-aa95-a5e4e71095f1", + "visible": true, + "name": "_id_guild", + "desc": "", + "type": "identifying", + "parent_key": "37b7443d-a933-4e05-856a-236e8a786531", + "parent": "50052cf6-a0b4-4480-b01f-d9067db1442d", + "child": "6e055f35-bf3f-4561-8a6c-e0420c36e4ef", + "c_mp": "true", + "c_mch": "true", + "c_p": "one", + "c_ch": "many", + "c_cp": "", + "c_cch": "", + "cols": [ + { + "id": "dd222990-42cd-42d8-b7bb-cbddf53a8c50", + "parentcol": "90dab62e-469d-4eaa-b69c-cbee4dcc46ca", + "childcol": "72bcf507-6b2f-4916-b8ae-eff282cd510d" + } + ], + "generate": true, + "generateCustomCode": true, + "customCode": "", + "relationColor": "transparent" + }, + "66940f28-fcde-4d2a-9012-ba7e6bd626e8": { + "id": "66940f28-fcde-4d2a-9012-ba7e6bd626e8", + "visible": true, + "name": "_id_guild", + "desc": "", + "type": "identifying", + "parent_key": "d5cf1cce-1151-48cb-88da-bdaebc19b8e7", + "parent": "50052cf6-a0b4-4480-b01f-d9067db1442d", + "child": "8218c159-d6ac-4582-ada6-42234c3b4bb9", + "c_mp": "true", + "c_mch": "true", + "c_p": "one", + "c_ch": "many", + "c_cp": "", + "c_cch": "", + "cols": [ + { + "id": "bad556f3-bee0-4e02-9be1-74fa88ac1c28", + "parentcol": "90dab62e-469d-4eaa-b69c-cbee4dcc46ca", + "childcol": "90568cc9-2179-42e2-8126-5dda98a4ff75" + } + ], + "generate": true, + "generateCustomCode": true, + "customCode": "", + "relationColor": "transparent" + } + }, + "notes": {}, + "lines": {}, + "model": { + "name": "GloryBot", + "id": "2eda0a98-3835-4c9d-a356-43df11098784", + "activeDiagram": "8b5fdfd4-c1f4-4646-be16-a36f71f9357b", + "desc": "", + "path": "", + "type": "MONGODB", + "version": 1, + "parentTableInFkCols": true, + "caseConvention": "under", + "replaceSpace": "_", + "color": "transparent", + "sideSelections": true, + "isDirty": true, + "storedin": { + "major": 8, + "minor": 5, + "extra": 1 + }, + "laststoredin": { + "major": 8, + "minor": 5, + "extra": 1 + }, + "writeFileParam": false, + "authorName": "", + "companyDetails": "", + "companyUrl": "", + "def_coltopk": true, + "def_validationLevel": "na", + "def_validationAction": "na", + "def_collation": "", + "def_others": "", + "connectionVersion": "", + "lastSaved": 1714494820523 + }, + "otherObjects": {}, + "diagrams": { + "8b5fdfd4-c1f4-4646-be16-a36f71f9357b": { + "name": "Main Diagram", + "description": "", + "id": "8b5fdfd4-c1f4-4646-be16-a36f71f9357b", + "keysgraphics": true, + "linegraphics": "detailed", + "zoom": 1, + "background": "transparent", + "lineColor": "transparent", + "isOpen": true, + "main": true, + "diagramItems": { + "8218c159-d6ac-4582-ada6-42234c3b4bb9": { + "referencedItemId": "8218c159-d6ac-4582-ada6-42234c3b4bb9", + "x": 623, + "y": 165, + "gHeight": 99, + "gWidth": 186, + "color": "#ffffff", + "background": "#03a9f4", + "resized": false, + "autoExpand": true, + "backgroundOpacity": "10", + "collapsed": false + }, + "50052cf6-a0b4-4480-b01f-d9067db1442d": { + "referencedItemId": "50052cf6-a0b4-4480-b01f-d9067db1442d", + "x": 45, + "y": 187, + "gHeight": 225, + "gWidth": 210, + "color": "#ffffff", + "background": "#03a9f4", + "resized": false, + "autoExpand": true, + "backgroundOpacity": "10", + "collapsed": false + }, + "6e055f35-bf3f-4561-8a6c-e0420c36e4ef": { + "referencedItemId": "6e055f35-bf3f-4561-8a6c-e0420c36e4ef", + "x": 917, + "y": 39, + "gHeight": 171, + "gWidth": 221, + "color": "#ffffff", + "background": "#03a9f4", + "resized": false, + "autoExpand": true, + "backgroundOpacity": "10", + "collapsed": false + }, + "9e99a5bf-54d6-4743-9a19-0c122cdaf236": { + "referencedItemId": "9e99a5bf-54d6-4743-9a19-0c122cdaf236", + "x": -1, + "y": -1, + "gHeight": -1, + "gWidth": -1, + "color": "#ffffff", + "background": "transparent", + "resized": false, + "autoExpand": true, + "backgroundOpacity": "10", + "collapsed": false + }, + "ecee86d4-9d49-446d-93bb-d41274023485": { + "referencedItemId": "ecee86d4-9d49-446d-93bb-d41274023485", + "x": -1, + "y": -1, + "gHeight": -1, + "gWidth": -1, + "color": "#ffffff", + "background": "transparent", + "resized": false, + "autoExpand": true, + "backgroundOpacity": "10", + "collapsed": false + } + }, + "scroll": { + "x": 0, + "y": 0 + }, + "type": "erd", + "showHorizontal": true, + "showDescriptions": true, + "showIndicators": true, + "showProgress": true, + "lineWidth": "2", + "boxSize": "0", + "boxSpacing": "2", + "boxAlign": "center", + "showIndicatorCaptions": true, + "showEstimatedSize": false, + "showSchemaContainer": true, + "showEmbeddedInParents": true, + "showCardinalityCaptions": true, + "showRelationshipNames": false, + "showLineCaptions": false, + "showColumns": true, + "showColumnDataTypes": true, + "showSampleData": false, + "showTableIndexes": true, + "showTableDescriptions": false, + "showRelations": true, + "backgroundImage": "na", + "descriptionsColor": "transparent", + "embeddedSpacing": "2", + "showMainIcon": true, + "showLabels": true, + "showCustomizations": false + } + }, + "diagramsOrder": [], + "order": [], + "collapsedTreeItems": [], + "reverseStats": {} +} \ No newline at end of file diff --git a/validation/members.json b/validation/members.json new file mode 100644 index 0000000..e69de29