mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 11:34:18 +00:00
Merge branch 'master' of github.com:Steffo99/royalpack into master
This commit is contained in:
commit
08582bfc82
132 changed files with 5594 additions and 1603 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,7 +1,7 @@
|
|||
# Royalnet ignores
|
||||
config*.toml
|
||||
downloads/
|
||||
|
||||
markov/
|
||||
|
||||
# Python ignores
|
||||
**/__pycache__/
|
||||
|
@ -11,4 +11,3 @@ dist/
|
|||
|
||||
# PyCharm ignores
|
||||
.idea/
|
||||
|
||||
|
|
242
README.md
242
README.md
|
@ -1,204 +1,84 @@
|
|||
<!--This documentation was autogenerated with `python -m royalnet.generate -f markdown`.-->
|
||||
|
||||
# `royalpack`
|
||||
|
||||
## Commands
|
||||
## Configuration
|
||||
|
||||
### `ciaoruozi`
|
||||
```toml
|
||||
[Packs."royalpack"]
|
||||
|
||||
Saluta Ruozi, un leggendario essere che una volta era in User Games.
|
||||
# The main Telegram group
|
||||
Telegram.main_group_id = -1001153723135
|
||||
|
||||
### `color`
|
||||
# The main Discord channel
|
||||
Discord.main_channel_id = 566023556618518538
|
||||
|
||||
Invia un colore in chat...?
|
||||
# A Imgur API token (https://apidocs.imgur.com/?version=latest)
|
||||
Imgur.token = "1234567890abcde"
|
||||
|
||||
### `cv`
|
||||
# A Steam Web API key (https://steamcommunity.com/dev/apikey)
|
||||
Steam.web_api_key = "123567890ABCDEF123567890ABCDEF12"
|
||||
|
||||
Elenca le persone attualmente connesse alla chat vocale.
|
||||
# The Peertube instance you want to use for new video notifications
|
||||
Peertube.instance_url = "https://pt.steffo.eu"
|
||||
|
||||
### `diario`
|
||||
# The delay in seconds between two new video checks
|
||||
Peertube.feed_update_timeout = 300
|
||||
|
||||
Aggiungi una citazione al Diario.
|
||||
# The Funkwhale instance you want to use for the fw commands
|
||||
Funkwhale.instance_url = "https://fw.steffo.eu"
|
||||
|
||||
### `rage`
|
||||
# The id of the role that users should have to be displayed by default in cv
|
||||
Cv.displayed_role_id = 424549048561958912
|
||||
|
||||
Arrabbiati per qualcosa, come una software house californiana.
|
||||
# The max duration of a song downloaded with the play commands
|
||||
Play.max_song_duration = 7230
|
||||
|
||||
> Aliases: `balurage` `madden`
|
||||
# The Telegram channel where matchmaking messages should be sent in
|
||||
Matchmaking.mm_chat_id = -1001204402796
|
||||
|
||||
### `reminder`
|
||||
[Packs."royalpack"."steampowered"]
|
||||
token = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
|
||||
Ti ricorda di fare qualcosa dopo un po' di tempo.
|
||||
[Packs."royalpack"."steampowered".updater]
|
||||
enabled = false
|
||||
period = 86400
|
||||
delay = 1
|
||||
target = -1001153723135
|
||||
|
||||
> Aliases: `calendar`
|
||||
[Packs."royalpack"."dota".updater]
|
||||
enabled = true
|
||||
period = 86400
|
||||
delay = 1
|
||||
target = -1001153723135
|
||||
|
||||
### `ship`
|
||||
[Packs."royalpack"."brawlhalla"]
|
||||
token = "1234567890ABCDEFGHJKLMNOPQRST"
|
||||
|
||||
Crea una ship tra due nomi.
|
||||
[Packs."royalpack"."brawlhalla".updater]
|
||||
enabled = true
|
||||
period = 86400
|
||||
delay = 1
|
||||
target = -1001153723135
|
||||
|
||||
### `smecds`
|
||||
[Packs."royalpack"."leagueoflegends"]
|
||||
token = "RGAPI-AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"
|
||||
region = "euw1"
|
||||
|
||||
Secondo me, è colpa dello stagista...
|
||||
[Packs."royalpack"."leagueoflegends".updater]
|
||||
enabled = true
|
||||
period = 86400
|
||||
delay = 1
|
||||
target = -1001153723135
|
||||
|
||||
> Aliases: `secondomeecolpadellostagista`
|
||||
[Packs."royalpack"."osu"]
|
||||
client_id = 123456789
|
||||
client_secret = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
|
||||
### `videochannel`
|
||||
|
||||
Converti il canale vocale in un canale video.
|
||||
|
||||
> Aliases: `golive` `live` `video`
|
||||
|
||||
### `pause`
|
||||
|
||||
Metti in pausa o riprendi la riproduzione di un file.
|
||||
|
||||
> Aliases: `resume`
|
||||
|
||||
### `play`
|
||||
|
||||
Aggiunge un url alla coda della chat vocale.
|
||||
|
||||
> Aliases: `p`
|
||||
|
||||
### `queue`
|
||||
|
||||
Visualizza la coda di riproduzione attuale..
|
||||
|
||||
> Aliases: `q`
|
||||
|
||||
### `skip`
|
||||
|
||||
Salta il file attualmente in riproduzione.
|
||||
|
||||
> Aliases: `s`
|
||||
|
||||
### `summon`
|
||||
|
||||
Evoca il bot in un canale vocale.
|
||||
|
||||
> Aliases: `cv`
|
||||
|
||||
### `youtube`
|
||||
|
||||
Cerca un video su YouTube e lo aggiunge alla coda della chat vocale.
|
||||
|
||||
> Aliases: `yt`
|
||||
|
||||
### `soundcloud`
|
||||
|
||||
Cerca un video su SoundCloud e lo aggiunge alla coda della chat vocale.
|
||||
|
||||
> Aliases: `sc`
|
||||
|
||||
### `emojify`
|
||||
|
||||
Converti un messaggio in emoji.
|
||||
|
||||
### `leagueoflegends`
|
||||
|
||||
Connetti un account di League of Legends a un account Royalnet, e visualizzane le statistiche.
|
||||
|
||||
> Aliases: `lol` `league`
|
||||
|
||||
### `diarioquote`
|
||||
|
||||
Cita una riga del diario.
|
||||
|
||||
> Aliases: `dq` `quote` `dquote`
|
||||
|
||||
### `peertube`
|
||||
|
||||
Guarda quando è uscito l'ultimo video su RoyalTube.
|
||||
|
||||
### `googlevideo`
|
||||
|
||||
Cerca un video su Google Video e lo aggiunge alla coda della chat vocale.
|
||||
|
||||
> Aliases: `gv`
|
||||
|
||||
### `yahoovideo`
|
||||
|
||||
Cerca un video su Yahoo Video e lo aggiunge alla coda della chat vocale.
|
||||
|
||||
> Aliases: `yv`
|
||||
|
||||
### `userinfo`
|
||||
|
||||
Visualizza informazioni su un utente.
|
||||
|
||||
> Aliases: `uinfo` `ui` `useri`
|
||||
|
||||
### `spell`
|
||||
|
||||
Genera casualmente una spell!
|
||||
|
||||
### `ahnonlosoio`
|
||||
|
||||
Ah, non lo so io!
|
||||
|
||||
### `eat`
|
||||
|
||||
Mangia qualcosa!
|
||||
|
||||
### `pmots`
|
||||
|
||||
Confondi Proto!
|
||||
|
||||
## Events
|
||||
|
||||
### `discord_cv`
|
||||
|
||||
### `discord_summon`
|
||||
|
||||
### `discord_play`
|
||||
|
||||
### `discord_skip`
|
||||
|
||||
### `discord_queue`
|
||||
|
||||
### `discord_pause`
|
||||
|
||||
## Page Stars
|
||||
|
||||
### `/api/user/list`
|
||||
|
||||
### `/api/user/get/{uid_str}`
|
||||
|
||||
### `/api/diario/list`
|
||||
|
||||
### `/api/diario/get/{diario_id}`
|
||||
|
||||
## Exception Stars
|
||||
|
||||
## Tables
|
||||
|
||||
### `diario`
|
||||
|
||||
### `aliases`
|
||||
|
||||
### `wikipages`
|
||||
|
||||
Wiki page properties.
|
||||
|
||||
Warning:
|
||||
Requires PostgreSQL!
|
||||
|
||||
### `wikirevisions`
|
||||
|
||||
A wiki page revision.
|
||||
|
||||
Warning:
|
||||
Requires PostgreSQL!
|
||||
|
||||
### `bios`
|
||||
|
||||
### `reminder`
|
||||
|
||||
### `triviascores`
|
||||
|
||||
### `mmevents`
|
||||
|
||||
### `mmresponse`
|
||||
|
||||
### `leagueoflegends`
|
||||
[Packs."royalpack"."osu".login]
|
||||
enabled = false
|
||||
|
||||
[Packs."royalpack"."osu".updater]
|
||||
enabled = true
|
||||
period = 86400
|
||||
delay = 5
|
||||
target = -1001153723135
|
||||
```
|
764
poetry.lock
generated
764
poetry.lock
generated
File diff suppressed because it is too large
Load diff
2
publish.bat
Normal file
2
publish.bat
Normal file
|
@ -0,0 +1,2 @@
|
|||
git commit -am "publish: %1"
|
||||
git push && poetry build && poetry publish && hub release create "%1" -m "Royalnet %1"
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[tool.poetry]
|
||||
name = "royalpack"
|
||||
version = "5.1.9"
|
||||
version = "5.13.4"
|
||||
description = "A Royalnet command pack for the Royal Games community"
|
||||
authors = ["Stefano Pigozzi <ste.pigozzi@gmail.com>"]
|
||||
license = "AGPL-3.0+"
|
||||
|
@ -20,11 +20,14 @@
|
|||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
|
||||
riotwatcher = "^2.7.1"
|
||||
riotwatcher = "^3.0.0"
|
||||
royalspells = "^3.2"
|
||||
steam = "*"
|
||||
sqlalchemy = "^1.3.18"
|
||||
bcrypt = "^3.1.7"
|
||||
|
||||
[tool.poetry.dependencies.royalnet]
|
||||
version = "^5.1.6"
|
||||
version = "~5.10.4"
|
||||
# Maybe... there is a way to make these selectable?
|
||||
extras = [
|
||||
"telegram",
|
||||
|
@ -34,7 +37,7 @@
|
|||
"constellation",
|
||||
"sentry",
|
||||
"herald",
|
||||
"coloredlogs"
|
||||
"coloredlogs",
|
||||
]
|
||||
|
||||
# Development dependencies
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
# This is a template Pack __init__. You can use this without changing anything in other packages too!
|
||||
|
||||
from . import commands, tables, stars, events
|
||||
from .commands import available_commands
|
||||
from .tables import available_tables
|
||||
from .stars import available_page_stars, available_exception_stars
|
||||
from .events import available_events
|
||||
|
||||
from .version import semantic as __version__
|
||||
|
||||
__all__ = [
|
||||
"commands",
|
||||
"tables",
|
||||
"stars",
|
||||
"events",
|
||||
"available_commands",
|
||||
"available_tables",
|
||||
"available_page_stars",
|
||||
"available_exception_stars",
|
||||
"available_events",
|
||||
]
|
|
@ -1,67 +1,87 @@
|
|||
# Imports go here!
|
||||
from .ahnonlosoio import AhnonlosoioCommand
|
||||
from .answer import AnswerCommand
|
||||
from .brawlhalla import BrawlhallaCommand
|
||||
from .cat import CatCommand
|
||||
from .ciaoruozi import CiaoruoziCommand
|
||||
from .color import ColorCommand
|
||||
from .cv import CvCommand
|
||||
from .cvstats import CvstatsCommand
|
||||
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 .pause import PauseCommand
|
||||
from .play import PlayCommand
|
||||
from .queue import QueueCommand
|
||||
from .skip import SkipCommand
|
||||
from .summon import SummonCommand
|
||||
from .youtube import YoutubeCommand
|
||||
from .soundcloud import SoundcloudCommand
|
||||
from .emojify import EmojifyCommand
|
||||
from .leagueoflegends import LeagueoflegendsCommand
|
||||
from .diarioquote import DiarioquoteCommand
|
||||
from .peertubeupdates import PeertubeUpdatesCommand
|
||||
from .googlevideo import GooglevideoCommand
|
||||
from .yahoovideo import YahoovideoCommand
|
||||
from .userinfo import UserinfoCommand
|
||||
from .spell import SpellCommand
|
||||
from .ahnonlosoio import AhnonlosoioCommand
|
||||
from .diarioshuffle import DiarioshuffleCommand
|
||||
from .dota import DotaCommand
|
||||
from .eat import EatCommand
|
||||
from .pmots import PmotsCommand
|
||||
from .peertube import PeertubeCommand
|
||||
from .emojify import EmojifyCommand
|
||||
from .eval import EvalCommand
|
||||
from .exec import ExecCommand
|
||||
from .fortune import FortuneCommand
|
||||
from .givefiorygi import GivefiorygiCommand
|
||||
from .givetreasure import GivetreasureCommand
|
||||
from .help import HelpCommand
|
||||
from .leagueoflegends import LeagueoflegendsCommand
|
||||
from .magickfiorygi import MagickfiorygiCommand
|
||||
from .magicktreasure import MagicktreasureCommand
|
||||
from .matchmaking import MatchmakingCommand
|
||||
from .peertubeupdates import PeertubeUpdatesCommand
|
||||
from .ping import PingCommand
|
||||
from .pmots import PmotsCommand
|
||||
from .dog import DogCommand
|
||||
from .rage import RageCommand
|
||||
from .reminder import ReminderCommand
|
||||
from .royalpackversion import RoyalpackCommand
|
||||
from .ship import ShipCommand
|
||||
from .smecds import SmecdsCommand
|
||||
from .spell import SpellCommand
|
||||
from .steammatch import SteammatchCommand
|
||||
from .steampowered import SteampoweredCommand
|
||||
from .treasure import TreasureCommand
|
||||
from .trivia import TriviaCommand
|
||||
from .userinfo import UserinfoCommand
|
||||
from .osu import OsuCommand
|
||||
|
||||
# Enter the commands of your Pack here!
|
||||
available_commands = [
|
||||
AhnonlosoioCommand,
|
||||
AnswerCommand,
|
||||
BrawlhallaCommand,
|
||||
CatCommand,
|
||||
CiaoruoziCommand,
|
||||
ColorCommand,
|
||||
CvCommand,
|
||||
CvstatsCommand,
|
||||
DiarioCommand,
|
||||
RageCommand,
|
||||
ReminderCommand,
|
||||
ShipCommand,
|
||||
SmecdsCommand,
|
||||
VideochannelCommand,
|
||||
PauseCommand,
|
||||
PlayCommand,
|
||||
QueueCommand,
|
||||
SkipCommand,
|
||||
SummonCommand,
|
||||
YoutubeCommand,
|
||||
SoundcloudCommand,
|
||||
EmojifyCommand,
|
||||
LeagueoflegendsCommand,
|
||||
DiarioquoteCommand,
|
||||
PeertubeUpdatesCommand,
|
||||
GooglevideoCommand,
|
||||
YahoovideoCommand,
|
||||
UserinfoCommand,
|
||||
SpellCommand,
|
||||
AhnonlosoioCommand,
|
||||
DiarioshuffleCommand,
|
||||
DotaCommand,
|
||||
EatCommand,
|
||||
PmotsCommand,
|
||||
PeertubeCommand,
|
||||
EmojifyCommand,
|
||||
EvalCommand,
|
||||
ExecCommand,
|
||||
FortuneCommand,
|
||||
GivefiorygiCommand,
|
||||
GivetreasureCommand,
|
||||
HelpCommand,
|
||||
LeagueoflegendsCommand,
|
||||
MagickfiorygiCommand,
|
||||
MagicktreasureCommand,
|
||||
MatchmakingCommand,
|
||||
PeertubeUpdatesCommand,
|
||||
PingCommand,
|
||||
PmotsCommand,
|
||||
DogCommand,
|
||||
RageCommand,
|
||||
ReminderCommand,
|
||||
RoyalpackCommand,
|
||||
ShipCommand,
|
||||
SmecdsCommand,
|
||||
SpellCommand,
|
||||
SteammatchCommand,
|
||||
SteampoweredCommand,
|
||||
TreasureCommand,
|
||||
TriviaCommand,
|
||||
UserinfoCommand,
|
||||
OsuCommand,
|
||||
]
|
||||
|
||||
# Don't change this, it should automatically generate __all__
|
||||
|
|
202
royalpack/commands/abstract/linker.py
Normal file
202
royalpack/commands/abstract/linker.py
Normal file
|
@ -0,0 +1,202 @@
|
|||
from typing import *
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
import royalnet.serf.telegram as rst
|
||||
import royalnet.backpack.tables as rbt
|
||||
import abc
|
||||
import logging
|
||||
import asyncio as aio
|
||||
from ...types import Updatable
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LinkerCommand(rc.Command, metaclass=abc.ABCMeta):
|
||||
|
||||
def __init__(self, interface: rc.CommandInterface):
|
||||
super().__init__(interface)
|
||||
|
||||
self.updater_task = None
|
||||
if self.enabled():
|
||||
self.updater_task = self.loop.create_task(self.run_updater())
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
author = await data.get_author(error_if_none=True)
|
||||
if len(args) == 0:
|
||||
message = []
|
||||
for obj in await self.get_updatables_of_user(session=data.session, user=author):
|
||||
async def change(attribute: str, value: Any):
|
||||
"""A shortcut for self.__change."""
|
||||
await self._change(session=data.session,
|
||||
obj=obj,
|
||||
attribute=attribute,
|
||||
new=value)
|
||||
|
||||
await self.update(session=data.session, obj=obj, change=change)
|
||||
message.append(self.describe(obj))
|
||||
if len(message) == 0:
|
||||
raise rc.UserError("Nessun account connesso.")
|
||||
await data.session_commit()
|
||||
await data.reply("\n".join(message))
|
||||
else:
|
||||
created = await self.create(session=data.session, user=author, args=args, data=data)
|
||||
await data.session_commit()
|
||||
if created is not None:
|
||||
message = ["🔗 Account collegato!", "", self.describe(created)]
|
||||
await data.reply("\n".join(message))
|
||||
|
||||
def describe(self, obj: Updatable) -> str:
|
||||
"""The text that should be appended to the report message for a given Updatable."""
|
||||
return str(obj)
|
||||
|
||||
@abc.abstractmethod
|
||||
async def get_updatables_of_user(self, session, user: rbt.User) -> List[Updatable]:
|
||||
"""Get the updatables of a specific user."""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
async def get_updatables(self, session) -> List[Updatable]:
|
||||
"""Return a list of all objects that should be updated at this updater cycle."""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
async def create(self,
|
||||
session,
|
||||
user: rbt.User,
|
||||
args: rc.CommandArgs,
|
||||
data: Optional[rc.CommandData] = None) -> Optional[Updatable]:
|
||||
"""Create a new updatable object for a user.
|
||||
|
||||
This function is responsible for adding the object to the session."""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
async def update(self, session, obj, change: Callable[[str, Any], Awaitable[None]]):
|
||||
"""Update a single updatable object. Use the change method to change values on the object!"""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
async def on_increase(self, session, obj: Updatable, attribute: str, old: Any, new: Any) -> None:
|
||||
"""Called when the attribute has increased from the old value."""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
async def on_unchanged(self, session, obj: Updatable, attribute: str, old: Any, new: Any) -> None:
|
||||
"""Called when the attribute stayed the same as the old value."""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
async def on_decrease(self, session, obj: Updatable, attribute: str, old: Any, new: Any) -> None:
|
||||
"""Called when the attribute has decreased from the old value."""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
async def on_first(self, session, obj: Updatable, attribute: str, old: None, new: Any) -> None:
|
||||
"""Called when the attribute changed from None."""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
async def on_reset(self, session, obj: Updatable, attribute: str, old: Any, new: None) -> None:
|
||||
"""Called when the attribute changed to None."""
|
||||
...
|
||||
|
||||
async def _change(self,
|
||||
session,
|
||||
obj,
|
||||
attribute: str,
|
||||
new) -> None:
|
||||
"""Set the value of an attribute of an object to a value, and call the corresponding method."""
|
||||
old = obj.__getattribute__(attribute)
|
||||
if new == old:
|
||||
await self.on_unchanged(session=session,
|
||||
obj=obj,
|
||||
attribute=attribute,
|
||||
old=old,
|
||||
new=new)
|
||||
else:
|
||||
if old is None:
|
||||
await self.on_first(session=session,
|
||||
obj=obj,
|
||||
attribute=attribute,
|
||||
old=old,
|
||||
new=new)
|
||||
elif new is None:
|
||||
await self.on_reset(session=session,
|
||||
obj=obj,
|
||||
attribute=attribute,
|
||||
old=old,
|
||||
new=new)
|
||||
elif new > old:
|
||||
await self.on_increase(session=session,
|
||||
obj=obj,
|
||||
attribute=attribute,
|
||||
old=old,
|
||||
new=new)
|
||||
else:
|
||||
await self.on_decrease(session=session,
|
||||
obj=obj,
|
||||
attribute=attribute,
|
||||
old=old,
|
||||
new=new)
|
||||
obj.__setattr__(attribute, new)
|
||||
|
||||
def enabled(self) -> bool:
|
||||
"""Whether the updater is enabled or not."""
|
||||
return self.config[self.name]["updater"]["enabled"] and self.interface.name == "telegram"
|
||||
|
||||
def period(self) -> int:
|
||||
"""The time between two updater cycles."""
|
||||
return self.config[self.name]["updater"]["period"]
|
||||
|
||||
def delay(self) -> int:
|
||||
"""The time between two object updates."""
|
||||
return self.config[self.name]["updater"]["delay"]
|
||||
|
||||
def target(self) -> int:
|
||||
"""The id of the Telegram chat where notifications should be sent."""
|
||||
return self.config[self.name]["updater"]["target"]
|
||||
|
||||
async def run_updater(self):
|
||||
log.info(f"Starting updater: {self.name}")
|
||||
|
||||
while True:
|
||||
log.debug(f"Updater cycle: {self.name}")
|
||||
session = self.alchemy.Session()
|
||||
objects = await self.get_updatables(session)
|
||||
|
||||
for obj in objects:
|
||||
log.debug(f"Updating: {obj} ({self.name})")
|
||||
|
||||
async def change(attribute: str, value: Any):
|
||||
"""A shortcut for self.__change."""
|
||||
await self._change(session=session,
|
||||
obj=obj,
|
||||
attribute=attribute,
|
||||
new=value)
|
||||
|
||||
try:
|
||||
await self.update(session=session,
|
||||
obj=obj,
|
||||
change=change)
|
||||
except Exception as e:
|
||||
ru.sentry_exc(e)
|
||||
|
||||
delay = self.delay()
|
||||
log.debug(f"Waiting for: {delay} seconds (delay)")
|
||||
await aio.sleep(delay)
|
||||
|
||||
log.debug(f"Committing updates: {self.name}")
|
||||
await ru.asyncify(session.commit)
|
||||
session.close()
|
||||
|
||||
period = self.period()
|
||||
log.debug(f"Waiting for: {period} seconds (period)")
|
||||
await aio.sleep(period)
|
||||
|
||||
async def notify(self, message):
|
||||
await self.serf.api_call(self.serf.client.send_message,
|
||||
chat_id=self.target(),
|
||||
text=rst.escape(message),
|
||||
parse_mode="HTML",
|
||||
disable_webpage_preview=True)
|
82
royalpack/commands/answer.py
Normal file
82
royalpack/commands/answer.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
from typing import *
|
||||
import royalnet
|
||||
import royalnet.commands as rc
|
||||
import random
|
||||
import datetime
|
||||
|
||||
|
||||
class AnswerCommand(rc.Command):
|
||||
name: str = "answer"
|
||||
|
||||
description: str = "Fai una domanda al bot, che possa essere risposta con un sì o un no: lui ti risponderà!"
|
||||
|
||||
syntax: str = ""
|
||||
|
||||
_answers = [
|
||||
#Cerchiamo di tenere bilanciate le tre colonne, o almeno le prime due.
|
||||
#Se avete un'idea ma metterebbe troppe opzioni in un'unica categoria, mettetela sotto commento.
|
||||
|
||||
#risposte "sì"
|
||||
"Sì.",
|
||||
"Decisamente sì!",
|
||||
"Uhm, secondo me sì.",
|
||||
"Sì! Sì! SÌ!",
|
||||
"Yup.",
|
||||
"👍",
|
||||
"Direi proprio di sì.",
|
||||
"Assolutamente sì.",
|
||||
"Ma certo!",
|
||||
"✔️",
|
||||
"👌",
|
||||
"Esatto!",
|
||||
"Senz'altro!",
|
||||
"Ovviamente.",
|
||||
"Questa domanda ha risposta affermativa.",
|
||||
"Hell yeah.",
|
||||
|
||||
#risposte "no"
|
||||
"No.",
|
||||
"Decisamente no!",
|
||||
"Uhm, secondo me sì.",
|
||||
"No, no, e ancora NO!",
|
||||
"Nope.",
|
||||
"👎",
|
||||
"Direi proprio di no.",
|
||||
"Assolutamente no.",
|
||||
"Certo che no!",
|
||||
"✖️",
|
||||
"🙅",
|
||||
"Neanche per idea!",
|
||||
"Neanche per sogno!",
|
||||
"Niente affatto!",
|
||||
"Questa domanda ha risposta negativa.",
|
||||
"Hell no.",
|
||||
|
||||
#risposte "boh"
|
||||
"Boh.",
|
||||
"E io che ne so?!",
|
||||
"Non so proprio rispondere",
|
||||
"Non lo so",
|
||||
"Mi rifiuto di rispondere alla domanda!",
|
||||
"Non parlerò senza il mio avvocato!",
|
||||
"Dunno.",
|
||||
"Perché lo chiedi a me?",
|
||||
"🤷 Ah, non lo so io! ¯\_(ツ)_/¯",
|
||||
"🤷",
|
||||
"¯\_(ツ)_/¯",
|
||||
"No idea.",
|
||||
"Dunno.",
|
||||
"Boooooh!",
|
||||
"Non ne ho la più pallida idea.",
|
||||
]
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
|
||||
h = hash(datetime.datetime.now())
|
||||
|
||||
r = random.Random(x=h)
|
||||
|
||||
message = r.sample(self._answers, 1)[0]
|
||||
await data.reply(message)
|
||||
|
||||
|
169
royalpack/commands/brawlhalla.py
Normal file
169
royalpack/commands/brawlhalla.py
Normal file
|
@ -0,0 +1,169 @@
|
|||
from typing import *
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import aiohttp
|
||||
|
||||
from royalnet.backpack import tables as rbt
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
from sqlalchemy import or_, and_
|
||||
|
||||
from .abstract.linker import LinkerCommand
|
||||
from ..tables import Steam, Brawlhalla, BrawlhallaDuo
|
||||
from ..types import BrawlhallaRank, BrawlhallaMetal, BrawlhallaTier, Updatable
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BrawlhallaCommand(LinkerCommand):
|
||||
name: str = "brawlhalla"
|
||||
|
||||
aliases = ["bh", "bruhalla", "bruhlalla"]
|
||||
|
||||
description: str = "Visualizza le tue statistiche di Brawlhalla."
|
||||
|
||||
syntax: str = ""
|
||||
|
||||
def token(self):
|
||||
return self.config['brawlhalla']['token']
|
||||
|
||||
async def get_updatables_of_user(self, session, user: rbt.User) -> List[Brawlhalla]:
|
||||
return user.steam
|
||||
|
||||
async def get_updatables(self, session) -> List[Brawlhalla]:
|
||||
return await ru.asyncify(session.query(self.alchemy.get(Steam)).all)
|
||||
|
||||
async def create(self,
|
||||
session,
|
||||
user: rbt.User,
|
||||
args: rc.CommandArgs,
|
||||
data: Optional[rc.CommandData] = None) -> Optional[Brawlhalla]:
|
||||
raise rc.InvalidInputError("Brawlhalla accounts are automatically linked from Steam.")
|
||||
|
||||
async def update(self, session, obj, change: Callable[[str, Any], Awaitable[None]]):
|
||||
BrawlhallaT = self.alchemy.get(Brawlhalla)
|
||||
DuoT = self.alchemy.get(BrawlhallaDuo)
|
||||
log.info(f"Updating: {obj}")
|
||||
async with aiohttp.ClientSession() as hcs:
|
||||
bh: Brawlhalla = obj.brawlhalla
|
||||
if bh is None:
|
||||
log.debug(f"Checking if player has an account...")
|
||||
async with hcs.get(f"https://api.brawlhalla.com/search?steamid={obj.steamid.as_64}&api_key={self.token()}") as response:
|
||||
if response.status != 200:
|
||||
raise rc.ExternalError(f"Brawlhalla API /search returned {response.status}!")
|
||||
j = await response.json()
|
||||
if j == {} or j == []:
|
||||
log.debug("No account found.")
|
||||
return
|
||||
bh = BrawlhallaT(
|
||||
steam=obj,
|
||||
brawlhalla_id=j["brawlhalla_id"],
|
||||
name=j["name"]
|
||||
)
|
||||
session.add(bh)
|
||||
session.flush()
|
||||
|
||||
async with hcs.get(f"https://api.brawlhalla.com/player/{bh.brawlhalla_id}/ranked?api_key={self.token()}") as response:
|
||||
if response.status != 200:
|
||||
raise rc.ExternalError(f"Brawlhalla API /ranked returned {response.status}!")
|
||||
j = await response.json()
|
||||
if j == {} or j == []:
|
||||
log.debug("No ranked info found.")
|
||||
else:
|
||||
await self._change(session=session, obj=bh, attribute="rating_1v1", new=j["rating"])
|
||||
metal_name, tier_name = j["tier"].split(" ", 1)
|
||||
metal = BrawlhallaMetal[metal_name.upper()]
|
||||
tier = BrawlhallaTier(int(tier_name))
|
||||
rank = BrawlhallaRank(metal=metal, tier=tier)
|
||||
await self._change(session=session, obj=bh, attribute="rank_1v1", new=rank)
|
||||
|
||||
for jduo in j.get("2v2", []):
|
||||
bhduo: Optional[BrawlhallaDuo] = await ru.asyncify(
|
||||
session.query(DuoT)
|
||||
.filter(
|
||||
or_(
|
||||
and_(
|
||||
DuoT.id_one == jduo["brawlhalla_id_one"],
|
||||
DuoT.id_two == jduo["brawlhalla_id_two"]
|
||||
),
|
||||
and_(
|
||||
DuoT.id_one == jduo["brawlhalla_id_two"],
|
||||
DuoT.id_two == jduo["brawlhalla_id_one"]
|
||||
)
|
||||
)
|
||||
)
|
||||
.one_or_none
|
||||
)
|
||||
if bhduo is None:
|
||||
if bh.brawlhalla_id == jduo["brawlhalla_id_one"]:
|
||||
otherbh: Optional[Brawlhalla] = await ru.asyncify(
|
||||
session.query(BrawlhallaT).get, jduo["brawlhalla_id_two"]
|
||||
)
|
||||
else:
|
||||
otherbh: Optional[Brawlhalla] = await ru.asyncify(
|
||||
session.query(BrawlhallaT).get, jduo["brawlhalla_id_one"]
|
||||
)
|
||||
if otherbh is None:
|
||||
continue
|
||||
bhduo = DuoT(
|
||||
one=bh,
|
||||
two=otherbh,
|
||||
)
|
||||
|
||||
session.add(bhduo)
|
||||
await self._change(session=session, obj=bhduo, attribute="rating_2v2", new=jduo["rating"])
|
||||
metal_name, tier_name = jduo["tier"].split(" ", 1)
|
||||
metal = BrawlhallaMetal[metal_name.upper()]
|
||||
tier = BrawlhallaTier(int(tier_name))
|
||||
rank = BrawlhallaRank(metal=metal, tier=tier)
|
||||
await self._change(session=session, obj=bhduo, attribute="rank_2v2", new=rank)
|
||||
|
||||
async def on_increase(self, session, obj: Union[Brawlhalla, BrawlhallaDuo], attribute: str, old: Any, new: Any) -> None:
|
||||
if attribute == "rank_1v1":
|
||||
await self.notify(f"📈 [b]{obj.steam.user}[/b] è salito a [b]{new}[/b] ({obj.rating_1v1} MMR) in 1v1 su Brawlhalla! Congratulazioni!")
|
||||
elif attribute == "rank_2v2":
|
||||
await self.notify(f"📈 [b]{obj.one.steam.user}[/b] e [b]{obj.two.steam.user}[/b] sono saliti a [b]{new}[/b] ({obj.rating_2v2} MMR) in 2v2 su Brawlhalla! Congratulazioni!")
|
||||
|
||||
async def on_unchanged(self, session, obj: Union[Brawlhalla, BrawlhallaDuo], attribute: str, old: Any, new: Any) -> None:
|
||||
pass
|
||||
|
||||
async def on_decrease(self, session, obj: Union[Brawlhalla, BrawlhallaDuo], attribute: str, old: Any, new: Any) -> None:
|
||||
if attribute == "rank_1v1":
|
||||
await self.notify(f"📉 [b]{obj.steam.user}[/b] è sceso a [b]{new}[/b] ({obj.rating_1v1} MMR) in 1v1 su Brawlhalla.")
|
||||
elif attribute == "rank_2v2":
|
||||
await self.notify(f"📉 [b]{obj.one.steam.user}[/b] e [b]{obj.two.steam.user}[/b] sono scesi a [b]{new}[/b] ({obj.rating_2v2} MMR) in 2v2 su Brawlhalla.")
|
||||
|
||||
async def on_first(self, session, obj: Union[Brawlhalla, BrawlhallaDuo], attribute: str, old: None, new: Any) -> None:
|
||||
if attribute == "rank_1v1":
|
||||
await self.notify(f"🌟 [b]{obj.steam.user}[/b] si è classificato a [b]{new}[/b] ({obj.rating_1v1} MMR) in 1v1 su Brawlhalla!")
|
||||
elif attribute == "rank_2v2":
|
||||
await self.notify(f"🌟 [b]{obj.one.steam.user}[/b] e [b]{obj.two.steam.user}[/b] si sono classificati a [b]{new}[/b] ({obj.rating_2v2} MMR) in 2v2 su Brawlhalla!")
|
||||
|
||||
async def on_reset(self, session, obj: Union[Brawlhalla, BrawlhallaDuo], attribute: str, old: Any, new: None) -> None:
|
||||
if attribute == "rank_1v1":
|
||||
await self.notify(f"⬜️ [b]{obj.steam.user}[/b] non ha più un rank su Brawlhalla.")
|
||||
elif attribute == "rank_2v2":
|
||||
await self.notify(f"⬜️ [b]{obj.one.steam.user}[/b] e [b]{obj.two.steam.user}[/b] non hanno più un rank su Brawlhalla.")
|
||||
|
||||
def describe(self, obj: Steam) -> str:
|
||||
bh = obj.brawlhalla
|
||||
|
||||
string = [f"ℹ️ [b]{bh.name}[/b]", ""]
|
||||
|
||||
if bh.rank_1v1:
|
||||
string.append("👤 [b]1v1[/b]")
|
||||
string.append(f"[b]{bh.rank_1v1}[/b] ({bh.rating_1v1} MMR)")
|
||||
string.append("")
|
||||
|
||||
if len(bh.duos) != 0:
|
||||
string.append(f"👥 [b]2v2[/b]")
|
||||
|
||||
for duo in sorted(bh.duos, key=lambda d: -d.rating_2v2):
|
||||
other = duo.other(bh)
|
||||
string.append(f"Con [b]{other.steam.user}[/b]: [b]{duo.rank_2v2}[/b] ({duo.rating_2v2} MMR)")
|
||||
|
||||
if len(bh.duos) != 0:
|
||||
string.append("")
|
||||
|
||||
return "\n".join(string)
|
28
royalpack/commands/cat.py
Normal file
28
royalpack/commands/cat.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from typing import *
|
||||
import royalnet.commands as rc
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
class CatCommand(rc.Command):
|
||||
name: str = "cat"
|
||||
|
||||
description: str = "Invia un gatto casuale in chat."
|
||||
|
||||
syntax: str = ""
|
||||
|
||||
aliases = ["catto", "kat", "kitty", "kitten", "gatto", "miao", "garf", "basta"]
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get("https://api.thecatapi.com/v1/images/search") as response:
|
||||
if response.status >= 400:
|
||||
raise rc.ExternalError(f"Request returned {response.status}")
|
||||
result = await response.json()
|
||||
assert len(result) == 1
|
||||
cat = result[0]
|
||||
assert "url" in cat
|
||||
url = cat["url"]
|
||||
async with session.get(url) as response:
|
||||
img = await response.content.read()
|
||||
await data.reply_image(image=io.BytesIO(img))
|
|
@ -1,3 +1,4 @@
|
|||
from typing import *
|
||||
import telegram
|
||||
from royalnet.commands import *
|
||||
|
||||
|
@ -5,12 +6,11 @@ from royalnet.commands import *
|
|||
class CiaoruoziCommand(Command):
|
||||
name: str = "ciaoruozi"
|
||||
|
||||
description: str = "Saluta Ruozi, un leggendario essere che una volta era in User Games."
|
||||
description: str = "Saluta Ruozi, un leggendario essere che è tornato in Royal Games."
|
||||
|
||||
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
|
||||
user: telegram.User = data.message.from_user
|
||||
# Se sei Ruozi, salutati da solo!
|
||||
if user.id == 112437036:
|
||||
await data.reply("👋 Ciao me!")
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from typing import *
|
||||
from royalnet.commands import *
|
||||
|
||||
|
||||
|
|
|
@ -77,7 +77,15 @@ class CvCommand(Command):
|
|||
activity += f" | 📺 {mact['name']}"
|
||||
# Custom Status
|
||||
elif mact["type"] == 4:
|
||||
activity += f" | ❓ {mact['state']}"
|
||||
if "emoji" in mact:
|
||||
emoji = f"{mact['emoji']['name']}"
|
||||
else:
|
||||
emoji = f"❓"
|
||||
if "state" in mact:
|
||||
state = f" {mact['state']}"
|
||||
else:
|
||||
state = ""
|
||||
activity += f" | {emoji}{state}"
|
||||
else:
|
||||
raise ExternalError(f"Unknown Discord activity type: {mact['type']}")
|
||||
|
||||
|
|
136
royalpack/commands/cvstats.py
Normal file
136
royalpack/commands/cvstats.py
Normal file
|
@ -0,0 +1,136 @@
|
|||
from typing import *
|
||||
import logging
|
||||
import asyncio
|
||||
import datetime
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
|
||||
from ..tables import Cvstats
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CvstatsCommand(rc.Command):
|
||||
name: str = "cvstats"
|
||||
|
||||
description: str = ""
|
||||
|
||||
syntax: str = ""
|
||||
|
||||
def __init__(self, interface: rc.CommandInterface):
|
||||
super().__init__(interface)
|
||||
if self.interface.name == "discord":
|
||||
self.loop.create_task(self._updater(1800))
|
||||
|
||||
def _is_ryg_member(self, member: dict):
|
||||
for role in member["roles"]:
|
||||
if role["id"] == self.interface.config["Cv"]["displayed_role_id"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def _update(self, db_session):
|
||||
log.info(f"Gathering Cvstats...")
|
||||
while True:
|
||||
try:
|
||||
response: Dict[str, Any] = await self.interface.call_herald_event("discord", "discord_cv")
|
||||
except rc.ConfigurationError:
|
||||
await asyncio.sleep(10)
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
users_total = 0
|
||||
members_total = 0
|
||||
users_online = 0
|
||||
members_online = 0
|
||||
users_connected = 0
|
||||
members_connected = 0
|
||||
users_playing = 0
|
||||
members_playing = 0
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
for m in response["guild"]["members"]:
|
||||
users_total += 1
|
||||
if self._is_ryg_member(m):
|
||||
members_total += 1
|
||||
|
||||
if m["status"]["main"] != "offline" and m["status"]["main"] != "idle":
|
||||
users_online += 1
|
||||
if self._is_ryg_member(m):
|
||||
members_online += 1
|
||||
|
||||
if m["voice"] is not None and not m["voice"]["afk"]:
|
||||
users_connected += 1
|
||||
if self._is_ryg_member(m):
|
||||
members_connected += 1
|
||||
|
||||
for mact in m["activities"]:
|
||||
if mact.get("type") == 0:
|
||||
users_playing += 1
|
||||
if self._is_ryg_member(m):
|
||||
members_playing += 1
|
||||
|
||||
assert users_online >= members_online
|
||||
assert users_online >= users_connected
|
||||
assert users_online >= users_playing
|
||||
assert members_online >= members_connected
|
||||
assert members_online >= members_playing
|
||||
|
||||
log.debug(f"Total users: {users_total}")
|
||||
log.debug(f"Total members: {members_total}")
|
||||
log.debug(f"Online users: {users_online}")
|
||||
log.debug(f"Online members: {members_online}")
|
||||
log.debug(f"Connected users: {users_connected}")
|
||||
log.debug(f"Connected members: {members_connected}")
|
||||
log.debug(f"Playing users: {users_playing}")
|
||||
log.debug(f"Playing members: {members_playing}")
|
||||
|
||||
CvstatsT = self.alchemy.get(Cvstats)
|
||||
|
||||
cvstats = CvstatsT(
|
||||
timestamp=datetime.datetime.now(),
|
||||
users_total=users_total,
|
||||
members_total=members_total,
|
||||
users_online=users_online,
|
||||
members_online=members_online,
|
||||
users_connected=users_connected,
|
||||
members_connected=members_connected,
|
||||
users_playing=users_playing,
|
||||
members_playing=members_playing
|
||||
)
|
||||
|
||||
log.debug("Saving to database...")
|
||||
db_session.add(cvstats)
|
||||
await ru.asyncify(db_session.commit)
|
||||
log.debug("Done!")
|
||||
|
||||
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()
|
||||
await self._update(session)
|
||||
session.close()
|
||||
log.info(f"Sleeping for {period}s")
|
||||
await asyncio.sleep(period)
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
CvstatsT = self.alchemy.get(Cvstats)
|
||||
|
||||
cvstats = data.session.query(CvstatsT).order_by(CvstatsT.timestamp.desc()).first()
|
||||
|
||||
message = [
|
||||
f"ℹ️ [b]Statistiche[/b]",
|
||||
f"Ultimo aggiornamento: [b]{cvstats.timestamp.strftime('%Y-%m-%d %H:%M')}[/b]",
|
||||
f"Utenti totali: [b]{cvstats.users_total}[/b]",
|
||||
f"Membri totali: [b]{cvstats.members_total}[/b]",
|
||||
f"Utenti online: [b]{cvstats.users_online}[/b]",
|
||||
f"Membri online: [b]{cvstats.members_online}[/b]",
|
||||
f"Utenti connessi: [b]{cvstats.users_connected}[/b]",
|
||||
f"Membri connessi: [b]{cvstats.members_connected}[/b]",
|
||||
f"Utenti in gioco: [b]{cvstats.users_playing}[/b]",
|
||||
f"Membri in gioco: [b]{cvstats.members_playing}[/b]"
|
||||
]
|
||||
|
||||
await data.reply("\n".join(message))
|
|
@ -1,11 +1,12 @@
|
|||
from typing import *
|
||||
import re
|
||||
import datetime
|
||||
import telegram
|
||||
import aiohttp
|
||||
from typing import *
|
||||
from royalnet.commands import *
|
||||
from royalnet.utils import asyncify
|
||||
from royalnet.backpack.tables import *
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
import royalnet.backpack.tables as rbt
|
||||
|
||||
from ..tables import *
|
||||
|
||||
|
||||
|
@ -13,7 +14,7 @@ async def to_imgur(imgur_api_key, photosizes: List[telegram.PhotoSize], caption=
|
|||
# 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)
|
||||
photo_file: telegram.File = await ru.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,
|
||||
|
@ -25,21 +26,20 @@ async def to_imgur(imgur_api_key, photosizes: List[telegram.PhotoSize], caption=
|
|||
}) as request:
|
||||
response = await request.json()
|
||||
if not response["success"]:
|
||||
raise CommandError("Imgur returned an error in the image upload.")
|
||||
raise rc.CommandError("Imgur returned an error in the image upload.")
|
||||
return response["data"]["link"]
|
||||
|
||||
|
||||
class DiarioCommand(Command):
|
||||
class DiarioCommand(rc.Command):
|
||||
name: str = "diario"
|
||||
|
||||
description: str = "Aggiungi una citazione al Diario."
|
||||
|
||||
syntax = "[!] \"{testo}\" --[autore], [contesto]"
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
if self.interface.name == "telegram":
|
||||
update: telegram.Update = data.update
|
||||
message: telegram.Message = update.message
|
||||
message: telegram.Message = data.message
|
||||
reply: telegram.Message = message.reply_to_message
|
||||
creator = await data.get_author()
|
||||
# noinspection PyUnusedLocal
|
||||
|
@ -71,12 +71,12 @@ class DiarioCommand(Command):
|
|||
media_url = None
|
||||
# Ensure there is a text or an image
|
||||
if not (text or media_url):
|
||||
raise InvalidInputError("Il messaggio a cui hai risposto non contiene testo o immagini.")
|
||||
raise rc.InvalidInputError("Il messaggio a cui hai risposto non contiene testo o immagini.")
|
||||
# Find the Royalnet account associated with the sender
|
||||
quoted_tg = await asyncify(data.session.query(self.alchemy.get(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
|
||||
quoted_tg = await ru.asyncify(data.session.query(self.alchemy.get(rbt.Telegram))
|
||||
.filter_by(tg_id=reply.from_user.id)
|
||||
.one_or_none)
|
||||
quoted_account = quoted_tg.user 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
|
||||
|
@ -122,13 +122,13 @@ class DiarioCommand(Command):
|
|||
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.alchemy.get(Alias))
|
||||
.filter_by(alias=quoted.lower()).one_or_none
|
||||
quoted_alias = await ru.asyncify(
|
||||
data.session.query(self.alchemy.get(rbt.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
|
||||
quoted_account = quoted_alias.user if quoted_alias is not None else None
|
||||
else:
|
||||
text = None
|
||||
quoted = None
|
||||
|
@ -137,7 +137,7 @@ class DiarioCommand(Command):
|
|||
context = None
|
||||
# Ensure there is a text or an image
|
||||
if not (text or media_url):
|
||||
raise InvalidInputError("Manca il testo o l'immagine da inserire nel diario.")
|
||||
raise rc.InvalidInputError("Manca il testo o l'immagine da inserire nel diario.")
|
||||
# Create the diario quote
|
||||
diario = self.alchemy.get(Diario)(creator=creator,
|
||||
quoted_account=quoted_account,
|
||||
|
@ -148,7 +148,7 @@ class DiarioCommand(Command):
|
|||
media_url=media_url,
|
||||
spoiler=spoiler)
|
||||
data.session.add(diario)
|
||||
await asyncify(data.session.commit)
|
||||
await ru.asyncify(data.session.commit)
|
||||
await data.reply(f"✅ {str(diario)}")
|
||||
else:
|
||||
# Find the creator of the quotes
|
||||
|
@ -173,7 +173,7 @@ class DiarioCommand(Command):
|
|||
timestamp = datetime.datetime.now()
|
||||
# Ensure there is some text
|
||||
if not text:
|
||||
raise InvalidInputError("Manca il testo o l'immagine da inserire nel diario.")
|
||||
raise rc.InvalidInputError("Manca il testo o l'immagine da inserire nel diario.")
|
||||
# Or a quoted
|
||||
if not quoted:
|
||||
quoted = None
|
||||
|
@ -181,17 +181,17 @@ class DiarioCommand(Command):
|
|||
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.alchemy.get(Alias))
|
||||
.filter_by(alias=quoted.lower())
|
||||
.one_or_none
|
||||
quoted_alias = await ru.asyncify(
|
||||
data.session.query(self.alchemy.get(rbt.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
|
||||
quoted_account = quoted_alias.user if quoted_alias is not None else None
|
||||
if quoted_alias is not None and quoted_account is None:
|
||||
raise UserError("Il nome dell'autore è ambiguo, quindi la riga non è stata aggiunta.\n"
|
||||
"Per piacere, ripeti il comando con un nome più specifico!")
|
||||
raise rc.UserError("Il nome dell'autore è ambiguo, quindi la riga non è stata aggiunta.\n"
|
||||
"Per piacere, ripeti il comando con un nome più specifico!")
|
||||
# Create the diario quote
|
||||
diario = self.alchemy.Diario(creator=creator,
|
||||
quoted_account=quoted_account,
|
||||
|
@ -202,5 +202,5 @@ class DiarioCommand(Command):
|
|||
media_url=None,
|
||||
spoiler=spoiler)
|
||||
data.session.add(diario)
|
||||
await asyncify(data.session.commit)
|
||||
await ru.asyncify(data.session.commit)
|
||||
await data.reply(f"✅ {str(diario)}")
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from royalnet.commands import *
|
||||
from royalnet.utils import *
|
||||
from typing import *
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
|
||||
from ..tables import Diario
|
||||
|
||||
|
||||
class DiarioquoteCommand(Command):
|
||||
class DiarioquoteCommand(rc.Command):
|
||||
name: str = "diarioquote"
|
||||
|
||||
description: str = "Cita una riga del diario."
|
||||
|
@ -12,12 +14,12 @@ class DiarioquoteCommand(Command):
|
|||
|
||||
syntax = "{id}"
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
async def run(self, args: rc.CommandArgs, data: rc.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.get(Diario)).get, entry_id)
|
||||
raise rc.CommandError("L'id che hai specificato non è valido.")
|
||||
entry: Diario = await ru.asyncify(data.session.query(self.alchemy.get(Diario)).get, entry_id)
|
||||
if entry is None:
|
||||
raise CommandError("Nessuna riga con quell'id trovata.")
|
||||
raise rc.CommandError("Nessuna riga con quell'id trovata.")
|
||||
await data.reply(f"ℹ️ {entry}")
|
||||
|
|
29
royalpack/commands/diarioshuffle.py
Normal file
29
royalpack/commands/diarioshuffle.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
from typing import *
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
from sqlalchemy import func
|
||||
|
||||
from ..tables import Diario
|
||||
|
||||
|
||||
class DiarioshuffleCommand(rc.Command):
|
||||
name: str = "diarioshuffle"
|
||||
|
||||
description: str = "Cita una riga casuale del diario."
|
||||
|
||||
aliases = ["dis", "dishuffle", "dish"]
|
||||
|
||||
syntax = ""
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
DiarioT = self.alchemy.get(Diario)
|
||||
entry: List[Diario] = await ru.asyncify(
|
||||
data.session
|
||||
.query(DiarioT)
|
||||
.order_by(func.random())
|
||||
.limit(1)
|
||||
.one_or_none
|
||||
)
|
||||
if entry is None:
|
||||
raise rc.CommandError("Nessuna riga del diario trovata.")
|
||||
await data.reply(f"ℹ️ {entry}")
|
181
royalpack/commands/dog.py
Normal file
181
royalpack/commands/dog.py
Normal file
|
@ -0,0 +1,181 @@
|
|||
from typing import *
|
||||
import royalnet.commands as rc
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
class DogCommand(rc.Command):
|
||||
name: str = "dog"
|
||||
|
||||
description: str = "Invia un cane della razza specificata in chat."
|
||||
|
||||
syntax: str = "[razza|list]"
|
||||
|
||||
_breeds = [
|
||||
"affenpinscher",
|
||||
"african",
|
||||
"airedale",
|
||||
"akita",
|
||||
"appenzeller",
|
||||
"australian-shepherd",
|
||||
"basenji",
|
||||
"beagle",
|
||||
"bluetick",
|
||||
"borzoi",
|
||||
"bouvier",
|
||||
"boxer",
|
||||
"brabancon",
|
||||
"briard",
|
||||
"buhund-norwegian",
|
||||
"bulldog-boston",
|
||||
"bulldog-english",
|
||||
"bulldog-french",
|
||||
"bullterrier-staffordshire",
|
||||
"cairn",
|
||||
"cattledog-australian",
|
||||
"chihuahua",
|
||||
"chow",
|
||||
"clumber",
|
||||
"cockapoo",
|
||||
"collie-border",
|
||||
"coonhound",
|
||||
"corgi-cardigan",
|
||||
"cotondetulear",
|
||||
"dachshund",
|
||||
"dalmatian",
|
||||
"dane-great",
|
||||
"deerhound-scottish",
|
||||
"dhole",
|
||||
"dingo",
|
||||
"doberman",
|
||||
"elkhound-norwegian",
|
||||
"entlebucher",
|
||||
"eskimo",
|
||||
"finnish-lapphund",
|
||||
"frise-bichon",
|
||||
"germanshepherd",
|
||||
"greyhound-italian",
|
||||
"groenendael",
|
||||
"havanese",
|
||||
"hound-afghan",
|
||||
"hound-basset",
|
||||
"hound-blood",
|
||||
"hound-english",
|
||||
"hound-ibizan",
|
||||
"hound-plott",
|
||||
"hound-walker",
|
||||
"husky",
|
||||
"keeshond",
|
||||
"kelpie",
|
||||
"komondor",
|
||||
"kuvasz",
|
||||
"labrador",
|
||||
"leonberg",
|
||||
"lhasa",
|
||||
"malamute",
|
||||
"malinois",
|
||||
"maltese",
|
||||
"mastiff-bull",
|
||||
"mastiff-english",
|
||||
"mastiff-tibetan",
|
||||
"mexicanhairless",
|
||||
"mix",
|
||||
"mountain-bernese",
|
||||
"mountain-swiss",
|
||||
"newfoundland",
|
||||
"otterhound",
|
||||
"ovcharka-caucasian",
|
||||
"papillon",
|
||||
"pekinese",
|
||||
"pembroke",
|
||||
"pinscher-miniature",
|
||||
"pitbull",
|
||||
"pointer-german",
|
||||
"pointer-germanlonghair",
|
||||
"pomeranian",
|
||||
"poodle-miniature",
|
||||
"poodle-standard",
|
||||
"poodle-toy",
|
||||
"pug",
|
||||
"puggle",
|
||||
"pyrenees",
|
||||
"redbone",
|
||||
"retriever-chesapeake",
|
||||
"retriever-curly",
|
||||
"retriever-flatcoated",
|
||||
"retriever-golden",
|
||||
"ridgeback-rhodesian",
|
||||
"rottweiler",
|
||||
"saluki",
|
||||
"samoyed",
|
||||
"schipperke",
|
||||
"schnauzer-giant",
|
||||
"schnauzer-miniature",
|
||||
"setter-english",
|
||||
"setter-gordon",
|
||||
"setter-irish",
|
||||
"sheepdog-english",
|
||||
"sheepdog-shetland",
|
||||
"shiba",
|
||||
"shihtzu",
|
||||
"spaniel-blenheim",
|
||||
"spaniel-brittany",
|
||||
"spaniel-cocker",
|
||||
"spaniel-irish",
|
||||
"spaniel-japanese",
|
||||
"spaniel-sussex",
|
||||
"spaniel-welsh",
|
||||
"springer-english",
|
||||
"stbernard",
|
||||
"terrier-american",
|
||||
"terrier-australian",
|
||||
"terrier-bedlington",
|
||||
"terrier-border",
|
||||
"terrier-dandie",
|
||||
"terrier-fox",
|
||||
"terrier-irish",
|
||||
"terrier-kerryblue",
|
||||
"terrier-lakeland",
|
||||
"terrier-norfolk",
|
||||
"terrier-norwich",
|
||||
"terrier-patterdale",
|
||||
"terrier-russell",
|
||||
"terrier-scottish",
|
||||
"terrier-sealyham",
|
||||
"terrier-silky",
|
||||
"terrier-tibetan",
|
||||
"terrier-toy",
|
||||
"terrier-westhighland",
|
||||
"terrier-wheaten",
|
||||
"terrier-yorkshire",
|
||||
"vizsla",
|
||||
"waterdog-spanish",
|
||||
"weimaraner",
|
||||
"whippet",
|
||||
"wolfhound-irish",
|
||||
]
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
breed = args.joined()
|
||||
if breed:
|
||||
if breed == "list":
|
||||
await data.reply("\n".join(["ℹ️ Razze disponibili:", [f"[c]{breed}[/c]" for breed in self._breeds]]))
|
||||
if breed in self._breeds:
|
||||
url = f"https://dog.ceo/api/breed/{breed}/images/random"
|
||||
else:
|
||||
raise rc.InvalidInputError("Questa razza non è disponibile.\n")
|
||||
else:
|
||||
url = f"https://dog.ceo/api/breeds/image/random"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as response:
|
||||
if response.status >= 400:
|
||||
raise rc.ExternalError(f"Request returned {response.status}")
|
||||
result = await response.json()
|
||||
assert "status" in result
|
||||
assert result["status"] == "success"
|
||||
assert "message" in result
|
||||
url = result["message"]
|
||||
async with session.get(url) as response:
|
||||
img = await response.content.read()
|
||||
await data.reply_image(image=io.BytesIO(img))
|
106
royalpack/commands/dota.py
Normal file
106
royalpack/commands/dota.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
from typing import *
|
||||
import logging
|
||||
import aiohttp
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
from royalnet.backpack import tables as rbt
|
||||
from .abstract.linker import LinkerCommand
|
||||
|
||||
from ..tables import Steam, Dota
|
||||
from ..types import DotaRank
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DotaCommand(LinkerCommand):
|
||||
name: str = "dota"
|
||||
|
||||
aliases = ["dota2", "doto", "doto2", "dotka", "dotka2"]
|
||||
|
||||
description: str = "Visualizza le tue statistiche di Dota."
|
||||
|
||||
syntax: str = ""
|
||||
|
||||
def describe(self, obj: Steam) -> str:
|
||||
string = f"ℹ️ [b]{obj.persona_name}[/b]\n"
|
||||
if obj.dota.rank:
|
||||
string += f"{obj.dota.rank}\n"
|
||||
string += f"\n" \
|
||||
f"Wins: [b]{obj.dota.wins}[/b]\n" \
|
||||
f"Losses: [b]{obj.dota.losses}[/b]\n" \
|
||||
f"\n"
|
||||
return string
|
||||
|
||||
async def get_updatables_of_user(self, session, user: rbt.User) -> List[Dota]:
|
||||
return user.steam
|
||||
|
||||
async def get_updatables(self, session) -> List[Dota]:
|
||||
return await ru.asyncify(session.query(self.alchemy.get(Steam)).all)
|
||||
|
||||
async def create(self,
|
||||
session,
|
||||
user: rbt.User,
|
||||
args: rc.CommandArgs,
|
||||
data: Optional[rc.CommandData] = None) -> Optional[Dota]:
|
||||
raise rc.InvalidInputError("Dota accounts are automatically linked from Steam.")
|
||||
|
||||
async def update(self, session, obj: Steam, change: Callable[[str, Any], Awaitable[None]]):
|
||||
log.debug(f"Getting player data from OpenDota...")
|
||||
async with aiohttp.ClientSession() as hcs:
|
||||
# Get profile data
|
||||
async with hcs.get(f"https://api.opendota.com/api/players/{obj.steamid.as_32}/") as response:
|
||||
if response.status != 200:
|
||||
raise rc.ExternalError(f"OpenDota / returned {response.status}!")
|
||||
p = await response.json()
|
||||
# No such user
|
||||
if "profile" not in p:
|
||||
log.debug(f"Not found: {obj}")
|
||||
return
|
||||
# Get win/loss data
|
||||
async with hcs.get(f"https://api.opendota.com/api/players/{obj.steamid.as_32}/wl") as response:
|
||||
if response.status != 200:
|
||||
raise rc.ExternalError(f"OpenDota /wl returned {response.status}!")
|
||||
wl = await response.json()
|
||||
# No such user
|
||||
if wl["win"] == 0 and wl["lose"] == 0:
|
||||
log.debug(f"Not found: {obj}")
|
||||
return
|
||||
# Find the Dota record, if it exists
|
||||
dota: Dota = obj.dota
|
||||
if dota is None:
|
||||
# Autocreate the Dota record
|
||||
dota = self.alchemy.get(Dota)(steam=obj)
|
||||
session.add(dota)
|
||||
session.flush()
|
||||
|
||||
# Make a custom change function
|
||||
async def change(attribute: str, new: Any):
|
||||
await self._change(session=session, obj=dota, attribute=attribute, new=new)
|
||||
|
||||
await change("wins", wl["win"])
|
||||
await change("losses", wl["lose"])
|
||||
if p["rank_tier"]:
|
||||
await change("rank", DotaRank(rank_tier=p["rank_tier"]))
|
||||
else:
|
||||
await change("rank", None)
|
||||
|
||||
async def on_increase(self, session, obj: Dota, attribute: str, old: Any, new: Any) -> None:
|
||||
if attribute == "rank":
|
||||
await self.notify(f"📈 [b]{obj.steam.user}[/b] è salito a [b]{new}[/b] su Dota 2! Congratulazioni!")
|
||||
|
||||
async def on_unchanged(self, session, obj: Dota, attribute: str, old: Any, new: Any) -> None:
|
||||
pass
|
||||
|
||||
async def on_decrease(self, session, obj: Dota, attribute: str, old: Any, new: Any) -> None:
|
||||
if attribute == "rank":
|
||||
await self.notify(f"📉 [b]{obj.steam.user}[/b] è sceso a [b]{new}[/b] su Dota 2.")
|
||||
|
||||
async def on_first(self, session, obj: Dota, attribute: str, old: None, new: Any) -> None:
|
||||
if attribute == "wins":
|
||||
await self.notify(f"↔️ Account {obj} connesso a {obj.steam.user}!")
|
||||
elif attribute == "rank":
|
||||
await self.notify(f"🌟 [b]{obj.steam.user}[/b] si è classificato [b]{new}[/b] su Dota 2!")
|
||||
|
||||
async def on_reset(self, session, obj: Dota, attribute: str, old: Any, new: None) -> None:
|
||||
if attribute == "rank":
|
||||
await self.notify(f"⬜️ [b]{obj.steam.user}[/b] non ha più un rank su Dota 2.")
|
|
@ -28,8 +28,7 @@ class EatCommand(Command):
|
|||
"evilbalu": "🚹 Hai mangiato {food}.\n[i]Sa di snado.[/i]",
|
||||
"balubis": "🚹 Hai mangiato {food}.\n[i]Sa di acqua calda.[/i]",
|
||||
"goodbalu": "🚹 Hai mangiato {food}.\n[i]Sa di acqua calda.[/i]",
|
||||
"chiara": "🚺 Hai mangiato {food}.\n[i]Sa un po' di biscotto, ma per lo più sa di curcuma, pepe e spezie"
|
||||
" varie.[/i]",
|
||||
"chiara": "🚺 Hai mangiato {food}.\n[i]Sa un po' di biscotto, ma per lo più sa di curcuma e pepe.[/i]",
|
||||
"fabio": "🚹 Hai mangiato {food}.\n[i]Sa di gelatina tuttigusti+1.[/i]",
|
||||
"proto": "🚹 Hai mangiato {food}.\n[i]Sa di gelatina tuttigusti+1.[/i]",
|
||||
"marco": "🚹 Hai mangiato {food}.\n[i]Sa di carlino <.<[/i]",
|
||||
|
@ -38,15 +37,28 @@ class EatCommand(Command):
|
|||
"maxsensei": "🚹 Hai mangiato {food}.\n[i]Sa di merda.[/i]",
|
||||
"steffo": "🚹 Hai mangiato {food}.\n[i]Sa di gelato e di Coca-Cola.[/i]",
|
||||
|
||||
# Sezione in cui mangi i professori dei membri Royal Games
|
||||
"arrigo": "🖍 Hai mangiato {food}!\n[i]Ti scrive F: V→W sulla parete dello stomaco con i gessetti colorati.[/i]",
|
||||
"bonisoli": "🖍 Hai mangiato {food}!\n[i]Ti scrive F: V→W sulla parete dello stomaco con i gessetti colorati.[/i]",
|
||||
"montangero": "📝 Hai mangiato la {food}!\n[i]La digerisci in O(n!).[/i]",
|
||||
"marongiu": "🔧 Hai mangiato {food}!\n[i]Il tuo apparato digerente viene trasformato in una pipeline.[/i]",
|
||||
"mandreoli": "⚠️ Hai mangiato la {food}!\n[c]Error: Segmentation fault (core dumped)[/c]",
|
||||
"la rocca": "📊 Hai mangiato {food}!\n[i]Si distribuisce nel tuo intestino come una Normale.[/i]",
|
||||
"villani": "🐜 Hai mangiato {food}!\n[i]Crea una rete neurale sfruttando i tuoi neuroni e le tue cellule.[/i]",
|
||||
"novellani": "❓Volevi mangiare {food}...\n[i]...ma invece trovi solo Dell'Amico.[/i]",
|
||||
|
||||
# Sezione delle supercazzole
|
||||
"antani": "❔ Hai mangiato {food}. \n[i]Con tarapia tapioco o scherziamo? No, mi permetta. Noi siamo in 4.\n"
|
||||
"Come se fosse antani anche per lei soltanto in due, oppure in quattro anche scribàcchi confaldina?\n"
|
||||
"Come antifurto, per esempio.[/i]",
|
||||
"indice": "☝️ Hai mangiato l'{food}. \n[i]Ecco, lo alzi. Lo vede, lo vede che stuzzica?[/i]",
|
||||
|
||||
# sezione con piante e anmali
|
||||
# Sezione con piante e animali
|
||||
"cactus": "🌵 Hai mangiato un {food}.\n[i]Gli hai tolto le spine prima, vero?[/i]",
|
||||
"tango": "🌳 Hai mangiato un {food}, e un albero insieme ad esso.\n[i]Senti le tue ferite curarsi...[/i]",
|
||||
"foglia": "🍁 Hai mangiato la {food}.\n[i]A te non la si fa![/i]",
|
||||
"pug": "🐶 Hai provato a mangiare un {food}...\n[i]...Ma Mallllco si è precipitato in soccorso e lo ha salvato![/i]",
|
||||
"carlino": "🐶 Hai provato a mangiare un {food}...\n[i]...Ma Mallllco si è precipitato in soccorso e lo ha salvato![/i]",
|
||||
"gatto": "🐱 Vieni fermato prima di poter compiere questo gesto orribile.\n"
|
||||
"[i]Il {food} verrà pettato da tutti per farlo riavere dal trauma.[/i]",
|
||||
"3 porcellini": "🐷 Hai mangiato i {food}.\n[i]La casa di mattoni non è bastata a fermarti![/i]",
|
||||
|
@ -73,13 +85,19 @@ class EatCommand(Command):
|
|||
"little salami": "🥓 Mmmh, tasty!\n[i]Cats can have {food} too![/i]",
|
||||
"a little salami": "🥓 Mmmh, tasty!\n[i]Cats can have {food} too![/i]",
|
||||
"pollo": '🍗 Il {food} che hai appena mangiato proveniva dallo spazio.\n[i]Coccodè?[/i]',
|
||||
"pranzo di marco": '🍗 Hai mangiato il {food}.\n[i]Ti senti lactose-free, ma un po\' povero in calcio.[/i]',
|
||||
"pranzo di mallllco": '🍗 Hai mangiato il {food}.\n[i]Ti senti lactose-free, ma un po\' povero in calcio.[/i]',
|
||||
"gnocchetti": "🥘 Ullà, sono duri 'sti {food}!\n[i]Fai fatica a digerirli.[/i]",
|
||||
"spam": "🥫 Hai mangiato {food}. La famosa carne in gelatina, ovviamente!\n[i]A questo proposito, di "
|
||||
"sicuro sarai interessato all'acquisto di 1087 scatole di Simmenthal in offerta speciale![/i]",
|
||||
"riso": "🍚 Hai mangiato del {food}. Non ci resta che il Pianto! \n[i]Ba dum tsss![/i]",
|
||||
"gelato": "🍨 Mangiando del {food}, hai invocato Steffo.\n[i]Cedigli ora il tuo gelato.[/i]",
|
||||
"gelato di steffo": "🍨 Hai provato a rubare il {food}...\n[i]...Ma sei arrivato tardi: l'ha già mangiato.[/i]",
|
||||
"biscotto": "🍪 Hai mangiato un {food} di contrabbando.\n[i]L'Inquisizione non lo saprà mai![/i]",
|
||||
"biscotti": "🍪 Hai mangiato tanti {food} di contrabbando.\n[i]Attento! L'Inquisizione è sulle tue tracce![/i]",
|
||||
"crocchette di pollo": "🍗 Hai mangiato {food}!\n[i]Dio porco maledetto, infame, CAPRA, porca Madonna, Dio cane, "
|
||||
"HAI PERSO. UN POMERIGGIO PER C- ooh se è questo dio cane, altro che sfondamento dei cieli "
|
||||
"*roba non capibile*, sfondi tutti dio can li distruggi, non ci rimane più niente.[/i]",
|
||||
|
||||
# Sezione delle bevande
|
||||
"acqua": "💧 Hai bevuto un po' d'{food}.\n[i]Ti depura e ti fa fare tanta plin plin![/i}",
|
||||
|
@ -95,6 +113,7 @@ class EatCommand(Command):
|
|||
"kaffé": "☕️ Ma BUONGIORNISSIMOOO !!!!\n[i]Non si può iniziare la giornata senza un buon {food} !![/i]",
|
||||
"kaffe": "☕️ Ma BUONGIORNISSIMOOO !!!!\n[i]Non si può iniziare la giornata senza un buon {food} !![/i]",
|
||||
"birra": "🍺 Hai mangiato {food}.\n[i]Adesso sei un povero barbone alcolizzato.[/i]",
|
||||
"martini": "🍸 Hai ordinato un {food}. Agitato, non mescolato.\n[i]Adesso hai licenza di uccidere![/i]",
|
||||
"redbull": "🍾 Hai mangiato {food}.\n[i]Adesso puoi volare![/i]",
|
||||
"red bull": "🍾 Hai mangiato {food}.\n[i]Adesso puoi volare![/i]",
|
||||
|
||||
|
@ -109,8 +128,20 @@ class EatCommand(Command):
|
|||
"red hat": "🐧 Hai mangiato {food}.\n[i]La tua anima appartiene a IBM, ora.[/i]",
|
||||
"redhat": "🐧 Hai mangiato {food}.\n[i]La tua anima appartiene a IBM, ora.[/i]",
|
||||
"linux from scratch": "🐧 Hai mangiato {food}.\n[i]Sei diventato un puzzle.[/i]",
|
||||
|
||||
|
||||
# Citazioni da film (nello specifico dai Blues Brothers)
|
||||
"pane bianco tostato liscio, quattro polli fritti e una coca": "🕶 Tu e tuo fratello avete ordinato {food}."
|
||||
" Il cuoco vi ha riconosciuto e vuole tornare a suonare nella vostra band.\n[i]Sua moglie gliene canta"
|
||||
" quattro (letteralmente), ma non riesce a fargli cambiare idea. Siete in missione per conto di Dio![/i]",
|
||||
"pane bianco tostato liscio": "🕶 Tu e tuo fratello avete ordinato {food}, quattro polli fritti e una coca."
|
||||
" Il cuoco vi ha riconosciuto e vuole tornare a suonare nella vostra band.\n[i]Sua moglie gliene canta"
|
||||
" quattro (letteralmente), ma non riesce a fargli cambiare idea. Siete in missione per conto di Dio![/i]",
|
||||
"quattro polli fritti e una coca": "🕶 Tu e tuo fratello avete ordinato pane bianco tostato liscio, {food}."
|
||||
" Il cuoco vi ha riconosciuto e vuole tornare a suonare nella vostra band.\n[i]Sua moglie gliene canta"
|
||||
" quattro (letteralmente), ma non riesce a fargli cambiare idea. Siete in missione per conto di Dio![/i]",
|
||||
|
||||
# Altro
|
||||
"vendetta": "😈 Ti sei gustato la tua {food}.\n[i]Deliziosa, se servita fredda![/i]",
|
||||
"demone": "👿 Hai mangiato un {food}. Non l'ha presa bene...\n[i]Hai terribili bruciori di stomaco.[/i]",
|
||||
"diavolo": "👿 Hai mangiato un {food}. Non l'ha presa bene...\n[i]Hai terribili bruciori di stomaco.[/i]",
|
||||
"cacca": "💩 Che schifo! Hai mangiato {food}!\n[i]Allontati per favore, PLEH![/i]",
|
||||
|
@ -121,12 +152,19 @@ class EatCommand(Command):
|
|||
"bot": "🤖 Come osi provare a mangiarmi?!\n[i]Il {food} è arrabbiato con te.[/i]",
|
||||
"royal bot": "🤖 Come osi provare a mangiarmi?!\n[i]Il {food} è arrabbiato con te.[/i]",
|
||||
"re": "👑 Hai mangiato il {food} avversario! \n[i]Scacco matto![/i]",
|
||||
"furry": "🐕 Hai mangiato {food}.\n[i]OwO[/i]",
|
||||
"qualcosa che non mi piace": "🥦 Hai assaggiato il cibo, ma non ti piace proprio./n[i]Dai, mangialo, che ti"
|
||||
" fa bene! In africa i bambini muoiono di fame, e tu... ![/i]",
|
||||
"qualcosa che non ti piace": "🥦 Hai assaggiato il cibo, ma non ti piace proprio./n[i]Dai, mangialo, che ti"
|
||||
" fa bene! In africa i bambini muoiono di fame, e tu... ![/i]",
|
||||
"polvere": "☁️ Hai mangiato la {food}.\n[i]Ti hanno proprio battuto![/i]",
|
||||
"giaroun": "🥌 Il {food} che hai mangiato era duro come un {food}.\n[i]Stai soffrendo di indigestione![/i]",
|
||||
"giarone": "🥌 Il {food} che hai mangiato era duro come un {food}.\n[i]Stai soffrendo di indigestione![/i]",
|
||||
"sasso": "🥌 Il {food} che hai mangiato era duro come un {food}.\n[i]Stai soffrendo di indigestione![/i]",
|
||||
"bomba": "💣 Hai mangiato una {food}. Speriamo fosse solo calorica!\n[i]3... 2... 1...[/i]",
|
||||
"ass": "🕳 Hai mangiato {food}./n[i]Bleah! Lo sai cosa fa quel coso per sopravvivere?[/i]",
|
||||
"ass": "🕳 Hai mangiato {food}.\n[i]Bleah! Lo sai cosa fa quel coso per sopravvivere?[/i]",
|
||||
"onion": "🗞 You ate the {food}. Ci sei proprio cascato!\n [i]Hai mai creduto a una notizia di Lercio,"
|
||||
" invece?[/i]",
|
||||
"uranio": "☢️ L'{food} che hai mangiato era radioattivo.\n[i]Stai brillando di verde![/i]",
|
||||
"tide pod": "☣️ I {food} che hai mangiato erano buonissimi.\n[i]Stai sbiancando![/i]",
|
||||
"tide pods": "☣️ I {food} che hai mangiato erano buonissimi.\n[i]Stai sbiancando![/i]",
|
||||
|
@ -138,6 +176,19 @@ class EatCommand(Command):
|
|||
"nulla": "⬜️ Non hai mangiato {food}.\n[i]Hai ancora più fame.[/i]",
|
||||
"torta": "⬜️ Non hai mangiato niente.\n[i]La {food} è una menzogna![/i]",
|
||||
"cake": "⬜️ Non hai mangiato niente.\n[i]The {food} is a lie![/i]",
|
||||
"markov": "🗨 Stai cercando di mangiare... un matematico russo di nome {food}?\n[i]Lo trovi un po' indigesto.[/i]",
|
||||
"mia sul fiume": "💧 Hai mangiato il miglior piatto al mondo, la {food}, esclusivo ai membri Royal Games.\n"
|
||||
"[i]Nessuno, tranne il bot, sa di cosa è fatta esattamente, ma una cosa è certa: è "
|
||||
"buonissima![/i]",
|
||||
"angelo": "👼 Oh mio dio! E' un {food}!\n[i]Ora hai un digramma ad onda blu.[/i]",
|
||||
"unicode": "🍗 Hai mangiato {food}!\n๓ค 𝔫𝔬𝔫 [i]è[/i] 𝓼𝓾𝓬𝓬𝓮𝓼𝓼𝓸 𝕟𝕦𝕝𝕝𝕒.",
|
||||
"eco": "🏔 Hai mangiato l'{food} eco eco!\n[i]Ma non è successo nulla ulla ulla.[/i]",
|
||||
"disinfettante": "🧴Hai mangiato {food}!\n[i]Secondo Trump, ora sei molto più sano.[/i]",
|
||||
|
||||
"terraria": "🌳 Hai provato a mangiare {food}, ma non ne sei stato all'Altezza (Coniglio).\n[i]Prova a mangiare qualcos'altro...[/i]",
|
||||
"cooked fish": "🐟 Hai mangiato {food}.\n[i]Ora sei Well Fed per 20 minuti.[/i]",
|
||||
"gestione": "🌐 Hai mangiato {food}, su cui si basa Condivisione.\n[i]Fa ridere di sotto, ma fa anche riflettere di sopra.[/i]",
|
||||
"condivisione": "🌐 Hai mangiato {food}, basato su Gestione.\n[i]Fa ridere di sopra, ma fa anche riflettere di sotto.[/i]",
|
||||
}
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from typing import *
|
||||
import random
|
||||
from royalnet.commands import *
|
||||
import royalnet.commands as rc
|
||||
|
||||
|
||||
class EmojifyCommand(Command):
|
||||
class EmojifyCommand(rc.Command):
|
||||
name: str = "emojify"
|
||||
|
||||
description: str = "Converti un messaggio in emoji."
|
||||
|
@ -94,6 +95,6 @@ class EmojifyCommand(Command):
|
|||
new_string = new_string.replace(key, selected_emoji)
|
||||
return new_string
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
string = args.joined(require_at_least=1)
|
||||
await data.reply(self._emojify(string))
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import royalnet
|
||||
from royalnet.commands import *
|
||||
from royalnet.backpack.tables import *
|
||||
from typing import *
|
||||
import royalnet.commands as rc
|
||||
import royalnet.backpack.tables as rbt
|
||||
|
||||
|
||||
class EvalCommand(Command):
|
||||
class EvalCommand(rc.Command):
|
||||
# oh god if there is a security vulnerability
|
||||
name: str = "eval"
|
||||
|
||||
|
@ -11,13 +11,13 @@ class EvalCommand(Command):
|
|||
|
||||
syntax: str = "{espressione}"
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
user: User = await data.get_author(error_if_none=True)
|
||||
if user.role != "Admin":
|
||||
raise CommandError("Non sei autorizzato a eseguire codice arbitrario!\n"
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
user: rbt.User = await data.get_author(error_if_none=True)
|
||||
if "admin" not in user.roles:
|
||||
raise rc.CommandError("Non sei autorizzato a eseguire codice arbitrario!\n"
|
||||
"(Sarebbe un po' pericoloso se te lo lasciassi eseguire, non trovi?)")
|
||||
try:
|
||||
result = eval(args.joined(require_at_least=1))
|
||||
except Exception as e:
|
||||
raise CommandError(f"Eval fallito: {e}")
|
||||
raise rc.CommandError(f"Eval fallito: {e}")
|
||||
await data.reply(repr(result))
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import royalnet
|
||||
from royalnet.commands import *
|
||||
from royalnet.backpack.tables import *
|
||||
from typing import *
|
||||
import royalnet.commands as rc
|
||||
import royalnet.backpack.tables as rbt
|
||||
|
||||
|
||||
class ExecCommand(Command):
|
||||
class ExecCommand(rc.Command):
|
||||
# oh god if there is a security vulnerability
|
||||
name: str = "exec"
|
||||
|
||||
|
@ -11,13 +11,13 @@ class ExecCommand(Command):
|
|||
|
||||
syntax: str = "{script}"
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
user: User = await data.get_author(error_if_none=True)
|
||||
if user.role != "Admin":
|
||||
raise CommandError("Non sei autorizzato a eseguire codice arbitrario!\n"
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
user: rbt.User = await data.get_author(error_if_none=True)
|
||||
if "admin" not in user.roles:
|
||||
raise rc.CommandError("Non sei autorizzato a eseguire codice arbitrario!\n"
|
||||
"(Sarebbe un po' pericoloso se te lo lasciassi eseguire, non trovi?)")
|
||||
try:
|
||||
exec(args.joined(require_at_least=1))
|
||||
except Exception as e:
|
||||
raise CommandError(f"Esecuzione fallita: {e}")
|
||||
raise rc.CommandError(f"Esecuzione fallita: {e}")
|
||||
await data.reply(f"✅ Fatto!")
|
||||
|
|
53
royalpack/commands/fortune.py
Normal file
53
royalpack/commands/fortune.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from typing import *
|
||||
import royalnet
|
||||
import royalnet.commands as rc
|
||||
import random
|
||||
import datetime
|
||||
|
||||
|
||||
class FortuneCommand(rc.Command):
|
||||
name: str = "fortune"
|
||||
|
||||
description: str = "Quanto sarai fortunato oggi?"
|
||||
|
||||
syntax: str = ""
|
||||
|
||||
_fortunes = [
|
||||
"😄 Oggi sarà una fantastica giornata!",
|
||||
"😌 Oggi sarà una giornata molto chill e rilassante.",
|
||||
"💰 Oggi sui tuoi alberi cresceranno più Stelline!",
|
||||
"🍎 Oggi un unicorno ti lascerà la sua Blessed Apple!",
|
||||
"📈 Oggi il tuo team in ranked sarà più amichevole e competente del solito!",
|
||||
"🏝 Oggi potrai raggiungere l'Isola Miraggio!",
|
||||
"🐱 Oggi vedrai più gatti del solito su Internet!",
|
||||
"🐶 Oggi vedrai più cani del solito su Internet!",
|
||||
"🐦 Oggi vedrai più uccelli del solito su Internet!",
|
||||
"🔥 Oggi vedrai più flame del solito su Internet!",
|
||||
"🤬 Oggi vedrai più discorsi politici del solito su Internet!",
|
||||
"🐌 Oggi incontrerai una chiocciola sperduta!",
|
||||
"🎁 Oggi i dispenser di regali in centro funzioneranno senza problemi!",
|
||||
"🥕 Oggi il tuo raccolto avrà qualità Iridium Star!",
|
||||
"🔴 Oggi troverai più oggetti di rarità rossa del solito!",
|
||||
"✨ Oggi farai molti più multicast!",
|
||||
"♦️ Oggi troverai una Leggendaria Dorata!",
|
||||
"⭐️ Oggi la stella della RYG ti sembrerà un pochino più dritta!",
|
||||
"⭐️ Oggi la stella della RYG ti sembrerà anche più storta del solito!",
|
||||
"💎 Oggi i tuoi avversari non riusciranno a deflettere i tuoi Emerald Splash!",
|
||||
"⁉️ Oggi le tue supercazzole prematureranno un po' più a sinistra!",
|
||||
"🌅 Oggi sarà il giorno dopo ieri e il giorno prima di domani!",
|
||||
"🤖 Oggi il Royal Bot ti dirà qualcosa di molto utile!",
|
||||
"💤 Oggi rischierai di addormentarti più volte!",
|
||||
"🥪 Oggi ti verrà fame fuori orario!",
|
||||
"😓 Oggi dirai molte stupidaggini!",
|
||||
]
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
author = await data.get_author()
|
||||
today = datetime.date.today()
|
||||
|
||||
h = author.uid * hash(today)
|
||||
|
||||
r = random.Random(x=h)
|
||||
|
||||
message = r.sample(self._fortunes, 1)[0]
|
||||
await data.reply(message)
|
42
royalpack/commands/givefiorygi.py
Normal file
42
royalpack/commands/givefiorygi.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
from typing import *
|
||||
import royalnet.commands as rc
|
||||
import royalnet.backpack.tables as rbt
|
||||
|
||||
from ..tables import FiorygiTransaction
|
||||
|
||||
|
||||
class GivefiorygiCommand(rc.Command):
|
||||
name: str = "givefiorygi"
|
||||
|
||||
description: str = "Cedi fiorygi a un altro utente."
|
||||
|
||||
syntax: str = "{destinatario} {quantità} {motivo}"
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
author = await data.get_author(error_if_none=True)
|
||||
|
||||
user_arg = args[0]
|
||||
qty_arg = args[1]
|
||||
|
||||
if user_arg is None:
|
||||
raise rc.InvalidInputError("Non hai specificato un destinatario!")
|
||||
user = await rbt.User.find(self.alchemy, data.session, user_arg)
|
||||
if user is None:
|
||||
raise rc.InvalidInputError("L'utente specificato non esiste!")
|
||||
if user.uid == author.uid:
|
||||
raise rc.InvalidInputError("Non puoi inviare fiorygi a te stesso!")
|
||||
|
||||
if qty_arg is None:
|
||||
raise rc.InvalidInputError("Non hai specificato una quantità!")
|
||||
try:
|
||||
qty = int(qty_arg)
|
||||
except ValueError:
|
||||
raise rc.InvalidInputError("La quantità specificata non è un numero!")
|
||||
if qty <= 0:
|
||||
raise rc.InvalidInputError("La quantità specificata deve essere almeno 1!")
|
||||
|
||||
if author.fiorygi.fiorygi < qty:
|
||||
raise rc.InvalidInputError("Non hai abbastanza fiorygi per effettuare la transazione!")
|
||||
|
||||
await FiorygiTransaction.spawn_fiorygi(data, author, -qty, f"aver ceduto fiorygi a {user}")
|
||||
await FiorygiTransaction.spawn_fiorygi(data, user, qty, f"aver ricevuto fiorygi da {author}")
|
35
royalpack/commands/givetreasure.py
Normal file
35
royalpack/commands/givetreasure.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from typing import *
|
||||
import royalnet
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
from ..tables import Treasure, FiorygiTransaction
|
||||
from .magicktreasure import MagicktreasureCommand
|
||||
|
||||
|
||||
class GivetreasureCommand(MagicktreasureCommand):
|
||||
name: str = "givetreasure"
|
||||
|
||||
description: str = "Crea un nuovo Treasure di Fiorygi (usando il tuo credito)"
|
||||
|
||||
syntax: str = "{codice} {valore}"
|
||||
|
||||
async def _permission_check(self, author, code, value, data):
|
||||
if author.fiorygi.fiorygi < value:
|
||||
raise rc.UserError("Non hai abbastanza fiorygi per creare questo Treasure.")
|
||||
|
||||
async def _create_treasure(self, author, code, value, data):
|
||||
TreasureT = self.alchemy.get(Treasure)
|
||||
|
||||
treasure = await ru.asyncify(data.session.query(TreasureT).get, code)
|
||||
if treasure is not None:
|
||||
raise rc.UserError("Esiste già un Treasure con quel codice.")
|
||||
|
||||
treasure = TreasureT(
|
||||
code=code,
|
||||
value=value,
|
||||
redeemed_by=None
|
||||
)
|
||||
|
||||
await FiorygiTransaction.spawn_fiorygi(data, author, -value, "aver creato un tesoro")
|
||||
|
||||
return treasure
|
|
@ -1,16 +0,0 @@
|
|||
from .play import PlayCommand
|
||||
|
||||
|
||||
class GooglevideoCommand(PlayCommand):
|
||||
name: str = "googlevideo"
|
||||
|
||||
aliases = ["gv"]
|
||||
|
||||
description: str = "Cerca un video su Google Video e lo aggiunge alla coda della chat vocale."
|
||||
|
||||
syntax = "{ricerca}"
|
||||
|
||||
async def get_url(self, args):
|
||||
return f"gvsearch:{args.joined()}"
|
||||
|
||||
# Too bad gvsearch: always finds nothing.
|
37
royalpack/commands/help.py
Normal file
37
royalpack/commands/help.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from typing import *
|
||||
import royalnet
|
||||
import royalnet.commands as rc
|
||||
|
||||
|
||||
class HelpCommand(rc.Command):
|
||||
name: str = "help"
|
||||
|
||||
description: str = "Visualizza informazioni su un comando."
|
||||
|
||||
syntax: str = "{comando}"
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
if len(args) == 0:
|
||||
message = [
|
||||
"ℹ️ Comandi disponibili:"
|
||||
]
|
||||
|
||||
for command in sorted(list(set(self.serf.commands.values())), key=lambda c: c.name):
|
||||
message.append(f"- [c]{self.interface.prefix}{command.name}[/c]")
|
||||
|
||||
await data.reply("\n".join(message))
|
||||
else:
|
||||
name: str = args[0].lstrip(self.interface.prefix)
|
||||
|
||||
try:
|
||||
command: rc.Command = self.serf.commands[f"{self.interface.prefix}{name}"]
|
||||
except KeyError:
|
||||
raise rc.InvalidInputError("Il comando richiesto non esiste.")
|
||||
|
||||
message = [
|
||||
f"ℹ️ [c]{self.interface.prefix}{command.name} {command.syntax}[/c]",
|
||||
"",
|
||||
f"{command.description}"
|
||||
]
|
||||
|
||||
await data.reply("\n".join(message))
|
|
@ -1,97 +1,84 @@
|
|||
import typing
|
||||
from typing import *
|
||||
import riotwatcher
|
||||
import logging
|
||||
import asyncio
|
||||
import sentry_sdk
|
||||
from royalnet.commands import *
|
||||
from royalnet.utils import *
|
||||
from royalnet.serf.telegram import *
|
||||
from ..tables import LeagueOfLegends
|
||||
from ..utils import LeagueLeague
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
import royalnet.serf.telegram as rst
|
||||
from royalnet.backpack import tables as rbt
|
||||
|
||||
from .abstract.linker import LinkerCommand
|
||||
from ..tables import LeagueOfLegends, FiorygiTransaction
|
||||
from ..types import LeagueLeague, Updatable
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LeagueoflegendsCommand(Command):
|
||||
class LeagueoflegendsCommand(LinkerCommand):
|
||||
name: str = "leagueoflegends"
|
||||
|
||||
aliases = ["lol", "league"]
|
||||
|
||||
description: str = "Connetti un account di League of Legends a un account Royalnet, e visualizzane le statistiche."
|
||||
description: str = "Connetti un account di League of Legends a un account Royalnet, o visualizzane le statistiche."
|
||||
|
||||
syntax = "[nomeevocatore]"
|
||||
|
||||
def __init__(self, interface: CommandInterface):
|
||||
queue_names = {
|
||||
"rank_soloq": "Solo/Duo",
|
||||
"rank_flexq": "Flex",
|
||||
}
|
||||
|
||||
def __init__(self, interface: rc.CommandInterface):
|
||||
super().__init__(interface)
|
||||
self._riotwatcher = riotwatcher.RiotWatcher(api_key=self.config["Lol"]["token"])
|
||||
if self.interface.name == "telegram":
|
||||
self.loop.create_task(self._updater(900))
|
||||
self._lolwatcher: Optional[riotwatcher.RiotWatcher] = None
|
||||
self._tftwatcher: Optional[riotwatcher.RiotWatcher] = None
|
||||
if self.enabled():
|
||||
self._lolwatcher = riotwatcher.LolWatcher(api_key=self.token())
|
||||
self._tftwatcher = riotwatcher.TftWatcher(api_key=self.token())
|
||||
|
||||
async def _send(self, message):
|
||||
client = self.serf.client
|
||||
await self.serf.api_call(client.send_message,
|
||||
chat_id=self.config["Telegram"]["main_group_id"],
|
||||
text=escape(message),
|
||||
parse_mode="HTML",
|
||||
disable_webpage_preview=True)
|
||||
def token(self):
|
||||
return self.config["leagueoflegends"]["token"]
|
||||
|
||||
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!")
|
||||
def region(self):
|
||||
return self.config["leagueoflegends"]["region"]
|
||||
|
||||
@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)
|
||||
def describe(self, obj: LeagueOfLegends) -> str:
|
||||
string = f"ℹ️ [b]{obj.summoner_name}[/b]\n" \
|
||||
f"Lv. {obj.summoner_level}\n" \
|
||||
f"Mastery score: {obj.mastery_score}\n" \
|
||||
f"\n"
|
||||
if obj.rank_soloq:
|
||||
string += f"Solo: {obj.rank_soloq}\n"
|
||||
if obj.rank_flexq:
|
||||
string += f"Flex: {obj.rank_flexq}\n"
|
||||
return string
|
||||
|
||||
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.config["Lol"]["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.config["Lol"]["region"],
|
||||
encrypted_summoner_id=lol.summoner_id)
|
||||
async def get_updatables_of_user(self, session, user: rbt.User) -> List[LeagueOfLegends]:
|
||||
return await ru.asyncify(session.query(self.alchemy.get(LeagueOfLegends)).filter_by(user=user).all)
|
||||
|
||||
async def get_updatables(self, session) -> List[LeagueOfLegends]:
|
||||
return await ru.asyncify(session.query(self.alchemy.get(LeagueOfLegends)).all)
|
||||
|
||||
async def create(self,
|
||||
session,
|
||||
user: rbt.User,
|
||||
args: rc.CommandArgs,
|
||||
data: Optional[rc.CommandData] = None) -> Optional[LeagueOfLegends]:
|
||||
name = args.joined()
|
||||
|
||||
# Connect a new League of Legends account to Royalnet
|
||||
log.debug(f"Searching for: {name}")
|
||||
summoner = self._lolwatcher.summoner.by_name(region=self.region(), summoner_name=name)
|
||||
# Ensure the account isn't already connected to something else
|
||||
leagueoflegends = await ru.asyncify(
|
||||
session.query(self.alchemy.get(LeagueOfLegends)).filter_by(summoner_id=summoner["id"]).one_or_none)
|
||||
if leagueoflegends:
|
||||
raise rc.CommandError(f"L'account {leagueoflegends} è già registrato su Royalnet.")
|
||||
# Get rank information
|
||||
log.debug(f"Getting leagues data: {name}")
|
||||
leagues = self._lolwatcher.league.by_summoner(region=self.region(),
|
||||
encrypted_summoner_id=summoner["id"])
|
||||
soloq = LeagueLeague()
|
||||
flexq = LeagueLeague()
|
||||
twtrq = LeagueLeague()
|
||||
|
@ -105,118 +92,80 @@ class LeagueoflegendsCommand(Command):
|
|||
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.config["Lol"]["region"],
|
||||
encrypted_summoner_id=lol.summoner_id)
|
||||
await self._change(lol, "mastery_score", mastery, self._notify)
|
||||
# Get mastery score
|
||||
log.debug(f"Getting mastery data: {name}")
|
||||
mastery = self._lolwatcher.champion_mastery.scores_by_summoner(region=self.region(),
|
||||
encrypted_summoner_id=summoner["id"])
|
||||
# Create database row
|
||||
leagueoflegends = self.alchemy.get(LeagueOfLegends)(
|
||||
region=self.region(),
|
||||
user=user,
|
||||
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
|
||||
)
|
||||
|
||||
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.get(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)
|
||||
await FiorygiTransaction.spawn_fiorygi(
|
||||
data=data,
|
||||
user=user,
|
||||
qty=1,
|
||||
reason="aver collegato a Royalnet il proprio account di League of Legends"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _display(lol: LeagueOfLegends) -> str:
|
||||
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
|
||||
session.add(leagueoflegends)
|
||||
return leagueoflegends
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
author = await data.get_author(error_if_none=True)
|
||||
async def update(self, session, obj: LeagueOfLegends, change: Callable[[str, Any], Awaitable[None]]):
|
||||
log.debug(f"Getting summoner data: {obj}")
|
||||
summoner = await ru.asyncify(self._lolwatcher.summoner.by_id, region=self.region(),
|
||||
encrypted_summoner_id=obj.summoner_id)
|
||||
await change("profile_icon_id", summoner["profileIconId"])
|
||||
await change("summoner_name", summoner["name"])
|
||||
await change("puuid", summoner["puuid"])
|
||||
await change("summoner_level", summoner["summonerLevel"])
|
||||
await change("summoner_id", summoner["id"])
|
||||
await change("account_id", summoner["accountId"])
|
||||
log.debug(f"Getting leagues data: {obj}")
|
||||
leagues = await ru.asyncify(self._lolwatcher.league.by_summoner, region=self.region(),
|
||||
encrypted_summoner_id=obj.summoner_id)
|
||||
soloq = LeagueLeague()
|
||||
flexq = 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)
|
||||
await change("rank_soloq", soloq)
|
||||
await change("rank_flexq", flexq)
|
||||
log.debug(f"Getting mastery data: {obj}")
|
||||
mastery = await ru.asyncify(self._lolwatcher.champion_mastery.scores_by_summoner,
|
||||
region=self.region(),
|
||||
encrypted_summoner_id=obj.summoner_id)
|
||||
await change("mastery_score", mastery)
|
||||
|
||||
name = args.joined()
|
||||
async def on_increase(self, session, obj: LeagueOfLegends, attribute: str, old: Any, new: Any) -> None:
|
||||
if attribute in self.queue_names.keys():
|
||||
await self.notify(f"📈 [b]{obj.user}[/b] è salito a {new} su League of Legends ({self.queue_names[attribute]})! Congratulazioni!")
|
||||
|
||||
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.config["Lol"]["region"], summoner_name=name)
|
||||
# Ensure the account isn't already connected to something else
|
||||
leagueoflegends = await asyncify(
|
||||
data.session.query(self.alchemy.get(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.config["Lol"]["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.config["Lol"]["region"],
|
||||
encrypted_summoner_id=summoner["id"])
|
||||
# Create database row
|
||||
leagueoflegends = self.alchemy.get(LeagueOfLegends)(
|
||||
region=self.config["Lol"]["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 UserError("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)
|
||||
async def on_unchanged(self, session, obj: LeagueOfLegends, attribute: str, old: Any, new: Any) -> None:
|
||||
pass
|
||||
|
||||
async def on_decrease(self, session, obj: LeagueOfLegends, attribute: str, old: Any, new: Any) -> None:
|
||||
if attribute in self.queue_names.keys():
|
||||
await self.notify(f"📉 [b]{obj.user}[/b] è sceso a {new} su League of Legends ({self.queue_names[attribute]}).")
|
||||
|
||||
async def on_first(self, session, obj: LeagueOfLegends, attribute: str, old: None, new: Any) -> None:
|
||||
if attribute in self.queue_names.keys():
|
||||
await self.notify(f"🌟 [b]{obj.user}[/b] si è classificato {new} su League of Legends ({self.queue_names[attribute]}!")
|
||||
|
||||
async def on_reset(self, session, obj: LeagueOfLegends, attribute: str, old: Any, new: None) -> None:
|
||||
if attribute in self.queue_names.keys():
|
||||
await self.notify(f"⬜️ [b]{obj.user}[/b] non ha più un rank su League of Legends ({self.queue_names[attribute]}).")
|
||||
|
|
40
royalpack/commands/magickfiorygi.py
Normal file
40
royalpack/commands/magickfiorygi.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from typing import *
|
||||
import royalnet.commands as rc
|
||||
import royalnet.backpack.tables as rbt
|
||||
|
||||
from ..tables import FiorygiTransaction
|
||||
|
||||
|
||||
class MagickfiorygiCommand(rc.Command):
|
||||
name: str = "magickfiorygi"
|
||||
|
||||
description: str = "Crea fiorygi dal nulla."
|
||||
|
||||
syntax: str = "{destinatario} {quantità} {motivo}"
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
author = await data.get_author(error_if_none=True)
|
||||
if "banker" not in author.roles:
|
||||
raise rc.UserError("Non hai permessi sufficienti per eseguire questo comando.")
|
||||
|
||||
user_arg = args[0]
|
||||
qty_arg = args[1]
|
||||
reason_arg = " ".join(args[2:])
|
||||
|
||||
if user_arg is None:
|
||||
raise rc.InvalidInputError("Non hai specificato un destinatario!")
|
||||
user = await rbt.User.find(self.alchemy, data.session, user_arg)
|
||||
if user is None:
|
||||
raise rc.InvalidInputError("L'utente specificato non esiste!")
|
||||
|
||||
if qty_arg is None:
|
||||
raise rc.InvalidInputError("Non hai specificato una quantità!")
|
||||
try:
|
||||
qty = int(qty_arg)
|
||||
except ValueError:
|
||||
raise rc.InvalidInputError("La quantità specificata non è un numero!")
|
||||
|
||||
if reason_arg == "":
|
||||
raise rc.InvalidInputError("Non hai specificato un motivo!")
|
||||
|
||||
await FiorygiTransaction.spawn_fiorygi(data, user, qty, reason_arg)
|
53
royalpack/commands/magicktreasure.py
Normal file
53
royalpack/commands/magicktreasure.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from typing import *
|
||||
import royalnet
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
from ..tables import Treasure
|
||||
|
||||
|
||||
class MagicktreasureCommand(rc.Command):
|
||||
name: str = "magicktreasure"
|
||||
|
||||
description: str = "Crea un nuovo Treasure di Fiorygi (senza spendere i tuoi)."
|
||||
|
||||
syntax: str = "{codice} {valore}"
|
||||
|
||||
async def _permission_check(self, author, code, value, data):
|
||||
if "banker" not in author.roles:
|
||||
raise rc.UserError("Non hai permessi sufficienti per eseguire questo comando.")
|
||||
return author
|
||||
|
||||
async def _create_treasure(self, author, code, value, data):
|
||||
TreasureT = self.alchemy.get(Treasure)
|
||||
|
||||
treasure = await ru.asyncify(data.session.query(TreasureT).get, code)
|
||||
if treasure is not None:
|
||||
raise rc.UserError("Esiste già un Treasure con quel codice.")
|
||||
|
||||
treasure = TreasureT(
|
||||
code=code,
|
||||
value=value,
|
||||
redeemed_by=None
|
||||
)
|
||||
|
||||
return treasure
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
await data.delete_invoking()
|
||||
author = await data.get_author(error_if_none=True)
|
||||
|
||||
code = args[0].lower()
|
||||
try:
|
||||
value = int(args[1])
|
||||
except ValueError:
|
||||
raise rc.InvalidInputError("Il valore deve essere maggiore o uguale a 0.")
|
||||
if value < 0:
|
||||
raise rc.InvalidInputError("Il valore deve essere maggiore o uguale a 0.")
|
||||
|
||||
await self._permission_check(author, code, value, data)
|
||||
treasure = await self._create_treasure(author, code, value, data)
|
||||
|
||||
data.session.add(treasure)
|
||||
await data.session_commit()
|
||||
|
||||
await data.reply("✅ Treasure creato!")
|
89
royalpack/commands/matchmaking.py
Normal file
89
royalpack/commands/matchmaking.py
Normal file
|
@ -0,0 +1,89 @@
|
|||
from typing import *
|
||||
import datetime
|
||||
import re
|
||||
import dateparser
|
||||
import typing
|
||||
import royalnet.commands as rc
|
||||
|
||||
from ..tables import MMEvent
|
||||
from ..utils import MMTask
|
||||
|
||||
|
||||
class MatchmakingCommand(rc.Command):
|
||||
name: str = "matchmaking"
|
||||
|
||||
description: str = "Cerca persone per una partita a qualcosa!"
|
||||
|
||||
syntax: str = ""
|
||||
|
||||
aliases = ["mm", "lfg"]
|
||||
|
||||
def __init__(self, interface: rc.CommandInterface):
|
||||
super().__init__(interface)
|
||||
|
||||
# Find all active MMEvents and run the tasks for them
|
||||
session = self.alchemy.Session()
|
||||
|
||||
# Create a new MMEvent and run it
|
||||
if self.interface.name == "telegram":
|
||||
MMEventT = self.alchemy.get(MMEvent)
|
||||
active_mmevents = (
|
||||
session
|
||||
.query(MMEventT)
|
||||
.filter(
|
||||
MMEventT.interface == self.interface.name,
|
||||
MMEventT.interrupted == False
|
||||
)
|
||||
.all()
|
||||
)
|
||||
for mmevent in active_mmevents:
|
||||
task = MMTask(mmevent.mmid, command=self)
|
||||
task.start()
|
||||
|
||||
@staticmethod
|
||||
def _parse_args(args) -> Tuple[Optional[datetime.datetime], str, str]:
|
||||
"""Parse command arguments, either using the standard syntax or the Proto syntax."""
|
||||
try:
|
||||
timestring, title, description = args.match(r"(?:\[\s*([^]]+)\s*]\s*)?([^\n]+)\s*\n?\s*(.+)?\s*", re.DOTALL)
|
||||
except rc.InvalidInputError:
|
||||
timestring, title, description = args.match(r"(?:\s*(.+?)\s*\n\s*)?([^\n]+)\s*\n?\s*(.+)?\s*", re.DOTALL)
|
||||
if timestring is not None:
|
||||
try:
|
||||
dt: typing.Optional[datetime.datetime] = dateparser.parse(timestring, settings={
|
||||
"PREFER_DATES_FROM": "future"
|
||||
})
|
||||
except OverflowError:
|
||||
dt = None
|
||||
if dt is None:
|
||||
raise rc.InvalidInputError("La data che hai specificato non è valida.")
|
||||
if dt <= datetime.datetime.now():
|
||||
raise rc.InvalidInputError("La data che hai specificato è nel passato.")
|
||||
if dt - datetime.datetime.now() >= datetime.timedelta(days=366):
|
||||
raise rc.InvalidInputError("Hai specificato una data tra più di un anno!\n"
|
||||
"Se volevi scrivere un'orario, ricordati che le ore sono separate da "
|
||||
"due punti (:) e non da punto semplice!")
|
||||
else:
|
||||
dt = None
|
||||
return dt, title, description
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
"""Handle a matchmaking command call."""
|
||||
author = await data.get_author(error_if_none=True)
|
||||
|
||||
# Parse the arguments, either with the standard syntax or with the Proto syntax
|
||||
dt, title, description = self._parse_args(args)
|
||||
|
||||
# Add the MMEvent to the database
|
||||
mmevent: MMEvent = self.alchemy.get(MMEvent)(creator=author,
|
||||
datetime=dt,
|
||||
title=title,
|
||||
description=description,
|
||||
interface=self.interface.name)
|
||||
data.session.add(mmevent)
|
||||
await data.session_commit()
|
||||
|
||||
# Create and run a task for the newly created MMEvent
|
||||
task = MMTask(mmevent.mmid, command=self)
|
||||
task.start()
|
||||
|
||||
await data.reply(f"🚩 Matchmaking creato!")
|
125
royalpack/commands/osu.py
Normal file
125
royalpack/commands/osu.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
from typing import *
|
||||
import itsdangerous
|
||||
import aiohttp
|
||||
|
||||
from royalnet.backpack import tables as rbt
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
|
||||
from .abstract.linker import LinkerCommand
|
||||
from ..types import Updatable
|
||||
from ..tables import Osu
|
||||
from ..stars.api_auth_login_osu import ApiAuthLoginOsuStar
|
||||
|
||||
|
||||
class OsuCommand(LinkerCommand):
|
||||
name = "osu"
|
||||
|
||||
description = "Connetti e sincronizza il tuo account di osu!"
|
||||
|
||||
@property
|
||||
def client_id(self):
|
||||
return self.config[self.name]['client_id']
|
||||
|
||||
@property
|
||||
def client_secret(self):
|
||||
return self.config[self.name]['client_secret']
|
||||
|
||||
@property
|
||||
def base_url(self):
|
||||
return self.config['base_url']
|
||||
|
||||
@property
|
||||
def secret_key(self):
|
||||
return self.config['secret_key']
|
||||
|
||||
async def get_updatables_of_user(self, session, user: rbt.User) -> List[Osu]:
|
||||
return user.osu
|
||||
|
||||
async def get_updatables(self, session) -> List[Osu]:
|
||||
return await ru.asyncify(session.query(self.alchemy.get(Osu)).all)
|
||||
|
||||
async def create(self,
|
||||
session,
|
||||
user: rbt.User,
|
||||
args: rc.CommandArgs,
|
||||
data: Optional[rc.CommandData] = None) -> Optional[Osu]:
|
||||
serializer = itsdangerous.URLSafeSerializer(self.secret_key, salt="osu")
|
||||
# TODO: Ensure the chat the link is being sent in is secure!!!
|
||||
await data.reply("🔑 [b]Login necessario[/b]\n"
|
||||
f"[url=https://osu.ppy.sh/oauth/authorize"
|
||||
f"?client_id={self.client_id}"
|
||||
f"&redirect_uri={self.base_url}{ApiAuthLoginOsuStar.path}"
|
||||
f"&response_type=code"
|
||||
f"&state={serializer.dumps(user.uid)}]"
|
||||
f"Connetti account di osu! a {user.username}"
|
||||
f"[/url]")
|
||||
return None
|
||||
|
||||
async def update(self, session, obj: Osu, change: Callable[[str, Any], Awaitable[None]]):
|
||||
await obj.refresh_if_expired(client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
base_url=self.base_url,
|
||||
path=ApiAuthLoginOsuStar.path)
|
||||
async with aiohttp.ClientSession(headers={"Authorization": f"Bearer {obj.access_token}"}) as session:
|
||||
async with session.get("https://osu.ppy.sh/api/v2/me/osu") as response:
|
||||
m = await response.json()
|
||||
obj.avatar_url = m["avatar_url"]
|
||||
obj.username = m["username"]
|
||||
if "statistics" in m:
|
||||
await change("standard_pp", m["statistics"].get("pp"))
|
||||
async with session.get("https://osu.ppy.sh/api/v2/me/taiko") as response:
|
||||
m = await response.json()
|
||||
if "statistics" in m:
|
||||
await change("taiko_pp", m["statistics"].get("pp"))
|
||||
async with session.get("https://osu.ppy.sh/api/v2/me/fruits") as response:
|
||||
m = await response.json()
|
||||
if "statistics" in m:
|
||||
await change("catch_pp", m["statistics"].get("pp"))
|
||||
async with session.get("https://osu.ppy.sh/api/v2/me/mania") as response:
|
||||
m = await response.json()
|
||||
if "statistics" in m:
|
||||
await change("mania_pp", m["statistics"].get("pp"))
|
||||
|
||||
async def on_increase(self, session, obj: Osu, attribute: str, old: Any, new: Any) -> None:
|
||||
if attribute == "standard_pp":
|
||||
await self.notify(f"📈 [b]{obj.user}[/b] è salito a [b]{new:.0f}pp[/b] su [i]osu![/i]! Congratulazioni!")
|
||||
elif attribute == "taiko_pp":
|
||||
await self.notify(f"📈 [b]{obj.user}[/b] è salito a [b]{new:.0f}pp[/b] su [i]osu!taiko[/i]! Congratulazioni!")
|
||||
elif attribute == "catch_pp":
|
||||
await self.notify(f"📈 [b]{obj.user}[/b] è salito a [b]{new:.0f}pp[/b] su [i]osu!catch[/i]! Congratulazioni!")
|
||||
elif attribute == "mania_pp":
|
||||
await self.notify(f"📈 [b]{obj.user}[/b] è salito a [b]{new:.0f}pp[/b] su [i]osu!mania[/i]! Congratulazioni!")
|
||||
|
||||
async def on_unchanged(self, session, obj: Osu, attribute: str, old: Any, new: Any) -> None:
|
||||
pass
|
||||
|
||||
async def on_decrease(self, session, obj: Osu, attribute: str, old: Any, new: Any) -> None:
|
||||
if attribute == "standard_pp":
|
||||
await self.notify(f"📉 [b]{obj.user}[/b] è sceso a [b]{new:.0f}pp[/b] su [i]osu![/i].")
|
||||
elif attribute == "taiko_pp":
|
||||
await self.notify(f"📉 [b]{obj.user}[/b] è sceso a [b]{new:.0f}pp[/b] su [i]osu!taiko[/i].")
|
||||
elif attribute == "catch_pp":
|
||||
await self.notify(f"📉 [b]{obj.user}[/b] è sceso a [b]{new:.0f}pp[/b] su [i]osu!catch[/i].")
|
||||
elif attribute == "mania_pp":
|
||||
await self.notify(f"📉 [b]{obj.user}[/b] è sceso a [b]{new:.0f}pp[/b] su [i]osu!mania[/i].")
|
||||
|
||||
async def on_first(self, session, obj: Osu, attribute: str, old: None, new: Any) -> None:
|
||||
if attribute == "standard_pp":
|
||||
await self.notify(f"⭐️ [b]{obj.user}[/b] ha guadagnato i suoi primi [b]{new:.0f}pp[/b] su [i]osu![/i]!")
|
||||
elif attribute == "taiko_pp":
|
||||
await self.notify(f"⭐️ [b]{obj.user}[/b] ha guadagnato i suoi primi [b]{new:.0f}pp[/b] su [i]osu!taiko[/i]!")
|
||||
elif attribute == "catch_pp":
|
||||
await self.notify(f"⭐️ [b]{obj.user}[/b] ha guadagnato i suoi primi [b]{new:.0f}pp[/b] su [i]osu!catch[/i]!")
|
||||
elif attribute == "mania_pp":
|
||||
await self.notify(f"⭐️ [b]{obj.user}[/b] ha guadagnato i suoi primi [b]{new:.0f}pp[/b] su [i]osu!mania[/i]!")
|
||||
|
||||
async def on_reset(self, session, obj: Osu, attribute: str, old: Any, new: None) -> None:
|
||||
if attribute == "standard_pp":
|
||||
await self.notify(f"⬜️ [b]{obj.user}[/b] non è più classificato su [i]osu![/i].")
|
||||
elif attribute == "taiko_pp":
|
||||
await self.notify(f" ⬜️[b]{obj.user}[/b] non è più classificato su [i]osu!taiko[/i].")
|
||||
elif attribute == "catch_pp":
|
||||
await self.notify(f"⬜️ [b]{obj.user}[/b] non è più classificato su [i]osu!catch[/i].")
|
||||
elif attribute == "mania_pp":
|
||||
await self.notify(f"⬜️ [b]{obj.user}[/b] non è più classificato su [i]osu!mania[/i].")
|
|
@ -1,27 +0,0 @@
|
|||
import discord
|
||||
from typing import *
|
||||
from royalnet.commands import *
|
||||
|
||||
|
||||
class PauseCommand(Command):
|
||||
name: str = "pause"
|
||||
|
||||
aliases = ["resume"]
|
||||
|
||||
description: str = "Metti in pausa o riprendi la riproduzione di un file."
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
if self.interface.name == "discord":
|
||||
message: discord.Message = data.message
|
||||
guild: discord.Guild = message.guild
|
||||
guild_id: Optional[int] = guild.id
|
||||
else:
|
||||
guild_id = None
|
||||
response: Dict[str, Any] = await self.interface.call_herald_event("discord", "discord_pause",
|
||||
guild_id=guild_id)
|
||||
|
||||
if response["action"] == "paused":
|
||||
await data.reply("⏸ Riproduzione messa in pausa.")
|
||||
|
||||
elif response["action"] == "resumed":
|
||||
await data.reply("▶️ Riproduzione ripresa!")
|
|
@ -1,24 +0,0 @@
|
|||
from .play import PlayCommand
|
||||
from royalnet.commands import *
|
||||
import aiohttp
|
||||
import urllib.parse
|
||||
|
||||
|
||||
class PeertubeCommand(PlayCommand):
|
||||
name: str = "peertube"
|
||||
|
||||
aliases = ["pt", "royaltube", "rt"]
|
||||
|
||||
description: str = "Cerca un video su RoyalTube e lo aggiunge alla coda della chat vocale."
|
||||
|
||||
syntax = "{ricerca}"
|
||||
|
||||
async def get_url(self, args):
|
||||
search = urllib.parse.quote(args.joined(require_at_least=1))
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(self.config["Peertube"]["instance_url"] +
|
||||
f"/api/v1/search/videos?search={search}") as response:
|
||||
j = await response.json()
|
||||
if j["total"] < 1:
|
||||
raise InvalidInputError("Nessun video trovato.")
|
||||
return f'{self.config["Peertube"]["instance_url"]}/videos/watch/{j["data"][0]["uuid"]}'
|
|
@ -1,16 +1,17 @@
|
|||
from typing import *
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import datetime
|
||||
import logging
|
||||
import dateparser
|
||||
from royalnet.commands import *
|
||||
from royalnet.serf.telegram.escape import escape
|
||||
import royalnet.commands as rc
|
||||
import royalnet.serf.telegram as rst
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PeertubeUpdatesCommand(Command):
|
||||
class PeertubeUpdatesCommand(rc.Command):
|
||||
name: str = "peertubeupdates"
|
||||
|
||||
description: str = "Guarda quando è uscito l'ultimo video su PeerTube."
|
||||
|
@ -21,7 +22,7 @@ class PeertubeUpdatesCommand(Command):
|
|||
|
||||
_latest_date: datetime.datetime = None
|
||||
|
||||
def __init__(self, interface: CommandInterface):
|
||||
def __init__(self, interface: rc.CommandInterface):
|
||||
super().__init__(interface)
|
||||
if self.interface.name == "telegram":
|
||||
self.loop.create_task(self._ready_up())
|
||||
|
@ -33,6 +34,8 @@ class PeertubeUpdatesCommand(Command):
|
|||
async with session.get(self.config["Peertube"]["instance_url"] +
|
||||
"/feeds/videos.json?sort=-publishedAt&filter=local") as response:
|
||||
log.debug("Parsing jsonfeed")
|
||||
if response.status != 200:
|
||||
raise rc.ExternalError("Peertube is unavailable")
|
||||
j = await response.json()
|
||||
log.debug("Jsonfeed parsed successfully")
|
||||
return j
|
||||
|
@ -41,14 +44,14 @@ class PeertubeUpdatesCommand(Command):
|
|||
client = self.interface.bot.client
|
||||
await self.interface.bot.safe_api_call(client.send_message,
|
||||
chat_id=self.config["Telegram"]["main_group_id"],
|
||||
text=escape(message),
|
||||
text=rst.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")
|
||||
raise rc.ConfigurationError("url is not a jsonfeed")
|
||||
videos = j["items"]
|
||||
for video in reversed(videos):
|
||||
date_modified = dateparser.parse(video["date_modified"])
|
||||
|
@ -72,7 +75,7 @@ class PeertubeUpdatesCommand(Command):
|
|||
f"{video['url']}")
|
||||
await asyncio.sleep(self.config["Peertube"]["feed_update_timeout"])
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
if self.interface.name != "telegram":
|
||||
raise UnsupportedError()
|
||||
raise rc.UnsupportedError()
|
||||
await data.reply(f"ℹ️ Ultimo video caricato il: [b]{self._latest_date.isoformat()}[/b]")
|
||||
|
|
41
royalpack/commands/ping.py
Normal file
41
royalpack/commands/ping.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
import datetime
|
||||
import asyncio
|
||||
from typing import *
|
||||
import royalnet
|
||||
import royalnet.commands as rc
|
||||
|
||||
|
||||
class PingCommand(rc.Command):
|
||||
name: str = "ping"
|
||||
|
||||
description: str = "Display the status of the Herald network."
|
||||
|
||||
syntax: str = ""
|
||||
|
||||
_targets = ["telegram", "discord", "matrix", "constellation"]
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
await data.reply("📶 Ping...")
|
||||
|
||||
tasks = {}
|
||||
|
||||
start = datetime.datetime.now()
|
||||
for target in self._targets:
|
||||
tasks[target] = self.loop.create_task(self.interface.call_herald_event(target, "pong"))
|
||||
|
||||
await asyncio.sleep(10)
|
||||
|
||||
lines = ["📶 [b]Pong![/b]", ""]
|
||||
|
||||
for name, task in tasks.items():
|
||||
try:
|
||||
d = task.result()
|
||||
except (asyncio.CancelledError, asyncio.InvalidStateError):
|
||||
lines.append(f"🔴 [c]{name}[/c]")
|
||||
else:
|
||||
end = datetime.datetime.fromtimestamp(d["timestamp"])
|
||||
delta = end - start
|
||||
|
||||
lines.append(f"🔵 [c]{name}[/c] ({delta.microseconds // 1000} ms)")
|
||||
|
||||
await data.reply("\n".join(lines))
|
|
@ -1,62 +0,0 @@
|
|||
import pickle
|
||||
import base64
|
||||
import discord
|
||||
from typing import *
|
||||
from royalnet.commands import *
|
||||
from royalnet.utils import *
|
||||
|
||||
|
||||
class PlayCommand(Command):
|
||||
name: str = "play"
|
||||
|
||||
aliases = ["p"]
|
||||
|
||||
description: str = "Aggiunge un url alla coda della chat vocale."
|
||||
|
||||
syntax = "{url}"
|
||||
|
||||
async def get_url(self, args: CommandArgs):
|
||||
return args.joined()
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
# 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, come misura temporanea puoi usare "
|
||||
# f"[c]ytsearch:nomevideo[/c] o [c]scsearch:nomevideo[/c] come url.")
|
||||
if self.interface.name == "discord":
|
||||
message: discord.Message = data.message
|
||||
guild: discord.Guild = message.guild
|
||||
if guild is None:
|
||||
guild_id = None
|
||||
else:
|
||||
guild_id: Optional[int] = guild.id
|
||||
else:
|
||||
guild_id = None
|
||||
response: Dict[str, Any] = await self.interface.call_herald_event("discord", "discord_play",
|
||||
url=await self.get_url(args),
|
||||
guild_id=guild_id)
|
||||
|
||||
too_long: List[Dict[str, Any]] = response["too_long"]
|
||||
if len(too_long) > 0:
|
||||
await data.reply(f"⚠ {len(too_long)} file non {'è' if len(too_long) == 1 else 'sono'}"
|
||||
f" stat{'o' if len(too_long) == 1 else 'i'} scaricat{'o' if len(too_long) == 1 else 'i'}"
|
||||
f" perchè durava{'' if len(too_long) == 1 else 'no'}"
|
||||
f" più di [c]{self.config['Play']['max_song_duration']}[/c] secondi.")
|
||||
|
||||
added: List[Dict[str, Any]] = response["added"]
|
||||
if len(added) > 0:
|
||||
reply = f"▶️ Aggiunt{'o' if len(added) == 1 else 'i'} {len(added)} file alla coda:\n"
|
||||
if self.interface.name == "discord":
|
||||
await data.reply(reply)
|
||||
for item in added:
|
||||
embed = pickle.loads(base64.b64decode(bytes(item["stringified_base64_pickled_discord_embed"],
|
||||
encoding="ascii")))
|
||||
# noinspection PyUnboundLocalVariable
|
||||
await message.channel.send(embed=embed)
|
||||
else:
|
||||
reply += numberemojiformat([a["title"] for a in added])
|
||||
await data.reply(reply)
|
||||
|
||||
if len(added) + len(too_long) == 0:
|
||||
raise ExternalError("Nessun video trovato.")
|
|
@ -1,11 +1,11 @@
|
|||
from typing import *
|
||||
from royalnet.commands import *
|
||||
import royalnet.commands as rc
|
||||
|
||||
|
||||
class PmotsCommand(Command):
|
||||
class PmotsCommand(rc.Command):
|
||||
name: str = "pmots"
|
||||
|
||||
description: str = "Confondi Proto!"
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
await data.reply("👣 pmots pmots")
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
import pickle
|
||||
import base64
|
||||
import discord
|
||||
from typing import *
|
||||
from royalnet.commands import *
|
||||
from royalnet.utils import *
|
||||
|
||||
|
||||
class QueueCommand(Command):
|
||||
name: str = "queue"
|
||||
|
||||
aliases = ["q"]
|
||||
|
||||
description: str = "Visualizza la coda di riproduzione attuale.."
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
if self.interface.name == "discord":
|
||||
message: discord.Message = data.message
|
||||
guild: discord.Guild = message.guild
|
||||
guild_id: Optional[int] = guild.id
|
||||
else:
|
||||
guild_id = None
|
||||
response: Dict[str, Any] = await self.interface.call_herald_event("discord", "discord_queue",
|
||||
guild_id=guild_id)
|
||||
|
||||
queue_type = response["type"]
|
||||
if queue_type == "RoyalQueue":
|
||||
next_up = response["next_up"]
|
||||
now_playing = response["now_playing"]
|
||||
await data.reply(f"ℹ️ La coda contiene {len(next_up)} file.\n\n")
|
||||
|
||||
if now_playing is not None:
|
||||
reply = f"Attualmente, sta venendo riprodotto:\n"
|
||||
if self.interface.name == "discord":
|
||||
await data.reply(reply)
|
||||
embed = pickle.loads(base64.b64decode(bytes(now_playing["stringified_base64_pickled_discord_embed"],
|
||||
encoding="ascii")))
|
||||
# noinspection PyUnboundLocalVariable
|
||||
await message.channel.send(embed=embed)
|
||||
else:
|
||||
reply += f"▶️ {now_playing['title']}\n\n"
|
||||
await data.reply(reply)
|
||||
else:
|
||||
await data.reply("⏹ Attualmente, non sta venendo riprodotto nulla.")
|
||||
|
||||
reply = ""
|
||||
if len(next_up) >= 1:
|
||||
reply += "I prossimi file in coda sono:\n"
|
||||
if self.interface.name == "discord":
|
||||
await data.reply(reply)
|
||||
for item in next_up[:5]:
|
||||
embed = pickle.loads(base64.b64decode(bytes(item["stringified_base64_pickled_discord_embed"],
|
||||
encoding="ascii")))
|
||||
# noinspection PyUnboundLocalVariable
|
||||
await message.channel.send(embed=embed)
|
||||
else:
|
||||
reply += numberemojiformat([a["title"] for a in next_up[:5]])
|
||||
await data.reply(reply)
|
||||
else:
|
||||
await data.reply("ℹ️ Non ci sono altri file in coda.")
|
||||
else:
|
||||
raise CommandError(f"Non so come visualizzare il contenuto di un [c]{queue_type}[/c].")
|
|
@ -1,20 +1,22 @@
|
|||
import typing
|
||||
from typing import *
|
||||
import random
|
||||
from royalnet.commands import *
|
||||
import royalnet.commands as rc
|
||||
|
||||
|
||||
class RageCommand(Command):
|
||||
class RageCommand(rc.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!"]
|
||||
_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:
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
await data.reply(f"😠 {random.sample(self._MAD, 1)[0]}")
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import typing
|
||||
from typing import *
|
||||
import dateparser
|
||||
import datetime
|
||||
import pickle
|
||||
import telegram
|
||||
import discord
|
||||
from sqlalchemy import and_
|
||||
from royalnet.commands import *
|
||||
from royalnet.utils import *
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
from royalnet.serf.telegram import escape as telegram_escape
|
||||
from royalnet.serf.discord import escape as discord_escape
|
||||
|
||||
from ..tables import Reminder
|
||||
|
||||
|
||||
class ReminderCommand(Command):
|
||||
class ReminderCommand(rc.Command):
|
||||
name: str = "reminder"
|
||||
|
||||
aliases = ["calendar"]
|
||||
|
@ -21,7 +22,7 @@ class ReminderCommand(Command):
|
|||
|
||||
syntax: str = "[ {data} ] {messaggio}"
|
||||
|
||||
def __init__(self, interface: CommandInterface):
|
||||
def __init__(self, interface: rc.CommandInterface):
|
||||
super().__init__(interface)
|
||||
session = interface.alchemy.Session()
|
||||
reminders = (
|
||||
|
@ -35,9 +36,9 @@ class ReminderCommand(Command):
|
|||
interface.loop.create_task(self._remind(reminder))
|
||||
|
||||
async def _remind(self, reminder):
|
||||
await sleep_until(reminder.datetime)
|
||||
await ru.sleep_until(reminder.datetime)
|
||||
if self.interface.name == "telegram":
|
||||
chat_id: int = pickle.loads(reminder.raw_interface_data)
|
||||
chat_id: int = pickle.loads(reminder.interface_data)
|
||||
client: telegram.Bot = self.serf.client
|
||||
await self.serf.api_call(client.send_message,
|
||||
chat_id=chat_id,
|
||||
|
@ -45,19 +46,19 @@ class ReminderCommand(Command):
|
|||
parse_mode="HTML",
|
||||
disable_web_page_preview=True)
|
||||
elif self.interface.name == "discord":
|
||||
channel_id: int = pickle.loads(reminder.raw_interface_data)
|
||||
channel_id: int = pickle.loads(reminder.interface_data)
|
||||
client: discord.Client = self.serf.client
|
||||
channel = client.get_channel(channel_id)
|
||||
await channel.send(discord_escape(f"❗️ {reminder.message}"))
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
try:
|
||||
date_str, reminder_text = args.match(r"\[\s*([^]]+)\s*]\s*([^\n]+)\s*")
|
||||
except InvalidInputError:
|
||||
except rc.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={
|
||||
date: Optional[datetime.datetime] = dateparser.parse(date_str, settings={
|
||||
"PREFER_DATES_FROM": "future"
|
||||
})
|
||||
except OverflowError:
|
||||
|
@ -70,11 +71,11 @@ class ReminderCommand(Command):
|
|||
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)
|
||||
interface_data = pickle.dumps(data.message.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.")
|
||||
raise rc.UnsupportedError("This command does not support the current interface.")
|
||||
creator = await data.get_author()
|
||||
reminder = self.interface.alchemy.get(Reminder)(creator=creator,
|
||||
interface_name=self.interface.name,
|
||||
|
@ -83,4 +84,4 @@ class ReminderCommand(Command):
|
|||
message=reminder_text)
|
||||
self.interface.loop.create_task(self._remind(reminder))
|
||||
data.session.add(reminder)
|
||||
await asyncify(data.session.commit)
|
||||
await ru.asyncify(data.session.commit)
|
||||
|
|
25
royalpack/commands/royalpackversion.py
Normal file
25
royalpack/commands/royalpackversion.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from typing import *
|
||||
|
||||
import pkg_resources
|
||||
import royalnet.commands as rc
|
||||
|
||||
|
||||
class RoyalpackCommand(rc.Command):
|
||||
name: str = "royalpackversion"
|
||||
|
||||
description: str = "Visualizza la versione attuale di Royalpack."
|
||||
|
||||
syntax: str = ""
|
||||
|
||||
@property
|
||||
def royalpack_version(self):
|
||||
return pkg_resources.get_distribution("royalpack").version
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
if __debug__:
|
||||
message = f"ℹ️ Royalpack [url=https://github.com/Steffo99/royalpack/]Unreleased[/url]\n"
|
||||
else:
|
||||
message = f"ℹ️ Royalpack [url=https://github.com/Steffo99/royalpack/releases/tag/{self.royalpack_version}]{self.royalpack_version}[/url]\n"
|
||||
if "69" in semantic:
|
||||
message += "(Nice.)"
|
||||
await data.reply(message)
|
|
@ -1,15 +1,16 @@
|
|||
from typing import *
|
||||
import re
|
||||
from royalnet.commands import *
|
||||
import royalnet.commands as rc
|
||||
|
||||
|
||||
class ShipCommand(Command):
|
||||
class ShipCommand(rc.Command):
|
||||
name: str = "ship"
|
||||
|
||||
description: str = "Crea una ship tra due nomi."
|
||||
|
||||
syntax = "{nomeuno} {nomedue}"
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
name_one = args[0]
|
||||
name_two = args[1]
|
||||
if name_two == "+":
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import discord
|
||||
from typing import *
|
||||
from royalnet.commands import *
|
||||
|
||||
|
||||
class SkipCommand(Command):
|
||||
name: str = "skip"
|
||||
|
||||
aliases = ["s"]
|
||||
|
||||
description: str = "Salta il file attualmente in riproduzione."
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
if self.interface.name == "discord":
|
||||
message: discord.Message = data.message
|
||||
guild: discord.Guild = message.guild
|
||||
guild_id: Optional[int] = guild.id
|
||||
else:
|
||||
guild_id = None
|
||||
response: Dict[str, Any] = await self.interface.call_herald_event("discord", "discord_skip", guild_id=guild_id)
|
||||
await data.reply("⏩ File attuale saltato!")
|
|
@ -1,9 +1,9 @@
|
|||
import typing
|
||||
from typing import *
|
||||
import random
|
||||
from royalnet.commands import *
|
||||
import royalnet.commands as rc
|
||||
|
||||
|
||||
class SmecdsCommand(Command):
|
||||
class SmecdsCommand(rc.Command):
|
||||
name: str = "smecds"
|
||||
|
||||
aliases = ["secondomeecolpadellostagista"]
|
||||
|
@ -61,6 +61,6 @@ class SmecdsCommand(Command):
|
|||
"dello Slime God", "del salassato", "della salsa", "di Senjougahara", "di Sugar", "della Stampa",
|
||||
"della Stampante"]
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
ds = random.sample(self._DS_LIST, 1)[0]
|
||||
await data.reply(f"🤔 Secondo me, è colpa {ds}.")
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
from .play import PlayCommand
|
||||
|
||||
|
||||
class SoundcloudCommand(PlayCommand):
|
||||
name: str = "soundcloud"
|
||||
|
||||
aliases = ["sc"]
|
||||
|
||||
description: str = "Cerca un video su SoundCloud e lo aggiunge alla coda della chat vocale."
|
||||
|
||||
syntax = "{ricerca}"
|
||||
|
||||
async def get_url(self, args):
|
||||
return f"scsearch:{args.joined()}"
|
|
@ -1,19 +1,17 @@
|
|||
from typing import *
|
||||
from royalnet.commands import *
|
||||
from royalnet.utils import *
|
||||
from royalnet.backpack.tables import User
|
||||
from sqlalchemy import func
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
import royalspells as rs
|
||||
|
||||
|
||||
class SpellCommand(Command):
|
||||
class SpellCommand(rc.Command):
|
||||
name: str = "spell"
|
||||
|
||||
description: str = "Genera casualmente una spell!"
|
||||
|
||||
syntax = "{nome_spell}"
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
spell_name = args.joined(require_at_least=1)
|
||||
spell = rs.Spell(spell_name)
|
||||
|
||||
|
@ -23,7 +21,7 @@ class SpellCommand(Command):
|
|||
dmg: rs.DamageComponent = spell.damage_component
|
||||
constant_str: str = f"{dmg.constant:+d}" if dmg.constant != 0 else ""
|
||||
rows.append(f"Danni: [b]{dmg.dice_number}d{dmg.dice_type}{constant_str}[/b]"
|
||||
f" {andformat(dmg.damage_types, final=' e ')}")
|
||||
f" {ru.andformat(dmg.damage_types, final=' e ')}")
|
||||
rows.append(f"Precisione: [b]{dmg.miss_chance}%[/b]")
|
||||
if dmg.repeat > 1:
|
||||
rows.append(f"Multiattacco: [b]×{dmg.repeat}[/b]")
|
||||
|
@ -39,7 +37,7 @@ class SpellCommand(Command):
|
|||
stats: rs.StatsComponent = spell.stats_component
|
||||
rows.append("Il caster riceve: ")
|
||||
for stat_name in stats.stat_changes:
|
||||
rows.append(f"[b]{stats.stat_changes[stat_name]}{stat_name}[/b]")
|
||||
rows.append(f"[b]{stat_name}{stats.stat_changes[stat_name]}[/b]")
|
||||
rows.append("")
|
||||
|
||||
if spell.status_effect_component:
|
||||
|
|
102
royalpack/commands/steammatch.py
Normal file
102
royalpack/commands/steammatch.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
from typing import *
|
||||
import steam.webapi
|
||||
import requests.exceptions
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
import royalnet.backpack.tables as rbt
|
||||
|
||||
from ..tables import Steam
|
||||
|
||||
|
||||
class SteamGame:
|
||||
def __init__(self,
|
||||
appid=None,
|
||||
name=None,
|
||||
playtime_forever=None,
|
||||
img_icon_url=None,
|
||||
img_logo_url=None,
|
||||
has_community_visible_stats=None,
|
||||
playtime_windows_forever=None,
|
||||
playtime_mac_forever=None,
|
||||
playtime_linux_forever=None,
|
||||
playtime_2weeks=None):
|
||||
self.appid = appid
|
||||
self.name = name
|
||||
self.playtime_forever = playtime_forever
|
||||
self.img_icon_url = img_icon_url
|
||||
self.img_logo_url = img_logo_url
|
||||
self.has_community_visible_stats = has_community_visible_stats
|
||||
self.playtime_windows_forever = playtime_windows_forever
|
||||
self.playtime_mac_forever = playtime_mac_forever
|
||||
self.playtime_linux_forever = playtime_linux_forever
|
||||
self.playtime_2weeks = playtime_2weeks
|
||||
|
||||
def __hash__(self):
|
||||
return self.appid
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, SteamGame):
|
||||
return self.appid == other.appid
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__qualname__} {self.appid} ({self.name})>"
|
||||
|
||||
|
||||
class SteammatchCommand(rc.Command):
|
||||
name: str = "steammatch"
|
||||
|
||||
description: str = "Vedi quali giochi hai in comune con uno o più membri!"
|
||||
|
||||
syntax: str = "{royalnet_username}+"
|
||||
|
||||
def __init__(self, interface: rc.CommandInterface):
|
||||
super().__init__(interface)
|
||||
self._api = steam.webapi.WebAPI(self.config["steampowered"]["token"])
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
users = []
|
||||
|
||||
author = await data.get_author(error_if_none=True)
|
||||
users.append(author)
|
||||
|
||||
for arg in args:
|
||||
user = await rbt.User.find(self.alchemy, data.session, arg)
|
||||
users.append(user)
|
||||
|
||||
if len(users) < 2:
|
||||
raise rc.InvalidInputError("Devi specificare almeno un altro utente!")
|
||||
|
||||
shared_games: Optional[set] = None
|
||||
for user in users:
|
||||
user_games = set()
|
||||
if len(user.steam) == 0:
|
||||
raise rc.UserError(f"{user} non ha un account Steam registrato!")
|
||||
for steam_account in user.steam:
|
||||
steam_account: Steam
|
||||
try:
|
||||
response = await ru.asyncify(self._api.IPlayerService.GetOwnedGames,
|
||||
steamid=steam_account._steamid,
|
||||
include_appinfo=True,
|
||||
include_played_free_games=True,
|
||||
include_free_sub=True,
|
||||
appids_filter=0)
|
||||
except requests.exceptions.HTTPError:
|
||||
raise rc.ExternalError(f"L'account Steam di {user} è privato!")
|
||||
games = response["response"]["games"]
|
||||
for game in games:
|
||||
user_games.add(SteamGame(**game))
|
||||
if shared_games is None:
|
||||
shared_games = user_games
|
||||
else:
|
||||
shared_games = shared_games.intersection(user_games)
|
||||
|
||||
message_rows = [f"🎮 Giochi in comune tra {ru.andformat([str(user) for user in users], final=' e ')}:"]
|
||||
for game in sorted(list(shared_games), key=lambda g: g.name):
|
||||
message_rows.append(f"- {game}")
|
||||
|
||||
message = "\n".join(message_rows)
|
||||
await data.reply(message)
|
125
royalpack/commands/steampowered.py
Normal file
125
royalpack/commands/steampowered.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
from typing import *
|
||||
import steam.steamid
|
||||
import steam.webapi
|
||||
import datetime
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
import logging
|
||||
from royalnet.backpack import tables as rbt
|
||||
|
||||
from .abstract.linker import LinkerCommand
|
||||
|
||||
from ..tables import Steam, FiorygiTransaction
|
||||
from ..types import Updatable
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SteampoweredCommand(LinkerCommand):
|
||||
name: str = "steampowered"
|
||||
|
||||
description: str = "Connetti e visualizza informazioni sul tuo account di Steam!"
|
||||
|
||||
syntax: str = "{url_profilo}"
|
||||
|
||||
def __init__(self, interface: rc.CommandInterface):
|
||||
super().__init__(interface)
|
||||
self._api = steam.webapi.WebAPI(self.token())
|
||||
|
||||
def token(self):
|
||||
return self.config["steampowered"]["token"]
|
||||
|
||||
async def get_updatables_of_user(self, session, user: rbt.User) -> List[Steam]:
|
||||
return user.steam
|
||||
|
||||
async def get_updatables(self, session) -> List[Steam]:
|
||||
return await ru.asyncify(session.query(self.alchemy.get(Steam)).all)
|
||||
|
||||
async def create(self,
|
||||
session,
|
||||
user: rbt.User,
|
||||
args: rc.CommandArgs,
|
||||
data: Optional[rc.CommandData] = None) -> Optional[Steam]:
|
||||
url = args.joined()
|
||||
steamid64 = await self._call(steam.steamid.steam64_from_url, url)
|
||||
if steamid64 is None:
|
||||
raise rc.InvalidInputError("Quel link non è associato ad alcun account Steam.")
|
||||
response = await self._call(self._api.ISteamUser.GetPlayerSummaries_v2, steamids=steamid64)
|
||||
r = response["response"]["players"][0]
|
||||
steam_account = self.alchemy.get(Steam)(
|
||||
user=user,
|
||||
_steamid=int(steamid64),
|
||||
persona_name=r["personaname"],
|
||||
profile_url=r["profileurl"],
|
||||
avatar=r["avatarfull"],
|
||||
primary_clan_id=r["primaryclanid"],
|
||||
account_creation_date=datetime.datetime.fromtimestamp(r["timecreated"])
|
||||
)
|
||||
|
||||
await FiorygiTransaction.spawn_fiorygi(
|
||||
data=data,
|
||||
user=user,
|
||||
qty=1,
|
||||
reason="aver collegato a Royalnet il proprio account di League of Legends"
|
||||
)
|
||||
|
||||
session.add(steam_account)
|
||||
return steam_account
|
||||
|
||||
async def update(self, session, obj: Steam, change: Callable[[str, Any], Awaitable[None]]):
|
||||
response = await self._call(self._api.ISteamUser.GetPlayerSummaries_v2, steamids=obj.steamid.as_64)
|
||||
r = response["response"]["players"][0]
|
||||
obj.persona_name = r["personaname"]
|
||||
obj.profile_url = r["profileurl"]
|
||||
obj.avatar = r["avatar"]
|
||||
obj.primary_clan_id = r["primaryclanid"]
|
||||
obj.account_creation_date = datetime.datetime.fromtimestamp(r["timecreated"])
|
||||
response = await self._call(self._api.IPlayerService.GetSteamLevel_v1, steamid=obj.steamid.as_64)
|
||||
obj.account_level = response["response"]["player_level"]
|
||||
response = await self._call(self._api.IPlayerService.GetOwnedGames_v1,
|
||||
steamid=obj.steamid.as_64,
|
||||
include_appinfo=False,
|
||||
include_played_free_games=True,
|
||||
include_free_sub=False,
|
||||
appids_filter=None)
|
||||
obj.owned_games_count = response["response"]["game_count"]
|
||||
if response["response"]["game_count"] >= 0:
|
||||
obj.most_played_game_2weeks = sorted(response["response"]["games"], key=lambda g: -g.get("playtime_2weeks", 0))[0]["appid"]
|
||||
obj.most_played_game_forever = sorted(response["response"]["games"], key=lambda g: -g.get("playtime_forever", 0))[0]["appid"]
|
||||
|
||||
async def on_increase(self, session, obj: Updatable, attribute: str, old: Any, new: Any) -> None:
|
||||
pass
|
||||
|
||||
async def on_unchanged(self, session, obj: Updatable, attribute: str, old: Any, new: Any) -> None:
|
||||
pass
|
||||
|
||||
async def on_decrease(self, session, obj: Updatable, attribute: str, old: Any, new: Any) -> None:
|
||||
pass
|
||||
|
||||
async def on_first(self, session, obj: Updatable, attribute: str, old: None, new: Any) -> None:
|
||||
pass
|
||||
|
||||
async def on_reset(self, session, obj: Updatable, attribute: str, old: Any, new: None) -> None:
|
||||
pass
|
||||
|
||||
def describe(self, obj: Steam):
|
||||
return f"ℹ️ [url={obj.profile_url}]{obj.persona_name}[/url]\n" \
|
||||
f"[b]Level {obj.account_level}[/b]\n" \
|
||||
f"\n" \
|
||||
f"Owned games: [b]{obj.owned_games_count}[/b]\n" \
|
||||
f"Most played 2 weeks: [url=https://store.steampowered.com/app/{obj.most_played_game_2weeks}]{obj.most_played_game_2weeks}[/url]\n" \
|
||||
f"Most played forever: [url=https://store.steampowered.com/app/{obj.most_played_game_forever}]{obj.most_played_game_forever}[/url]\n" \
|
||||
f"\n" \
|
||||
f"SteamID32: [c]{obj.steamid.as_32}[/c]\n" \
|
||||
f"SteamID64: [c]{obj.steamid.as_64}[/c]\n" \
|
||||
f"SteamID2: [c]{obj.steamid.as_steam2}[/c]\n" \
|
||||
f"SteamID3: [c]{obj.steamid.as_steam3}[/c]\n" \
|
||||
f"\n" \
|
||||
f"Created on: [b]{obj.account_creation_date}[/b]\n"
|
||||
|
||||
async def _call(self, method, *args, **kwargs):
|
||||
log.debug(f"Calling {method}")
|
||||
try:
|
||||
return await ru.asyncify(method, *args, **kwargs)
|
||||
except Exception as e:
|
||||
raise rc.ExternalError("\n".join(e.args).replace(self.token(), "HIDDEN"))
|
|
@ -1,28 +0,0 @@
|
|||
import discord
|
||||
from royalnet.commands import *
|
||||
|
||||
|
||||
class SummonCommand(Command):
|
||||
name: str = "summon"
|
||||
|
||||
aliases = ["cv"]
|
||||
|
||||
description: str = "Evoca il bot in un canale vocale."
|
||||
|
||||
syntax: str = "[nomecanale]"
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
channel_name = args.joined()
|
||||
if self.interface.name == "discord":
|
||||
message: discord.Message = data.message
|
||||
guild_id = message.guild.id
|
||||
user_id = message.author.id
|
||||
else:
|
||||
guild_id = None
|
||||
user_id = None
|
||||
response = await self.interface.call_herald_event("discord", "discord_summon",
|
||||
channel_name=channel_name, guild_id=guild_id, user_id=user_id)
|
||||
if self.interface.name == "discord":
|
||||
await data.reply(f"✅ Mi sono connesso in <#{response['channel']['id']}>!")
|
||||
else:
|
||||
await data.reply(f"✅ Mi sono connesso in [b]#{response['channel']['name']}[/b]!")
|
33
royalpack/commands/treasure.py
Normal file
33
royalpack/commands/treasure.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from typing import *
|
||||
import royalnet
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
from ..tables import Treasure, FiorygiTransaction
|
||||
|
||||
|
||||
class TreasureCommand(rc.Command):
|
||||
name: str = "treasure"
|
||||
|
||||
description: str = "Riscatta un Treasure che hai trovato da qualche parte."
|
||||
|
||||
syntax: str = "{code}"
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
author = await data.get_author(error_if_none=True)
|
||||
code = args[0].lower()
|
||||
|
||||
TreasureT = self.alchemy.get(Treasure)
|
||||
|
||||
treasure = await ru.asyncify(data.session.query(TreasureT).get, code)
|
||||
if treasure is None:
|
||||
raise rc.UserError("Non esiste nessun Treasure con quel codice.")
|
||||
if treasure.redeemed_by is not None:
|
||||
raise rc.UserError(f"Quel tesoro è già stato riscattato da {treasure.redeemed_by}.")
|
||||
|
||||
treasure.redeemed_by = author
|
||||
await data.session_commit()
|
||||
await FiorygiTransaction.spawn_fiorygi(data,
|
||||
author,
|
||||
treasure.value,
|
||||
f'aver trovato il tesoro "{treasure.code}"')
|
||||
await data.reply("🤑 Tesoro riscattato!")
|
147
royalpack/commands/trivia.py
Normal file
147
royalpack/commands/trivia.py
Normal file
|
@ -0,0 +1,147 @@
|
|||
from typing import *
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import random
|
||||
import uuid
|
||||
import html
|
||||
import royalnet.commands as rc
|
||||
import royalnet.utils as ru
|
||||
import royalnet.backpack.tables as rbt
|
||||
from ..tables import TriviaScore
|
||||
|
||||
|
||||
class TriviaCommand(rc.Command):
|
||||
name: str = "trivia"
|
||||
|
||||
aliases = ["t"]
|
||||
|
||||
description: str = "Manda una domanda dell'OpenTDB in chat."
|
||||
|
||||
syntax = "[credits|scores]"
|
||||
|
||||
_letter_emojis = ["🇦", "🇧", "🇨", "🇩"]
|
||||
|
||||
_medal_emojis = ["🥇", "🥈", "🥉", "🔹"]
|
||||
|
||||
_correct_emoji = "✅"
|
||||
|
||||
_wrong_emoji = "❌"
|
||||
|
||||
_answer_time = 20
|
||||
|
||||
# _question_lock: bool = False
|
||||
|
||||
def __init__(self, interface: rc.CommandInterface):
|
||||
super().__init__(interface)
|
||||
self._answerers: Dict[uuid.UUID, Dict[str, bool]] = {}
|
||||
|
||||
async def run(self, args: rc.CommandArgs, data: rc.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 ru.asyncify(data.session.query(self.alchemy.get(TriviaScore)).all)
|
||||
strings = ["🏆 [b]Trivia Leaderboards[/b]\n"]
|
||||
for index, ts in enumerate(sorted(trivia_scores, key=lambda ts: -ts.score)):
|
||||
if index > 3:
|
||||
index = 3
|
||||
strings.append(f"{self._medal_emojis[index]} {ts.user.username}: [b]{ts.score:.0f}p[/b]"
|
||||
f" ({ts.correct_answers}/{ts.total_answers})")
|
||||
await data.reply("\n".join(strings))
|
||||
return
|
||||
# if self._question_lock:
|
||||
# raise rc.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 rc.CommandError(f"OpenTDB returned an error response_code ({j['response_code']}).")
|
||||
question = j["results"][0]
|
||||
text = f'❓ [b]{question["category"]}[/b]\n' \
|
||||
f'{html.unescape(question["question"])}'
|
||||
# Prepare answers
|
||||
correct_answer: str = question["correct_answer"]
|
||||
wrong_answers: List[str] = question["incorrect_answers"]
|
||||
answers: 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: rc.CommandData):
|
||||
answerer_ = await data.get_author(error_if_none=True)
|
||||
try:
|
||||
self._answerers[question_id][answerer_.uid] = True
|
||||
except KeyError:
|
||||
raise rc.UserError("Tempo scaduto!")
|
||||
await data.reply("🆗 Hai risposto alla domanda. Ora aspetta un attimo per i risultati!")
|
||||
|
||||
async def wrong(data: rc.CommandData):
|
||||
answerer_ = await data.get_author(error_if_none=True)
|
||||
try:
|
||||
self._answerers[question_id][answerer_.uid] = False
|
||||
except KeyError:
|
||||
raise rc.UserError("Tempo scaduto!")
|
||||
await data.reply("🆗 Hai risposto alla domanda. Ora aspetta un attimo per i risultati!")
|
||||
|
||||
# Add question
|
||||
keyboard: List[rc.KeyboardKey] = []
|
||||
for index, answer in enumerate(answers):
|
||||
if index == correct_index:
|
||||
keyboard.append(rc.KeyboardKey(interface=self.interface,
|
||||
short=self._letter_emojis[index],
|
||||
text=answers[index],
|
||||
callback=correct))
|
||||
else:
|
||||
keyboard.append(rc.KeyboardKey(interface=self.interface,
|
||||
short=self._letter_emojis[index],
|
||||
text=answers[index],
|
||||
callback=wrong))
|
||||
async with data.keyboard(text=text, keys=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.get(rbt.users.User)).get(answerer_id)
|
||||
if answerer.trivia_score is None:
|
||||
ts = self.interface.alchemy.get(TriviaScore)(user=answerer)
|
||||
data.session.add(ts)
|
||||
await ru.asyncify(data.session.commit)
|
||||
previous_score = answerer.trivia_score.score
|
||||
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
|
||||
current_score = answerer.trivia_score.score
|
||||
score_difference = current_score - previous_score
|
||||
results += f" {answerer}: [b]{current_score:.0f}p[/b] ({score_difference:+.0f}p)\n"
|
||||
await data.reply(results)
|
||||
del self._answerers[question_id]
|
||||
await ru.asyncify(data.session.commit)
|
||||
# self._question_lock = False
|
|
@ -1,11 +1,9 @@
|
|||
from typing import *
|
||||
from royalnet.commands import *
|
||||
from royalnet.utils import *
|
||||
from royalnet.backpack.tables import User
|
||||
from sqlalchemy import func
|
||||
import royalnet.commands as rc
|
||||
import royalnet.backpack.tables as rbt
|
||||
|
||||
|
||||
class UserinfoCommand(Command):
|
||||
class UserinfoCommand(rc.Command):
|
||||
name: str = "userinfo"
|
||||
|
||||
aliases = ["uinfo", "ui", "useri"]
|
||||
|
@ -14,31 +12,26 @@ class UserinfoCommand(Command):
|
|||
|
||||
syntax = "[username]"
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
|
||||
username = args.optional(0)
|
||||
if username is None:
|
||||
user: User = await data.get_author(error_if_none=True)
|
||||
user: rbt.User = await data.get_author(error_if_none=True)
|
||||
else:
|
||||
found: Optional[User] = await asyncify(
|
||||
data.session
|
||||
.query(self.alchemy.get(User))
|
||||
.filter(func.lower(self.alchemy.get(User).username) == func.lower(username))
|
||||
.one_or_none
|
||||
)
|
||||
found: Optional[rbt.User] = await rbt.User.find(self.alchemy, data.session, username)
|
||||
if not found:
|
||||
raise InvalidInputError("Utente non trovato.")
|
||||
raise rc.InvalidInputError("Utente non trovato.")
|
||||
else:
|
||||
user = found
|
||||
|
||||
r = [
|
||||
f"ℹ️ [b]{user.username}[/b] (ID: {user.uid})",
|
||||
f"{user.role}",
|
||||
"",
|
||||
f"ℹ️ [url=https://ryg.steffo.eu/#/user/{user.uid}]{user.username}[/url]",
|
||||
f"{', '.join(user.roles)}",
|
||||
]
|
||||
|
||||
if user.fiorygi:
|
||||
r.append(f"{user.fiorygi}")
|
||||
r.append("")
|
||||
if user.email:
|
||||
r.append(f"{user.email}")
|
||||
|
||||
r.append("")
|
||||
|
||||
# Bios are a bit too long
|
||||
# if user.bio:
|
||||
|
@ -50,18 +43,32 @@ class UserinfoCommand(Command):
|
|||
for account in user.discord:
|
||||
r.append(f"{account}")
|
||||
|
||||
for account in user.steam:
|
||||
r.append(f"{account}")
|
||||
if account.dota is not None:
|
||||
r.append(f"{account.dota}")
|
||||
if account.brawlhalla is not None:
|
||||
r.append(f"{account.brawlhalla}")
|
||||
|
||||
for account in user.leagueoflegends:
|
||||
r.append(f"{account}")
|
||||
|
||||
r.append("")
|
||||
|
||||
r.append(f"Ha creato [b]{len(user.diario_created)}[/b] righe di diario, e vi compare in"
|
||||
r.append(f"Ha creato [b]{len(user.diario_created)}[/b] righe di "
|
||||
f"[url=https://ryg.steffo.eu/#/diario]Diario[/url], e vi compare in"
|
||||
f" [b]{len(user.diario_quoted)}[/b] righe.")
|
||||
|
||||
r.append("")
|
||||
|
||||
if user.trivia_score:
|
||||
r.append(f"Trivia: [b]{user.trivia_score.correct_answers}[/b] risposte corrette / "
|
||||
f"{user.trivia_score.total_answers} totali")
|
||||
r.append(f"Ha [b]{user.trivia_score.score:.0f}[/b] punti Trivia, avendo risposto correttamente a"
|
||||
f" [b]{user.trivia_score.correct_answers}[/b] domande su"
|
||||
f" [b]{user.trivia_score.total_answers}[/b].")
|
||||
r.append("")
|
||||
|
||||
if user.fiorygi:
|
||||
r.append(f"Ha [b]{user.fiorygi}[/b].")
|
||||
r.append("")
|
||||
|
||||
await data.reply("\n".join(r))
|
||||
|
|
|
@ -1,49 +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 = "[nomecanale]"
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
if self.interface.name != "discord":
|
||||
raise UnsupportedError(f"{self} non è supportato su {self.interface.name}.")
|
||||
bot: discord.Client = self.serf.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:
|
||||
raise InvalidInputError("Non esiste alcun canale vocale con il nome specificato.")
|
||||
elif len(matching_channels) > 1:
|
||||
raise UserError("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 InvalidInputError("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"<https://discordapp.com/channels/{channel.guild.id}/{channel.id}>\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"<https://discordapp.com/channels/{channel.guild.id}/{channel.id}>")
|
|
@ -1,16 +0,0 @@
|
|||
from .play import PlayCommand
|
||||
|
||||
|
||||
class YahoovideoCommand(PlayCommand):
|
||||
name: str = "yahoovideo"
|
||||
|
||||
aliases = ["yv"]
|
||||
|
||||
description: str = "Cerca un video su Yahoo Video e lo aggiunge alla coda della chat vocale."
|
||||
|
||||
syntax = "{ricerca}"
|
||||
|
||||
async def get_url(self, args):
|
||||
return f"yvsearch:{args.joined()}"
|
||||
|
||||
# Too bad yvsearch: always finds nothing.
|
|
@ -1,14 +0,0 @@
|
|||
from .play import PlayCommand
|
||||
|
||||
|
||||
class YoutubeCommand(PlayCommand):
|
||||
name: str = "youtube"
|
||||
|
||||
aliases = ["yt"]
|
||||
|
||||
description: str = "Cerca un video su YouTube e lo aggiunge alla coda della chat vocale."
|
||||
|
||||
syntax = "{ricerca}"
|
||||
|
||||
async def get_url(self, args):
|
||||
return f"ytsearch:{args.joined()}"
|
|
@ -5,6 +5,10 @@ from .discord_play import DiscordPlayEvent
|
|||
from .discord_skip import DiscordSkipEvent
|
||||
from .discord_queue import DiscordQueueEvent
|
||||
from .discord_pause import DiscordPauseEvent
|
||||
from .discord_playable import DiscordPlaymodeEvent
|
||||
from .discord_lazy_play import DiscordLazyPlayEvent
|
||||
from .telegram_message import TelegramMessageEvent
|
||||
from .pong import PongEvent
|
||||
|
||||
# Enter the commands of your Pack here!
|
||||
available_events = [
|
||||
|
@ -14,6 +18,10 @@ available_events = [
|
|||
DiscordSkipEvent,
|
||||
DiscordQueueEvent,
|
||||
DiscordPauseEvent,
|
||||
DiscordPlaymodeEvent,
|
||||
DiscordLazyPlayEvent,
|
||||
TelegramMessageEvent,
|
||||
PongEvent,
|
||||
]
|
||||
|
||||
# Don't change this, it should automatically generate __all__
|
||||
|
|
100
royalpack/events/discord_lazy_play.py
Normal file
100
royalpack/events/discord_lazy_play.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
import datetime
|
||||
from typing import *
|
||||
|
||||
import discord
|
||||
import royalnet.commands as rc
|
||||
import royalnet.serf.discord as rsd
|
||||
import royalnet.bard.discord as rbd
|
||||
|
||||
from ..utils import RoyalQueue, RoyalPool
|
||||
|
||||
|
||||
class DiscordLazyPlayEvent(rc.Event):
|
||||
name = "discord_lazy_play"
|
||||
|
||||
async def run(self,
|
||||
urls: List[str],
|
||||
guild_id: Optional[int] = None,
|
||||
user: Optional[str] = None,
|
||||
force_color: Optional[int] = None,
|
||||
**kwargs) -> dict:
|
||||
if not isinstance(self.serf, rsd.DiscordSerf):
|
||||
raise rc.UnsupportedError()
|
||||
|
||||
serf: rsd.DiscordSerf = self.serf
|
||||
client: discord.Client = self.serf.client
|
||||
guild: discord.Guild = client.get_guild(guild_id) if guild_id is not None else None
|
||||
candidate_players: List[rsd.VoicePlayer] = serf.find_voice_players(guild)
|
||||
|
||||
if len(candidate_players) == 0:
|
||||
raise rc.UserError("Il bot non è in nessun canale vocale.\n"
|
||||
"Evocalo prima con [c]summon[/c]!")
|
||||
elif len(candidate_players) == 1:
|
||||
voice_player = candidate_players[0]
|
||||
else:
|
||||
raise rc.CommandError("Non so in che Server riprodurre questo file...\n"
|
||||
"Invia il comando su Discord, per favore!")
|
||||
|
||||
added: List[rbd.YtdlDiscord] = []
|
||||
too_long: List[rbd.YtdlDiscord] = []
|
||||
|
||||
for url in urls:
|
||||
ytds = await rbd.YtdlDiscord.from_url(url)
|
||||
if isinstance(voice_player.playing, RoyalQueue):
|
||||
for index, ytd in enumerate(ytds):
|
||||
if ytd.info.duration >= datetime.timedelta(seconds=self.config["Play"]["max_song_duration"]):
|
||||
too_long.append(ytd)
|
||||
continue
|
||||
added.append(ytd)
|
||||
voice_player.playing.contents.append(ytd)
|
||||
if not voice_player.voice_client.is_playing():
|
||||
await voice_player.start()
|
||||
elif isinstance(voice_player.playing, RoyalPool):
|
||||
for index, ytd in enumerate(ytds):
|
||||
if ytd.info.duration >= datetime.timedelta(seconds=self.config["Play"]["max_song_duration"]):
|
||||
too_long.append(ytd)
|
||||
continue
|
||||
added.append(ytd)
|
||||
voice_player.playing.full_pool.append(ytd)
|
||||
voice_player.playing.remaining_pool.append(ytd)
|
||||
if not voice_player.voice_client.is_playing():
|
||||
await voice_player.start()
|
||||
else:
|
||||
raise rc.CommandError(f"Non so come aggiungere musica a [c]{voice_player.playing.__class__.__qualname__}[/c]!")
|
||||
|
||||
main_channel: discord.TextChannel = client.get_channel(self.config["Discord"]["main_channel_id"])
|
||||
|
||||
if len(added) > 0:
|
||||
if user:
|
||||
await main_channel.send(rsd.escape(f"▶️ {user} ha aggiunto {len(added)} file _(lazy)_ alla coda:"))
|
||||
else:
|
||||
await main_channel.send(rsd.escape(f"▶️ Aggiunt{'o' if len(added) == 1 else 'i'} {len(added)} file "
|
||||
f"[i](lazy)[/i] alla coda:"))
|
||||
for ytd in added[:5]:
|
||||
embed: discord.Embed = ytd.embed()
|
||||
if force_color:
|
||||
embed._colour = discord.Colour(force_color)
|
||||
await main_channel.send(embed=embed)
|
||||
if len(added) > 5:
|
||||
await main_channel.send(f"e altri {len(added) - 5}!")
|
||||
|
||||
if len(too_long) > 0:
|
||||
if user:
|
||||
await main_channel.send(rsd.escape(
|
||||
f"⚠ {len(too_long)} file non {'è' if len(too_long) == 1 else 'sono'}"
|
||||
f" stat{'o' if len(too_long) == 1 else 'i'} scaricat{'o' if len(too_long) == 1 else 'i'}"
|
||||
f" perchè durava{'' if len(too_long) == 1 else 'no'}"
|
||||
f" più di [c]{self.config['Play']['max_song_duration']}[/c] secondi."
|
||||
))
|
||||
|
||||
if len(added) + len(too_long) == 0:
|
||||
raise rc.InvalidInputError("Non è stato aggiunto nessun file alla coda.")
|
||||
|
||||
return {
|
||||
"added": [{
|
||||
"title": ytd.info.title,
|
||||
} for ytd in added],
|
||||
"too_long": [{
|
||||
"title": ytd.info.title,
|
||||
} for ytd in too_long]
|
||||
}
|
|
@ -1,32 +1,37 @@
|
|||
import discord
|
||||
from typing import *
|
||||
from royalnet.commands import *
|
||||
import royalnet.commands as rc
|
||||
from royalnet.serf.discord import *
|
||||
|
||||
|
||||
class DiscordPauseEvent(Event):
|
||||
class DiscordPauseEvent(rc.Event):
|
||||
name = "discord_pause"
|
||||
|
||||
async def run(self,
|
||||
guild_id: Optional[int] = None,
|
||||
**kwargs) -> dict:
|
||||
if not isinstance(self.serf, DiscordSerf):
|
||||
raise UnsupportedError()
|
||||
raise rc.UnsupportedError()
|
||||
client: discord.Client = self.serf.client
|
||||
if len(self.serf.voice_players) == 1:
|
||||
voice_player: VoicePlayer = self.serf.voice_players[0]
|
||||
else:
|
||||
if guild_id is None:
|
||||
# TODO: trovare un modo per riprodurre canzoni su più server da Telegram
|
||||
raise InvalidInputError("Non so in che Server riprodurre questo file...\n"
|
||||
"Invia il comando su Discord, per favore!")
|
||||
raise rc.InvalidInputError("Non so in che Server riprodurre questo file...\n"
|
||||
"Invia il comando su Discord, per favore!")
|
||||
guild: discord.Guild = client.get_guild(guild_id)
|
||||
if guild is None:
|
||||
raise InvalidInputError("Impossibile trovare il Server specificato.")
|
||||
voice_player: VoicePlayer = self.serf.find_voice_player(guild)
|
||||
if voice_player is None:
|
||||
raise UserError("Il bot non è in nessun canale vocale.\n"
|
||||
"Evocalo prima con [c]summon[/c]!")
|
||||
raise rc.InvalidInputError("Impossibile trovare il Server specificato.")
|
||||
candidate_players = self.serf.find_voice_players(guild)
|
||||
if len(candidate_players) == 0:
|
||||
raise rc.UserError("Il bot non è in nessun canale vocale.\n"
|
||||
"Evocalo prima con [c]summon[/c]!")
|
||||
elif len(candidate_players) == 1:
|
||||
voice_player = candidate_players[0]
|
||||
else:
|
||||
raise rc.CommandError("Non so su che Server saltare canzone...\n"
|
||||
"Invia il comando su Discord, per favore!")
|
||||
|
||||
if voice_player.voice_client.is_paused():
|
||||
voice_player.voice_client.resume()
|
||||
|
|
|
@ -1,62 +1,102 @@
|
|||
import discord
|
||||
import pickle
|
||||
import base64
|
||||
import datetime
|
||||
from typing import *
|
||||
from royalnet.commands import *
|
||||
from royalnet.serf.discord import *
|
||||
from royalnet.bard import *
|
||||
from ..utils import RoyalQueue
|
||||
|
||||
import discord
|
||||
import royalnet.commands as rc
|
||||
import royalnet.serf.discord as rsd
|
||||
import royalnet.bard.discord as rbd
|
||||
|
||||
from ..utils import RoyalQueue, RoyalPool
|
||||
|
||||
|
||||
class DiscordPlayEvent(Event):
|
||||
class DiscordPlayEvent(rc.Event):
|
||||
name = "discord_play"
|
||||
|
||||
async def run(self,
|
||||
url: str,
|
||||
urls: List[str],
|
||||
guild_id: Optional[int] = None,
|
||||
user: Optional[str] = None,
|
||||
force_color: Optional[int] = None,
|
||||
**kwargs) -> dict:
|
||||
if not isinstance(self.serf, DiscordSerf):
|
||||
raise UnsupportedError()
|
||||
if not isinstance(self.serf, rsd.DiscordSerf):
|
||||
raise rc.UnsupportedError()
|
||||
|
||||
serf: rsd.DiscordSerf = self.serf
|
||||
client: discord.Client = self.serf.client
|
||||
if len(self.serf.voice_players) == 1:
|
||||
voice_player: VoicePlayer = self.serf.voice_players[0]
|
||||
guild: discord.Guild = client.get_guild(guild_id) if guild_id is not None else None
|
||||
candidate_players: List[rsd.VoicePlayer] = serf.find_voice_players(guild)
|
||||
|
||||
if len(candidate_players) == 0:
|
||||
raise rc.UserError("Il bot non è in nessun canale vocale.\n"
|
||||
"Evocalo prima con [c]summon[/c]!")
|
||||
elif len(candidate_players) == 1:
|
||||
voice_player = candidate_players[0]
|
||||
else:
|
||||
if guild_id is None:
|
||||
# TODO: trovare un modo per riprodurre canzoni su più server da Telegram
|
||||
raise InvalidInputError("Non so in che Server riprodurre questo file...\n"
|
||||
"Invia il comando su Discord, per favore!")
|
||||
guild: discord.Guild = client.get_guild(guild_id)
|
||||
if guild is None:
|
||||
raise InvalidInputError("Impossibile trovare il Server specificato.")
|
||||
voice_player: VoicePlayer = self.serf.find_voice_player(guild)
|
||||
if voice_player is None:
|
||||
raise UserError("Il bot non è in nessun canale vocale.\n"
|
||||
"Evocalo prima con [c]summon[/c]!")
|
||||
ytds = await YtdlDiscord.from_url(url)
|
||||
added: List[YtdlDiscord] = []
|
||||
too_long: List[YtdlDiscord] = []
|
||||
if isinstance(voice_player.playing, RoyalQueue):
|
||||
for index, ytd in enumerate(ytds):
|
||||
if ytd.info.duration >= datetime.timedelta(seconds=self.config["Play"]["max_song_duration"]):
|
||||
too_long.append(ytd)
|
||||
continue
|
||||
await ytd.convert_to_pcm()
|
||||
added.append(ytd)
|
||||
voice_player.playing.contents.append(ytd)
|
||||
if not voice_player.voice_client.is_playing():
|
||||
await voice_player.start()
|
||||
else:
|
||||
raise CommandError(f"Non so come aggiungere musica a [c]{voice_player.playing.__class__.__qualname__}[/c]!")
|
||||
raise rc.CommandError("Non so in che Server riprodurre questo file...\n"
|
||||
"Invia il comando su Discord, per favore!")
|
||||
|
||||
added: List[rbd.YtdlDiscord] = []
|
||||
too_long: List[rbd.YtdlDiscord] = []
|
||||
|
||||
for url in urls:
|
||||
ytds = await rbd.YtdlDiscord.from_url(url)
|
||||
if isinstance(voice_player.playing, RoyalQueue):
|
||||
for index, ytd in enumerate(ytds):
|
||||
if ytd.info.duration >= datetime.timedelta(seconds=self.config["Play"]["max_song_duration"]):
|
||||
too_long.append(ytd)
|
||||
continue
|
||||
await ytd.convert_to_pcm()
|
||||
added.append(ytd)
|
||||
voice_player.playing.contents.append(ytd)
|
||||
if not voice_player.voice_client.is_playing():
|
||||
await voice_player.start()
|
||||
elif isinstance(voice_player.playing, RoyalPool):
|
||||
for index, ytd in enumerate(ytds):
|
||||
if ytd.info.duration >= datetime.timedelta(seconds=self.config["Play"]["max_song_duration"]):
|
||||
too_long.append(ytd)
|
||||
continue
|
||||
await ytd.convert_to_pcm()
|
||||
added.append(ytd)
|
||||
voice_player.playing.full_pool.append(ytd)
|
||||
voice_player.playing.remaining_pool.append(ytd)
|
||||
if not voice_player.voice_client.is_playing():
|
||||
await voice_player.start()
|
||||
else:
|
||||
raise rc.CommandError(f"Non so come aggiungere musica a [c]{voice_player.playing.__class__.__qualname__}[/c]!")
|
||||
|
||||
main_channel: discord.TextChannel = client.get_channel(self.config["Discord"]["main_channel_id"])
|
||||
|
||||
if len(added) > 0:
|
||||
if user:
|
||||
await main_channel.send(rsd.escape(f"▶️ {user} ha aggiunto {len(added)} file alla coda:"))
|
||||
else:
|
||||
await main_channel.send(rsd.escape(f"▶️ Aggiunt{'o' if len(added) == 1 else 'i'} {len(added)} file alla"
|
||||
f" coda:"))
|
||||
for ytd in added[:5]:
|
||||
embed: discord.Embed = ytd.embed()
|
||||
if force_color:
|
||||
embed._colour = discord.Colour(force_color)
|
||||
await main_channel.send(embed=embed)
|
||||
if len(added) > 5:
|
||||
await main_channel.send(f"e altri {len(added) - 5}!")
|
||||
|
||||
if len(too_long) > 0:
|
||||
if user:
|
||||
await main_channel.send(rsd.escape(
|
||||
f"⚠ {len(too_long)} file non {'è' if len(too_long) == 1 else 'sono'}"
|
||||
f" stat{'o' if len(too_long) == 1 else 'i'} scaricat{'o' if len(too_long) == 1 else 'i'}"
|
||||
f" perchè durava{'' if len(too_long) == 1 else 'no'}"
|
||||
f" più di [c]{self.config['Play']['max_song_duration']}[/c] secondi."
|
||||
))
|
||||
|
||||
if len(added) + len(too_long) == 0:
|
||||
raise rc.InvalidInputError("Non è stato aggiunto nessun file alla coda.")
|
||||
|
||||
return {
|
||||
"added": [{
|
||||
"title": ytd.info.title,
|
||||
"stringified_base64_pickled_discord_embed": str(base64.b64encode(pickle.dumps(ytd.embed())),
|
||||
encoding="ascii")
|
||||
} for ytd in added],
|
||||
"too_long": [{
|
||||
"title": ytd.info.title,
|
||||
"stringified_base64_pickled_discord_embed": str(base64.b64encode(pickle.dumps(ytd.embed())),
|
||||
encoding="ascii")
|
||||
} for ytd in too_long]
|
||||
}
|
||||
|
|
48
royalpack/events/discord_playable.py
Normal file
48
royalpack/events/discord_playable.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
import datetime
|
||||
from typing import *
|
||||
|
||||
import discord
|
||||
import royalnet.commands as rc
|
||||
import royalnet.serf.discord as rsd
|
||||
import royalnet.bard.discord as rbd
|
||||
|
||||
from ..utils import RoyalQueue, RoyalPool
|
||||
|
||||
|
||||
class DiscordPlaymodeEvent(rc.Event):
|
||||
name = "discord_playmode"
|
||||
|
||||
async def run(self,
|
||||
playable_string: str,
|
||||
guild_id: Optional[int] = None,
|
||||
user: Optional[str] = None,
|
||||
**kwargs) -> dict:
|
||||
if not isinstance(self.serf, rsd.DiscordSerf):
|
||||
raise rc.UnsupportedError()
|
||||
|
||||
serf: rsd.DiscordSerf = self.serf
|
||||
client: discord.Client = self.serf.client
|
||||
guild: discord.Guild = client.get_guild(guild_id) if guild_id is not None else None
|
||||
candidate_players: List[rsd.VoicePlayer] = serf.find_voice_players(guild)
|
||||
|
||||
if len(candidate_players) == 0:
|
||||
raise rc.UserError("Il bot non è in nessun canale vocale.\n"
|
||||
"Evocalo prima con [c]summon[/c]!")
|
||||
elif len(candidate_players) == 1:
|
||||
voice_player = candidate_players[0]
|
||||
else:
|
||||
raise rc.CommandError("Non so a che Server cambiare Playable...\n"
|
||||
"Invia il comando su Discord, per favore!")
|
||||
|
||||
if playable_string.upper() == "QUEUE":
|
||||
playable = await RoyalQueue.create()
|
||||
elif playable_string.upper() == "POOL":
|
||||
playable = await RoyalPool.create()
|
||||
else:
|
||||
raise rc.InvalidInputError(f"Unknown playable '{playable_string.upper()}'")
|
||||
|
||||
await voice_player.change_playing(playable)
|
||||
|
||||
return {
|
||||
"name": f"{playable.__class__.__qualname__}"
|
||||
}
|
|
@ -2,34 +2,39 @@ import discord
|
|||
import pickle
|
||||
import base64
|
||||
from typing import *
|
||||
from royalnet.commands import *
|
||||
import royalnet.commands as rc
|
||||
from royalnet.serf.discord import *
|
||||
from ..utils import RoyalQueue
|
||||
|
||||
|
||||
class DiscordQueueEvent(Event):
|
||||
class DiscordQueueEvent(rc.Event):
|
||||
name = "discord_queue"
|
||||
|
||||
async def run(self,
|
||||
guild_id: Optional[int] = None,
|
||||
**kwargs) -> dict:
|
||||
if not isinstance(self.serf, DiscordSerf):
|
||||
raise UnsupportedError()
|
||||
raise rc.UnsupportedError()
|
||||
client: discord.Client = self.serf.client
|
||||
if len(self.serf.voice_players) == 1:
|
||||
voice_player: VoicePlayer = self.serf.voice_players[0]
|
||||
else:
|
||||
if guild_id is None:
|
||||
# TODO: trovare un modo per riprodurre canzoni su più server da Telegram
|
||||
raise InvalidInputError("Non so in che Server riprodurre questo file...\n"
|
||||
raise rc.InvalidInputError("Non so in che Server riprodurre questo file...\n"
|
||||
"Invia il comando su Discord, per favore!")
|
||||
guild: discord.Guild = client.get_guild(guild_id)
|
||||
if guild is None:
|
||||
raise InvalidInputError("Impossibile trovare il Server specificato.")
|
||||
voice_player: VoicePlayer = self.serf.find_voice_player(guild)
|
||||
if voice_player is None:
|
||||
raise UserError("Il bot non è in nessun canale vocale.\n"
|
||||
"Evocalo prima con [c]summon[/c]!")
|
||||
raise rc.InvalidInputError("Impossibile trovare il Server specificato.")
|
||||
candidate_players = self.serf.find_voice_players(guild)
|
||||
if len(candidate_players) == 0:
|
||||
raise rc.UserError("Il bot non è in nessun canale vocale.\n"
|
||||
"Evocalo prima con [c]summon[/c]!")
|
||||
elif len(candidate_players) == 1:
|
||||
voice_player = candidate_players[0]
|
||||
else:
|
||||
raise rc.CommandError("Non so di che Server visualizzare la coda...\n"
|
||||
"Invia il comando su Discord, per favore!")
|
||||
if isinstance(voice_player.playing, RoyalQueue):
|
||||
now_playing = voice_player.playing.now_playing
|
||||
return {
|
||||
|
@ -46,5 +51,5 @@ class DiscordQueueEvent(Event):
|
|||
} for ytd in voice_player.playing.contents]
|
||||
}
|
||||
else:
|
||||
raise CommandError(f"Non so come visualizzare il contenuto di un "
|
||||
raise rc.CommandError(f"Non so come visualizzare il contenuto di un "
|
||||
f"[c]{voice_player.playing.__class__.__qualname__}[/c].")
|
||||
|
|
|
@ -1,32 +1,37 @@
|
|||
import discord
|
||||
from typing import *
|
||||
from royalnet.commands import *
|
||||
import royalnet.commands as rc
|
||||
from royalnet.serf.discord import *
|
||||
|
||||
|
||||
class DiscordSkipEvent(Event):
|
||||
class DiscordSkipEvent(rc.Event):
|
||||
name = "discord_skip"
|
||||
|
||||
async def run(self,
|
||||
guild_id: Optional[int] = None,
|
||||
**kwargs) -> dict:
|
||||
if not isinstance(self.serf, DiscordSerf):
|
||||
raise UnsupportedError()
|
||||
raise rc.UnsupportedError()
|
||||
client: discord.Client = self.serf.client
|
||||
if len(self.serf.voice_players) == 1:
|
||||
voice_player: VoicePlayer = self.serf.voice_players[0]
|
||||
else:
|
||||
if guild_id is None:
|
||||
# TODO: trovare un modo per riprodurre canzoni su più server da Telegram
|
||||
raise InvalidInputError("Non so in che Server riprodurre questo file...\n"
|
||||
"Invia il comando su Discord, per favore!")
|
||||
raise rc.InvalidInputError("Non so in che Server riprodurre questo file...\n"
|
||||
"Invia il comando su Discord, per favore!")
|
||||
guild: discord.Guild = client.get_guild(guild_id)
|
||||
if guild is None:
|
||||
raise InvalidInputError("Impossibile trovare il Server specificato.")
|
||||
voice_player: VoicePlayer = self.serf.find_voice_player(guild)
|
||||
if voice_player is None:
|
||||
raise UserError("Il bot non è in nessun canale vocale.\n"
|
||||
"Evocalo prima con [c]summon[/c]!")
|
||||
raise rc.InvalidInputError("Impossibile trovare il Server specificato.")
|
||||
candidate_players = self.serf.find_voice_players(guild)
|
||||
if len(candidate_players) == 0:
|
||||
raise rc.UserError("Il bot non è in nessun canale vocale.\n"
|
||||
"Evocalo prima con [c]summon[/c]!")
|
||||
elif len(candidate_players) == 1:
|
||||
voice_player = candidate_players[0]
|
||||
else:
|
||||
raise rc.CommandError("Non so su che Server saltare canzone...\n"
|
||||
"Invia il comando su Discord, per favore!")
|
||||
# Stop the playback of the current song
|
||||
voice_player.voice_client.stop()
|
||||
# Done!
|
||||
|
|
|
@ -60,6 +60,12 @@ class DiscordSummonEvent(Event):
|
|||
# Connect to the channel
|
||||
try:
|
||||
await vp.connect(channel)
|
||||
except OpusNotLoadedError:
|
||||
raise ConfigurationError("libopus non è disponibile sul sistema in cui sta venendo eseguito questo bot,"
|
||||
" pertanto non è possibile con")
|
||||
except DiscordTimeoutError:
|
||||
raise ExternalError("Timeout durante la connessione al canale."
|
||||
" Forse il bot non ha i permessi per entrarci?")
|
||||
except GuildAlreadyConnectedError:
|
||||
raise UserError("Il bot è già connesso in un canale vocale nel Server!\n"
|
||||
"Spostalo manualmente, o disconnettilo e riinvoca [c]summon[/c]!")
|
||||
|
|
11
royalpack/events/pong.py
Normal file
11
royalpack/events/pong.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from typing import *
|
||||
import royalnet
|
||||
import royalnet.commands as rc
|
||||
import datetime
|
||||
|
||||
|
||||
class PongEvent(rc.Event):
|
||||
name = "pong"
|
||||
|
||||
async def run(self, **kwargs) -> dict:
|
||||
return {"timestamp": datetime.datetime.now().timestamp()}
|
28
royalpack/events/telegram_message.py
Normal file
28
royalpack/events/telegram_message.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
import logging
|
||||
import telegram
|
||||
from typing import *
|
||||
from royalnet.serf.telegram.telegramserf import TelegramSerf, escape
|
||||
from royalnet.commands import *
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TelegramMessageEvent(Event):
|
||||
name = "telegram_message"
|
||||
|
||||
async def run(self, chat_id, text, **kwargs) -> dict:
|
||||
if not self.interface.name == "telegram":
|
||||
raise UnsupportedError()
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
serf: TelegramSerf = self.interface.serf
|
||||
|
||||
log.debug("Forwarding message from Herald to Telegram.")
|
||||
await serf.api_call(serf.client.send_message,
|
||||
chat_id=chat_id,
|
||||
text=escape(text),
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True)
|
||||
|
||||
return {}
|
70
royalpack/pycharm/test_apis.http
Normal file
70
royalpack/pycharm/test_apis.http
Normal file
|
@ -0,0 +1,70 @@
|
|||
POST http://localhost:44445/api/login/royalnet/v1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "Steffo",
|
||||
"password": "ciao"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
POST http://localhost:44445/api/token/create/v1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": "NFFU4qg6-WxfAWMN-IW6dexEjNcLzNQNZJko2_pbTsE",
|
||||
"duration": 31536000
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
GET http://localhost:44445/api/bio/get/v1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": "NFFU4qg6-WxfAWMN-IW6dexEjNcLzNQNZJko2_pbTsE",
|
||||
"id": 1
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
POST http://localhost:44445/api/bio/set/v1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": "NFFU4qg6-WxfAWMN-IW6dexEjNcLzNQNZJko2_pbTsE",
|
||||
"contents": "Ciao!"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
GET http://localhost:44445/api/wiki/list/v1
|
||||
|
||||
###
|
||||
|
||||
POST http://localhost:44445/api/wiki/edit/v1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"token": "NFFU4qg6-WxfAWMN-IW6dexEjNcLzNQNZJko2_pbTsE",
|
||||
"title": "Prova!",
|
||||
"contents": "Questa è una pagina wiki di prova.",
|
||||
"format": "text",
|
||||
"theme": "default"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
POST http://localhost:44445/api/wiki/edit/v1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": "80d54849-fab1-4458-9d89-2429773118ef",
|
||||
"token": "NFFU4qg6-WxfAWMN-IW6dexEjNcLzNQNZJko2_pbTsE",
|
||||
"title": "Prova2",
|
||||
"contents": "Questa è una pagina wiki di prova2.",
|
||||
"format": "text",
|
||||
"theme": "default"
|
||||
}
|
||||
|
||||
###
|
|
@ -1,27 +1,38 @@
|
|||
# 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
|
||||
from .api_bio import ApiBioSetStar
|
||||
from .api_diario import ApiDiarioGetStar
|
||||
from .api_diario_list import ApiDiarioPagesStar
|
||||
from .api_discord_cv import ApiDiscordCvStar
|
||||
from .api_wiki_get import ApiWikiGetStar
|
||||
from .api_wiki_list import ApiUserListStar
|
||||
from .api_discord_play import ApiDiscordPlayStar
|
||||
from .api_fiorygi import ApiFiorygiStar
|
||||
from .api_diario_random import ApiDiarioRandomStar
|
||||
from .api_poll import ApiPollStar
|
||||
from .api_poll_list import ApiPollsListStar
|
||||
from .api_cvstats_latest import ApiCvstatsLatestStar
|
||||
from .api_cvstats_avg import ApiCvstatsAvgStar
|
||||
from .api_user_ryg import ApiUserRygStar
|
||||
from .api_user_ryg_list import ApiUserRygListStar
|
||||
from .api_user_avatar import ApiUserAvatarStar
|
||||
from .api_auth_login_osu import ApiAuthLoginOsuStar
|
||||
|
||||
# Enter the PageStars of your Pack here!
|
||||
available_page_stars = [
|
||||
ApiUserListStar,
|
||||
ApiUserGetStar,
|
||||
ApiDiarioListStar,
|
||||
ApiBioSetStar,
|
||||
ApiDiarioGetStar,
|
||||
ApiDiarioPagesStar,
|
||||
ApiDiscordCvStar,
|
||||
ApiWikiGetStar,
|
||||
ApiUserListStar,
|
||||
]
|
||||
|
||||
# Enter the ExceptionStars of your Pack here!
|
||||
available_exception_stars = [
|
||||
|
||||
ApiDiscordPlayStar,
|
||||
ApiFiorygiStar,
|
||||
ApiDiarioRandomStar,
|
||||
ApiPollStar,
|
||||
ApiPollsListStar,
|
||||
ApiCvstatsLatestStar,
|
||||
ApiCvstatsAvgStar,
|
||||
ApiUserRygStar,
|
||||
ApiUserRygListStar,
|
||||
ApiUserAvatarStar,
|
||||
ApiAuthLoginOsuStar,
|
||||
]
|
||||
|
||||
# Don't change this, it should automatically generate __all__
|
||||
__all__ = [star.__name__ for star in [*available_page_stars, *available_exception_stars]]
|
||||
__all__ = [star.__name__ for star in available_page_stars]
|
||||
|
|
102
royalpack/stars/api_auth_login_osu.py
Normal file
102
royalpack/stars/api_auth_login_osu.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
import royalnet.utils as ru
|
||||
import royalnet.backpack.tables as rbt
|
||||
import royalnet.constellation.api as rca
|
||||
import royalnet.constellation.api.apierrors as rcae
|
||||
import itsdangerous
|
||||
import aiohttp
|
||||
import aiohttp.client_exceptions
|
||||
import datetime
|
||||
from ..types import oauth_refresh
|
||||
from ..tables import Osu, FiorygiTransaction
|
||||
|
||||
|
||||
class ApiAuthLoginOsuStar(rca.ApiStar):
|
||||
path = "/api/auth/login/osu/v1"
|
||||
|
||||
parameters = {
|
||||
"get": {
|
||||
"code": "The code returned by the osu! API.",
|
||||
"state": "(Optional) The state payload generated by the osu! command to link a new account. "
|
||||
"If missing, just login."
|
||||
}
|
||||
}
|
||||
|
||||
auth = {
|
||||
"get": False,
|
||||
}
|
||||
|
||||
tags = ["auth"]
|
||||
|
||||
@property
|
||||
def client_id(self):
|
||||
return self.config['osu']['client_id']
|
||||
|
||||
@property
|
||||
def client_secret(self):
|
||||
return self.config['osu']['client_secret']
|
||||
|
||||
@property
|
||||
def base_url(self):
|
||||
return self.config['base_url']
|
||||
|
||||
@property
|
||||
def secret_key(self):
|
||||
return self.config['secret_key']
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Login to Royalnet with your osu! account."""
|
||||
OsuT = self.alchemy.get(Osu)
|
||||
TokenT = self.alchemy.get(rbt.Token)
|
||||
|
||||
code = data.str("code")
|
||||
state = data.str("state", optional=True)
|
||||
|
||||
if state is not None:
|
||||
serializer = itsdangerous.URLSafeSerializer(self.config["secret_key"], salt="osu")
|
||||
uid = serializer.loads(state)
|
||||
user = await rbt.User.find(self.alchemy, data.session, uid)
|
||||
else:
|
||||
user = None
|
||||
|
||||
try:
|
||||
t = await oauth_refresh(url="https://osu.ppy.sh/oauth/token",
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
redirect_uri=f"{self.base_url}{self.path}",
|
||||
refresh_code=code)
|
||||
except aiohttp.client_exceptions.ClientResponseError:
|
||||
raise rca.ForbiddenError("osu! API returned an error in the OAuth token exchange")
|
||||
|
||||
async with aiohttp.ClientSession(headers={"Authorization": f"Bearer {t['access_token']}"}) as session:
|
||||
async with session.get("https://osu.ppy.sh/api/v2/me/") as response:
|
||||
m = await response.json()
|
||||
|
||||
if user is not None:
|
||||
osu = OsuT(
|
||||
user=user,
|
||||
access_token=t["access_token"],
|
||||
refresh_token=t["refresh_token"],
|
||||
expiration_date=datetime.datetime.now() + datetime.timedelta(seconds=t["expires_in"]),
|
||||
osu_id=m["id"],
|
||||
username=m["username"]
|
||||
)
|
||||
|
||||
data.session.add(osu)
|
||||
else:
|
||||
osu = await ru.asyncify(
|
||||
data.session.query(OsuT).filter_by(osu_id=m["id"]).all
|
||||
)
|
||||
if osu is None:
|
||||
raise rcae.ForbiddenError("Unknown osu! account")
|
||||
user = osu.user
|
||||
|
||||
if self.config["osu"]["login"]["enabled"]:
|
||||
token: rbt.Token = TokenT.generate(alchemy=self.alchemy, user=user, expiration_delta=datetime.timedelta(days=7))
|
||||
data.session.add(token)
|
||||
await data.session_commit()
|
||||
|
||||
return token.json()
|
||||
else:
|
||||
raise rcae.ForbiddenError("Account linked successfully; cannot use this account to generate a Royalnet"
|
||||
" login token, as osu! login is currently disabled on this Royalnet instance.")
|
45
royalpack/stars/api_bio.py
Normal file
45
royalpack/stars/api_bio.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
import royalnet.utils as ru
|
||||
import royalnet.backpack.tables as rbt
|
||||
import royalnet.constellation.api as rca
|
||||
from ..tables import Bio
|
||||
|
||||
|
||||
class ApiBioSetStar(rca.ApiStar):
|
||||
path = "/api/bio/v2"
|
||||
|
||||
parameters = {
|
||||
"get": {
|
||||
"uid": "The id of the user to get the bio of."
|
||||
},
|
||||
"put": {
|
||||
"contents": "The contents of the bio."
|
||||
}
|
||||
}
|
||||
|
||||
auth = {
|
||||
"get": False,
|
||||
"put": True,
|
||||
}
|
||||
|
||||
tags = ["bio"]
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Get the bio of a specific user."""
|
||||
user = await rbt.User.find(self.alchemy, data.session, data.int("uid"))
|
||||
return user.bio.json() if user.bio else None
|
||||
|
||||
@rca.magic
|
||||
async def put(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Set the bio of current user."""
|
||||
contents = data["contents"]
|
||||
BioT = self.alchemy.get(Bio)
|
||||
user = await data.user()
|
||||
bio = user.bio
|
||||
if bio is None:
|
||||
bio = BioT(user=user, contents=contents)
|
||||
data.session.add(bio)
|
||||
else:
|
||||
bio.contents = contents
|
||||
await data.session_commit()
|
||||
return bio.json()
|
164
royalpack/stars/api_cvstats_avg.py
Normal file
164
royalpack/stars/api_cvstats_avg.py
Normal file
|
@ -0,0 +1,164 @@
|
|||
import royalnet.constellation.api as rca
|
||||
import royalnet.utils as ru
|
||||
|
||||
|
||||
class ApiCvstatsAvgStar(rca.ApiStar):
|
||||
path = "/api/cvstats/avg/v1"
|
||||
|
||||
tags = ["cvstats"]
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Get some averages on the cvstats."""
|
||||
results = data.session.execute("""
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT date_part('hour', c.h) ph,
|
||||
AVG(c.members_connected) members_connected,
|
||||
AVG(c.users_connected) users_connected,
|
||||
AVG(c.members_online) members_online,
|
||||
AVG(c.users_online) users_online,
|
||||
AVG(c.members_playing) members_playing,
|
||||
AVG(c.users_playing) users_playing,
|
||||
AVG(c.members_total) members_total,
|
||||
AVG(c.users_total) users_total
|
||||
FROM (
|
||||
SELECT date_trunc('hour', c.timestamp) h,
|
||||
AVG(c.members_connected) members_connected,
|
||||
AVG(c.users_connected) users_connected,
|
||||
AVG(c.members_online) members_online,
|
||||
AVG(c.users_online) users_online,
|
||||
AVG(c.members_playing) members_playing,
|
||||
AVG(c.users_playing) users_playing,
|
||||
AVG(c.members_total) members_total,
|
||||
AVG(c.users_total) users_total
|
||||
FROM cvstats c
|
||||
GROUP BY h
|
||||
) c
|
||||
GROUP BY ph
|
||||
) all_time
|
||||
LEFT JOIN
|
||||
(
|
||||
SELECT date_part('hour', c.h) ph,
|
||||
AVG(c.members_connected) members_connected,
|
||||
AVG(c.users_connected) users_connected,
|
||||
AVG(c.members_online) members_online,
|
||||
AVG(c.users_online) users_online,
|
||||
AVG(c.members_playing) members_playing,
|
||||
AVG(c.users_playing) users_playing,
|
||||
AVG(c.members_total) members_total,
|
||||
AVG(c.users_total) users_total
|
||||
FROM (
|
||||
SELECT date_trunc('hour', c.timestamp) h,
|
||||
AVG(c.members_connected) members_connected,
|
||||
AVG(c.users_connected) users_connected,
|
||||
AVG(c.members_online) members_online,
|
||||
AVG(c.users_online) users_online,
|
||||
AVG(c.members_playing) members_playing,
|
||||
AVG(c.users_playing) users_playing,
|
||||
AVG(c.members_total) members_total,
|
||||
AVG(c.users_total) users_total
|
||||
FROM cvstats c
|
||||
WHERE c.timestamp > current_timestamp - interval '7 day'
|
||||
GROUP BY h
|
||||
) c
|
||||
GROUP BY ph
|
||||
) last_week ON last_week.ph = all_time.ph
|
||||
LEFT JOIN
|
||||
(
|
||||
SELECT date_part('hour', c.h) ph,
|
||||
AVG(c.members_connected) members_connected,
|
||||
AVG(c.users_connected) users_connected,
|
||||
AVG(c.members_online) members_online,
|
||||
AVG(c.users_online) users_online,
|
||||
AVG(c.members_playing) members_playing,
|
||||
AVG(c.users_playing) users_playing,
|
||||
AVG(c.members_total) members_total,
|
||||
AVG(c.users_total) users_total
|
||||
FROM (
|
||||
SELECT date_trunc('hour', c.timestamp) h,
|
||||
AVG(c.members_connected) members_connected,
|
||||
AVG(c.users_connected) users_connected,
|
||||
AVG(c.members_online) members_online,
|
||||
AVG(c.users_online) users_online,
|
||||
AVG(c.members_playing) members_playing,
|
||||
AVG(c.users_playing) users_playing,
|
||||
AVG(c.members_total) members_total,
|
||||
AVG(c.users_total) users_total
|
||||
FROM cvstats c
|
||||
WHERE c.timestamp > current_timestamp - interval '30 day'
|
||||
GROUP BY h
|
||||
) c
|
||||
GROUP BY ph
|
||||
) last_month ON last_month.ph = all_time.ph
|
||||
LEFT JOIN
|
||||
(
|
||||
SELECT date_part('hour', c.h) ph,
|
||||
AVG(c.members_connected) members_connected,
|
||||
AVG(c.users_connected) users_connected,
|
||||
AVG(c.members_online) members_online,
|
||||
AVG(c.users_online) users_online,
|
||||
AVG(c.members_playing) members_playing,
|
||||
AVG(c.users_playing) users_playing,
|
||||
AVG(c.members_total) members_total,
|
||||
AVG(c.users_total) users_total
|
||||
FROM (
|
||||
SELECT date_trunc('hour', c.timestamp) h,
|
||||
AVG(c.members_connected) members_connected,
|
||||
AVG(c.users_connected) users_connected,
|
||||
AVG(c.members_online) members_online,
|
||||
AVG(c.users_online) users_online,
|
||||
AVG(c.members_playing) members_playing,
|
||||
AVG(c.users_playing) users_playing,
|
||||
AVG(c.members_total) members_total,
|
||||
AVG(c.users_total) users_total
|
||||
FROM cvstats c
|
||||
WHERE c.timestamp > current_timestamp - interval '1 day'
|
||||
GROUP BY h
|
||||
) c
|
||||
GROUP BY ph
|
||||
) last_day ON last_day.ph = all_time.ph;
|
||||
""")
|
||||
|
||||
return [{
|
||||
"h": r[0],
|
||||
"all_time": {
|
||||
"members_connected": float(r[1]) if r[1] is not None else None,
|
||||
"users_connected": float(r[2]) if r[2] is not None else None,
|
||||
"members_online": float(r[3]) if r[3] is not None else None,
|
||||
"users_online": float(r[4]) if r[4] is not None else None,
|
||||
"members_playing": float(r[5]) if r[5] is not None else None,
|
||||
"users_playing": float(r[6]) if r[6] is not None else None,
|
||||
"members_total": float(r[7]) if r[7] is not None else None,
|
||||
"users_total": float(r[8]) if r[8] is not None else None,
|
||||
},
|
||||
"last_week": {
|
||||
"members_connected": float(r[10]) if r[10] is not None else None,
|
||||
"users_connected": float(r[11]) if r[11] is not None else None,
|
||||
"members_online": float(r[12]) if r[12] is not None else None,
|
||||
"users_online": float(r[13]) if r[13] is not None else None,
|
||||
"members_playing": float(r[14]) if r[14] is not None else None,
|
||||
"users_playing": float(r[15]) if r[15] is not None else None,
|
||||
"members_total": float(r[16]) if r[16] is not None else None,
|
||||
"users_total": float(r[17]) if r[17] is not None else None,
|
||||
},
|
||||
"last_month": {
|
||||
"members_connected": float(r[19]) if r[19] is not None else None,
|
||||
"users_connected": float(r[20]) if r[20] is not None else None,
|
||||
"members_online": float(r[21]) if r[21] is not None else None,
|
||||
"users_online": float(r[22]) if r[22] is not None else None,
|
||||
"members_playing": float(r[23]) if r[23] is not None else None,
|
||||
"users_playing": float(r[24]) if r[24] is not None else None,
|
||||
"members_total": float(r[25]) if r[25] is not None else None,
|
||||
"users_total": float(r[26]) if r[26] is not None else None,
|
||||
},
|
||||
"last_day": {
|
||||
"members_connected": float(r[28]) if r[28] is not None else None,
|
||||
"users_connected": float(r[29]) if r[29] is not None else None,
|
||||
"members_online": float(r[30]) if r[30] is not None else None,
|
||||
"users_online": float(r[31]) if r[31] is not None else None,
|
||||
"members_playing": float(r[32]) if r[32] is not None else None,
|
||||
"users_playing": float(r[33]) if r[33] is not None else None,
|
||||
"members_total": float(r[34]) if r[34] is not None else None,
|
||||
},
|
||||
} for r in sorted(results.fetchall(), key=lambda s: s[0])]
|
18
royalpack/stars/api_cvstats_latest.py
Normal file
18
royalpack/stars/api_cvstats_latest.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
import royalnet.utils as ru
|
||||
import royalnet.constellation.api as rca
|
||||
from ..tables import Cvstats
|
||||
|
||||
|
||||
class ApiCvstatsLatestStar(rca.ApiStar):
|
||||
path = "/api/cvstats/latest/v1"
|
||||
|
||||
tags = ["cvstats"]
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Get the latest 500 cvstats recorded."""
|
||||
CvstatsT = self.alchemy.get(Cvstats)
|
||||
|
||||
cvstats = data.session.query(CvstatsT).order_by(CvstatsT.timestamp.desc()).limit(500).all()
|
||||
|
||||
return list(map(lambda c: c.json(), cvstats))
|
24
royalpack/stars/api_diario.py
Normal file
24
royalpack/stars/api_diario.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import royalnet.constellation.api as rca
|
||||
import royalnet.utils as ru
|
||||
from ..tables import *
|
||||
|
||||
|
||||
class ApiDiarioGetStar(rca.ApiStar):
|
||||
path = "/api/diario/v2"
|
||||
|
||||
parameters = {
|
||||
"get": {
|
||||
"id": "The id of the diario entry to get."
|
||||
}
|
||||
}
|
||||
|
||||
tags = ["diario"]
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Get a specific diario entry."""
|
||||
diario_id = data.int("id")
|
||||
entry: Diario = await ru.asyncify(data.session.query(self.alchemy.get(Diario)).get, diario_id)
|
||||
if entry is None:
|
||||
raise rca.NotFoundError("No such diario entry.")
|
||||
return entry.json()
|
|
@ -1,21 +0,0 @@
|
|||
from starlette.requests import Request
|
||||
from starlette.responses import *
|
||||
from royalnet.constellation import *
|
||||
from royalnet.utils import *
|
||||
from ..tables import *
|
||||
|
||||
|
||||
class ApiDiarioGetStar(PageStar):
|
||||
path = "/api/diario/get/{diario_id}"
|
||||
|
||||
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 shoot(400, "Invalid diario_id")
|
||||
async with self.alchemy.session_acm() as session:
|
||||
entry: Diario = await asyncify(session.query(self.alchemy.get(Diario)).get, diario_id)
|
||||
if entry is None:
|
||||
return shoot(404, "No such user")
|
||||
return JSONResponse(entry.json())
|
|
@ -1,34 +1,45 @@
|
|||
from starlette.requests import Request
|
||||
from starlette.responses import *
|
||||
from royalnet.constellation import *
|
||||
from royalnet.utils import *
|
||||
from typing import *
|
||||
import royalnet.constellation.api as rca
|
||||
import royalnet.utils as ru
|
||||
from ..tables import *
|
||||
|
||||
|
||||
class ApiDiarioListStar(PageStar):
|
||||
path = "/api/diario/list"
|
||||
class ApiDiarioPagesStar(rca.ApiStar):
|
||||
path = "/api/diario/pages/v1"
|
||||
|
||||
async def page(self, request: Request) -> JSONResponse:
|
||||
page_str = request.query_params.get("page", "0")
|
||||
parameters = {
|
||||
"get": {
|
||||
"page": "The diario page you want to get. Can be negative to get the entries in reverse order."
|
||||
}
|
||||
}
|
||||
|
||||
tags = ["diario"]
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Get a diario page made of up to 500 diario entries."""
|
||||
page_str = data["page"]
|
||||
try:
|
||||
page = int(page_str)
|
||||
except (ValueError, TypeError):
|
||||
return shoot(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.get(Diario))
|
||||
.order_by(self.alchemy.get(Diario).diario_id.desc()).limit(500)
|
||||
.offset(page * 500)
|
||||
.all
|
||||
)
|
||||
else:
|
||||
entries: typing.List[Diario] = await asyncify(
|
||||
session.query(self.alchemy.get(Diario))
|
||||
.order_by(self.alchemy.get(Diario).diario_id)
|
||||
.limit(500)
|
||||
.offset(page * 500)
|
||||
.all)
|
||||
response = [entry.json() for entry in entries]
|
||||
return JSONResponse(response)
|
||||
except ValueError:
|
||||
raise rca.InvalidParameterError("'page' is not a valid int.")
|
||||
if page < 0:
|
||||
page = -page-1
|
||||
entries: List[Diario] = await ru.asyncify(
|
||||
data.session
|
||||
.query(self.alchemy.get(Diario))
|
||||
.order_by(self.alchemy.get(Diario).diario_id.desc()).limit(500)
|
||||
.offset(page * 500)
|
||||
.all
|
||||
)
|
||||
else:
|
||||
entries: List[Diario] = await ru.asyncify(
|
||||
data.session
|
||||
.query(self.alchemy.get(Diario))
|
||||
.order_by(self.alchemy.get(Diario).diario_id)
|
||||
.limit(500)
|
||||
.offset(page * 500)
|
||||
.all
|
||||
)
|
||||
response = [entry.json() for entry in entries]
|
||||
return response
|
||||
|
|
36
royalpack/stars/api_diario_random.py
Normal file
36
royalpack/stars/api_diario_random.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from typing import *
|
||||
import royalnet.constellation.api as rca
|
||||
import royalnet.utils as ru
|
||||
from ..tables import *
|
||||
from sqlalchemy import func
|
||||
|
||||
|
||||
class ApiDiarioRandomStar(rca.ApiStar):
|
||||
path = "/api/diario/random/v1"
|
||||
|
||||
parameters = {
|
||||
"get": {
|
||||
"amount": "The number of diario entries to get."
|
||||
}
|
||||
}
|
||||
|
||||
tags = ["diario"]
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Get random diario entries."""
|
||||
DiarioT = self.alchemy.get(Diario)
|
||||
try:
|
||||
amount = int(data["amount"])
|
||||
except ValueError:
|
||||
raise rca.InvalidParameterError("'amount' is not a valid int.")
|
||||
entries: List[Diario] = await ru.asyncify(
|
||||
data.session
|
||||
.query(DiarioT)
|
||||
.order_by(func.random())
|
||||
.limit(amount)
|
||||
.all
|
||||
)
|
||||
if len(entries) < amount:
|
||||
raise rca.NotFoundError("Not enough diario entries.")
|
||||
return list(map(lambda e: e.json(), entries))
|
|
@ -1,12 +1,16 @@
|
|||
from starlette.requests import Request
|
||||
from starlette.responses import *
|
||||
from royalnet.constellation import *
|
||||
from royalnet.utils import *
|
||||
import royalnet.utils as ru
|
||||
import royalnet.constellation.api as rca
|
||||
|
||||
|
||||
class ApiDiscordCvStar(PageStar):
|
||||
path = "/api/discord/cv"
|
||||
class ApiDiscordCvStar(rca.ApiStar):
|
||||
path = "/api/discord/cv/v1"
|
||||
|
||||
async def page(self, request: Request) -> JSONResponse:
|
||||
tags = ["discord"]
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Get the members status of a single Discord guild.
|
||||
|
||||
Equivalent to calling /cv in a chat."""
|
||||
response = await self.interface.call_herald_event("discord", "discord_cv")
|
||||
return JSONResponse(response)
|
||||
return response
|
||||
|
|
40
royalpack/stars/api_discord_play.py
Normal file
40
royalpack/stars/api_discord_play.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from typing import *
|
||||
import royalnet.constellation.api as rca
|
||||
import logging
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ApiDiscordPlayStar(rca.ApiStar):
|
||||
path = "/api/discord/play/v2"
|
||||
|
||||
parameters = {
|
||||
"post": {
|
||||
"url": "The url of the audio file to add.",
|
||||
"user": "The name to display in the File Added message.",
|
||||
"guild_id": "The id of the guild owning the RoyalQueue to add the audio file to.",
|
||||
}
|
||||
}
|
||||
|
||||
tags = ["discord"]
|
||||
|
||||
@rca.magic
|
||||
async def post(self, data: rca.ApiData) -> dict:
|
||||
"""Add a audio file to the RoyalQueue of a Discord Guild."""
|
||||
url = data["url"]
|
||||
user = data.get("user")
|
||||
guild_id_str = data.get("guild_id")
|
||||
if guild_id_str:
|
||||
try:
|
||||
guild_id: Optional[int] = int(guild_id_str)
|
||||
except (ValueError, TypeError):
|
||||
raise rca.InvalidParameterError("'guild_id' is not a valid int.")
|
||||
else:
|
||||
guild_id = None
|
||||
log.info(f"Received request to play {url} on guild_id {guild_id} via web")
|
||||
response = await self.interface.call_herald_event("discord", "discord_play",
|
||||
urls=[url],
|
||||
guild_id=guild_id,
|
||||
user=user)
|
||||
return response
|
39
royalpack/stars/api_fiorygi.py
Normal file
39
royalpack/stars/api_fiorygi.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from typing import *
|
||||
import royalnet.utils as ru
|
||||
import royalnet.backpack.tables as rbt
|
||||
import royalnet.constellation.api as rca
|
||||
from ..tables import Fiorygi
|
||||
|
||||
|
||||
class ApiFiorygiStar(rca.ApiStar):
|
||||
path = "/api/fiorygi/v2"
|
||||
|
||||
parameters = {
|
||||
"get": {
|
||||
"uid": "The user to get the fiorygi of."
|
||||
}
|
||||
}
|
||||
|
||||
tags = ["fiorygi"]
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Get fiorygi information about a specific user."""
|
||||
user = await rbt.User.find(self.alchemy, data.session, data.int("uid"))
|
||||
if user.fiorygi is None:
|
||||
return {
|
||||
"fiorygi": 0,
|
||||
"transactions": [],
|
||||
"warning": "No associated fiorygi table"
|
||||
}
|
||||
fiorygi: Fiorygi = user.fiorygi
|
||||
transactions: ru.JSON = sorted(fiorygi.transactions, key=lambda t: -t.id)
|
||||
return {
|
||||
"fiorygi": fiorygi.fiorygi,
|
||||
"transactions": list(map(lambda t: {
|
||||
"id": t.id,
|
||||
"change": t.change,
|
||||
"reason": t.reason,
|
||||
"timestamp": t.timestamp.isoformat() if t.timestamp else None
|
||||
}, transactions))
|
||||
}
|
63
royalpack/stars/api_poll.py
Normal file
63
royalpack/stars/api_poll.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
from typing import *
|
||||
import datetime
|
||||
import uuid
|
||||
import royalnet.utils as ru
|
||||
import royalnet.constellation.api as rca
|
||||
from ..tables import Poll
|
||||
|
||||
|
||||
class ApiPollStar(rca.ApiStar):
|
||||
path = "/api/poll/v2"
|
||||
|
||||
parameters = {
|
||||
"get": {
|
||||
"uuid": "The UUID of the poll to get.",
|
||||
},
|
||||
"post": {
|
||||
"question": "The question to ask in the poll.",
|
||||
"description": "A longer Markdown-formatted description.",
|
||||
"expires": "A ISO timestamp of the expiration date for the poll.",
|
||||
}
|
||||
}
|
||||
|
||||
auth = {
|
||||
"get": False,
|
||||
"post": True
|
||||
}
|
||||
|
||||
tags = ["poll"]
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Get a specific poll."""
|
||||
PollT = self.alchemy.get(Poll)
|
||||
|
||||
try:
|
||||
pid = uuid.UUID(data["uuid"])
|
||||
except (ValueError, AttributeError, TypeError):
|
||||
raise rca.InvalidParameterError("'uuid' is not a valid UUID.")
|
||||
|
||||
poll: Poll = await ru.asyncify(data.session.query(PollT).get, pid)
|
||||
if poll is None:
|
||||
raise rca.NotFoundError("No such page.")
|
||||
|
||||
return poll.json()
|
||||
|
||||
@rca.magic
|
||||
async def post(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Create a new poll."""
|
||||
PollT = self.alchemy.get(Poll)
|
||||
|
||||
poll = PollT(
|
||||
id=uuid.uuid4(),
|
||||
creator=await data.user(),
|
||||
created=datetime.datetime.now(),
|
||||
expires=datetime.datetime.fromisoformat(data["expires"]) if "expires" in data else None,
|
||||
question=data["question"],
|
||||
description=data.get("description"),
|
||||
)
|
||||
|
||||
data.session.add(poll)
|
||||
await data.session_commit()
|
||||
|
||||
return poll.json()
|
25
royalpack/stars/api_poll_list.py
Normal file
25
royalpack/stars/api_poll_list.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from typing import *
|
||||
import royalnet.constellation.api as rca
|
||||
import royalnet.utils as ru
|
||||
from ..tables import Poll
|
||||
|
||||
|
||||
class ApiPollsListStar(rca.ApiStar):
|
||||
path = "/api/poll/list/v2"
|
||||
|
||||
tags = ["poll"]
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Get a list of all polls."""
|
||||
PollT = self.alchemy.get(Poll)
|
||||
|
||||
polls: List[Poll] = await ru.asyncify(data.session.query(PollT).all)
|
||||
|
||||
return list(map(lambda p: {
|
||||
"id": p.id,
|
||||
"question": p.question,
|
||||
"creator": p.creator.json(),
|
||||
"expires": p.expires.isoformat(),
|
||||
"created": p.created.isoformat(),
|
||||
}, polls))
|
38
royalpack/stars/api_user_avatar.py
Normal file
38
royalpack/stars/api_user_avatar.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
import re
|
||||
import royalnet.utils as ru
|
||||
import royalnet.constellation.api as rca
|
||||
|
||||
|
||||
url_validation = re.compile(r'^(?:http|ftp)s?://'
|
||||
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'
|
||||
r'localhost|'
|
||||
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
|
||||
r'(?::\d+)?'
|
||||
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
||||
|
||||
|
||||
class ApiUserAvatarStar(rca.ApiStar):
|
||||
path = "/api/user/avatar/v2"
|
||||
|
||||
parameters = {
|
||||
"put": {
|
||||
"avatar_url": "The url that the user wants to set as avatar."
|
||||
}
|
||||
}
|
||||
|
||||
auth = {
|
||||
"put": True,
|
||||
}
|
||||
|
||||
tags = ["user"]
|
||||
|
||||
@rca.magic
|
||||
async def put(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Set the avatar of current user."""
|
||||
avatar_url = data["avatar_url"]
|
||||
user = await data.user()
|
||||
if not re.match(url_validation, avatar_url):
|
||||
raise rca.InvalidParameterError("avatar_url is not a valid url.")
|
||||
user.avatar_url = avatar_url
|
||||
await data.session_commit()
|
||||
return user.json()
|
|
@ -1,21 +0,0 @@
|
|||
from starlette.requests import Request
|
||||
from starlette.responses import *
|
||||
from royalnet.constellation import *
|
||||
from royalnet.utils import *
|
||||
from royalnet.backpack.tables import *
|
||||
|
||||
|
||||
class ApiUserGetStar(PageStar):
|
||||
path = "/api/user/get/{uid_str}"
|
||||
|
||||
async def page(self, request: Request) -> JSONResponse:
|
||||
uid_str = request.path_params.get("uid_str", "")
|
||||
try:
|
||||
uid = int(uid_str)
|
||||
except (ValueError, TypeError):
|
||||
return shoot(400, "Invalid uid")
|
||||
async with self.alchemy.session_acm() as session:
|
||||
user: User = await asyncify(session.query(self.alchemy.get(User)).get, uid)
|
||||
if user is None:
|
||||
return shoot(404, "No such user")
|
||||
return JSONResponse(user.json())
|
|
@ -1,14 +0,0 @@
|
|||
from starlette.requests import Request
|
||||
from starlette.responses import *
|
||||
from royalnet.constellation import *
|
||||
from royalnet.utils import *
|
||||
from royalnet.backpack.tables import *
|
||||
|
||||
|
||||
class ApiUserListStar(PageStar):
|
||||
path = "/api/user/list"
|
||||
|
||||
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.get(User)).all)
|
||||
return JSONResponse([user.json() for user in users])
|
45
royalpack/stars/api_user_ryg.py
Normal file
45
royalpack/stars/api_user_ryg.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
import royalnet.backpack.tables as rbt
|
||||
import royalnet.constellation.api as rca
|
||||
|
||||
|
||||
class ApiUserRygStar(rca.ApiStar):
|
||||
path = "/api/user/ryg/v2"
|
||||
|
||||
parameters = {
|
||||
"get": {
|
||||
"uid": "(Choose one) The id of the user to get information about.",
|
||||
"alias": "(Choose one) The alias of the user to get information about.",
|
||||
}
|
||||
}
|
||||
|
||||
tags = ["user"]
|
||||
|
||||
async def get_user(self, data: rca.ApiData):
|
||||
uid = data.int("uid", optional=True)
|
||||
alias = data.str("alias", optional=True)
|
||||
|
||||
if uid:
|
||||
user = await rbt.User.find(self.alchemy, data.session, uid)
|
||||
elif alias:
|
||||
user = await rbt.User.find(self.alchemy, data.session, alias)
|
||||
else:
|
||||
raise rca.MissingParameterError("Neither uid or alias were specified.")
|
||||
|
||||
if user is None:
|
||||
raise rca.NotFoundError("No such user.")
|
||||
|
||||
return user
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> dict:
|
||||
"""Get Royalpack information about a user."""
|
||||
user = await self.get_user(data)
|
||||
result = {
|
||||
**user.json(),
|
||||
"bio": user.bio.json() if user.bio is not None else None,
|
||||
"fiorygi": user.fiorygi.fiorygi if user.fiorygi is not None else None,
|
||||
"steam": [steam.json() for steam in user.steam],
|
||||
"leagueoflegends": [leagueoflegends.json() for leagueoflegends in user.leagueoflegends],
|
||||
"trivia": user.trivia_score.json() if user.trivia_score is not None else None
|
||||
}
|
||||
return result
|
23
royalpack/stars/api_user_ryg_list.py
Normal file
23
royalpack/stars/api_user_ryg_list.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from starlette.responses import *
|
||||
import royalnet.utils as ru
|
||||
import royalnet.backpack.tables as rbt
|
||||
import royalnet.constellation.api as rca
|
||||
|
||||
|
||||
class ApiUserRygListStar(rca.ApiStar):
|
||||
path = "/api/user/ryg/list/v1"
|
||||
|
||||
tags = ["user"]
|
||||
|
||||
@rca.magic
|
||||
async def get(self, data: rca.ApiData) -> ru.JSON:
|
||||
"""Get Royalpack information about all user."""
|
||||
users: typing.List[rbt.User] = await ru.asyncify(data.session.query(self.alchemy.get(rbt.User)).all)
|
||||
return [{
|
||||
**user.json(),
|
||||
"bio": user.bio.json() if user.bio is not None else None,
|
||||
"fiorygi": user.fiorygi.fiorygi if user.fiorygi is not None else None,
|
||||
"steam": [steam.json() for steam in user.steam],
|
||||
"leagueoflegends": [leagueoflegends.json() for leagueoflegends in user.leagueoflegends],
|
||||
"trivia": user.trivia_score.json() if user.trivia_score is not None else None
|
||||
} for user in users]
|
|
@ -1,22 +0,0 @@
|
|||
from starlette.requests import Request
|
||||
from starlette.responses import *
|
||||
from royalnet.constellation import *
|
||||
from royalnet.utils import *
|
||||
from ..tables import *
|
||||
import uuid
|
||||
|
||||
|
||||
class ApiWikiGetStar(PageStar):
|
||||
path = "/api/wiki/get/{wiki_page_uuid}"
|
||||
|
||||
async def page(self, request: Request) -> JSONResponse:
|
||||
wiki_page_uuid_str = request.path_params.get("wiki_page_uuid", "")
|
||||
try:
|
||||
wiki_page_uuid = uuid.UUID(wiki_page_uuid_str)
|
||||
except (ValueError, AttributeError, TypeError):
|
||||
return shoot(400, "Invalid wiki_page_uuid")
|
||||
async with self.alchemy.session_acm() as session:
|
||||
wikipage: WikiPage = await asyncify(session.query(self.alchemy.get(WikiPage)).get, wiki_page_uuid)
|
||||
if wikipage is None:
|
||||
return shoot(404, "No such page")
|
||||
return JSONResponse(wikipage.json_full())
|
|
@ -1,15 +0,0 @@
|
|||
from starlette.requests import Request
|
||||
from starlette.responses import *
|
||||
from royalnet.constellation import *
|
||||
from royalnet.utils import *
|
||||
from royalnet.backpack.tables import *
|
||||
from ..tables import *
|
||||
|
||||
|
||||
class ApiUserListStar(PageStar):
|
||||
path = "/api/wiki/list"
|
||||
|
||||
async def page(self, request: Request) -> JSONResponse:
|
||||
async with self.alchemy.session_acm() as session:
|
||||
pages: typing.List[WikiPage] = await asyncify(session.query(self.alchemy.get(WikiPage)).all)
|
||||
return JSONResponse([page.json_list() for page in pages])
|
|
@ -1,29 +1,47 @@
|
|||
# Imports go here!
|
||||
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
|
||||
from .fiorygi import Fiorygi
|
||||
from .steam import Steam
|
||||
from .dota import Dota
|
||||
from .fiorygitransactions import FiorygiTransaction
|
||||
from .brawlhalla import Brawlhalla
|
||||
from .polls import Poll
|
||||
from .pollcomments import PollComment
|
||||
from .pollvotes import PollVote
|
||||
from .brawlhalladuos import BrawlhallaDuo
|
||||
from .mmevents import MMEvent
|
||||
from .mmresponse import MMResponse
|
||||
from .cvstats import Cvstats
|
||||
from .treasure import Treasure
|
||||
from .osu import Osu
|
||||
|
||||
# Enter the tables of your Pack here!
|
||||
available_tables = [
|
||||
Diario,
|
||||
Alias,
|
||||
WikiPage,
|
||||
WikiRevision,
|
||||
Bio,
|
||||
Reminder,
|
||||
TriviaScore,
|
||||
MMEvent,
|
||||
MMResponse,
|
||||
LeagueOfLegends,
|
||||
Fiorygi,
|
||||
Steam,
|
||||
Dota,
|
||||
FiorygiTransaction,
|
||||
Brawlhalla,
|
||||
Poll,
|
||||
PollComment,
|
||||
PollVote,
|
||||
BrawlhallaDuo,
|
||||
MMEvent,
|
||||
MMResponse,
|
||||
Cvstats,
|
||||
Treasure,
|
||||
Osu,
|
||||
]
|
||||
|
||||
# Don't change this, it should automatically generate __all__
|
||||
|
|
|
@ -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"<Alias {str(self)}>"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.alias}->{self.royal_id}"
|
|
@ -10,19 +10,24 @@ class Bio:
|
|||
__tablename__ = "bios"
|
||||
|
||||
@declared_attr
|
||||
def royal_id(self):
|
||||
def user_id(self):
|
||||
return Column(Integer, ForeignKey("users.uid"), primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def royal(self):
|
||||
def user(self):
|
||||
return relationship("User", backref=backref("bio", uselist=False))
|
||||
|
||||
@declared_attr
|
||||
def contents(self):
|
||||
return Column(Text, nullable=False, default="")
|
||||
|
||||
def json(self) -> dict:
|
||||
return {
|
||||
"contents": self.contents
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Bio of {self.royal}>"
|
||||
return f"<Bio of {self.user}>"
|
||||
|
||||
def __str__(self):
|
||||
return self.contents
|
||||
|
|
110
royalpack/tables/brawlhalla.py
Normal file
110
royalpack/tables/brawlhalla.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
import steam.steamid
|
||||
from ..types import BrawlhallaRank, BrawlhallaTier, BrawlhallaMetal, Updatable
|
||||
|
||||
|
||||
# noinspection PyAttributeOutsideInit
|
||||
class Brawlhalla(Updatable):
|
||||
__tablename__ = "brawlhalla"
|
||||
|
||||
@declared_attr
|
||||
def brawlhalla_id(self):
|
||||
return Column(Integer, primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def _steamid(self):
|
||||
return Column(BigInteger, ForeignKey("steam._steamid"), unique=True)
|
||||
|
||||
@declared_attr
|
||||
def steam(self):
|
||||
return relationship("Steam", backref=backref("brawlhalla", uselist=False))
|
||||
|
||||
@property
|
||||
def steamid(self):
|
||||
return steam.steamid.SteamID(self._steamid)
|
||||
|
||||
@declared_attr
|
||||
def name(self):
|
||||
return Column(String, nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def rating_1v1(self):
|
||||
return Column(Integer)
|
||||
|
||||
@declared_attr
|
||||
def tier_1v1(self):
|
||||
return Column(Enum(BrawlhallaTier))
|
||||
|
||||
@declared_attr
|
||||
def metal_1v1(self):
|
||||
return Column(Enum(BrawlhallaMetal))
|
||||
|
||||
@property
|
||||
def rank_1v1(self):
|
||||
if self.metal_1v1 is None:
|
||||
return None
|
||||
return BrawlhallaRank(metal=self.metal_1v1, tier=self.tier_1v1)
|
||||
|
||||
@rank_1v1.setter
|
||||
def rank_1v1(self, value):
|
||||
if not isinstance(value, BrawlhallaRank):
|
||||
raise TypeError("rank_1v1 can only be set to BrawlhallaRank values.")
|
||||
self.metal_1v1 = value.metal
|
||||
self.tier_1v1 = value.tier
|
||||
|
||||
@property
|
||||
def duos(self):
|
||||
return [*self._duos_one, *self._duos_two]
|
||||
|
||||
@property
|
||||
def rating_2v2(self):
|
||||
duos = sorted(self.duos, key=lambda d: -d.rating_2v2)
|
||||
if len(duos) == 0:
|
||||
return None
|
||||
return duos[0].rating_2v2
|
||||
|
||||
@property
|
||||
def tier_2v2(self):
|
||||
duos = sorted(self.duos, key=lambda d: -d.rating_2v2)
|
||||
if len(duos) == 0:
|
||||
return None
|
||||
return duos[0].tier_2v2
|
||||
|
||||
@property
|
||||
def metal_2v2(self):
|
||||
duos = sorted(self.duos, key=lambda d: -d.rating_2v2)
|
||||
if len(duos) == 0:
|
||||
return None
|
||||
return duos[0].metal_2v2
|
||||
|
||||
@property
|
||||
def rank_2v2(self):
|
||||
duos = sorted(self.duos, key=lambda d: -d.rating_2v2)
|
||||
if len(duos) == 0:
|
||||
return None
|
||||
return duos[0].rank_2v2
|
||||
|
||||
def json(self):
|
||||
one_rank = self.rank_1v1
|
||||
two_rank = self.rank_2v2
|
||||
return {
|
||||
"name": self.name,
|
||||
"1v1": {
|
||||
"rating": self.rating_1v1,
|
||||
"metal": one_rank.metal.name,
|
||||
"tier": one_rank.tier.name
|
||||
} if one_rank is not None else None,
|
||||
"2v2": {
|
||||
"rating": self.rating_2v2,
|
||||
"metal": two_rank.metal.name,
|
||||
"tier": two_rank.tier.name
|
||||
} if two_rank is not None else None
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Brawlhalla account {self._steamid}>"
|
||||
|
||||
def __str__(self):
|
||||
return f"[c]brawlhalla:{self.brawlhalla_id}[/c]"
|
58
royalpack/tables/brawlhalladuos.py
Normal file
58
royalpack/tables/brawlhalladuos.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from ..types import BrawlhallaRank, BrawlhallaTier, BrawlhallaMetal
|
||||
|
||||
|
||||
class BrawlhallaDuo:
|
||||
__tablename__ = "brawlhalladuos"
|
||||
|
||||
@declared_attr
|
||||
def id_one(self):
|
||||
return Column(Integer, ForeignKey("brawlhalla.brawlhalla_id"), primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def id_two(self):
|
||||
return Column(Integer, ForeignKey("brawlhalla.brawlhalla_id"), primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def one(self):
|
||||
return relationship("Brawlhalla", foreign_keys=self.id_one, backref=backref("_duos_one"))
|
||||
|
||||
@declared_attr
|
||||
def two(self):
|
||||
return relationship("Brawlhalla", foreign_keys=self.id_two, backref=backref("_duos_two"))
|
||||
|
||||
@declared_attr
|
||||
def rating_2v2(self):
|
||||
return Column(Integer)
|
||||
|
||||
@declared_attr
|
||||
def tier_2v2(self):
|
||||
return Column(Enum(BrawlhallaTier))
|
||||
|
||||
@declared_attr
|
||||
def metal_2v2(self):
|
||||
return Column(Enum(BrawlhallaMetal))
|
||||
|
||||
@property
|
||||
def rank_2v2(self):
|
||||
return BrawlhallaRank(metal=self.metal_2v2, tier=self.tier_2v2)
|
||||
|
||||
@rank_2v2.setter
|
||||
def rank_2v2(self, value):
|
||||
if not isinstance(value, BrawlhallaRank):
|
||||
raise TypeError("rank_1v1 can only be set to BrawlhallaRank values.")
|
||||
self.metal_2v2 = value.metal
|
||||
self.tier_2v2 = value.tier
|
||||
|
||||
def other(self, bh):
|
||||
if bh == self.one:
|
||||
return self.two
|
||||
elif bh == self.two:
|
||||
return self.one
|
||||
else:
|
||||
raise ValueError("Argument is unrelated to this duo.")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<BrawlhallaDuo {self.id_one} & {self.id_two}>"
|
60
royalpack/tables/cvstats.py
Normal file
60
royalpack/tables/cvstats.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
|
||||
|
||||
class Cvstats:
|
||||
__tablename__ = "cvstats"
|
||||
|
||||
@declared_attr
|
||||
def id(self):
|
||||
return Column(Integer, primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def timestamp(self):
|
||||
return Column(DateTime)
|
||||
|
||||
@declared_attr
|
||||
def members_connected(self):
|
||||
return Column(Integer)
|
||||
|
||||
@declared_attr
|
||||
def users_connected(self):
|
||||
return Column(Integer)
|
||||
|
||||
@declared_attr
|
||||
def members_online(self):
|
||||
return Column(Integer)
|
||||
|
||||
@declared_attr
|
||||
def users_online(self):
|
||||
return Column(Integer)
|
||||
|
||||
@declared_attr
|
||||
def members_playing(self):
|
||||
return Column(Integer)
|
||||
|
||||
@declared_attr
|
||||
def users_playing(self):
|
||||
return Column(Integer)
|
||||
|
||||
@declared_attr
|
||||
def members_total(self):
|
||||
return Column(Integer)
|
||||
|
||||
@declared_attr
|
||||
def users_total(self):
|
||||
return Column(Integer)
|
||||
|
||||
def json(self):
|
||||
return {
|
||||
"timestamp": self.timestamp.isoformat(),
|
||||
"users_total": self.users_total,
|
||||
"members_total": self.members_total,
|
||||
"users_online": self.users_online,
|
||||
"members_online": self.members_online,
|
||||
"users_connected": self.users_connected,
|
||||
"members_connected": self.members_connected,
|
||||
"users_playing": self.users_playing,
|
||||
"members_playing": self.members_playing,
|
||||
}
|
|
@ -88,7 +88,7 @@ class Diario:
|
|||
text += f" 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"
|
||||
text += f"[url={self.media_url}]Media[/url]\n"
|
||||
if self.text is not None:
|
||||
if self.spoiler:
|
||||
hidden = re.sub(r"\w", "█", self.text)
|
||||
|
|
92
royalpack/tables/dota.py
Normal file
92
royalpack/tables/dota.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
from typing import *
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship, backref
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from ..types import DotaMedal, DotaStars, DotaRank, Updatable
|
||||
import steam.steamid
|
||||
|
||||
|
||||
class Dota(Updatable):
|
||||
__tablename__ = "dota"
|
||||
|
||||
@declared_attr
|
||||
def _steamid(self):
|
||||
return Column(BigInteger, ForeignKey("steam._steamid"), primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def steam(self):
|
||||
return relationship("Steam", backref=backref("dota", uselist=False))
|
||||
|
||||
@property
|
||||
def steamid(self):
|
||||
return steam.steamid.SteamID(self._steamid)
|
||||
|
||||
@declared_attr
|
||||
def _rank_tier(self):
|
||||
return Column(Integer)
|
||||
|
||||
@property
|
||||
def medal(self) -> Optional[DotaMedal]:
|
||||
if self._rank_tier is None:
|
||||
return None
|
||||
return DotaMedal(self._rank_tier // 10)
|
||||
|
||||
@medal.setter
|
||||
def medal(self, value: DotaMedal):
|
||||
if not isinstance(value, DotaMedal):
|
||||
raise AttributeError("medal can only be set to DotaMedal objects.")
|
||||
self._rank_tier = value.value * 10 + self.stars.value
|
||||
|
||||
@property
|
||||
def stars(self) -> Optional[DotaStars]:
|
||||
if self._rank_tier is None:
|
||||
return None
|
||||
return DotaStars(self._rank_tier % 10)
|
||||
|
||||
@stars.setter
|
||||
def stars(self, value: DotaStars):
|
||||
if not isinstance(value, DotaStars):
|
||||
raise AttributeError("stars can only be set to DotaStars objects.")
|
||||
self._rank_tier = self.medal.value * 10 + value.value
|
||||
|
||||
@property
|
||||
def rank(self) -> Optional[DotaRank]:
|
||||
if self._rank_tier is None:
|
||||
return None
|
||||
return DotaRank(self.medal, self.stars)
|
||||
|
||||
@rank.setter
|
||||
def rank(self, value: Optional[DotaRank]):
|
||||
if value is None:
|
||||
self._rank_tier = None
|
||||
return
|
||||
if not isinstance(value, DotaRank):
|
||||
raise AttributeError("rank can only be set to DotaRank objects (or None).")
|
||||
self._rank_tier = value.rank_tier
|
||||
|
||||
@declared_attr
|
||||
def wins(self):
|
||||
return Column(Integer)
|
||||
|
||||
@declared_attr
|
||||
def losses(self):
|
||||
return Column(Integer)
|
||||
|
||||
def json(self):
|
||||
rank = self.rank
|
||||
|
||||
return {
|
||||
"rank": {
|
||||
"raw": self._rank_tier,
|
||||
"medal": rank.medal.name,
|
||||
"rank": rank.stars.name
|
||||
} if self._rank_tier is not None else None,
|
||||
"wins": self.wins,
|
||||
"losses": self.losses
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Dota account {self._steamid}>"
|
||||
|
||||
def __str__(self):
|
||||
return f"[c]dota:{self._steamid}[/c]"
|
|
@ -19,7 +19,7 @@ class Fiorygi:
|
|||
return Column(Integer, nullable=False, default=0)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Fiorygi di {self.royal}: {self.fiorygi}>"
|
||||
return f"<{self.__class__.__name__} di {self.user}: {self.fiorygi}>"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.fiorygi} fioryg" + ("i" if self.fiorygi != 1 else "")
|
||||
|
|
82
royalpack/tables/fiorygitransactions.py
Normal file
82
royalpack/tables/fiorygitransactions.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
from typing import *
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
|
||||
from .fiorygi import Fiorygi
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from royalnet.commands import CommandData
|
||||
|
||||
|
||||
class FiorygiTransaction:
|
||||
__tablename__ = "fiorygitransactions"
|
||||
|
||||
@declared_attr
|
||||
def id(self):
|
||||
return Column(Integer, primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def change(self):
|
||||
return Column(Integer, nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def user_id(self):
|
||||
return Column(Integer, ForeignKey("fiorygi.user_id"), nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def wallet(self):
|
||||
return relationship("Fiorygi", backref=backref("transactions"))
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
return self.wallet.user
|
||||
|
||||
@declared_attr
|
||||
def reason(self):
|
||||
return Column(String, nullable=False, default="")
|
||||
|
||||
@declared_attr
|
||||
def timestamp(self):
|
||||
return Column(DateTime)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}: {self.change:+} to {self.user.username} for {self.reason}>"
|
||||
|
||||
@classmethod
|
||||
async def spawn_fiorygi(cls, data: "CommandData", user, qty: int, reason: str):
|
||||
if user.fiorygi is None:
|
||||
data.session.add(data._interface.alchemy.get(Fiorygi)(
|
||||
user_id=user.uid,
|
||||
fiorygi=0
|
||||
))
|
||||
await data.session_commit()
|
||||
|
||||
transaction = data._interface.alchemy.get(FiorygiTransaction)(
|
||||
user_id=user.uid,
|
||||
change=qty,
|
||||
reason=reason,
|
||||
timestamp=datetime.datetime.now()
|
||||
)
|
||||
data.session.add(transaction)
|
||||
|
||||
user.fiorygi.fiorygi += qty
|
||||
await data.session_commit()
|
||||
|
||||
if len(user.telegram) > 0:
|
||||
user_str = user.telegram[0].mention()
|
||||
else:
|
||||
user_str = user.username
|
||||
|
||||
if qty > 0:
|
||||
msg = f"💰 [b]{user_str}[/b] ha ottenuto [b]{qty}[/b] fioryg{'i' if qty != 1 else ''} per [i]{reason}[/i]!"
|
||||
elif qty == 0:
|
||||
msg = f"❓ [b]{user_str}[/b] ha mantenuto i suoi fiorygi attuali per [i]{reason}[/i].\nWait, cosa?"
|
||||
else:
|
||||
msg = f"💸 [b]{user_str}[/b] ha perso [b]{-qty}[/b] fioryg{'i' if qty != -1 else ''} per [i]{reason}[/i]."
|
||||
|
||||
await data._interface.call_herald_event("telegram", "telegram_message",
|
||||
chat_id=data._interface.config["Telegram"]["main_group_id"],
|
||||
text=msg)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue