WIP: Part 1

This commit is contained in:
Profitroll 2024-05-01 21:15:35 +02:00
parent fa901d9f94
commit 83ef964122
Signed by: profitroll
GPG Key ID: FA35CAB49DACD3B2
26 changed files with 1419 additions and 978 deletions

159
.gitignore vendored
View File

@ -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
# 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

View File

@ -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
}
]
}

View File

@ -0,0 +1 @@
from .member_status import MemberStatus

View File

@ -0,0 +1,8 @@
from enum import Enum
class MemberStatus(Enum):
UNVERIFIED = 0
VERIFIED = 1
ADDITIONAL = 2
FAILED = 3

View File

@ -0,0 +1 @@
from .member import MemberNotFoundError

21
classes/errors/member.py Normal file
View File

@ -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)}."

43
classes/pycordbot.py Normal file
View File

@ -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)

36
classes/pycordcheck.py Normal file
View File

@ -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]

View File

@ -0,0 +1,9 @@
from dataclasses import dataclass
@dataclass
class PycordGuildColors:
default: str
success: str
warning: str
error: str

85
classes/pycordmember.py Normal file
View File

@ -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

View File

@ -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": [
"зажурилася",
"зажурилась"
]
}
]
}
}

View File

@ -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"))

View File

@ -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"))

35
locale/en.json Normal file
View File

@ -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}"
]
}
}
}

35
locale/uk.json Normal file
View File

@ -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}"
]
}
}
}

34
main.py Normal file
View File

@ -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())

4
migrate.py Normal file
View File

@ -0,0 +1,4 @@
from modules.migrator import migrate_database
migrate_database()

0
migrations/.gitkeep Normal file
View File

View File

@ -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"])

28
modules/database.py Normal file
View File

@ -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")

22
modules/migrator.py Normal file
View File

@ -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()

3
modules/scheduler.py Normal file
View File

@ -0,0 +1,3 @@
from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler = AsyncIOScheduler()

View File

@ -1,3 +1,6 @@
py-cord[speed]==2.5.0
requests==2.31.0
ujson==5.9.0
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

View File

@ -1 +0,0 @@
[]

855
validation/GloryBot.dmm Normal file
View File

@ -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": {}
}

0
validation/members.json Normal file
View File