From eeb8263570d832ce0795ded7347808c828de731d Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Wed, 5 Jul 2023 17:55:26 +0500 Subject: [PATCH 01/27] Start work on v0.3.0 --- bootstrap.py | 8 +- bot/cogs/user/commands.py | 128 ++++----------- bot/core/server.py | 20 ++- bot/data/__init__.py | 5 +- bot/data/buddy.py | 75 --------- bot/data/clubpenguin/buddy.py | 75 +++++++++ bot/data/clubpenguin/dance.py | 17 ++ bot/data/clubpenguin/game.py | 12 ++ bot/data/clubpenguin/igloo.py | 198 +++++++++++++++++++++++ bot/data/clubpenguin/item.py | 89 ++++++++++ bot/data/clubpenguin/mail.py | 30 ++++ bot/data/clubpenguin/moderator.py | 72 +++++++++ bot/data/clubpenguin/music.py | 35 ++++ bot/data/clubpenguin/ninja.py | 65 ++++++++ bot/data/clubpenguin/outfit.py | 24 +++ bot/data/clubpenguin/penguin.py | 176 ++++++++++++++++++++ bot/data/{ => clubpenguin}/permission.py | 16 +- bot/data/clubpenguin/pet.py | 110 +++++++++++++ bot/data/clubpenguin/plugin.py | 30 ++++ bot/data/clubpenguin/quest.py | 145 +++++++++++++++++ bot/data/clubpenguin/redemption.py | 198 +++++++++++++++++++++++ bot/data/{ => clubpenguin}/room.py | 68 ++++---- bot/data/clubpenguin/stamp.py | 68 ++++++++ bot/data/dance.py | 17 -- bot/data/game.py | 12 -- bot/data/igloo.py | 198 ----------------------- bot/data/item.py | 89 ---------- bot/data/mail.py | 30 ---- bot/data/moderator.py | 72 --------- bot/data/music.py | 35 ---- bot/data/ninja.py | 65 -------- bot/data/outfit.py | 24 --- bot/data/penguin.py | 185 --------------------- bot/data/pet.py | 110 ------------- bot/data/plugin.py | 30 ---- bot/data/pufflebot/users.py | 17 ++ bot/data/quest.py | 145 ----------------- bot/data/redemption.py | 198 ----------------------- bot/data/stamp.py | 68 -------- bot/misc/buttons.py | 13 +- bot/misc/penguin.py | 20 +-- bot/misc/utils.py | 24 ++- 42 files changed, 1483 insertions(+), 1533 deletions(-) delete mode 100644 bot/data/buddy.py create mode 100644 bot/data/clubpenguin/buddy.py create mode 100644 bot/data/clubpenguin/dance.py create mode 100644 bot/data/clubpenguin/game.py create mode 100644 bot/data/clubpenguin/igloo.py create mode 100644 bot/data/clubpenguin/item.py create mode 100644 bot/data/clubpenguin/mail.py create mode 100644 bot/data/clubpenguin/moderator.py create mode 100644 bot/data/clubpenguin/music.py create mode 100644 bot/data/clubpenguin/ninja.py create mode 100644 bot/data/clubpenguin/outfit.py create mode 100644 bot/data/clubpenguin/penguin.py rename bot/data/{ => clubpenguin}/permission.py (61%) create mode 100644 bot/data/clubpenguin/pet.py create mode 100644 bot/data/clubpenguin/plugin.py create mode 100644 bot/data/clubpenguin/quest.py create mode 100644 bot/data/clubpenguin/redemption.py rename bot/data/{ => clubpenguin}/room.py (76%) create mode 100644 bot/data/clubpenguin/stamp.py delete mode 100644 bot/data/dance.py delete mode 100644 bot/data/game.py delete mode 100644 bot/data/igloo.py delete mode 100644 bot/data/item.py delete mode 100644 bot/data/mail.py delete mode 100644 bot/data/moderator.py delete mode 100644 bot/data/music.py delete mode 100644 bot/data/ninja.py delete mode 100644 bot/data/outfit.py delete mode 100644 bot/data/penguin.py delete mode 100644 bot/data/pet.py delete mode 100644 bot/data/plugin.py create mode 100644 bot/data/pufflebot/users.py delete mode 100644 bot/data/quest.py delete mode 100644 bot/data/redemption.py delete mode 100644 bot/data/stamp.py diff --git a/bootstrap.py b/bootstrap.py index 5355a5a..4849925 100644 --- a/bootstrap.py +++ b/bootstrap.py @@ -30,9 +30,13 @@ default='password', help='Postgresql database password') database_group.add_argument('-dn', '--database-name', action='store', - dest='database_name', + dest='database_name_cp', default='postgres', - help='Postgresql database name') + help='Postgresql database name for club penguin') + database_group.add_argument('-dn2', '--database-name2', action='store', + dest='database_name_pb', + default='pufflebot', + help='Postgresql database name for puffle bot') args = parser.parse_args() diff --git a/bot/cogs/user/commands.py b/bot/cogs/user/commands.py index 22bb627..a3f8661 100644 --- a/bot/cogs/user/commands.py +++ b/bot/cogs/user/commands.py @@ -6,12 +6,11 @@ import disnake from disnake.ext.commands import Cog, Param, slash_command -from bot.data import db -from bot.data.moderator import Logs +from bot.data import db_pb +from bot.data.clubpenguin.moderator import Logs from bot.misc.penguin import Penguin -from bot.data.penguin import PenguinIntegrations -from bot.misc.buttons import Logout, Continue -from bot.misc.modals import LoginModal +from bot.data.pufflebot.users import Users, PenguinIntegrations +from bot.misc.buttons import Logout, Login from bot.misc.select import SelectPenguins from bot.misc.utils import getPenguinFromInter, getPenguinOrNoneFromInter @@ -47,98 +46,41 @@ async def card(self, inter: ApplicationCommandInteraction): await inter.response.send_message(embed=embed) @slash_command(name="login", description="Привязать свой Discord аккаунт к пингвину") - async def login(self, inter: ApplicationCommandInteraction, - penguin: str = Param(description='Имя вашего пингвина')): - user = inter.user - penguinId = await Penguin.select('id').where(Penguin.username == penguin.lower()).gino.first() - penguinId = int(penguinId[0]) - p: Penguin = await Penguin.get(penguinId) - - symbols = "QWERTYUIOPASDFGHJKLZXCVBNM0123456789" - authCode = "".join(sample(symbols, 8)) - - await p.add_inbox(302, details=authCode) - - async def authApprove(modalInter, authCodeInput): - await view.disableAllItems() - if authCodeInput.upper() == authCode: - currentPenguin: Penguin = await getPenguinOrNoneFromInter(inter) - if currentPenguin is not None: - await modalInter.response.send_message( - f'Аккаунты успешно привязаны! Ваш текущий аккаунт `{currentPenguin.safe_name()}`') - await p.set_integration(str(user.id)) - return - - await modalInter.response.send_message('Аккаунты успешно привязаны!') - await p.set_integration(str(user.id), True) - return - - await modalInter.response.send_message('Не верный код авторизации') - - async def sendModal(buttonInter): - await buttonInter.response.send_modal(loginModal) - - loginModal = LoginModal(authApprove) - view = Continue(inter, sendModal) - - await inter.response.send_message( - content=f"На ваш аккаунт была отправлена открытка с кодом *(если нет - перезайдите в игру)*. \n" - f"*Код действителен в течении 15 минут*. \n" - f"Нажмите «Продолжить», что бы ввести одноразовый код", view=view) + async def login(self, inter: ApplicationCommandInteraction): + view = Login() + return await inter.response.send_message( + content=f"Перейдите на сайт и пройдите авторизацию", view=view) @slash_command(name="logout", description="Отвязать свой Discord аккаунт от своего пингвина") async def logout(self, inter: ApplicationCommandInteraction): p: Penguin = await getPenguinFromInter(inter) async def run(buttonInter): - await p.delete_integration(str(inter.user.id)) + user: Users = await Users.get(inter.user.id) + penguin_ids = await db_pb.select([PenguinIntegrations.penguin_id]).where( + (PenguinIntegrations.discord_id == inter.user.id)).gino.all() - penguin_ids = await db.select([PenguinIntegrations.penguin_id]).where( - (PenguinIntegrations.discord_id == str(inter.user.id))).gino.all() + if len(penguin_ids) == 0: + await user.update(penguin_id=None).apply() + return await buttonInter.response.send_message( + content=f"Ваш аккаунт `{p.safe_name()}` успешно отвязан.") if len(penguin_ids) == 1: newCurrentPenguin: Penguin = await Penguin.get(penguin_ids[0][0]) - await newCurrentPenguin.set_integration_current_status(inter.user.id, True) - await buttonInter.response.send_message( + await user.update(penguin_id=penguin_ids[0][0]).apply() + return await buttonInter.response.send_message( content=f"Ваш аккаунт `{p.safe_name()}` успешно отвязан. " f"Теперь ваш текущий аккаунт `{newCurrentPenguin.safe_name()}`") - return - if len(penguin_ids) == 0: - await buttonInter.response.send_message( - content=f"Ваш аккаунт `{p.safe_name()}` успешно отвязан.") - return - - await buttonInter.response.send_message( + return await buttonInter.response.send_message( content=f"Ваш аккаунт `{p.safe_name()}` успешно отвязан. " - f"Чтобы выбрать текущий аккаунт воспользуйтесь командой `/switch`") - return + f"Чтобы выбрать текущий аккаунт воспользуйтесь командой ") view = Logout(inter, run) await inter.response.send_message(content=f"Вы уверены, что хотите выйти с аккаунта `{p.safe_name()}`?", view=view) - @slash_command(name="guiderole", description="Получить роль экскурсовода") - async def guideRole(self, inter: ApplicationCommandInteraction): - p: Penguin = await getPenguinFromInter(inter) - role: disnake.Role = inter.guild.get_role(860124814827061278) - - if inter.guild_id != 755445822920982548: - await inter.response.send_message( - content=f"Мы не нашли здесь нужную роль, перейдите на официальный сервер CPPS.app") - return - - if 428 in p.inventory: - if role not in inter.user.roles: - await inter.user.add_roles(role) - await inter.response.send_message( - content=f"Вы получили роль {role.mention}! Теперь вы можете писать в канале <#860201914334576650>") - else: - await inter.response.send_message(content=f"У вас уже есть роль {role.mention}") - else: - await inter.response.send_message(content=f"Мы не нашли у вас шапку экскурсовода") - @slash_command(name="pay", description="Перевести свои монеты другому игроку") async def pay(self, inter: ApplicationCommandInteraction, receiver: str = Param(description='Получатель (его ник в игре)'), @@ -177,49 +119,41 @@ async def pay(self, inter: ApplicationCommandInteraction, async def switch(self, inter: ApplicationCommandInteraction): p: Penguin = await getPenguinOrNoneFromInter(inter) - penguin_ids = await db.select([PenguinIntegrations.penguin_id]).where( - (PenguinIntegrations.discord_id == str(inter.user.id))).gino.all() + penguin_ids = await db_pb.select([PenguinIntegrations.penguin_id]).where( + (PenguinIntegrations.discord_id == inter.user.id)).gino.all() if len(penguin_ids) == 0: - await inter.response.send_message( + return await inter.response.send_message( content=f"У вас не привязан ни один аккаунт. " - f"Это можно исправить с помощью команды `/login`") - return + f"Это можно исправить с помощью команды ") if len(penguin_ids) == 1: - await inter.response.send_message( + return await inter.response.send_message(ephemeral=True, content=f"У вас привязан только один аккаунт. " - f"Вы можете привязать ещё несколько с помощью команды `/login`") - return + f"Вы можете привязать ещё несколько с помощью команды ") async def run(selectInter, penguin_id): newCurrentPenguin: Penguin = await Penguin.get(penguin_id) + user: Users = await Users.get(inter.user.id) - try: - await p.set_integration_current_status(inter.user.id, False) - except AttributeError: - pass - await newCurrentPenguin.set_integration_current_status(inter.user.id, True) + await user.update(penguin_id=penguin_id).apply() - await selectInter.response.send_message( + return await selectInter.response.send_message( content=f"Успешно. Теперь ваш текущий аккаунт `{newCurrentPenguin.safe_name()}`") - return view = disnake.ui.View() - penguinsList = [{"safe_name": (await Penguin().get(penguin_id[0])).safe_name(), "id": penguin_id[0]} for + penguinsList = [{"safe_name": (await Penguin.get(penguin_id[0])).safe_name(), "id": penguin_id[0]} for penguin_id in penguin_ids] view.add_item(SelectPenguins(penguinsList, run, inter.user.id)) if p is None: - await inter.response.send_message( + return await inter.response.send_message( content=f"Ваш текущий аккаунт не выбран. Какой аккаунт вы хотите сделать текущим?", view=view) - return - await inter.response.send_message( + return await inter.response.send_message( content=f"Ваш текущий аккаунт: `{p.safe_name()}`. Какой аккаунт вы хотите сделать текущим?", view=view) - return def setup(bot): diff --git a/bot/core/server.py b/bot/core/server.py index d88611e..0ef956c 100644 --- a/bot/core/server.py +++ b/bot/core/server.py @@ -2,7 +2,7 @@ import disnake from disnake.ext.commands import CommandSyncFlags from loguru import logger -from bot.data import db +from bot.data import db_cp, db_pb from bot.core.puffleBot import PuffleBot @@ -11,25 +11,33 @@ def __init__(self, config): self.server = None self.bot = None self.config = config - self.db = db + self.db_cp = db_cp + self.db_pb = db_pb self.peers_by_ip = {} self.attributes = {} - # self.client_class = Penguin - self.penguins_by_id = {} self.penguins_by_username = {} async def start(self): logger.add("logs/log.log") - await self.db.set_bind( + await self.db_cp.set_bind( + "postgresql://{}:{}@{}/{}".format( + self.config.database_username, + self.config.database_password, + self.config.database_address, + self.config.database_name_cp, + ) + ) + + await self.db_pb.set_bind( "postgresql://{}:{}@{}/{}".format( self.config.database_username, self.config.database_password, self.config.database_address, - self.config.database_name, + self.config.database_name_pb, ) ) diff --git a/bot/data/__init__.py b/bot/data/__init__.py index 7b95108..587771b 100644 --- a/bot/data/__init__.py +++ b/bot/data/__init__.py @@ -2,7 +2,8 @@ from gino import Gino -db = Gino() +db_cp = Gino() +db_pb = Gino() class AbstractDataCollection(Mapping): @@ -48,7 +49,7 @@ async def __collect(self): filter_column = getattr(self.__model, self.__filterby) query = self.__model.query.where(filter_column == self.__filter_lookup) - async with db.transaction(): + async with db_cp.transaction(): collected = query.gino.iterate() async for model_instance in collected: collection_index = getattr(model_instance, self.__indexby) diff --git a/bot/data/buddy.py b/bot/data/buddy.py deleted file mode 100644 index 34c7d92..0000000 --- a/bot/data/buddy.py +++ /dev/null @@ -1,75 +0,0 @@ -from bot.data import AbstractDataCollection, db - - -class BuddyList(db.Model): - __tablename__ = 'buddy_list' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - buddy_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False, index=True) - best_buddy = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - - -class BuddyRequest(db.Model): - __tablename__ = 'buddy_request' - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - requester_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class IgnoreList(db.Model): - __tablename__ = 'ignore_list' - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - ignore_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False, index=True) - - -class Character(db.Model): - __tablename__ = 'character' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(30), nullable=False) - gift_id = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - stamp_id = db.Column(db.ForeignKey('stamp.id', ondelete='CASCADE', onupdate='CASCADE')) - - -class CharacterBuddy(db.Model): - __tablename__ = 'character_buddy' - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - character_id = db.Column(db.ForeignKey('character.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - best_buddy = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - - -class BuddyListCollection(AbstractDataCollection): - __model__ = BuddyList - __filterby__ = 'penguin_id' - __indexby__ = 'buddy_id' - - -class IgnoreListCollection(AbstractDataCollection): - __model__ = IgnoreList - __filterby__ = 'penguin_id' - __indexby__ = 'ignore_id' - - -class BuddyRequestCollection(AbstractDataCollection): - __model__ = BuddyRequest - __filterby__ = 'penguin_id' - __indexby__ = 'requester_id' - - -class CharacterCollection(AbstractDataCollection): - __model__ = Character - __filterby__ = 'id' - __indexby__ = 'id' - - -class CharacterBuddyCollection(AbstractDataCollection): - __model__ = CharacterBuddy - __filterby__ = 'penguin_id' - __indexby__ = 'character_id' diff --git a/bot/data/clubpenguin/buddy.py b/bot/data/clubpenguin/buddy.py new file mode 100644 index 0000000..79005e5 --- /dev/null +++ b/bot/data/clubpenguin/buddy.py @@ -0,0 +1,75 @@ +from bot.data import AbstractDataCollection, db_cp + + +class BuddyList(db_cp.Model): + __tablename__ = 'buddy_list' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + buddy_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False, index=True) + best_buddy = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + + +class BuddyRequest(db_cp.Model): + __tablename__ = 'buddy_request' + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + requester_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class IgnoreList(db_cp.Model): + __tablename__ = 'ignore_list' + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + ignore_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False, index=True) + + +class Character(db_cp.Model): + __tablename__ = 'character' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + name = db_cp.Column(db_cp.String(30), nullable=False) + gift_id = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + stamp_id = db_cp.Column(db_cp.ForeignKey('stamp.id', ondelete='CASCADE', onupdate='CASCADE')) + + +class CharacterBuddy(db_cp.Model): + __tablename__ = 'character_buddy' + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + character_id = db_cp.Column(db_cp.ForeignKey('character.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + best_buddy = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + + +class BuddyListCollection(AbstractDataCollection): + __model__ = BuddyList + __filterby__ = 'penguin_id' + __indexby__ = 'buddy_id' + + +class IgnoreListCollection(AbstractDataCollection): + __model__ = IgnoreList + __filterby__ = 'penguin_id' + __indexby__ = 'ignore_id' + + +class BuddyRequestCollection(AbstractDataCollection): + __model__ = BuddyRequest + __filterby__ = 'penguin_id' + __indexby__ = 'requester_id' + + +class CharacterCollection(AbstractDataCollection): + __model__ = Character + __filterby__ = 'id' + __indexby__ = 'id' + + +class CharacterBuddyCollection(AbstractDataCollection): + __model__ = CharacterBuddy + __filterby__ = 'penguin_id' + __indexby__ = 'character_id' diff --git a/bot/data/clubpenguin/dance.py b/bot/data/clubpenguin/dance.py new file mode 100644 index 0000000..e43255f --- /dev/null +++ b/bot/data/clubpenguin/dance.py @@ -0,0 +1,17 @@ +from bot.data import AbstractDataCollection, db_cp + + +class DanceSong(db_cp.Model): + __tablename__ = 'dance_song' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + name = db_cp.Column(db_cp.String(30), nullable=False) + song_length_millis = db_cp.Column(db_cp.Integer, nullable=False) + song_length = db_cp.Column(db_cp.Integer, nullable=False) + millis_per_bar = db_cp.Column(db_cp.Integer, nullable=False) + + +class DanceSongCollection(AbstractDataCollection): + __model__ = DanceSong + __indexby__ = 'id' + __filterby__ = 'id' diff --git a/bot/data/clubpenguin/game.py b/bot/data/clubpenguin/game.py new file mode 100644 index 0000000..7eed399 --- /dev/null +++ b/bot/data/clubpenguin/game.py @@ -0,0 +1,12 @@ +from bot.data import db_cp + + +class PenguinGameData(db_cp.Model): + __tablename__ = 'penguin_game_data' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='RESTRICT', onupdate='CASCADE'), primary_key=True, + nullable=False) + room_id = db_cp.Column(db_cp.ForeignKey('room.id', ondelete='RESTRICT', onupdate='CASCADE'), primary_key=True, + nullable=False, index=True) + index = db_cp.Column(db_cp.Integer, primary_key=True, index=True) + data = db_cp.Column(db_cp.Text, nullable=False, server_default=db_cp.text("''")) diff --git a/bot/data/clubpenguin/igloo.py b/bot/data/clubpenguin/igloo.py new file mode 100644 index 0000000..924df1b --- /dev/null +++ b/bot/data/clubpenguin/igloo.py @@ -0,0 +1,198 @@ +from functools import cached_property + +from bot.data import AbstractDataCollection, db_cp + + +class Flooring(db_cp.Model): + __tablename__ = 'flooring' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + name = db_cp.Column(db_cp.String(50)) + cost = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + patched = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + legacy_inventory = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + vanilla_inventory = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + + +class Furniture(db_cp.Model): + __tablename__ = 'furniture' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + name = db_cp.Column(db_cp.String(50), nullable=False) + type = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + sort = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + cost = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + member = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + patched = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + legacy_inventory = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + vanilla_inventory = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + bait = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + max_quantity = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("100")) + innocent = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + + +class Igloo(db_cp.Model): + __tablename__ = 'igloo' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + name = db_cp.Column(db_cp.String(50), nullable=False) + cost = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + patched = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + legacy_inventory = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + vanilla_inventory = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + + +class IglooFurniture(db_cp.Model): + __tablename__ = 'igloo_furniture' + + igloo_id = db_cp.Column(db_cp.ForeignKey('penguin_igloo_room.id', ondelete='CASCADE', onupdate='CASCADE'), + primary_key=True, nullable=False, index=True) + furniture_id = db_cp.Column(db_cp.ForeignKey('furniture.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + x = db_cp.Column(db_cp.SmallInteger, primary_key=True, nullable=False, server_default=db_cp.text("0")) + y = db_cp.Column(db_cp.SmallInteger, primary_key=True, nullable=False, server_default=db_cp.text("0")) + frame = db_cp.Column(db_cp.SmallInteger, primary_key=True, nullable=False, server_default=db_cp.text("0")) + rotation = db_cp.Column(db_cp.SmallInteger, primary_key=True, nullable=False, server_default=db_cp.text("0")) + + +class IglooLike(db_cp.Model): + __tablename__ = 'igloo_like' + + igloo_id = db_cp.Column(db_cp.ForeignKey('penguin_igloo_room.id', ondelete='CASCADE', onupdate='CASCADE'), + primary_key=True, nullable=False) + player_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + count = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + date = db_cp.Column(db_cp.DateTime, nullable=False, server_default=db_cp.text("now()")) + + +class Location(db_cp.Model): + __tablename__ = 'location' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + name = db_cp.Column(db_cp.String(50), nullable=False) + cost = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + patched = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + legacy_inventory = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + vanilla_inventory = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + + +class PenguinIgloo(db_cp.Model): + __tablename__ = 'penguin_igloo' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + igloo_id = db_cp.Column(db_cp.ForeignKey('igloo.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class PenguinLocation(db_cp.Model): + __tablename__ = 'penguin_location' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + location_id = db_cp.Column(db_cp.ForeignKey('location.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class PenguinFurniture(db_cp.Model): + __tablename__ = 'penguin_furniture' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + furniture_id = db_cp.Column(db_cp.ForeignKey('furniture.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + quantity = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + + +class PenguinFlooring(db_cp.Model): + __tablename__ = 'penguin_flooring' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + flooring_id = db_cp.Column(db_cp.ForeignKey('flooring.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class IglooCollection(AbstractDataCollection): + __model__ = Igloo + __indexby__ = 'id' + __filterby__ = 'id' + + @cached_property + def legacy_inventory(self): + return [item for item in self.values() if item.legacy_inventory] + + @cached_property + def vanilla_inventory(self): + return [item for item in self.values() if item.vanilla_inventory] + + +class PenguinIglooCollection(AbstractDataCollection): + __model__ = PenguinIgloo + __indexby__ = 'igloo_id' + __filterby__ = 'penguin_id' + + +class LocationCollection(AbstractDataCollection): + __model__ = Location + __indexby__ = 'id' + __filterby__ = 'id' + + @cached_property + def legacy_inventory(self): + return [item for item in self.values() if item.legacy_inventory] + + @cached_property + def vanilla_inventory(self): + return [item for item in self.values() if item.vanilla_inventory] + + +class PenguinLocationCollection(AbstractDataCollection): + __model__ = PenguinLocation + __indexby__ = 'location_id' + __filterby__ = 'penguin_id' + + +class FurnitureCollection(AbstractDataCollection): + __model__ = Furniture + __indexby__ = 'id' + __filterby__ = 'id' + + @cached_property + def innocent(self): + return [item for item in self.values() if item.innocent] + + @cached_property + def legacy_inventory(self): + return [item for item in self.values() if item.legacy_inventory] + + @cached_property + def vanilla_inventory(self): + return [item for item in self.values() if item.vanilla_inventory] + + +class PenguinFurnitureCollection(AbstractDataCollection): + __model__ = PenguinFurniture + __indexby__ = 'furniture_id' + __filterby__ = 'penguin_id' + + +class FlooringCollection(AbstractDataCollection): + __model__ = Flooring + __indexby__ = 'id' + __filterby__ = 'id' + + @cached_property + def legacy_inventory(self): + return [item for item in self.values() if item.legacy_inventory] + + @cached_property + def vanilla_inventory(self): + return [item for item in self.values() if item.vanilla_inventory] + + +class PenguinFlooringCollection(AbstractDataCollection): + __model__ = PenguinFlooring + __indexby__ = 'flooring_id' + __filterby__ = 'penguin_id' diff --git a/bot/data/clubpenguin/item.py b/bot/data/clubpenguin/item.py new file mode 100644 index 0000000..e2fa931 --- /dev/null +++ b/bot/data/clubpenguin/item.py @@ -0,0 +1,89 @@ +from functools import cached_property + +from bot.data import AbstractDataCollection, db_cp + + +class Item(db_cp.Model): + __tablename__ = 'item' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + name = db_cp.Column(db_cp.String(50)) + type = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + cost = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + member = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + bait = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + patched = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + legacy_inventory = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + vanilla_inventory = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + epf = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + tour = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + release_date = db_cp.Column(db_cp.Date, nullable=False, server_default=db_cp.text("now()")) + treasure = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + innocent = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + + def is_color(self): + return self.type == 1 + + def is_head(self): + return self.type == 2 + + def is_face(self): + return self.type == 3 + + def is_neck(self): + return self.type == 4 + + def is_body(self): + return self.type == 5 + + def is_hand(self): + return self.type == 6 + + def is_feet(self): + return self.type == 7 + + def is_flag(self): + return self.type == 8 + + def is_photo(self): + return self.type == 9 + + def is_award(self): + return self.type == 10 + + +class PenguinItem(db_cp.Model): + __tablename__ = 'penguin_item' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), + primary_key=True, nullable=False) + item_id = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE'), + primary_key=True, nullable=False) + + +class ItemCollection(AbstractDataCollection): + __model__ = Item + __indexby__ = 'id' + __filterby__ = 'id' + + @cached_property + def treasure(self): + return { item for item in self.values() if item.treasure } + + @cached_property + def innocent(self): + return { item for item in self.values() if item.innocent } + + @cached_property + def legacy_inventory(self): + return { item for item in self.values() if item.legacy_inventory } + + @cached_property + def vanilla_inventory(self): + return { item for item in self.values() if item.vanilla_inventory } + + +class PenguinItemCollection(AbstractDataCollection): + __model__ = PenguinItem + __indexby__ = 'item_id' + __filterby__ = 'penguin_id' diff --git a/bot/data/clubpenguin/mail.py b/bot/data/clubpenguin/mail.py new file mode 100644 index 0000000..1897d24 --- /dev/null +++ b/bot/data/clubpenguin/mail.py @@ -0,0 +1,30 @@ +from bot.data import AbstractDataCollection, db_cp + + +class Postcard(db_cp.Model): + __tablename__ = 'postcard' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + name = db_cp.Column(db_cp.String(50), nullable=False) + cost = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("10")) + enabled = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + + +class PenguinPostcard(db_cp.Model): + __tablename__ = 'penguin_postcard' + + id = db_cp.Column(db_cp.Integer, primary_key=True, + server_default=db_cp.text("nextval('\"penguin_postcard_id_seq\"'::regclass)")) + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, + index=True) + sender_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), index=True) + postcard_id = db_cp.Column(db_cp.ForeignKey('postcard.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + send_date = db_cp.Column(db_cp.DateTime, nullable=False, server_default=db_cp.text("now()")) + details = db_cp.Column(db_cp.String(255), nullable=False, server_default=db_cp.text("''::character varying")) + has_read = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + + +class PostcardCollection(AbstractDataCollection): + __model__ = Postcard + __indexby__ = 'id' + __filterby__ = 'id' diff --git a/bot/data/clubpenguin/moderator.py b/bot/data/clubpenguin/moderator.py new file mode 100644 index 0000000..6b58208 --- /dev/null +++ b/bot/data/clubpenguin/moderator.py @@ -0,0 +1,72 @@ +from bot.data import AbstractDataCollection, db_cp + + +class Ban(db_cp.Model): + __tablename__ = 'ban' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + issued = db_cp.Column(db_cp.DateTime, primary_key=True, nullable=False, server_default=db_cp.text("now()")) + expires = db_cp.Column(db_cp.DateTime, primary_key=True, nullable=False, server_default=db_cp.text("now()")) + moderator_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), index=True) + reason = db_cp.Column(db_cp.SmallInteger, nullable=False) + comment = db_cp.Column(db_cp.Text) + message = db_cp.Column(db_cp.Text) + + +class Mute(db_cp.Model): + __tablename__ = 'mute' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + issued = db_cp.Column(db_cp.DateTime, primary_key=True, nullable=False, server_default=db_cp.text("now()")) + expires = db_cp.Column(db_cp.DateTime, primary_key=True, nullable=False, server_default=db_cp.text("now()")) + moderator_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), index=True) + message = db_cp.Column(db_cp.Text) + + +class Warning(db_cp.Model): + __tablename__ = 'warning' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + expires = db_cp.Column(db_cp.DateTime, primary_key=True, nullable=False) + issued = db_cp.Column(db_cp.DateTime, primary_key=True, nullable=False) + + +class Report(db_cp.Model): + __tablename__ = 'report' + + id = db_cp.Column(db_cp.Integer, primary_key=True, server_default=db_cp.text("nextval('\"report_ID_seq\"'::regclass)")) + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + reporter_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + report_type = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + date = db_cp.Column(db_cp.DateTime, nullable=False, server_default=db_cp.text("now()")) + server_id = db_cp.Column(db_cp.Integer, nullable=False) + room_id = db_cp.Column(db_cp.ForeignKey('room.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + + +class Logs(db_cp.Model): + __tablename__ = 'logs' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + date = db_cp.Column(db_cp.DateTime, primary_key=True, nullable=False, server_default=db_cp.text("now()")) + type = db_cp.Column(db_cp.Integer, nullable=False) + text = db_cp.Column(db_cp.Text) + room_id = db_cp.Column(db_cp.ForeignKey('room.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + server_id = db_cp.Column(db_cp.Integer, nullable=False) + + +class ChatFilterRule(db_cp.Model): + __tablename__ = 'chat_filter_rule' + + word = db_cp.Column(db_cp.Text, primary_key=True) + filter = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + warn = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + ban = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + + +class ChatFilterRuleCollection(AbstractDataCollection): + __model__ = ChatFilterRule + __filterby__ = 'word' + __indexby__ = 'word' \ No newline at end of file diff --git a/bot/data/clubpenguin/music.py b/bot/data/clubpenguin/music.py new file mode 100644 index 0000000..a6d6eb2 --- /dev/null +++ b/bot/data/clubpenguin/music.py @@ -0,0 +1,35 @@ +from bot.data import db_cp + + +class PenguinTrack(db_cp.Model): + __tablename__ = 'penguin_track' + + id = db_cp.Column(db_cp.Integer, primary_key=True, + server_default=db_cp.text("nextval('\"penguin_track_id_seq\"'::regclass)")) + name = db_cp.Column(db_cp.String(12), nullable=False, server_default=db_cp.text("''::character varying")) + owner_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + sharing = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + pattern = db_cp.Column(db_cp.Text, nullable=False) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self._likes = 0 + + @property + def likes(self): + return self._likes + + @likes.setter + def likes(self, like_count): + self._likes = like_count + + +class TrackLike(db_cp.Model): + __tablename__ = 'track_like' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + track_id = db_cp.Column(db_cp.ForeignKey('penguin_track.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False, index=True) + date = db_cp.Column(db_cp.DateTime, primary_key=True, nullable=False, server_default=db_cp.text("now()")) diff --git a/bot/data/clubpenguin/ninja.py b/bot/data/clubpenguin/ninja.py new file mode 100644 index 0000000..2a6b7a5 --- /dev/null +++ b/bot/data/clubpenguin/ninja.py @@ -0,0 +1,65 @@ +from bot.data import AbstractDataCollection, db_cp + + +class Card(db_cp.Model): + __tablename__ = 'card' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + name = db_cp.Column(db_cp.String(50), nullable=False) + set_id = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + power_id = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + element = db_cp.Column(db_cp.CHAR(1), nullable=False, server_default=db_cp.text("'s'::bpchar")) + color = db_cp.Column(db_cp.CHAR(1), nullable=False, server_default=db_cp.text("'b'::bpchar")) + value = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("2")) + description = db_cp.Column(db_cp.String(255), nullable=False, server_default=db_cp.text("''::character varying")) + + def get_string(self): + return f'{self.id}|{self.element}|{self.value}|{self.color}|{self.power_id}' + + +class CardStarterDeck(db_cp.Model): + __tablename__ = 'card_starter_deck' + + item_id = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False, index=True) + card_id = db_cp.Column(db_cp.ForeignKey('card.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + quantity = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + + +class PenguinCard(db_cp.Model): + __tablename__ = 'penguin_card' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False, index=True) + card_id = db_cp.Column(db_cp.ForeignKey('card.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + quantity = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + member_quantity = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + + +class CardCollection(AbstractDataCollection): + __model__ = Card + __indexby__ = 'id' + __filterby__ = 'id' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.starter_decks = {} + + def set_starter_decks(self, starter_deck_cards): + for card in starter_deck_cards: + starter_deck = self.starter_decks.get(card.item_id, []) + starter_deck.append((self.get(card.card_id), card.quantity)) + self.starter_decks[card.item_id] = starter_deck + + @property + def power_cards(self): + return [card for card in self.values() if card.power_id > 0] + + +class PenguinCardCollection(AbstractDataCollection): + __model__ = PenguinCard + __indexby__ = 'card_id' + __filterby__ = 'penguin_id' diff --git a/bot/data/clubpenguin/outfit.py b/bot/data/clubpenguin/outfit.py new file mode 100644 index 0000000..c222a97 --- /dev/null +++ b/bot/data/clubpenguin/outfit.py @@ -0,0 +1,24 @@ +from bot.data import AbstractDataCollection, db_cp + + +class Outfit(db_cp.Model): + __tablename__ = 'outfit' + + id = db_cp.Column(db_cp.Integer, primary_key=True, server_default=db_cp.text("nextval('\"outfit_id_seq\"'::regclass)")) + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + photo = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + flag = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + color = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + head = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + face = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + body = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + neck = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + hand = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + feet = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + + +class OutfitCollection(AbstractDataCollection): + __model__ = Outfit + __indexby__ = 'id' + __filterby__ = 'penguin_id' diff --git a/bot/data/clubpenguin/penguin.py b/bot/data/clubpenguin/penguin.py new file mode 100644 index 0000000..00f0112 --- /dev/null +++ b/bot/data/clubpenguin/penguin.py @@ -0,0 +1,176 @@ +from datetime import datetime +from functools import cached_property + +from bot.data import db_cp + + +class Penguin(db_cp.Model): + __tablename__ = 'penguin' + + id = db_cp.Column(db_cp.Integer, primary_key=True, server_default=db_cp.text("nextval('\"penguin_id_seq\"'::regclass)")) + username = db_cp.Column(db_cp.String(14), nullable=False, unique=True) + nickname = db_cp.Column(db_cp.String(30), nullable=False) + password = db_cp.Column(db_cp.CHAR(60), nullable=False) + email = db_cp.Column(db_cp.String(255), nullable=False, index=True) + registration_date = db_cp.Column(db_cp.DateTime, nullable=False, server_default=db_cp.text("now()")) + active = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + safe_chat = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + last_paycheck = db_cp.Column(db_cp.DateTime, nullable=False, server_default=db_cp.text("now()")) + minutes_played = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + moderator = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + stealth_moderator = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + character = db_cp.Column(db_cp.ForeignKey('character.id', ondelete='CASCADE', onupdate='CASCADE')) + igloo = db_cp.Column(db_cp.ForeignKey('penguin_igloo_room.id', ondelete='CASCADE', onupdate='CASCADE')) + coins = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("500")) + color = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + head = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + face = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + neck = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + body = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + hand = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + feet = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + photo = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + flag = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + permaban = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + book_modified = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + book_color = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + book_highlight = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + book_pattern = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + book_icon = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + agent_status = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + field_op_status = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + career_medals = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + agent_medals = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + last_field_op = db_cp.Column(db_cp.DateTime, nullable=False, server_default=db_cp.text("now()")) + com_message_read_date = db_cp.Column(db_cp.DateTime, nullable=False, server_default=db_cp.text("now()")) + ninja_rank = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + ninja_progress = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + fire_ninja_rank = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + fire_ninja_progress = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + water_ninja_rank = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + water_ninja_progress = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + ninja_matches_won = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + fire_matches_won = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + water_matches_won = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + rainbow_adoptability = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + has_dug = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + puffle_handler = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + nuggets = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + walking = db_cp.Column(db_cp.ForeignKey('penguin_puffle.id', ondelete='CASCADE', onupdate='CASCADE')) + opened_playercard = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + special_wave = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + special_dance = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + special_snowball = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + map_category = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + status_field = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + timer_active = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + timer_start = db_cp.Column(db_cp.Time, nullable=False, server_default=db_cp.text("'00:00:00'::time without time zone")) + timer_end = db_cp.Column(db_cp.Time, nullable=False, server_default=db_cp.text("'23:59:59'::time without time zone")) + timer_total = db_cp.Column(db_cp.Interval, nullable=False, server_default=db_cp.text("'01:00:00'::interval")) + grounded = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + approval_en = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + approval_pt = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + approval_fr = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + approval_es = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + approval_de = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + approval_ru = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + rejection_en = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + rejection_pt = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + rejection_fr = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + rejection_es = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + rejection_de = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + rejection_ru = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + ip = db_cp.Column(db_cp.String(15), nullable=False) + snow_ninja_progress = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + snow_ninja_rank = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + + def __init__(self, *args, **kwargs): + self.inventory = None + self.permissions = None + self.attributes = None + self.igloos = None + self.igloo_rooms = None + self.furniture = None + self.flooring = None + self.locations = None + self.stamps = None + self.cards = None + self.puffles = None + self.puffle_items = None + self.buddies = None + self.buddy_requests = None + self.character_buddies = None + self.ignore = None + + super().__init__(*args, **kwargs) + + def safe_nickname(self): + return self.nickname if self.approval else "P" + str(self.id) + + async def status_field_set(self, field_bitmask): + if (self.status_field & field_bitmask) == 0: + await self.update(status_field=self.status_field ^ field_bitmask).apply() + + def status_field_get(self, field_bitmask): + return (self.status_field & field_bitmask) != 0 + + @cached_property + def age(self): + return (datetime.now() - self.registration_date).days + + @cached_property + def approval(self): + return int(f'{self.approval_ru * 1}{self.approval_de * 1}0{self.approval_es * 1}' + f'{self.approval_fr * 1}{self.approval_pt * 1}{self.approval_en * 1}', 2) + + @cached_property + def rejection(self): + return int(f'{self.rejection_ru * 1}{self.rejection_de * 1}0{self.rejection_es * 1}' + f'{self.rejection_fr * 1}{self.rejection_pt * 1}{self.rejection_en * 1}', 2) + + +class ActivationKey(db_cp.Model): + __tablename__ = 'activation_key' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + activation_key = db_cp.Column(db_cp.CHAR(255), primary_key=True, nullable=False) + + +class PenguinMembership(db_cp.Model): + __tablename__ = 'penguin_membership' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + start = db_cp.Column(db_cp.DateTime, primary_key=True, nullable=False) + expires = db_cp.Column(db_cp.DateTime) + start_aware = db_cp.Column(db_cp.Boolean, server_default=db_cp.text("false")) + expires_aware = db_cp.Column(db_cp.Boolean, server_default=db_cp.text("false")) + expired_aware = db_cp.Column(db_cp.Boolean, server_default=db_cp.text("false")) + + +class Login(db_cp.Model): + __tablename__ = 'login' + + id = db_cp.Column(db_cp.Integer, primary_key=True, server_default=db_cp.text("nextval('\"login_id_seq\"'::regclass)")) + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + date = db_cp.Column(db_cp.DateTime, nullable=False, server_default=db_cp.text("now()")) + ip_hash = db_cp.Column(db_cp.CHAR(255), nullable=False) + minutes_played = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + + +class EpfComMessage(db_cp.Model): + __tablename__ = 'epf_com_message' + + message = db_cp.Column(db_cp.Text, nullable=False) + character_id = db_cp.Column(db_cp.ForeignKey('character.id', ondelete='RESTRICT', onupdate='CASCADE'), nullable=False) + date = db_cp.Column(db_cp.DateTime, nullable=False, server_default=db_cp.text("now()")) + + +class CfcDonation(db_cp.Model): + __tablename__ = 'cfc_donation' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + coins = db_cp.Column(db_cp.Integer, nullable=False) + charity = db_cp.Column(db_cp.Integer, nullable=False) + date = db_cp.Column(db_cp.DateTime, nullable=False, server_default=db_cp.text("now()")) diff --git a/bot/data/permission.py b/bot/data/clubpenguin/permission.py similarity index 61% rename from bot/data/permission.py rename to bot/data/clubpenguin/permission.py index 5f468cb..4df042b 100644 --- a/bot/data/permission.py +++ b/bot/data/clubpenguin/permission.py @@ -1,19 +1,19 @@ -from bot.data import AbstractDataCollection, db +from bot.data import AbstractDataCollection, db_cp -class Permission(db.Model): +class Permission(db_cp.Model): __tablename__ = 'permission' - name = db.Column(db.String(50), nullable=False, primary_key=True) - enabled = db.Column(db.Boolean, nullable=False, server_default=db.text("true")) + name = db_cp.Column(db_cp.String(50), nullable=False, primary_key=True) + enabled = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("true")) -class PenguinPermission(db.Model): +class PenguinPermission(db_cp.Model): __tablename__ = 'penguin_permission' - penguin_id = db.Column(db.ForeignKey(u'penguin.id', ondelete=u'CASCADE', onupdate=u'CASCADE'), primary_key=True) - permission_name = db.Column(db.ForeignKey(u'permission.name', ondelete=u'CASCADE', onupdate=u'CASCADE'), - nullable=False, primary_key=True) + penguin_id = db_cp.Column(db_cp.ForeignKey(u'penguin.id', ondelete=u'CASCADE', onupdate=u'CASCADE'), primary_key=True) + permission_name = db_cp.Column(db_cp.ForeignKey(u'permission.name', ondelete=u'CASCADE', onupdate=u'CASCADE'), + nullable=False, primary_key=True) class PermissionCollection(AbstractDataCollection): diff --git a/bot/data/clubpenguin/pet.py b/bot/data/clubpenguin/pet.py new file mode 100644 index 0000000..260d287 --- /dev/null +++ b/bot/data/clubpenguin/pet.py @@ -0,0 +1,110 @@ +from bot.data import AbstractDataCollection, db_cp + + +class Puffle(db_cp.Model): + __tablename__ = 'puffle' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + parent_id = db_cp.Column(db_cp.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + name = db_cp.Column(db_cp.String(50), nullable=False, server_default=db_cp.text("''::character varying")) + cost = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + member = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + favourite_food = db_cp.Column(db_cp.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + favourite_toy = db_cp.Column(db_cp.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE')) + runaway_postcard = db_cp.Column(db_cp.ForeignKey('postcard.id', ondelete='CASCADE', onupdate='CASCADE')) + + +class PuffleItem(db_cp.Model): + __tablename__ = 'puffle_item' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + parent_id = db_cp.Column(db_cp.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + name = db_cp.Column(db_cp.String(50), nullable=False, server_default=db_cp.text("''::character varying")) + type = db_cp.Column(db_cp.String(10), nullable=False, server_default=db_cp.text("'care'::character varying")) + play_external = db_cp.Column(db_cp.String(10), nullable=False, server_default=db_cp.text("'none'::character varying")) + cost = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + quantity = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + member = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + food_effect = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + rest_effect = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + play_effect = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + clean_effect = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + + +class PuffleTreasureFurniture(db_cp.Model): + __tablename__ = 'puffle_treasure_furniture' + + puffle_id = db_cp.Column(db_cp.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + furniture_id = db_cp.Column(db_cp.ForeignKey('furniture.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class PuffleTreasureItem(db_cp.Model): + __tablename__ = 'puffle_treasure_item' + + puffle_id = db_cp.Column(db_cp.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + item_id = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class PuffleTreasurePuffleItem(db_cp.Model): + __tablename__ = 'puffle_treasure_puffle_item' + + puffle_id = db_cp.Column(db_cp.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + puffle_item_id = db_cp.Column(db_cp.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), + primary_key=True, nullable=False) + + +class PenguinPuffle(db_cp.Model): + __tablename__ = 'penguin_puffle' + + id = db_cp.Column(db_cp.Integer, primary_key=True, + server_default=db_cp.text("nextval('\"penguin_puffle_id_seq\"'::regclass)")) + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + puffle_id = db_cp.Column(db_cp.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + name = db_cp.Column(db_cp.String(16), nullable=False) + adoption_date = db_cp.Column(db_cp.DateTime, nullable=False, server_default=db_cp.text("now()")) + food = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("100")) + play = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("100")) + rest = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("100")) + clean = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("100")) + hat = db_cp.Column(db_cp.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE')) + backyard = db_cp.Column(db_cp.Boolean, server_default=db_cp.text("false")) + has_dug = db_cp.Column(db_cp.Boolean, server_default=db_cp.text("false")) + + +class PenguinPuffleItem(db_cp.Model): + __tablename__ = 'penguin_puffle_item' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + item_id = db_cp.Column(db_cp.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + quantity = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + + +class PuffleCollection(AbstractDataCollection): + __model__ = Puffle + __indexby__ = 'id' + __filterby__ = 'id' + + +class PenguinPuffleCollection(AbstractDataCollection): + __model__ = PenguinPuffle + __indexby__ = 'id' + __filterby__ = 'penguin_id' + + +class PuffleItemCollection(AbstractDataCollection): + __model__ = PuffleItem + __indexby__ = 'id' + __filterby__ = 'id' + + +class PenguinPuffleItemCollection(AbstractDataCollection): + __model__ = PenguinPuffleItem + __indexby__ = 'item_id' + __filterby__ = 'penguin_id' diff --git a/bot/data/clubpenguin/plugin.py b/bot/data/clubpenguin/plugin.py new file mode 100644 index 0000000..5bd9d36 --- /dev/null +++ b/bot/data/clubpenguin/plugin.py @@ -0,0 +1,30 @@ +from bot.data import AbstractDataCollection, db_cp + + +class PluginAttribute(db_cp.Model): + __tablename__ = 'plugin_attribute' + + plugin_name = db_cp.Column(db_cp.Text, primary_key=True, nullable=False) + name = db_cp.Column(db_cp.Text, primary_key=True, nullable=False) + value = db_cp.Column(db_cp.Text) + + +class PenguinAttribute(db_cp.Model): + __tablename__ = 'penguin_attribute' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + name = db_cp.Column(db_cp.Text, primary_key=True, nullable=False) + value = db_cp.Column(db_cp.Text) + + +class PenguinAttributeCollection(AbstractDataCollection): + __model__ = PenguinAttribute + __indexby__ = 'name' + __filterby__ = 'penguin_id' + + +class PluginAttributeCollection(AbstractDataCollection): + __model__ = PluginAttribute + __indexby__ = 'name' + __filterby__ = 'plugin_name' diff --git a/bot/data/clubpenguin/quest.py b/bot/data/clubpenguin/quest.py new file mode 100644 index 0000000..2a80671 --- /dev/null +++ b/bot/data/clubpenguin/quest.py @@ -0,0 +1,145 @@ +from bot.data import db_cp + + +class Quest(db_cp.Model): + __tablename__ = 'quest' + + id = db_cp.Column(db_cp.Integer, primary_key=True, server_default=db_cp.text("nextval('\"quest_id_seq\"'::regclass)")) + name = db_cp.Column(db_cp.String(50), nullable=False, unique=True) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._tasks = set() + + self._items = set() + self._furniture = set() + self._pet = set() + + self._complete = set() + self._in_progress = set() + + @property + def items(self): + return self._items + + @property + def furniture(self): + return self._furniture + + @property + def pet(self): + return self._pet + + @property + def complete(self): + return self._complete + + @property + def in_progress(self): + return self._in_progress + + @property + def awards(self): + return self._items.union(self._furniture.union(self._pet)) + + @property + def tasks(self): + return self._tasks + + @items.setter + def items(self, child): + if isinstance(child, QuestAwardItem): + self._items.add(child) + + @furniture.setter + def furniture(self, child): + if isinstance(child, QuestAwardFurniture): + self._furniture.add(child) + + @pet.setter + def pet(self, child): + if isinstance(child, QuestAwardPuffleItem): + self._pet.add(child) + + @tasks.setter + def tasks(self, child): + if isinstance(child, QuestTask): + self._tasks.add(child) + + @complete.setter + def complete(self, child): + if isinstance(child, PenguinQuestTask): + if child.complete: + self._complete.add(child.task_id) + else: + self._in_progress.add(child.task_id) + +class GameQuest(db_cp.Model): + __tablename__ = 'game_quest' + + id = db_cp.Column(db_cp.Integer(), nullable=False, primary_key=True, server_default=db_cp.text("nextval('\"game_quest_id_seq\"'::regclass)")) + type_id = db_cp.Column(db_cp.Integer(), nullable=False, server_default='0') + coins = db_cp.Column(db_cp.Integer(), nullable=False, server_default='500') + item = db_cp.Column(db_cp.Integer(), nullable=False, server_default='0') + furniture = db_cp.Column(db_cp.Integer(), nullable=False, server_default='0') + + +class PenguinQuest(db_cp.Model): + __tablename__ = 'penguin_quest' + + id = db_cp.Column(db_cp.Integer(), nullable=False, primary_key=True, server_default=db_cp.text("nextval('\"game_quest_id_seq\"'::regclass)")) + penguin_id = db_cp.Column(db_cp.Integer(), nullable=False, server_default='0') + quest_id = db_cp.Column(db_cp.Integer(), nullable=False, server_default='500') + argv1 = db_cp.Column(db_cp.Integer(), nullable=False, server_default='0') + argv2 = db_cp.Column(db_cp.Integer(), nullable=False, server_default='0') + status = db_cp.Column(db_cp.Integer(), nullable=False, server_default='0') + expires = db_cp.Column(db_cp.DateTime, nullable=False, server_default=db_cp.text("(now() + '23:59:59'::interval)")) + + +class QuestAwardItem(db_cp.Model): + __tablename__ = 'quest_award_item' + + quest_id = db_cp.Column(db_cp.ForeignKey('quest.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + item_id = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class QuestAwardFurniture(db_cp.Model): + __tablename__ = 'quest_award_furniture' + + quest_id = db_cp.Column(db_cp.ForeignKey('quest.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + furniture_id = db_cp.Column(db_cp.ForeignKey('furniture.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + quantity = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + + +class QuestAwardPuffleItem(db_cp.Model): + __tablename__ = 'quest_award_puffle_item' + + quest_id = db_cp.Column(db_cp.ForeignKey('quest.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + puffle_item_id = db_cp.Column(db_cp.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), + primary_key=True, nullable=False) + quantity = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + + +class QuestTask(db_cp.Model): + __tablename__ = 'quest_task' + + id = db_cp.Column(db_cp.Integer, primary_key=True, server_default=db_cp.text("nextval('\"quest_id_seq\"'::regclass)")) + quest_id = db_cp.Column(db_cp.ForeignKey('quest.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + description = db_cp.Column(db_cp.String(50), nullable=False) + room_id = db_cp.Column(db_cp.ForeignKey('room.id', ondelete='CASCADE', onupdate='CASCADE')) + data = db_cp.Column(db_cp.String(50)) + + +class PenguinQuestTask(db_cp.Model): + __tablename__ = 'penguin_quest_task' + + task_id = db_cp.Column(db_cp.ForeignKey('quest_task.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, + primary_key=True) + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, + primary_key=True) + complete = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) diff --git a/bot/data/clubpenguin/redemption.py b/bot/data/clubpenguin/redemption.py new file mode 100644 index 0000000..a649b6c --- /dev/null +++ b/bot/data/clubpenguin/redemption.py @@ -0,0 +1,198 @@ +from bot.data import db_cp + + +class RedemptionCode(db_cp.Model): + __tablename__ = 'redemption_code' + + id = db_cp.Column(db_cp.Integer, primary_key=True, + server_default=db_cp.text("nextval('\"redemption_code_id_seq\"'::regclass)")) + code = db_cp.Column(db_cp.String(16), nullable=False, unique=True) + type = db_cp.Column(db_cp.String(8), nullable=False, server_default=db_cp.text("'BLANKET'::character varying")) + coins = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + expires = db_cp.Column(db_cp.DateTime) + uses = db_cp.Column(db_cp.Integer) + party_coins = db_cp.Column(db_cp.Integer, nullable=False, server_default=db_cp.text("0")) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._cards = set() + self._flooring = set() + self._furniture = set() + self._igloos = set() + self._items = set() + self._locations = set() + self._puffles = set() + self._puffle_items = set() + + @property + def cards(self): + return self._cards + + @cards.setter + def cards(self, child): + if isinstance(child, RedemptionAwardCard): + self._cards.add(child) + + @property + def flooring(self): + return self._flooring + + @flooring.setter + def flooring(self, child): + if isinstance(child, RedemptionAwardFlooring): + self._flooring.add(child) + + @property + def furniture(self): + return self._furniture + + @furniture.setter + def furniture(self, child): + if isinstance(child, RedemptionAwardFurniture): + self._furniture.add(child) + + @property + def igloos(self): + return self._igloos + + @igloos.setter + def igloos(self, child): + if isinstance(child, RedemptionAwardIgloo): + self._igloos.add(child) + + @property + def items(self): + return self._items + + @items.setter + def items(self, child): + if isinstance(child, RedemptionAwardItem): + self._items.add(child) + + @property + def locations(self): + return self._locations + + @locations.setter + def locations(self, child): + if isinstance(child, RedemptionAwardLocation): + self._locations.add(child) + + @property + def puffles(self): + return self._puffles + + @puffles.setter + def puffles(self, child): + if isinstance(child, RedemptionAwardPuffle): + self._puffles.add(child) + + @property + def puffle_items(self): + return self._puffle_items + + @puffle_items.setter + def puffle_items(self, child): + if isinstance(child, RedemptionAwardPuffleItem): + self._puffle_items.add(child) + + +class RedemptionBook(db_cp.Model): + __tablename__ = 'redemption_book' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + name = db_cp.Column(db_cp.String(255), nullable=False) + + +class RedemptionBookWord(db_cp.Model): + __tablename__ = 'redemption_book_word' + + question_id = db_cp.Column(db_cp.Integer, primary_key=True, server_default=db_cp.text("nextval('\"redemption_book_word_question_id_seq\"'::regclass)")) + book_id = db_cp.Column(db_cp.ForeignKey('redemption_book.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + page = db_cp.Column(db_cp.SmallInteger, primary_key=True, nullable=False, server_default=db_cp.text("1")) + line = db_cp.Column(db_cp.SmallInteger, primary_key=True, nullable=False, server_default=db_cp.text("1")) + word_number = db_cp.Column(db_cp.SmallInteger, primary_key=True, nullable=False, server_default=db_cp.text("1")) + answer = db_cp.Column(db_cp.String(20), nullable=False) + + +class RedemptionAwardCard(db_cp.Model): + __tablename__ = 'redemption_award_card' + code_id = db_cp.Column(db_cp.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + card_id = db_cp.Column(db_cp.ForeignKey('card.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class RedemptionAwardFlooring(db_cp.Model): + __tablename__ = 'redemption_award_flooring' + code_id = db_cp.Column(db_cp.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + flooring_id = db_cp.Column(db_cp.ForeignKey('flooring.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class RedemptionAwardFurniture(db_cp.Model): + __tablename__ = 'redemption_award_furniture' + code_id = db_cp.Column(db_cp.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + furniture_id = db_cp.Column(db_cp.ForeignKey('furniture.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class RedemptionAwardIgloo(db_cp.Model): + __tablename__ = 'redemption_award_igloo' + code_id = db_cp.Column(db_cp.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), + primary_key=True, nullable=False) + igloo_id = db_cp.Column(db_cp.ForeignKey('igloo.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class RedemptionAwardItem(db_cp.Model): + __tablename__ = 'redemption_award_item' + code_id = db_cp.Column(db_cp.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + item_id = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class RedemptionAwardLocation(db_cp.Model): + __tablename__ = 'redemption_award_location' + code_id = db_cp.Column(db_cp.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + location_id = db_cp.Column(db_cp.ForeignKey('location.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class RedemptionAwardPuffle(db_cp.Model): + __tablename__ = 'redemption_award_puffle' + code_id = db_cp.Column(db_cp.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + puffle_id = db_cp.Column(db_cp.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + + +class RedemptionAwardPuffleItem(db_cp.Model): + __tablename__ = 'redemption_award_puffle_item' + code_id = db_cp.Column(db_cp.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + puffle_item_id = db_cp.Column(db_cp.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), + primary_key=True, nullable=False) + + +class PenguinRedemptionCode(db_cp.Model): + __tablename__ = 'penguin_redemption_code' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + code_id = db_cp.Column(db_cp.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False, index=True) + + +class PenguinRedemptionBook(db_cp.Model): + __tablename__ = 'penguin_redemption_book' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + book_id = db_cp.Column(db_cp.ForeignKey('redemption_book.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False, index=True) diff --git a/bot/data/room.py b/bot/data/clubpenguin/room.py similarity index 76% rename from bot/data/room.py rename to bot/data/clubpenguin/room.py index da46d7b..679fee4 100644 --- a/bot/data/room.py +++ b/bot/data/clubpenguin/room.py @@ -1,6 +1,6 @@ import random -from bot.data import AbstractDataCollection, db +from bot.data import AbstractDataCollection, db_cp def stealth_mod_filter(stealth_mod_id): @@ -104,20 +104,20 @@ async def send_xt(self, *data, f=None): await self.penguin.send_xt(*data) -class Room(db.Model, RoomMixin): +class Room(db_cp.Model, RoomMixin): __tablename__ = 'room' - id = db.Column(db.Integer, primary_key=True) - internal_id = db.Column(db.Integer, nullable=False, unique=True, - server_default=db.text("nextval('\"room_internal_id_seq\"'::regclass)")) - name = db.Column(db.String(50), nullable=False) - member = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - max_users = db.Column(db.SmallInteger, nullable=False, server_default=db.text("80")) - required_item = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - game = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - blackhole = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - spawn = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - stamp_group = db.Column(db.ForeignKey('stamp_group.id', ondelete='CASCADE', onupdate='CASCADE')) + id = db_cp.Column(db_cp.Integer, primary_key=True) + internal_id = db_cp.Column(db_cp.Integer, nullable=False, unique=True, + server_default=db_cp.text("nextval('\"room_internal_id_seq\"'::regclass)")) + name = db_cp.Column(db_cp.String(50), nullable=False) + member = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + max_users = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("80")) + required_item = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) + game = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + blackhole = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + spawn = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + stamp_group = db_cp.Column(db_cp.ForeignKey('stamp_group.id', ondelete='CASCADE', onupdate='CASCADE')) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -151,18 +151,18 @@ async def leave_blackhole(self, p): p.room = self.blackhole_penguins.pop(p.id) -class PenguinIglooRoom(db.Model, RoomMixin): +class PenguinIglooRoom(db_cp.Model, RoomMixin): __tablename__ = 'penguin_igloo_room' - id = db.Column(db.Integer, primary_key=True, - server_default=db.text("nextval('\"penguin_igloo_room_id_seq\"'::regclass)")) - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - type = db.Column(db.ForeignKey('igloo.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - flooring = db.Column(db.ForeignKey('flooring.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - music = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - location = db.Column(db.ForeignKey('location.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - locked = db.Column(db.Boolean, nullable=False, server_default=db.text("true")) - competition = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) + id = db_cp.Column(db_cp.Integer, primary_key=True, + server_default=db_cp.text("nextval('\"penguin_igloo_room_id_seq\"'::regclass)")) + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + type = db_cp.Column(db_cp.ForeignKey('igloo.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + flooring = db_cp.Column(db_cp.ForeignKey('flooring.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + music = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + location = db_cp.Column(db_cp.ForeignKey('location.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + locked = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("true")) + competition = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) internal_id = 2000 name = 'Igloo' @@ -201,13 +201,13 @@ async def remove_penguin(self, p): del p.server.igloos_by_penguin_id[self.penguin_id] -class RoomTable(db.Model): +class RoomTable(db_cp.Model): __tablename__ = 'room_table' - id = db.Column(db.Integer, primary_key=True, nullable=False) - room_id = db.Column(db.ForeignKey('room.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - game = db.Column(db.String(20), nullable=False) + id = db_cp.Column(db_cp.Integer, primary_key=True, nullable=False) + room_id = db_cp.Column(db_cp.ForeignKey('room.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + game = db_cp.Column(db_cp.String(20), nullable=False) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -267,14 +267,14 @@ async def send_xt(self, *data): await penguin.send_xt(*data) -class RoomWaddle(db.Model): +class RoomWaddle(db_cp.Model): __tablename__ = 'room_waddle' - id = db.Column(db.Integer, primary_key=True, nullable=False) - room_id = db.Column(db.ForeignKey('room.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - seats = db.Column(db.SmallInteger, nullable=False, server_default=db.text("2")) - game = db.Column(db.String(20), nullable=False) + id = db_cp.Column(db_cp.Integer, primary_key=True, nullable=False) + room_id = db_cp.Column(db_cp.ForeignKey('room.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + seats = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("2")) + game = db_cp.Column(db_cp.String(20), nullable=False) def __init__(self, *args, **kwargs): self.temporary = kwargs.pop('temporary', False) diff --git a/bot/data/clubpenguin/stamp.py b/bot/data/clubpenguin/stamp.py new file mode 100644 index 0000000..56e6f0e --- /dev/null +++ b/bot/data/clubpenguin/stamp.py @@ -0,0 +1,68 @@ +from bot.data import AbstractDataCollection, db_cp + + +class Stamp(db_cp.Model): + __tablename__ = 'stamp' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + name = db_cp.Column(db_cp.String(50), nullable=False) + group_id = db_cp.Column(db_cp.ForeignKey('stamp_group.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) + member = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("false")) + rank = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("1")) + description = db_cp.Column(db_cp.String(255), nullable=False, server_default=db_cp.text("''::character varying")) + + +class StampGroup(db_cp.Model): + __tablename__ = 'stamp_group' + + id = db_cp.Column(db_cp.Integer, primary_key=True) + name = db_cp.Column(db_cp.String(50), nullable=False) + parent_id = db_cp.Column(db_cp.ForeignKey('stamp_group.id', ondelete='CASCADE', onupdate='CASCADE')) + + +class CoverStamp(db_cp.Model): + __tablename__ = 'cover_stamp' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + stamp_id = db_cp.Column(db_cp.ForeignKey('stamp.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + x = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + y = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + rotation = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + depth = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + + +class CoverItem(db_cp.Model): + __tablename__ = 'cover_item' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + item_id = db_cp.Column(db_cp.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + x = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + y = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + rotation = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + depth = db_cp.Column(db_cp.SmallInteger, nullable=False, server_default=db_cp.text("0")) + + +class PenguinStamp(db_cp.Model): + __tablename__ = 'penguin_stamp' + + penguin_id = db_cp.Column(db_cp.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + stamp_id = db_cp.Column(db_cp.ForeignKey('stamp.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + recent = db_cp.Column(db_cp.Boolean, nullable=False, server_default=db_cp.text("true")) + + +class StampCollection(AbstractDataCollection): + __model__ = Stamp + __indexby__ = 'id' + __filterby__ = 'id' + + +class PenguinStampCollection(AbstractDataCollection): + __model__ = PenguinStamp + __indexby__ = 'stamp_id' + __filterby__ = 'penguin_id' diff --git a/bot/data/dance.py b/bot/data/dance.py deleted file mode 100644 index a127997..0000000 --- a/bot/data/dance.py +++ /dev/null @@ -1,17 +0,0 @@ -from bot.data import AbstractDataCollection, db - - -class DanceSong(db.Model): - __tablename__ = 'dance_song' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(30), nullable=False) - song_length_millis = db.Column(db.Integer, nullable=False) - song_length = db.Column(db.Integer, nullable=False) - millis_per_bar = db.Column(db.Integer, nullable=False) - - -class DanceSongCollection(AbstractDataCollection): - __model__ = DanceSong - __indexby__ = 'id' - __filterby__ = 'id' diff --git a/bot/data/game.py b/bot/data/game.py deleted file mode 100644 index 2f0f3c7..0000000 --- a/bot/data/game.py +++ /dev/null @@ -1,12 +0,0 @@ -from bot.data import db - - -class PenguinGameData(db.Model): - __tablename__ = 'penguin_game_data' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='RESTRICT', onupdate='CASCADE'), primary_key=True, - nullable=False) - room_id = db.Column(db.ForeignKey('room.id', ondelete='RESTRICT', onupdate='CASCADE'), primary_key=True, - nullable=False, index=True) - index = db.Column(db.Integer, primary_key=True, index=True) - data = db.Column(db.Text, nullable=False, server_default=db.text("''")) diff --git a/bot/data/igloo.py b/bot/data/igloo.py deleted file mode 100644 index 1e408a6..0000000 --- a/bot/data/igloo.py +++ /dev/null @@ -1,198 +0,0 @@ -from functools import cached_property - -from bot.data import AbstractDataCollection, db - - -class Flooring(db.Model): - __tablename__ = 'flooring' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50)) - cost = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - patched = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - legacy_inventory = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - vanilla_inventory = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - - -class Furniture(db.Model): - __tablename__ = 'furniture' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - type = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - sort = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - cost = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - member = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - patched = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - legacy_inventory = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - vanilla_inventory = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - bait = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - max_quantity = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100")) - innocent = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - - -class Igloo(db.Model): - __tablename__ = 'igloo' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - cost = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - patched = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - legacy_inventory = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - vanilla_inventory = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - - -class IglooFurniture(db.Model): - __tablename__ = 'igloo_furniture' - - igloo_id = db.Column(db.ForeignKey('penguin_igloo_room.id', ondelete='CASCADE', onupdate='CASCADE'), - primary_key=True, nullable=False, index=True) - furniture_id = db.Column(db.ForeignKey('furniture.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - x = db.Column(db.SmallInteger, primary_key=True, nullable=False, server_default=db.text("0")) - y = db.Column(db.SmallInteger, primary_key=True, nullable=False, server_default=db.text("0")) - frame = db.Column(db.SmallInteger, primary_key=True, nullable=False, server_default=db.text("0")) - rotation = db.Column(db.SmallInteger, primary_key=True, nullable=False, server_default=db.text("0")) - - -class IglooLike(db.Model): - __tablename__ = 'igloo_like' - - igloo_id = db.Column(db.ForeignKey('penguin_igloo_room.id', ondelete='CASCADE', onupdate='CASCADE'), - primary_key=True, nullable=False) - player_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - count = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - date = db.Column(db.DateTime, nullable=False, server_default=db.text("now()")) - - -class Location(db.Model): - __tablename__ = 'location' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - cost = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - patched = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - legacy_inventory = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - vanilla_inventory = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - - -class PenguinIgloo(db.Model): - __tablename__ = 'penguin_igloo' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - igloo_id = db.Column(db.ForeignKey('igloo.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class PenguinLocation(db.Model): - __tablename__ = 'penguin_location' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - location_id = db.Column(db.ForeignKey('location.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class PenguinFurniture(db.Model): - __tablename__ = 'penguin_furniture' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - furniture_id = db.Column(db.ForeignKey('furniture.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - quantity = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - - -class PenguinFlooring(db.Model): - __tablename__ = 'penguin_flooring' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - flooring_id = db.Column(db.ForeignKey('flooring.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class IglooCollection(AbstractDataCollection): - __model__ = Igloo - __indexby__ = 'id' - __filterby__ = 'id' - - @cached_property - def legacy_inventory(self): - return [item for item in self.values() if item.legacy_inventory] - - @cached_property - def vanilla_inventory(self): - return [item for item in self.values() if item.vanilla_inventory] - - -class PenguinIglooCollection(AbstractDataCollection): - __model__ = PenguinIgloo - __indexby__ = 'igloo_id' - __filterby__ = 'penguin_id' - - -class LocationCollection(AbstractDataCollection): - __model__ = Location - __indexby__ = 'id' - __filterby__ = 'id' - - @cached_property - def legacy_inventory(self): - return [item for item in self.values() if item.legacy_inventory] - - @cached_property - def vanilla_inventory(self): - return [item for item in self.values() if item.vanilla_inventory] - - -class PenguinLocationCollection(AbstractDataCollection): - __model__ = PenguinLocation - __indexby__ = 'location_id' - __filterby__ = 'penguin_id' - - -class FurnitureCollection(AbstractDataCollection): - __model__ = Furniture - __indexby__ = 'id' - __filterby__ = 'id' - - @cached_property - def innocent(self): - return [item for item in self.values() if item.innocent] - - @cached_property - def legacy_inventory(self): - return [item for item in self.values() if item.legacy_inventory] - - @cached_property - def vanilla_inventory(self): - return [item for item in self.values() if item.vanilla_inventory] - - -class PenguinFurnitureCollection(AbstractDataCollection): - __model__ = PenguinFurniture - __indexby__ = 'furniture_id' - __filterby__ = 'penguin_id' - - -class FlooringCollection(AbstractDataCollection): - __model__ = Flooring - __indexby__ = 'id' - __filterby__ = 'id' - - @cached_property - def legacy_inventory(self): - return [item for item in self.values() if item.legacy_inventory] - - @cached_property - def vanilla_inventory(self): - return [item for item in self.values() if item.vanilla_inventory] - - -class PenguinFlooringCollection(AbstractDataCollection): - __model__ = PenguinFlooring - __indexby__ = 'flooring_id' - __filterby__ = 'penguin_id' diff --git a/bot/data/item.py b/bot/data/item.py deleted file mode 100644 index 66e7c12..0000000 --- a/bot/data/item.py +++ /dev/null @@ -1,89 +0,0 @@ -from functools import cached_property - -from bot.data import AbstractDataCollection, db - - -class Item(db.Model): - __tablename__ = 'item' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50)) - type = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - cost = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - member = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - bait = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - patched = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - legacy_inventory = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - vanilla_inventory = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - epf = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - tour = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - release_date = db.Column(db.Date, nullable=False, server_default=db.text("now()")) - treasure = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - innocent = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - - def is_color(self): - return self.type == 1 - - def is_head(self): - return self.type == 2 - - def is_face(self): - return self.type == 3 - - def is_neck(self): - return self.type == 4 - - def is_body(self): - return self.type == 5 - - def is_hand(self): - return self.type == 6 - - def is_feet(self): - return self.type == 7 - - def is_flag(self): - return self.type == 8 - - def is_photo(self): - return self.type == 9 - - def is_award(self): - return self.type == 10 - - -class PenguinItem(db.Model): - __tablename__ = 'penguin_item' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), - primary_key=True, nullable=False) - item_id = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE'), - primary_key=True, nullable=False) - - -class ItemCollection(AbstractDataCollection): - __model__ = Item - __indexby__ = 'id' - __filterby__ = 'id' - - @cached_property - def treasure(self): - return { item for item in self.values() if item.treasure } - - @cached_property - def innocent(self): - return { item for item in self.values() if item.innocent } - - @cached_property - def legacy_inventory(self): - return { item for item in self.values() if item.legacy_inventory } - - @cached_property - def vanilla_inventory(self): - return { item for item in self.values() if item.vanilla_inventory } - - -class PenguinItemCollection(AbstractDataCollection): - __model__ = PenguinItem - __indexby__ = 'item_id' - __filterby__ = 'penguin_id' diff --git a/bot/data/mail.py b/bot/data/mail.py deleted file mode 100644 index 82161d2..0000000 --- a/bot/data/mail.py +++ /dev/null @@ -1,30 +0,0 @@ -from bot.data import AbstractDataCollection, db - - -class Postcard(db.Model): - __tablename__ = 'postcard' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - cost = db.Column(db.Integer, nullable=False, server_default=db.text("10")) - enabled = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - - -class PenguinPostcard(db.Model): - __tablename__ = 'penguin_postcard' - - id = db.Column(db.Integer, primary_key=True, - server_default=db.text("nextval('\"penguin_postcard_id_seq\"'::regclass)")) - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, - index=True) - sender_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), index=True) - postcard_id = db.Column(db.ForeignKey('postcard.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - send_date = db.Column(db.DateTime, nullable=False, server_default=db.text("now()")) - details = db.Column(db.String(255), nullable=False, server_default=db.text("''::character varying")) - has_read = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - - -class PostcardCollection(AbstractDataCollection): - __model__ = Postcard - __indexby__ = 'id' - __filterby__ = 'id' diff --git a/bot/data/moderator.py b/bot/data/moderator.py deleted file mode 100644 index 0604be4..0000000 --- a/bot/data/moderator.py +++ /dev/null @@ -1,72 +0,0 @@ -from bot.data import AbstractDataCollection, db - - -class Ban(db.Model): - __tablename__ = 'ban' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - issued = db.Column(db.DateTime, primary_key=True, nullable=False, server_default=db.text("now()")) - expires = db.Column(db.DateTime, primary_key=True, nullable=False, server_default=db.text("now()")) - moderator_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), index=True) - reason = db.Column(db.SmallInteger, nullable=False) - comment = db.Column(db.Text) - message = db.Column(db.Text) - - -class Mute(db.Model): - __tablename__ = 'mute' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - issued = db.Column(db.DateTime, primary_key=True, nullable=False, server_default=db.text("now()")) - expires = db.Column(db.DateTime, primary_key=True, nullable=False, server_default=db.text("now()")) - moderator_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), index=True) - message = db.Column(db.Text) - - -class Warning(db.Model): - __tablename__ = 'warning' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - expires = db.Column(db.DateTime, primary_key=True, nullable=False) - issued = db.Column(db.DateTime, primary_key=True, nullable=False) - - -class Report(db.Model): - __tablename__ = 'report' - - id = db.Column(db.Integer, primary_key=True, server_default=db.text("nextval('\"report_ID_seq\"'::regclass)")) - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - reporter_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - report_type = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - date = db.Column(db.DateTime, nullable=False, server_default=db.text("now()")) - server_id = db.Column(db.Integer, nullable=False) - room_id = db.Column(db.ForeignKey('room.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - - -class Logs(db.Model): - __tablename__ = 'logs' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - date = db.Column(db.DateTime, primary_key=True, nullable=False, server_default=db.text("now()")) - type = db.Column(db.Integer, nullable=False) - text = db.Column(db.Text) - room_id = db.Column(db.ForeignKey('room.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - server_id = db.Column(db.Integer, nullable=False) - - -class ChatFilterRule(db.Model): - __tablename__ = 'chat_filter_rule' - - word = db.Column(db.Text, primary_key=True) - filter = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - warn = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - ban = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - - -class ChatFilterRuleCollection(AbstractDataCollection): - __model__ = ChatFilterRule - __filterby__ = 'word' - __indexby__ = 'word' \ No newline at end of file diff --git a/bot/data/music.py b/bot/data/music.py deleted file mode 100644 index e3d4f40..0000000 --- a/bot/data/music.py +++ /dev/null @@ -1,35 +0,0 @@ -from bot.data import db - - -class PenguinTrack(db.Model): - __tablename__ = 'penguin_track' - - id = db.Column(db.Integer, primary_key=True, - server_default=db.text("nextval('\"penguin_track_id_seq\"'::regclass)")) - name = db.Column(db.String(12), nullable=False, server_default=db.text("''::character varying")) - owner_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - sharing = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - pattern = db.Column(db.Text, nullable=False) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - self._likes = 0 - - @property - def likes(self): - return self._likes - - @likes.setter - def likes(self, like_count): - self._likes = like_count - - -class TrackLike(db.Model): - __tablename__ = 'track_like' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - track_id = db.Column(db.ForeignKey('penguin_track.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False, index=True) - date = db.Column(db.DateTime, primary_key=True, nullable=False, server_default=db.text("now()")) diff --git a/bot/data/ninja.py b/bot/data/ninja.py deleted file mode 100644 index 891e765..0000000 --- a/bot/data/ninja.py +++ /dev/null @@ -1,65 +0,0 @@ -from bot.data import AbstractDataCollection, db - - -class Card(db.Model): - __tablename__ = 'card' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - set_id = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - power_id = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - element = db.Column(db.CHAR(1), nullable=False, server_default=db.text("'s'::bpchar")) - color = db.Column(db.CHAR(1), nullable=False, server_default=db.text("'b'::bpchar")) - value = db.Column(db.SmallInteger, nullable=False, server_default=db.text("2")) - description = db.Column(db.String(255), nullable=False, server_default=db.text("''::character varying")) - - def get_string(self): - return f'{self.id}|{self.element}|{self.value}|{self.color}|{self.power_id}' - - -class CardStarterDeck(db.Model): - __tablename__ = 'card_starter_deck' - - item_id = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False, index=True) - card_id = db.Column(db.ForeignKey('card.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - quantity = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - - -class PenguinCard(db.Model): - __tablename__ = 'penguin_card' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False, index=True) - card_id = db.Column(db.ForeignKey('card.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - quantity = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - member_quantity = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - - -class CardCollection(AbstractDataCollection): - __model__ = Card - __indexby__ = 'id' - __filterby__ = 'id' - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.starter_decks = {} - - def set_starter_decks(self, starter_deck_cards): - for card in starter_deck_cards: - starter_deck = self.starter_decks.get(card.item_id, []) - starter_deck.append((self.get(card.card_id), card.quantity)) - self.starter_decks[card.item_id] = starter_deck - - @property - def power_cards(self): - return [card for card in self.values() if card.power_id > 0] - - -class PenguinCardCollection(AbstractDataCollection): - __model__ = PenguinCard - __indexby__ = 'card_id' - __filterby__ = 'penguin_id' diff --git a/bot/data/outfit.py b/bot/data/outfit.py deleted file mode 100644 index aa77a88..0000000 --- a/bot/data/outfit.py +++ /dev/null @@ -1,24 +0,0 @@ -from bot.data import AbstractDataCollection, db - - -class Outfit(db.Model): - __tablename__ = 'outfit' - - id = db.Column(db.Integer, primary_key=True, server_default=db.text("nextval('\"outfit_id_seq\"'::regclass)")) - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - photo = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - flag = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - color = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - head = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - face = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - body = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - neck = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - hand = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - feet = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - - -class OutfitCollection(AbstractDataCollection): - __model__ = Outfit - __indexby__ = 'id' - __filterby__ = 'penguin_id' diff --git a/bot/data/penguin.py b/bot/data/penguin.py deleted file mode 100644 index 6ce18f8..0000000 --- a/bot/data/penguin.py +++ /dev/null @@ -1,185 +0,0 @@ -from datetime import datetime -from functools import cached_property - -from bot.data import db - - -class Penguin(db.Model): - __tablename__ = 'penguin' - - id = db.Column(db.Integer, primary_key=True, server_default=db.text("nextval('\"penguin_id_seq\"'::regclass)")) - username = db.Column(db.String(14), nullable=False, unique=True) - nickname = db.Column(db.String(30), nullable=False) - password = db.Column(db.CHAR(60), nullable=False) - email = db.Column(db.String(255), nullable=False, index=True) - registration_date = db.Column(db.DateTime, nullable=False, server_default=db.text("now()")) - active = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - safe_chat = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - last_paycheck = db.Column(db.DateTime, nullable=False, server_default=db.text("now()")) - minutes_played = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - moderator = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - stealth_moderator = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - character = db.Column(db.ForeignKey('character.id', ondelete='CASCADE', onupdate='CASCADE')) - igloo = db.Column(db.ForeignKey('penguin_igloo_room.id', ondelete='CASCADE', onupdate='CASCADE')) - coins = db.Column(db.Integer, nullable=False, server_default=db.text("500")) - color = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - head = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - face = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - neck = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - body = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - hand = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - feet = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - photo = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - flag = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE')) - permaban = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - book_modified = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - book_color = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - book_highlight = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - book_pattern = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - book_icon = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - agent_status = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - field_op_status = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - career_medals = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - agent_medals = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - last_field_op = db.Column(db.DateTime, nullable=False, server_default=db.text("now()")) - com_message_read_date = db.Column(db.DateTime, nullable=False, server_default=db.text("now()")) - ninja_rank = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - ninja_progress = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - fire_ninja_rank = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - fire_ninja_progress = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - water_ninja_rank = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - water_ninja_progress = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - ninja_matches_won = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - fire_matches_won = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - water_matches_won = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - rainbow_adoptability = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - has_dug = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - puffle_handler = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - nuggets = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - walking = db.Column(db.ForeignKey('penguin_puffle.id', ondelete='CASCADE', onupdate='CASCADE')) - opened_playercard = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - special_wave = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - special_dance = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - special_snowball = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - map_category = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - status_field = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - timer_active = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - timer_start = db.Column(db.Time, nullable=False, server_default=db.text("'00:00:00'::time without time zone")) - timer_end = db.Column(db.Time, nullable=False, server_default=db.text("'23:59:59'::time without time zone")) - timer_total = db.Column(db.Interval, nullable=False, server_default=db.text("'01:00:00'::interval")) - grounded = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - approval_en = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - approval_pt = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - approval_fr = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - approval_es = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - approval_de = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - approval_ru = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - rejection_en = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - rejection_pt = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - rejection_fr = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - rejection_es = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - rejection_de = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - rejection_ru = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - ip = db.Column(db.String(15), nullable=False) - snow_ninja_progress = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - snow_ninja_rank = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - - def __init__(self, *args, **kwargs): - self.inventory = None - self.permissions = None - self.attributes = None - self.igloos = None - self.igloo_rooms = None - self.furniture = None - self.flooring = None - self.locations = None - self.stamps = None - self.cards = None - self.puffles = None - self.puffle_items = None - self.buddies = None - self.buddy_requests = None - self.character_buddies = None - self.ignore = None - - super().__init__(*args, **kwargs) - - def safe_nickname(self): - return self.nickname if self.approval else "P" + str(self.id) - - async def status_field_set(self, field_bitmask): - if (self.status_field & field_bitmask) == 0: - await self.update(status_field=self.status_field ^ field_bitmask).apply() - - def status_field_get(self, field_bitmask): - return (self.status_field & field_bitmask) != 0 - - @cached_property - def age(self): - return (datetime.now() - self.registration_date).days - - @cached_property - def approval(self): - return int(f'{self.approval_ru * 1}{self.approval_de * 1}0{self.approval_es * 1}' - f'{self.approval_fr * 1}{self.approval_pt * 1}{self.approval_en * 1}', 2) - - @cached_property - def rejection(self): - return int(f'{self.rejection_ru * 1}{self.rejection_de * 1}0{self.rejection_es * 1}' - f'{self.rejection_fr * 1}{self.rejection_pt * 1}{self.rejection_en * 1}', 2) - - -class ActivationKey(db.Model): - __tablename__ = 'activation_key' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - activation_key = db.Column(db.CHAR(255), primary_key=True, nullable=False) - - -class PenguinMembership(db.Model): - __tablename__ = 'penguin_membership' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - start = db.Column(db.DateTime, primary_key=True, nullable=False) - expires = db.Column(db.DateTime) - start_aware = db.Column(db.Boolean, server_default=db.text("false")) - expires_aware = db.Column(db.Boolean, server_default=db.text("false")) - expired_aware = db.Column(db.Boolean, server_default=db.text("false")) - - -class Login(db.Model): - __tablename__ = 'login' - - id = db.Column(db.Integer, primary_key=True, server_default=db.text("nextval('\"login_id_seq\"'::regclass)")) - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - date = db.Column(db.DateTime, nullable=False, server_default=db.text("now()")) - ip_hash = db.Column(db.CHAR(255), nullable=False) - minutes_played = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - - -class EpfComMessage(db.Model): - __tablename__ = 'epf_com_message' - - message = db.Column(db.Text, nullable=False) - character_id = db.Column(db.ForeignKey('character.id', ondelete='RESTRICT', onupdate='CASCADE'), nullable=False) - date = db.Column(db.DateTime, nullable=False, server_default=db.text("now()")) - - -class CfcDonation(db.Model): - __tablename__ = 'cfc_donation' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - coins = db.Column(db.Integer, nullable=False) - charity = db.Column(db.Integer, nullable=False) - date = db.Column(db.DateTime, nullable=False, server_default=db.text("now()")) - - -class PenguinIntegrations(db.Model): - __tablename__ = 'penguin_integrations' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - discord_id = db.Column(db.String, primary_key=True, nullable=False) - current = db.Column(db.Boolean, server_default=db.text("false")) diff --git a/bot/data/pet.py b/bot/data/pet.py deleted file mode 100644 index 87969cb..0000000 --- a/bot/data/pet.py +++ /dev/null @@ -1,110 +0,0 @@ -from bot.data import AbstractDataCollection, db - - -class Puffle(db.Model): - __tablename__ = 'puffle' - - id = db.Column(db.Integer, primary_key=True) - parent_id = db.Column(db.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - name = db.Column(db.String(50), nullable=False, server_default=db.text("''::character varying")) - cost = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - member = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - favourite_food = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - favourite_toy = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE')) - runaway_postcard = db.Column(db.ForeignKey('postcard.id', ondelete='CASCADE', onupdate='CASCADE')) - - -class PuffleItem(db.Model): - __tablename__ = 'puffle_item' - - id = db.Column(db.Integer, primary_key=True) - parent_id = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - name = db.Column(db.String(50), nullable=False, server_default=db.text("''::character varying")) - type = db.Column(db.String(10), nullable=False, server_default=db.text("'care'::character varying")) - play_external = db.Column(db.String(10), nullable=False, server_default=db.text("'none'::character varying")) - cost = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - quantity = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - member = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - food_effect = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - rest_effect = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - play_effect = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - clean_effect = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - - -class PuffleTreasureFurniture(db.Model): - __tablename__ = 'puffle_treasure_furniture' - - puffle_id = db.Column(db.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - furniture_id = db.Column(db.ForeignKey('furniture.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class PuffleTreasureItem(db.Model): - __tablename__ = 'puffle_treasure_item' - - puffle_id = db.Column(db.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - item_id = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class PuffleTreasurePuffleItem(db.Model): - __tablename__ = 'puffle_treasure_puffle_item' - - puffle_id = db.Column(db.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - puffle_item_id = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), - primary_key=True, nullable=False) - - -class PenguinPuffle(db.Model): - __tablename__ = 'penguin_puffle' - - id = db.Column(db.Integer, primary_key=True, - server_default=db.text("nextval('\"penguin_puffle_id_seq\"'::regclass)")) - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - puffle_id = db.Column(db.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - name = db.Column(db.String(16), nullable=False) - adoption_date = db.Column(db.DateTime, nullable=False, server_default=db.text("now()")) - food = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100")) - play = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100")) - rest = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100")) - clean = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100")) - hat = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE')) - backyard = db.Column(db.Boolean, server_default=db.text("false")) - has_dug = db.Column(db.Boolean, server_default=db.text("false")) - - -class PenguinPuffleItem(db.Model): - __tablename__ = 'penguin_puffle_item' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - item_id = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - quantity = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - - -class PuffleCollection(AbstractDataCollection): - __model__ = Puffle - __indexby__ = 'id' - __filterby__ = 'id' - - -class PenguinPuffleCollection(AbstractDataCollection): - __model__ = PenguinPuffle - __indexby__ = 'id' - __filterby__ = 'penguin_id' - - -class PuffleItemCollection(AbstractDataCollection): - __model__ = PuffleItem - __indexby__ = 'id' - __filterby__ = 'id' - - -class PenguinPuffleItemCollection(AbstractDataCollection): - __model__ = PenguinPuffleItem - __indexby__ = 'item_id' - __filterby__ = 'penguin_id' diff --git a/bot/data/plugin.py b/bot/data/plugin.py deleted file mode 100644 index 9f06253..0000000 --- a/bot/data/plugin.py +++ /dev/null @@ -1,30 +0,0 @@ -from bot.data import AbstractDataCollection, db - - -class PluginAttribute(db.Model): - __tablename__ = 'plugin_attribute' - - plugin_name = db.Column(db.Text, primary_key=True, nullable=False) - name = db.Column(db.Text, primary_key=True, nullable=False) - value = db.Column(db.Text) - - -class PenguinAttribute(db.Model): - __tablename__ = 'penguin_attribute' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - name = db.Column(db.Text, primary_key=True, nullable=False) - value = db.Column(db.Text) - - -class PenguinAttributeCollection(AbstractDataCollection): - __model__ = PenguinAttribute - __indexby__ = 'name' - __filterby__ = 'penguin_id' - - -class PluginAttributeCollection(AbstractDataCollection): - __model__ = PluginAttribute - __indexby__ = 'name' - __filterby__ = 'plugin_name' diff --git a/bot/data/pufflebot/users.py b/bot/data/pufflebot/users.py new file mode 100644 index 0000000..24ecc76 --- /dev/null +++ b/bot/data/pufflebot/users.py @@ -0,0 +1,17 @@ +from bot.data import db_pb + + +class Users(db_pb.Model): + __tablename__ = 'users' + + id = db_pb.Column(db_pb.BigInteger, primary_key=True) + penguin_id = db_pb.Column(db_pb.BigInteger, nullable=False) + language = db_pb.Column(db_pb.String(2), nullable=False, unique=True, server_default=db_pb.text("ru")) + + +class PenguinIntegrations(db_pb.Model): + __tablename__ = 'penguin_integrations' + + discord_id = db_pb.Column(db_pb.BigInteger, primary_key=True) + penguin_id = db_pb.Column(db_pb.BigInteger, primary_key=True, + server_default=db_pb.text("nextval('penguin_integrations_discord_id_seq'::regclass)")) diff --git a/bot/data/quest.py b/bot/data/quest.py deleted file mode 100644 index d02a734..0000000 --- a/bot/data/quest.py +++ /dev/null @@ -1,145 +0,0 @@ -from bot.data import db - - -class Quest(db.Model): - __tablename__ = 'quest' - - id = db.Column(db.Integer, primary_key=True, server_default=db.text("nextval('\"quest_id_seq\"'::regclass)")) - name = db.Column(db.String(50), nullable=False, unique=True) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._tasks = set() - - self._items = set() - self._furniture = set() - self._pet = set() - - self._complete = set() - self._in_progress = set() - - @property - def items(self): - return self._items - - @property - def furniture(self): - return self._furniture - - @property - def pet(self): - return self._pet - - @property - def complete(self): - return self._complete - - @property - def in_progress(self): - return self._in_progress - - @property - def awards(self): - return self._items.union(self._furniture.union(self._pet)) - - @property - def tasks(self): - return self._tasks - - @items.setter - def items(self, child): - if isinstance(child, QuestAwardItem): - self._items.add(child) - - @furniture.setter - def furniture(self, child): - if isinstance(child, QuestAwardFurniture): - self._furniture.add(child) - - @pet.setter - def pet(self, child): - if isinstance(child, QuestAwardPuffleItem): - self._pet.add(child) - - @tasks.setter - def tasks(self, child): - if isinstance(child, QuestTask): - self._tasks.add(child) - - @complete.setter - def complete(self, child): - if isinstance(child, PenguinQuestTask): - if child.complete: - self._complete.add(child.task_id) - else: - self._in_progress.add(child.task_id) - -class GameQuest(db.Model): - __tablename__ = 'game_quest' - - id = db.Column(db.Integer(), nullable=False, primary_key=True, server_default=db.text("nextval('\"game_quest_id_seq\"'::regclass)")) - type_id = db.Column(db.Integer(), nullable=False, server_default='0') - coins = db.Column(db.Integer(), nullable=False, server_default='500') - item = db.Column(db.Integer(), nullable=False, server_default='0') - furniture = db.Column(db.Integer(), nullable=False, server_default='0') - - -class PenguinQuest(db.Model): - __tablename__ = 'penguin_quest' - - id = db.Column(db.Integer(), nullable=False, primary_key=True, server_default=db.text("nextval('\"game_quest_id_seq\"'::regclass)")) - penguin_id = db.Column(db.Integer(), nullable=False, server_default='0') - quest_id = db.Column(db.Integer(), nullable=False, server_default='500') - argv1 = db.Column(db.Integer(), nullable=False, server_default='0') - argv2 = db.Column(db.Integer(), nullable=False, server_default='0') - status = db.Column(db.Integer(), nullable=False, server_default='0') - expires = db.Column(db.DateTime, nullable=False, server_default=db.text("(now() + '23:59:59'::interval)")) - - -class QuestAwardItem(db.Model): - __tablename__ = 'quest_award_item' - - quest_id = db.Column(db.ForeignKey('quest.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - item_id = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class QuestAwardFurniture(db.Model): - __tablename__ = 'quest_award_furniture' - - quest_id = db.Column(db.ForeignKey('quest.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - furniture_id = db.Column(db.ForeignKey('furniture.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - quantity = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - - -class QuestAwardPuffleItem(db.Model): - __tablename__ = 'quest_award_puffle_item' - - quest_id = db.Column(db.ForeignKey('quest.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - puffle_item_id = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), - primary_key=True, nullable=False) - quantity = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - - -class QuestTask(db.Model): - __tablename__ = 'quest_task' - - id = db.Column(db.Integer, primary_key=True, server_default=db.text("nextval('\"quest_id_seq\"'::regclass)")) - quest_id = db.Column(db.ForeignKey('quest.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - description = db.Column(db.String(50), nullable=False) - room_id = db.Column(db.ForeignKey('room.id', ondelete='CASCADE', onupdate='CASCADE')) - data = db.Column(db.String(50)) - - -class PenguinQuestTask(db.Model): - __tablename__ = 'penguin_quest_task' - - task_id = db.Column(db.ForeignKey('quest_task.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, - primary_key=True) - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, - primary_key=True) - complete = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) diff --git a/bot/data/redemption.py b/bot/data/redemption.py deleted file mode 100644 index 997c1a7..0000000 --- a/bot/data/redemption.py +++ /dev/null @@ -1,198 +0,0 @@ -from bot.data import db - - -class RedemptionCode(db.Model): - __tablename__ = 'redemption_code' - - id = db.Column(db.Integer, primary_key=True, - server_default=db.text("nextval('\"redemption_code_id_seq\"'::regclass)")) - code = db.Column(db.String(16), nullable=False, unique=True) - type = db.Column(db.String(8), nullable=False, server_default=db.text("'BLANKET'::character varying")) - coins = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - expires = db.Column(db.DateTime) - uses = db.Column(db.Integer) - party_coins = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._cards = set() - self._flooring = set() - self._furniture = set() - self._igloos = set() - self._items = set() - self._locations = set() - self._puffles = set() - self._puffle_items = set() - - @property - def cards(self): - return self._cards - - @cards.setter - def cards(self, child): - if isinstance(child, RedemptionAwardCard): - self._cards.add(child) - - @property - def flooring(self): - return self._flooring - - @flooring.setter - def flooring(self, child): - if isinstance(child, RedemptionAwardFlooring): - self._flooring.add(child) - - @property - def furniture(self): - return self._furniture - - @furniture.setter - def furniture(self, child): - if isinstance(child, RedemptionAwardFurniture): - self._furniture.add(child) - - @property - def igloos(self): - return self._igloos - - @igloos.setter - def igloos(self, child): - if isinstance(child, RedemptionAwardIgloo): - self._igloos.add(child) - - @property - def items(self): - return self._items - - @items.setter - def items(self, child): - if isinstance(child, RedemptionAwardItem): - self._items.add(child) - - @property - def locations(self): - return self._locations - - @locations.setter - def locations(self, child): - if isinstance(child, RedemptionAwardLocation): - self._locations.add(child) - - @property - def puffles(self): - return self._puffles - - @puffles.setter - def puffles(self, child): - if isinstance(child, RedemptionAwardPuffle): - self._puffles.add(child) - - @property - def puffle_items(self): - return self._puffle_items - - @puffle_items.setter - def puffle_items(self, child): - if isinstance(child, RedemptionAwardPuffleItem): - self._puffle_items.add(child) - - -class RedemptionBook(db.Model): - __tablename__ = 'redemption_book' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(255), nullable=False) - - -class RedemptionBookWord(db.Model): - __tablename__ = 'redemption_book_word' - - question_id = db.Column(db.Integer, primary_key=True, server_default=db.text("nextval('\"redemption_book_word_question_id_seq\"'::regclass)")) - book_id = db.Column(db.ForeignKey('redemption_book.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - page = db.Column(db.SmallInteger, primary_key=True, nullable=False, server_default=db.text("1")) - line = db.Column(db.SmallInteger, primary_key=True, nullable=False, server_default=db.text("1")) - word_number = db.Column(db.SmallInteger, primary_key=True, nullable=False, server_default=db.text("1")) - answer = db.Column(db.String(20), nullable=False) - - -class RedemptionAwardCard(db.Model): - __tablename__ = 'redemption_award_card' - code_id = db.Column(db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - card_id = db.Column(db.ForeignKey('card.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class RedemptionAwardFlooring(db.Model): - __tablename__ = 'redemption_award_flooring' - code_id = db.Column(db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - flooring_id = db.Column(db.ForeignKey('flooring.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class RedemptionAwardFurniture(db.Model): - __tablename__ = 'redemption_award_furniture' - code_id = db.Column(db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - furniture_id = db.Column(db.ForeignKey('furniture.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class RedemptionAwardIgloo(db.Model): - __tablename__ = 'redemption_award_igloo' - code_id = db.Column(db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), - primary_key=True, nullable=False) - igloo_id = db.Column(db.ForeignKey('igloo.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class RedemptionAwardItem(db.Model): - __tablename__ = 'redemption_award_item' - code_id = db.Column(db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - item_id = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class RedemptionAwardLocation(db.Model): - __tablename__ = 'redemption_award_location' - code_id = db.Column(db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - location_id = db.Column(db.ForeignKey('location.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class RedemptionAwardPuffle(db.Model): - __tablename__ = 'redemption_award_puffle' - code_id = db.Column(db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - puffle_id = db.Column(db.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - - -class RedemptionAwardPuffleItem(db.Model): - __tablename__ = 'redemption_award_puffle_item' - code_id = db.Column(db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - puffle_item_id = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), - primary_key=True, nullable=False) - - -class PenguinRedemptionCode(db.Model): - __tablename__ = 'penguin_redemption_code' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - code_id = db.Column(db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False, index=True) - - -class PenguinRedemptionBook(db.Model): - __tablename__ = 'penguin_redemption_book' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - book_id = db.Column(db.ForeignKey('redemption_book.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False, index=True) diff --git a/bot/data/stamp.py b/bot/data/stamp.py deleted file mode 100644 index 072235c..0000000 --- a/bot/data/stamp.py +++ /dev/null @@ -1,68 +0,0 @@ -from bot.data import AbstractDataCollection, db - - -class Stamp(db.Model): - __tablename__ = 'stamp' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - group_id = db.Column(db.ForeignKey('stamp_group.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) - member = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) - rank = db.Column(db.SmallInteger, nullable=False, server_default=db.text("1")) - description = db.Column(db.String(255), nullable=False, server_default=db.text("''::character varying")) - - -class StampGroup(db.Model): - __tablename__ = 'stamp_group' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - parent_id = db.Column(db.ForeignKey('stamp_group.id', ondelete='CASCADE', onupdate='CASCADE')) - - -class CoverStamp(db.Model): - __tablename__ = 'cover_stamp' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - stamp_id = db.Column(db.ForeignKey('stamp.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - x = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - y = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - rotation = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - depth = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - - -class CoverItem(db.Model): - __tablename__ = 'cover_item' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - item_id = db.Column(db.ForeignKey('item.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - x = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - y = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - rotation = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - depth = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) - - -class PenguinStamp(db.Model): - __tablename__ = 'penguin_stamp' - - penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - stamp_id = db.Column(db.ForeignKey('stamp.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, - nullable=False) - recent = db.Column(db.Boolean, nullable=False, server_default=db.text("true")) - - -class StampCollection(AbstractDataCollection): - __model__ = Stamp - __indexby__ = 'id' - __filterby__ = 'id' - - -class PenguinStampCollection(AbstractDataCollection): - __model__ = PenguinStamp - __indexby__ = 'stamp_id' - __filterby__ = 'penguin_id' diff --git a/bot/misc/buttons.py b/bot/misc/buttons.py index 1eebf4f..83086a9 100644 --- a/bot/misc/buttons.py +++ b/bot/misc/buttons.py @@ -2,10 +2,10 @@ class Buttons(disnake.ui.View): - def __init__(self, inter, function, timeout: int = 900): + def __init__(self, inter, function, timeout=900): super().__init__(timeout=timeout) self.inter = inter - self.userID = inter.user.id + self.userID = inter.user.id if inter is not None else None self.function = function async def disableAllItems(self): @@ -69,3 +69,12 @@ async def continueButton(self, button: disnake.ui.Button, inter: disnake.Command await inter.response.send_message(content=f"Это кнопка не для вас", ephemeral=True) return await self.function(inter) + + +class Login(disnake.ui.View): + def __init__(self): + super().__init__() + + @disnake.ui.button(label="Войти", url="https://cpps.app/discord/login") + async def loginButton(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + ... diff --git a/bot/misc/penguin.py b/bot/misc/penguin.py index eeff2cf..487bb8b 100644 --- a/bot/misc/penguin.py +++ b/bot/misc/penguin.py @@ -1,10 +1,9 @@ from loguru import logger -from bot.data import penguin -from bot.data.item import PenguinItemCollection -from bot.data.mail import PenguinPostcard -from bot.data.penguin import PenguinIntegrations -from bot.data.plugin import PenguinAttributeCollection -from bot.data.stamp import PenguinStampCollection +from bot.data.clubpenguin import penguin +from bot.data.clubpenguin.item import PenguinItemCollection +from bot.data.clubpenguin.mail import PenguinPostcard +from bot.data.clubpenguin.plugin import PenguinAttributeCollection +from bot.data.clubpenguin.stamp import PenguinStampCollection class Penguin(penguin.Penguin): @@ -222,15 +221,6 @@ async def add_coins(self, coins): await self.update(coins=self.coins + coins).apply() return self.coins - async def set_integration(self, userID, currentStatus=False): - await PenguinIntegrations.create(penguin_id=self.id, discord_id=str(userID), current=currentStatus) - - async def set_integration_current_status(self, userID, currentStatus): - await (await PenguinIntegrations.get([self.id, str(userID)])).update(current=currentStatus).apply() - - async def delete_integration(self, userID): - await (await PenguinIntegrations.get([self.id, str(userID)])).delete() - def __repr__(self): if self.id is not None: return f'' diff --git a/bot/misc/utils.py b/bot/misc/utils.py index b1fb11e..1bb25fd 100644 --- a/bot/misc/utils.py +++ b/bot/misc/utils.py @@ -1,27 +1,23 @@ -from bot.data import db -from bot.data.penguin import PenguinIntegrations +from bot.data.pufflebot.users import Users from bot.misc.penguin import Penguin async def getPenguinFromInter(inter): - penguin_id = await db.select([PenguinIntegrations.penguin_id]).where( - ((PenguinIntegrations.discord_id == str(inter.user.id)) & - (PenguinIntegrations.current == True))).gino.scalar() - if penguin_id is None: + user = await Users.get(inter.user.id) + if user is None: await inter.response.send_message( - content=f"Мы не нашли вашего пингвина. Пожалуйста воспользуйтесь командой `/login`", ephemeral=True) - return - p = await Penguin().get(penguin_id) + content=f"Мы не нашли вашего пингвина. Пожалуйста воспользуйтесь командой ", + ephemeral=True) + return + p = await Penguin.get(user.penguin_id) await p.setup() return p async def getPenguinOrNoneFromInter(inter): - penguin_id = await db.select([PenguinIntegrations.penguin_id]).where( - ((PenguinIntegrations.discord_id == str(inter.user.id)) & - (PenguinIntegrations.current == True))).gino.scalar() - if penguin_id is None: + user = await Users.get(inter.user.id) + if user is None: return None - p = await Penguin().get(penguin_id) + p = await Penguin.get(user.penguin_id) await p.setup() return p From a0d220346812e1fbd1457046381577d78dedb16c Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Wed, 5 Jul 2023 19:41:07 +0500 Subject: [PATCH 02/27] Refactoring --- bot/cogs/admin/commands.py | 31 ------------------------------- bot/cogs/{user => }/commands.py | 4 ++-- bot/core/puffleBot.py | 20 +++++--------------- bot/core/server.py | 8 +------- 4 files changed, 8 insertions(+), 55 deletions(-) delete mode 100644 bot/cogs/admin/commands.py rename bot/cogs/{user => }/commands.py (98%) diff --git a/bot/cogs/admin/commands.py b/bot/cogs/admin/commands.py deleted file mode 100644 index 6f7e413..0000000 --- a/bot/cogs/admin/commands.py +++ /dev/null @@ -1,31 +0,0 @@ -from loguru import logger -from disnake.ext.commands import Bot, Cog, command, is_owner - - -class AdminCommands(Cog): - def __init__(self, bot): - self.bot: Bot = bot - - logger.info("Admin commands ready") - - @command() - @is_owner() - async def load(self, context, extension): - self.bot.load_extension(f'cogs.admin{extension}') - context.reply("load_extension successful") - - @command() - @is_owner() - async def unload(self, context, extension): - self.bot.unload_extension(f'cogs.admin{extension}') - context.reply("unload_extension successful") - - @command() - @is_owner() - async def reload(self, context, extension): - self.bot.reload_extension(f'cogs.admin{extension}') - context.reply("reload_extension successful") - - -def setup(bot): - bot.add_cog(AdminCommands(bot)) diff --git a/bot/cogs/user/commands.py b/bot/cogs/commands.py similarity index 98% rename from bot/cogs/user/commands.py rename to bot/cogs/commands.py index a3f8661..6dfed25 100644 --- a/bot/cogs/user/commands.py +++ b/bot/cogs/commands.py @@ -1,5 +1,4 @@ from datetime import datetime -from random import sample from disnake import ApplicationCommandInteraction from loguru import logger @@ -128,7 +127,8 @@ async def switch(self, inter: ApplicationCommandInteraction): f"Это можно исправить с помощью команды ") if len(penguin_ids) == 1: - return await inter.response.send_message(ephemeral=True, + return await inter.response.send_message( + ephemeral=True, content=f"У вас привязан только один аккаунт. " f"Вы можете привязать ещё несколько с помощью команды ") diff --git a/bot/core/puffleBot.py b/bot/core/puffleBot.py index 94c03c9..f203efc 100644 --- a/bot/core/puffleBot.py +++ b/bot/core/puffleBot.py @@ -1,23 +1,19 @@ import os import disnake -from disnake.ext.commands import Bot, MissingPermissions +from disnake.ext.commands import InteractionBot, MissingPermissions from loguru import logger -import bot.cogs.admin -import bot.cogs.user +import bot.cogs -class PuffleBot(Bot): +class PuffleBot(InteractionBot): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def load_cogs(self): - for file in os.listdir(bot.cogs.admin.__path__[0]): + for file in os.listdir(bot.cogs.__path__[0]): if file.endswith(".py"): - self.load_extension(f"bot.cogs.admin.{file[:-3]}") - for file in os.listdir(bot.cogs.user.__path__[0]): - if file.endswith(".py"): - self.load_extension(f"bot.cogs.user.{file[:-3]}") + self.load_extension(f"bot.cogs.{file[:-3]}") async def on_ready(self): await self.change_presence(activity=disnake.Game(name="CPPS.APP")) @@ -27,12 +23,6 @@ async def on_ready(self): async def on_error(self, event_method: str, *args, **kwargs): logger.error(f"{event_method}.{args}.{kwargs}") - async def on_command_error(self, context, exception): - logger.error(exception) - - if isinstance(exception, MissingPermissions): - await context.send(f"{context.author}, у вас недостаточно прав для выполнения данной команды!") - async def on_slash_command_error(self, inter, exception): logger.error(exception) diff --git a/bot/core/server.py b/bot/core/server.py index 0ef956c..1956289 100644 --- a/bot/core/server.py +++ b/bot/core/server.py @@ -13,12 +13,6 @@ def __init__(self, config): self.config = config self.db_cp = db_cp self.db_pb = db_pb - self.peers_by_ip = {} - - self.attributes = {} - - self.penguins_by_id = {} - self.penguins_by_username = {} async def start(self): logger.add("logs/log.log") @@ -53,7 +47,7 @@ async def start(self): command_sync_flags = CommandSyncFlags.default() command_sync_flags.sync_commands = True - self.bot = PuffleBot(command_prefix="!", intents=intents, command_sync_flags=command_sync_flags, + self.bot = PuffleBot(intents=intents, command_sync_flags=command_sync_flags, owner_id=527140180696629248) # test_guilds=[755445822920982548], self.bot.load_cogs() From b44722342c8d8cce48f618206f79e731ab5601c9 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Thu, 6 Jul 2023 02:03:56 +0500 Subject: [PATCH 03/27] Add private commands for Discord server CPPS.APP --- bot/cogs/commands.py | 2 +- bot/cogs/private.py | 53 +++++++++++++++++++ bot/core/puffleBot.py | 18 ++++++- bot/misc/buttons.py | 96 +++++++++++++++++++++++++++++++-- bot/misc/constants.py | 120 ++++++++++++++++++++++++++++++++++++++++++ bot/misc/select.py | 33 ++++++++++-- requirements.txt | 7 +-- 7 files changed, 316 insertions(+), 13 deletions(-) create mode 100644 bot/cogs/private.py create mode 100644 bot/misc/constants.py diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index 6dfed25..9168180 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -18,7 +18,7 @@ class UserCommands(Cog): def __init__(self, bot): self.bot = bot - logger.info("Users commands ready") + logger.info(f"Loads {len(self.get_slash_commands())} public users commands") @slash_command(name="ilyash", description=":D") async def ilyash(self, inter: ApplicationCommandInteraction): diff --git a/bot/cogs/private.py b/bot/cogs/private.py new file mode 100644 index 0000000..39a101c --- /dev/null +++ b/bot/cogs/private.py @@ -0,0 +1,53 @@ +import disnake +from disnake import ApplicationCommandInteraction, AllowedMentions, Webhook +from disnake.ext.commands import Cog, slash_command +from loguru import logger + +from bot.misc.buttons import Rules +from bot.misc.constants import embedRuleImageRu, embedRuleRu, embedAboutImage, embedAbout, guild_ids, \ + avatarImageBytearray +from bot.misc.select import About + + +class PrivateCommands(Cog): + def __init__(self, bot): + self.bot = bot + + logger.info(f"Loads {len(self.get_slash_commands())} private commands") + + @slash_command(name="transfer", description="Transfer images from the current channel to the forum", + guild_ids=guild_ids) + async def transfer(self, inter: ApplicationCommandInteraction, channel_id: str): + await inter.response.defer() + source_channel = inter.channel + destination_channel = disnake.utils.get(inter.guild.channels, id=int(channel_id)) + + async for message in source_channel.history(limit=None): + if message.attachments: + content = f", комментарий: {message.content}" if message.content else '' + await destination_channel.create_thread(name=f"{message.author.display_name}", + content=f'Автор: {message.author.mention}{content}', + files=[await attachment.to_file() for attachment in + message.attachments], + allowed_mentions=AllowedMentions(users=False) + ) + await inter.edit_original_response(f"Успешно перенесено в <#{channel_id}>!") + + @slash_command(name="rules", description="Send rules embed", guild_ids=guild_ids) + async def rules(self, inter: ApplicationCommandInteraction): + webhook: Webhook = await inter.channel.create_webhook(name="CPPS.APP", avatar=avatarImageBytearray) + message = await webhook.send(embeds=[embedRuleImageRu, embedRuleRu], wait=True) + await message.edit(view=Rules(message)) + await inter.send("Success", ephemeral=True) + + @slash_command(name="about", description="Send about embed", guild_ids=guild_ids) + async def about(self, inter: ApplicationCommandInteraction): + view = disnake.ui.View(timeout=None) + view.add_item(About()) + webhook = await inter.channel.create_webhook(name="CPPS.APP", avatar=avatarImageBytearray) + await webhook.send(embeds=[embedAboutImage, embedAbout], view=view) + await inter.send("Success", ephemeral=True) + + +def setup(bot): + bot.add_cog(PrivateCommands(bot)) diff --git a/bot/core/puffleBot.py b/bot/core/puffleBot.py index f203efc..567a244 100644 --- a/bot/core/puffleBot.py +++ b/bot/core/puffleBot.py @@ -1,9 +1,13 @@ import os import disnake +from disnake import Webhook, Game from disnake.ext.commands import InteractionBot, MissingPermissions from loguru import logger import bot.cogs +from bot.misc.buttons import Rules +from bot.misc.constants import rules_message_id, about_message_id, rules_webhook_id +from bot.misc.select import About class PuffleBot(InteractionBot): @@ -16,10 +20,22 @@ def load_cogs(self): self.load_extension(f"bot.cogs.{file[:-3]}") async def on_ready(self): - await self.change_presence(activity=disnake.Game(name="CPPS.APP")) + await self.change_presence(activity=Game(name="CPPS.APP")) logger.info(f"Bot {self.user} ready") + async def on_connect(self): + logger.info(f'Bot connected') + + if rules_message_id is not None: + rules_webhook: Webhook = await self.fetch_webhook(rules_webhook_id) + rules_message = await rules_webhook.fetch_message(rules_message_id) + self.add_view(Rules(rules_message), message_id=rules_message_id) + if about_message_id is not None: + view = disnake.ui.View(timeout=None) + view.add_item(About()) + self.add_view(view, message_id=about_message_id) + async def on_error(self, event_method: str, *args, **kwargs): logger.error(f"{event_method}.{args}.{kwargs}") diff --git a/bot/misc/buttons.py b/bot/misc/buttons.py index 83086a9..2533035 100644 --- a/bot/misc/buttons.py +++ b/bot/misc/buttons.py @@ -1,5 +1,8 @@ import disnake +from bot.misc.constants import embedRuleImageRu, embedRuleRu, embedRuleImageEn, embedRuleEn, embedRolesRu, embedRolesEn, \ + enFullRulesLink, ruFullRulesLink + class Buttons(disnake.ui.View): def __init__(self, inter, function, timeout=900): @@ -22,7 +25,7 @@ def __init__(self, inter, function): super().__init__(inter, function) @disnake.ui.button(label="Да", style=disnake.ButtonStyle.green, custom_id="yes") - async def yesButton(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + async def yesButton(self, _, inter: disnake.CommandInteraction): if inter.user.id != self.userID: await inter.response.send_message(content=f"Это кнопка не для вас", ephemeral=True) return @@ -30,7 +33,7 @@ async def yesButton(self, button: disnake.ui.Button, inter: disnake.CommandInter await self.function(inter) @disnake.ui.button(label="Нет", style=disnake.ButtonStyle.red, custom_id="no") - async def noButton(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + async def noButton(self, _, inter: disnake.CommandInteraction): if inter.user.id != self.userID: await inter.response.send_message(content=f"Это кнопка не для вас", ephemeral=True) return @@ -43,7 +46,7 @@ def __init__(self, inter: disnake.CommandInteraction, function): super().__init__(inter, function) @disnake.ui.button(label="Отмена", style=disnake.ButtonStyle.gray, custom_id="no") - async def noButton(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + async def noButton(self, _, inter: disnake.CommandInteraction): if inter.user.id != self.userID: await inter.response.send_message(content=f"Это кнопка не для вас", ephemeral=True) return @@ -51,7 +54,7 @@ async def noButton(self, button: disnake.ui.Button, inter: disnake.CommandIntera await inter.response.send_message(content=f"Отменено") @disnake.ui.button(label="Выйти", style=disnake.ButtonStyle.red, custom_id="yes") - async def yesButton(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + async def yesButton(self, _, inter: disnake.CommandInteraction): if inter.user.id != self.userID: await inter.response.send_message(content=f"Это кнопка не для вас", ephemeral=True) return @@ -64,7 +67,7 @@ def __init__(self, inter: disnake.CommandInteraction, function): super().__init__(inter, function) @disnake.ui.button(label="Продолжить", style=disnake.ButtonStyle.blurple, custom_id="continue") - async def continueButton(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + async def continueButton(self, _, inter: disnake.CommandInteraction): if inter.user.id != self.userID: await inter.response.send_message(content=f"Это кнопка не для вас", ephemeral=True) return @@ -78,3 +81,86 @@ def __init__(self): @disnake.ui.button(label="Войти", url="https://cpps.app/discord/login") async def loginButton(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): ... + + +class Rules(disnake.ui.View): + def __init__(self, massage): + super().__init__(timeout=None) + self.massage = massage + self.language = "Ru" + + @disnake.ui.button(label="Translate", style=disnake.ButtonStyle.primary, emoji="🇺🇸", custom_id="translate") + async def translate(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + if self.language == "En": + self.language = "Ru" + button.label = "Translate" + button.emoji = "🇺🇸" + self.children[1].label = "Полные правила" + self.children[1].url = ruFullRulesLink + await self.massage.edit(embeds=[embedRuleImageRu, embedRuleRu], view=self) + elif self.language == "Ru": + self.language = "En" + button.label = "Перевести" + button.emoji = "🇷🇺" + self.children[1].label = "Full rules" + self.children[1].url = enFullRulesLink + + await self.massage.edit(embeds=[embedRuleImageEn, embedRuleEn], view=self) + await inter.response.defer() + + @disnake.ui.button(label="Полные правила", style=disnake.ButtonStyle.link, + url="https://wiki.cpps.app/index.php?title=Правила") + async def FullRules(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + ... + + +class RulesEphemeral(disnake.ui.View): + def __init__(self, inter): + super().__init__(timeout=None) + self.inter = inter + self.language = "Ru" + + @disnake.ui.button(label="Translate", style=disnake.ButtonStyle.primary, emoji="🇺🇸", custom_id="translate") + async def translate(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + if self.language == "En": + self.language = "Ru" + button.label = "Translate" + button.emoji = "🇺🇸" + self.children[1].label = "Полные правила" + self.children[1].url = ruFullRulesLink + await self.inter.edit_original_response(embeds=[embedRuleImageRu, embedRuleRu], view=self) + elif self.language == "Ru": + self.language = "En" + button.label = "Перевести" + button.emoji = "🇷🇺" + self.children[1].label = "Full rules" + + self.children[1].url = enFullRulesLink + await self.inter.edit_original_response(embeds=[embedRuleImageEn, embedRuleEn], view=self) + await inter.response.defer() + + @disnake.ui.button(label="Полные правила", style=disnake.ButtonStyle.link, + url="https://wiki.cpps.app/index.php?title=Правила") + async def FullRules(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + ... + + +class Roles(disnake.ui.View): + def __init__(self, inter): + super().__init__(timeout=None) + self.inter = inter + self.language = "Ru" + + @disnake.ui.button(label="Translate", style=disnake.ButtonStyle.primary, emoji="🇺🇸", custom_id="translate") + async def translate(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + if self.language == "En": + self.language = "Ru" + button.label = "Translate" + button.emoji = "🇺🇸" + await self.inter.edit_original_response(embeds=[embedRolesRu], view=self) + elif self.language == "Ru": + self.language = "En" + button.label = "Перевести" + button.emoji = "🇷🇺" + await self.inter.edit_original_response(embeds=[embedRolesEn], view=self) + await inter.response.defer() diff --git a/bot/misc/constants.py b/bot/misc/constants.py new file mode 100644 index 0000000..fa0e42f --- /dev/null +++ b/bot/misc/constants.py @@ -0,0 +1,120 @@ +from io import BytesIO + +from requests import get +from disnake import Embed + +# ids +cppsapp_server_id = 755445822920982548 +test_server_id = 830884170934124585 +rules_webhook_id = 1126228044084949083 +rules_message_id = 1126228045854937178 +about_message_id = 1126228166961287228 + +# Links +avatarImageLink = "https://sun9-53.userapi.com/impg/u2ZUQnEo_eY4SgbIGIWy860tArL6mIeeObol1w/WoMi6hnW2QE.jpg?size=1536x1536&quality=96&sign=be7a5cade4cbb255a120f937271f327c" +enFullRulesLink = "https://docs.google.com/document/d/1gqgdoT1BeAUGahlAr3zrPqWmr3yDh0irG3sRVqMNt1c/" +ruFullRulesLink = "https://wiki.cpps.app/index.php?title=Правила" +placeholderImageLink = "https://cdn.discordapp.com/attachments/937328189859057674/944647455109156884/1.png" + +# Arrays +guild_ids = [cppsapp_server_id, test_server_id] + +# Bytearrays +avatarImageBytearray = BytesIO(get(avatarImageLink).content).getvalue() + +# Custom emojis +emojiLaughing = "<:laughing:788392697336954920>" +emojiDay = "<:day:788395886450966588>" +emojiModerator = "<:moderator:789476531915325510>" +emojiGame = "<:game:788396232456011796>" +emojiVk = "<:VK:1121760227994382459>" +emojiYt = "<:yt:1121764618239496263>" +emojiGameFavicon = "<:favicon:1121758719634571405>" +emojiWikiFavicon = "<:wiki:1121764147307237447>" + +# ========== EMBEDS ========== + + +# Russian rule embeds +embedRuleRu = Embed(color=0x2B2D31) +embedRuleRu.add_field(name=f"{emojiLaughing} **Уважай окружающих**", value="Не дразни и не обижай окружающих", + inline=False) +embedRuleRu.add_field(name=f"{emojiDay} **Общайся вежливо**", + value="Не используй грубые или неприличные выражения", inline=False) +embedRuleRu.add_field(name=f"{emojiModerator} **Соблюдай правила безопасности в интернете**", + value="Не делись личной информацией", inline=False) +embedRuleRu.add_field(name=f"{emojiGame} **Играй по-честному**", + value="Не используй программы сторонних разработчиков", inline=False) +embedRuleRu.set_image(url=placeholderImageLink) +embedRuleRu.set_footer(text="До встречи на острове!") +embedRuleImageRu = Embed(color=0x2B2D31) +embedRuleImageRu.set_image(url="https://cpps.app/rules-ru.png") + +# English rule embeds +embedRuleEn = Embed(color=0x2B2D31) +embedRuleEn.add_field(name=f"{emojiLaughing} **Respect others**", value="No bullying or being mean to others", + inline=False) +embedRuleEn.add_field(name=f"{emojiDay} **Chat nicely**", value="No rude or inappropriate language", inline=False) +embedRuleEn.add_field(name=f"{emojiModerator} **Stay safe online**", + value="No sharing personal information", inline=False) +embedRuleEn.add_field(name=f"{emojiGame} **Play fair**", + value="No cheating or use of third party programs", inline=False) +embedRuleEn.set_image(url=placeholderImageLink) +embedRuleEn.set_footer(text="Waddle On!") +embedRuleImageEn = Embed(color=0x2B2D31) +embedRuleImageEn.set_image(url="https://cpps.app/rules-en.png") + +# About embeds +embedAbout = Embed(color=0x2B2D31) +embedAbout.description = """ +## :flag_ru: +Мы возрождение оригинального Клуба Пингвинов от Disney, сделанное его фанатами. +*Перейдите в раздел, нажав на него в меню выбора, чтобы узнать подробнее.* + +## :flag_us: +We are a copy of the original game from Disney, made by fans. +*Go to the section by clicking on it in the selection menu to learn more.* +""" +embedAbout.set_image(url=placeholderImageLink) +embedAboutImage = Embed(color=0x2B2D31) +embedAboutImage.set_image(url="https://cpps.app/CPPS.APP.png") + +# Links embed +embedLinks = Embed(color=0x2B2D31) +embedLinks.description = f""" +```Links to social networks and other``` +> **{emojiVk} • VK – https://vk.com/cpps_official/** + +> **{emojiYt} • YOUTUBE – https://www.youtube.com/@cppsapp/** + +> **{emojiGameFavicon} • GAME – https://cpps.app/** + +> **{emojiWikiFavicon} • WIKI – https://wiki.cpps.app/** +""" +embedLinks.set_image(url=placeholderImageLink) + +# Roles embeds +embedRolesRu = Embed(color=0x2B2D31) +embedRolesRu.description = """ +```Роли-достижения (ACHIEVEMENTS):``` +<@&860176034834153543> — Победитель конкурса «Рейтинг активности». + +<@&792899688608038913> — [Nitro бустеры](https://support.discord.com/hc/ru/articles/360028038352) сервера. + +<@&860125656427003905> — Пингвины, владеющие популярным бизнесом. + +<@&1124784263754158090> — Игроки, получившие *шляпу экскурсовода* в игре. +""" +embedRolesRu.set_image(url=placeholderImageLink) +embedRolesEn = Embed(color=0x2B2D31) +embedRolesEn.description = """ +```Achievements roles:``` +<@&860176034834153543> — The winner of the «Activity Rating» contest. + +<@&792899688608038913> — [Nitro boosters](https://support.discord.com/hc/en-us/articles/360028038352). + +<@&860125656427003905> — Penguins who own a popular business. + +<@&1124784263754158090> — Players who have received a *tour guide hat* in the game. +""" +embedRolesEn.set_image(url=placeholderImageLink) diff --git a/bot/misc/select.py b/bot/misc/select.py index f6f4f30..cac6bd1 100644 --- a/bot/misc/select.py +++ b/bot/misc/select.py @@ -1,6 +1,9 @@ -import disnake +from disnake import MessageInteraction, SelectOption, AllowedMentions from disnake.ui import Select +from bot.misc.buttons import RulesEphemeral, Roles +from bot.misc.constants import embedLinks, embedRuleImageRu, embedRuleRu, embedRolesRu + class SelectPenguins(Select): def __init__(self, penguinsList, function, userID): @@ -10,10 +13,10 @@ def __init__(self, penguinsList, function, userID): options = [] for penguin in penguinsList: - options.append(disnake.SelectOption(label=penguin["safe_name"], value=penguin["id"])) + options.append(SelectOption(label=penguin["safe_name"], value=penguin["id"])) super().__init__(placeholder="Выберите пингвина", options=options, custom_id="penguins") - async def callback(self, interaction: disnake.MessageInteraction): + async def callback(self, interaction: MessageInteraction): if interaction.user.id != self.userID: await interaction.response.send_message(content=f"Ай-ай-ай,не суй пальцы в розетку", ephemeral=True) return @@ -24,3 +27,27 @@ async def callback(self, interaction: disnake.MessageInteraction): penguin_id = int(interaction.values[0]) await self.function(interaction, penguin_id) + + +class About(Select): + def __init__(self): + options = [SelectOption(label="Links", value="Links"), + SelectOption(label="Rules", value="Rules"), + SelectOption(label="List of roles", value="Roles")] + super().__init__(options=options, custom_id="about") + + async def callback(self, inter: MessageInteraction): + if inter.values[0] == "Links": + await inter.send(ephemeral=True, embeds=[embedLinks], + allowed_mentions=AllowedMentions(roles=False, users=False)) + elif inter.values[0] == "Rules": + await inter.send(ephemeral=True, embeds=[embedRuleImageRu, embedRuleRu], + allowed_mentions=AllowedMentions(roles=False, users=False)) + await inter.edit_original_response(view=RulesEphemeral(inter)) + elif inter.values[0] == "Roles": + await inter.send(ephemeral=True, embeds=[embedRolesRu], + allowed_mentions=AllowedMentions(roles=False, users=False)) + await inter.edit_original_response(view=Roles(inter)) + + else: + await inter.send(ephemeral=True, content=inter.values[0]) diff --git a/requirements.txt b/requirements.txt index 5d6f299..fdf9304 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -asyncio +asyncio~=3.4.3 gino~=1.0.1 -disnake~=2.8.1 -loguru~=0.7.0 \ No newline at end of file +disnake==2.9.0 +loguru~=0.7.0 +requests==2.31.0 From a14cf755989be0dbe7858aea93b79ce1984447fb Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Thu, 6 Jul 2023 02:16:57 +0500 Subject: [PATCH 04/27] Refactoring --- bot/cogs/commands.py | 43 +++++++++++++++++-------------------------- bot/misc/buttons.py | 19 +++++++------------ bot/misc/modals.py | 6 +++--- bot/misc/select.py | 4 ++-- bot/misc/utils.py | 2 +- 5 files changed, 30 insertions(+), 44 deletions(-) diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index 9168180..f056819 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -22,7 +22,7 @@ def __init__(self, bot): @slash_command(name="ilyash", description=":D") async def ilyash(self, inter: ApplicationCommandInteraction): - await inter.response.send_message(content=f"Теперь вы пешка иляша!") + await inter.send(content=f"Теперь вы пешка иляша!") @slash_command(name="card", description="Показывает полезную информацию о твоём аккаунте") async def card(self, inter: ApplicationCommandInteraction): @@ -42,13 +42,11 @@ async def card(self, inter: ApplicationCommandInteraction): embed.add_field(name="Марки", value=len(p.stamps) + p.count_epf_awards()) embed.add_field(name="Возраст пингвина", value=f"{(datetime.now() - p.registration_date).days} дней") embed.add_field(name="Сотрудник", value="Да" if p.moderator else "Нет") - await inter.response.send_message(embed=embed) + await inter.send(embed=embed) @slash_command(name="login", description="Привязать свой Discord аккаунт к пингвину") async def login(self, inter: ApplicationCommandInteraction): - view = Login() - return await inter.response.send_message( - content=f"Перейдите на сайт и пройдите авторизацию", view=view) + return await inter.send(content=f"Перейдите на сайт и пройдите авторизацию", view=Login()) @slash_command(name="logout", description="Отвязать свой Discord аккаунт от своего пингвина") async def logout(self, inter: ApplicationCommandInteraction): @@ -61,24 +59,21 @@ async def run(buttonInter): if len(penguin_ids) == 0: await user.update(penguin_id=None).apply() - return await buttonInter.response.send_message( - content=f"Ваш аккаунт `{p.safe_name()}` успешно отвязан.") + return await buttonInter.send(content=f"Ваш аккаунт `{p.safe_name()}` успешно отвязан.") if len(penguin_ids) == 1: newCurrentPenguin: Penguin = await Penguin.get(penguin_ids[0][0]) await user.update(penguin_id=penguin_ids[0][0]).apply() - return await buttonInter.response.send_message( + return await buttonInter.send( content=f"Ваш аккаунт `{p.safe_name()}` успешно отвязан. " f"Теперь ваш текущий аккаунт `{newCurrentPenguin.safe_name()}`") - return await buttonInter.response.send_message( + return await buttonInter.send( content=f"Ваш аккаунт `{p.safe_name()}` успешно отвязан. " f"Чтобы выбрать текущий аккаунт воспользуйтесь командой ") - view = Logout(inter, run) - - await inter.response.send_message(content=f"Вы уверены, что хотите выйти с аккаунта `{p.safe_name()}`?", - view=view) + await inter.send(content=f"Вы уверены, что хотите выйти с аккаунта `{p.safe_name()}`?", + view=Logout(inter, run)) @slash_command(name="pay", description="Перевести свои монеты другому игроку") async def pay(self, inter: ApplicationCommandInteraction, @@ -91,16 +86,13 @@ async def pay(self, inter: ApplicationCommandInteraction, r: Penguin = await Penguin.get(receiverId) if amount <= 0: - return await inter.response.send_message( - content='Пожалуйста введите правильное число монет') + return await inter.send(content='Пожалуйста введите правильное число монет') if p.id == r.id: - return await inter.response.send_message( - content="Вы не можете передать монеты самому себе!") + return await inter.send(content="Вы не можете передать монеты самому себе!") if p.coins < amount: - return await inter.response.send_message( - content='У вас недостаточно монет для перевода') + return await inter.send(content='У вас недостаточно монет для перевода') await p.update(coins=p.coins - amount).apply() await r.update(coins=r.coins + amount).apply() @@ -111,8 +103,7 @@ async def pay(self, inter: ApplicationCommandInteraction, text=f"Перевёл игроку {r.username} {int(amount)} монет. Через Discord бота", room_id=0, server_id=8000) - await inter.response.send_message( - content=f"Вы успешно передали `{amount}` монет игроку `{receiver}`!") + await inter.send(content=f"Вы успешно передали `{amount}` монет игроку `{receiver}`!") @slash_command(name="switch", description="Сменить текущий аккаунт") async def switch(self, inter: ApplicationCommandInteraction): @@ -122,12 +113,12 @@ async def switch(self, inter: ApplicationCommandInteraction): (PenguinIntegrations.discord_id == inter.user.id)).gino.all() if len(penguin_ids) == 0: - return await inter.response.send_message( + return await inter.send( content=f"У вас не привязан ни один аккаунт. " f"Это можно исправить с помощью команды ") if len(penguin_ids) == 1: - return await inter.response.send_message( + return await inter.send( ephemeral=True, content=f"У вас привязан только один аккаунт. " f"Вы можете привязать ещё несколько с помощью команды ") @@ -138,7 +129,7 @@ async def run(selectInter, penguin_id): await user.update(penguin_id=penguin_id).apply() - return await selectInter.response.send_message( + return await selectInter.send( content=f"Успешно. Теперь ваш текущий аккаунт `{newCurrentPenguin.safe_name()}`") view = disnake.ui.View() @@ -147,11 +138,11 @@ async def run(selectInter, penguin_id): view.add_item(SelectPenguins(penguinsList, run, inter.user.id)) if p is None: - return await inter.response.send_message( + return await inter.send( content=f"Ваш текущий аккаунт не выбран. Какой аккаунт вы хотите сделать текущим?", view=view) - return await inter.response.send_message( + return await inter.send( content=f"Ваш текущий аккаунт: `{p.safe_name()}`. Какой аккаунт вы хотите сделать текущим?", view=view) diff --git a/bot/misc/buttons.py b/bot/misc/buttons.py index 2533035..4d2bc70 100644 --- a/bot/misc/buttons.py +++ b/bot/misc/buttons.py @@ -27,18 +27,16 @@ def __init__(self, inter, function): @disnake.ui.button(label="Да", style=disnake.ButtonStyle.green, custom_id="yes") async def yesButton(self, _, inter: disnake.CommandInteraction): if inter.user.id != self.userID: - await inter.response.send_message(content=f"Это кнопка не для вас", ephemeral=True) - return + return await inter.send(content=f"Это кнопка не для вас", ephemeral=True) await self.disableAllItems() await self.function(inter) @disnake.ui.button(label="Нет", style=disnake.ButtonStyle.red, custom_id="no") async def noButton(self, _, inter: disnake.CommandInteraction): if inter.user.id != self.userID: - await inter.response.send_message(content=f"Это кнопка не для вас", ephemeral=True) - return + return await inter.send(content=f"Это кнопка не для вас", ephemeral=True) await self.disableAllItems() - await inter.response.send_message(content=f"Отменено") + await inter.send(content=f"Отменено") class Logout(Buttons): @@ -48,16 +46,14 @@ def __init__(self, inter: disnake.CommandInteraction, function): @disnake.ui.button(label="Отмена", style=disnake.ButtonStyle.gray, custom_id="no") async def noButton(self, _, inter: disnake.CommandInteraction): if inter.user.id != self.userID: - await inter.response.send_message(content=f"Это кнопка не для вас", ephemeral=True) - return + return await inter.send(content=f"Это кнопка не для вас", ephemeral=True) await self.disableAllItems() - await inter.response.send_message(content=f"Отменено") + await inter.send(content=f"Отменено") @disnake.ui.button(label="Выйти", style=disnake.ButtonStyle.red, custom_id="yes") async def yesButton(self, _, inter: disnake.CommandInteraction): if inter.user.id != self.userID: - await inter.response.send_message(content=f"Это кнопка не для вас", ephemeral=True) - return + return await inter.send(content=f"Это кнопка не для вас", ephemeral=True) await self.disableAllItems() await self.function(inter) @@ -69,8 +65,7 @@ def __init__(self, inter: disnake.CommandInteraction, function): @disnake.ui.button(label="Продолжить", style=disnake.ButtonStyle.blurple, custom_id="continue") async def continueButton(self, _, inter: disnake.CommandInteraction): if inter.user.id != self.userID: - await inter.response.send_message(content=f"Это кнопка не для вас", ephemeral=True) - return + return await inter.send(content=f"Это кнопка не для вас", ephemeral=True) await self.function(inter) diff --git a/bot/misc/modals.py b/bot/misc/modals.py index d0d94a2..fbfaf2e 100644 --- a/bot/misc/modals.py +++ b/bot/misc/modals.py @@ -11,6 +11,6 @@ def __init__(self, function): super().__init__(title="Привяжите свои аккаунты", components=components, custom_id="login") - async def callback(self, interaction: disnake.ModalInteraction): - authCode = interaction.text_values["authCodeInput"] - await self.function(interaction, authCode) + async def callback(self, inter: disnake.ModalInteraction): + authCode = inter.text_values["authCodeInput"] + await self.function(inter, authCode) diff --git a/bot/misc/select.py b/bot/misc/select.py index cac6bd1..908655d 100644 --- a/bot/misc/select.py +++ b/bot/misc/select.py @@ -18,10 +18,10 @@ def __init__(self, penguinsList, function, userID): async def callback(self, interaction: MessageInteraction): if interaction.user.id != self.userID: - await interaction.response.send_message(content=f"Ай-ай-ай,не суй пальцы в розетку", ephemeral=True) + await interaction.send(content=f"Ай-ай-ай,не суй пальцы в розетку", ephemeral=True) return if self.disabled: - await interaction.response.send_message(content=f"Вы уже сменили аккаунт", ephemeral=True) + await interaction.send(content=f"Вы уже сменили аккаунт", ephemeral=True) return self.disabled = True diff --git a/bot/misc/utils.py b/bot/misc/utils.py index 1bb25fd..0f14bc5 100644 --- a/bot/misc/utils.py +++ b/bot/misc/utils.py @@ -5,7 +5,7 @@ async def getPenguinFromInter(inter): user = await Users.get(inter.user.id) if user is None: - await inter.response.send_message( + await inter.send( content=f"Мы не нашли вашего пингвина. Пожалуйста воспользуйтесь командой ", ephemeral=True) return From e4e7a3b082bb3e47d21ccda47a80ec8baea6c1b3 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Fri, 7 Jul 2023 15:34:05 +0500 Subject: [PATCH 05/27] Add command `/online` --- bot/cogs/commands.py | 14 ++++++++++++++ bot/misc/constants.py | 13 +++++++++---- requirements.txt | 3 +++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index f056819..29c78d1 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -1,12 +1,16 @@ +import ast from datetime import datetime +from bs4 import BeautifulSoup from disnake import ApplicationCommandInteraction from loguru import logger import disnake from disnake.ext.commands import Cog, Param, slash_command +from requests import Session from bot.data import db_pb from bot.data.clubpenguin.moderator import Logs +from bot.misc.constants import online_url, headers from bot.misc.penguin import Penguin from bot.data.pufflebot.users import Users, PenguinIntegrations from bot.misc.buttons import Logout, Login @@ -146,6 +150,16 @@ async def run(selectInter, penguin_id): content=f"Ваш текущий аккаунт: `{p.safe_name()}`. Какой аккаунт вы хотите сделать текущим?", view=view) + @slash_command(name="online", description="Показывает количество игроков которые сейчас онлайн") + async def online(self, inter: ApplicationCommandInteraction): + with Session() as s: + s.headers.update(headers) + response = s.get(online_url) + soup = BeautifulSoup(response.text, "html.parser") + + online: int = ast.literal_eval(soup.text)[0]['3104'] + await inter.send(content=f"В нашей игре сейчас `{online}` человек/а онлайн") + def setup(bot): bot.add_cog(UserCommands(bot)) diff --git a/bot/misc/constants.py b/bot/misc/constants.py index fa0e42f..4f04f74 100644 --- a/bot/misc/constants.py +++ b/bot/misc/constants.py @@ -2,19 +2,24 @@ from requests import get from disnake import Embed - + # ids cppsapp_server_id = 755445822920982548 test_server_id = 830884170934124585 -rules_webhook_id = 1126228044084949083 -rules_message_id = 1126228045854937178 -about_message_id = 1126228166961287228 +rules_webhook_id = 1126642813543657525 +rules_message_id = 1126642815544344628 +about_message_id = 1126642785219530782 # Links avatarImageLink = "https://sun9-53.userapi.com/impg/u2ZUQnEo_eY4SgbIGIWy860tArL6mIeeObol1w/WoMi6hnW2QE.jpg?size=1536x1536&quality=96&sign=be7a5cade4cbb255a120f937271f327c" enFullRulesLink = "https://docs.google.com/document/d/1gqgdoT1BeAUGahlAr3zrPqWmr3yDh0irG3sRVqMNt1c/" ruFullRulesLink = "https://wiki.cpps.app/index.php?title=Правила" placeholderImageLink = "https://cdn.discordapp.com/attachments/937328189859057674/944647455109156884/1.png" +online_url = "https://play.cpps.app/ru/online" + +# Dicts +headers = { + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"} # Arrays guild_ids = [cppsapp_server_id, test_server_id] diff --git a/requirements.txt b/requirements.txt index fdf9304..d94e872 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,6 @@ gino~=1.0.1 disnake==2.9.0 loguru~=0.7.0 requests==2.31.0 + +bs4~=0.0.1 +beautifulsoup4~=4.12.2 \ No newline at end of file From a64e38374e22b8b3970cd32c652a65ef6c977466 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Fri, 7 Jul 2023 23:51:16 +0500 Subject: [PATCH 06/27] Minor changes in commands `/card`, `/logout` and `/switch`. Refactoring --- bot/cogs/commands.py | 90 +++++++++++++++++-------------------------- bot/misc/buttons.py | 68 +++++++++++++++++--------------- bot/misc/constants.py | 6 ++- bot/misc/select.py | 24 +++++------- bot/misc/utils.py | 11 ++++-- 5 files changed, 95 insertions(+), 104 deletions(-) diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index 29c78d1..0b54188 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -10,12 +10,12 @@ from bot.data import db_pb from bot.data.clubpenguin.moderator import Logs -from bot.misc.constants import online_url, headers +from bot.misc.constants import online_url, headers, loginCommand from bot.misc.penguin import Penguin from bot.data.pufflebot.users import Users, PenguinIntegrations from bot.misc.buttons import Logout, Login from bot.misc.select import SelectPenguins -from bot.misc.utils import getPenguinFromInter, getPenguinOrNoneFromInter +from bot.misc.utils import getPenguinFromInter, getPenguinOrNoneFromId class UserCommands(Cog): @@ -26,11 +26,18 @@ def __init__(self, bot): @slash_command(name="ilyash", description=":D") async def ilyash(self, inter: ApplicationCommandInteraction): - await inter.send(content=f"Теперь вы пешка иляша!") + await inter.send(f"Теперь вы пешка иляша!") @slash_command(name="card", description="Показывает полезную информацию о твоём аккаунте") - async def card(self, inter: ApplicationCommandInteraction): - p: Penguin = await getPenguinFromInter(inter) + async def card(self, inter: ApplicationCommandInteraction, + user: disnake.User = Param(default=None, description='Пользователь, чью карточку нужно показать')): + if user: + p = await getPenguinOrNoneFromId(user.id) + if p is None: + return await inter.send(f"Мы не нашли пингвина у указанного пользователя.", ephemeral=True) + else: + p: Penguin = await getPenguinFromInter(inter) + if p.get_custom_attribute("mood") and p.get_custom_attribute("mood") != " ": mood = f'*{p.get_custom_attribute("mood")}*' else: @@ -50,34 +57,17 @@ async def card(self, inter: ApplicationCommandInteraction): @slash_command(name="login", description="Привязать свой Discord аккаунт к пингвину") async def login(self, inter: ApplicationCommandInteraction): - return await inter.send(content=f"Перейдите на сайт и пройдите авторизацию", view=Login()) + return await inter.send(f"Перейдите на сайт и пройдите авторизацию", view=Login()) @slash_command(name="logout", description="Отвязать свой Discord аккаунт от своего пингвина") async def logout(self, inter: ApplicationCommandInteraction): p: Penguin = await getPenguinFromInter(inter) + user: Users = await Users.get(inter.user.id) + penguin_ids = await db_pb.select([PenguinIntegrations.penguin_id]).where( + (PenguinIntegrations.discord_id == inter.user.id)).gino.all() - async def run(buttonInter): - user: Users = await Users.get(inter.user.id) - penguin_ids = await db_pb.select([PenguinIntegrations.penguin_id]).where( - (PenguinIntegrations.discord_id == inter.user.id)).gino.all() - - if len(penguin_ids) == 0: - await user.update(penguin_id=None).apply() - return await buttonInter.send(content=f"Ваш аккаунт `{p.safe_name()}` успешно отвязан.") - - if len(penguin_ids) == 1: - newCurrentPenguin: Penguin = await Penguin.get(penguin_ids[0][0]) - await user.update(penguin_id=penguin_ids[0][0]).apply() - return await buttonInter.send( - content=f"Ваш аккаунт `{p.safe_name()}` успешно отвязан. " - f"Теперь ваш текущий аккаунт `{newCurrentPenguin.safe_name()}`") - - return await buttonInter.send( - content=f"Ваш аккаунт `{p.safe_name()}` успешно отвязан. " - f"Чтобы выбрать текущий аккаунт воспользуйтесь командой ") - - await inter.send(content=f"Вы уверены, что хотите выйти с аккаунта `{p.safe_name()}`?", - view=Logout(inter, run)) + await inter.send(f"Вы уверены, что хотите выйти с аккаунта `{p.safe_name()}`?", + view=Logout(inter, p, user, penguin_ids), ephemeral=True) @slash_command(name="pay", description="Перевести свои монеты другому игроку") async def pay(self, inter: ApplicationCommandInteraction, @@ -90,13 +80,13 @@ async def pay(self, inter: ApplicationCommandInteraction, r: Penguin = await Penguin.get(receiverId) if amount <= 0: - return await inter.send(content='Пожалуйста введите правильное число монет') + return await inter.send('Пожалуйста введите правильное число монет') if p.id == r.id: - return await inter.send(content="Вы не можете передать монеты самому себе!") + return await inter.send("Вы не можете передать монеты самому себе!") if p.coins < amount: - return await inter.send(content='У вас недостаточно монет для перевода') + return await inter.send('У вас недостаточно монет для перевода') await p.update(coins=p.coins - amount).apply() await r.update(coins=r.coins + amount).apply() @@ -107,48 +97,38 @@ async def pay(self, inter: ApplicationCommandInteraction, text=f"Перевёл игроку {r.username} {int(amount)} монет. Через Discord бота", room_id=0, server_id=8000) - await inter.send(content=f"Вы успешно передали `{amount}` монет игроку `{receiver}`!") + await inter.send(f"Вы успешно передали `{amount}` монет игроку `{receiver}`!") @slash_command(name="switch", description="Сменить текущий аккаунт") async def switch(self, inter: ApplicationCommandInteraction): - p: Penguin = await getPenguinOrNoneFromInter(inter) - + p: Penguin = await getPenguinOrNoneFromId(inter.user.id) + user: Users = await Users.get(inter.user.id) penguin_ids = await db_pb.select([PenguinIntegrations.penguin_id]).where( (PenguinIntegrations.discord_id == inter.user.id)).gino.all() if len(penguin_ids) == 0: return await inter.send( - content=f"У вас не привязан ни один аккаунт. " - f"Это можно исправить с помощью команды ") + f"У вас не привязан ни один аккаунт. " + f"Это можно исправить с помощью команды {loginCommand}", ephemeral=True) if len(penguin_ids) == 1: return await inter.send( - ephemeral=True, - content=f"У вас привязан только один аккаунт. " - f"Вы можете привязать ещё несколько с помощью команды ") - - async def run(selectInter, penguin_id): - newCurrentPenguin: Penguin = await Penguin.get(penguin_id) - user: Users = await Users.get(inter.user.id) - - await user.update(penguin_id=penguin_id).apply() + f"У вас привязан только один аккаунт. " + f"Вы можете привязать ещё несколько с помощью команды {loginCommand}", ephemeral=True) - return await selectInter.send( - content=f"Успешно. Теперь ваш текущий аккаунт `{newCurrentPenguin.safe_name()}`") - - view = disnake.ui.View() penguinsList = [{"safe_name": (await Penguin.get(penguin_id[0])).safe_name(), "id": penguin_id[0]} for penguin_id in penguin_ids] - view.add_item(SelectPenguins(penguinsList, run, inter.user.id)) + view = disnake.ui.View() + view.add_item(SelectPenguins(penguinsList, user)) if p is None: return await inter.send( - content=f"Ваш текущий аккаунт не выбран. Какой аккаунт вы хотите сделать текущим?", - view=view) + f"Ваш текущий аккаунт не выбран. Какой аккаунт вы хотите сделать текущим?", + view=view, ephemeral=True) return await inter.send( - content=f"Ваш текущий аккаунт: `{p.safe_name()}`. Какой аккаунт вы хотите сделать текущим?", - view=view) + f"Ваш текущий аккаунт: `{p.safe_name()}`. Какой аккаунт вы хотите сделать текущим?", + view=view, ephemeral=True) @slash_command(name="online", description="Показывает количество игроков которые сейчас онлайн") async def online(self, inter: ApplicationCommandInteraction): @@ -158,7 +138,7 @@ async def online(self, inter: ApplicationCommandInteraction): soup = BeautifulSoup(response.text, "html.parser") online: int = ast.literal_eval(soup.text)[0]['3104'] - await inter.send(content=f"В нашей игре сейчас `{online}` человек/а онлайн") + await inter.send(f"В нашей игре сейчас `{online}` человек/а онлайн") def setup(bot): diff --git a/bot/misc/buttons.py b/bot/misc/buttons.py index 4d2bc70..411d647 100644 --- a/bot/misc/buttons.py +++ b/bot/misc/buttons.py @@ -1,20 +1,20 @@ import disnake -from bot.misc.constants import embedRuleImageRu, embedRuleRu, embedRuleImageEn, embedRuleEn, embedRolesRu, embedRolesEn, \ - enFullRulesLink, ruFullRulesLink +from bot.data.pufflebot.users import PenguinIntegrations +from bot.misc.constants import embedRuleImageRu, embedRuleRu, embedRuleImageEn, embedRuleEn, embedRolesRu, \ + embedRolesEn, enFullRulesLink, ruFullRulesLink, switchCommand +from bot.misc.penguin import Penguin class Buttons(disnake.ui.View): - def __init__(self, inter, function, timeout=900): + def __init__(self, original_inter, timeout=900): super().__init__(timeout=timeout) - self.inter = inter - self.userID = inter.user.id if inter is not None else None - self.function = function + self.original_inter = original_inter async def disableAllItems(self): for item in self.children: item.disabled = True - await self.inter.edit_original_response(view=self) + await self.original_inter.edit_original_response(view=self) async def on_timeout(self): await self.disableAllItems() @@ -26,36 +26,46 @@ def __init__(self, inter, function): @disnake.ui.button(label="Да", style=disnake.ButtonStyle.green, custom_id="yes") async def yesButton(self, _, inter: disnake.CommandInteraction): - if inter.user.id != self.userID: - return await inter.send(content=f"Это кнопка не для вас", ephemeral=True) - await self.disableAllItems() - await self.function(inter) + ... @disnake.ui.button(label="Нет", style=disnake.ButtonStyle.red, custom_id="no") async def noButton(self, _, inter: disnake.CommandInteraction): - if inter.user.id != self.userID: - return await inter.send(content=f"Это кнопка не для вас", ephemeral=True) - await self.disableAllItems() - await inter.send(content=f"Отменено") + ... class Logout(Buttons): - def __init__(self, inter: disnake.CommandInteraction, function): - super().__init__(inter, function) + def __init__(self, inter: disnake.CommandInteraction, p, user, penguin_ids): + super().__init__(inter, timeout=None) + self.p = p + self.user = user + self.penguin_ids = penguin_ids - @disnake.ui.button(label="Отмена", style=disnake.ButtonStyle.gray, custom_id="no") - async def noButton(self, _, inter: disnake.CommandInteraction): - if inter.user.id != self.userID: - return await inter.send(content=f"Это кнопка не для вас", ephemeral=True) + async def on_timeout(self): + ... + + @disnake.ui.button(label="Отмена", style=disnake.ButtonStyle.gray, custom_id="cancel") + async def cancelButton(self, _, inter: disnake.CommandInteraction): await self.disableAllItems() - await inter.send(content=f"Отменено") + await inter.send(f"Отменено", ephemeral=True) - @disnake.ui.button(label="Выйти", style=disnake.ButtonStyle.red, custom_id="yes") - async def yesButton(self, _, inter: disnake.CommandInteraction): - if inter.user.id != self.userID: - return await inter.send(content=f"Это кнопка не для вас", ephemeral=True) + @disnake.ui.button(label="Выйти", style=disnake.ButtonStyle.red, custom_id="logout") + async def logoutButton(self, _, inter: disnake.CommandInteraction): await self.disableAllItems() - await self.function(inter) + await PenguinIntegrations.delete.where(PenguinIntegrations.penguin_id == self.p.id).gino.status() + if len(self.penguin_ids) == 1: + await self.user.update(penguin_id=None).apply() + return await inter.send(f"Ваш аккаунт `{self.p.safe_name()}` успешно отвязан.", ephemeral=True) + + if len(self.penguin_ids) == 2: + newCurrentPenguin: Penguin = await Penguin.get(self.penguin_ids[0][0]) + await self.user.update(penguin_id=self.penguin_ids[0][0]).apply() + return await inter.send( + f"Ваш аккаунт `{self.p.safe_name()}` успешно отвязан. " + f"Сейчас ваш текущий аккаунт `{newCurrentPenguin.safe_name()}`", ephemeral=True) + + return await inter.send( + f"Ваш аккаунт `{self.p.safe_name()}` успешно отвязан. " + f"Чтобы выбрать текущий аккаунт воспользуйтесь командой {switchCommand}", ephemeral=True) class Continue(Buttons): @@ -64,9 +74,7 @@ def __init__(self, inter: disnake.CommandInteraction, function): @disnake.ui.button(label="Продолжить", style=disnake.ButtonStyle.blurple, custom_id="continue") async def continueButton(self, _, inter: disnake.CommandInteraction): - if inter.user.id != self.userID: - return await inter.send(content=f"Это кнопка не для вас", ephemeral=True) - await self.function(inter) + ... class Login(disnake.ui.View): diff --git a/bot/misc/constants.py b/bot/misc/constants.py index 4f04f74..b3a05fc 100644 --- a/bot/misc/constants.py +++ b/bot/misc/constants.py @@ -3,7 +3,7 @@ from requests import get from disnake import Embed -# ids +# Ids cppsapp_server_id = 755445822920982548 test_server_id = 830884170934124585 rules_webhook_id = 1126642813543657525 @@ -27,6 +27,10 @@ # Bytearrays avatarImageBytearray = BytesIO(get(avatarImageLink).content).getvalue() +# Bot commands +loginCommand = "" +switchCommand = "" + # Custom emojis emojiLaughing = "<:laughing:788392697336954920>" emojiDay = "<:day:788395886450966588>" diff --git a/bot/misc/select.py b/bot/misc/select.py index 908655d..0914dc6 100644 --- a/bot/misc/select.py +++ b/bot/misc/select.py @@ -3,30 +3,26 @@ from bot.misc.buttons import RulesEphemeral, Roles from bot.misc.constants import embedLinks, embedRuleImageRu, embedRuleRu, embedRolesRu +from bot.misc.penguin import Penguin class SelectPenguins(Select): - def __init__(self, penguinsList, function, userID): + def __init__(self, penguinsList, user): self.disabled = False - self.function = function - self.userID = userID + self.user = user options = [] for penguin in penguinsList: options.append(SelectOption(label=penguin["safe_name"], value=penguin["id"])) super().__init__(placeholder="Выберите пингвина", options=options, custom_id="penguins") - async def callback(self, interaction: MessageInteraction): - if interaction.user.id != self.userID: - await interaction.send(content=f"Ай-ай-ай,не суй пальцы в розетку", ephemeral=True) - return - if self.disabled: - await interaction.send(content=f"Вы уже сменили аккаунт", ephemeral=True) - return - self.disabled = True - - penguin_id = int(interaction.values[0]) - await self.function(interaction, penguin_id) + async def callback(self, inter: MessageInteraction): + penguin_id = int(inter.values[0]) + newCurrentPenguin = await Penguin.get(penguin_id) + + await self.user.update(penguin_id=penguin_id).apply() + + await inter.send(f"Успешно. Теперь ваш текущий аккаунт `{newCurrentPenguin.safe_name()}`", ephemeral=True) class About(Select): diff --git a/bot/misc/utils.py b/bot/misc/utils.py index 0f14bc5..d1be614 100644 --- a/bot/misc/utils.py +++ b/bot/misc/utils.py @@ -1,12 +1,15 @@ +from disnake import ApplicationCommandInteraction + from bot.data.pufflebot.users import Users +from bot.misc.constants import loginCommand from bot.misc.penguin import Penguin -async def getPenguinFromInter(inter): +async def getPenguinFromInter(inter: ApplicationCommandInteraction): user = await Users.get(inter.user.id) if user is None: await inter.send( - content=f"Мы не нашли вашего пингвина. Пожалуйста воспользуйтесь командой ", + f"Мы не нашли вашего пингвина. Пожалуйста воспользуйтесь командой {loginCommand}", ephemeral=True) return p = await Penguin.get(user.penguin_id) @@ -14,8 +17,8 @@ async def getPenguinFromInter(inter): return p -async def getPenguinOrNoneFromInter(inter): - user = await Users.get(inter.user.id) +async def getPenguinOrNoneFromId(user_id: int): + user = await Users.get(user_id) if user is None: return None p = await Penguin.get(user.penguin_id) From 5c4f237d80034482d8f79f79cd0bd4a040b12187 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Sat, 8 Jul 2023 16:04:33 +0500 Subject: [PATCH 07/27] Minor changes in command `/online` --- bot/cogs/commands.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index 0b54188..6d646f3 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -10,7 +10,7 @@ from bot.data import db_pb from bot.data.clubpenguin.moderator import Logs -from bot.misc.constants import online_url, headers, loginCommand +from bot.misc.constants import online_url, headers, loginCommand, emojiCuteSad from bot.misc.penguin import Penguin from bot.data.pufflebot.users import Users, PenguinIntegrations from bot.misc.buttons import Logout, Login @@ -138,7 +138,11 @@ async def online(self, inter: ApplicationCommandInteraction): soup = BeautifulSoup(response.text, "html.parser") online: int = ast.literal_eval(soup.text)[0]['3104'] - await inter.send(f"В нашей игре сейчас `{online}` человек/а онлайн") + if online == 0: + textMessage = f"В нашей игре сейчас никого нет {emojiCuteSad}" + else: + textMessage = f"В нашей игре сейчас `{online}` человек/а онлайн" + await inter.send(textMessage) def setup(bot): From 06cb3e9623a7d08c21e9adc44c0cb69b174e9775 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Sat, 8 Jul 2023 19:17:40 +0500 Subject: [PATCH 08/27] Minor changes in command `/online` --- bot/cogs/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index 6d646f3..6c7f3dc 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -137,7 +137,7 @@ async def online(self, inter: ApplicationCommandInteraction): response = s.get(online_url) soup = BeautifulSoup(response.text, "html.parser") - online: int = ast.literal_eval(soup.text)[0]['3104'] + online = int(ast.literal_eval(soup.text)[0]['3104']) if online == 0: textMessage = f"В нашей игре сейчас никого нет {emojiCuteSad}" else: From 9ca400c7382497cb7177673b7f308d727c1adf70 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Sat, 8 Jul 2023 19:18:25 +0500 Subject: [PATCH 09/27] Minor changes in command `/online` --- bot/misc/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/misc/constants.py b/bot/misc/constants.py index b3a05fc..94ee3df 100644 --- a/bot/misc/constants.py +++ b/bot/misc/constants.py @@ -40,6 +40,7 @@ emojiYt = "<:yt:1121764618239496263>" emojiGameFavicon = "<:favicon:1121758719634571405>" emojiWikiFavicon = "<:wiki:1121764147307237447>" +emojiCuteSad = "<:cuteSad:794874872835735553>" # ========== EMBEDS ========== From 5d4ce1466d66c86ecce8c73ab4257f4628263f7c Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Sat, 8 Jul 2023 21:27:00 +0500 Subject: [PATCH 10/27] Minor changes in command `/pay`. Add docsting in utils.py --- bot/cogs/commands.py | 22 ++------------- bot/misc/utils.py | 67 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 20 deletions(-) diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index 6c7f3dc..0cf502f 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -15,7 +15,7 @@ from bot.data.pufflebot.users import Users, PenguinIntegrations from bot.misc.buttons import Logout, Login from bot.misc.select import SelectPenguins -from bot.misc.utils import getPenguinFromInter, getPenguinOrNoneFromId +from bot.misc.utils import getPenguinFromInter, getPenguinOrNoneFromId, transferCoinsAndReturnStatus class UserCommands(Cog): @@ -79,25 +79,7 @@ async def pay(self, inter: ApplicationCommandInteraction, receiverId = int(receiverId[0]) r: Penguin = await Penguin.get(receiverId) - if amount <= 0: - return await inter.send('Пожалуйста введите правильное число монет') - - if p.id == r.id: - return await inter.send("Вы не можете передать монеты самому себе!") - - if p.coins < amount: - return await inter.send('У вас недостаточно монет для перевода') - - await p.update(coins=p.coins - amount).apply() - await r.update(coins=r.coins + amount).apply() - await Logs.create(penguin_id=int(r.id), type=4, - text=f"Получил от {p.username} {int(amount)} монет. Через Discord бота", room_id=0, - server_id=8000) - await Logs.create(penguin_id=int(p.id), type=4, - text=f"Перевёл игроку {r.username} {int(amount)} монет. Через Discord бота", room_id=0, - server_id=8000) - - await inter.send(f"Вы успешно передали `{amount}` монет игроку `{receiver}`!") + await inter.send((await transferCoinsAndReturnStatus(p, r, amount))) @slash_command(name="switch", description="Сменить текущий аккаунт") async def switch(self, inter: ApplicationCommandInteraction): diff --git a/bot/misc/utils.py b/bot/misc/utils.py index d1be614..fa745f2 100644 --- a/bot/misc/utils.py +++ b/bot/misc/utils.py @@ -1,11 +1,26 @@ from disnake import ApplicationCommandInteraction +from bot.data.clubpenguin.moderator import Logs from bot.data.pufflebot.users import Users from bot.misc.constants import loginCommand from bot.misc.penguin import Penguin async def getPenguinFromInter(inter: ApplicationCommandInteraction): + """ + Retrieves a penguin object from the database based on the discord user ID. + **If the penguin is not found, the function sends a response to the interaction** + + Parameters + ---------- + inter: ApplicationCommandInteraction + The interaction object representing the user's command. + + Returns + ---------- + Penguin + The penguin object retrieved from the database. + """ user = await Users.get(inter.user.id) if user is None: await inter.send( @@ -18,9 +33,61 @@ async def getPenguinFromInter(inter: ApplicationCommandInteraction): async def getPenguinOrNoneFromId(user_id: int): + """ + Get a penguin object from a user ID. + + Parameters + ---------- + user_id: int + The ID of the discord user to get the penguin object for. + + Returns + ------- + Optional[Penguin] + The penguin object, or `None` if the user is not found. + """ user = await Users.get(user_id) if user is None: return None p = await Penguin.get(user.penguin_id) await p.setup() return p + + +async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amount: int) -> str: + """ + Transfer coins between two penguins and return a status message. + + Parameters + ---------- + sender: Penguin + The penguin sending the coins. + receiver: Penguin + The penguin receiving the coins. + amount: int + The number of coins to transfer. + + Returns + ---------- + str + A status message indicating whether the transfer was successful or not. + """ + if amount <= 0: + return "Пожалуйста введите правильное число монет" + + if sender == receiver: + return "Вы не можете передать монеты самому себе!" + + if sender.coins < amount: + return "У вас недостаточно монет для перевода" + + await sender.update(coins=sender.coins - amount).apply() + await receiver.update(coins=receiver.coins + amount).apply() + await Logs.create(penguin_id=int(receiver.id), type=4, + text=f"Получил от {sender.username} {int(amount)} монет. Через Discord бота", room_id=0, + server_id=8000) + await Logs.create(penguin_id=int(sender.id), type=4, + text=f"Перевёл игроку {receiver.username} {int(amount)} монет. Через Discord бота", room_id=0, + server_id=8000) + + return f"Вы успешно передали `{amount}` монет игроку `{receiver.safe_name()}`!" From 5b8ac80d7db7004af6d2ce4c7a14432ea3527add Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Mon, 10 Jul 2023 20:00:35 +0500 Subject: [PATCH 11/27] Minor changes and refactoring --- bot/cogs/commands.py | 9 ++++++--- bot/cogs/private.py | 11 ++++++++--- bot/data/pufflebot/users.py | 8 ++++---- bot/misc/utils.py | 23 ++++++++++++----------- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index 0cf502f..9724d18 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -9,7 +9,6 @@ from requests import Session from bot.data import db_pb -from bot.data.clubpenguin.moderator import Logs from bot.misc.constants import online_url, headers, loginCommand, emojiCuteSad from bot.misc.penguin import Penguin from bot.data.pufflebot.users import Users, PenguinIntegrations @@ -22,7 +21,7 @@ class UserCommands(Cog): def __init__(self, bot): self.bot = bot - logger.info(f"Loads {len(self.get_slash_commands())} public users commands") + logger.info(f"Loads {len(self.get_application_commands())} public users app commands") @slash_command(name="ilyash", description=":D") async def ilyash(self, inter: ApplicationCommandInteraction): @@ -79,7 +78,11 @@ async def pay(self, inter: ApplicationCommandInteraction, receiverId = int(receiverId[0]) r: Penguin = await Penguin.get(receiverId) - await inter.send((await transferCoinsAndReturnStatus(p, r, amount))) + statusDict = await transferCoinsAndReturnStatus(p, r, amount) + if statusDict["code"] == 400: + await inter.send(statusDict["message"], ephemeral=True) + + await inter.send(statusDict["message"]) @slash_command(name="switch", description="Сменить текущий аккаунт") async def switch(self, inter: ApplicationCommandInteraction): diff --git a/bot/cogs/private.py b/bot/cogs/private.py index 39a101c..9e4238e 100644 --- a/bot/cogs/private.py +++ b/bot/cogs/private.py @@ -4,8 +4,13 @@ from loguru import logger from bot.misc.buttons import Rules -from bot.misc.constants import embedRuleImageRu, embedRuleRu, embedAboutImage, embedAbout, guild_ids, \ - avatarImageBytearray +from bot.misc.constants import ( + embedRuleImageRu, + embedRuleRu, + embedAboutImage, + embedAbout, + guild_ids, + avatarImageBytearray) from bot.misc.select import About @@ -13,7 +18,7 @@ class PrivateCommands(Cog): def __init__(self, bot): self.bot = bot - logger.info(f"Loads {len(self.get_slash_commands())} private commands") + logger.info(f"Loads {len(self.get_application_commands())} private app commands") @slash_command(name="transfer", description="Transfer images from the current channel to the forum", guild_ids=guild_ids) diff --git a/bot/data/pufflebot/users.py b/bot/data/pufflebot/users.py index 24ecc76..2ed4fea 100644 --- a/bot/data/pufflebot/users.py +++ b/bot/data/pufflebot/users.py @@ -4,14 +4,14 @@ class Users(db_pb.Model): __tablename__ = 'users' - id = db_pb.Column(db_pb.BigInteger, primary_key=True) + id = db_pb.Column(db_pb.BigInteger, primary_key=True, nullable=False) penguin_id = db_pb.Column(db_pb.BigInteger, nullable=False) - language = db_pb.Column(db_pb.String(2), nullable=False, unique=True, server_default=db_pb.text("ru")) + language = db_pb.Column(db_pb.String(2), nullable=False, server_default=db_pb.text("ru")) class PenguinIntegrations(db_pb.Model): __tablename__ = 'penguin_integrations' - discord_id = db_pb.Column(db_pb.BigInteger, primary_key=True) - penguin_id = db_pb.Column(db_pb.BigInteger, primary_key=True, + discord_id = db_pb.Column(db_pb.BigInteger, primary_key=True, nullable=False) + penguin_id = db_pb.Column(db_pb.BigInteger, primary_key=True, nullable=False, server_default=db_pb.text("nextval('penguin_integrations_discord_id_seq'::regclass)")) diff --git a/bot/misc/utils.py b/bot/misc/utils.py index fa745f2..21d965e 100644 --- a/bot/misc/utils.py +++ b/bot/misc/utils.py @@ -54,32 +54,33 @@ async def getPenguinOrNoneFromId(user_id: int): return p -async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amount: int) -> str: +async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amount: int) -> dict: """ - Transfer coins between two penguins and return a status message. + Transfer coins between two penguins and return a status dictionary. Parameters ---------- sender: Penguin - The penguin sending the coins. + The penguin object representing the sender of the coins. receiver: Penguin - The penguin receiving the coins. + The penguin object representing the receiver of the coins. amount: int The number of coins to transfer. Returns ---------- - str - A status message indicating whether the transfer was successful or not. + dict + A dictionary containing the status code and message. """ + # TODO: Make a notification system if amount <= 0: - return "Пожалуйста введите правильное число монет" + return {"code": 400, "message": "Пожалуйста введите правильное число монет"} - if sender == receiver: - return "Вы не можете передать монеты самому себе!" + if sender.id == receiver.id: + return {"code": 400, "message": "Вы не можете передать монеты самому себе!"} if sender.coins < amount: - return "У вас недостаточно монет для перевода" + return {"code": 400, "message": "У вас недостаточно монет для перевода"} await sender.update(coins=sender.coins - amount).apply() await receiver.update(coins=receiver.coins + amount).apply() @@ -90,4 +91,4 @@ async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amoun text=f"Перевёл игроку {receiver.username} {int(amount)} монет. Через Discord бота", room_id=0, server_id=8000) - return f"Вы успешно передали `{amount}` монет игроку `{receiver.safe_name()}`!" + return {"code": 200, "message": f"Вы успешно передали `{amount}` монет игроку `{receiver.safe_name()}`!"} From 6385091e43a6550b412e1fbf8f3da20b9d60dcef Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Mon, 10 Jul 2023 20:00:35 +0500 Subject: [PATCH 12/27] Minor changes --- bot/cogs/commands.py | 9 ++++++--- bot/cogs/private.py | 11 ++++++++--- bot/data/pufflebot/users.py | 8 ++++---- bot/misc/utils.py | 23 ++++++++++++----------- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index 0cf502f..1ca09d3 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -9,7 +9,6 @@ from requests import Session from bot.data import db_pb -from bot.data.clubpenguin.moderator import Logs from bot.misc.constants import online_url, headers, loginCommand, emojiCuteSad from bot.misc.penguin import Penguin from bot.data.pufflebot.users import Users, PenguinIntegrations @@ -22,7 +21,7 @@ class UserCommands(Cog): def __init__(self, bot): self.bot = bot - logger.info(f"Loads {len(self.get_slash_commands())} public users commands") + logger.info(f"Loaded {len(self.get_application_commands())} public users app commands") @slash_command(name="ilyash", description=":D") async def ilyash(self, inter: ApplicationCommandInteraction): @@ -79,7 +78,11 @@ async def pay(self, inter: ApplicationCommandInteraction, receiverId = int(receiverId[0]) r: Penguin = await Penguin.get(receiverId) - await inter.send((await transferCoinsAndReturnStatus(p, r, amount))) + statusDict = await transferCoinsAndReturnStatus(p, r, amount) + if statusDict["code"] == 400: + await inter.send(statusDict["message"], ephemeral=True) + + await inter.send(statusDict["message"]) @slash_command(name="switch", description="Сменить текущий аккаунт") async def switch(self, inter: ApplicationCommandInteraction): diff --git a/bot/cogs/private.py b/bot/cogs/private.py index 39a101c..801e2a3 100644 --- a/bot/cogs/private.py +++ b/bot/cogs/private.py @@ -4,8 +4,13 @@ from loguru import logger from bot.misc.buttons import Rules -from bot.misc.constants import embedRuleImageRu, embedRuleRu, embedAboutImage, embedAbout, guild_ids, \ - avatarImageBytearray +from bot.misc.constants import ( + embedRuleImageRu, + embedRuleRu, + embedAboutImage, + embedAbout, + guild_ids, + avatarImageBytearray) from bot.misc.select import About @@ -13,7 +18,7 @@ class PrivateCommands(Cog): def __init__(self, bot): self.bot = bot - logger.info(f"Loads {len(self.get_slash_commands())} private commands") + logger.info(f"Loaded {len(self.get_application_commands())} private app commands") @slash_command(name="transfer", description="Transfer images from the current channel to the forum", guild_ids=guild_ids) diff --git a/bot/data/pufflebot/users.py b/bot/data/pufflebot/users.py index 24ecc76..2ed4fea 100644 --- a/bot/data/pufflebot/users.py +++ b/bot/data/pufflebot/users.py @@ -4,14 +4,14 @@ class Users(db_pb.Model): __tablename__ = 'users' - id = db_pb.Column(db_pb.BigInteger, primary_key=True) + id = db_pb.Column(db_pb.BigInteger, primary_key=True, nullable=False) penguin_id = db_pb.Column(db_pb.BigInteger, nullable=False) - language = db_pb.Column(db_pb.String(2), nullable=False, unique=True, server_default=db_pb.text("ru")) + language = db_pb.Column(db_pb.String(2), nullable=False, server_default=db_pb.text("ru")) class PenguinIntegrations(db_pb.Model): __tablename__ = 'penguin_integrations' - discord_id = db_pb.Column(db_pb.BigInteger, primary_key=True) - penguin_id = db_pb.Column(db_pb.BigInteger, primary_key=True, + discord_id = db_pb.Column(db_pb.BigInteger, primary_key=True, nullable=False) + penguin_id = db_pb.Column(db_pb.BigInteger, primary_key=True, nullable=False, server_default=db_pb.text("nextval('penguin_integrations_discord_id_seq'::regclass)")) diff --git a/bot/misc/utils.py b/bot/misc/utils.py index fa745f2..21d965e 100644 --- a/bot/misc/utils.py +++ b/bot/misc/utils.py @@ -54,32 +54,33 @@ async def getPenguinOrNoneFromId(user_id: int): return p -async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amount: int) -> str: +async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amount: int) -> dict: """ - Transfer coins between two penguins and return a status message. + Transfer coins between two penguins and return a status dictionary. Parameters ---------- sender: Penguin - The penguin sending the coins. + The penguin object representing the sender of the coins. receiver: Penguin - The penguin receiving the coins. + The penguin object representing the receiver of the coins. amount: int The number of coins to transfer. Returns ---------- - str - A status message indicating whether the transfer was successful or not. + dict + A dictionary containing the status code and message. """ + # TODO: Make a notification system if amount <= 0: - return "Пожалуйста введите правильное число монет" + return {"code": 400, "message": "Пожалуйста введите правильное число монет"} - if sender == receiver: - return "Вы не можете передать монеты самому себе!" + if sender.id == receiver.id: + return {"code": 400, "message": "Вы не можете передать монеты самому себе!"} if sender.coins < amount: - return "У вас недостаточно монет для перевода" + return {"code": 400, "message": "У вас недостаточно монет для перевода"} await sender.update(coins=sender.coins - amount).apply() await receiver.update(coins=receiver.coins + amount).apply() @@ -90,4 +91,4 @@ async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amoun text=f"Перевёл игроку {receiver.username} {int(amount)} монет. Через Discord бота", room_id=0, server_id=8000) - return f"Вы успешно передали `{amount}` монет игроку `{receiver.safe_name()}`!" + return {"code": 200, "message": f"Вы успешно передали `{amount}` монет игроку `{receiver.safe_name()}`!"} From 4b23db014e5cffa6b92b03d8fe9a892db502fc89 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Fri, 14 Jul 2023 03:15:31 +0500 Subject: [PATCH 13/27] Big refactoring. Add handler and plugins system --- bot/__init__.py | 165 ++++++++++ bot/cogs/account.py | 68 +++++ bot/cogs/commands.py | 59 +--- bot/cogs/private.py | 4 +- bot/core/puffleBot.py | 14 +- bot/core/server.py | 6 + bot/handlers/__init__.py | 282 ++++++++++++++++++ bot/{misc => handlers}/buttons.py | 51 ++++ bot/handlers/censure.py | 53 ++++ bot/{misc => handlers}/modals.py | 0 bot/{misc => handlers}/select.py | 2 +- bot/misc/converters.py | 481 ++++++++++++++++++++++++++++++ bot/misc/cooldown.py | 87 ++++++ bot/misc/utils.py | 2 +- bot/plugins/__init__.py | 85 ++++++ 15 files changed, 1291 insertions(+), 68 deletions(-) create mode 100644 bot/__init__.py create mode 100644 bot/cogs/account.py create mode 100644 bot/handlers/__init__.py rename bot/{misc => handlers}/buttons.py (73%) create mode 100644 bot/handlers/censure.py rename bot/{misc => handlers}/modals.py (100%) rename bot/{misc => handlers}/select.py (97%) create mode 100644 bot/misc/converters.py create mode 100644 bot/misc/cooldown.py create mode 100644 bot/plugins/__init__.py diff --git a/bot/__init__.py b/bot/__init__.py new file mode 100644 index 0000000..a68666e --- /dev/null +++ b/bot/__init__.py @@ -0,0 +1,165 @@ +import asyncio +import importlib +import logging +import pkgutil +from abc import ABC, abstractmethod +from collections import OrderedDict +from types import FunctionType + + +def get_package_modules(package): + package_modules = [] + for importer, module_name, is_package in pkgutil.iter_modules(package.__path__): + full_module_name = f'{package.__name__}.{module_name}' + subpackage_object = importlib.import_module(full_module_name, package=package.__path__) + if is_package: + sub_package_modules = get_package_modules(subpackage_object) + + package_modules = package_modules + sub_package_modules + package_modules.append(subpackage_object) + return package_modules + + +class _AbstractManager(dict): + def __init__(self, server): + self.server = server + self.logger = logging.getLogger('houdini') + + super().__init__() + + @abstractmethod + async def setup(self, module): + """Setup manager class""" + + @abstractmethod + async def load(self, module): + """Loads entries from module""" + + +class ITable(ABC): + """ + All table game logic classes must implement this interface. + """ + + @abstractmethod + def make_move(self, *args): + """Tells logic a move has been made.""" + + @abstractmethod + def is_valid_move(self, *args): + """Returns true if the move is valid.""" + + @abstractmethod + def get_string(self): + """Returns string representation of the game.""" + + +class IWaddle(ABC): + """ + All waddle game logic classes must implement this interface. + """ + + @property + @abstractmethod + def room_id(self): + """External ID of waddle game room.""" + + def __init__(self, waddle): + self.penguins = list(waddle.penguins) + self.seats = waddle.seats + + async def start(self): + room_id = type(self).room_id + for penguin in self.penguins: + penguin.waddle = self + await penguin.join_room(penguin.server.rooms[room_id]) + + async def remove_penguin(self, p): + self.penguins[self.penguins.index(p)] = None + p.waddle = None + + async def send_xt(self, *data, f=None): + for penguin in filter(f, self.penguins): + if penguin is not None: + await penguin.send_xt(*data) + + def get_seat_id(self, p): + return self.penguins.index(p) + + +class PenguinStringCompiler(OrderedDict): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def __setitem__(self, key, compiler_method): + assert type(compiler_method) == FunctionType + super().__setitem__(key, compiler_method) + + async def compile(self, p): + compiler_method_results = [] + + for compiler_method in self.values(): + if asyncio.iscoroutinefunction(compiler_method): + compiler_method_result = await compiler_method(p) + else: + compiler_method_result = compiler_method(p) + compiler_method_results.append(str(compiler_method_result)) + + compiler_result = '|'.join(compiler_method_results) + return compiler_result + + @classmethod + def attribute_by_name(cls, attribute_name): + async def attribute_method(p): + return getattr(p, attribute_name) or 0 + return attribute_method + + @classmethod + def custom_attribute_by_name(cls, attribute_name): + async def attribute_method(p): + return p.get_custom_attribute(attribute_name, '') + return attribute_method + + @classmethod + def setup_default_builder(cls, string_builder): + string_builder.update({ + 'ID': PenguinStringCompiler.attribute_by_name('id'), + 'Nickname': PenguinStringCompiler.attribute_by_name('nickname'), + 'Approval': PenguinStringCompiler.attribute_by_name('approval'), + 'Color': PenguinStringCompiler.attribute_by_name('color'), + 'Head': PenguinStringCompiler.attribute_by_name('head'), + 'Face': PenguinStringCompiler.attribute_by_name('face'), + 'Neck': PenguinStringCompiler.attribute_by_name('neck'), + 'Body': PenguinStringCompiler.attribute_by_name('body'), + 'Hand': PenguinStringCompiler.attribute_by_name('hand'), + 'Feet': PenguinStringCompiler.attribute_by_name('feet'), + 'Flag': PenguinStringCompiler.attribute_by_name('flag'), + 'Photo': PenguinStringCompiler.attribute_by_name('photo'), + 'X': PenguinStringCompiler.attribute_by_name('x'), + 'Y': PenguinStringCompiler.attribute_by_name('y'), + 'Frame': PenguinStringCompiler.attribute_by_name('frame'), + 'Member': PenguinStringCompiler.attribute_by_name('member'), + 'MemberDays': PenguinStringCompiler.attribute_by_name('membership_days_total'), + 'Avatar': PenguinStringCompiler.attribute_by_name('avatar'), + 'PenguinState': PenguinStringCompiler.attribute_by_name('penguin_state'), + 'PartyState': PenguinStringCompiler.attribute_by_name('party_state'), + 'PuffleState': PenguinStringCompiler.attribute_by_name('puffle_state') + }) + + @classmethod + def setup_anonymous_default_builder(cls, string_builder): + string_builder.update({ + 'ID': PenguinStringCompiler.attribute_by_name('id'), + 'Nickname': PenguinStringCompiler.attribute_by_name('nickname'), + 'Approval': PenguinStringCompiler.attribute_by_name('approval'), + 'Color': PenguinStringCompiler.attribute_by_name('color'), + 'Head': PenguinStringCompiler.attribute_by_name('head'), + 'Face': PenguinStringCompiler.attribute_by_name('face'), + 'Neck': PenguinStringCompiler.attribute_by_name('neck'), + 'Body': PenguinStringCompiler.attribute_by_name('body'), + 'Hand': PenguinStringCompiler.attribute_by_name('hand'), + 'Feet': PenguinStringCompiler.attribute_by_name('feet'), + 'Flag': PenguinStringCompiler.attribute_by_name('flag'), + 'Photo': PenguinStringCompiler.attribute_by_name('photo') + }) diff --git a/bot/cogs/account.py b/bot/cogs/account.py new file mode 100644 index 0000000..99a98b7 --- /dev/null +++ b/bot/cogs/account.py @@ -0,0 +1,68 @@ +from disnake import ApplicationCommandInteraction +from loguru import logger +import disnake +from disnake.ext.commands import Cog, slash_command + +from bot.data import db_pb +from bot.misc.constants import loginCommand +from bot.misc.penguin import Penguin +from bot.data.pufflebot.users import Users, PenguinIntegrations +from bot.handlers.buttons import Logout, Login +from bot.handlers.select import SelectPenguins +from bot.misc.utils import getPenguinFromInter, getPenguinOrNoneFromUserId + + +class AccountManagementCommands(Cog): + def __init__(self, bot): + self.bot = bot + + logger.info(f"Loaded {len(self.get_application_commands())} account management app commands") + + @slash_command(name="login", description="Привязать свой Discord аккаунт к пингвину") + async def login(self, inter: ApplicationCommandInteraction): + return await inter.send(f"Перейдите на сайт и пройдите авторизацию", view=Login()) + + @slash_command(name="logout", description="Отвязать свой Discord аккаунт от своего пингвина") + async def logout(self, inter: ApplicationCommandInteraction): + p: Penguin = await getPenguinFromInter(inter) + user: Users = await Users.get(inter.user.id) + penguin_ids = await db_pb.select([PenguinIntegrations.penguin_id]).where( + (PenguinIntegrations.discord_id == inter.user.id)).gino.all() + + await inter.send(f"Вы уверены, что хотите выйти с аккаунта `{p.safe_name()}`?", + view=Logout(inter, p, user, penguin_ids), ephemeral=True) + + @slash_command(name="switch", description="Сменить текущий аккаунт") + async def switch(self, inter: ApplicationCommandInteraction): + p: Penguin = await getPenguinOrNoneFromUserId(inter.user.id) + user: Users = await Users.get(inter.user.id) + penguin_ids = await db_pb.select([PenguinIntegrations.penguin_id]).where( + (PenguinIntegrations.discord_id == inter.user.id)).gino.all() + + if len(penguin_ids) == 0: + return await inter.send( + f"У вас не привязан ни один аккаунт. " + f"Это можно исправить с помощью команды {loginCommand}", ephemeral=True) + + if len(penguin_ids) == 1: + return await inter.send( + f"У вас привязан только один аккаунт. " + f"Вы можете привязать ещё несколько с помощью команды {loginCommand}", ephemeral=True) + + penguinsList = [{"safe_name": (await Penguin.get(penguin_id[0])).safe_name(), "id": penguin_id[0]} for + penguin_id in penguin_ids] + view = disnake.ui.View() + view.add_item(SelectPenguins(penguinsList, user)) + + if p is None: + return await inter.send( + f"Ваш текущий аккаунт не выбран. Какой аккаунт вы хотите сделать текущим?", + view=view, ephemeral=True) + + return await inter.send( + f"Ваш текущий аккаунт: `{p.safe_name()}`. Какой аккаунт вы хотите сделать текущим?", + view=view, ephemeral=True) + + +def setup(bot): + bot.add_cog(AccountManagementCommands(bot)) diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index 1ca09d3..5a42971 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -8,13 +8,9 @@ from disnake.ext.commands import Cog, Param, slash_command from requests import Session -from bot.data import db_pb -from bot.misc.constants import online_url, headers, loginCommand, emojiCuteSad +from bot.misc.constants import online_url, headers, emojiCuteSad from bot.misc.penguin import Penguin -from bot.data.pufflebot.users import Users, PenguinIntegrations -from bot.misc.buttons import Logout, Login -from bot.misc.select import SelectPenguins -from bot.misc.utils import getPenguinFromInter, getPenguinOrNoneFromId, transferCoinsAndReturnStatus +from bot.misc.utils import getPenguinFromInter, getPenguinOrNoneFromUserId, transferCoinsAndReturnStatus class UserCommands(Cog): @@ -31,14 +27,14 @@ async def ilyash(self, inter: ApplicationCommandInteraction): async def card(self, inter: ApplicationCommandInteraction, user: disnake.User = Param(default=None, description='Пользователь, чью карточку нужно показать')): if user: - p = await getPenguinOrNoneFromId(user.id) + p = await getPenguinOrNoneFromUserId(user.id) if p is None: - return await inter.send(f"Мы не нашли пингвина у указанного пользователя.", ephemeral=True) + return await inter.send(f"Мы не нашли пингвина у указанного вами+ пользователя.", ephemeral=True) else: p: Penguin = await getPenguinFromInter(inter) if p.get_custom_attribute("mood") and p.get_custom_attribute("mood") != " ": - mood = f'*{p.get_custom_attribute("mood")}*' + mood = f'*{p.get_custom_attribute("mood")}*'.replace("\n", " ") else: mood = None @@ -54,20 +50,6 @@ async def card(self, inter: ApplicationCommandInteraction, embed.add_field(name="Сотрудник", value="Да" if p.moderator else "Нет") await inter.send(embed=embed) - @slash_command(name="login", description="Привязать свой Discord аккаунт к пингвину") - async def login(self, inter: ApplicationCommandInteraction): - return await inter.send(f"Перейдите на сайт и пройдите авторизацию", view=Login()) - - @slash_command(name="logout", description="Отвязать свой Discord аккаунт от своего пингвина") - async def logout(self, inter: ApplicationCommandInteraction): - p: Penguin = await getPenguinFromInter(inter) - user: Users = await Users.get(inter.user.id) - penguin_ids = await db_pb.select([PenguinIntegrations.penguin_id]).where( - (PenguinIntegrations.discord_id == inter.user.id)).gino.all() - - await inter.send(f"Вы уверены, что хотите выйти с аккаунта `{p.safe_name()}`?", - view=Logout(inter, p, user, penguin_ids), ephemeral=True) - @slash_command(name="pay", description="Перевести свои монеты другому игроку") async def pay(self, inter: ApplicationCommandInteraction, receiver: str = Param(description='Получатель (его ник в игре)'), @@ -84,37 +66,6 @@ async def pay(self, inter: ApplicationCommandInteraction, await inter.send(statusDict["message"]) - @slash_command(name="switch", description="Сменить текущий аккаунт") - async def switch(self, inter: ApplicationCommandInteraction): - p: Penguin = await getPenguinOrNoneFromId(inter.user.id) - user: Users = await Users.get(inter.user.id) - penguin_ids = await db_pb.select([PenguinIntegrations.penguin_id]).where( - (PenguinIntegrations.discord_id == inter.user.id)).gino.all() - - if len(penguin_ids) == 0: - return await inter.send( - f"У вас не привязан ни один аккаунт. " - f"Это можно исправить с помощью команды {loginCommand}", ephemeral=True) - - if len(penguin_ids) == 1: - return await inter.send( - f"У вас привязан только один аккаунт. " - f"Вы можете привязать ещё несколько с помощью команды {loginCommand}", ephemeral=True) - - penguinsList = [{"safe_name": (await Penguin.get(penguin_id[0])).safe_name(), "id": penguin_id[0]} for - penguin_id in penguin_ids] - view = disnake.ui.View() - view.add_item(SelectPenguins(penguinsList, user)) - - if p is None: - return await inter.send( - f"Ваш текущий аккаунт не выбран. Какой аккаунт вы хотите сделать текущим?", - view=view, ephemeral=True) - - return await inter.send( - f"Ваш текущий аккаунт: `{p.safe_name()}`. Какой аккаунт вы хотите сделать текущим?", - view=view, ephemeral=True) - @slash_command(name="online", description="Показывает количество игроков которые сейчас онлайн") async def online(self, inter: ApplicationCommandInteraction): with Session() as s: diff --git a/bot/cogs/private.py b/bot/cogs/private.py index 801e2a3..bfe6614 100644 --- a/bot/cogs/private.py +++ b/bot/cogs/private.py @@ -3,7 +3,7 @@ from disnake.ext.commands import Cog, slash_command from loguru import logger -from bot.misc.buttons import Rules +from bot.handlers.buttons import Rules from bot.misc.constants import ( embedRuleImageRu, embedRuleRu, @@ -11,7 +11,7 @@ embedAbout, guild_ids, avatarImageBytearray) -from bot.misc.select import About +from bot.handlers.select import About class PrivateCommands(Cog): diff --git a/bot/core/puffleBot.py b/bot/core/puffleBot.py index 567a244..b1dc1f7 100644 --- a/bot/core/puffleBot.py +++ b/bot/core/puffleBot.py @@ -2,12 +2,12 @@ import disnake from disnake import Webhook, Game -from disnake.ext.commands import InteractionBot, MissingPermissions +from disnake.ext.commands import InteractionBot from loguru import logger import bot.cogs -from bot.misc.buttons import Rules +from bot.handlers.buttons import Rules from bot.misc.constants import rules_message_id, about_message_id, rules_webhook_id -from bot.misc.select import About +from bot.handlers.select import About class PuffleBot(InteractionBot): @@ -31,16 +31,10 @@ async def on_connect(self): rules_webhook: Webhook = await self.fetch_webhook(rules_webhook_id) rules_message = await rules_webhook.fetch_message(rules_message_id) self.add_view(Rules(rules_message), message_id=rules_message_id) + if about_message_id is not None: view = disnake.ui.View(timeout=None) view.add_item(About()) self.add_view(view, message_id=about_message_id) - async def on_error(self, event_method: str, *args, **kwargs): - logger.error(f"{event_method}.{args}.{kwargs}") - - async def on_slash_command_error(self, inter, exception): - logger.error(exception) - if isinstance(exception, MissingPermissions): - await inter.response.send(f"У вас недостаточно прав для выполнения данной команды!", ephemeral=True) diff --git a/bot/core/server.py b/bot/core/server.py index 1956289..a51be97 100644 --- a/bot/core/server.py +++ b/bot/core/server.py @@ -4,6 +4,8 @@ from loguru import logger from bot.data import db_cp, db_pb from bot.core.puffleBot import PuffleBot +from bot.handlers import DummyEventListenerManager +import bot.handlers class Server: @@ -13,6 +15,7 @@ def __init__(self, config): self.config = config self.db_cp = db_cp self.db_pb = db_pb + self.dummy_event_listeners = DummyEventListenerManager(self) async def start(self): logger.add("logs/log.log") @@ -51,6 +54,9 @@ async def start(self): owner_id=527140180696629248) # test_guilds=[755445822920982548], self.bot.load_cogs() + await self.dummy_event_listeners.setup(bot.handlers) + await self.dummy_event_listeners.fire('boot', self) + try: await self.bot.start(self.config.token) finally: diff --git a/bot/handlers/__init__.py b/bot/handlers/__init__.py new file mode 100644 index 0000000..919dc75 --- /dev/null +++ b/bot/handlers/__init__.py @@ -0,0 +1,282 @@ +import enum +import inspect +import itertools +from types import FunctionType + +from bot import _AbstractManager, get_package_modules, plugins +from bot.misc.converters import ChecklistError, _ArgumentDeserializer, _ConverterContext, _listener, do_conversion, \ + get_converter +from bot.misc.cooldown import BucketType, CooldownError, _Cooldown, _CooldownMapping + + +class AuthorityError(Exception): + """Raised when a packet is received but user has not yet authenticated""" + + +class AbortHandlerChain(Exception): + """Exception raised when handler wants to abort the rest of the handler chain""" + + +class _Packet: + __slots__ = ['id'] + + def __init__(self, packet_id): + self.id = packet_id + + def __eq__(self, other): + return self.id == other.id + + def __hash__(self): + return hash(self.id) + + +class XTPacket(_Packet): + def __init__(self, *packet_id, ext='s'): + super().__init__(ext + '%' + '#'.join(packet_id)) + + +class XMLPacket(_Packet): + pass + + +class DummyEventPacket(_Packet): + pass + + +class Priority(enum.Enum): + Override = 3 + High = 2 + Low = 1 + + +class _Listener(_ArgumentDeserializer): + + __slots__ = ['priority', 'packet', 'overrides', 'before', 'after', 'client_type'] + + def __init__(self, packet, callback, **kwargs): + super().__init__(packet.id, callback, **kwargs) + self.packet = packet + + self.priority = kwargs.get('priority', Priority.Low) + self.before = kwargs.get('before') + self.after = kwargs.get('after') + self.client_type = kwargs.get('client') + + self.overrides = kwargs.get('overrides', []) + + if type(self.overrides) is not list: + self.overrides = [self.overrides] + + +class _XTListener(_Listener): + + __slots__ = ['pre_login', 'match'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.pre_login = kwargs.get('pre_login') + self.match = kwargs.get('match') + + async def __call__(self, p, packet_data): + try: + if not self.pre_login and not p.joined_world: + await p.close() + raise AuthorityError(f'{p} tried sending XT packet before authentication!') + + if self.match is None or packet_data[:len(self.match)] == self.match: + await super()._check_cooldown(p) + super()._check_list(p) + + await super().__call__(p, packet_data) + except CooldownError: + p.logger.debug(f'{p} tried to send a packet during a cooldown') + except ChecklistError: + p.logger.debug(f'{p} sent a packet without meeting checklist requirements') + + +class _XMLListener(_Listener): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + async def __call__(self, p, packet_data): + try: + await super()._check_cooldown(p) + super()._check_list(p) + + handler_call_arguments = [self.instance, p] if self.instance is not None else [p] + + ctx = _ConverterContext(None, None, packet_data, p) + for ctx.component in itertools.islice(self._signature, len(handler_call_arguments), len(self._signature)): + if ctx.component.default is not ctx.component.empty: + handler_call_arguments.append(ctx.component.default) + elif ctx.component.kind == ctx.component.POSITIONAL_OR_KEYWORD: + converter = get_converter(ctx.component) + + handler_call_arguments.append(await do_conversion(converter, ctx)) + return await self.callback(*handler_call_arguments) + except CooldownError: + p.logger.debug(f'{p} tried to send a packet during a cooldown') + except ChecklistError: + p.logger.debug(f'{p} sent a packet without meeting checklist requirements') + + +class _DummyListener(_Listener): + async def __call__(self, p, *_): + try: + super()._check_list(p) + handler_call_arguments = [self.instance, p] if self.instance is not None else [p] + return await self.callback(*handler_call_arguments) + except ChecklistError: + p.logger.debug(f'{p} sent a packet without meeting checklist requirements') + + +class _ListenerManager(_AbstractManager): + ListenerClass = _Listener + + def __init__(self, server): + self.strict_load = None + self.exclude_load = None + + super().__init__(server) + + async def setup(self, module, strict_load=None, exclude_load=None): + self.strict_load, self.exclude_load = strict_load, exclude_load + for handler_module in get_package_modules(module): + await self.load(handler_module) + + async def load(self, module): + module_name = module.__module__ if isinstance(module, plugins.IPlugin) else module.__name__ + if not (self.strict_load and module_name not in self.strict_load or + self.exclude_load and module_name in self.exclude_load): + listener_objects = inspect.getmembers(module, self.is_listener) + for listener_name, listener_object in listener_objects: + if isinstance(module, plugins.IPlugin): + listener_object.instance = module + + if listener_object.packet not in self: + self[listener_object.packet] = [] + + if listener_object not in self[listener_object.packet]: + if listener_object.priority == Priority.High: + self[listener_object.packet].insert(0, listener_object) + elif listener_object.priority == Priority.Override: + self[listener_object.packet] = [listener_object] + else: + self[listener_object.packet].append(listener_object) + + for listener_name, listener_object in listener_objects: + if listener_object.before in self[listener_object.packet]: + index_of_before = self[listener_object.packet].index(listener_object.before) + old_index = self[listener_object.packet].index(listener_object) + self[listener_object.packet].insert(index_of_before, self[listener_object.packet].pop(old_index)) + if listener_object.after in self[listener_object.packet]: + index_of_after = self[listener_object.packet].index(listener_object.after) + old_index = self[listener_object.packet].index(listener_object) + self[listener_object.packet].insert(index_of_after + 1, self[listener_object.packet].pop(old_index)) + for override in listener_object.overrides: + self[override.packet].remove(override) + + @classmethod + def is_listener(cls, listener): + return issubclass(type(listener), cls.ListenerClass) + + +class XTListenerManager(_ListenerManager): + ListenerClass = _XTListener + + +class XMLListenerManager(_ListenerManager): + ListenerClass = _XMLListener + + +class DummyEventListenerManager(_ListenerManager): + ListenerClass = _DummyListener + + async def fire(self, event, *args, **kwargs): + dummy_event = DummyEventPacket(event) + if dummy_event in self.server.dummy_event_listeners: + dummy_event_listeners = self.server.dummy_event_listeners[dummy_event] + for listener in dummy_event_listeners: + await listener(*args, **kwargs) + + +def handler(packet, **kwargs): + if not issubclass(type(packet), _Packet): + raise TypeError('All handlers can only listen for either XMLPacket or XTPacket.') + + listener_class = _XTListener if isinstance(packet, XTPacket) else _XMLListener + return _listener(listener_class, packet, **kwargs) + + +boot = _listener(_DummyListener, DummyEventPacket('boot')) +connected = _listener(_DummyListener, DummyEventPacket('connected')) +disconnected = _listener(_DummyListener, DummyEventPacket('disconnected')) + + +def cooldown(per=1.0, rate=1, bucket_type=BucketType.Default, callback=None): + def decorator(handler_function): + handler_function.__cooldown = _CooldownMapping(callback, _Cooldown(per, rate, bucket_type)) + return handler_function + return decorator + + +def check(predicate): + def decorator(handler_function): + if not hasattr(handler_function, '__checks'): + handler_function.__checks = [] + + if not type(predicate) == FunctionType: + raise TypeError('All handler checks must be a function') + + handler_function.__checks.append(predicate) + return handler_function + return decorator + + +def check_for_packet(listener, p): + return listener.packet not in p.received_packets + + +allow_once = check(check_for_packet) + + +def depends_on_packet(*packets): + def check_for_packets(_, p): + for packet in packets: + if packet not in p.received_packets: + return False + return True + return check(check_for_packets) + + +def player_attribute(**attrs): + def check_for_attributes(_, p): + for attr, value in attrs.items(): + if not getattr(p, attr) == value: + return False + return True + return check(check_for_attributes) + + +def player_in_room(*room_ids): + def check_room_id(_, p): + return p.room is not None and p.room.id in room_ids + return check(check_room_id) + + +def table(*logic): + def check_table_game(_, p): + if p.table is not None and type(p.table.logic) in logic: + return True + return False + return check(check_table_game) + + +def waddle(*waddle): + def check_waddle_game(_, p): + if p.waddle is not None and type(p.waddle) in waddle: + return True + return False + return check(check_waddle_game) diff --git a/bot/misc/buttons.py b/bot/handlers/buttons.py similarity index 73% rename from bot/misc/buttons.py rename to bot/handlers/buttons.py index 411d647..f46e99e 100644 --- a/bot/misc/buttons.py +++ b/bot/handlers/buttons.py @@ -1,9 +1,11 @@ import disnake +from disnake import Embed from bot.data.pufflebot.users import PenguinIntegrations from bot.misc.constants import embedRuleImageRu, embedRuleRu, embedRuleImageEn, embedRuleEn, embedRolesRu, \ embedRolesEn, enFullRulesLink, ruFullRulesLink, switchCommand from bot.misc.penguin import Penguin +from bot.misc.utils import getPenguinFromInter, transferCoinsAndReturnStatus class Buttons(disnake.ui.View): @@ -77,6 +79,55 @@ async def continueButton(self, _, inter: disnake.CommandInteraction): ... +class FundraisingButtons(disnake.ui.View): + # TODO: Make the "other amount" button + def __init__(self, fundraising: Fundraising, message: disnake.Message, receiver: Penguin, backers: int): + super().__init__(timeout=None) + self.fundraising = fundraising + self.message = message + self.receiver = receiver + self.raised = fundraising.raised + self.goal = fundraising.goal + self.backers = backers + + async def donate(self, inter: disnake.CommandInteraction, coins: int): + p: Penguin = await getPenguinFromInter(inter) + statusDict = await transferCoinsAndReturnStatus(p, self.receiver, int(coins)) + if statusDict["code"] == 400: + return await inter.send(statusDict["message"], ephemeral=True) + + self.raised += int(coins) + embed = Embed(color=0x2B2D31, title=self.message.embeds[0].title) + embed.add_field("Собрано монет", f"{self.raised}{f' из {self.goal}' if self.goal else ''}") + embed.set_footer(text=f"Спонсоры: {self.backers + 1}") + + if not await FundraisingBackers.get([self.message.id, p.id]): + self.backers += 1 + await FundraisingBackers.create(message_id=self.message.id, receiver_penguin_id=self.receiver.id, + backer_penguin_id=p.id) + + embed.set_footer(text=f"Спонсоры: {self.backers}") + + await self.message.edit(embed=embed) + await self.fundraising.update(raised=self.raised).apply() + await inter.send(statusDict["message"], ephemeral=True) + + @disnake.ui.button(label="100", style=disnake.ButtonStyle.blurple, emoji="<:coin:788877461588279336>", + custom_id="100") + async def coins100Button(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + await self.donate(inter, int(button.label)) + + @disnake.ui.button(label="500", style=disnake.ButtonStyle.blurple, emoji="<:coin:788877461588279336>", + custom_id="500") + async def coins500Button(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + await self.donate(inter, int(button.label)) + + @disnake.ui.button(label="1000", style=disnake.ButtonStyle.blurple, emoji="<:coin:788877461588279336>", + custom_id="1000") + async def coins1000Button(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + await self.donate(inter, int(button.label)) + + class Login(disnake.ui.View): def __init__(self): super().__init__() diff --git a/bot/handlers/censure.py b/bot/handlers/censure.py new file mode 100644 index 0000000..3eda4a3 --- /dev/null +++ b/bot/handlers/censure.py @@ -0,0 +1,53 @@ +from re import findall +from functools import lru_cache + +from loguru import logger +from bot.data.clubpenguin.moderator import ChatFilterRuleCollection +from bot.handlers import boot + +TRANS_TAB = dict((ord(a), b) for a, b in zip( + 'a6bgreё3ukmho0npctyx4w', + 'абвдгеезикмноопрстухчш' +)) +chat_filter_words: ChatFilterRuleCollection + + +@boot +async def filter_load(server): + global chat_filter_words + chat_filter_words = await ChatFilterRuleCollection.get_collection() + logger.info(f'Loaded {len(chat_filter_words)} filter words') + + +def simplify_message(message): + if len(message) < 2: + return message + return ''.join(letter for i, letter in enumerate(message) if letter != message[i - 1]) + + +def message_to_tokens(message): + tokens_set = set() + + for token in set(findall(r'\w+', simplify_message(message.lower()))): + tokens_set.add(token) + tokens_set.add(simplify_message(token.translate(TRANS_TAB))) + + return tokens_set + + +@lru_cache(maxsize=None) +def is_message_valid(message): + if not chat_filter_words: + return False + + tokens = message_to_tokens(message) + return not any(word in tokens for word in set(chat_filter_words.keys())) + + +@lru_cache(maxsize=None) +def get_consequence_from_message(message): + if not chat_filter_words: + return None + + tokens = message_to_tokens(message) + return next((c for w, c in chat_filter_words.items() if w in tokens), None) diff --git a/bot/misc/modals.py b/bot/handlers/modals.py similarity index 100% rename from bot/misc/modals.py rename to bot/handlers/modals.py diff --git a/bot/misc/select.py b/bot/handlers/select.py similarity index 97% rename from bot/misc/select.py rename to bot/handlers/select.py index 0914dc6..dcbbe88 100644 --- a/bot/misc/select.py +++ b/bot/handlers/select.py @@ -1,7 +1,7 @@ from disnake import MessageInteraction, SelectOption, AllowedMentions from disnake.ui import Select -from bot.misc.buttons import RulesEphemeral, Roles +from bot.handlers.buttons import RulesEphemeral, Roles from bot.misc.constants import embedLinks, embedRuleImageRu, embedRuleRu, embedRolesRu from bot.misc.penguin import Penguin diff --git a/bot/misc/converters.py b/bot/misc/converters.py new file mode 100644 index 0000000..77b4afe --- /dev/null +++ b/bot/misc/converters.py @@ -0,0 +1,481 @@ +import asyncio +import collections +import inspect +import itertools +from abc import ABC, abstractmethod + +from bot.misc.cooldown import CooldownError +from bot.data.clubpenguin.igloo import Flooring, Furniture, Igloo, Location +from bot.data.clubpenguin.item import Item +from bot.data.clubpenguin.permission import Permission +from bot.data.clubpenguin.pet import PenguinPuffle, Puffle +from bot.data.clubpenguin.room import Room +from bot.data.clubpenguin.stamp import Stamp + + +class ChecklistError(Exception): + """Raised when a checklist fails""" + + +class _ArgumentDeserializer: + __slots__ = ['name', 'components', 'callback', 'parent', 'pass_raw', 'cooldown', + 'checklist', 'instance', 'alias', 'rest_raw', 'string_delimiter', + 'string_separator', '_signature', '_arguments', '_exception_callback', + '_exception_class'] + + def __init__(self, name, callback, **kwargs): + self.callback = callback + + self.name = callback.__name__ if name is None else name + self.cooldown = kwargs.get('cooldown') + self.checklist = kwargs.get('checklist', []) + self.rest_raw = kwargs.get('rest_raw', False) + self.string_delimiter = kwargs.get('string_delimiter', []) + self.string_separator = kwargs.get('string_separator', str()) + + self.instance = None + + self._signature = list(inspect.signature(self.callback).parameters.values()) + self._arguments = inspect.getfullargspec(self.callback) + + self._exception_callback = None + self._exception_class = Exception + + if self.rest_raw: + self._signature = self._signature[:-1] + + def _can_run(self, p): + return True if not self.checklist else all(predicate(self, p) for predicate in self.checklist) + + async def _check_cooldown(self, p): + if self.cooldown is not None: + bucket = self.cooldown.get_bucket(p) + if bucket.is_cooling: + if self.cooldown.callback is not None: + if self.instance: + await self.cooldown.callback(self.instance, p) + else: + await self.cooldown.callback(p) + raise CooldownError(f'{p} invoked listener during cooldown') + + def _check_list(self, p): + if not self._can_run(p): + raise ChecklistError('Could not invoke listener due to checklist failure') + + def _consume_separated_string(self, ctx): + if ctx.argument and ctx.argument[0] in self.string_delimiter: + while not ctx.argument.endswith(ctx.argument[0]): + ctx.argument += self.string_separator + next(ctx.arguments) + ctx.argument = ctx.argument[1:-1] + + def error(self, exception_class=Exception): + def decorator(exception_callback): + self._exception_callback = exception_callback + self._exception_class = exception_class + return decorator + + async def _deserialize(self, p, data): + handler_call_arguments = [self.instance, p] if self.instance is not None else [p] + handler_call_keywords = {} + + arguments = itertools.islice(data, len(data) - len(self._arguments.kwonlyargs)) + keyword_arguments = itertools.islice(data, len(data) - len(self._arguments.kwonlyargs), len(data)) + + ctx = _ConverterContext(None, arguments, None, p) + for ctx.component in itertools.islice(self._signature, len(handler_call_arguments), len(self._signature)): + if ctx.component.annotation is ctx.component.empty and ctx.component.default is not ctx.component.empty: + handler_call_arguments.append(ctx.component.default) + elif ctx.component.kind == ctx.component.POSITIONAL_OR_KEYWORD: + ctx.argument = next(ctx.arguments, None) + + if ctx.argument is None: + if ctx.component.default is not ctx.component.empty: + handler_call_arguments.append(ctx.component.default) + else: + raise StopIteration + else: + converter = get_converter(ctx.component) + + if converter == str: + self._consume_separated_string(ctx) + + handler_call_arguments.append(await do_conversion(converter, ctx)) + elif ctx.component.kind == ctx.component.VAR_POSITIONAL: + for argument in ctx.arguments: + ctx.argument = argument + converter = get_converter(ctx.component) + + if converter == str: + self._consume_separated_string(ctx) + + handler_call_arguments.append(await do_conversion(converter, ctx)) + elif ctx.component.kind == ctx.component.KEYWORD_ONLY: + ctx.arguments = keyword_arguments + ctx.argument = next(keyword_arguments) + converter = get_converter(ctx.component) + + if converter == str: + self._consume_separated_string(ctx) + + handler_call_keywords[ctx.component.name] = await do_conversion(converter, ctx) + + if self.rest_raw: + handler_call_arguments.append(list(ctx.arguments)) + + return handler_call_arguments, handler_call_keywords + + async def __call__(self, p, data): + try: + handler_call_arguments, handler_call_keywords = await self._deserialize(p, data) + + return await self.callback(*handler_call_arguments, **handler_call_keywords) + except Exception as e: + if self._exception_callback and isinstance(e, self._exception_class): + if self.instance: + await self._exception_callback(self.instance, e) + else: + await self._exception_callback(e) + else: + raise e + + def __hash__(self): + return hash(self.__name__()) + + def __name__(self): + return f'{self.callback.__module__}.{self.callback.__name__}' + + +def _listener(cls, name, **kwargs): + def decorator(callback): + if not asyncio.iscoroutinefunction(callback): + raise TypeError('All listeners must be a coroutine.') + + try: + cooldown_object = callback.__cooldown + del callback.__cooldown + except AttributeError: + cooldown_object = None + + try: + checklist = callback.__checks + del callback.__checks + except AttributeError: + checklist = [] + + listener_object = cls(name, callback, cooldown=cooldown_object, checklist=checklist, **kwargs) + return listener_object + return decorator + + +class IConverter(ABC): + + @property + @abstractmethod + def description(self): + """A short description of the purpose of the converter""" + + @abstractmethod + async def convert(self, ctx): + """The actual converter implementation""" + + +Credentials = collections.namedtuple('Credentials', ('username', 'password')) +WorldCredentials = collections.namedtuple('Credentials', [ + 'id', + 'username', + 'login_key', + 'language_approved', 'language_rejected', + 'client_key', + 'confirmation_hash' +]) + + +class CredentialsConverter(IConverter): + + description = """Used for obtaining login credentials from XML login data""" + + async def convert(self, ctx): + username = ctx.argument[0][0].text + password = ctx.argument[0][1].text + return Credentials(username.lower(), password) + + +class WorldCredentialsConverter(IConverter): + + description = """Used for obtaining login credentials on the world server""" + + async def convert(self, ctx): + raw_login_data = ctx.argument[0][0].text + password_hashes = ctx.argument[0][1].text + penguin_id, _, username, login_key, _, language_approved, language_rejected = raw_login_data.split('|') + client_key, confirmation_hash = password_hashes.split('#') + return WorldCredentials(int(penguin_id), username.lower(), login_key, + int(language_approved), + int(language_rejected), + client_key, + confirmation_hash) + + +class VersionChkConverter(IConverter): + + description = """Used for checking the verChk version number""" + + async def convert(self, ctx): + return int(ctx.argument[0].get('v')) + + +class ConnectedPenguinConverter(IConverter): + + description = """Converts a penguin ID into a live penguin instance + or none if the player is offline""" + + async def convert(self, ctx): + try: + try: + return ctx.p.server.penguins_by_id[int(ctx.argument)] + except ValueError: + return ctx.p.server.penguins_by_username[ctx.argument.lower()] + except KeyError: + return None + + +class ConnectedIglooConverter(IConverter): + + description = """Converts a penguin ID into a live igloo instance or + none if it's not available""" + + async def convert(self, ctx): + igloo_id = int(ctx.argument) + if igloo_id in ctx.p.server.igloo_map: + return ctx.p.server.igloo_map[igloo_id] + return None + + +class RoomConverter(IConverter): + + description = """Converts a room ID into a houdini.data.Room instance""" + + async def convert(self, ctx): + room_id = int(ctx.argument) + if room_id in ctx.p.server.rooms: + return ctx.p.server.rooms[room_id] + return None + + +class ItemConverter(IConverter): + + description = """Converts an item ID into a houdini.data.Item instance""" + + async def convert(self, ctx): + item_id = int(ctx.argument) + if item_id in ctx.p.server.items: + return ctx.p.server.items[item_id] + return None + + +class IglooConverter(IConverter): + + description = """Converts an igloo ID into a houdini.data.Igloo instance""" + + async def convert(self, ctx): + igloo_id = int(ctx.argument) + if igloo_id in ctx.p.server.igloos: + return ctx.p.server.igloos[igloo_id] + return None + + +class FurnitureConverter(IConverter): + + description = """Converts a furniture ID into a houdini.data.Furniture instance""" + + async def convert(self, ctx): + furniture_id = int(ctx.argument) + if furniture_id in ctx.p.server.furniture: + return ctx.p.server.furniture[furniture_id] + return None + + +class FlooringConverter(IConverter): + + description = """Converts a flooring ID into a houdini.data.Flooring instance""" + + async def convert(self, ctx): + flooring_id = int(ctx.argument) + if flooring_id in ctx.p.server.flooring: + return ctx.p.server.flooring[flooring_id] + return None + + +class LocationConverter(IConverter): + + description = """Converts a location ID into a houdini.data.Location instance""" + + async def convert(self, ctx): + location_id = int(ctx.argument) + if location_id in ctx.p.server.locations: + return ctx.p.server.locations[location_id] + return None + + +class StampConverter(IConverter): + + description = """Converts a stamp ID into a houdini.data.Stamp instance""" + + async def convert(self, ctx): + stamp_id = int(ctx.argument) + if stamp_id in ctx.p.server.stamps: + return ctx.p.server.stamps[stamp_id] + return None + + +class PuffleConverter(IConverter): + + description = """Converts a puffle ID into a houdini.data.Puffle instance""" + + async def convert(self, ctx): + puffle_id = int(ctx.argument) + try: + return ctx.p.server.puffles[puffle_id] + except KeyError: + return None + + +class PenguinPuffleConverter(IConverter): + + description = """Converts a penguin puffle ID into a houdini.data.PenguinPuffle instance""" + + async def convert(self, ctx): + puffle_id = int(ctx.argument) + try: + return ctx.p.puffles[puffle_id] + except KeyError: + return None + + +class PermissionConverter(IConverter): + + description = """Converts a permission identifier to a houdini.data.Permission instance""" + + async def convert(self, ctx): + try: + return ctx.p.server.permissions[ctx.argument] + except KeyError: + return None + + +class SeparatorConverter(IConverter): + + __slots__ = ['separator', 'mapper'] + + description = """Converts strings separated by char into a list of type""" + + def __init__(self, separator='|', mapper=int): + self.separator = separator + self.mapper = mapper + + async def convert(self, ctx): + return map(self.mapper, ctx.argument.split(self.separator)) + + +class UnionConverter(IConverter): + + __slots__ = ['types'] + + description = """Converts union type into argument""" + + def __init__(self, *types, skip_none=False): + self.types = types + self.skip_none = skip_none + + async def convert(self, ctx): + for converter in self.types: + try: + result = await do_conversion(converter, ctx) + if not self.skip_none or result is not None: + return result + except ValueError: + continue + + +class GreedyConverter(IConverter): + + __slots__ = ['target'] + + description = """Converts until it can't any longer""" + + def __init__(self, target=int): + self.target = target + + async def convert(self, ctx): + converted = [] + try: + converted.append(await do_conversion(self.target, ctx)) + for ctx.argument in ctx.arguments: + converted.append(await do_conversion(self.target, ctx)) + except ValueError: + return converted + return converted + + +class OptionalConverter(IConverter): + + __slots__ = ['target'] + + description = """Tries to convert but ignores if it can't""" + + def __init__(self, target=int): + self.target = target + + async def convert(self, ctx): + try: + return await do_conversion(self.target, ctx) + except ValueError: + return ctx.component.default + + +class _ConverterContext: + + __slots__ = ['component', 'arguments', 'argument', 'p'] + + def __init__(self, component, arguments, argument, p): + self.component = component + self.arguments = arguments + self.argument = argument + self.p = p + + +ConverterTypes = { + Credentials: CredentialsConverter, + WorldCredentials: WorldCredentialsConverter, + + Room: RoomConverter, + Item: ItemConverter, + Furniture: FurnitureConverter, + Igloo: IglooConverter, + Flooring: FlooringConverter, + Location: LocationConverter, + Stamp: StampConverter, + Puffle: PuffleConverter, + + PenguinPuffle: PenguinPuffleConverter, + + Permission: PermissionConverter +} + + +def get_converter(component): + if component.annotation in ConverterTypes: + return ConverterTypes[component.annotation] + if component.annotation is component.empty: + return str + return component.annotation + + +async def do_conversion(converter, ctx): + if inspect.isclass(converter) and issubclass(converter, IConverter): + converter = converter() + if isinstance(converter, IConverter): + if asyncio.iscoroutinefunction(converter.convert): + return await converter.convert(ctx) + return converter.convert(ctx) + return converter(ctx.argument) diff --git a/bot/misc/cooldown.py b/bot/misc/cooldown.py new file mode 100644 index 0000000..13bee69 --- /dev/null +++ b/bot/misc/cooldown.py @@ -0,0 +1,87 @@ +import enum +import time + + +class CooldownError(Exception): + """Raised when packets are sent whilst a cooldown is active""" + pass + + +class BucketType(enum.Enum): + Default = 1 + Penguin = 1 + Server = 2 + + +class _Cooldown: + + __slots__ = ['rate', 'per', 'bucket_type', 'last', + '_window', '_tokens'] + + def __init__(self, per, rate, bucket_type): + self.per = float(per) + self.rate = int(rate) + self.bucket_type = bucket_type + self.last = 0.0 + + self._window = 0.0 + self._tokens = self.rate + + @property + def is_cooling(self): + current = time.time() + self.last = current + + if self._tokens == self.rate: + self._window = current + + if current > self._window + self.per: + self._tokens = self.rate + self._window = current + + if self._tokens == 0: + return self.per - (current - self._window) + + self._tokens -= 1 + if self._tokens == 0: + self._window = current + + def reset(self): + self._tokens = self.rate + self.last = 0.0 + + def copy(self): + return _Cooldown(self.per, self.rate, self.bucket_type) + + +class _CooldownMapping: + + __slots__ = ['_cooldown', '_cache', 'callback'] + + def __init__(self, callback, cooldown_object): + self._cooldown = cooldown_object + + self.callback = callback + + self._cache = {} + + def _get_bucket_key(self, p): + if self._cooldown.bucket_type == BucketType.Default: + return p.id + return p.server + + def _verify_cache_integrity(self): + current = time.time() + self._cache = {cache_key: bucket for cache_key, bucket in + self._cache.items() if current < bucket.last + bucket.per} + + def get_bucket(self, p): + self._verify_cache_integrity() + cache_key = self._get_bucket_key(p) + if cache_key not in self._cache: + bucket = self._cooldown.copy() + self._cache[cache_key] = bucket + else: + bucket = self._cache[cache_key] + return bucket + diff --git a/bot/misc/utils.py b/bot/misc/utils.py index 21d965e..089c0f0 100644 --- a/bot/misc/utils.py +++ b/bot/misc/utils.py @@ -32,7 +32,7 @@ async def getPenguinFromInter(inter: ApplicationCommandInteraction): return p -async def getPenguinOrNoneFromId(user_id: int): +async def getPenguinOrNoneFromUserId(user_id: int): """ Get a penguin object from a user ID. diff --git a/bot/plugins/__init__.py b/bot/plugins/__init__.py new file mode 100644 index 0000000..3ae8bfa --- /dev/null +++ b/bot/plugins/__init__.py @@ -0,0 +1,85 @@ +import inspect +from abc import ABC, abstractmethod + +from bot import _AbstractManager, get_package_modules +from bot.data.clubpenguin.plugin import PluginAttributeCollection + + +class IPlugin(ABC): + """ + Plugin interface which all plugins *must* implement. + """ + + @property + @abstractmethod + def author(self): + """The plugin's author which is usually a nickname and an e-mail address.""" + + @property + @abstractmethod + def version(self): + """The version of the plugin.""" + + @property + @abstractmethod + def description(self): + """Short summary of the plugin's intended purpose.""" + + @abstractmethod + async def ready(self): + """Called when the plugin is ready to function.""" + + def get_attribute(self, name, default=None): + plugin_attribute = self.attributes.get(name, default) + if plugin_attribute == default: + return default + return plugin_attribute.value + + async def set_attribute(self, name, value): + if name not in self.attributes: + await self.attributes.insert(name=name, value=value) + else: + plugin_attribute = self.attributes.get(name) + await plugin_attribute.update(value=value).apply() + + async def delete_attribute(self, name): + if name in self.attributes: + await self.attributes.delete(name) + + @abstractmethod + def __init__(self, server): + self.server = server + self.attributes = None + + +class PluginManager(_AbstractManager): + async def setup(self, module): + for plugin_package in get_package_modules(module): + await self.load(plugin_package) + + async def load(self, module): + plugin_class, plugin_type = inspect.getmembers(module, is_plugin).pop() + plugin_index = plugin_class.lower() + + if self.server.config.plugins != '*' and \ + plugin_index not in self.server.config.plugins: + return + + plugin_object = plugin_type(self.server) + + if plugin_index in self: + raise KeyError(f'Duplicate plugin name "{plugin_index}" exists') + + self[plugin_index] = plugin_object + + await self.server.commands.load(plugin_object) + await self.server.xt_listeners.load(plugin_object) + await self.server.xml_listeners.load(plugin_object) + await self.server.dummy_event_listeners.load(plugin_object) + + plugin_object.attributes = await PluginAttributeCollection.get_collection(plugin_index) + await plugin_object.ready() + + +def is_plugin(plugin_class): + return inspect.isclass(plugin_class) and issubclass(plugin_class, IPlugin) and plugin_class != IPlugin From 5a6d43d3f89275d3b0341bec5728e9e69cd7de87 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Fri, 14 Jul 2023 03:23:35 +0500 Subject: [PATCH 14/27] Added notification system. Added notify for coins transfer --- bot/cogs/commands.py | 14 +++++++------- bot/handlers/notification.py | 32 ++++++++++++++++++++++++++++++++ bot/misc/utils.py | 7 +++++-- 3 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 bot/handlers/notification.py diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index 5a42971..2d5e36d 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -52,15 +52,15 @@ async def card(self, inter: ApplicationCommandInteraction, @slash_command(name="pay", description="Перевести свои монеты другому игроку") async def pay(self, inter: ApplicationCommandInteraction, - receiver: str = Param(description='Получатель (его ник в игре)'), - amount: int = Param(description='Количество монет')): + receiver: disnake.Member = Param(description='Получатель'), + amount: int = Param(description='Количество монет'), + message: str = Param(default=None, description='Сообщение получателю')): p: Penguin = await getPenguinFromInter(inter) + r: Penguin = await getPenguinOrNoneFromUserId(receiver.id) + if r is None: + return await inter.send(f"Мы не нашли пингвина у указанного вами пользователя.", ephemeral=True) - receiverId = await Penguin.select('id').where(Penguin.username == receiver.lower()).gino.first() - receiverId = int(receiverId[0]) - r: Penguin = await Penguin.get(receiverId) - - statusDict = await transferCoinsAndReturnStatus(p, r, amount) + statusDict = await transferCoinsAndReturnStatus(p, r, amount, message) if statusDict["code"] == 400: await inter.send(statusDict["message"], ephemeral=True) diff --git a/bot/handlers/notification.py b/bot/handlers/notification.py new file mode 100644 index 0000000..a127f7f --- /dev/null +++ b/bot/handlers/notification.py @@ -0,0 +1,32 @@ +import disnake +from disnake import Embed +from loguru import logger + +from bot.data.pufflebot.users import Users +from bot.handlers import boot +from bot.misc.penguin import Penguin + + +@boot +async def setup(server): + global bot + bot = server.bot + logger.info(f'Loaded notification system') + + +async def notifyCoinsReceive(senderPenguin: Penguin, receiverPenguin: Penguin, coins, message=None): + receiverId = await Users.select("id").where(Users.penguin_id == receiverPenguin.id).gino.first() + receiver = await bot.fetch_user(int(receiverId[0])) + senderId = await Users.select("id").where(Users.penguin_id == senderPenguin.id).gino.first() + sender = await bot.fetch_user(int(senderId[0])) + + embed = Embed(color=0xB7F360, title=f"{sender} перевел(а) Вам {coins}м") + if message: + embed.add_field("Сообщение", message, inline=False) + embed.add_field("Баланс", receiverPenguin.coins, inline=False) + embed.set_footer(text=f"Ваш аккаунт: {receiverPenguin.safe_name()}") + await sendNotify(receiver, embed) + + +async def sendNotify(user: disnake.User, embed): + await user.send(embed=embed) diff --git a/bot/misc/utils.py b/bot/misc/utils.py index 089c0f0..2ea898e 100644 --- a/bot/misc/utils.py +++ b/bot/misc/utils.py @@ -2,6 +2,7 @@ from bot.data.clubpenguin.moderator import Logs from bot.data.pufflebot.users import Users +from bot.handlers.notification import notifyCoinsReceive from bot.misc.constants import loginCommand from bot.misc.penguin import Penguin @@ -54,7 +55,7 @@ async def getPenguinOrNoneFromUserId(user_id: int): return p -async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amount: int) -> dict: +async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amount: int, message: str = None) -> dict: """ Transfer coins between two penguins and return a status dictionary. @@ -66,13 +67,14 @@ async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amoun The penguin object representing the receiver of the coins. amount: int The number of coins to transfer. + message : str, optional + An optional message to include with the transfer. Returns ---------- dict A dictionary containing the status code and message. """ - # TODO: Make a notification system if amount <= 0: return {"code": 400, "message": "Пожалуйста введите правильное число монет"} @@ -90,5 +92,6 @@ async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amoun await Logs.create(penguin_id=int(sender.id), type=4, text=f"Перевёл игроку {receiver.username} {int(amount)} монет. Через Discord бота", room_id=0, server_id=8000) + await notifyCoinsReceive(sender, receiver, amount, message) return {"code": 200, "message": f"Вы успешно передали `{amount}` монет игроку `{receiver.safe_name()}`!"} From a78150eb1e9427958bd3fa2c2b526f2ba1784927 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Sun, 16 Jul 2023 18:31:51 +0500 Subject: [PATCH 15/27] Added /pay2 --- bot/cogs/commands.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index 2d5e36d..bb4f3db 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -8,6 +8,7 @@ from disnake.ext.commands import Cog, Param, slash_command from requests import Session +from bot.handlers.notification import notifyCoinsReceive from bot.misc.constants import online_url, headers, emojiCuteSad from bot.misc.penguin import Penguin from bot.misc.utils import getPenguinFromInter, getPenguinOrNoneFromUserId, transferCoinsAndReturnStatus @@ -52,19 +53,39 @@ async def card(self, inter: ApplicationCommandInteraction, @slash_command(name="pay", description="Перевести свои монеты другому игроку") async def pay(self, inter: ApplicationCommandInteraction, - receiver: disnake.Member = Param(description='Получатель'), + receiver: str = Param(description='Получатель (его ник в игре)'), amount: int = Param(description='Количество монет'), message: str = Param(default=None, description='Сообщение получателю')): p: Penguin = await getPenguinFromInter(inter) + receiverId = await Penguin.select('id').where(Penguin.username == receiver.lower()).gino.first() + r: Penguin = await Penguin.get(int(receiverId[0])) + + if r is None: + return await inter.send(f"Мы не нашли указанного пингвина.", ephemeral=True) + + statusDict = await transferCoinsAndReturnStatus(p, r, amount) + if statusDict["code"] == 400: + return await inter.send(statusDict["message"], ephemeral=True) + + await inter.send(statusDict["message"]) + await notifyCoinsReceive(p, r, amount, message) + + @slash_command(name="pay2", description="Перевести свои монеты другому пользователю") + async def pay2(self, inter: ApplicationCommandInteraction, + receiver: disnake.User = Param(description='Получатель'), + amount: int = Param(description='Количество монет'), + message: str = Param(default=None, description='Сообщение получателю')): + p: Penguin = await getPenguinFromInter(inter) r: Penguin = await getPenguinOrNoneFromUserId(receiver.id) if r is None: return await inter.send(f"Мы не нашли пингвина у указанного вами пользователя.", ephemeral=True) - statusDict = await transferCoinsAndReturnStatus(p, r, amount, message) + statusDict = await transferCoinsAndReturnStatus(p, r, amount) if statusDict["code"] == 400: - await inter.send(statusDict["message"], ephemeral=True) + return await inter.send(statusDict["message"], ephemeral=True) await inter.send(statusDict["message"]) + await notifyCoinsReceive(p, r, amount, message) @slash_command(name="online", description="Показывает количество игроков которые сейчас онлайн") async def online(self, inter: ApplicationCommandInteraction): From c073f0b023b6f203c47f38182623a5c1f951363b Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Sun, 16 Jul 2023 18:32:13 +0500 Subject: [PATCH 16/27] Some refactoring --- bot/cogs/account.py | 54 ++++++++++++++++++++++++++--- bot/handlers/buttons.py | 76 ++++------------------------------------- 2 files changed, 55 insertions(+), 75 deletions(-) diff --git a/bot/cogs/account.py b/bot/cogs/account.py index 99a98b7..87e1ec0 100644 --- a/bot/cogs/account.py +++ b/bot/cogs/account.py @@ -4,10 +4,10 @@ from disnake.ext.commands import Cog, slash_command from bot.data import db_pb -from bot.misc.constants import loginCommand +from bot.misc.constants import loginCommand, switchCommand from bot.misc.penguin import Penguin -from bot.data.pufflebot.users import Users, PenguinIntegrations -from bot.handlers.buttons import Logout, Login +from bot.data.pufflebot.user import User, PenguinIntegrations +from bot.handlers.buttons import Buttons from bot.handlers.select import SelectPenguins from bot.misc.utils import getPenguinFromInter, getPenguinOrNoneFromUserId @@ -25,7 +25,7 @@ async def login(self, inter: ApplicationCommandInteraction): @slash_command(name="logout", description="Отвязать свой Discord аккаунт от своего пингвина") async def logout(self, inter: ApplicationCommandInteraction): p: Penguin = await getPenguinFromInter(inter) - user: Users = await Users.get(inter.user.id) + user: User = await User.get(inter.user.id) penguin_ids = await db_pb.select([PenguinIntegrations.penguin_id]).where( (PenguinIntegrations.discord_id == inter.user.id)).gino.all() @@ -35,7 +35,7 @@ async def logout(self, inter: ApplicationCommandInteraction): @slash_command(name="switch", description="Сменить текущий аккаунт") async def switch(self, inter: ApplicationCommandInteraction): p: Penguin = await getPenguinOrNoneFromUserId(inter.user.id) - user: Users = await Users.get(inter.user.id) + user: User = await User.get(inter.user.id) penguin_ids = await db_pb.select([PenguinIntegrations.penguin_id]).where( (PenguinIntegrations.discord_id == inter.user.id)).gino.all() @@ -64,5 +64,49 @@ async def switch(self, inter: ApplicationCommandInteraction): view=view, ephemeral=True) +class Login(disnake.ui.View): + def __init__(self): + super().__init__() + + @disnake.ui.button(label="Войти", url="https://cpps.app/discord/login") + async def loginButton(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + ... + + +class Logout(Buttons): + def __init__(self, inter: disnake.CommandInteraction, p, user, penguin_ids): + super().__init__(inter, timeout=None) + self.p = p + self.user = user + self.penguin_ids = penguin_ids + + async def on_timeout(self): + ... + + @disnake.ui.button(label="Отмена", style=disnake.ButtonStyle.gray, custom_id="cancel") + async def cancelButton(self, _, inter: disnake.CommandInteraction): + await self.disableAllItems() + await inter.send(f"Отменено", ephemeral=True) + + @disnake.ui.button(label="Выйти", style=disnake.ButtonStyle.red, custom_id="logout") + async def logoutButton(self, _, inter: disnake.CommandInteraction): + await self.disableAllItems() + await PenguinIntegrations.delete.where(PenguinIntegrations.penguin_id == self.p.id).gino.status() + if len(self.penguin_ids) == 1: + await self.user.update(penguin_id=None).apply() + return await inter.send(f"Ваш аккаунт `{self.p.safe_name()}` успешно отвязан.", ephemeral=True) + + if len(self.penguin_ids) == 2: + newCurrentPenguin: Penguin = await Penguin.get(self.penguin_ids[0][0]) + await self.user.update(penguin_id=self.penguin_ids[0][0]).apply() + return await inter.send( + f"Ваш аккаунт `{self.p.safe_name()}` успешно отвязан. " + f"Сейчас ваш текущий аккаунт `{newCurrentPenguin.safe_name()}`", ephemeral=True) + + return await inter.send( + f"Ваш аккаунт `{self.p.safe_name()}` успешно отвязан. " + f"Чтобы выбрать текущий аккаунт воспользуйтесь командой {switchCommand}", ephemeral=True) + + def setup(bot): bot.add_cog(AccountManagementCommands(bot)) diff --git a/bot/handlers/buttons.py b/bot/handlers/buttons.py index f46e99e..9ca9487 100644 --- a/bot/handlers/buttons.py +++ b/bot/handlers/buttons.py @@ -1,9 +1,10 @@ import disnake from disnake import Embed -from bot.data.pufflebot.users import PenguinIntegrations +from bot.data.pufflebot.fundraising import Fundraising, FundraisingBackers +from bot.handlers.notification import notifyCoinsReceive from bot.misc.constants import embedRuleImageRu, embedRuleRu, embedRuleImageEn, embedRuleEn, embedRolesRu, \ - embedRolesEn, enFullRulesLink, ruFullRulesLink, switchCommand + embedRolesEn, enFullRulesLink, ruFullRulesLink from bot.misc.penguin import Penguin from bot.misc.utils import getPenguinFromInter, transferCoinsAndReturnStatus @@ -22,63 +23,6 @@ async def on_timeout(self): await self.disableAllItems() -class Question(Buttons): - def __init__(self, inter, function): - super().__init__(inter, function) - - @disnake.ui.button(label="Да", style=disnake.ButtonStyle.green, custom_id="yes") - async def yesButton(self, _, inter: disnake.CommandInteraction): - ... - - @disnake.ui.button(label="Нет", style=disnake.ButtonStyle.red, custom_id="no") - async def noButton(self, _, inter: disnake.CommandInteraction): - ... - - -class Logout(Buttons): - def __init__(self, inter: disnake.CommandInteraction, p, user, penguin_ids): - super().__init__(inter, timeout=None) - self.p = p - self.user = user - self.penguin_ids = penguin_ids - - async def on_timeout(self): - ... - - @disnake.ui.button(label="Отмена", style=disnake.ButtonStyle.gray, custom_id="cancel") - async def cancelButton(self, _, inter: disnake.CommandInteraction): - await self.disableAllItems() - await inter.send(f"Отменено", ephemeral=True) - - @disnake.ui.button(label="Выйти", style=disnake.ButtonStyle.red, custom_id="logout") - async def logoutButton(self, _, inter: disnake.CommandInteraction): - await self.disableAllItems() - await PenguinIntegrations.delete.where(PenguinIntegrations.penguin_id == self.p.id).gino.status() - if len(self.penguin_ids) == 1: - await self.user.update(penguin_id=None).apply() - return await inter.send(f"Ваш аккаунт `{self.p.safe_name()}` успешно отвязан.", ephemeral=True) - - if len(self.penguin_ids) == 2: - newCurrentPenguin: Penguin = await Penguin.get(self.penguin_ids[0][0]) - await self.user.update(penguin_id=self.penguin_ids[0][0]).apply() - return await inter.send( - f"Ваш аккаунт `{self.p.safe_name()}` успешно отвязан. " - f"Сейчас ваш текущий аккаунт `{newCurrentPenguin.safe_name()}`", ephemeral=True) - - return await inter.send( - f"Ваш аккаунт `{self.p.safe_name()}` успешно отвязан. " - f"Чтобы выбрать текущий аккаунт воспользуйтесь командой {switchCommand}", ephemeral=True) - - -class Continue(Buttons): - def __init__(self, inter: disnake.CommandInteraction, function): - super().__init__(inter, function) - - @disnake.ui.button(label="Продолжить", style=disnake.ButtonStyle.blurple, custom_id="continue") - async def continueButton(self, _, inter: disnake.CommandInteraction): - ... - - class FundraisingButtons(disnake.ui.View): # TODO: Make the "other amount" button def __init__(self, fundraising: Fundraising, message: disnake.Message, receiver: Penguin, backers: int): @@ -93,8 +37,9 @@ def __init__(self, fundraising: Fundraising, message: disnake.Message, receiver: async def donate(self, inter: disnake.CommandInteraction, coins: int): p: Penguin = await getPenguinFromInter(inter) statusDict = await transferCoinsAndReturnStatus(p, self.receiver, int(coins)) + await inter.send(statusDict["message"], ephemeral=True) if statusDict["code"] == 400: - return await inter.send(statusDict["message"], ephemeral=True) + return self.raised += int(coins) embed = Embed(color=0x2B2D31, title=self.message.embeds[0].title) @@ -110,7 +55,7 @@ async def donate(self, inter: disnake.CommandInteraction, coins: int): await self.message.edit(embed=embed) await self.fundraising.update(raised=self.raised).apply() - await inter.send(statusDict["message"], ephemeral=True) + await notifyCoinsReceive(p, self.receiver, int(coins)) @disnake.ui.button(label="100", style=disnake.ButtonStyle.blurple, emoji="<:coin:788877461588279336>", custom_id="100") @@ -128,15 +73,6 @@ async def coins1000Button(self, button: disnake.ui.Button, inter: disnake.Comman await self.donate(inter, int(button.label)) -class Login(disnake.ui.View): - def __init__(self): - super().__init__() - - @disnake.ui.button(label="Войти", url="https://cpps.app/discord/login") - async def loginButton(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): - ... - - class Rules(disnake.ui.View): def __init__(self, massage): super().__init__(timeout=None) From ec386dd9a8b2185896f3fe4d4b839b1e1801da28 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Sun, 16 Jul 2023 18:35:03 +0500 Subject: [PATCH 17/27] Add penguins cache and some refactoring --- bot/data/pufflebot/{users.py => user.py} | 6 +++-- bot/misc/utils.py | 30 ++++++++++++++++-------- 2 files changed, 24 insertions(+), 12 deletions(-) rename bot/data/pufflebot/{users.py => user.py} (70%) diff --git a/bot/data/pufflebot/users.py b/bot/data/pufflebot/user.py similarity index 70% rename from bot/data/pufflebot/users.py rename to bot/data/pufflebot/user.py index 2ed4fea..db062df 100644 --- a/bot/data/pufflebot/users.py +++ b/bot/data/pufflebot/user.py @@ -1,12 +1,14 @@ from bot.data import db_pb -class Users(db_pb.Model): - __tablename__ = 'users' +class User(db_pb.Model): + __tablename__ = 'user' id = db_pb.Column(db_pb.BigInteger, primary_key=True, nullable=False) penguin_id = db_pb.Column(db_pb.BigInteger, nullable=False) language = db_pb.Column(db_pb.String(2), nullable=False, server_default=db_pb.text("ru")) + enabled_notify = db_pb.Column(db_pb.Boolean, nullable=False, server_default=db_pb.text("true")) + enabled_coins_notify = db_pb.Column(db_pb.Boolean, nullable=False, server_default=db_pb.text("true")) class PenguinIntegrations(db_pb.Model): diff --git a/bot/misc/utils.py b/bot/misc/utils.py index 2ea898e..673e990 100644 --- a/bot/misc/utils.py +++ b/bot/misc/utils.py @@ -1,13 +1,14 @@ from disnake import ApplicationCommandInteraction from bot.data.clubpenguin.moderator import Logs -from bot.data.pufflebot.users import Users -from bot.handlers.notification import notifyCoinsReceive +from bot.data.pufflebot.user import User from bot.misc.constants import loginCommand from bot.misc.penguin import Penguin +penguins_by_id = {} -async def getPenguinFromInter(inter: ApplicationCommandInteraction): + +async def getPenguinFromInter(inter: ApplicationCommandInteraction, *, cache=True) -> Penguin: """ Retrieves a penguin object from the database based on the discord user ID. **If the penguin is not found, the function sends a response to the interaction** @@ -16,24 +17,30 @@ async def getPenguinFromInter(inter: ApplicationCommandInteraction): ---------- inter: ApplicationCommandInteraction The interaction object representing the user's command. + cache : bool, optional + Whether to cache the penguin object, by default True Returns ---------- Penguin The penguin object retrieved from the database. """ - user = await Users.get(inter.user.id) + user = await User.get(inter.user.id) if user is None: await inter.send( f"Мы не нашли вашего пингвина. Пожалуйста воспользуйтесь командой {loginCommand}", ephemeral=True) return + if cache and user.penguin_id in penguins_by_id: + return penguins_by_id[user.penguin_id] + p = await Penguin.get(user.penguin_id) await p.setup() + penguins_by_id[user.penguin_id] = p return p -async def getPenguinOrNoneFromUserId(user_id: int): +async def getPenguinOrNoneFromUserId(user_id: int, *, cache=True) -> Penguin: """ Get a penguin object from a user ID. @@ -41,21 +48,27 @@ async def getPenguinOrNoneFromUserId(user_id: int): ---------- user_id: int The ID of the discord user to get the penguin object for. + cache : bool, optional + Whether to cache the penguin object, by default True Returns ------- Optional[Penguin] The penguin object, or `None` if the user is not found. """ - user = await Users.get(user_id) + user = await User.get(user_id) if user is None: return None + if cache and user.penguin_id in penguins_by_id: + return penguins_by_id[user.penguin_id] + p = await Penguin.get(user.penguin_id) await p.setup() + penguins_by_id[user.penguin_id] = p return p -async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amount: int, message: str = None) -> dict: +async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amount: int) -> dict: """ Transfer coins between two penguins and return a status dictionary. @@ -67,8 +80,6 @@ async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amoun The penguin object representing the receiver of the coins. amount: int The number of coins to transfer. - message : str, optional - An optional message to include with the transfer. Returns ---------- @@ -92,6 +103,5 @@ async def transferCoinsAndReturnStatus(sender: Penguin, receiver: Penguin, amoun await Logs.create(penguin_id=int(sender.id), type=4, text=f"Перевёл игроку {receiver.username} {int(amount)} монет. Через Discord бота", room_id=0, server_id=8000) - await notifyCoinsReceive(sender, receiver, amount, message) return {"code": 200, "message": f"Вы успешно передали `{amount}` монет игроку `{receiver.safe_name()}`!"} From 1ed165e3a5dace2be8a56b58c892b1c0e11adcfd Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Sun, 16 Jul 2023 18:35:43 +0500 Subject: [PATCH 18/27] Add membership ended notifications --- bot/handlers/notification.py | 70 +++++++++++++++++++++++++++++++----- bot/misc/constants.py | 1 + bot/misc/penguin.py | 38 ++++++++++++++++++-- 3 files changed, 99 insertions(+), 10 deletions(-) diff --git a/bot/handlers/notification.py b/bot/handlers/notification.py index a127f7f..e36ba1c 100644 --- a/bot/handlers/notification.py +++ b/bot/handlers/notification.py @@ -1,32 +1,86 @@ import disnake +from asyncio import sleep, create_task from disnake import Embed from loguru import logger -from bot.data.pufflebot.users import Users +from bot.data.pufflebot.user import User, PenguinIntegrations from bot.handlers import boot +from bot.misc.constants import emojiCoin from bot.misc.penguin import Penguin +from bot.misc.utils import getPenguinOrNoneFromUserId @boot async def setup(server): global bot bot = server.bot + create_task(check_membership()) logger.info(f'Loaded notification system') +async def check_membership(): + while True: + await sleep(86_400) # 24 hours + for user in await User.query.gino.all(): + p = await getPenguinOrNoneFromUserId(user.id, cache=False) + + if p.membership_days_remain == 0: + await notifyMembershipEnded(p) + continue + if 0 < p.membership_days_remain < 6 and not p.membership_expires_aware: + await notifyMembershipSoonEnded(p) + continue + + async def notifyCoinsReceive(senderPenguin: Penguin, receiverPenguin: Penguin, coins, message=None): - receiverId = await Users.select("id").where(Users.penguin_id == receiverPenguin.id).gino.first() + receiverId = await PenguinIntegrations.select("discord_id").where( + PenguinIntegrations.penguin_id == senderPenguin.id).gino.first() receiver = await bot.fetch_user(int(receiverId[0])) - senderId = await Users.select("id").where(Users.penguin_id == senderPenguin.id).gino.first() + senderId = await User.select("id").where(User.penguin_id == senderPenguin.id).gino.first() sender = await bot.fetch_user(int(senderId[0])) - embed = Embed(color=0xB7F360, title=f"{sender} перевел(а) Вам {coins}м") + embed = Embed(color=0xB7F360, title=f"{sender.global_name} перевел(а) Вам {coins}м") if message: embed.add_field("Сообщение", message, inline=False) - embed.add_field("Баланс", receiverPenguin.coins, inline=False) - embed.set_footer(text=f"Ваш аккаунт: {receiverPenguin.safe_name()}") + embed.add_field("Баланс", f"{receiverPenguin.coins} {emojiCoin}", inline=False) + embed.set_footer(text=f"Ваш аккаунт: {receiverPenguin.safe_name()}", + icon_url=f"https://play.cpps.app/avatar/{receiverPenguin.id}/cp?size=600") await sendNotify(receiver, embed) -async def sendNotify(user: disnake.User, embed): - await user.send(embed=embed) +async def notifyMembershipEnded(p: Penguin): + embed = Embed(color=0xFFD947, title=f"Подписка закончилась") + embed.set_footer(text=f"Аккаунт: {p.safe_name()}", + icon_url=f"https://play.cpps.app/avatar/{p.id}/cp?size=600") + embed.set_image(url="https://cpps.app/NO%20MEMBERSHIP.png") + + userId = await User.select("id").where(User.penguin_id == p.id).gino.first() + user = await bot.fetch_user(int(userId[0])) + await sendNotify(user, embed, view=MembershipButton()) + + +async def notifyMembershipSoonEnded(p: Penguin): + embed = Embed(color=0xFFD947, title=f"Подписка скоро закончится", + description="Войдите в игру, что бы больше не получать напоминания") + embed.add_field("До конца подписки", f"{p.membership_days_remain} дней", inline=False) + embed.set_footer(text=f"Аккаунт: {p.safe_name()}", + icon_url=f"https://play.cpps.app/avatar/{p.id}/cp?size=600") + + userId = await User.select("id").where(User.penguin_id == p.id).gino.first() + user = await bot.fetch_user(int(userId[0])) + await sendNotify(user, embed, view=MembershipButton()) + + +async def sendNotify(user: disnake.User, embed, *, view=None): + if (await User.get(user.id)).enabled_notify: + await user.send(embed=embed, view=view) + + +class MembershipButton(disnake.ui.View): + def __init__(self): + super().__init__(timeout=None) + + @disnake.ui.button(label="Приобрести подписку", style=disnake.ButtonStyle.link, + url="https://cpps.app/membership") + async def buyMembership(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + ... diff --git a/bot/misc/constants.py b/bot/misc/constants.py index 94ee3df..253576b 100644 --- a/bot/misc/constants.py +++ b/bot/misc/constants.py @@ -41,6 +41,7 @@ emojiGameFavicon = "<:favicon:1121758719634571405>" emojiWikiFavicon = "<:wiki:1121764147307237447>" emojiCuteSad = "<:cuteSad:794874872835735553>" +emojiCoin = "<:coin:788877461588279336>" # ========== EMBEDS ========== diff --git a/bot/misc/penguin.py b/bot/misc/penguin.py index 487bb8b..a33a5a0 100644 --- a/bot/misc/penguin.py +++ b/bot/misc/penguin.py @@ -1,7 +1,12 @@ +from datetime import datetime + from loguru import logger + +from bot.data import db_cp from bot.data.clubpenguin import penguin from bot.data.clubpenguin.item import PenguinItemCollection from bot.data.clubpenguin.mail import PenguinPostcard +from bot.data.clubpenguin.penguin import PenguinMembership from bot.data.clubpenguin.plugin import PenguinAttributeCollection from bot.data.clubpenguin.stamp import PenguinStampCollection @@ -20,6 +25,7 @@ class Penguin(penguin.Penguin): def __init__(self, *args): super().__init__(*args) + self.membership_expires_aware = None self.muted = False self.login_key = None @@ -37,6 +43,35 @@ async def setup(self): self.stamps = await PenguinStampCollection.get_collection(self.id) self.attributes = await PenguinAttributeCollection.get_collection(self.id) + membership_history = PenguinMembership.query.where(PenguinMembership.penguin_id == self.id) + current_timestamp = datetime.now() + very_old_memberships = await PenguinMembership.query.where((PenguinMembership.penguin_id == self.id)).order_by( + PenguinMembership.start.asc()).gino.first() + + async with db_cp.transaction(): + async for membership_record in membership_history.gino.iterate(): + membership_recurring = membership_record.expires is None + membership_active = membership_recurring or membership_record.expires >= current_timestamp + + if membership_record.start < current_timestamp: + if membership_active: + self.is_member = True + + if not membership_recurring: + self.membership_days_remain = ( + membership_record.expires.date() - datetime.now().date()).days + self.membership_expires_aware = membership_record.expires_aware + else: + if self.membership_days_remain < 0: + days_since_expiry = (membership_record.expires.date() - datetime.now().date()).days + self.membership_days_remain = min(self.membership_days_remain, days_since_expiry) + + membership_end_date = current_timestamp if membership_active else membership_record.expires + if very_old_memberships is None: + self.membership_days_total += (membership_end_date - membership_record.start).days + else: + self.membership_days_total = (current_timestamp - very_old_memberships.start).days + def safe_name(self): return self.safe_nickname() @@ -110,8 +145,7 @@ async def add_puffle_item(self, care_item, quantity=1, cost=None): await penguin_care_item.update( quantity=penguin_care_item.quantity + quantity).apply() else: - penguin_care_item = await self.puffle_items.insert(item_id=care_item_id, - quantity=quantity) + await self.puffle_items.insert(item_id=care_item_id, quantity=quantity) cost = cost if cost is not None else care_item.cost await self.update(coins=self.coins - cost).apply() From 2243e04cdeb851e7d458876b5f6bf4a9e04f0ab7 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Sun, 16 Jul 2023 22:05:30 +0500 Subject: [PATCH 19/27] Add settings --- bot/cogs/account.py | 51 ++++++++++++++++++++++++++++++++++++ bot/data/pufflebot/user.py | 1 + bot/handlers/notification.py | 19 +++++++++----- 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/bot/cogs/account.py b/bot/cogs/account.py index 87e1ec0..884e171 100644 --- a/bot/cogs/account.py +++ b/bot/cogs/account.py @@ -63,6 +63,14 @@ async def switch(self, inter: ApplicationCommandInteraction): f"Ваш текущий аккаунт: `{p.safe_name()}`. Какой аккаунт вы хотите сделать текущим?", view=view, ephemeral=True) + @slash_command(name="settings", description="Настройки пользователя бота") + async def settings(self, inter: ApplicationCommandInteraction): + p: Penguin = await getPenguinFromInter(inter) + user: User = await User.get(inter.user.id) + + await inter.send("Можете изменить свои настройки здесь.\n# Уведомления", view=Settings(inter, user), + ephemeral=True) + class Login(disnake.ui.View): def __init__(self): @@ -108,5 +116,48 @@ async def logoutButton(self, _, inter: disnake.CommandInteraction): f"Чтобы выбрать текущий аккаунт воспользуйтесь командой {switchCommand}", ephemeral=True) +class Settings(disnake.ui.View): + def __init__(self, original_inter, user): + super().__init__() + self.original_inter = original_inter + self.user: User = user + + @disnake.ui.button(label="Все", style=disnake.ButtonStyle.green, custom_id="allNotify") + async def allNotify(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + await inter.response.defer() + if self.user.enabled_notify: + button.style = disnake.ButtonStyle.gray + self.children[1].disabled = True + self.children[2].disabled = True + else: + button.style = disnake.ButtonStyle.green + self.children[1].disabled = False + self.children[2].disabled = False + self.user.enabled_notify = not self.user.enabled_notify + await self.original_inter.edit_original_response(view=self) + + @disnake.ui.button(label="О пополнении баланса", style=disnake.ButtonStyle.green, + custom_id="coinsNotify") + async def coinsNotify(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + await inter.response.defer() + if self.user.enabled_coins_notify: + button.style = disnake.ButtonStyle.gray + else: + button.style = disnake.ButtonStyle.green + self.user.enabled_coins_notify = not self.user.enabled_coins_notify + await self.original_inter.edit_original_response(view=self) + + @disnake.ui.button(label="Об окончании подписки", style=disnake.ButtonStyle.green, + custom_id="membershipNotify") + async def membershipNotify(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + await inter.response.defer() + if self.user.enabled_membership_notify: + button.style = disnake.ButtonStyle.gray + else: + button.style = disnake.ButtonStyle.green + self.user.enabled_membership_notify = not self.user.enabled_membership_notify + await self.original_inter.edit_original_response(view=self) + + def setup(bot): bot.add_cog(AccountManagementCommands(bot)) diff --git a/bot/data/pufflebot/user.py b/bot/data/pufflebot/user.py index db062df..c22feac 100644 --- a/bot/data/pufflebot/user.py +++ b/bot/data/pufflebot/user.py @@ -9,6 +9,7 @@ class User(db_pb.Model): language = db_pb.Column(db_pb.String(2), nullable=False, server_default=db_pb.text("ru")) enabled_notify = db_pb.Column(db_pb.Boolean, nullable=False, server_default=db_pb.text("true")) enabled_coins_notify = db_pb.Column(db_pb.Boolean, nullable=False, server_default=db_pb.text("true")) + enabled_membership_notify = db_pb.Column(db_pb.Boolean, nullable=False, server_default=db_pb.text("true")) class PenguinIntegrations(db_pb.Model): diff --git a/bot/handlers/notification.py b/bot/handlers/notification.py index e36ba1c..1dd0ca0 100644 --- a/bot/handlers/notification.py +++ b/bot/handlers/notification.py @@ -38,6 +38,9 @@ async def notifyCoinsReceive(senderPenguin: Penguin, receiverPenguin: Penguin, c receiver = await bot.fetch_user(int(receiverId[0])) senderId = await User.select("id").where(User.penguin_id == senderPenguin.id).gino.first() sender = await bot.fetch_user(int(senderId[0])) + user = await User.get(receiver.id) + if not user.enabled_coins_notify: + return embed = Embed(color=0xB7F360, title=f"{sender.global_name} перевел(а) Вам {coins}м") if message: @@ -49,26 +52,30 @@ async def notifyCoinsReceive(senderPenguin: Penguin, receiverPenguin: Penguin, c async def notifyMembershipEnded(p: Penguin): + user = await User.query.where(User.penguin_id == p.id).gino.first() + if not user.enabled_membership_notify: + return + embed = Embed(color=0xFFD947, title=f"Подписка закончилась") embed.set_footer(text=f"Аккаунт: {p.safe_name()}", icon_url=f"https://play.cpps.app/avatar/{p.id}/cp?size=600") embed.set_image(url="https://cpps.app/NO%20MEMBERSHIP.png") - userId = await User.select("id").where(User.penguin_id == p.id).gino.first() - user = await bot.fetch_user(int(userId[0])) - await sendNotify(user, embed, view=MembershipButton()) + await sendNotify(await bot.fetch_user(user.id), embed, view=MembershipButton()) async def notifyMembershipSoonEnded(p: Penguin): + user = await User.query.where(User.penguin_id == p.id).gino.first() + if not user.enabled_membership_notify: + return + embed = Embed(color=0xFFD947, title=f"Подписка скоро закончится", description="Войдите в игру, что бы больше не получать напоминания") embed.add_field("До конца подписки", f"{p.membership_days_remain} дней", inline=False) embed.set_footer(text=f"Аккаунт: {p.safe_name()}", icon_url=f"https://play.cpps.app/avatar/{p.id}/cp?size=600") - userId = await User.select("id").where(User.penguin_id == p.id).gino.first() - user = await bot.fetch_user(int(userId[0])) - await sendNotify(user, embed, view=MembershipButton()) + await sendNotify(await bot.fetch_user(user.id), embed, view=MembershipButton()) async def sendNotify(user: disnake.User, embed, *, view=None): From 4f4df095720816636ce09bd06701da70325d7d0a Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Mon, 17 Jul 2023 15:31:36 +0500 Subject: [PATCH 20/27] Fix --- bot/handlers/notification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/handlers/notification.py b/bot/handlers/notification.py index 1dd0ca0..94dc56a 100644 --- a/bot/handlers/notification.py +++ b/bot/handlers/notification.py @@ -34,7 +34,7 @@ async def check_membership(): async def notifyCoinsReceive(senderPenguin: Penguin, receiverPenguin: Penguin, coins, message=None): receiverId = await PenguinIntegrations.select("discord_id").where( - PenguinIntegrations.penguin_id == senderPenguin.id).gino.first() + PenguinIntegrations.penguin_id == receiverPenguin.id).gino.first() receiver = await bot.fetch_user(int(receiverId[0])) senderId = await User.select("id").where(User.penguin_id == senderPenguin.id).gino.first() sender = await bot.fetch_user(int(senderId[0])) From 94d775f759b03c9c625a1a354d3d9c5768ec6df1 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Fri, 21 Jul 2023 15:09:18 +0500 Subject: [PATCH 21/27] Add command /top --- bot/cogs/commands.py | 88 ++++++++++++++++++++++++++++++++++++++++--- bot/misc/constants.py | 1 + bot/misc/utils.py | 35 +++++++++++------ 3 files changed, 108 insertions(+), 16 deletions(-) diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index bb4f3db..1f65284 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -8,10 +8,13 @@ from disnake.ext.commands import Cog, Param, slash_command from requests import Session +from bot.data import db_pb +from bot.data.clubpenguin.stamp import PenguinStamp from bot.handlers.notification import notifyCoinsReceive -from bot.misc.constants import online_url, headers, emojiCuteSad +from bot.misc.constants import online_url, headers, emojiCuteSad, emojiCoin, emojiGame, emojiStamp from bot.misc.penguin import Penguin -from bot.misc.utils import getPenguinFromInter, getPenguinOrNoneFromUserId, transferCoinsAndReturnStatus +from bot.misc.utils import getPenguinFromInter, getPenguinOrNoneFromUserId, transferCoinsAndReturnStatus, \ + getPenguinFromPenguinId class UserCommands(Cog): @@ -58,11 +61,11 @@ async def pay(self, inter: ApplicationCommandInteraction, message: str = Param(default=None, description='Сообщение получателю')): p: Penguin = await getPenguinFromInter(inter) receiverId = await Penguin.select('id').where(Penguin.username == receiver.lower()).gino.first() - r: Penguin = await Penguin.get(int(receiverId[0])) - if r is None: - return await inter.send(f"Мы не нашли указанного пингвина.", ephemeral=True) + if receiverId is None: + return await inter.send(f"Мы не нашли указанного пингвина", ephemeral=True) + r: Penguin = await Penguin.get(int(receiverId[0])) statusDict = await transferCoinsAndReturnStatus(p, r, amount) if statusDict["code"] == 400: return await inter.send(statusDict["message"], ephemeral=True) @@ -101,6 +104,81 @@ async def online(self, inter: ApplicationCommandInteraction): textMessage = f"В нашей игре сейчас `{online}` человек/а онлайн" await inter.send(textMessage) + @slash_command(name="top", description="Топ 10 игроков острова") + async def top(self, inter: ApplicationCommandInteraction, + category=Param(description="Категория топа", choices=["coins", "online", "stamps"])): + await inter.response.defer() + if category == "coins": + embed = disnake.Embed(title=f"{emojiCoin} Богачи острова", color=0x035BD1, + description="## Пингвин — монеты\n") + result = await Penguin.query \ + .where((Penguin.moderator == False) & (Penguin.permaban == False) & (Penguin.character == None)) \ + .order_by(Penguin.coins.desc()).limit(10).gino.all() + for i, penguin in enumerate(result): + embed.description += f"{i + 1}. {penguin.nickname} — {'{0:,}'.format(penguin.coins).replace(',', ' ')}\n" + view = TopCoinsButton() + + elif category == "online": + embed = disnake.Embed(title=f"{emojiGame} Самые активные на острове", color=0x035BD1, + description="## Пингвин — минуты\n") + result = await Penguin.query.where((Penguin.permaban == False) & (Penguin.character == None)) \ + .order_by(Penguin.minutes_played.desc()).limit(10).gino.all() + for i, penguin in enumerate(result): + embed.description += f"{i + 1}. {penguin.nickname} — {'{0:,}'.format(penguin.minutes_played).replace(',', ' ')}\n" + view = TopOnlineButton() + + elif category == "stamps": + embed = disnake.Embed(title=f"{emojiStamp} Лучшие сыщики марок", color=0x035BD1, + description="## Пингвин — марки\n") + penguins_ids_list = await PenguinStamp.select('penguin_id') \ + .group_by(PenguinStamp.penguin_id) \ + .order_by(db_pb.func.count(PenguinStamp.stamp_id).desc()) \ + .limit(10).gino.all() + result = [] + for penguin_id in penguins_ids_list: + p = await getPenguinFromPenguinId(int(penguin_id[0])) + result.append({"nickname": p.nickname, "stamps": len(p.stamps) + p.count_epf_awards()}) + result.sort(key=lambda x: x["stamps"], reverse=True) + for i, p in enumerate(result): + embed.description += f"{i + 1}. {p['nickname']} — {p['stamps']}\n" + view = TopStampsButton() + + else: + embed = disnake.Embed(title=f"{emojiCuteSad} произошла ошибка", color=0x035BD1) + view = None + + await inter.send(embed=embed, view=view) + + +class TopOnlineButton(disnake.ui.View): + def __init__(self): + super().__init__(timeout=None) + + @disnake.ui.button(label="Топ 50", style=disnake.ButtonStyle.link, + url="https://play.cpps.app/ru/top/?top=online") + async def top(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + ... + + +class TopCoinsButton(disnake.ui.View): + def __init__(self): + super().__init__(timeout=None) + + @disnake.ui.button(label="Топ 50", style=disnake.ButtonStyle.link, + url="https://play.cpps.app/ru/top/?top=coins") + async def top(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + ... + + +class TopStampsButton(disnake.ui.View): + def __init__(self): + super().__init__(timeout=None) + + @disnake.ui.button(label="Топ 50", style=disnake.ButtonStyle.link, + url="https://play.cpps.app/ru/top/?top=stamp") + async def top(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): + ... + def setup(bot): bot.add_cog(UserCommands(bot)) diff --git a/bot/misc/constants.py b/bot/misc/constants.py index 253576b..7e8e7cb 100644 --- a/bot/misc/constants.py +++ b/bot/misc/constants.py @@ -42,6 +42,7 @@ emojiWikiFavicon = "<:wiki:1121764147307237447>" emojiCuteSad = "<:cuteSad:794874872835735553>" emojiCoin = "<:coin:788877461588279336>" +emojiStamp = "<:stamp:1131352118788362341>" # ========== EMBEDS ========== diff --git a/bot/misc/utils.py b/bot/misc/utils.py index 673e990..30a768b 100644 --- a/bot/misc/utils.py +++ b/bot/misc/utils.py @@ -31,13 +31,7 @@ async def getPenguinFromInter(inter: ApplicationCommandInteraction, *, cache=Tru f"Мы не нашли вашего пингвина. Пожалуйста воспользуйтесь командой {loginCommand}", ephemeral=True) return - if cache and user.penguin_id in penguins_by_id: - return penguins_by_id[user.penguin_id] - - p = await Penguin.get(user.penguin_id) - await p.setup() - penguins_by_id[user.penguin_id] = p - return p + return await getPenguinFromPenguinId(user.penguin_id, cache=cache) async def getPenguinOrNoneFromUserId(user_id: int, *, cache=True) -> Penguin: @@ -59,12 +53,31 @@ async def getPenguinOrNoneFromUserId(user_id: int, *, cache=True) -> Penguin: user = await User.get(user_id) if user is None: return None - if cache and user.penguin_id in penguins_by_id: - return penguins_by_id[user.penguin_id] + return await getPenguinFromPenguinId(user.penguin_id, cache=cache) + + +async def getPenguinFromPenguinId(penguin_id: int, *, cache=True) -> Penguin: + """ + Get a penguin object from a penguin ID. + + Parameters + ---------- + penguin_id: int + The ID of the discord user to get the penguin object for. + cache : bool, optional + Whether to cache the penguin object, by default True + + Returns + ------- + Optional[Penguin] + The penguin object, or `None` if the user is not found. + """ + if cache and penguin_id in penguins_by_id: + return penguins_by_id[penguin_id] - p = await Penguin.get(user.penguin_id) + p = await Penguin.get(penguin_id) await p.setup() - penguins_by_id[user.penguin_id] = p + penguins_by_id[penguin_id] = p return p From 75f2a5b86010e3dd974ea9cc6dd3a45ff95cbfd1 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Tue, 25 Jul 2023 01:13:10 +0500 Subject: [PATCH 22/27] Add fundraising system --- bot/cogs/fundraising.py | 72 +++++++++++++++++++++++++++++++ bot/core/puffleBot.py | 15 ++++++- bot/data/pufflebot/fundraising.py | 20 +++++++++ bot/handlers/buttons.py | 13 ++++-- bot/handlers/modals.py | 20 ++++++--- 5 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 bot/cogs/fundraising.py create mode 100644 bot/data/pufflebot/fundraising.py diff --git a/bot/cogs/fundraising.py b/bot/cogs/fundraising.py new file mode 100644 index 0000000..f440367 --- /dev/null +++ b/bot/cogs/fundraising.py @@ -0,0 +1,72 @@ +import disnake +from disnake import ApplicationCommandInteraction, Embed +from disnake.ext.commands import Cog, slash_command +from loguru import logger + +from bot.data.pufflebot.fundraising import Fundraising +from bot.handlers.buttons import FundraisingButtons +from bot.handlers.censure import is_message_valid +from bot.misc.constants import guild_ids +from bot.misc.penguin import Penguin +from bot.misc.utils import getPenguinFromInter + + +class FundraisingCommands(Cog): + def __init__(self, bot): + self.bot = bot + + logger.info(f"Loaded {len(self.get_application_commands())} fundraising app commands") + + @slash_command() + async def fundraising(self, inter: ApplicationCommandInteraction): + ... + + @fundraising.sub_command(name="open", description="Начать сбор пожертвований") + async def fundraising_open(self, inter: ApplicationCommandInteraction, title: str, goal: int = None): + # TODO: notifications about the collection of goal amount. + # TODO: show a beautiful picture. + if not is_message_valid(title): + return await inter.send("Соблюдайте правила!", ephemeral=True) + + p: Penguin = await getPenguinFromInter(inter) + currentFundraising = await Fundraising.query.where(Fundraising.penguin_id == p.id).gino.first() + if currentFundraising: + try: + channel = await self.bot.fetch_channel(currentFundraising.channel_id) + message = await channel.fetch_message(currentFundraising.message_id) + return await inter.send( + f"Вы уже начали сбор пожертвований: {message.jump_url}", + ephemeral=True) + except disnake.errors.NotFound: + await currentFundraising.delete() + + embed = Embed(color=0x2B2D31, title=title) + embed.set_author(name=inter.author.name, icon_url=inter.author.avatar.url) + embed.add_field("Собрано монет", f"0{f' из {goal}' if goal else ''}") + embed.set_footer(text="Спонсоры: 0") + message: disnake.Message = await inter.channel.send(embed=embed) + + fundraising = await Fundraising.create(server_id=inter.guild_id, channel_id=inter.channel_id, + message_id=message.id, penguin_id=p.id, goal=goal) + await message.edit(view=FundraisingButtons(fundraising, message, p, 0)) + await inter.send("Сбор пожертвований начат.", ephemeral=True) + + @fundraising.sub_command(name="close", description="Закончить сбор пожертвований") + async def fundraising_close(self, inter: ApplicationCommandInteraction): + p: Penguin = await getPenguinFromInter(inter) + fundraising = await Fundraising.query.where(Fundraising.penguin_id == p.id).gino.first() + try: + if not fundraising: + return await inter.send("Сбор пожертвований ещё не был открыт", ephemeral=True) + + await fundraising.delete() + channel = await self.bot.fetch_channel(fundraising.channel_id) + message = await channel.fetch_message(fundraising.message_id) + await message.edit("Закрыт", view=None) + except disnake.NotFound: + pass + await inter.send("Успешно", ephemeral=True) + + +def setup(bot): + bot.add_cog(FundraisingCommands(bot)) diff --git a/bot/core/puffleBot.py b/bot/core/puffleBot.py index b1dc1f7..f2ccf7b 100644 --- a/bot/core/puffleBot.py +++ b/bot/core/puffleBot.py @@ -5,9 +5,11 @@ from disnake.ext.commands import InteractionBot from loguru import logger import bot.cogs -from bot.handlers.buttons import Rules +from bot.data.pufflebot.fundraising import Fundraising, FundraisingBackers +from bot.handlers.buttons import Rules, FundraisingButtons from bot.misc.constants import rules_message_id, about_message_id, rules_webhook_id from bot.handlers.select import About +from bot.misc.penguin import Penguin class PuffleBot(InteractionBot): @@ -37,4 +39,13 @@ async def on_connect(self): view.add_item(About()) self.add_view(view, message_id=about_message_id) - + for fundraising in await Fundraising.query.gino.all(): + try: + channel = await self.fetch_channel(fundraising.channel_id) + message = await channel.fetch_message(fundraising.message_id) + p = await Penguin.get(fundraising.penguin_id) + backers = len( + await FundraisingBackers.query.where(FundraisingBackers.message_id == message.id).gino.all()) + self.add_view(FundraisingButtons(fundraising, message, p, backers), message_id=message.id) + except disnake.NotFound: + await fundraising.delete() diff --git a/bot/data/pufflebot/fundraising.py b/bot/data/pufflebot/fundraising.py new file mode 100644 index 0000000..dc34bf1 --- /dev/null +++ b/bot/data/pufflebot/fundraising.py @@ -0,0 +1,20 @@ +from bot.data import db_pb + + +class Fundraising(db_pb.Model): + __tablename__ = 'fundraising' + + server_id = db_pb.Column(db_pb.BigInteger, primary_key=True, nullable=False) + channel_id = db_pb.Column(db_pb.BigInteger, primary_key=True, nullable=False) + message_id = db_pb.Column(db_pb.BigInteger, primary_key=True, nullable=False) + penguin_id = db_pb.Column(db_pb.BigInteger, nullable=False) + raised = db_pb.Column(db_pb.Integer, nullable=False, server_default=db_pb.text("0")) + goal = db_pb.Column(db_pb.Integer) + + +class FundraisingBackers(db_pb.Model): + __tablename__ = 'fundraising_backers' + + message_id = db_pb.Column(db_pb.BigInteger, primary_key=True, nullable=False) + receiver_penguin_id = db_pb.Column(db_pb.BigInteger, nullable=False) + backer_penguin_id = db_pb.Column(db_pb.BigInteger, primary_key=True, nullable=False) diff --git a/bot/handlers/buttons.py b/bot/handlers/buttons.py index 9ca9487..b02a064 100644 --- a/bot/handlers/buttons.py +++ b/bot/handlers/buttons.py @@ -2,6 +2,7 @@ from disnake import Embed from bot.data.pufflebot.fundraising import Fundraising, FundraisingBackers +from bot.handlers.modals import FundraisingModal from bot.handlers.notification import notifyCoinsReceive from bot.misc.constants import embedRuleImageRu, embedRuleRu, embedRuleImageEn, embedRuleEn, embedRolesRu, \ embedRolesEn, enFullRulesLink, ruFullRulesLink @@ -24,7 +25,6 @@ async def on_timeout(self): class FundraisingButtons(disnake.ui.View): - # TODO: Make the "other amount" button def __init__(self, fundraising: Fundraising, message: disnake.Message, receiver: Penguin, backers: int): super().__init__(timeout=None) self.fundraising = fundraising @@ -42,8 +42,9 @@ async def donate(self, inter: disnake.CommandInteraction, coins: int): return self.raised += int(coins) - embed = Embed(color=0x2B2D31, title=self.message.embeds[0].title) - embed.add_field("Собрано монет", f"{self.raised}{f' из {self.goal}' if self.goal else ''}") + embed = self.message.embeds[0] + embed.add_field(embed.fields[0].name, f"{self.raised}{f' из {self.goal}' if self.goal else ''}") + embed.remove_field(0) embed.set_footer(text=f"Спонсоры: {self.backers + 1}") if not await FundraisingBackers.get([self.message.id, p.id]): @@ -72,6 +73,12 @@ async def coins500Button(self, button: disnake.ui.Button, inter: disnake.Command async def coins1000Button(self, button: disnake.ui.Button, inter: disnake.CommandInteraction): await self.donate(inter, int(button.label)) + @disnake.ui.button(label="Другая сумма", style=disnake.ButtonStyle.gray, + custom_id="other") + async def otherSumButton(self, _, inter: disnake.CommandInteraction): + await inter.response.send_modal( + modal=FundraisingModal(self.donate, f"Сбор монет для {self.receiver.safe_name()}")) + class Rules(disnake.ui.View): def __init__(self, massage): diff --git a/bot/handlers/modals.py b/bot/handlers/modals.py index fbfaf2e..de6a0f0 100644 --- a/bot/handlers/modals.py +++ b/bot/handlers/modals.py @@ -1,16 +1,22 @@ import disnake -class LoginModal(disnake.ui.Modal): - def __init__(self, function): +class FundraisingModal(disnake.ui.Modal): + def __init__(self, function, title): self.function = function + self.title = title components = [ - disnake.ui.TextInput(label="Одноразовый код аутентификации", required=True, - placeholder="Введите код, который вы получили в открытке", custom_id="authCodeInput")] + disnake.ui.TextInput(label="Перевести монеты", required=True, + placeholder="Введите сумму", custom_id="payInput")] - super().__init__(title="Привяжите свои аккаунты", components=components, custom_id="login") + super().__init__(title=self.title, components=components, custom_id="fundraising") async def callback(self, inter: disnake.ModalInteraction): - authCode = inter.text_values["authCodeInput"] - await self.function(inter, authCode) + coins = inter.text_values["payInput"] + try: + coins = int(coins) + except ValueError: + return await inter.send("Пожалуйста введите правильное число монет", ephemeral=True) + + await self.function(inter, int(coins)) From 65f1f6a679100e179d90aa66a14a76b36b52a440 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev Date: Tue, 25 Jul 2023 01:33:08 +0500 Subject: [PATCH 23/27] Update coins notify --- bot/handlers/notification.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/handlers/notification.py b/bot/handlers/notification.py index 94dc56a..94397f9 100644 --- a/bot/handlers/notification.py +++ b/bot/handlers/notification.py @@ -42,10 +42,11 @@ async def notifyCoinsReceive(senderPenguin: Penguin, receiverPenguin: Penguin, c if not user.enabled_coins_notify: return - embed = Embed(color=0xB7F360, title=f"{sender.global_name} перевел(а) Вам {coins}м") + embed = Embed(color=0xB7F360, title=f"Пингвин под ником «{senderPenguin.safe_name()}» перевел(а) Вам {coins}м") if message: embed.add_field("Сообщение", message, inline=False) embed.add_field("Баланс", f"{receiverPenguin.coins} {emojiCoin}", inline=False) + embed.add_field("Пользователь", f"{sender.mention}", inline=False) embed.set_footer(text=f"Ваш аккаунт: {receiverPenguin.safe_name()}", icon_url=f"https://play.cpps.app/avatar/{receiverPenguin.id}/cp?size=600") await sendNotify(receiver, embed) From 9baf4f9b627e1c8fb65ed4b83fd6b04094305c5d Mon Sep 17 00:00:00 2001 From: ilyash Date: Fri, 28 Jul 2023 19:33:12 +0300 Subject: [PATCH 24/27] some shit --- .gitignore | 1 + bot/cogs/commands.py | 4 ++-- bot/cogs/fundraising.py | 2 +- bot/handlers/buttons.py | 3 ++- bot/misc/utils.py | 7 +++---- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 6308242..d1fe93d 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ pip-delete-this-directory.txt .idea/ logs/ +/reboot.sh diff --git a/bot/cogs/commands.py b/bot/cogs/commands.py index 1f65284..2eeb96b 100644 --- a/bot/cogs/commands.py +++ b/bot/cogs/commands.py @@ -115,7 +115,7 @@ async def top(self, inter: ApplicationCommandInteraction, .where((Penguin.moderator == False) & (Penguin.permaban == False) & (Penguin.character == None)) \ .order_by(Penguin.coins.desc()).limit(10).gino.all() for i, penguin in enumerate(result): - embed.description += f"{i + 1}. {penguin.nickname} — {'{0:,}'.format(penguin.coins).replace(',', ' ')}\n" + embed.description += f"{i + 1}. {penguin.nickname} — {f'{penguin.coins:,}'.replace(',', ' ')}\n" view = TopCoinsButton() elif category == "online": @@ -124,7 +124,7 @@ async def top(self, inter: ApplicationCommandInteraction, result = await Penguin.query.where((Penguin.permaban == False) & (Penguin.character == None)) \ .order_by(Penguin.minutes_played.desc()).limit(10).gino.all() for i, penguin in enumerate(result): - embed.description += f"{i + 1}. {penguin.nickname} — {'{0:,}'.format(penguin.minutes_played).replace(',', ' ')}\n" + embed.description += f"{i + 1}. {penguin.nickname} — {f'{penguin.minutes_played:,}'.replace(',', ' ')}\n" view = TopOnlineButton() elif category == "stamps": diff --git a/bot/cogs/fundraising.py b/bot/cogs/fundraising.py index f440367..16490ea 100644 --- a/bot/cogs/fundraising.py +++ b/bot/cogs/fundraising.py @@ -42,7 +42,7 @@ async def fundraising_open(self, inter: ApplicationCommandInteraction, title: st embed = Embed(color=0x2B2D31, title=title) embed.set_author(name=inter.author.name, icon_url=inter.author.avatar.url) - embed.add_field("Собрано монет", f"0{f' из {goal}' if goal else ''}") + embed.add_field("Собрано монет", f"0{f' из {goal:,}' if goal else ''}".replace(',', ' ')) embed.set_footer(text="Спонсоры: 0") message: disnake.Message = await inter.channel.send(embed=embed) diff --git a/bot/handlers/buttons.py b/bot/handlers/buttons.py index b02a064..340df0a 100644 --- a/bot/handlers/buttons.py +++ b/bot/handlers/buttons.py @@ -43,7 +43,8 @@ async def donate(self, inter: disnake.CommandInteraction, coins: int): self.raised += int(coins) embed = self.message.embeds[0] - embed.add_field(embed.fields[0].name, f"{self.raised}{f' из {self.goal}' if self.goal else ''}") + embed.add_field(embed.fields[0].name, + f"{self.raised:,}{f' из {self.goal:,}' if self.goal else ''}".replace(',', ' ')) embed.remove_field(0) embed.set_footer(text=f"Спонсоры: {self.backers + 1}") diff --git a/bot/misc/utils.py b/bot/misc/utils.py index 30a768b..c47ea0e 100644 --- a/bot/misc/utils.py +++ b/bot/misc/utils.py @@ -27,10 +27,9 @@ async def getPenguinFromInter(inter: ApplicationCommandInteraction, *, cache=Tru """ user = await User.get(inter.user.id) if user is None: - await inter.send( + return await inter.send( f"Мы не нашли вашего пингвина. Пожалуйста воспользуйтесь командой {loginCommand}", ephemeral=True) - return return await getPenguinFromPenguinId(user.penguin_id, cache=cache) @@ -72,8 +71,8 @@ async def getPenguinFromPenguinId(penguin_id: int, *, cache=True) -> Penguin: Optional[Penguin] The penguin object, or `None` if the user is not found. """ - if cache and penguin_id in penguins_by_id: - return penguins_by_id[penguin_id] + # if cache and penguin_id in penguins_by_id: + # return penguins_by_id[penguin_id] p = await Penguin.get(penguin_id) await p.setup() From bf8894ab4cb3c07fcd5c38aef4dc873db908700a Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev <119329448+ilyash0@users.noreply.github.com> Date: Wed, 2 Aug 2023 20:10:27 +0500 Subject: [PATCH 25/27] Update README.md --- README.md | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 3258a3a..b56fd85 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,30 @@

- Puffle Bot for Discord + Puffle Bot

#
-[![Github All Releases](https://img.shields.io/github/v/release/ilyash0/discord-bot)](https://github.com/ilyash0/discord-bot/releases) +[![Github All Releases](https://img.shields.io/github/v/release/ilyash0/puffle-bot)](https://github.com/ilyash0/puffle-bot/releases) [![Custom badge](https://img.shields.io/badge/-add%20bot%20to%20server-5865F2)](https://discord.com/api/oauth2/authorize?client_id=875078308688179200&permissions=412317240384&scope=applications.commands%20bot) [![Discord](https://img.shields.io/discord/755445822920982548?logo=discord&logoColor=white)](https://discord.gg/ntZUXsWZaM)
-

A Discord bot with Club Penguin Private Server (specifically CPPS.app) written in Python 3.

+

A public Discord bot for interaction with online game "CPPS.APP".

- [Place for Terms of Service]() - [Place for Documentation]() ## Features -- **Commands**: Use a slash-commands. -- **Database**: Combines with a database from CPPS. -- **Integration**: link CP and Discord accounts. -- **Pay**: Transfer coins from one penguin to another - -## Installation - -Puffle bot can be run as a normal Python program under any operating system. Do not forget to substitute your data, such as a ``(without <>) - -```shell -$ git clone https://github.com/ilyash0/Puffle-bot -$ cd Puffle-bot -$ pip install -r requirements.txt -$ python /Puffle-bot/bootstrap.py -du -dp -dn -p -t & -``` +- **Integration**: link CP and Discord accounts; +- **Commands**: Use a slash-commands for: + - Transfer coins; + - View penguin profile; + - View players online; + - And more. +- **Liked roles**: Create you own linked roles with Puffle Bot. ## Screenshots From ea65a06bc6174f182f72a589546a3d7327b7e581 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev <119329448+ilyash0@users.noreply.github.com> Date: Wed, 2 Aug 2023 20:17:28 +0500 Subject: [PATCH 26/27] Update README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b56fd85..40a5420 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ Puffle Bot -#
@@ -20,10 +19,10 @@ - **Integration**: link CP and Discord accounts; - **Commands**: Use a slash-commands for: - - Transfer coins; - - View penguin profile; - - View players online; - - And more. + - Transfer coins; + - View penguin profile; + - View players online; + - And more. - **Liked roles**: Create you own linked roles with Puffle Bot. ## Screenshots From 5ada4f306865ade5a71b9c79d0e1004e1b9e5878 Mon Sep 17 00:00:00 2001 From: Ilya Shmyrev <119329448+ilyash0@users.noreply.github.com> Date: Wed, 2 Aug 2023 21:07:27 +0500 Subject: [PATCH 27/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 40a5420..aa9ab09 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Github All Releases](https://img.shields.io/github/v/release/ilyash0/puffle-bot)](https://github.com/ilyash0/puffle-bot/releases) [![Custom badge](https://img.shields.io/badge/-add%20bot%20to%20server-5865F2)](https://discord.com/api/oauth2/authorize?client_id=875078308688179200&permissions=412317240384&scope=applications.commands%20bot) -[![Discord](https://img.shields.io/discord/755445822920982548?logo=discord&logoColor=white)](https://discord.gg/ntZUXsWZaM) +[![Discord](https://img.shields.io/discord/755445822920982548?logo=discord&logoColor=white&label=discord)](https://discord.gg/ntZUXsWZaM)

A public Discord bot for interaction with online game "CPPS.APP".