diff --git a/requirements.txt b/requirements.txt index 2cb93bcd..822dedbe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,44 +1,9 @@ -aiohttp==3.5.4 -async-timeout==3.0.1 -attrs==19.3.0 -bcrypt==3.1.7 -certifi==2019.9.11 -cffi==1.13.01 -chardet==3.0.4 -click==7.0 -cryptography==2.8 -dateparser==0.7.2 -dice==2.4.2 -dnspython==1.15.0 -dnspython3==1.15.0 -entrypoints==0.3 -ffmpeg-python==0.2.0 -future==0.18.1 -idna==2.8 -keyring==19.2.0 -markdown2==2.3.8 -markupsafe==1.1.1 -mcstatus==2.2.1 -multidict==4.5.2 -psycopg2-binary==2.8.3 -pycparser==2.19 -python-dateutil==2.8.0 -python-telegram-bot==12.2.0 -pytz==2019.3 -pywin32-ctypes==0.2.0 -regex==2019.8.19 -royalherald==5.1b2 -sentry-sdk==0.13.0 -six==1.12.0 -sortedcontainers==2.1.0 -sqlalchemy==1.3.10 -tornado==6.0.3 -tzlocal==2.0.0 -urllib3==1.25.6 -websockets==8.1 -yarl==1.3.0 -youtube-dl==2019.10.16 -riotwatcher==2.7.1 -# discord.py is missing as we currently use the git version and we ignore the websockets<7.0 requirement -uvicorn==0.10.3 -starlette==0.12.13 +dateparser +youtube_dl +ffmpeg_python +urllib3 +sqlalchemy +starlette +keyring +click +royalherald diff --git a/royalnet/commands/commandinterface.py b/royalnet/commands/commandinterface.py index cccbabb2..5050d77a 100644 --- a/royalnet/commands/commandinterface.py +++ b/royalnet/commands/commandinterface.py @@ -1,6 +1,5 @@ import typing import asyncio -import royalherald as rh from .commanderrors import UnsupportedError if typing.TYPE_CHECKING: from .command import Command diff --git a/royalnet/packs/__init__.py b/royalnet/packs/__init__.py index aa2d62f6..0b86d1d7 100644 --- a/royalnet/packs/__init__.py +++ b/royalnet/packs/__init__.py @@ -1,9 +1,5 @@ from . import common -from . import royal -from . import rpg __all__ = [ "common", - "royal", - "rpg", ] diff --git a/royalnet/packs/royal/__init__.py b/royalnet/packs/royal/__init__.py deleted file mode 100644 index feb329ab..00000000 --- a/royalnet/packs/royal/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# This is a template Pack __init__. You can use this without changing anything in other packages too! - -from . import commands, tables, stars -from .commands import available_commands -from .tables import available_tables -from .stars import available_page_stars, available_exception_stars - -__all__ = [ - "commands", - "tables", - "stars", - "available_commands", - "available_tables", - "available_page_stars", - "available_exception_stars", -] diff --git a/royalnet/packs/royal/commands/__init__.py b/royalnet/packs/royal/commands/__init__.py deleted file mode 100644 index 01eeda13..00000000 --- a/royalnet/packs/royal/commands/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -# Imports go here! -from .ciaoruozi import CiaoruoziCommand -from .color import ColorCommand -from .cv import CvCommand -from .diario import DiarioCommand -from .rage import RageCommand -from .reminder import ReminderCommand -from .ship import ShipCommand -from .smecds import SmecdsCommand -from .videochannel import VideochannelCommand -from .trivia import TriviaCommand -from .matchmaking import MatchmakingCommand -from .pause import PauseCommand -from .play import PlayCommand -from .playmode import PlaymodeCommand -from .queue import QueueCommand -from .skip import SkipCommand -from .summon import SummonCommand -from .youtube import YoutubeCommand -from .soundcloud import SoundcloudCommand -from .zawarudo import ZawarudoCommand -from .emojify import EmojifyCommand -from .leagueoflegends import LeagueoflegendsCommand -from .diarioquote import DiarioquoteCommand -from .mp3 import Mp3Command -from .peertube import PeertubeCommand - -# Enter the commands of your Pack here! -available_commands = [ - CiaoruoziCommand, - ColorCommand, - CvCommand, - DiarioCommand, - RageCommand, - ReminderCommand, - ShipCommand, - SmecdsCommand, - VideochannelCommand, - TriviaCommand, - MatchmakingCommand, - PauseCommand, - PlayCommand, - PlaymodeCommand, - QueueCommand, - SkipCommand, - SummonCommand, - YoutubeCommand, - SoundcloudCommand, - ZawarudoCommand, - EmojifyCommand, - LeagueoflegendsCommand, - DiarioquoteCommand, - Mp3Command, - PeertubeCommand, -] - -# Don't change this, it should automatically generate __all__ -__all__ = [command.__name__ for command in available_commands] diff --git a/royalnet/packs/royal/commands/ciaoruozi.py b/royalnet/packs/royal/commands/ciaoruozi.py deleted file mode 100644 index 7deed002..00000000 --- a/royalnet/packs/royal/commands/ciaoruozi.py +++ /dev/null @@ -1,22 +0,0 @@ -import typing -import telegram -from royalnet.commands import * - - -class CiaoruoziCommand(Command): - name: str = "ciaoruozi" - - description: str = "Saluta Ruozi, un leggendario essere che una volta era in User Games." - - syntax: str = "" - - tables: typing.Set = set() - - async def run(self, args: CommandArgs, data: CommandData) -> None: - if self.interface.name == "telegram": - update: telegram.Update = data.update - user: telegram.User = update.effective_user - if user.id == 112437036: - await data.reply("๐Ÿ‘‹ Ciao me!") - return - await data.reply("๐Ÿ‘‹ Ciao Ruozi!") diff --git a/royalnet/packs/royal/commands/color.py b/royalnet/packs/royal/commands/color.py deleted file mode 100644 index a7432f45..00000000 --- a/royalnet/packs/royal/commands/color.py +++ /dev/null @@ -1,12 +0,0 @@ -from royalnet.commands import * - - -class ColorCommand(Command): - name: str = "color" - - description: str = "Invia un colore in chat...?" - - async def run(self, args: CommandArgs, data: CommandData) -> None: - await data.reply(""" - [i]I am sorry, unknown error occured during working with your request, Admin were notified[/i] - """) diff --git a/royalnet/packs/royal/commands/cv.py b/royalnet/packs/royal/commands/cv.py deleted file mode 100644 index bf9f2b23..00000000 --- a/royalnet/packs/royal/commands/cv.py +++ /dev/null @@ -1,130 +0,0 @@ -import discord -import typing -from royalnet.commands import * -from royalnet.utils import andformat -from royalnet.bots import DiscordBot - - -class CvCommand(Command): - name: str = "cv" - - description: str = "Elenca le persone attualmente connesse alla chat vocale." - - syntax: str = "[guildname] [all]" - - @staticmethod - async def _legacy_cv_handler(bot: DiscordBot, guild_name: typing.Optional[str], everyone: bool): - # Find the matching guild - if guild_name: - guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(guild_name) - else: - guilds = bot.client.guilds - if len(guilds) == 0: - raise CommandError("No guilds with the specified name found.") - if len(guilds) > 1: - raise CommandError("Multiple guilds with the specified name found.") - guild = list(bot.client.guilds)[0] - # Edit the message, sorted by channel - discord_members = list(guild.members) - channels = {0: None} - members_in_channels = {0: []} - message = "" - # Find all the channels - for member in discord_members: - if member.voice is not None: - channel = members_in_channels.get(member.voice.channel.id) - if channel is None: - members_in_channels[member.voice.channel.id] = list() - channel = members_in_channels[member.voice.channel.id] - channels[member.voice.channel.id] = member.voice.channel - channel.append(member) - else: - members_in_channels[0].append(member) - # Edit the message, sorted by channel - for channel in sorted(channels, key=lambda c: -c): - members_in_channels[channel].sort(key=lambda x: x.nick if x.nick is not None else x.name) - if channel == 0 and len(members_in_channels[0]) > 0: - message += "[b]Non in chat vocale:[/b]\n" - else: - message += f"[b]In #{channels[channel].name}:[/b]\n" - for member in members_in_channels[channel]: - member: typing.Union[discord.User, discord.Member] - # Ignore not-connected non-notable members - if not everyone and channel == 0 and len(member.roles) < 2: - continue - # Ignore offline members - if member.status == discord.Status.offline and member.voice is None: - continue - # Online status emoji - if member.bot: - message += "๐Ÿค– " - elif member.status == discord.Status.online: - message += "๐Ÿ”ต " - elif member.status == discord.Status.idle: - message += "โšซ " - elif member.status == discord.Status.dnd: - message += "๐Ÿ”ด " - elif member.status == discord.Status.offline: - message += "โšช " - # Voice - if channel != 0: - # Voice status - if member.voice.afk: - message += "๐Ÿ’ค " - elif member.voice.self_deaf or member.voice.deaf: - message += "๐Ÿ”‡ " - elif member.voice.self_mute or member.voice.mute: - message += "๐Ÿ”ˆ " - elif member.voice.self_video or member.voice.self_stream: - message += "๐Ÿ–ฅ " - else: - message += "๐Ÿ”Š " - # Nickname - # if member.nick is not None: - # message += f"[i]{member.nick}[/i]" - # else: - message += member.name - # Game or stream - if member.activity is not None: - if member.activity.type == discord.ActivityType.playing: - message += f" | ๐ŸŽฎ {member.activity.name}" - # Rich presence - try: - if member.activity.state is not None: - message += f" ({member.activity.state}" \ - f" | {member.activity.details})" - except AttributeError: - pass - elif member.activity.type == discord.ActivityType.streaming: - message += f" | ๐Ÿ“ก {member.activity.url}" - elif member.activity.type == discord.ActivityType.listening: - if isinstance(member.activity, discord.Spotify): - if member.activity.title == member.activity.album: - message += f" | ๐ŸŽง {member.activity.title} ({andformat(member.activity.artists, final=' e ')})" - else: - message += f" | ๐ŸŽง {member.activity.title} ({member.activity.album} | {andformat(member.activity.artists, final=' e ')})" - else: - message += f" | ๐ŸŽง {member.activity.name}" - elif member.activity.type == discord.ActivityType.watching: - message += f" | ๐Ÿ“บ {member.activity.name}" - else: - message += f" | โ“ {member.activity.state}" - message += "\n" - message += "\n" - return {"response": message} - - _event_name = "_legacy_cv" - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - if interface.name == "discord": - interface.register_herald_action(self._event_name, self._legacy_cv_handler) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - # noinspection PyTypeChecker - guild_name, everyone = args.match(r"(?:\[(.+)])?\s*(\S+)?\s*") - response = await self.interface.call_herald_action("discord", self._event_name, { - "guild_name": guild_name, - "everyone": everyone - }) - await data.reply(response["response"]) diff --git a/royalnet/packs/royal/commands/diario.py b/royalnet/packs/royal/commands/diario.py deleted file mode 100644 index fecc0b09..00000000 --- a/royalnet/packs/royal/commands/diario.py +++ /dev/null @@ -1,206 +0,0 @@ -import typing -import re -import datetime -import telegram -import aiohttp -from royalnet.commands import * -from royalnet.utils import asyncify -from ..tables import User, Diario, Alias - - -async def to_imgur(imgur_api_key, photosizes: typing.List[telegram.PhotoSize], caption="") -> str: - # Select the largest photo - largest_photo = sorted(photosizes, key=lambda p: p.width * p.height)[-1] - # Get the photo url - photo_file: telegram.File = await asyncify(largest_photo.get_file) - # Forward the url to imgur, as an upload - async with aiohttp.request("post", "https://api.imgur.com/3/upload", data={ - "image": photo_file.file_path, - "type": "URL", - "title": "Diario image", - "description": caption - }, headers={ - "Authorization": f"Client-ID {imgur_api_key}" - }) as request: - response = await request.json() - if not response["success"]: - raise CommandError("Imgur returned an error in the image upload.") - return response["data"]["link"] - - -class DiarioCommand(Command): - name: str = "diario" - - description: str = "Aggiungi una citazione al Diario." - - syntax = "[!] \"{testo}\" --[autore], [contesto]" - - tables = {User, Diario, Alias} - - async def run(self, args: CommandArgs, data: CommandData) -> None: - if self.interface.name == "telegram": - update: telegram.Update = data.update - message: telegram.Message = update.message - reply: telegram.Message = message.reply_to_message - creator = await data.get_author() - # noinspection PyUnusedLocal - quoted: typing.Optional[str] - # noinspection PyUnusedLocal - text: typing.Optional[str] - # noinspection PyUnusedLocal - context: typing.Optional[str] - # noinspection PyUnusedLocal - timestamp: datetime.datetime - # noinspection PyUnusedLocal - media_url: typing.Optional[str] - # noinspection PyUnusedLocal - spoiler: bool - if creator is None: - await data.reply("โš ๏ธ Devi essere registrato a Royalnet per usare questo comando!") - return - if reply is not None: - # Get the message text - text = reply.text - # Check if there's an image associated with the reply - photosizes: typing.Optional[typing.List[telegram.PhotoSize]] = reply.photo - if photosizes: - # Text is a caption - text = reply.caption - media_url = await to_imgur(self.interface.bot.get_secret("imgur"), - photosizes, text if text is not None else "") - else: - media_url = None - # Ensure there is a text or an image - if not (text or media_url): - raise InvalidInputError("Missing text.") - # Find the Royalnet account associated with the sender - quoted_tg = await asyncify(data.session.query(self.interface.alchemy.Telegram) - .filter_by(tg_id=reply.from_user.id) - .one_or_none) - quoted_account = quoted_tg.royal if quoted_tg is not None else None - # Find the quoted name to assign - quoted_user: telegram.User = reply.from_user - quoted = quoted_user.full_name - # Get the timestamp - timestamp = reply.date - # Set the other properties - spoiler = False - context = None - else: - # Get the current timestamp - timestamp = datetime.datetime.now() - # Get the message text - raw_text = " ".join(args) - # Check if there's an image associated with the reply - photosizes: typing.Optional[typing.List[telegram.PhotoSize]] = message.photo - if photosizes: - media_url = await to_imgur(self.interface.bot.get_secret("imgur"), - photosizes, raw_text if raw_text is not None else "") - else: - media_url = None - # Parse the text, if it exists - if raw_text: - # Pass the sentence through the diario regex - match = re.match( - r'(!)? *["ยซโ€˜โ€œโ€›โ€Ÿโ›โใ€๏ผ‚`]([^"]+)["ยปโ€™โ€โœโžใ€ž๏ผ‚`] *(?:(?:-{1,2}|โ€”) *([^,]+))?(?:, *([^ ].*))?', - raw_text) - # Find the corresponding matches - if match is not None: - spoiler = bool(match.group(1)) - text = match.group(2) - quoted = match.group(3) - context = match.group(4) - # Otherwise, consider everything part of the text - else: - spoiler = False - text = raw_text - quoted = None - context = None - # Ensure there's a quoted - if not quoted: - quoted = None - if not context: - context = None - # Find if there's a Royalnet account associated with the quoted name - if quoted is not None: - quoted_alias = await asyncify( - data.session.query(self.interface.alchemy.Alias) - .filter_by(alias=quoted.lower()).one_or_none) - else: - quoted_alias = None - quoted_account = quoted_alias.royal if quoted_alias is not None else None - else: - text = None - quoted = None - quoted_account = None - spoiler = False - context = None - # Ensure there is a text or an image - if not (text or media_url): - raise InvalidInputError("Missing text.") - # Create the diario quote - diario = self.interface.alchemy.Diario(creator=creator, - quoted_account=quoted_account, - quoted=quoted, - text=text, - context=context, - timestamp=timestamp, - media_url=media_url, - spoiler=spoiler) - data.session.add(diario) - await asyncify(data.session.commit) - await data.reply(f"โœ… {str(diario)}") - else: - # Find the creator of the quotes - creator = await data.get_author(error_if_none=True) - # Recreate the full sentence - raw_text = " ".join(args) - # Pass the sentence through the diario regex - match = re.match(r'(!)? *["ยซโ€˜โ€œโ€›โ€Ÿโ›โใ€๏ผ‚`]([^"]+)["ยปโ€™โ€โœโžใ€ž๏ผ‚`] *(?:(?:-{1,2}|โ€”) *([^,]+))?(?:, *([^ ].*))?', - raw_text) - # Find the corresponding matches - if match is not None: - spoiler = bool(match.group(1)) - text = match.group(2) - quoted = match.group(3) - context = match.group(4) - # Otherwise, consider everything part of the text - else: - spoiler = False - text = raw_text - quoted = None - context = None - timestamp = datetime.datetime.now() - # Ensure there is some text - if not text: - raise InvalidInputError("Missing text.") - # Or a quoted - if not quoted: - quoted = None - if not context: - context = None - # Find if there's a Royalnet account associated with the quoted name - if quoted is not None: - quoted_alias = await asyncify( - data.session.query(self.interface.alchemy.Alias) - .filter_by(alias=quoted.lower()) - .one_or_none) - else: - quoted_alias = None - quoted_account = quoted_alias.royal if quoted_alias is not None else None - if quoted_alias is not None and quoted_account is None: - await data.reply("โš ๏ธ Il nome dell'autore รจ ambiguo, quindi la riga non รจ stata aggiunta.\n" - "Per piacere, ripeti il comando con un nome piรน specifico!") - return - # Create the diario quote - diario = self.interface.alchemy.Diario(creator=creator, - quoted_account=quoted_account, - quoted=quoted, - text=text, - context=context, - timestamp=timestamp, - media_url=None, - spoiler=spoiler) - data.session.add(diario) - await asyncify(data.session.commit) - await data.reply(f"โœ… {str(diario)}") diff --git a/royalnet/packs/royal/commands/diarioquote.py b/royalnet/packs/royal/commands/diarioquote.py deleted file mode 100644 index adaf74f1..00000000 --- a/royalnet/packs/royal/commands/diarioquote.py +++ /dev/null @@ -1,25 +0,0 @@ -from royalnet.commands import * -from royalnet.utils import * -from ..tables import Diario - - -class DiarioquoteCommand(Command): - name: str = "diarioquote" - - description: str = "Cita una riga del diario." - - aliases = ["dq", "quote", "dquote"] - - syntax = "{id}" - - tables = {Diario} - - async def run(self, args: CommandArgs, data: CommandData) -> None: - try: - entry_id = int(args[0].lstrip("#")) - except ValueError: - raise CommandError("L'id che hai specificato non รจ valido.") - entry: Diario = await asyncify(data.session.query(self.alchemy.Diario).get, entry_id) - if entry is None: - raise CommandError("Nessuna riga con quell'id trovata.") - await data.reply(str(entry)) diff --git a/royalnet/packs/royal/commands/emojify.py b/royalnet/packs/royal/commands/emojify.py deleted file mode 100644 index acd7177e..00000000 --- a/royalnet/packs/royal/commands/emojify.py +++ /dev/null @@ -1,73 +0,0 @@ -import random -from royalnet.commands import * - - -class EmojifyCommand(Command): - name: str = "emojify" - - description: str = "Converti un messaggio in emoji." - - syntax = "{messaggio}" - - _emojis = { - "abcd": ["๐Ÿ”ก", "๐Ÿ” "], - "back": ["๐Ÿ”™"], - "cool": ["๐Ÿ†’"], - "free": ["๐Ÿ†“"], - "abc": ["๐Ÿ”ค"], - "atm": ["๐Ÿง"], - "new": ["๐Ÿ†•"], - "sos": ["๐Ÿ†˜"], - "top": ["๐Ÿ”"], - "zzz": ["๐Ÿ’ค"], - "end": ["๐Ÿ”š"], - "ab": ["๐Ÿ†Ž"], - "cl": ["๐Ÿ†‘"], - "id": ["๐Ÿ†”"], - "ng": ["๐Ÿ†–"], - "no": ["โ™‘๏ธ"], - "ok": ["๐Ÿ†—"], - "on": ["๐Ÿ”›"], - "sy": ["๐Ÿ’ฑ"], - "tm": ["โ„ข๏ธ"], - "wc": ["๐Ÿšพ"], - "up": ["๐Ÿ†™"], - "a": ["๐Ÿ…ฐ๏ธ"], - "b": ["๐Ÿ…ฑ๏ธ"], - "c": ["โ˜ช๏ธ", "ยฉ", "๐Ÿฅ"], - "d": ["๐Ÿ‡ฉ"], - "e": ["๐Ÿ“ง", "๐Ÿ’ถ"], - "f": ["๐ŸŽ"], - "g": ["๐Ÿ‡ฌ"], - "h": ["๐Ÿจ", "๐Ÿฉ", "๐Ÿ‹โ€โ™€", "๐Ÿ‹โ€โ™‚"], - "i": ["โ„น๏ธ", "โ™Š๏ธ", "๐Ÿ••"], - "j": ["โคด๏ธ"], - "k": ["๐ŸŽ‹", "๐Ÿฆ…", "๐Ÿ’ƒ"], - "l": ["๐Ÿ›ด", "๐Ÿ•’"], - "m": ["โ™๏ธ", "โ“‚๏ธ", "ใ€ฝ๏ธ"], - "n": ["๐Ÿ“ˆ"], - "o": ["โญ•๏ธ", "๐Ÿ…พ๏ธ", "๐Ÿ“ฏ", "๐ŸŒ", "๐ŸŒš", "๐ŸŒ•", "๐Ÿฅฏ", "๐Ÿ™†โ€โ™€", "๐Ÿ™†โ€โ™‚"], - "p": ["๐Ÿ…ฟ๏ธ"], - "q": ["๐Ÿ”", "๐Ÿ€"], - "r": ["ยฎ"], - "s": ["๐Ÿ’ฐ", "๐Ÿ’ต", "๐Ÿ’ธ", "๐Ÿ’ฒ"], - "t": ["โœ๏ธ", "โฌ†๏ธ", "โ˜ฆ๏ธ"], - "u": ["โ›Ž", "โš“๏ธ", "๐Ÿ‰", "๐ŸŒ™", "๐Ÿ‹"], - "v": ["โœ…", "๐Ÿ”ฝ", "โ˜‘๏ธ", "โœ”๏ธ"], - "w": ["๐Ÿคทโ€โ™€", "๐Ÿคทโ€โ™‚", "๐Ÿคพโ€โ™€", "๐Ÿคพโ€โ™‚", "๐Ÿคฝโ€โ™€", "๐Ÿคฝโ€โ™‚"], - "x": ["๐Ÿ™…โ€โ™€", "๐Ÿ™…โ€โ™‚", "โŒ", "โŽ"], - "y": ["๐Ÿ’ด"], - "z": ["โšก๏ธ"] - } - - @classmethod - def _emojify(cls, string: str): - new_string = string.lower() - for key in cls._emojis: - selected_emoji = random.sample(cls._emojis[key], 1)[0] - new_string = new_string.replace(key, selected_emoji) - return new_string - - async def run(self, args: CommandArgs, data: CommandData) -> None: - string = args.joined(require_at_least=1) - await data.reply(self._emojify(string)) diff --git a/royalnet/packs/royal/commands/leagueoflegends.py b/royalnet/packs/royal/commands/leagueoflegends.py deleted file mode 100644 index 227afa53..00000000 --- a/royalnet/packs/royal/commands/leagueoflegends.py +++ /dev/null @@ -1,224 +0,0 @@ -import typing -import riotwatcher -import logging -import asyncio -import sentry_sdk -from royalnet.commands import * -from royalnet.utils import * -from ..tables import LeagueOfLegends -from ..utils import LeagueLeague - -log = logging.getLogger(__name__) - - -class LeagueoflegendsCommand(Command): - name: str = "leagueoflegends" - - aliases = ["lol", "league"] - - description: str = "Connetti un account di League of Legends a un account Royalnet, e visualizzane le statistiche." - - syntax = "[nomeevocatore]" - - tables = {LeagueOfLegends} - - _region = "euw1" - - _telegram_group_id = -1001153723135 - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - self._riotwatcher = riotwatcher.RiotWatcher(api_key=self.interface.bot.get_secret("leagueoflegends")) - if self.interface.name == "telegram": - self.loop.create_task(self._updater(900)) - - async def _send(self, message): - client = self.interface.bot.client - await self.interface.bot.safe_api_call(client.send_message, - chat_id=self._telegram_group_id, - text=telegram_escape(message), - parse_mode="HTML", - disable_webpage_preview=True) - - async def _notify(self, - obj: LeagueOfLegends, - attribute_name: str, - old_value: typing.Any, - new_value: typing.Any): - if self.interface.name == "telegram": - if isinstance(old_value, LeagueLeague): - # This is a rank change! - # Don't send messages for every rank change, send messages just if the TIER or RANK changes! - if old_value.tier == new_value.tier and old_value.rank == new_value.rank: - return - # Find the queue - queue_names = { - "rank_soloq": "Solo/Duo", - "rank_flexq": "Flex", - "rank_twtrq": "3v3", - "rank_tftq": "TFT" - } - # Prepare the message - if new_value > old_value: - message = f"๐Ÿ“ˆ [b]{obj.user}[/b] รจ salito a {new_value} su League of Legends " \ - f"({queue_names[attribute_name]})! Congratulazioni!" - else: - message = f"๐Ÿ“‰ [b]{obj.user}[/b] รจ sceso a {new_value} su League of Legends " \ - f"({queue_names[attribute_name]})." - # Send the message - await self._send(message) - # Level up! - elif attribute_name == "summoner_level": - if new_value == 30 or (new_value >= 50 and (new_value % 25 == 0)): - await self._send(f"๐Ÿ†™ [b]{obj.user}[/b] รจ salito al livello [b]{new_value}[/b] su League of Legends!") - - @staticmethod - async def _change(obj: LeagueOfLegends, - attribute_name: str, - new_value: typing.Any, - callback: typing.Callable[ - [LeagueOfLegends, str, typing.Any, typing.Any], typing.Awaitable[None]]): - old_value = obj.__getattribute__(attribute_name) - if old_value != new_value: - await callback(obj, attribute_name, old_value, new_value) - obj.__setattr__(attribute_name, new_value) - - async def _update(self, lol: LeagueOfLegends): - log.info(f"Updating: {lol}") - log.debug(f"Getting summoner data: {lol}") - summoner = await asyncify(self._riotwatcher.summoner.by_id, region=self._region, - encrypted_summoner_id=lol.summoner_id) - await self._change(lol, "profile_icon_id", summoner["profileIconId"], self._notify) - await self._change(lol, "summoner_name", summoner["name"], self._notify) - await self._change(lol, "puuid", summoner["puuid"], self._notify) - await self._change(lol, "summoner_level", summoner["summonerLevel"], self._notify) - await self._change(lol, "summoner_id", summoner["id"], self._notify) - await self._change(lol, "account_id", summoner["accountId"], self._notify) - log.debug(f"Getting leagues data: {lol}") - leagues = await asyncify(self._riotwatcher.league.by_summoner, region=self._region, - encrypted_summoner_id=lol.summoner_id) - soloq = LeagueLeague() - flexq = LeagueLeague() - twtrq = LeagueLeague() - tftq = LeagueLeague() - for league in leagues: - if league["queueType"] == "RANKED_SOLO_5x5": - soloq = LeagueLeague.from_dict(league) - if league["queueType"] == "RANKED_FLEX_SR": - flexq = LeagueLeague.from_dict(league) - if league["queueType"] == "RANKED_FLEX_TT": - twtrq = LeagueLeague.from_dict(league) - if league["queueType"] == "RANKED_TFT": - tftq = LeagueLeague.from_dict(league) - await self._change(lol, "rank_soloq", soloq, self._notify) - await self._change(lol, "rank_flexq", flexq, self._notify) - await self._change(lol, "rank_twtrq", twtrq, self._notify) - await self._change(lol, "rank_tftq", tftq, self._notify) - log.debug(f"Getting mastery data: {lol}") - mastery = await asyncify(self._riotwatcher.champion_mastery.scores_by_summoner, region=self._region, - encrypted_summoner_id=lol.summoner_id) - await self._change(lol, "mastery_score", mastery, self._notify) - - async def _updater(self, period: int): - log.info(f"Started updater with {period}s period") - while True: - log.info(f"Updating...") - session = self.alchemy.Session() - log.info("") - lols = session.query(self.alchemy.LeagueOfLegends).all() - for lol in lols: - try: - await self._update(lol) - except Exception as e: - sentry_sdk.capture_exception(e) - log.error(f"Error while updating {lol.user.username}: {e}") - await asyncio.sleep(1) - await asyncify(session.commit) - session.close() - log.info(f"Sleeping for {period}s") - await asyncio.sleep(period) - - def _display(self, lol: LeagueOfLegends): - string = f"โ„น๏ธ [b]{lol.summoner_name}[/b]\n" \ - f"Lv. {lol.summoner_level}\n" \ - f"Mastery score: {lol.mastery_score}\n" \ - f"\n" - if lol.rank_soloq: - string += f"Solo: {lol.rank_soloq}\n" - if lol.rank_flexq: - string += f"Flex: {lol.rank_flexq}\n" - if lol.rank_twtrq: - string += f"3v3: {lol.rank_twtrq}\n" - if lol.rank_tftq: - string += f"TFT: {lol.rank_tftq}\n" - return string - - async def run(self, args: CommandArgs, data: CommandData) -> None: - author = await data.get_author(error_if_none=True) - - name = args.joined() - - if name: - # Connect a new League of Legends account to Royalnet - log.debug(f"Searching for: {name}") - summoner = self._riotwatcher.summoner.by_name(region=self._region, summoner_name=name) - # Ensure the account isn't already connected to something else - leagueoflegends = await asyncify( - data.session.query(self.alchemy.LeagueOfLegends).filter_by(summoner_id=summoner["id"]).one_or_none) - if leagueoflegends: - raise CommandError(f"L'account {leagueoflegends} รจ giร  registrato su Royalnet.") - # Get rank information - log.debug(f"Getting leagues data: {name}") - leagues = self._riotwatcher.league.by_summoner(region=self._region, encrypted_summoner_id=summoner["id"]) - soloq = LeagueLeague() - flexq = LeagueLeague() - twtrq = LeagueLeague() - tftq = LeagueLeague() - for league in leagues: - if league["queueType"] == "RANKED_SOLO_5x5": - soloq = LeagueLeague.from_dict(league) - if league["queueType"] == "RANKED_FLEX_SR": - flexq = LeagueLeague.from_dict(league) - if league["queueType"] == "RANKED_FLEX_TT": - twtrq = LeagueLeague.from_dict(league) - if league["queueType"] == "RANKED_TFT": - tftq = LeagueLeague.from_dict(league) - # Get mastery score - log.debug(f"Getting mastery data: {name}") - mastery = self._riotwatcher.champion_mastery.scores_by_summoner(region=self._region, - encrypted_summoner_id=summoner["id"]) - # Create database row - leagueoflegends = self.alchemy.LeagueOfLegends( - region=self._region, - user=author, - profile_icon_id=summoner["profileIconId"], - summoner_name=summoner["name"], - puuid=summoner["puuid"], - summoner_level=summoner["summonerLevel"], - summoner_id=summoner["id"], - account_id=summoner["accountId"], - rank_soloq=soloq, - rank_flexq=flexq, - rank_twtrq=twtrq, - rank_tftq=tftq, - mastery_score=mastery - ) - log.debug(f"Saving to the DB: {name}") - data.session.add(leagueoflegends) - await data.session_commit() - await data.reply(f"โ†”๏ธ Account {leagueoflegends} connesso a {author}!") - else: - # Update and display the League of Legends stats for the current account - if len(author.leagueoflegends) == 0: - raise CommandError("Nessun account di League of Legends trovato.") - message = "" - for account in author.leagueoflegends: - try: - await self._update(account) - message += self._display(account) - except riotwatcher.ApiError as e: - message += f"โš ๏ธ [b]{account.summoner_name}[/b]\n" \ - f"{e}" - message += "\n" - await data.session_commit() - await data.reply(message) diff --git a/royalnet/packs/royal/commands/matchmaking.py b/royalnet/packs/royal/commands/matchmaking.py deleted file mode 100644 index 3fe9c01c..00000000 --- a/royalnet/packs/royal/commands/matchmaking.py +++ /dev/null @@ -1,244 +0,0 @@ -import datetime -import re -import dateparser -import typing -from telegram import Bot as PTBBot -from telegram import Message as PTBMessage -from telegram.error import BadRequest, Unauthorized -from telegram import InlineKeyboardMarkup as InKeMa -from telegram import InlineKeyboardButton as InKeBu -from royalnet.commands import * -from royalnet.bots import TelegramBot -from royalnet.utils import telegram_escape, asyncify, sleep_until -from ..tables import MMEvent, MMResponse, User -from ..utils import MMChoice, MMInterfaceDataTelegram - - -class MatchmakingCommand(Command): - name: str = "matchmaking" - - description: str = "Cerca persone per una partita a qualcosa!" - - syntax: str = "[ {ora} ] {nome}\n[descrizione]" - - aliases = ["mm", "lfg"] - - tables = {MMEvent, MMResponse} - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - # Find all relevant MMEvents and run them - session = self.alchemy.Session() - mmevents = ( - session - .query(self.alchemy.MMEvent) - .filter(self.alchemy.MMEvent.interface == self.interface.name, - self.alchemy.MMEvent.datetime > datetime.datetime.now()) - .all() - ) - for mmevent in mmevents: - self.interface.loop.create_task(self._run_mmevent(mmevent.mmid)) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - # Create a new MMEvent and run it - if self.interface.name != "telegram": - raise UnsupportedError(f"{self.interface.prefix}matchmaking funziona solo su Telegram. Per ora.") - author = await data.get_author(error_if_none=True) - - try: - timestring, title, description = args.match(r"\[\s*([^]]+)\s*]\s*([^\n]+)\s*\n?\s*(.+)?\s*", re.DOTALL) - except InvalidInputError: - timestring, title, description = args.match(r"\s*(.+?)\s*\n\s*([^\n]+)\s*\n?\s*(.+)?\s*", re.DOTALL) - try: - dt: typing.Optional[datetime.datetime] = dateparser.parse(timestring, settings={ - "PREFER_DATES_FROM": "future" - }) - except OverflowError: - dt = None - if dt is None: - raise CommandError("La data che hai specificato non รจ valida.") - if dt <= datetime.datetime.now(): - raise CommandError("La data che hai specificato รจ nel passato.") - if dt >= datetime.datetime.now() + datetime.timedelta(days=90): - raise CommandError("La data che hai specificato รจ a piรน di 90 giorni di distanza da oggi.") - mmevent: MMEvent = self.interface.alchemy.MMEvent(creator=author, - datetime=dt, - title=title, - description=description, - interface=self.interface.name) - data.session.add(mmevent) - await data.session_commit() - self.interface.loop.create_task(self._run_mmevent(mmevent.mmid)) - await data.reply(f"โœ… Evento [b]{mmevent.title}[/b] creato!") - - _mm_chat_id = -1001287169422 - - _mm_error_chat_id = -1001153723135 - - def _gen_mm_message(self, mmevent: MMEvent) -> str: - text = f"๐ŸŒ [{mmevent.datetime.strftime('%Y-%m-%d %H:%M')}] [b]{mmevent.title}[/b]\n" - if mmevent.description: - text += f"{mmevent.description}\n" - text += "\n" - for response in mmevent.responses: - response: MMResponse - text += f"{response.choice.value} {response.user}\n" - return text - - def _gen_telegram_keyboard(self, mmevent: MMEvent): - return InKeMa([ - [InKeBu(f"{MMChoice.YES.value} Ci sarรฒ!", callback_data=f"mm{mmevent.mmid}_YES")], - [InKeBu(f"{MMChoice.MAYBE.value} (Forse.)", callback_data=f"mm{mmevent.mmid}_MAYBE")], - [InKeBu(f"{MMChoice.LATE_SHORT.value} Arrivo dopo 5-10 min.", - callback_data=f"mm{mmevent.mmid}_LATE_SHORT")], - [InKeBu(f"{MMChoice.LATE_MEDIUM.value} Arrivo dopo 15-35 min.", - callback_data=f"mm{mmevent.mmid}_LATE_MEDIUM")], - [InKeBu(f"{MMChoice.LATE_LONG.value} Arrivo dopo 40+ min.", callback_data=f"mm{mmevent.mmid}_LATE_LONG")], - [InKeBu(f"{MMChoice.NO_TIME.value} Non posso a quell'ora...", callback_data=f"mm{mmevent.mmid}_NO_TIME")], - [InKeBu(f"{MMChoice.NO_INTEREST.value} Non mi interessa.", callback_data=f"mm{mmevent.mmid}_NO_INTEREST")], - [InKeBu(f"{MMChoice.NO_TECH.value} Ho un problema!", callback_data=f"mm{mmevent.mmid}_NO_TECH")], - ]) - - async def _update_telegram_mm_message(self, client: PTBBot, mmevent: MMEvent): - try: - await self.interface.bot.safe_api_call(client.edit_message_text, - chat_id=self._mm_chat_id, - text=telegram_escape(self._gen_mm_message(mmevent)), - message_id=mmevent.interface_data.message_id, - parse_mode="HTML", - disable_web_page_preview=True, - reply_markup=self._gen_telegram_keyboard(mmevent)) - except BadRequest: - pass - - def _gen_mm_telegram_callback(self, client: PTBBot, mmid: int, choice: MMChoice): - async def callback(data: CommandData): - author = await data.get_author(error_if_none=True) - # Find the MMEvent with the current session - mmevent: MMEvent = await asyncify(data.session.query(self.alchemy.MMEvent).get, mmid) - mmresponse: MMResponse = await asyncify( - data.session.query(self.alchemy.MMResponse).filter_by(user=author, mmevent=mmevent).one_or_none) - if mmresponse is None: - mmresponse = self.alchemy.MMResponse(user=author, mmevent=mmevent, choice=choice) - data.session.add(mmresponse) - else: - mmresponse.choice = choice - await data.session_commit() - await self._update_telegram_mm_message(client, mmevent) - return f"โœ… Messaggio ricevuto!" - - return callback - - def _gen_event_start_message(self, mmevent: MMEvent): - text = f"๐Ÿšฉ L'evento [b]{mmevent.title}[/b] รจ iniziato!\n\n" - for response in mmevent.responses: - response: MMResponse - text += f"{response.choice.value} {response.user}\n" - return text - - def _gen_unauth_message(self, user: User): - return f"โš ๏ธ Non sono autorizzato a mandare messaggi a [b]{user.username}[/b]!\n" \ - f"{user.telegram.mention()}, apri una chat privata con me e mandami un messaggio!" - - async def _run_mmevent(self, mmid: int): - """Run a MMEvent.""" - # Open a new Alchemy Session - session = self.alchemy.Session() - # Find the MMEvent with the current session - mmevent: MMEvent = await asyncify(session.query(self.alchemy.MMEvent).get, mmid) - if mmevent is None: - raise ValueError("Invalid mmid.") - # Ensure the MMEvent hasn't already started - if mmevent.datetime <= datetime.datetime.now(): - raise ValueError("MMEvent has already started.") - # Ensure the MMEvent interface matches the current one - if mmevent.interface != self.interface.name: - raise ValueError("Invalid interface.") - # If the matchmaking message hasn't been sent yet, do so now - if mmevent.interface_data is None: - if self.interface.name == "telegram": - bot: TelegramBot = self.interface.bot - client: PTBBot = bot.client - # Build the Telegram keyboard - # Send the keyboard - message: PTBMessage = await self.interface.bot.safe_api_call(client.send_message, - chat_id=self._mm_chat_id, - text=telegram_escape( - self._gen_mm_message(mmevent)), - parse_mode="HTML", - disable_webpage_preview=True, - reply_markup=self._gen_telegram_keyboard( - mmevent)) - # Store message data in the interface data object - mmevent.interface_data = MMInterfaceDataTelegram(chat_id=self._mm_chat_id, - message_id=message.message_id) - await asyncify(session.commit) - else: - raise UnsupportedError() - # Register handlers for the keyboard events - if self.interface.name == "telegram": - bot: TelegramBot = self.interface.bot - client: PTBBot = bot.client - self.interface.register_keyboard_key(f"mm{mmevent.mmid}_YES", - callback=self._gen_mm_telegram_callback(client, mmid, MMChoice.YES)) - self.interface.register_keyboard_key(f"mm{mmevent.mmid}_MAYBE", - callback=self._gen_mm_telegram_callback(client, mmid, MMChoice.MAYBE)) - self.interface.register_keyboard_key(f"mm{mmevent.mmid}_LATE_SHORT", - callback=self._gen_mm_telegram_callback(client, mmid, - MMChoice.LATE_SHORT)) - self.interface.register_keyboard_key(f"mm{mmevent.mmid}_LATE_MEDIUM", - callback=self._gen_mm_telegram_callback(client, mmid, - MMChoice.LATE_MEDIUM)) - self.interface.register_keyboard_key(f"mm{mmevent.mmid}_LATE_LONG", - callback=self._gen_mm_telegram_callback(client, mmid, - MMChoice.LATE_LONG)) - self.interface.register_keyboard_key(f"mm{mmevent.mmid}_NO_TIME", - callback=self._gen_mm_telegram_callback(client, mmid, - MMChoice.NO_TIME)) - self.interface.register_keyboard_key(f"mm{mmevent.mmid}_NO_INTEREST", - callback=self._gen_mm_telegram_callback(client, mmid, - MMChoice.NO_INTEREST)) - self.interface.register_keyboard_key(f"mm{mmevent.mmid}_NO_TECH", - callback=self._gen_mm_telegram_callback(client, mmid, - MMChoice.NO_TECH)) - else: - raise UnsupportedError() - # Sleep until the time of the event - await sleep_until(mmevent.datetime) - # Notify the positive answers of the event start - if self.interface.name == "telegram": - bot: TelegramBot = self.interface.bot - client: PTBBot = bot.client - self.interface.unregister_keyboard_key(f"mm{mmevent.mmid}_YES") - self.interface.unregister_keyboard_key(f"mm{mmevent.mmid}_MAYBE") - self.interface.unregister_keyboard_key(f"mm{mmevent.mmid}_LATE_SHORT") - self.interface.unregister_keyboard_key(f"mm{mmevent.mmid}_LATE_MEDIUM") - self.interface.unregister_keyboard_key(f"mm{mmevent.mmid}_LATE_LONG") - self.interface.unregister_keyboard_key(f"mm{mmevent.mmid}_NO_TIME") - self.interface.unregister_keyboard_key(f"mm{mmevent.mmid}_NO_INTEREST") - self.interface.unregister_keyboard_key(f"mm{mmevent.mmid}_NO_TECH") - for response in mmevent.responses: - if response.choice == MMChoice.NO_INTEREST or response.choice == MMChoice.NO_TIME: - return - try: - await self.interface.bot.safe_api_call(client.send_message, - chat_id=response.user.telegram[0].tg_id, - text=telegram_escape(self._gen_event_start_message(mmevent)), - parse_mode="HTML", - disable_webpage_preview=True) - except Unauthorized: - await self.interface.bot.safe_api_call(client.send_message, - chat_id=self._mm_error_chat_id, - text=telegram_escape( - self._gen_unauth_message(response.user)), - parse_mode="HTML", - disable_webpage_preview=True) - else: - raise UnsupportedError() - # Delete the event message - if self.interface.name == "telegram": - await self.interface.bot.safe_api_call(client.delete_message, - chat_id=mmevent.interface_data.chat_id, - message_id=mmevent.interface_data.message_id) - # The end! - await asyncify(session.close) diff --git a/royalnet/packs/royal/commands/mp3.py b/royalnet/packs/royal/commands/mp3.py deleted file mode 100644 index 45efec4c..00000000 --- a/royalnet/packs/royal/commands/mp3.py +++ /dev/null @@ -1,40 +0,0 @@ -import typing -import urllib.parse -import asyncio -from royalnet.commands import * -from royalnet.utils import asyncify -from royalnet.audio import YtdlMp3 - - -class Mp3Command(Command): - name: str = "mp3" - - aliases = ["dlmusic"] - - description: str = "Scarica un video con youtube-dl e invialo in chat." - - syntax = "{ytdlstring}" - - ytdl_args = { - "format": "bestaudio", - "outtmpl": f"./downloads/%(title)s.%(ext)s" - } - - seconds_before_deletion = 15 * 60 - - async def run(self, args: CommandArgs, data: CommandData) -> None: - url = args.joined() - if url.startswith("http://") or url.startswith("https://"): - vfiles: typing.List[YtdlMp3] = await asyncify(YtdlMp3.create_and_ready_from_url, - url, - **self.ytdl_args) - else: - vfiles = await asyncify(YtdlMp3.create_and_ready_from_url, f"ytsearch:{url}", **self.ytdl_args) - for vfile in vfiles: - await data.reply(f"โฌ‡๏ธ Il file richiesto puรฒ essere scaricato a:\n" - f"https://scaleway.steffo.eu/{urllib.parse.quote(vfile.mp3_filename.replace('./downloads/', './musicbot_cache/'))}\n" - f"Verrร  eliminato tra {self.seconds_before_deletion} secondi.") - await asyncio.sleep(self.seconds_before_deletion) - for vfile in vfiles: - vfile.delete() - await data.reply(f"โน Il file {vfile.info.title} รจ scaduto ed รจ stato eliminato.") diff --git a/royalnet/packs/royal/commands/pause.py b/royalnet/packs/royal/commands/pause.py deleted file mode 100644 index 14147e0e..00000000 --- a/royalnet/packs/royal/commands/pause.py +++ /dev/null @@ -1,53 +0,0 @@ -import typing -import discord -from royalnet.commands import * -from royalnet.bots import DiscordBot - - -class PauseCommand(Command): - name: str = "pause" - - description: str = "Mette in pausa o riprende la riproduzione della canzone attuale." - - syntax = "[ [guild] ]" - - @staticmethod - async def _legacy_pause_handler(bot: DiscordBot, guild_name: typing.Optional[str]): - # Find the matching guild - if guild_name: - guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(guild_name) - else: - guilds = bot.client.guilds - if len(guilds) == 0: - raise CommandError("No guilds with the specified name found.") - if len(guilds) > 1: - raise CommandError("Multiple guilds with the specified name found.") - guild = list(bot.client.guilds)[0] - # Set the currently playing source as ended - voice_client: discord.VoiceClient = bot.client.find_voice_client_by_guild(guild) - if not (voice_client.is_playing() or voice_client.is_paused()): - raise CommandError("There is nothing to pause.") - # Toggle pause - resume = voice_client._player.is_paused() - if resume: - voice_client._player.resume() - else: - voice_client._player.pause() - return {"resumed": resume} - - _event_name = "_legacy_pause" - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - if interface.name == "discord": - interface.register_herald_action(self._event_name, self._legacy_pause_handler) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - guild_name, = args.match(r"(?:\[(.+)])?") - response = await self.interface.call_herald_action("discord", self._event_name, { - "guild_name": guild_name - }) - if response["resumed"]: - await data.reply(f"โ–ถ๏ธ Riproduzione ripresa.") - else: - await data.reply(f"โธ Riproduzione messa in pausa.") diff --git a/royalnet/packs/royal/commands/peertube.py b/royalnet/packs/royal/commands/peertube.py deleted file mode 100644 index f0c60a45..00000000 --- a/royalnet/packs/royal/commands/peertube.py +++ /dev/null @@ -1,81 +0,0 @@ -import aiohttp -import asyncio -import datetime -import logging -import dateparser -from royalnet.commands import * -from royalnet.utils import telegram_escape - - -log = logging.getLogger(__name__) - - -class PeertubeCommand(Command): - name: str = "peertube" - - description: str = "Guarda quando รจ uscito l'ultimo video su RoyalTube." - - _url = r"https://pt.steffo.eu/feeds/videos.json?sort=-publishedAt&filter=local" - - _ready = asyncio.Event() - - _timeout = 300 - - _latest_date: datetime.datetime = None - - _telegram_group_id = -1001153723135 - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - if self.interface.name == "telegram": - self.loop.create_task(self._ready_up()) - self.loop.create_task(self._update()) - - async def _get_json(self): - log.debug("Getting jsonfeed") - async with aiohttp.ClientSession() as session: - async with session.get(self._url) as response: - log.debug("Parsing jsonfeed") - j = await response.json() - log.debug("Jsonfeed parsed successfully") - return j - - async def _send(self, message): - client = self.interface.bot.client - await self.interface.bot.safe_api_call(client.send_message, - chat_id=self._telegram_group_id, - text=telegram_escape(message), - parse_mode="HTML", - disable_webpage_preview=True) - - async def _ready_up(self): - j = await self._get_json() - if j["version"] != "https://jsonfeed.org/version/1": - raise ConfigurationError("_url is not a jsonfeed") - videos = j["items"] - for video in reversed(videos): - date_modified = dateparser.parse(video["date_modified"]) - if self._latest_date is None or date_modified > self._latest_date: - log.debug(f"Found newer video: {date_modified}") - self._latest_date = date_modified - self._ready.set() - - async def _update(self): - await self._ready.wait() - while True: - j = await self._get_json() - videos = j["items"] - for video in reversed(videos): - date_modified = dateparser.parse(video["date_modified"]) - if date_modified > self._latest_date: - log.debug(f"Found newer video: {date_modified}") - self._latest_date = date_modified - await self._send(f"๐Ÿ†• Nuovo video su RoyalTube!\n" - f"[b]{video['title']}[/b]\n" - f"{video['url']}") - await asyncio.sleep(self._timeout) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - if self.interface.name != "telegram": - raise UnsupportedError() - await data.reply(f"โ„น๏ธ Ultimo video caricato il: [b]{self._latest_date.isoformat()}[/b]") diff --git a/royalnet/packs/royal/commands/play.py b/royalnet/packs/royal/commands/play.py deleted file mode 100644 index 44e7823d..00000000 --- a/royalnet/packs/royal/commands/play.py +++ /dev/null @@ -1,79 +0,0 @@ -import typing -import pickle -import datetime -import discord -from royalnet.commands import * -from royalnet.utils import asyncify -from royalnet.audio import YtdlDiscord -from royalnet.bots import DiscordBot - - -class PlayCommand(Command): - name: str = "play" - - aliases = ["p"] - - description: str = "Aggiunge un url alla coda della chat vocale." - - syntax = "[ [guild] ] {url}" - - @staticmethod - async def _legacy_play_handler(bot: "DiscordBot", guild_name: typing.Optional[str], url: str): - """Handle a play Royalnet request. That is, add audio to a PlayMode.""" - # Find the matching guild - if guild_name: - guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(guild_name) - else: - guilds = bot.client.guilds - if len(guilds) == 0: - raise CommandError("Server non trovato.") - if len(guilds) > 1: - raise CommandError("Il nome del server รจ ambiguo.") - guild = list(bot.client.guilds)[0] - # Ensure the guild has a PlayMode before adding the file to it - if not bot.music_data.get(guild): - raise CommandError("Il bot non รจ in nessun canale vocale.") - # Create url - ytdl_args = { - "format": "bestaudio/best", - "outtmpl": f"./downloads/{datetime.datetime.now().timestamp()}_%(title)s.%(ext)s" - } - # Start downloading - dfiles: typing.List[YtdlDiscord] = await asyncify(YtdlDiscord.create_from_url, url, **ytdl_args) - await bot.add_to_music_data(dfiles, guild) - # Create response dictionary - response = { - "videos": [{ - "title": dfile.info.title, - "discord_embed_pickle": str(pickle.dumps(dfile.info.to_discord_embed())) - } for dfile in dfiles] - } - return response - - _event_name = "_legacy_play" - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - if interface.name == "discord": - interface.register_herald_action(self._event_name, self._legacy_play_handler) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - guild_name, url = args.match(r"(?:\[(.+)])?\s*?") - if not (url.startswith("http://") or url.startswith("https://")): - raise CommandError(f"Il comando [c]{self.interface.prefix}play[/c] funziona solo per riprodurre file da" - f" un URL.\n" - f"Se vuoi cercare un video, usa [c]{self.interface.prefix}youtube[/c] o" - f" [c]{self.interface.prefix}soundcloud[/c]!") - response: dict = await self.interface.call_herald_action("discord", self._event_name, { - "guild_name": guild_name, - "url": url - }) - if len(response["videos"]) == 0: - raise CommandError(f"Nessun file trovato.") - for video in response["videos"]: - if self.interface.name == "discord": - # This is one of the unsafest things ever - embed = pickle.loads(eval(video["discord_embed_pickle"])) - await data.message.channel.send(content="โ–ถ๏ธ Aggiunto alla coda:", embed=embed) - else: - await data.reply(f"โ–ถ๏ธ Aggiunto alla coda: [i]{video['title']}[/i]") diff --git a/royalnet/packs/royal/commands/playmode.py b/royalnet/packs/royal/commands/playmode.py deleted file mode 100644 index f99a5616..00000000 --- a/royalnet/packs/royal/commands/playmode.py +++ /dev/null @@ -1,57 +0,0 @@ -import typing -import discord -from royalnet.commands import * -from royalnet.audio.playmodes import Playlist, Pool, Layers -from royalnet.bots import DiscordBot - - -class PlaymodeCommand(Command): - name: str = "playmode" - - aliases = ["pm", "mode"] - - description: str = "Cambia modalitร  di riproduzione per la chat vocale." - - syntax = "[ [guild] ] {mode}" - - @staticmethod - async def _legacy_playmode_handler(bot: "DiscordBot", guild_name: typing.Optional[str], mode_name: str): - """Handle a playmode Royalnet request. That is, change current PlayMode.""" - # Find the matching guild - if guild_name: - guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(guild_name) - else: - guilds = bot.client.guilds - if len(guilds) == 0: - raise CommandError("No guilds with the specified name found.") - if len(guilds) > 1: - raise CommandError("Multiple guilds with the specified name found.") - guild = list(bot.client.guilds)[0] - # Delete the previous PlayMode, if it exists - if bot.music_data[guild] is not None: - bot.music_data[guild].playmode.delete() - # Create the new PlayMode - if mode_name == "playlist": - bot.music_data[guild].playmode = Playlist() - elif mode_name == "pool": - bot.music_data[guild].playmode = Pool() - elif mode_name == "layers": - bot.music_data[guild].playmode = Layers() - else: - raise CommandError("Unknown PlayMode specified.") - return {} - - _event_name = "_legacy_playmode" - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - if interface.name == "discord": - interface.register_herald_action(self._event_name, self._legacy_playmode_handler) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - guild_name, mode_name = args.match(r"(?:\[(.+)])?\s*(\S+)\s*") - await self.interface.call_herald_action("discord", self._event_name, { - "guild_name": guild_name, - "mode_name": mode_name - }) - await data.reply(f"๐Ÿ”ƒ Impostata la modalitร  di riproduzione a: [c]{mode_name}[/c].") diff --git a/royalnet/packs/royal/commands/queue.py b/royalnet/packs/royal/commands/queue.py deleted file mode 100644 index a242d83f..00000000 --- a/royalnet/packs/royal/commands/queue.py +++ /dev/null @@ -1,93 +0,0 @@ -import typing -import pickle -import discord -from royalnet.commands import * -from royalnet.utils import numberemojiformat -from royalnet.bots import DiscordBot - - -class QueueCommand(Command): - name: str = "queue" - - aliases = ["q"] - - description: str = "Visualizza la coda di riproduzione attuale." - - syntax = "[ [guild] ]" - - @staticmethod - async def _legacy_queue_handler(bot: "DiscordBot", guild_name: typing.Optional[str]): - # Find the matching guild - if guild_name: - guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(guild_name) - else: - guilds = bot.client.guilds - if len(guilds) == 0: - raise CommandError("No guilds with the specified name found.") - if len(guilds) > 1: - raise CommandError("Multiple guilds with the specified name found.") - guild = list(bot.client.guilds)[0] - # Check if the guild has a PlayMode - playmode = bot.music_data.get(guild) - if not playmode: - return { - "type": None - } - try: - queue = playmode.queue_preview() - except NotImplementedError: - return { - "type": playmode.__class__.__name__ - } - return { - "type": playmode.__class__.__name__, - "queue": - { - "strings": [str(dfile.info) for dfile in queue], - "pickled_embeds": str(pickle.dumps([dfile.info.to_discord_embed() for dfile in queue])) - } - } - - _event_name = "_legacy_queue" - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - if interface.name == "discord": - interface.register_herald_action(self._event_name, self._legacy_queue_handler) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - guild_name, = args.match(r"(?:\[(.+)])?") - response = await self.interface.call_herald_action("discord", self._event_name, {"guild_name": guild_name}) - if response["type"] is None: - await data.reply("โ„น๏ธ Non c'รจ nessuna coda di riproduzione attiva al momento.") - return - elif "queue" not in response: - await data.reply(f"โ„น๏ธ La coda di riproduzione attuale ([c]{response['type']}[/c]) non permette l'anteprima.") - return - if response["type"] == "Playlist": - if len(response["queue"]["strings"]) == 0: - message = f"โ„น๏ธ Questa [c]Playlist[/c] รจ vuota." - else: - message = f"โ„น๏ธ Questa [c]Playlist[/c] contiene {len(response['queue']['strings'])} elementi, e i prossimi saranno:\n" - elif response["type"] == "Pool": - if len(response["queue"]["strings"]) == 0: - message = f"โ„น๏ธ Questo [c]Pool[/c] รจ vuoto." - else: - message = f"โ„น๏ธ Questo [c]Pool[/c] contiene {len(response['queue']['strings'])} elementi, tra cui:\n" - elif response["type"] == "Layers": - if len(response["queue"]["strings"]) == 0: - message = f"โ„น๏ธ Nessun elemento รจ attualmente in riproduzione, pertanto non ci sono [c]Layers[/c]:" - else: - message = f"โ„น๏ธ I [c]Layers[/c] dell'elemento attualmente in riproduzione sono {len(response['queue']['strings'])}, tra cui:\n" - else: - if len(response["queue"]["strings"]) == 0: - message = f"โ„น๏ธ Il PlayMode attuale, [c]{response['type']}[/c], รจ vuoto.\n" - else: - message = f"โ„น๏ธ Il PlayMode attuale, [c]{response['type']}[/c], contiene {len(response['queue']['strings'])} elementi:\n" - if self.interface.name == "discord": - await data.reply(message) - for embed in pickle.loads(eval(response["queue"]["pickled_embeds"]))[:5]: - await data.message.channel.send(embed=embed) - else: - message += numberemojiformat(response["queue"]["strings"][:10]) - await data.reply(message) diff --git a/royalnet/packs/royal/commands/rage.py b/royalnet/packs/royal/commands/rage.py deleted file mode 100644 index 91435811..00000000 --- a/royalnet/packs/royal/commands/rage.py +++ /dev/null @@ -1,20 +0,0 @@ -import typing -import random -from royalnet.commands import * - - -class RageCommand(Command): - name: str = "rage" - - aliases = ["balurage", "madden"] - - description: str = "Arrabbiati per qualcosa, come una software house californiana." - - MAD = ["MADDEN MADDEN MADDEN MADDEN", - "EA bad, praise Geraldo!", - "Stai sfogando la tua ira sul bot!", - "Basta, io cambio gilda!", - "Fondiamo la RRYG!"] - - async def run(self, args: CommandArgs, data: CommandData) -> None: - await data.reply(f"๐Ÿ˜  {random.sample(self.MAD, 1)[0]}") diff --git a/royalnet/packs/royal/commands/reminder.py b/royalnet/packs/royal/commands/reminder.py deleted file mode 100644 index e33b5e80..00000000 --- a/royalnet/packs/royal/commands/reminder.py +++ /dev/null @@ -1,86 +0,0 @@ -import typing -import dateparser -import datetime -import pickle -import telegram -import discord -from sqlalchemy import and_ -from royalnet.commands import * -from royalnet.utils import sleep_until, asyncify, telegram_escape, discord_escape -from ..tables import Reminder - - -class ReminderCommand(Command): - name: str = "reminder" - - aliases = ["calendar"] - - description: str = "Ti ricorda di fare qualcosa dopo un po' di tempo." - - syntax: str = "[ {data} ] {messaggio}" - - tables = {Reminder} - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - session = interface.alchemy.Session() - reminders = ( - session.query(interface.alchemy.Reminder) - .filter(and_( - interface.alchemy.Reminder.datetime >= datetime.datetime.now(), - interface.alchemy.Reminder.interface_name == interface.name)) - .all() - ) - for reminder in reminders: - interface.loop.create_task(self._remind(reminder)) - - async def _remind(self, reminder): - await sleep_until(reminder.datetime) - if self.interface.name == "telegram": - chat_id: int = pickle.loads(reminder.raw_interface_data) - bot: telegram.Bot = self.interface.bot.client - await asyncify(bot.send_message, - chat_id=chat_id, - text=telegram_escape(f"โ—๏ธ {reminder.message}"), - parse_mode="HTML", - disable_web_page_preview=True) - elif self.interface.name == "discord": - channel_id: int = pickle.loads(reminder.raw_interface_data) - bot: discord.Client = self.interface.bot.client - channel = bot.get_channel(channel_id) - await channel.send(discord_escape(f"โ—๏ธ {reminder.message}")) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - try: - date_str, reminder_text = args.match(r"\[\s*([^]]+)\s*]\s*([^\n]+)\s*") - except InvalidInputError: - date_str, reminder_text = args.match(r"\s*(.+?)\s*\n\s*([^\n]+)\s*") - - try: - date: typing.Optional[datetime.datetime] = dateparser.parse(date_str, settings={ - "PREFER_DATES_FROM": "future" - }) - except OverflowError: - date = None - if date is None: - await data.reply("โš ๏ธ La data che hai inserito non รจ valida.") - return - if date <= datetime.datetime.now(): - await data.reply("โš ๏ธ La data che hai specificato รจ nel passato.") - return - await data.reply(f"โœ… Promemoria impostato per [b]{date.strftime('%Y-%m-%d %H:%M:%S')}[/b]") - if self.interface.name == "telegram": - interface_data = pickle.dumps(data.update.effective_chat.id) - elif self.interface.name == "discord": - interface_data = pickle.dumps(data.message.channel.id) - else: - raise UnsupportedError("This command does not support the current interface.") - creator = await data.get_author() - reminder = self.interface.alchemy.Reminder(creator=creator, - interface_name=self.interface.name, - interface_data=interface_data, - datetime=date, - message=reminder_text) - self.interface.loop.create_task(self._remind(reminder)) - data.session.add(reminder) - await asyncify(data.session.commit) diff --git a/royalnet/packs/royal/commands/ship.py b/royalnet/packs/royal/commands/ship.py deleted file mode 100644 index 83677766..00000000 --- a/royalnet/packs/royal/commands/ship.py +++ /dev/null @@ -1,40 +0,0 @@ -import typing -import re -from royalnet.commands import * -from royalnet.utils import safeformat - - -class ShipCommand(Command): - name: str = "ship" - - aliases = ["โ›ต๏ธ"] - - description: str = "Crea una ship tra due nomi." - - syntax = "{nomeuno} {nomedue}" - - async def run(self, args: CommandArgs, data: CommandData) -> None: - name_one = args[0] - name_two = args[1] - if name_two == "+": - name_two = args[2] - name_one = name_one.lower() - name_two = name_two.lower() - # Get all letters until the first vowel, included - match_one = re.search(r"^[A-Za-z][^aeiouAEIOU]*[aeiouAEIOU]?", name_one) - if match_one is None: - part_one = name_one[:int(len(name_one) / 2)] - else: - part_one = match_one.group(0) - # Get all letters from the second to last vowel, excluded - match_two = re.search(r"[^aeiouAEIOU]*[aeiouAEIOU]?[A-Za-z]$", name_two) - if match_two is None: - part_two = name_two[int(len(name_two) / 2):] - else: - part_two = match_two.group(0) - # Combine the two name parts - mixed = part_one + part_two - await data.reply(safeformat("๐Ÿ’• {one} + {two} = [b]{result}[/b]", - one=name_one.capitalize(), - two=name_two.capitalize(), - result=mixed.capitalize())) diff --git a/royalnet/packs/royal/commands/skip.py b/royalnet/packs/royal/commands/skip.py deleted file mode 100644 index 48e2a4ed..00000000 --- a/royalnet/packs/royal/commands/skip.py +++ /dev/null @@ -1,48 +0,0 @@ -import typing -import discord -from royalnet.commands import * -from royalnet.bots import DiscordBot - - -class SkipCommand(Command): - name: str = "skip" - - aliases = ["s", "next", "n"] - - description: str = "Salta la canzone attualmente in riproduzione in chat vocale." - - syntax: str = "[ [guild] ]" - - @staticmethod - async def _legacy_skip_handler(bot: "DiscordBot", guild_name: typing.Optional[str]): - # Find the matching guild - if guild_name: - guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(guild_name) - else: - guilds = bot.client.guilds - if len(guilds) == 0: - raise CommandError("No guilds with the specified name found.") - if len(guilds) > 1: - raise CommandError("Multiple guilds with the specified name found.") - guild = list(bot.client.guilds)[0] - # Set the currently playing source as ended - voice_client: discord.VoiceClient = bot.client.find_voice_client_by_guild(guild) - if voice_client and not (voice_client.is_playing() or voice_client.is_paused()): - raise CommandError("Nothing to skip") - # noinspection PyProtectedMember - voice_client._player.stop() - return {} - - _event_name = "_legacy_skip" - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - if interface.name == "discord": - interface.register_herald_action(self._event_name, self._legacy_skip_handler) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - guild_name, = args.match(r"(?:\[(.+)])?") - await self.interface.call_herald_action("discord", self._event_name, { - "guild_name": guild_name - }) - await data.reply(f"โฉ Richiesto lo skip della canzone attuale.") diff --git a/royalnet/packs/royal/commands/smecds.py b/royalnet/packs/royal/commands/smecds.py deleted file mode 100644 index d83f453a..00000000 --- a/royalnet/packs/royal/commands/smecds.py +++ /dev/null @@ -1,70 +0,0 @@ -import typing -import random -from royalnet.commands import * -from royalnet.utils import safeformat - - -class SmecdsCommand(Command): - name: str = "smecds" - - aliases = ["secondomeecolpadellostagista"] - - description: str = "Secondo me, รจ colpa dello stagista..." - - syntax = "" - - DS_LIST = ["della secca", "del seccatore", "del secchiello", "del secchio", "del secchione", "del secondino", - "del sedano", "del sedativo", "della sedia", "del sedicente", "del sedile", "della sega", "del segale", - "della segatura", "della seggiola", "del seggiolino", "della seggiovia", "della segheria", - "del seghetto", "del segnalibro", "del segnaposto", "del segno", "del segretario", "della segreteria", - "del seguace", "del segugio", "della selce", "della sella", "della selz", "della selva", - "della selvaggina", "del semaforo", "del seme", "del semifreddo", "del seminario", "della seminarista", - "della semola", "del semolino", "del semplicione", "della senape", "del senatore", "del seno", - "del sensore", "della sentenza", "della sentinella", "del sentore", "della seppia", "del sequestratore", - "della serenata", "del sergente", "del sermone", "della serpe", "del serpente", "della serpentina", - "della serra", "del serraglio", "del serramanico", "della serranda", "della serratura", "del servitore", - "della servitรน", "del servizievole", "del servo", "del set", "della seta", "della setola", "del sigaro", - "del sidecar", "del siderurgico", "del sidro", "della siepe", "del sifone", "della sigaretta", - "del sigillo", "della signora", "della signorina", "del silenziatore", "della silhouette", "del silicio", - "del silicone", "del siluro", "della sinagoga", "della sindacalista", "del sindacato", "del sindaco", - "della sindrome", "della sinfonia", "del sipario", "del sire", "della sirena", "della siringa", - "del sismografo", "del sobborgo", "del sobillatore", "del sobrio", "del soccorritore", "del socio", - "del sociologo", "della soda", "del sofร ", "della soffitta", "del software", "dello sogghignare", - "del soggiorno", "della sogliola", "del sognatore", "della soia", "del solaio", "del solco", - "del soldato", "del soldo", "del sole", "della soletta", "della solista", "del solitario", - "del sollazzare", "del sollazzo", "del sollecito", "del solleone", "del solletico", "del sollevare", - "del sollievo", "del solstizio", "del solubile", "del solvente", "della soluzione", "del somaro", - "del sombrero", "del sommergibile", "del sommo", "della sommossa", "del sommozzatore", "del sonar", - "della sonda", "del sondaggio", "del sondare", "del sonnacchioso", "del sonnambulo", "del sonnellino", - "del sonnifero", "del sonno", "della sonnolenza", "del sontuoso", "del soppalco", "del soprabito", - "del sopracciglio", "del sopraffare", "del sopraffino", "del sopraluogo", "del sopramobile", - "del soprannome", "del soprano", "del soprappensiero", "del soprassalto", "del soprassedere", - "del sopravvento", "del sopravvivere", "del soqquadro", "del sorbetto", "del sordido", "della sordina", - "del sordo", "della sorella", "della sorgente", "del sornione", "del sorpasso", "della sorpresa", - "del sorreggere", "del sorridere", "della sorsata", "del sorteggio", "del sortilegio", - "del sorvegliante", "del sorvolare", "del sosia", "del sospettoso", "del sospirare", "della sosta", - "della sostanza", "del sostegno", "del sostenitore", "del sostituto", "del sottaceto", "della sottana", - "del sotterfugio", "del sotterraneo", "del sottile", "del sottilizzare", "del sottintendere", - "del sottobanco", "del sottobosco", "del sottomarino", "del sottopassaggio", "del sottoposto", - "del sottoscala", "della sottoscrizione", "del sottostare", "del sottosuolo", "del sottotetto", - "del sottotitolo", "del sottovalutare", "del sottovaso", "della sottoveste", "del sottovuoto", - "del sottufficiale", "della soubrette", "del souvenir", "del soverchiare", "del sovrano", - "del sovrapprezzo", "della sovvenzione", "del sovversivo", "del sozzo", "dello suadente", "del sub", - "del subalterno", "del subbuglio", "del subdolo", "del sublime", "del suburbano", "del successore", - "del succo", "della succube", "del succulento", "della succursale", "del sudario", "della sudditanza", - "del suddito", "del sudicio", "del suffisso", "del suffragio", "del suffumigio", "del suggeritore", - "del sughero", "del sugo", "del suino", "della suite", "del sulfureo", "del sultano", "di Steffo", - "di Spaggia", "di Sabrina", "del sas", "del ses", "del sis", "del sos", "del sus", "della supremazia", - "del Santissimo", "della scatola", "del supercalifragilistichespiralidoso", "del sale", "del salame", - "di (Town of) Salem", "di Stronghold", "di SOMA", "dei Saints", "di S.T.A.L.K.E.R.", "di Sanctum", - "dei Sims", "di Sid", "delle Skullgirls", "di Sonic", "di Spiral (Knights)", "di Spore", "di Starbound", - "di SimCity", "di Sensei", "di Ssssssssssssss... Boom! E' esploso il dizionario", "della scala", - "di Sakura", "di Suzie", "di Shinji", "del senpai", "del support", "di Superman", "di Sekiro", - "dello Slime God", "del salassato", "della salsa", "di Senjougahara", "di Sugar", "della Stampa", - "della Stampante"] - - SMECDS = "๐Ÿค” Secondo me, รจ colpa {ds}." - - async def run(self, args: CommandArgs, data: CommandData) -> None: - ds = random.sample(self.DS_LIST, 1)[0] - await data.reply(safeformat(self.SMECDS, ds=ds)) diff --git a/royalnet/packs/royal/commands/soundcloud.py b/royalnet/packs/royal/commands/soundcloud.py deleted file mode 100644 index 64ad73d2..00000000 --- a/royalnet/packs/royal/commands/soundcloud.py +++ /dev/null @@ -1,77 +0,0 @@ -import typing -import pickle -import datetime -import discord -from royalnet.commands import * -from royalnet.utils import asyncify -from royalnet.audio import YtdlDiscord -from royalnet.bots import DiscordBot - - -class SoundcloudCommand(Command): - name: str = "soundcloud" - - aliases = ["sc"] - - description: str = "Cerca una canzone su Soundcloud e la aggiunge alla coda della chat vocale." - - syntax = "[ [guild] ] {url}" - - @staticmethod - async def _legacy_soundcloud_handler(bot: "DiscordBot", guild_name: typing.Optional[str], search: str): - # Find the matching guild - if guild_name: - guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(guild_name) - else: - guilds = bot.client.guilds - if len(guilds) == 0: - raise CommandError("Server non trovato.") - if len(guilds) > 1: - raise CommandError("Il nome del server รจ ambiguo.") - guild = list(bot.client.guilds)[0] - # Ensure the guild has a PlayMode before adding the file to it - if not bot.music_data.get(guild): - raise CommandError("Il bot non รจ in nessun canale vocale.") - # Create url - ytdl_args = { - "format": "bestaudio/best", - "outtmpl": f"./downloads/{datetime.datetime.now().timestamp()}_%(title)s.%(ext)s" - } - # Start downloading - dfiles: typing.List[YtdlDiscord] = await asyncify(YtdlDiscord.create_from_url, f'scsearch:{search}', - **ytdl_args) - await bot.add_to_music_data(dfiles, guild) - # Create response dictionary - return { - "videos": [{ - "title": dfile.info.title, - "discord_embed_pickle": str(pickle.dumps(dfile.info.to_discord_embed())) - } for dfile in dfiles] - } - - _event_name = "_legacy_soundcloud" - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - if interface.name == "discord": - interface.register_herald_action(self._event_name, self._legacy_soundcloud_handler) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - guild_name, search = args.match(r"(?:\[(.+)])?\s*?") - if search.startswith("http://") or search.startswith("https://"): - raise CommandError(f"Il comando [c]{self.interface.prefix}soundcloud[/c] funziona solo per cercare audio su" - f" Soundcloud con un dato nome.\n" - f"Se vuoi riprodurre una canzone da un URL, usa [c]{self.interface.prefix}play[/c]!") - response = await self.interface.call_herald_action("discord", self._event_name, { - "guild_name": guild_name, - "search": search - }) - if len(response["videos"]) == 0: - raise CommandError(f"Il video non puรฒ essere scaricato a causa di un blocco imposto da Soundcloud.") - for video in response["videos"]: - if self.interface.name == "discord": - # This is one of the unsafest things ever - embed = pickle.loads(eval(video["discord_embed_pickle"])) - await data.message.channel.send(content="โ–ถ๏ธ Aggiunto alla coda:", embed=embed) - else: - await data.reply(f"โ–ถ๏ธ Aggiunto alla coda: [i]{video['title']}[/i]") diff --git a/royalnet/packs/royal/commands/summon.py b/royalnet/packs/royal/commands/summon.py deleted file mode 100644 index c38c9edd..00000000 --- a/royalnet/packs/royal/commands/summon.py +++ /dev/null @@ -1,77 +0,0 @@ -import typing -import discord -from royalnet.commands import * -from royalnet.bots import DiscordBot - - -class SummonCommand(Command): - name: str = "summon" - - aliases = ["cv"] - - description: str = "Evoca il bot in un canale vocale." - - syntax: str = "[nomecanale]" - - @staticmethod - async def _legacy_summon_handler(bot: "DiscordBot", channel_name: str): - """Handle a summon Royalnet request. - That is, join a voice channel, or move to a different one if that is not possible.""" - channels = bot.client.find_channel_by_name(channel_name) - if len(channels) < 1: - raise CommandError(f"Nessun canale vocale con il nome [c]{channel_name}[/c] trovato.") - channel = channels[0] - if not isinstance(channel, discord.VoiceChannel): - raise CommandError(f"Il canale [c]{channel}[/c] non รจ un canale vocale.") - bot.loop.create_task(bot.client.vc_connect_or_move(channel)) - return {} - - _event_name = "_legacy_summon" - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - if interface.name == "discord": - interface.register_herald_action(self._event_name, self._legacy_summon_handler) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - if self.interface.name == "discord": - bot = self.interface.bot.client - message: discord.Message = data.message - channel_name: str = args.optional(0) - if channel_name: - guild: typing.Optional[discord.Guild] = message.guild - if guild is not None: - channels: typing.List[discord.abc.GuildChannel] = guild.channels - else: - channels = bot.get_all_channels() - matching_channels: typing.List[discord.VoiceChannel] = [] - for channel in channels: - if isinstance(channel, discord.VoiceChannel): - if channel.name == channel_name: - matching_channels.append(channel) - if len(matching_channels) == 0: - await data.reply("โš ๏ธ Non esiste alcun canale vocale con il nome specificato.") - return - elif len(matching_channels) > 1: - await data.reply("โš ๏ธ Esiste piรน di un canale vocale con il nome specificato.") - return - channel = matching_channels[0] - else: - author: discord.Member = message.author - try: - voice: typing.Optional[discord.VoiceState] = author.voice - except AttributeError: - await data.reply("โš ๏ธ Non puoi evocare il bot da una chat privata!") - return - if voice is None: - await data.reply("โš ๏ธ Non sei connesso a nessun canale vocale!") - return - channel = voice.channel - await bot.vc_connect_or_move(channel) - await data.reply(f"โœ… Mi sono connesso in [c]#{channel.name}[/c].") - else: - channel_name: str = args[0].lstrip("#") - response = await self.interface.call_herald_action("discord", self._event_name, { - "channel_name": channel_name - }) - await data.reply(f"โœ… Mi sono connesso in [c]#{channel_name}[/c].") diff --git a/royalnet/packs/royal/commands/trivia.py b/royalnet/packs/royal/commands/trivia.py deleted file mode 100644 index b3f637ed..00000000 --- a/royalnet/packs/royal/commands/trivia.py +++ /dev/null @@ -1,139 +0,0 @@ -import typing -import asyncio -import aiohttp -import random -import uuid -import html -from royalnet.commands import * -from royalnet.utils import asyncify -from ..tables import TriviaScore - - -class TriviaCommand(Command): - name: str = "trivia" - - aliases = ["t"] - - description: str = "Manda una domanda dell'OpenTDB in chat." - - tables = {TriviaScore} - - syntax = "[credits|scores]" - - _letter_emojis = ["๐Ÿ‡ฆ", "๐Ÿ‡ง", "๐Ÿ‡จ", "๐Ÿ‡ฉ"] - - _medal_emojis = ["๐Ÿฅ‡", "๐Ÿฅˆ", "๐Ÿฅ‰", "๐Ÿ”น"] - - _correct_emoji = "โœ…" - - _wrong_emoji = "โŒ" - - _answer_time = 17 - - _question_lock: bool = False - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - self._answerers: typing.Dict[uuid.UUID, typing.Dict[str, bool]] = {} - - async def run(self, args: CommandArgs, data: CommandData) -> None: - arg = args.optional(0) - if arg == "credits": - await data.reply(f"โ„น๏ธ [c]{self.interface.prefix}{self.name}[/c] di [i]Steffo[/i]\n" - f"\n" - f"Tutte le domande vengono dall'[b]Open Trivia Database[/b] di [i]Pixeltail Games[/i]," - f" creatori di Tower Unite, e sono rilasciate sotto la licenza [b]CC BY-SA 4.0[/b].") - return - elif arg == "scores": - trivia_scores = await asyncify(data.session.query(self.alchemy.TriviaScore).all) - strings = ["๐Ÿ† [b]Trivia Leaderboards[/b]\n"] - for index, ts in enumerate(sorted(trivia_scores, key=lambda ts: -ts.correct_rate)): - if index > 3: - index = 3 - strings.append(f"{self._medal_emojis[index]} {ts.royal.username}" - f" ({ts.correct_answers}/{ts.total_answers})") - await data.reply("\n".join(strings)) - return - if self._question_lock: - raise CommandError("C'รจ giร  un'altra domanda attiva!") - self._question_lock = True - # Fetch the question - async with aiohttp.ClientSession() as session: - async with session.get("https://opentdb.com/api.php?amount=1") as response: - j = await response.json() - # Parse the question - if j["response_code"] != 0: - raise CommandError(f"OpenTDB returned an error response_code ({j['response_code']}).") - question = j["results"][0] - text = f'โ“ [b]{question["category"]} - {question["difficulty"].capitalize()}[/b]\n' \ - f'{html.unescape(question["question"])}' - # Prepare answers - correct_answer: str = question["correct_answer"] - wrong_answers: typing.List[str] = question["incorrect_answers"] - answers: typing.List[str] = [correct_answer, *wrong_answers] - if question["type"] == "multiple": - random.shuffle(answers) - elif question["type"] == "boolean": - answers.sort(key=lambda a: a) - answers.reverse() - else: - raise NotImplementedError("Unknown question type") - # Find the correct index - for index, answer in enumerate(answers): - if answer == correct_answer: - correct_index = index - break - else: - raise ValueError("correct_index not found") - # Add emojis - for index, answer in enumerate(answers): - answers[index] = f"{self._letter_emojis[index]} {html.unescape(answers[index])}" - # Create the question id - question_id = uuid.uuid4() - self._answerers[question_id] = {} - - # Create the correct and wrong functions - async def correct(data: CommandData): - answerer_ = await data.get_author(error_if_none=True) - try: - self._answerers[question_id][answerer_.uid] = True - except KeyError: - raise KeyboardExpiredError("Tempo scaduto!") - return "๐Ÿ†— Hai risposto alla domanda. Ora aspetta un attimo per i risultati!" - - async def wrong(data: CommandData): - answerer_ = await data.get_author(error_if_none=True) - try: - self._answerers[question_id][answerer_.uid] = False - except KeyError: - raise KeyboardExpiredError("Tempo scaduto!") - return "๐Ÿ†— Hai risposto alla domanda. Ora aspetta un attimo per i risultati!" - - # Add question - keyboard = {} - for index, answer in enumerate(answers): - if index == correct_index: - keyboard[answer] = correct - else: - keyboard[answer] = wrong - await data.keyboard(text, keyboard) - await asyncio.sleep(self._answer_time) - results = f"โ—๏ธ Tempo scaduto!\n" \ - f"La risposta corretta era [b]{answers[correct_index]}[/b]!\n\n" - for answerer_id in self._answerers[question_id]: - answerer = data.session.query(self.alchemy.User).get(answerer_id) - if answerer.trivia_score is None: - ts = self.interface.alchemy.TriviaScore(royal=answerer) - data.session.add(ts) - await asyncify(data.session.commit) - if self._answerers[question_id][answerer_id]: - results += self._correct_emoji - answerer.trivia_score.correct_answers += 1 - else: - results += self._wrong_emoji - answerer.trivia_score.wrong_answers += 1 - results += f" {answerer} ({answerer.trivia_score.correct_answers}/{answerer.trivia_score.total_answers})\n" - await data.reply(results) - del self._answerers[question_id] - await asyncify(data.session.commit) - self._question_lock = False diff --git a/royalnet/packs/royal/commands/videochannel.py b/royalnet/packs/royal/commands/videochannel.py deleted file mode 100644 index a09cd17a..00000000 --- a/royalnet/packs/royal/commands/videochannel.py +++ /dev/null @@ -1,50 +0,0 @@ -import typing -import discord -from royalnet.commands import * - - -class VideochannelCommand(Command): - name: str = "videochannel" - - aliases = ["golive", "live", "video"] - - description: str = "Converti il canale vocale in un canale video." - - syntax = "[channelname]" - - async def run(self, args: CommandArgs, data: CommandData) -> None: - if self.interface.name == "discord": - bot: discord.Client = self.interface.bot - message: discord.Message = data.message - channel_name: str = args.optional(0) - if channel_name: - guild: typing.Optional[discord.Guild] = message.guild - if guild is not None: - channels: typing.List[discord.abc.GuildChannel] = guild.channels - else: - channels = bot.get_all_channels() - matching_channels: typing.List[discord.VoiceChannel] = [] - for channel in channels: - if isinstance(channel, discord.VoiceChannel): - if channel.name == channel_name: - matching_channels.append(channel) - if len(matching_channels) == 0: - raise CommandError("Non esiste alcun canale vocale con il nome specificato.") - elif len(matching_channels) > 1: - raise CommandError("Esiste piรน di un canale vocale con il nome specificato.") - channel = matching_channels[0] - else: - author: discord.Member = message.author - voice: typing.Optional[discord.VoiceState] = author.voice - if voice is None: - raise CommandError("Non sei connesso a nessun canale vocale.") - channel = voice.channel - if author.is_on_mobile(): - await data.reply(f"๐Ÿ“น Per entrare in modalitร  video, clicca qui:\n" - f"\n" - f"[b]Attenzione: la modalitร  video non funziona su Android e iOS![/b]") - return - await data.reply(f"๐Ÿ“น Per entrare in modalitร  video, clicca qui:\n" - f"") - else: - raise UnsupportedError() diff --git a/royalnet/packs/royal/commands/youtube.py b/royalnet/packs/royal/commands/youtube.py deleted file mode 100644 index 04f28fec..00000000 --- a/royalnet/packs/royal/commands/youtube.py +++ /dev/null @@ -1,76 +0,0 @@ -import typing -import pickle -import datetime -import discord -from royalnet.commands import * -from royalnet.utils import asyncify -from royalnet.audio import YtdlDiscord -from royalnet.bots import DiscordBot - - -class YoutubeCommand(Command): - name: str = "youtube" - - aliases = ["yt"] - - description: str = "Cerca un video su YouTube e lo aggiunge alla coda della chat vocale." - - syntax = "[ [guild] ] {url}" - - @classmethod - async def _legacy_youtube_handler(cls, bot: "DiscordBot", guild_name: typing.Optional[str], search: str): - # Find the matching guild - if guild_name: - guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(guild_name) - else: - guilds = bot.client.guilds - if len(guilds) == 0: - raise CommandError("Server non trovato.") - if len(guilds) > 1: - raise CommandError("Il nome del server รจ ambiguo.") - guild = list(bot.client.guilds)[0] - # Ensure the guild has a PlayMode before adding the file to it - if not bot.music_data.get(guild): - raise CommandError("Il bot non รจ in nessun canale vocale.") - # Create url - ytdl_args = { - "format": "bestaudio/best", - "outtmpl": f"./downloads/{datetime.datetime.now().timestamp()}_%(title)s.%(ext)s" - } - # Start downloading - dfiles: typing. List[YtdlDiscord] = await asyncify(YtdlDiscord.create_from_url, f'ytsearch:{search}', **ytdl_args) - await bot.add_to_music_data(dfiles, guild) - # Create response dictionary - return { - "videos": [{ - "title": dfile.info.title, - "discord_embed_pickle": str(pickle.dumps(dfile.info.to_discord_embed())) - } for dfile in dfiles] - } - - _event_name = "_legacy_youtube" - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - if interface.name == "discord": - interface.register_herald_action(self._event_name, self._legacy_youtube_handler) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - guild_name, search = args.match(r"(?:\[(.+)])?\s*?") - if search.startswith("http://") or search.startswith("https://"): - raise CommandError(f"Il comando [c]{self.interface.prefix}youtube[/c] funziona solo per cercare video su" - f" YouTube con un dato nome.\n" - f"Se vuoi riprodurre una canzone da un URL, usa [c]{self.interface.prefix}play[/c]!") - response = await self.interface.call_herald_action("discord", self._event_name, { - "guild_name": guild_name, - "search": search - }) - if len(response["videos"]) == 0: - raise CommandError(f"Il video non puรฒ essere scaricato a causa di un blocco imposto da YouTube.") - for video in response["videos"]: - if self.interface.name == "discord": - # This is one of the unsafest things ever - embed = pickle.loads(eval(video["discord_embed_pickle"])) - await data.message.channel.send(content="โ–ถ๏ธ Aggiunto alla coda:", embed=embed) - else: - await data.reply(f"โ–ถ๏ธ Aggiunto alla coda: [i]{video['title']}[/i]") diff --git a/royalnet/packs/royal/commands/zawarudo.py b/royalnet/packs/royal/commands/zawarudo.py deleted file mode 100644 index 06f1c946..00000000 --- a/royalnet/packs/royal/commands/zawarudo.py +++ /dev/null @@ -1,91 +0,0 @@ -import typing -import discord -import asyncio -import datetime -from royalnet.commands import * -from royalnet.utils import asyncify -from royalnet.audio import YtdlDiscord -from royalnet.audio.playmodes import Playlist -from royalnet.bots import DiscordBot - - -class ZawarudoCommand(Command): - name: str = "zawarudo" - - aliases = ["theworld", "world"] - - description: str = "Ferma il tempo!" - - syntax = "[ [guild] ] [1-9]" - - @staticmethod - async def _legacy_zawarudo_handler(bot: "DiscordBot", guild_name: typing.Optional[str], time: int): - # Find the matching guild - if guild_name: - guilds: typing.List[discord.Guild] = bot.client.find_guild_by_name(guild_name) - else: - guilds = bot.client.guilds - if len(guilds) == 0: - raise CommandError("Server non trovato.") - if len(guilds) > 1: - raise CommandError("Il nome del server รจ ambiguo.") - guild = list(bot.client.guilds)[0] - # Ensure the guild has a PlayMode before adding the file to it - if not bot.music_data.get(guild): - raise CommandError("Il bot non รจ in nessun canale vocale.") - # Create url - ytdl_args = { - "format": "bestaudio", - "outtmpl": f"./downloads/{datetime.datetime.now().timestamp()}_%(title)s.%(ext)s" - } - # Start downloading - zw_start: typing.List[YtdlDiscord] = await asyncify(YtdlDiscord.create_from_url, - "https://scaleway.steffo.eu/jojo/zawarudo_intro.mp3", - **ytdl_args) - zw_end: typing.List[YtdlDiscord] = await asyncify(YtdlDiscord.create_from_url, - "https://scaleway.steffo.eu/jojo/zawarudo_outro.mp3", - **ytdl_args) - old_playlist = bot.music_data[guild] - bot.music_data[guild].playmode = Playlist() - # Get voice client - vc: discord.VoiceClient = bot.client.find_voice_client_by_guild(guild) - channel: discord.VoiceChannel = vc.channel - affected: typing.List[typing.Union[discord.User, discord.Member]] = channel.members - await bot.add_to_music_data(zw_start, guild) - for member in affected: - if member.bot: - continue - await member.edit(mute=True) - await asyncio.sleep(time) - await bot.add_to_music_data(zw_end, guild) - for member in affected: - member: typing.Union[discord.User, discord.Member] - if member.bot: - continue - await member.edit(mute=False) - bot.music_data[guild] = old_playlist - await bot.advance_music_data(guild) - return {} - - _event_name = "_legacy_zawarudo" - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - if interface.name == "discord": - interface.register_herald_action(self._event_name, self._legacy_zawarudo_handler) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - guild_name, time = args.match(r"(?:\[(.+)])?\s*(.+)?") - if time is None: - time = 5 - else: - time = int(time) - if time < 1: - raise InvalidInputError("The World can't stop time for less than a second.") - if time > 10: - raise InvalidInputError("The World can stop time only for 10 seconds.") - await data.reply(f"๐Ÿ•’ ZA WARUDO! TOKI WO TOMARE!") - await self.interface.call_herald_action("discord", self._event_name, { - "guild_name": guild_name, - "time": time - }) diff --git a/royalnet/packs/royal/stars/__init__.py b/royalnet/packs/royal/stars/__init__.py deleted file mode 100644 index 511a9101..00000000 --- a/royalnet/packs/royal/stars/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Imports go here! -from .api_user_list import ApiUserListStar -from .api_user_get import ApiUserGetStar -from .api_diario_list import ApiDiarioListStar -from .api_diario_get import ApiDiarioGetStar - -# Enter the PageStars of your Pack here! -available_page_stars = [ - ApiUserListStar, - ApiUserGetStar, - ApiDiarioListStar, - ApiDiarioGetStar, -] - -# Enter the ExceptionStars of your Pack here! -available_exception_stars = [ - -] - -# Don't change this, it should automatically generate __all__ -__all__ = [star.__name__ for star in [*available_page_stars, *available_exception_stars]] diff --git a/royalnet/packs/royal/stars/api_diario_get.py b/royalnet/packs/royal/stars/api_diario_get.py deleted file mode 100644 index 5c2ddf53..00000000 --- a/royalnet/packs/royal/stars/api_diario_get.py +++ /dev/null @@ -1,22 +0,0 @@ -from starlette.requests import Request -from starlette.responses import * -from royalnet.web import * -from royalnet.utils import * -from ..tables import Diario - - -class ApiDiarioGetStar(PageStar): - path = "/api/diario/get/{diario_id}" - tables = {Diario} - - async def page(self, request: Request) -> JSONResponse: - diario_id_str = request.path_params.get("diario_id", "") - try: - diario_id = int(diario_id_str) - except (ValueError, TypeError): - return error(400, "Invalid diario_id") - async with self.alchemy.session_acm() as session: - entry: Diario = await asyncify(session.query(self.alchemy.User).get, diario_id) - if entry is None: - return error(404, "No such user") - return JSONResponse(entry.json()) diff --git a/royalnet/packs/royal/stars/api_diario_list.py b/royalnet/packs/royal/stars/api_diario_list.py deleted file mode 100644 index 0b11af91..00000000 --- a/royalnet/packs/royal/stars/api_diario_list.py +++ /dev/null @@ -1,25 +0,0 @@ -from starlette.requests import Request -from starlette.responses import * -from royalnet.web import * -from royalnet.utils import * -from ..tables import Diario - - -class ApiDiarioListStar(PageStar): - path = "/api/diario/list" - tables = {Diario} - - async def page(self, request: Request) -> JSONResponse: - page_str = request.query_params.get("page", "0") - try: - page = int(page_str) - except (ValueError, TypeError): - return error(400, "Invalid offset") - async with self.alchemy.session_acm() as session: - if page < 0: - page = -page-1 - entries: typing.List[Diario] = await asyncify(session.query(self.alchemy.Diario).order_by(self.alchemy.Diario.diario_id.desc()).limit(500).offset(page * 500).all) - else: - entries: typing.List[Diario] = await asyncify(session.query(self.alchemy.Diario).order_by(self.alchemy.Diario.diario_id).limit(500).offset(page * 500).all) - response = [entry.json() for entry in entries] - return JSONResponse(response) diff --git a/royalnet/packs/royal/stars/api_user_get.py b/royalnet/packs/royal/stars/api_user_get.py deleted file mode 100644 index d547c7b6..00000000 --- a/royalnet/packs/royal/stars/api_user_get.py +++ /dev/null @@ -1,22 +0,0 @@ -from starlette.requests import Request -from starlette.responses import * -from royalnet.web import * -from royalnet.utils import * -from royalnet.packs.common.tables import User - - -class ApiUserGetStar(PageStar): - path = "/api/user/get/{uid_str}" - tables = {User} - - async def page(self, request: Request) -> JSONResponse: - uid_str = request.path_params.get("uid_str", "") - try: - uid = int(uid_str) - except (ValueError, TypeError): - return error(400, "Invalid uid") - async with self.alchemy.session_acm() as session: - user: User = await asyncify(session.query(self.alchemy.User).get, uid) - if user is None: - return error(404, "No such user") - return JSONResponse(user.json()) diff --git a/royalnet/packs/royal/stars/api_user_list.py b/royalnet/packs/royal/stars/api_user_list.py deleted file mode 100644 index 3b9a562e..00000000 --- a/royalnet/packs/royal/stars/api_user_list.py +++ /dev/null @@ -1,15 +0,0 @@ -from starlette.requests import Request -from starlette.responses import * -from royalnet.web import * -from royalnet.utils import * -from royalnet.packs.common.tables import User - - -class ApiUserListStar(PageStar): - path = "/api/user/list" - tables = {User} - - async def page(self, request: Request) -> JSONResponse: - async with self.alchemy.session_acm() as session: - users: typing.List[User] = await asyncify(session.query(self.alchemy.User).all) - return JSONResponse([user.json() for user in users]) diff --git a/royalnet/packs/royal/tables/__init__.py b/royalnet/packs/royal/tables/__init__.py deleted file mode 100644 index 30ce6f79..00000000 --- a/royalnet/packs/royal/tables/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -# Imports go here! -from royalnet.packs.common.tables import User -from royalnet.packs.common.tables import Telegram -from royalnet.packs.common.tables import Discord - -from .diario import Diario -from .aliases import Alias -from .wikipages import WikiPage -from .wikirevisions import WikiRevision -from .bios import Bio -from .reminders import Reminder -from .triviascores import TriviaScore -from .mmevents import MMEvent -from .mmresponse import MMResponse -from .leagueoflegends import LeagueOfLegends - -# Enter the tables of your Pack here! -available_tables = [ - User, - Telegram, - Discord, - Diario, - Alias, - WikiPage, - WikiRevision, - Bio, - Reminder, - TriviaScore, - MMEvent, - MMResponse, - LeagueOfLegends -] - -# Don't change this, it should automatically generate __all__ -__all__ = [table.__name__ for table in available_tables] diff --git a/royalnet/packs/royal/tables/aliases.py b/royalnet/packs/royal/tables/aliases.py deleted file mode 100644 index 2a1c99ec..00000000 --- a/royalnet/packs/royal/tables/aliases.py +++ /dev/null @@ -1,28 +0,0 @@ -from sqlalchemy import Column, \ - Integer, \ - String, \ - ForeignKey -from sqlalchemy.orm import relationship -from sqlalchemy.ext.declarative import declared_attr - - -class Alias: - __tablename__ = "aliases" - - @declared_attr - def royal_id(self): - return Column(Integer, ForeignKey("users.uid")) - - @declared_attr - def alias(self): - return Column(String, primary_key=True) - - @declared_attr - def royal(self): - return relationship("User", backref="aliases") - - def __repr__(self): - return f"" - - def __str__(self): - return f"{self.alias}->{self.royal_id}" diff --git a/royalnet/packs/royal/tables/bios.py b/royalnet/packs/royal/tables/bios.py deleted file mode 100644 index 25a3a630..00000000 --- a/royalnet/packs/royal/tables/bios.py +++ /dev/null @@ -1,28 +0,0 @@ -from sqlalchemy import Column, \ - Integer, \ - Text, \ - ForeignKey -from sqlalchemy.orm import relationship, backref -from sqlalchemy.ext.declarative import declared_attr - - -class Bio: - __tablename__ = "bios" - - @declared_attr - def royal_id(self): - return Column(Integer, ForeignKey("users.uid"), primary_key=True) - - @declared_attr - def royal(self): - return relationship("User", backref=backref("bio", uselist=False)) - - @declared_attr - def contents(self): - return Column(Text, nullable=False, default="") - - def __repr__(self): - return f"" - - def __str__(self): - return self.contents diff --git a/royalnet/packs/royal/tables/diario.py b/royalnet/packs/royal/tables/diario.py deleted file mode 100644 index 32769861..00000000 --- a/royalnet/packs/royal/tables/diario.py +++ /dev/null @@ -1,105 +0,0 @@ -import re -import datetime -from sqlalchemy import Column, \ - Integer, \ - Text, \ - Boolean, \ - DateTime, \ - ForeignKey, \ - String -from sqlalchemy.orm import relationship -from sqlalchemy.ext.declarative import declared_attr - - -class Diario: - __tablename__ = "diario" - - @declared_attr - def diario_id(self): - return Column(Integer, primary_key=True) - - @declared_attr - def creator_id(self): - return Column(Integer, ForeignKey("users.uid")) - - @declared_attr - def quoted_account_id(self): - return Column(Integer, ForeignKey("users.uid")) - - @declared_attr - def quoted(self): - return Column(String) - - @declared_attr - def text(self): - return Column(Text) - - @declared_attr - def context(self): - return Column(Text) - - @declared_attr - def timestamp(self) -> datetime.datetime: - return Column(DateTime, nullable=False) - - @declared_attr - def media_url(self): - return Column(String) - - @declared_attr - def spoiler(self): - return Column(Boolean, default=False) - - @declared_attr - def creator(self): - return relationship("User", foreign_keys=self.creator_id, backref="diario_created") - - @declared_attr - def quoted_account(self): - return relationship("User", foreign_keys=self.quoted_account_id, backref="diario_quoted") - - def json(self) -> dict: - return { - "diario_id": self.diario_id, - "creator": self.creator.json() if self.creator else None, - "quoted_account": self.quoted_account.json() if self.quoted_account else None, - "quoted": self.quoted, - "text": self.text, - "context": self.context, - "timestamp": self.timestamp.isoformat(), - "media_url": self.media_url, - "spoiler": self.spoiler - } - - def __repr__(self): - return f"" - - def __str__(self): - text = f"Riga #{self.diario_id}" - text += f" (salvata da {str(self.creator)}" - text += f" il {self.timestamp.strftime('%Y-%m-%d %H:%M')}):\n" - if self.media_url is not None: - text += f"{self.media_url}\n" - if self.text is not None: - if self.spoiler: - hidden = re.sub(r"\w", "โ–ˆ", self.text) - text += f"\"{hidden}\"\n" - else: - text += f"[b]\"{self.text}\"[/b]\n" - if self.quoted_account is not None: - text += f" โ€”{str(self.quoted_account)}" - elif self.quoted is not None: - text += f" โ€”{self.quoted}" - else: - text += f" โ€”Anonimo" - if self.context: - text += f", [i]{self.context}[/i]" - return text diff --git a/royalnet/packs/royal/tables/leagueoflegends.py b/royalnet/packs/royal/tables/leagueoflegends.py deleted file mode 100644 index 87763fc3..00000000 --- a/royalnet/packs/royal/tables/leagueoflegends.py +++ /dev/null @@ -1,256 +0,0 @@ -from sqlalchemy import * -from sqlalchemy.orm import relationship, composite -from sqlalchemy.ext.declarative import declared_attr -from ..utils import LeagueRank, LeagueTier, LeagueLeague - - -class LeagueOfLegends: - __tablename__ = "leagueoflegends" - - @declared_attr - def region(self): - return Column(String, nullable=False) - - @declared_attr - def user_id(self): - return Column(Integer, ForeignKey("users.uid")) - - @declared_attr - def user(self): - return relationship("User", backref="leagueoflegends") - - @declared_attr - def profile_icon_id(self): - # 3777 - return Column(Integer, nullable=False) - - @declared_attr - def summoner_name(self): - # SteffoRYG - return Column(String, nullable=False) - - @declared_attr - def puuid(self): - # iNW0i7w_cC2kxgNB13UhyGPeyxZChmRqKylZ--bzbZAhFM6EXAImUqeRWmGtK6iKiYbz3bkCV8fMQQ - return Column(String, nullable=False) - - @declared_attr - def summoner_level(self): - # 68 - return Column(Integer, nullable=False) - - @declared_attr - def summoner_id(self): - # aEsHyfXA2q8bK-g7GlT4kFK_0uLL3w-jBPyfMAy8kOXTJXo - return Column(String, nullable=False, primary_key=True) - - @declared_attr - def account_id(self): - # -2Ex-VpkkNBN4ceQev8oJsamxY5iGb2liRUqkES5TU_7vtI - return Column(String, nullable=False) - - @declared_attr - def rank_soloq_tier(self): - return Column(Enum(LeagueTier)) - - @declared_attr - def rank_soloq_rank(self): - return Column(Enum(LeagueRank)) - - @declared_attr - def rank_soloq_points(self): - return Column(Integer) - - @declared_attr - def rank_soloq_wins(self): - return Column(Integer) - - @declared_attr - def rank_soloq_losses(self): - return Column(Integer) - - @declared_attr - def rank_soloq_inactive(self): - return Column(Boolean) - - @declared_attr - def rank_soloq_hot_streak(self): - return Column(Boolean) - - @declared_attr - def rank_soloq_fresh_blood(self): - return Column(Boolean) - - @declared_attr - def rank_soloq_veteran(self): - return Column(Boolean) - - @declared_attr - def rank_soloq(self): - return composite(LeagueLeague, - self.rank_soloq_tier, - self.rank_soloq_rank, - self.rank_soloq_points, - self.rank_soloq_wins, - self.rank_soloq_losses, - self.rank_soloq_inactive, - self.rank_soloq_hot_streak, - self.rank_soloq_fresh_blood, - self.rank_soloq_veteran) - - @declared_attr - def rank_flexq_tier(self): - return Column(Enum(LeagueTier)) - - @declared_attr - def rank_flexq_rank(self): - return Column(Enum(LeagueRank)) - - @declared_attr - def rank_flexq_points(self): - return Column(Integer) - - @declared_attr - def rank_flexq_wins(self): - return Column(Integer) - - @declared_attr - def rank_flexq_losses(self): - return Column(Integer) - - @declared_attr - def rank_flexq_inactive(self): - return Column(Boolean) - - @declared_attr - def rank_flexq_hot_streak(self): - return Column(Boolean) - - @declared_attr - def rank_flexq_fresh_blood(self): - return Column(Boolean) - - @declared_attr - def rank_flexq_veteran(self): - return Column(Boolean) - - @declared_attr - def rank_flexq(self): - return composite(LeagueLeague, - self.rank_flexq_tier, - self.rank_flexq_rank, - self.rank_flexq_points, - self.rank_flexq_wins, - self.rank_flexq_losses, - self.rank_flexq_inactive, - self.rank_flexq_hot_streak, - self.rank_flexq_fresh_blood, - self.rank_flexq_veteran) - - @declared_attr - def rank_twtrq_tier(self): - return Column(Enum(LeagueTier)) - - @declared_attr - def rank_twtrq_rank(self): - return Column(Enum(LeagueRank)) - - @declared_attr - def rank_twtrq_points(self): - return Column(Integer) - - @declared_attr - def rank_twtrq_wins(self): - return Column(Integer) - - @declared_attr - def rank_twtrq_losses(self): - return Column(Integer) - - @declared_attr - def rank_twtrq_inactive(self): - return Column(Boolean) - - @declared_attr - def rank_twtrq_hot_streak(self): - return Column(Boolean) - - @declared_attr - def rank_twtrq_fresh_blood(self): - return Column(Boolean) - - @declared_attr - def rank_twtrq_veteran(self): - return Column(Boolean) - - @declared_attr - def rank_twtrq(self): - return composite(LeagueLeague, - self.rank_twtrq_tier, - self.rank_twtrq_rank, - self.rank_twtrq_points, - self.rank_twtrq_wins, - self.rank_twtrq_losses, - self.rank_twtrq_inactive, - self.rank_twtrq_hot_streak, - self.rank_twtrq_fresh_blood, - self.rank_twtrq_veteran) - - @declared_attr - def rank_tftq_tier(self): - return Column(Enum(LeagueTier)) - - @declared_attr - def rank_tftq_rank(self): - return Column(Enum(LeagueRank)) - - @declared_attr - def rank_tftq_points(self): - return Column(Integer) - - @declared_attr - def rank_tftq_wins(self): - return Column(Integer) - - @declared_attr - def rank_tftq_losses(self): - return Column(Integer) - - @declared_attr - def rank_tftq_inactive(self): - return Column(Boolean) - - @declared_attr - def rank_tftq_hot_streak(self): - return Column(Boolean) - - @declared_attr - def rank_tftq_fresh_blood(self): - return Column(Boolean) - - @declared_attr - def rank_tftq_veteran(self): - return Column(Boolean) - - @declared_attr - def rank_tftq(self): - return composite(LeagueLeague, - self.rank_tftq_tier, - self.rank_tftq_rank, - self.rank_tftq_points, - self.rank_tftq_wins, - self.rank_tftq_losses, - self.rank_tftq_inactive, - self.rank_tftq_hot_streak, - self.rank_tftq_fresh_blood, - self.rank_tftq_veteran) - - @declared_attr - def mastery_score(self): - return Column(Integer, nullable=False, default=0) - - def __repr__(self): - return f"<{self.__class__.__qualname__} {str(self)}>" - - def __str__(self): - return f"[c]{self.__tablename__}:{self.summoner_name}[/c]" diff --git a/royalnet/packs/royal/tables/mmevents.py b/royalnet/packs/royal/tables/mmevents.py deleted file mode 100644 index f633c0f8..00000000 --- a/royalnet/packs/royal/tables/mmevents.py +++ /dev/null @@ -1,52 +0,0 @@ -import pickle -from sqlalchemy import * -from sqlalchemy.orm import relationship -from sqlalchemy.ext.declarative import declared_attr - - -class MMEvent: - __tablename__ = "mmevents" - - @declared_attr - def creator_id(self): - return Column(Integer, ForeignKey("users.uid"), nullable=False) - - @declared_attr - def creator(self): - return relationship("User", backref="mmevents_created") - - @declared_attr - def mmid(self): - return Column(Integer, primary_key=True) - - @declared_attr - def datetime(self): - return Column(DateTime, nullable=False) - - @declared_attr - def title(self): - return Column(String, nullable=False) - - @declared_attr - def description(self): - return Column(Text, nullable=False, default="") - - @declared_attr - def interface(self): - return Column(String, nullable=False) - - @declared_attr - def raw_interface_data(self): - # The default is a pickled None - return Column(Binary, nullable=False, default=b'\x80\x03N.') - - @property - def interface_data(self): - return pickle.loads(self.raw_interface_data) - - @interface_data.setter - def interface_data(self, value): - self.raw_interface_data = pickle.dumps(value) - - def __repr__(self): - return f"" diff --git a/royalnet/packs/royal/tables/mmresponse.py b/royalnet/packs/royal/tables/mmresponse.py deleted file mode 100644 index 4d04931a..00000000 --- a/royalnet/packs/royal/tables/mmresponse.py +++ /dev/null @@ -1,31 +0,0 @@ -from sqlalchemy import * -from sqlalchemy.orm import relationship -from sqlalchemy.ext.declarative import declared_attr -from ..utils import MMChoice - - -class MMResponse: - __tablename__ = "mmresponse" - - @declared_attr - def user_id(self): - return Column(Integer, ForeignKey("users.uid"), primary_key=True) - - @declared_attr - def user(self): - return relationship("User", backref="mmresponses_given") - - @declared_attr - def mmevent_id(self): - return Column(Integer, ForeignKey("mmevents.mmid"), primary_key=True) - - @declared_attr - def mmevent(self): - return relationship("MMEvent", backref="responses") - - @declared_attr - def choice(self): - return Column(Enum(MMChoice), nullable=False) - - def __repr__(self): - return f"" diff --git a/royalnet/packs/royal/tables/reminders.py b/royalnet/packs/royal/tables/reminders.py deleted file mode 100644 index 17f94302..00000000 --- a/royalnet/packs/royal/tables/reminders.py +++ /dev/null @@ -1,46 +0,0 @@ -from sqlalchemy import Column, \ - Integer, \ - String, \ - LargeBinary, \ - DateTime, \ - ForeignKey -from sqlalchemy.orm import relationship -from sqlalchemy.ext.declarative import declared_attr - - -class Reminder: - __tablename__ = "reminder" - - @declared_attr - def reminder_id(self): - return Column(Integer, primary_key=True) - - @declared_attr - def creator_id(self): - return Column(Integer, ForeignKey("users.uid")) - - @declared_attr - def creator(self): - return relationship("User", backref="reminders_created") - - @declared_attr - def interface_name(self): - return Column(String) - - @declared_attr - def interface_data(self): - return Column(LargeBinary) - - @declared_attr - def datetime(self): - return Column(DateTime) - - @declared_attr - def message(self): - return Column(String) - - def __repr__(self): - return f"" - - def __str__(self): - return self.message diff --git a/royalnet/packs/royal/tables/triviascores.py b/royalnet/packs/royal/tables/triviascores.py deleted file mode 100644 index 39d251ae..00000000 --- a/royalnet/packs/royal/tables/triviascores.py +++ /dev/null @@ -1,40 +0,0 @@ -from sqlalchemy import Column, \ - Integer, \ - ForeignKey -from sqlalchemy.orm import relationship, backref -from sqlalchemy.ext.declarative import declared_attr - - -class TriviaScore: - __tablename__ = "triviascores" - - @declared_attr - def royal_id(self): - return Column(Integer, ForeignKey("users.uid"), primary_key=True) - - @declared_attr - def royal(self): - return relationship("User", backref=backref("trivia_score", uselist=False)) - - @declared_attr - def correct_answers(self): - return Column(Integer, nullable=False, default=0) - - @declared_attr - def wrong_answers(self): - return Column(Integer, nullable=False, default=0) - - @property - def total_answers(self): - return self.correct_answers + self.wrong_answers - - @property - def offset(self): - return self.correct_answers - self.wrong_answers - - @property - def correct_rate(self): - return self.correct_answers / self.total_answers - - def __repr__(self): - return f"" diff --git a/royalnet/packs/royal/tables/wikipages.py b/royalnet/packs/royal/tables/wikipages.py deleted file mode 100644 index 91719fc8..00000000 --- a/royalnet/packs/royal/tables/wikipages.py +++ /dev/null @@ -1,38 +0,0 @@ -from sqlalchemy import Column, \ - Text, \ - String -from sqlalchemy.dialects.postgresql import UUID -from sqlalchemy.ext.declarative import declared_attr -from royalnet.utils import to_urluuid - - -class WikiPage: - """Wiki page properties. - - Warning: - Requires PostgreSQL!""" - __tablename__ = "wikipages" - - @declared_attr - def page_id(self): - return Column(UUID(as_uuid=True), primary_key=True) - - @declared_attr - def title(self): - return Column(String, nullable=False) - - @declared_attr - def contents(self): - return Column(Text) - - @declared_attr - def format(self): - return Column(String, nullable=False, default="markdown") - - @declared_attr - def css(self): - return Column(String) - - @property - def page_short_id(self): - return to_urluuid(self.page_id) diff --git a/royalnet/packs/royal/tables/wikirevisions.py b/royalnet/packs/royal/tables/wikirevisions.py deleted file mode 100644 index 456376d7..00000000 --- a/royalnet/packs/royal/tables/wikirevisions.py +++ /dev/null @@ -1,48 +0,0 @@ -from sqlalchemy import Column, \ - Integer, \ - Text, \ - DateTime, \ - ForeignKey -from sqlalchemy.dialects.postgresql import UUID -from sqlalchemy.orm import relationship -from sqlalchemy.ext.declarative import declared_attr - - -class WikiRevision: - """A wiki page revision. - - Warning: - Requires PostgreSQL!""" - __tablename__ = "wikirevisions" - - @declared_attr - def revision_id(self): - return Column(UUID(as_uuid=True), primary_key=True) - - @declared_attr - def page_id(self): - return Column(UUID(as_uuid=True), ForeignKey("wikipages.page_id"), nullable=False) - - @declared_attr - def page(self): - return relationship("WikiPage", foreign_keys=self.page_id, backref="revisions") - - @declared_attr - def author_id(self): - return Column(Integer, ForeignKey("users.uid"), nullable=False) - - @declared_attr - def author(self): - return relationship("User", foreign_keys=self.author_id, backref="wiki_contributions") - - @declared_attr - def timestamp(self): - return Column(DateTime, nullable=False) - - @declared_attr - def reason(self): - return Column(Text) - - @declared_attr - def diff(self): - return Column(Text) diff --git a/royalnet/packs/royal/utils/__init__.py b/royalnet/packs/royal/utils/__init__.py deleted file mode 100644 index aaab3673..00000000 --- a/royalnet/packs/royal/utils/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .mmchoice import MMChoice -from .mminterfacedata import MMInterfaceData, MMInterfaceDataTelegram -from .leaguetier import LeagueTier -from .leaguerank import LeagueRank -from .leagueleague import LeagueLeague - -__all__ = ["MMChoice", "MMInterfaceData", "MMInterfaceDataTelegram", "LeagueTier", "LeagueRank", "LeagueLeague"] diff --git a/royalnet/packs/royal/utils/leagueleague.py b/royalnet/packs/royal/utils/leagueleague.py deleted file mode 100644 index 96d17523..00000000 --- a/royalnet/packs/royal/utils/leagueleague.py +++ /dev/null @@ -1,134 +0,0 @@ -from .leaguetier import LeagueTier -from .leaguerank import LeagueRank - - -class LeagueLeague: - def __init__(self, - tier: LeagueTier = None, - rank: LeagueRank = None, - points: int = None, - wins: int = None, - losses: int = None, - inactive: bool = None, - hot_streak: bool = None, - fresh_blood: bool = None, - veteran: bool = None): - self.tier: LeagueTier = tier # IRON - self.rank: LeagueRank = rank # I - self.points: int = points # 40 LP - self.wins: int = wins - self.losses: int = losses - self.inactive: bool = inactive - self.hot_streak: bool = hot_streak - self.fresh_blood: bool = fresh_blood - self.veteran: bool = veteran - - def __str__(self) -> str: - emojis = "" - if self.veteran: - emojis += "๐Ÿ†" - if self.hot_streak: - emojis += "๐Ÿ”ฅ" - if self.fresh_blood: - emojis += "โญ๏ธ" - return f"[b]{self.tier} {self.rank}[/b] ({self.points} LP){' ' if emojis else ''}{emojis}" - - def __repr__(self) -> str: - return f"<{self.__class__.__qualname__} {self}>" - - def __eq__(self, other) -> bool: - if other is None: - return False - if not isinstance(other, LeagueLeague): - raise TypeError(f"Can't compare {self.__class__.__qualname__} with {other.__class__.__qualname__}") - equal = True - if other.veteran: - equal &= self.veteran == other.veteran - if other.fresh_blood: - equal &= self.fresh_blood == other.fresh_blood - if other.hot_streak: - equal &= self.hot_streak == other.hot_streak - if other.inactive: - equal &= self.inactive == other.inactive - if other.losses: - equal &= self.losses == other.losses - if other.wins: - equal &= self.wins == other.wins - if other.points: - equal &= self.points == other.points - if other.rank: - equal &= self.rank == other.rank - if other.tier: - equal &= self.tier == other.tier - return equal - - def __ne__(self, other) -> bool: - return not self.__eq__(other) - - def __gt__(self, other) -> bool: - if other is None: - return True - if not isinstance(other, LeagueLeague): - raise TypeError(f"Can't compare {self.__class__.__qualname__} with {other.__class__.__qualname__}") - if not (bool(self) and bool(other)): - raise ValueError("Can't compare partial LeagueLeagues.") - if self.tier != other.tier: - # Silver is better than Bronze - return self.tier > other.tier - elif self.rank != other.rank: - # Silver I is better than Silver IV - return self.rank > other.rank - elif self.points != other.points: - # Silver I (100 LP) is better than Silver I (0 LP) - return self.points > other.points - elif self.winrate != other.winrate: - # Silver I (100 LP with 60% winrate) is better than Silver I (100 LP with 40% winrate) - return self.winrate > other.winrate - else: - return False - - def __bool__(self): - result = True - result &= self.veteran is not None - result &= self.fresh_blood is not None - result &= self.hot_streak is not None - result &= self.inactive is not None - result &= self.losses is not None - result &= self.wins is not None - result &= self.points is not None - result &= self.rank is not None - result &= self.tier is not None - return result - - def __composite_values__(self): - return self.tier, \ - self.rank, \ - self.points, \ - self.wins, \ - self.losses, \ - self.inactive, \ - self.hot_streak, \ - self.fresh_blood, \ - self.veteran - - @property - def played(self): - return self.wins + self.losses - - @property - def winrate(self): - return self.wins / self.played - - @classmethod - def from_dict(cls, d: dict): - return cls( - tier=LeagueTier.from_string(d["tier"]), - rank=LeagueRank.from_string(d["rank"]), - points=d["leaguePoints"], - wins=d["wins"], - losses=d["losses"], - inactive=d["inactive"], - hot_streak=d["hotStreak"], - fresh_blood=d["freshBlood"], - veteran=d["veteran"], - ) diff --git a/royalnet/packs/royal/utils/leaguerank.py b/royalnet/packs/royal/utils/leaguerank.py deleted file mode 100644 index ede917d7..00000000 --- a/royalnet/packs/royal/utils/leaguerank.py +++ /dev/null @@ -1,21 +0,0 @@ -import enum - - -class LeagueRank(enum.Enum): - I = 1 - II = 2 - III = 3 - IV = 4 - - def __str__(self): - return self.name - - def __repr__(self): - return f"{self.__class__.__qualname__}.{self.name}" - - def __gt__(self, other): - return self.value < other.value - - @classmethod - def from_string(cls, string: str): - return cls.__members__.get(string) diff --git a/royalnet/packs/royal/utils/leaguetier.py b/royalnet/packs/royal/utils/leaguetier.py deleted file mode 100644 index 011b290a..00000000 --- a/royalnet/packs/royal/utils/leaguetier.py +++ /dev/null @@ -1,26 +0,0 @@ -import enum - - -class LeagueTier(enum.Enum): - IRON = 0 - BRONZE = 1 - SILVER = 2 - GOLD = 3 - PLATINUM = 4 - DIAMOND = 5 - MASTER = 6 - GRANDMASTER = 7 - CHALLENGER = 8 - - def __str__(self): - return self.name.capitalize() - - def __repr__(self): - return f"{self.__class__.__qualname__}.{self.name}" - - def __gt__(self, other): - return self.value > other.value - - @classmethod - def from_string(cls, string: str): - return cls.__members__.get(string) diff --git a/royalnet/packs/royal/utils/mmchoice.py b/royalnet/packs/royal/utils/mmchoice.py deleted file mode 100644 index 5f54a507..00000000 --- a/royalnet/packs/royal/utils/mmchoice.py +++ /dev/null @@ -1,12 +0,0 @@ -import enum - - -class MMChoice(enum.Enum): - YES = "๐Ÿ”ต" - MAYBE = "โ”" - LATE_SHORT = "๐Ÿ•" - LATE_MEDIUM = "๐Ÿ•’" - LATE_LONG = "๐Ÿ•—" - NO_TIME = "๐Ÿ”ด" - NO_INTEREST = "โŒ" - NO_TECH = "โ—๏ธ" diff --git a/royalnet/packs/royal/utils/mminterfacedata.py b/royalnet/packs/royal/utils/mminterfacedata.py deleted file mode 100644 index 4cfd6d32..00000000 --- a/royalnet/packs/royal/utils/mminterfacedata.py +++ /dev/null @@ -1,10 +0,0 @@ -class MMInterfaceData: - def __init__(self): - pass - - -class MMInterfaceDataTelegram(MMInterfaceData): - def __init__(self, chat_id: int, message_id: int): - super().__init__() - self.chat_id = chat_id - self.message_id = message_id diff --git a/royalnet/packs/rpg/__init__.py b/royalnet/packs/rpg/__init__.py deleted file mode 100644 index feb329ab..00000000 --- a/royalnet/packs/rpg/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# This is a template Pack __init__. You can use this without changing anything in other packages too! - -from . import commands, tables, stars -from .commands import available_commands -from .tables import available_tables -from .stars import available_page_stars, available_exception_stars - -__all__ = [ - "commands", - "tables", - "stars", - "available_commands", - "available_tables", - "available_page_stars", - "available_exception_stars", -] diff --git a/royalnet/packs/rpg/commands/__init__.py b/royalnet/packs/rpg/commands/__init__.py deleted file mode 100644 index aa045b90..00000000 --- a/royalnet/packs/rpg/commands/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# Imports go here! -from .roll import RollCommand -from .dice import DiceCommand -from .dndactive import DndactiveCommand -from .dndinfo import DndinfoCommand -from .dndnew import DndnewCommand -from .dndedit import DndeditCommand -from .dndroll import DndrollCommand -from .dnditem import DnditemCommand -from .dndspell import DndspellCommand - -# Enter the commands of your Pack here! -available_commands = [ - RollCommand, - DiceCommand, - DndactiveCommand, - DndinfoCommand, - DndnewCommand, - DndeditCommand, - DndrollCommand, - DnditemCommand, - DndspellCommand, -] - -# Don't change this, it should automatically generate __all__ -__all__ = [command.__name__ for command in available_commands] diff --git a/royalnet/packs/rpg/commands/dice.py b/royalnet/packs/rpg/commands/dice.py deleted file mode 100644 index f75d8388..00000000 --- a/royalnet/packs/rpg/commands/dice.py +++ /dev/null @@ -1,40 +0,0 @@ -import dice -from royalnet.commands import * - - -class DiceCommand(Command): - name: str = "dice" - - description: str = "Roll a dice, using 'dice'." - - syntax = "{dice}" - - aliases = ["d"] - - async def run(self, args: CommandArgs, data: CommandData) -> None: - dice_str = args.joined(require_at_least=1) - try: - roll = dice.roll(dice_str) - except dice.DiceFatalException as e: - raise CommandError(e.msg) - except dice.DiceException as e: - raise CommandError(e.msg) - except dice.DiceBaseException as e: - raise CommandError(str(e)) - try: - result = list(roll) - except TypeError: - result = [roll] - message = f"๐ŸŽฒ {dice_str}" - total = 0 - if len(result) > 1: - message += f" = " - for index, die in enumerate(result): - message += f"{die}" - total += int(die) - if (index + 1) < len(result): - message += "+" - else: - total += int(result[0]) - message += f" = [b]{total}[/b]" - await data.reply(message) diff --git a/royalnet/packs/rpg/commands/dndactive.py b/royalnet/packs/rpg/commands/dndactive.py deleted file mode 100644 index 876b1369..00000000 --- a/royalnet/packs/rpg/commands/dndactive.py +++ /dev/null @@ -1,56 +0,0 @@ -from royalnet.commands import * -from royalnet.utils import asyncify -from ..tables import DndCharacter, DndActiveCharacter - - -class DndactiveCommand(Command): - name: str = "dndactive" - - description: str = "Set a DnD character as active." - - aliases = ["da", "dnda", "active", "dactive"] - - syntax = "{name|id}" - - tables = {DndCharacter, DndActiveCharacter} - - async def run(self, args: CommandArgs, data: CommandData) -> None: - identifier = args.optional(0) - author = await data.get_author(error_if_none=True) - if identifier is None: - # Display the active character - if author.dnd_active_character is None: - await data.reply("โ„น๏ธ You have no active characters.") - else: - await data.reply(f"โ„น๏ธ You currently active character is [b]{author.dnd_active_character}[/b].") - return - try: - identifier = int(identifier) - except ValueError: - # Find the character by name - chars = await asyncify(data.session.query(self.alchemy.DndCharacter).filter_by(name=identifier).all) - if len(chars) >= 2: - char_string = "\n".join([f"[c]{char.character_id}[/c] (LV {char.level}) by {char.creator})" for char in chars]) - raise CommandError(f"Multiple characters share the name {identifier}, " - f"please activate them using their id:\n{char_string}") - elif len(chars) == 1: - char = chars[0] - else: - char = None - else: - # Find the character by id - char = await asyncify(data.session.query(self.alchemy.DndCharacter) - .filter_by(character_id=identifier) - .one_or_none) - if char is None: - raise CommandError("No character found.") - # Check if the player already has an active character - if author.dnd_active_character is None: - # Create a new active character - achar = self.alchemy.DndActiveCharacter(character=char, user=author) - data.session.add(achar) - else: - # Change the active character - author.dnd_active_character.character = char - await data.session_commit() - await data.reply(f"โœ… Active character set to [b]{char}[/b]!") diff --git a/royalnet/packs/rpg/commands/dndedit.py b/royalnet/packs/rpg/commands/dndedit.py deleted file mode 100644 index 918e431d..00000000 --- a/royalnet/packs/rpg/commands/dndedit.py +++ /dev/null @@ -1,35 +0,0 @@ -import re -from royalnet.commands import * -from .dndnew import DndnewCommand -from ..tables import DndCharacter, DndActiveCharacter - - -class DndeditCommand(DndnewCommand): - name: str = "dndedit" - - description: str = "Edit the active DnD character." - - aliases = ["de", "dnde", "edit", "dedit"] - - tables = {DndCharacter, DndActiveCharacter} - - async def run(self, args: CommandArgs, data: CommandData) -> None: - character_sheet = args.joined() - - if character_sheet == "": - await data.reply(self._syntax()) - return - - author = await data.get_author(error_if_none=True) - if author.dnd_active_character is None: - raise CommandError("You don't have an active character.") - - char: DndCharacter = author.dnd_active_character.character - - arguments = self._parse(character_sheet) - for key in arguments: - char.__setattr__(key, arguments[key]) - - await data.session_commit() - - await data.reply(f"โœ… Edit successful!") diff --git a/royalnet/packs/rpg/commands/dndinfo.py b/royalnet/packs/rpg/commands/dndinfo.py deleted file mode 100644 index 865955db..00000000 --- a/royalnet/packs/rpg/commands/dndinfo.py +++ /dev/null @@ -1,19 +0,0 @@ -from royalnet.commands import * -from royalnet.utils import asyncify -from ..tables import DndCharacter, DndActiveCharacter - - -class DndinfoCommand(Command): - name: str = "dndinfo" - - description: str = "Display the character sheet of the active DnD character." - - aliases = ["di", "dndi", "info", "dinfo"] - - tables = {DndCharacter, DndActiveCharacter} - - async def run(self, args: CommandArgs, data: CommandData) -> None: - author = await data.get_author(error_if_none=True) - if author.dnd_active_character is None: - raise CommandError("You don't have an active character.") - await data.reply(author.dnd_active_character.character.character_sheet()) diff --git a/royalnet/packs/rpg/commands/dnditem.py b/royalnet/packs/rpg/commands/dnditem.py deleted file mode 100644 index 3aa470b3..00000000 --- a/royalnet/packs/rpg/commands/dnditem.py +++ /dev/null @@ -1,56 +0,0 @@ -import aiohttp -import sortedcontainers -from royalnet.commands import * -from ..utils import parse_5etools_entry - - -class DnditemCommand(Command): - name: str = "dnditem" - - aliases = ["item"] - - description: str = "Ottieni informazioni su un oggetto di D&D5e." - - syntax = "{nomeoggetto}" - - _dnddata: sortedcontainers.SortedKeyList = None - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - interface.loop.create_task(self._fetch_dnddata()) - - async def _fetch_dnddata(self): - self._dnddata = self._dnddata = sortedcontainers.SortedKeyList([], key=lambda i: i["name"].lower()) - async with aiohttp.ClientSession() as session: - async with session.get("https://5e.tools/data/items.json") as response: - j = await response.json() - for item in j["item"]: - self._dnddata.add(item) - async with session.get("https://5e.tools/data/fluff-items.json") as response: - j = await response.json() - for item in j["item"]: - self._dnddata.add(item) - async with session.get("https://5e.tools/data/items-base.json") as response: - j = await response.json() - for item in j["baseitem"]: - self._dnddata.add(item) - - async def run(self, args: CommandArgs, data: CommandData) -> None: - if self._dnddata is None: - await data.reply("โš ๏ธ Il database degli oggetti di D&D non รจ ancora stato scaricato.") - return - search = args.joined().lower() - result = self._dnddata[self._dnddata.bisect_key_left(search)] - string = f'๐Ÿ“ฆ [b]{result["name"]}[/b]\n' - if "source" in result: - string += f'[i]{result["source"]}, page {result["page"]}[/i]\n' - string += f'\n' \ - f'Type: [b]{result.get("type", "None")}[/b]\n' \ - f'Value: [b]{result.get("value", "-")}[/b]\n' \ - f'Weight: [b]{result.get("weight", "0")} lb[/b]\n' \ - f'Rarity: [b]{result["rarity"] if result.get("rarity", "None") != "None" else "Mundane"}[/b]\n' \ - f'\n' - for entry in result.get("entries", []): - string += parse_5etools_entry(entry) - string += "\n\n" - await data.reply(string) diff --git a/royalnet/packs/rpg/commands/dndnew.py b/royalnet/packs/rpg/commands/dndnew.py deleted file mode 100644 index 28754404..00000000 --- a/royalnet/packs/rpg/commands/dndnew.py +++ /dev/null @@ -1,71 +0,0 @@ -import re -# noinspection PyUnresolvedReferences -from royalnet.commands import * -from ..tables import DndCharacter -from ..utils import DndProficiencyType - - -class DndnewCommand(Command): - name: str = "dndnew" - - description: str = "Create a new DnD character." - - aliases = ["dn", "dndn", "new", "dnew"] - - syntax = "{name}\n{character_sheet}" - - tables = {DndCharacter} - - def _search_value(self, name: str, string: str): - return re.search(r"\s*" + name + r"\s*([0-9]+)\s*", string, re.IGNORECASE) - - def _parse(self, character_sheet: str) -> dict: - columns = list(self.alchemy.DndCharacter.__table__.columns) - column_names = [column.name for column in columns if (not column.primary_key and - not column.foreign_keys and - column.name != "name")] - arguments = {} - for column_name in column_names: - match = self._search_value(column_name, character_sheet) - if match: - if column_name.endswith("_proficiency"): - arguments[column_name] = DndProficiencyType(float(match.group(1))) - else: - arguments[column_name] = match.group(1) - return arguments - - def _syntax(self) -> str: - columns = list(self.alchemy.DndCharacter.__table__.columns) - column_names = [column.name for column in columns if (not column.primary_key and - not column.foreign_keys and - column.name != "name")] - message = "โ„น๏ธ How to create a new character:\n[p]/dndnew YOUR_CHARACTER_NAME\n" - for column_name in column_names: - message += f"{column_name} _\n" - message += "[/p]" - return message - - async def run(self, args: CommandArgs, data: CommandData) -> None: - character_sheet = args.joined() - - if character_sheet == "": - await data.reply(self._syntax()) - return - - creator = await data.get_author() - - name, rest = character_sheet.split("\n", 1) - - character = self.alchemy.DndCharacter(name=name, creator=creator, **self._parse(rest)) - data.session.add(character) - - try: - await data.session_commit() - except Exception as err: - # THIS IS INTENDED - if err.__class__.__name__ == "IntegrityError": - param_name = re.search(r'in column "(\S+)"', err.args[0]).group(1) - raise CommandError(f"Mandatory parameter '{param_name}' is missing.") - raise - - await data.reply(f"โœ… Character [b]{character.name}[/b] created!") diff --git a/royalnet/packs/rpg/commands/dndroll.py b/royalnet/packs/rpg/commands/dndroll.py deleted file mode 100644 index 2daf07f1..00000000 --- a/royalnet/packs/rpg/commands/dndroll.py +++ /dev/null @@ -1,146 +0,0 @@ -import re -import random -from royalnet.commands import * -from ..tables import DndCharacter, DndActiveCharacter -from royalnet.utils import plusformat - - -class DndrollCommand(Command): - name: str = "dndroll" - - description: str = "Roll dice as the active DnD character." - - aliases = ["dr", "dndr", "roll", "droll"] - - tables = {DndCharacter, DndActiveCharacter} - - _skill_names = { - "str": "strength", - "for": "strength", - "dex": "dexterity", - "des": "dexterity", - "con": "constitution", - "cos": "constitution", - "inte": "intelligence", - "wis": "wisdom", - "sag": "wisdom", - "cha": "charisma", - "car": "charisma", - - "ststr": "strength_save", - "stfor": "strength_save", - "stdex": "dexterity_save", - "stdes": "dexterity_save", - "stcon": "constitution_save", - "stcos": "constitution_save", - "stint": "intelligence_save", - "stwis": "wisdom_save", - "stsag": "wisdom_save", - "stcha": "charisma_save", - "stcar": "charisma_save", - - "tsstr": "strength_save", - "tsfor": "strength_save", - "tsdex": "dexterity_save", - "tsdes": "dexterity_save", - "tscon": "constitution_save", - "tscos": "constitution_save", - "tsint": "intelligence_save", - "tswis": "wisdom_save", - "tssag": "wisdom_save", - "tscha": "charisma_save", - "tscar": "charisma_save", - - "acr": "acrobatics", - "add": "animal_handling", - "ani": "animal_handling", - "arc": "arcana", - "ath": "athletics", - "dec": "deception", - "ing": "deception", - "his": "history", - "sto": "history", - "ins": "insight", - "intu": "insight", - "inti": "intimidation", - "inv": "investigation", - "med": "medicine", - "nat": "nature", - "perc": "perception", - "perf": "performance", - "pers": "persuasion", - "rel": "religion", - "sle": "sleight_of_hand", - "soh": "sleight_of_hand", - "rap": "sleight_of_hand", - "ste": "stealth", - "nas": "stealth", - "sur": "survival", - "sop": "sopravvivenza", - } - - async def run(self, args: CommandArgs, data: CommandData) -> None: - author = await data.get_author(error_if_none=True) - if author.dnd_active_character is None: - raise CommandError("You don't have an active character.") - char: DndCharacter = author.dnd_active_character.character - - first = args[0] - second = args.optional(1) - third = args.optional(2) - - advantage = False - disadvantage = False - extra_modifier = 0 - - if third: - try: - extra_modifier = int(third) - except ValueError: - raise InvalidInputError("Invalid modifier value (third parameter).") - if second.startswith("a") or second.startswith("v"): - advantage = True - elif second.startswith("d") or second.startswith("d"): - disadvantage = True - else: - raise InvalidInputError("Invalid advantage string (second parameter).") - - elif second: - try: - extra_modifier = int(second) - except ValueError: - if second.startswith("a") or second.startswith("v"): - advantage = True - elif second.startswith("d") or second.startswith("d"): - disadvantage = True - else: - raise InvalidInputError("Invalid modifier value or advantage string (second parameter).") - - skill_short_name = first.lower() - for root in self._skill_names: - if skill_short_name.startswith(root): - skill_name = self._skill_names[root] - break - else: - raise CommandError("Invalid skill name (first parameter).") - - skill_modifier = char.__getattribute__(skill_name) - modifier = skill_modifier + extra_modifier - modifier_str = plusformat(modifier, empty_if_zero=True) - - if advantage: - roll_a = random.randrange(1, 21) - roll_b = random.randrange(1, 21) - roll = max([roll_a, roll_b]) - total = roll + modifier - await data.reply(f"๐ŸŽฒ 2d20h1{modifier_str} = ({roll_a}|{roll_b}){modifier_str} = [b]{total}[/b]") - elif disadvantage: - roll_a = random.randrange(1, 21) - roll_b = random.randrange(1, 21) - roll = min([roll_a, roll_b]) - total = roll + modifier - await data.reply(f"๐ŸŽฒ 2d20l1{modifier_str} = ({roll_a}|{roll_b}){modifier_str} = [b]{total}[/b]") - else: - roll = random.randrange(1, 21) - total = roll + modifier - await data.reply(f"๐ŸŽฒ 1d20{modifier_str} = {roll}{modifier_str} = [b]{total}[/b]") diff --git a/royalnet/packs/rpg/commands/dndspell.py b/royalnet/packs/rpg/commands/dndspell.py deleted file mode 100644 index 6d645f06..00000000 --- a/royalnet/packs/rpg/commands/dndspell.py +++ /dev/null @@ -1,114 +0,0 @@ -import aiohttp -import sortedcontainers -from royalnet.commands import * -from royalnet.utils import ordinalformat, andformat -from ..utils import parse_5etools_entry - - -class DndspellCommand(Command): - name: str = "dndspell" - - aliases = ["spell"] - - description: str = "Ottieni informazioni su una magia di D&D5e." - - syntax = "{nomemagia}" - - _dnddata: sortedcontainers.SortedKeyList = None - - def __init__(self, interface: CommandInterface): - super().__init__(interface) - interface.loop.create_task(self._fetch_dnddata()) - - async def _fetch_dnddata(self): - self._dnddata = self._dnddata = sortedcontainers.SortedKeyList([], key=lambda i: i["name"].lower()) - async with aiohttp.ClientSession() as session: - for url in [ - "https://5e.tools/data/spells/spells-ai.json", - "https://5e.tools/data/spells/spells-ggr.json", - "https://5e.tools/data/spells/spells-llk.json", - "https://5e.tools/data/spells/spells-phb.json", - "https://5e.tools/data/spells/spells-scag.json", - "https://5e.tools/data/spells/spells-stream.json", - "https://5e.tools/data/spells/spells-ua-ar.json", - "https://5e.tools/data/spells/spells-ua-mm.json", - "https://5e.tools/data/spells/spells-ua-ss.json", - "https://5e.tools/data/spells/spells-ua-tobm.json", - "https://5e.tools/data/spells/spells-xge.json" - ]: - async with session.get(url) as response: - j = await response.json() - for spell in j["spell"]: - self._dnddata.add(spell) - - @staticmethod - def _parse_spell(spell: dict) -> str: - string = f'โœจ [b]{spell["name"]}[/b]\n' - if "source" in spell: - string += f'[i]{spell["source"]}, page {spell["page"]}[/i]\n' - string += "\n" - if spell["level"] == 0: - string += f'[b]Cantrip[/b] {spell["school"]}\n' - else: - string += f'[b]{ordinalformat(spell["level"])}[/b] level {spell["school"]}\n' - if "time" in spell: - for time in spell["time"]: - string += f'Cast time: โŒ›๏ธ [b]{time["number"]} {time["unit"]}[/b]\n' - if "range" in spell: - if spell["range"]["distance"]["type"] == "touch": - string += "Range: ๐Ÿ‘‰ [b]Touch[/b]\n" - elif spell["range"]["distance"]["type"] == "self": - string += "Range: ๐Ÿ‘ค [b]Self[/b]\n" - else: - string += f'Range: ๐Ÿน [b]{spell["range"]["distance"]["amount"]} {spell["range"]["distance"]["type"]}[/b] ({spell["range"]["type"]})\n' - if "components" in spell: - string += f'Components: ' - if spell["components"].get("v", False): - string += "๐Ÿ‘„ [b]Verbal[/b] | " - if spell["components"].get("s", False): - string += "๐Ÿค™ [b]Somatic[/b] | " - if spell["components"].get("r", False): - # TODO: wtf is this - string += "โ“ [b]R...?[/b] | " - if spell["components"].get("m", False): - if "text" in spell["components"]["m"]: - string += f'๐Ÿ’Ž [b]Material[/b] ([i]{spell["components"]["m"]["text"]}[/i]) | ' - else: - string += f'๐Ÿ’Ž [b]Material[/b] ([i]{spell["components"]["m"]}[/i]) | ' - string = string.rstrip(" ").rstrip("|") - string += "\n" - string += "\n" - if "duration" in spell: - for duration in spell["duration"]: - if duration["type"] == "timed": - string += f'Duration: ๐Ÿ•’ [b]{duration["duration"]["amount"]} {duration["duration"]["type"]}[/b]' - elif duration["type"] == "instant": - string += 'Duration: โ˜๏ธ [b]Instantaneous[/b]' - elif duration["type"] == "special": - string += 'Duration: โญ๏ธ [b]Special[/b]' - elif duration["type"] == "permanent": - string += f"Duration: โ™พ [b]Permanent[/b] (ends on {andformat(duration['ends'], final=' or ')})" - else: - string += f'Duration: โš ๏ธ[b]UNKNOWN[/b]' - if duration.get("concentration", False): - string += " (requires concentration)" - string += "\n" - if "meta" in spell: - if spell["meta"].get("ritual", False): - string += "๐Ÿ”ฎ Can be casted as ritual\n" - string += "\n" - for entry in spell.get("entries", []): - string += parse_5etools_entry(entry) - string += "\n\n" - for entry in spell.get("entriesHigherLevel", []): - string += parse_5etools_entry(entry) - string += "\n\n" - return string - - async def run(self, args: CommandArgs, data: CommandData) -> None: - if self._dnddata is None: - await data.reply("โš ๏ธ Il database degli oggetti di D&D non รจ ancora stato scaricato.") - return - search = args.joined().lower() - result = self._dnddata[self._dnddata.bisect_key_left(search)] - await data.reply(self._parse_spell(result)) diff --git a/royalnet/packs/rpg/commands/roll.py b/royalnet/packs/rpg/commands/roll.py deleted file mode 100644 index bb69dd8f..00000000 --- a/royalnet/packs/rpg/commands/roll.py +++ /dev/null @@ -1,28 +0,0 @@ -import typing -import random -from royalnet.commands import * - - -class RollCommand(Command): - name: str = "roll" - - description: str = "Roll a dice, from N to M (defaults to 1-100)." - - syntax = "[min] [max]" - - aliases = ["r", "random"] - - async def run(self, args: CommandArgs, data: CommandData) -> None: - first: typing.Optional[str] = args.optional(0) - second: typing.Optional[str] = args.optional(1) - if second: - minimum = int(first) - maximum = int(second) - elif first: - minimum = 1 - maximum = int(first) - else: - minimum = 1 - maximum = 100 - result = random.randrange(minimum, maximum+1) - await data.reply(f"๐ŸŽฒ Dice roll [{minimum}-{maximum}]: [b]{result}[/b]") diff --git a/royalnet/packs/rpg/stars/__init__.py b/royalnet/packs/rpg/stars/__init__.py deleted file mode 100644 index 3aa1c42f..00000000 --- a/royalnet/packs/rpg/stars/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Imports go here! - - -# Enter the PageStars of your Pack here! -available_page_stars = [ - -] - -# Enter the ExceptionStars of your Pack here! -available_exception_stars = [ - -] - -# Don't change this, it should automatically generate __all__ -__all__ = [star.__name__ for star in [*available_page_stars, *available_exception_stars]] diff --git a/royalnet/packs/rpg/tables/__init__.py b/royalnet/packs/rpg/tables/__init__.py deleted file mode 100644 index 70dc4390..00000000 --- a/royalnet/packs/rpg/tables/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# Imports go here! -from .dndactivecharacters import DndActiveCharacter -from .dndcharacters import DndCharacter - -# Enter the tables of your Pack here! -available_tables = [ - DndActiveCharacter, - DndCharacter, -] - -# Don't change this, it should automatically generate __all__ -__all__ = [table.__name__ for table in available_tables] diff --git a/royalnet/packs/rpg/tables/dndactivecharacters.py b/royalnet/packs/rpg/tables/dndactivecharacters.py deleted file mode 100644 index 36693add..00000000 --- a/royalnet/packs/rpg/tables/dndactivecharacters.py +++ /dev/null @@ -1,26 +0,0 @@ -from sqlalchemy import * -from sqlalchemy.orm import * -from sqlalchemy.ext.declarative import * - - -class DndActiveCharacter: - __tablename__ = "dndactivecharacters" - - @declared_attr - def character_id(self): - return Column(Integer, ForeignKey("dndcharacters.character_id"), primary_key=True) - - @declared_attr - def user_id(self): - return Column(Integer, ForeignKey("users.uid"), primary_key=True) - - @declared_attr - def character(self): - return relationship("DndCharacter", foreign_keys=self.character_id, backref="activated_by") - - @declared_attr - def user(self): - return relationship("User", foreign_keys=self.user_id, backref=backref("dnd_active_character", uselist=False)) - - def __repr__(self): - return f"<{self.__class__.__qualname__} for {self.user_id}: {self.character_id}>" diff --git a/royalnet/packs/rpg/tables/dndcharacters.py b/royalnet/packs/rpg/tables/dndcharacters.py deleted file mode 100644 index 7699d1ef..00000000 --- a/royalnet/packs/rpg/tables/dndcharacters.py +++ /dev/null @@ -1,301 +0,0 @@ -from sqlalchemy import * -from sqlalchemy.orm import * -from sqlalchemy.ext.declarative import * -from ..utils import DndProficiencyType - - -class DndCharacter: - __tablename__ = "dndcharacters" - - @declared_attr - def character_id(self): - return Column(Integer, primary_key=True) - - @declared_attr - def creator_id(self): - return Column(Integer, ForeignKey("users.uid")) - - @declared_attr - def creator(self): - return relationship("User", foreign_keys=self.creator_id, backref="dndcharacters_created") - - @declared_attr - def name(self): - return Column(String, nullable=False) - - @declared_attr - def strength_score(self): - return Column(Integer, nullable=False) - - @property - def strength(self): - return (self.strength_score - 10) // 2 - - @declared_attr - def dexterity_score(self): - return Column(Integer, nullable=False) - - @property - def dexterity(self): - return (self.dexterity_score - 10) // 2 - - @declared_attr - def constitution_score(self): - return Column(Integer, nullable=False) - - @property - def constitution(self): - return (self.constitution_score - 10) // 2 - - @declared_attr - def intelligence_score(self): - return Column(Integer, nullable=False) - - @property - def intelligence(self): - return (self.intelligence_score - 10) // 2 - - @declared_attr - def wisdom_score(self): - return Column(Integer, nullable=False) - - @property - def wisdom(self): - return (self.wisdom_score - 10) // 2 - - @declared_attr - def charisma_score(self): - return Column(Integer, nullable=False) - - @property - def charisma(self): - return (self.charisma_score - 10) // 2 - - @declared_attr - def level(self): - return Column(Integer, nullable=False) - - @property - def proficiency_bonus(self): - return ((self.level - 1) // 4) + 2 - - @declared_attr - def current_hp(self): - return Column(Integer, nullable=False) - - @declared_attr - def max_hp(self): - return Column(Integer, nullable=False) - - @declared_attr - def armor_class(self): - return Column(Integer, nullable=False) - - @declared_attr - def strength_save_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def dexterity_save_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def constitution_save_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def intelligence_save_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def wisdom_save_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def charisma_save_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def acrobatics_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def animal_handling_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def arcana_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def athletics_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def deception_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def history_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def insight_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def intimidation_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def investigation_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def medicine_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def nature_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def perception_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def performance_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def persuasion_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def religion_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def sleight_of_hand_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def stealth_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @declared_attr - def survival_proficiency(self): - return Column(Enum(DndProficiencyType), nullable=False, default=DndProficiencyType.NONE) - - @property - def strength_save(self): - return self.strength + self.proficiency_bonus * self.strength_save_proficiency.value - - @property - def dexterity_save(self): - return self.dexterity + self.proficiency_bonus * self.dexterity_save_proficiency.value - - @property - def constitution_save(self): - return self.constitution + self.proficiency_bonus * self.constitution_save_proficiency.value - - @property - def intelligence_save(self): - return self.intelligence + self.proficiency_bonus * self.intelligence_save_proficiency.value - - @property - def wisdom_save(self): - return self.wisdom + self.proficiency_bonus * self.wisdom_save_proficiency.value - - @property - def charisma_save(self): - return self.charisma + self.proficiency_bonus * self.charisma_save_proficiency.value - - @property - def acrobatics(self): - return self.dexterity + self.proficiency_bonus * self.acrobatics_proficiency.value - - @property - def animal_handling(self): - return self.wisdom + self.proficiency_bonus * self.animal_handling_proficiency.value - - @property - def arcana(self): - return self.intelligence + self.proficiency_bonus * self.arcana_proficiency.value - - @property - def athletics(self): - return self.strength + self.proficiency_bonus * self.athletics_proficiency.value - - @property - def deception(self): - return self.charisma + self.proficiency_bonus * self.deception_proficiency.value - - @property - def history(self): - return self.intelligence + self.proficiency_bonus * self.history_proficiency.value - - @property - def insight(self): - return self.wisdom + self.proficiency_bonus * self.insight_proficiency.value - - @property - def intimidation(self): - return self.charisma + self.proficiency_bonus * self.intimidation_proficiency.value - - @property - def investigation(self): - return self.intelligence + self.proficiency_bonus * self.investigation_proficiency.value - - @property - def medicine(self): - return self.wisdom + self.proficiency_bonus * self.medicine_proficiency.value - - @property - def nature(self): - return self.intelligence + self.proficiency_bonus * self.nature_proficiency.value - - @property - def perception(self): - return self.wisdom + self.proficiency_bonus * self.perception_proficiency.value - - @property - def performance(self): - return self.charisma + self.proficiency_bonus * self.performance_proficiency.value - - @property - def persuasion(self): - return self.charisma + self.proficiency_bonus * self.persuasion_proficiency.value - - @property - def religion(self): - return self.intelligence + self.proficiency_bonus * self.religion_proficiency.value - - @property - def sleight_of_hand(self): - return self.dexterity + self.proficiency_bonus * self.sleight_of_hand_proficiency.value - - @property - def stealth(self): - return self.dexterity + self.proficiency_bonus * self.stealth_proficiency.value - - @property - def survival(self): - return self.wisdom + self.proficiency_bonus * self.survival_proficiency.value - - def __repr__(self): - return f"<{self.__class__.__qualname__} {self.name}>" - - def __str__(self): - return f"{self.name}" - - def character_sheet(self) -> str: - columns = list(self.__class__.__table__.columns) - column_names = [column.name for column in columns if (not column.primary_key and - not column.foreign_keys and - column.name != "name")] - message = f"[b]{self.name}[/b]\n" - for column_name in column_names: - value = self.__getattribute__(column_name) - message += f"{column_name} {value}\n" - return message diff --git a/royalnet/packs/rpg/utils/__init__.py b/royalnet/packs/rpg/utils/__init__.py deleted file mode 100644 index affccc25..00000000 --- a/royalnet/packs/rpg/utils/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .dndproficiencytype import DndProficiencyType -from .parse5etoolsentry import parse_5etools_entry - -__all__ = ["DndProficiencyType", "parse_5etools_entry"] diff --git a/royalnet/packs/rpg/utils/dndproficiencytype.py b/royalnet/packs/rpg/utils/dndproficiencytype.py deleted file mode 100644 index d7ba925b..00000000 --- a/royalnet/packs/rpg/utils/dndproficiencytype.py +++ /dev/null @@ -1,11 +0,0 @@ -import enum - - -class DndProficiencyType(enum.Enum): - NONE = 0 - HALF_PROFICIENCY = 0.5 - FULL_PROFICIENCY = 1 - EXPERTISE = 2 - - def __str__(self): - return str(self.value) \ No newline at end of file diff --git a/royalnet/packs/rpg/utils/parse5etoolsentry.py b/royalnet/packs/rpg/utils/parse5etoolsentry.py deleted file mode 100644 index b027249c..00000000 --- a/royalnet/packs/rpg/utils/parse5etoolsentry.py +++ /dev/null @@ -1,31 +0,0 @@ -def parse_5etools_entry(entry) -> str: - if isinstance(entry, str): - return entry - elif isinstance(entry, dict): - string = "" - if entry["type"] == "entries": - string += f'[b]{entry.get("name", "")}[/b]\n' - for subentry in entry["entries"]: - string += parse_5etools_entry(subentry) - string += "\n\n" - elif entry["type"] == "table": - string += "[i][table hidden][/i]" - # for label in entry["colLabels"]: - # string += f"| {label} " - # string += "|" - # for row in entry["rows"]: - # for column in row: - # string += f"| {self._parse_entry(column)} " - # string += "|\n" - elif entry["type"] == "cell": - return parse_5etools_entry(entry["entry"]) - elif entry["type"] == "list": - string = "" - for item in entry["items"]: - string += f"- {parse_5etools_entry(item)}\n" - string.rstrip("\n") - else: - string += "[i]โš ๏ธ [unknown type][/i]" - else: - return "[/i]โš ๏ธ [unknown data][/i]" - return string diff --git a/royalnet/version.py b/royalnet/version.py index 71edc665..37d8d6a0 100644 --- a/royalnet/version.py +++ b/royalnet/version.py @@ -1,4 +1,4 @@ -semantic = "5.0a91" +semantic = "5.0a93" if __name__ == "__main__": print(semantic)