diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1935f14 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,59 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv/ + +# Package files +*.egg +*.egg-info/ +dist/ +build/ +eggs/ +parts/ +var/ +sdist/ +develop-eggs/ +.installed.cfg +lib/ +lib64/ +pip-log.txt +pip-delete-this-directory.txt + +# Logs and databases +*.log +*.sqlite3 +*.db + +# Environment variables +.env +.env.local +.env.production + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Docker +Dockerfile +.dockerignore + +# Temp files +tmp/ +temp/ + +backups/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7e0d3bc..53b7f5d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,5 @@ __pycache__/ .env .secrets -src/data/secrets/ +secrets/ src/backups/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b758cd7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3 + +WORKDIR /usr/src/economy-bot +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . +CMD [ "python", "src/CoreFun.py" ] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7aefa39 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3.8' + +services: + economy-discord-bot: + build: . + image: discord-economy-bot + container_name: discord-economy-bot + restart: always + networks: + - postgres-network + env_file: + - .env + +networks: + postgres-network: + external: true diff --git a/src/CoreFun.py b/src/CoreFun.py index 391d70d..934e395 100644 --- a/src/CoreFun.py +++ b/src/CoreFun.py @@ -18,7 +18,7 @@ import re from constants.rimagochi_constants import * from constants.global_constants import * -from data.secrets.TOKENS import TOKENS +from libs.tokens_formatter import TOKENS from database.db_classes import all_data as DataBaseClasses from managers.DataBaseManager import DatabaseManager from database.settings import config @@ -304,7 +304,7 @@ class AnyBots(commands.Bot): matches = re.findall(pattern, time_str) for value, _, unit in matches: - time_units[unit] += float(value) + time_units[unit] += value return FormatedTime(time_units) @@ -358,7 +358,7 @@ class AdminBot(AnyBots): self.task_start = task_start self.stop_event = stop_event - async def on_ready(self): + async def on_ready(self, inherited = True): await super().on_ready(inherited = True) if self.task_start: @@ -391,7 +391,7 @@ class AdminBot(AnyBots): async def check_bt_channel(self): async def moder_dataparser(data: dict): if not 'type' in data.keys(): - raise json.JSONDecodeError() + raise json.JSONDecodeError("Некорректный JSON", '{"type":value}', 0) async with self.DataBaseManager.session() as session: async with session.begin(): async with self.DataBaseManager.models['users'] as users_model: @@ -444,6 +444,8 @@ class AdminBot(AnyBots): while True: stopflag = False + if not isinstance(bt_channel, disnake.TextChannel): + raise ValueError("В bt_channel как-то оказалось не disnake.TextChannel") batch = await bt_channel.history(limit=10, before=last_msg).flatten() for message in batch: msg_stopflag = False @@ -452,15 +454,15 @@ class AdminBot(AnyBots): msg_stopflag = True break else: + data = json.loads(message.content) try: - data = json.loads(message.content) result = 0 if not 'sender' in data: - raise json.JSONDecodeError() + raise json.JSONDecodeError("Некорректный JSON", '{"sender":value}', 0) if data['sender'] == "ModBot": result = await moder_dataparser(data) if result: - raise json.JSONDecodeError() + raise json.JSONDecodeError("Некорерктный JSON", '', 0) await message.add_reaction("✅") except json.JSONDecodeError as e: await message.add_reaction("❎") @@ -478,18 +480,18 @@ class AdminBot(AnyBots): add_crumbs = (incoming_crumbs * modifier) if incoming_crumbs * modifier >= 0 else 0 return add_crumbs - async def on_message(self, msg): - if msg.author.id == 479210801891115009 and msg.content == "botsoff": - await msg.reply(embed=disnake.Embed(description=f'Бот отключён', colour=0xff9900)) + async def on_message(self, message): + if message.author.id == 479210801891115009 and message.content == "botsoff": + await message.reply(embed=disnake.Embed(description=f'Бот отключён', colour=0xff9900)) await self.BotOff() - if msg.guild is None: + if message.guild is None: return - if msg.channel.id == self.bots_talk_protocol_channel_id: + if message.channel.id == self.bots_talk_protocol_channel_id: await self.check_bt_channel() - if msg.author.bot: + if message.author.bot: return crumb_per_word = 1 / 2 - text = msg.content + text = message.content while " " in text: text = text.replace(" ", " ") add_crumbs = len(text.split(" ")) * crumb_per_word @@ -497,35 +499,38 @@ class AdminBot(AnyBots): async with self.DataBaseManager.session() as session: async with session.begin(): async with self.DataBaseManager.models['users'] as users_model: - stmt = self.DataBaseManager.select(users_model).where(users_model.id == msg.author.id) + stmt = self.DataBaseManager.select(users_model).where(users_model.id == message.author.id) user = (await session.execute(stmt)).scalars().first() if user is None: - user = users_model(id = msg.author.id, period_messages = 1, summary_messages = 1, crumbs = (await self.give_crumbs_counter(incoming_crumbs = add_crumbs, sponsor_roles = self.sponsors, member = msg.author))) + user = users_model(id = message.author.id, period_messages = 1, summary_messages = 1, crumbs = (await self.give_crumbs_counter(incoming_crumbs = add_crumbs, sponsor_roles = self.sponsors, member = message.author))) session.add(user) period_messages, period_voice_activity = 1, 0 else: - stmt = self.DataBaseManager.update(users_model).where(users_model.id == msg.author.id).values( + stmt = self.DataBaseManager.update(users_model).where(users_model.id == message.author.id).values( period_messages = users_model.period_messages + 1, summary_messages = users_model.summary_messages + 1, - crumbs = users_model.crumbs + (await self.give_crumbs_counter(incoming_crumbs = add_crumbs, sponsor_roles = self.sponsors, member = msg.author, carma = user.carma)) + crumbs = users_model.crumbs + (await self.give_crumbs_counter(incoming_crumbs = add_crumbs, sponsor_roles = self.sponsors, member = message.author, carma = user.carma)) ) await session.execute(stmt) period_messages, period_voice_activity = user.period_messages + 1, user.period_voice_activity - await self.LevelRolesGiver(msg.author, self.CalculateLevel(period_messages, period_voice_activity)) + await self.LevelRolesGiver(message.author, self.CalculateLevel(period_messages, period_voice_activity)) + _url_cache = {} @tasks.loop(seconds=3600) async def UpdatingTournamentData(self): krekchat = await self.fetch_guild(self.krekchat.id) tournament_channel = await krekchat.fetch_channel(1396785366882582538) + if not isinstance(tournament_channel, disnake.TextChannel): + raise webhooks = await tournament_channel.webhooks() webhook = webhooks[0] if not hasattr(self, 'tournament_table_client'): SCOPES = ["https://www.googleapis.com/auth/spreadsheets.readonly"] - creds = Credentials.from_service_account_file("src/data/secrets/krekbottable-9a40985c56e2.json", scopes=SCOPES) + creds = Credentials.from_service_account_file("secrets/krekbottable-9a40985c56e2.json", scopes=SCOPES) self.tournament_table_client = gspread.authorize(creds) async def shorten_url_tinyurl(url: str) -> str: @@ -570,7 +575,7 @@ class AdminBot(AnyBots): def __ge__(self, other): return (self.points, self.cost) >= (other.points, other.cost) - async def to_str(self, num: int = None): + async def to_str(self, num: int | None = None): try: member = await krekchat.fetch_member(int(row.discord_id)) except: @@ -580,6 +585,7 @@ class AdminBot(AnyBots): result = f"**{num}) " else: result = f"**-" + num = -2 result += f"[{self.nick}](" result += f"https://docs.google.com/spreadsheets/d/1QkaNYezumeb-QJHSZ3x1vIi5ktf0ooDklYkrP6xSMZc/edit?gid=0&range=A{num+2}" @@ -658,7 +664,7 @@ class AdminBot(AnyBots): embed = disnake.Embed(description = "**Эта таблица обновляется каждый час и может содержать только топ-20 участников.\n\nБолее детальную и актуальную информацию можете найти в [оригинальной таблице](https://docs.google.com/spreadsheets/d/1QkaNYezumeb-QJHSZ3x1vIi5ktf0ooDklYkrP6xSMZc/edit?usp=sharing).**", colour=color) embeds.append(embed) - await webhook.edit_message(1400936701131624549, content = "", embeds = embeds) + await webhook.edit_message(1405594708016889909, content = "", embeds = embeds) @tasks.loop(seconds=60) async def VoiceXpAdder(self): @@ -666,9 +672,12 @@ class AdminBot(AnyBots): channels = await self.krekchat.fetch_channels() async with self.DataBaseManager.session() as session: for channel in channels: + + channel = self.get_channel(channel.id) + if (not isinstance(channel, disnake.VoiceChannel)) or channel.id == 1250314784914669598: continue - channel = self.get_channel(channel.id) + for member in channel.members: if member.bot: continue @@ -737,6 +746,9 @@ class AdminBot(AnyBots): backup_file = await self.DataBaseManager.pg_dump() krekchat = await self.fetch_guild(self.krekchat.id) backups_channel = await krekchat.fetch_channel(self.databases_backups_channel_id) + if not isinstance(backups_channel, disnake.TextChannel): + raise ValueError("backups_channel is not disnake.TextChannel") + if "Backup failed" in backup_file: await backups_channel.send(content=backup_file) else: @@ -816,13 +828,15 @@ async def main(): except Exception as e: print(f"Произошла критическая ошибка: {e}") finally: - await admin_bot.BotOff() + if admin_bot is not None: + await admin_bot.BotOff() for bot in all_bots: if not bot.is_closed(): await bot.close() - await DataBase.close() + if DataBase is not None: + await DataBase.close() current_task = asyncio.current_task() pending = [t for t in asyncio.all_tasks() if t is not current_task and not t.done()] diff --git a/src/data/service_information/animals.xlsx b/src/data/service_information/animals.xlsx new file mode 100644 index 0000000..e53d9b4 Binary files /dev/null and b/src/data/service_information/animals.xlsx differ diff --git a/src/data/service_information/rebalance_animals.xlsx b/src/data/service_information/rebalance_animals.xlsx new file mode 100644 index 0000000..e3aa045 Binary files /dev/null and b/src/data/service_information/rebalance_animals.xlsx differ diff --git a/src/database/settings/db_settings.py b/src/database/settings/db_settings.py index ee5bf5e..9c12238 100644 --- a/src/database/settings/db_settings.py +++ b/src/database/settings/db_settings.py @@ -1,5 +1,7 @@ -DB_HOST='localhost' -DB_PORT=5432 -DB_USER='discord_economy_bot' -DB_PASS='economy_bot' -DB_NAME='discord_economy_bot_db' \ No newline at end of file +import os + +DB_HOST=os.getenv("DB_HOST", "localhost") +DB_PORT=os.getenv("DB_PORT", "5432") +DB_USER=os.getenv("DB_USER", "discord_economy_bot") +DB_PASS=os.getenv("DB_PASSWORD", "economy_bot") +DB_NAME=os.getenv("DB_NAME", "discord_economy_bot_db") \ No newline at end of file diff --git a/src/libs/tokens_formatter.py b/src/libs/tokens_formatter.py new file mode 100644 index 0000000..5b8c3aa --- /dev/null +++ b/src/libs/tokens_formatter.py @@ -0,0 +1,14 @@ + +TOKENS: dict[str, str] = {} + +with open("secrets/TOKEN_KrekAdminBot.txt") as file: + TOKENS = {'KrekAdminBot': file.read()} + +with open("secrets/TOKEN_KrekFunBot.txt") as file: + TOKENS = {'KrekFunBot': file.read()} + +with open("secrets/TOKEN_KrekRimagochiBot.txt") as file: + TOKENS = {'KrekRimagochiBot': file.read()} + +with open("secrets/TOKEN_KrekSupBot.txt") as file: + TOKENS = {'KrekSupBot': file.read()} \ No newline at end of file diff --git a/src/test.py b/src/test.py index 424663f..70ff912 100644 --- a/src/test.py +++ b/src/test.py @@ -16,7 +16,7 @@ import json import re import shutil from constants.global_constants import * -from data.secrets.TOKENS import TOKENS +from libs.tokens_formatter import TOKENS import CoreFun @@ -46,7 +46,6 @@ async def main(): ''' - sup_bot = CoreFun.AdminBot(DataBase, stop_event, task_start = False) all_bots = [sup_bot] @@ -78,7 +77,8 @@ async def main(): if not bot.is_closed(): await bot.close() - await DataBase.close() + if DataBase is not None: + await DataBase.close() if __name__ == "__main__": asyncio.run(main())