mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
what a mess
This commit is contained in:
parent
7bbf7f334b
commit
b2d5039c76
9 changed files with 178 additions and 94 deletions
|
@ -1,16 +1,19 @@
|
|||
"""A Pack that is imported by default by all :mod:`royalnet` instances."""
|
||||
|
||||
from . import commands, tables, stars
|
||||
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
|
||||
|
||||
__all__ = [
|
||||
"commands",
|
||||
"tables",
|
||||
"stars",
|
||||
"events",
|
||||
"available_commands",
|
||||
"available_tables",
|
||||
"available_page_stars",
|
||||
"available_exception_stars",
|
||||
"available_events",
|
||||
]
|
||||
|
|
|
@ -12,6 +12,8 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class SummonCommand(Command):
|
||||
# TODO: possibly move this in another pack
|
||||
|
||||
name: str = "summon"
|
||||
|
||||
description = "Connect the bot to a Discord voice channel."
|
||||
|
@ -19,89 +21,13 @@ class SummonCommand(Command):
|
|||
syntax = "[channelname]"
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
# This command only runs on Discord!
|
||||
if self.interface.name != "discord":
|
||||
# TODO: use a Herald Event to remotely connect the bot
|
||||
raise UnsupportedError()
|
||||
if discord is None:
|
||||
raise ConfigurationError("'discord' extra is not installed.")
|
||||
# noinspection PyUnresolvedReferences
|
||||
message: discord.Message = data.message
|
||||
member: Union[discord.User, discord.Member] = message.author
|
||||
serf: DiscordSerf = self.interface.serf
|
||||
client: discord.Client = serf.client
|
||||
channel_name: Optional[str] = args.joined()
|
||||
|
||||
# If the channel name was passed as an argument...
|
||||
if channel_name != "":
|
||||
# Try to find all possible channels
|
||||
channels: List[discord.VoiceChannel] = []
|
||||
for ch in client.get_all_channels():
|
||||
guild: discord.Guild = ch.guild
|
||||
# Ensure the channel is a voice channel
|
||||
if not isinstance(ch, discord.VoiceChannel):
|
||||
continue
|
||||
# Ensure the channel starts with the requested name
|
||||
ch_name: str = ch.name
|
||||
if not ch_name.startswith(channel_name):
|
||||
continue
|
||||
# Ensure that the command author can access the channel
|
||||
if guild.get_member(member.id) is None:
|
||||
continue
|
||||
member_permissions: discord.Permissions = ch.permissions_for(member)
|
||||
if not (member_permissions.connect and member_permissions.speak):
|
||||
continue
|
||||
# Ensure that the bot can access the channel
|
||||
bot_member = guild.get_member(client.user.id)
|
||||
bot_permissions: discord.Permissions = ch.permissions_for(bot_member)
|
||||
if not (bot_permissions.connect and bot_permissions.speak):
|
||||
continue
|
||||
# Found one!
|
||||
channels.append(ch)
|
||||
|
||||
# Ensure at least a single channel is returned
|
||||
if len(channels) == 0:
|
||||
raise InvalidInputError("Could not find any channel to connect to.")
|
||||
elif len(channels) == 1:
|
||||
channel = channels[0]
|
||||
else:
|
||||
# Give priority to channels in the current guild
|
||||
filter_by_guild = False
|
||||
for ch in channels:
|
||||
if ch.guild == message.guild:
|
||||
filter_by_guild = True
|
||||
break
|
||||
if filter_by_guild:
|
||||
new_channels = []
|
||||
for ch in channels:
|
||||
if ch.guild == message.guild:
|
||||
new_channels.append(ch)
|
||||
channels = new_channels
|
||||
|
||||
# Give priority to channels with the most people
|
||||
def people_count(c: discord.VoiceChannel):
|
||||
return len(c.members)
|
||||
channels.sort(key=people_count, reverse=True)
|
||||
|
||||
channel = channels[0]
|
||||
|
||||
else:
|
||||
# Try to use the channel in which the command author is in
|
||||
voice: Optional[discord.VoiceState] = message.author.voice
|
||||
if voice is None:
|
||||
raise UserError("You must be connected to a voice channel to summon the bot without any arguments.")
|
||||
channel: discord.VoiceChannel = voice.channel
|
||||
|
||||
# Try to connect to the voice channel
|
||||
try:
|
||||
await channel.connect()
|
||||
except asyncio.TimeoutError:
|
||||
raise ExternalError("Timed out while trying to connect to the channel")
|
||||
except discord.opus.OpusNotLoaded:
|
||||
raise ConfigurationError("[c]libopus[/c] is not loaded in the serf")
|
||||
except discord.ClientException as e:
|
||||
# The bot is already connected to a voice channel
|
||||
# TODO: safely move the bot somewhere else
|
||||
raise CommandError("The bot is already connected in another channel.")
|
||||
|
||||
await data.reply(f"✅ Connected to <#{channel.id}>!")
|
||||
await self.interface.call_herald_action("discord", "discordvoice", {
|
||||
"operation": "summon",
|
||||
"data": {
|
||||
"channel_name": args.joined()
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
breakpoint()
|
||||
await data.reply(f"✅ Connesso alla chat vocale.")
|
10
royalnet/backpack/events/__init__.py
Normal file
10
royalnet/backpack/events/__init__.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Imports go here!
|
||||
|
||||
|
||||
# Enter the commands of your Pack here!
|
||||
available_events = [
|
||||
|
||||
]
|
||||
|
||||
# Don't change this, it should automatically generate __all__
|
||||
__all__ = [command.__name__ for command in available_events]
|
113
royalnet/backpack/events/discordvoice.py
Normal file
113
royalnet/backpack/events/discordvoice.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
import asyncio
|
||||
from typing import Dict, List, Optional
|
||||
from royalnet.commands import *
|
||||
from royalnet.serf import Serf
|
||||
from royalnet.serf.discord import DiscordSerf
|
||||
from royalnet.bard import DiscordBard
|
||||
from royalnet.bard.implementations import *
|
||||
|
||||
try:
|
||||
import discord
|
||||
except ImportError:
|
||||
discord = None
|
||||
|
||||
|
||||
class DiscordvoiceEvent(Event):
|
||||
name: str = "discordvoice"
|
||||
|
||||
def __init__(self, serf: Serf):
|
||||
super().__init__(serf)
|
||||
self.bards: Dict["discord.Guild", DiscordBard] = {}
|
||||
|
||||
async def run(self, data: dict):
|
||||
if not isinstance(self.serf, DiscordSerf):
|
||||
raise ValueError("`discordvoice` event cannot run on other serfs.")
|
||||
|
||||
operation = data["operation"]
|
||||
|
||||
if operation == "summon":
|
||||
channel_name: str = data["data"]["channel_name"]
|
||||
member_id: int = data["data"].get("member_id")
|
||||
guild_id: int = data["data"].get("guild_id")
|
||||
client: discord.Client = self.serf.client
|
||||
|
||||
# Get the guild, if it exists
|
||||
if guild_id is not None:
|
||||
guild: Optional[discord.Guild] = client.get_guild(guild_id)
|
||||
else:
|
||||
guild = None
|
||||
|
||||
# Get the member, if it exists
|
||||
if member_id is not None and guild is not None:
|
||||
member: Optional[discord.Member] = guild.get_member(member_id)
|
||||
else:
|
||||
member = None
|
||||
|
||||
# Try to find all possible channels
|
||||
channels: List[discord.VoiceChannel] = []
|
||||
for ch in client.get_all_channels():
|
||||
guild: discord.Guild = ch.guild
|
||||
# Ensure the channel is a voice channel
|
||||
if not isinstance(ch, discord.VoiceChannel):
|
||||
continue
|
||||
# Ensure the channel starts with the requested name
|
||||
ch_name: str = ch.name
|
||||
if not ch_name.startswith(channel_name):
|
||||
continue
|
||||
# Ensure that the command author can access the channel
|
||||
if member is not None:
|
||||
member_permissions: discord.Permissions = ch.permissions_for(member)
|
||||
if not (member_permissions.connect and member_permissions.speak):
|
||||
continue
|
||||
# Ensure that the bot can access the channel
|
||||
bot_member = guild.get_member(client.user.id)
|
||||
bot_permissions: discord.Permissions = ch.permissions_for(bot_member)
|
||||
if not (bot_permissions.connect and bot_permissions.speak):
|
||||
continue
|
||||
# Found one!
|
||||
channels.append(ch)
|
||||
|
||||
# Ensure at least a single channel is returned
|
||||
if len(channels) == 0:
|
||||
raise InvalidInputError("Could not find any channel to connect to.")
|
||||
else:
|
||||
# Give priority to channels in the current guild
|
||||
filter_by_guild = False
|
||||
for ch in channels:
|
||||
if ch.guild == guild:
|
||||
filter_by_guild = True
|
||||
break
|
||||
if filter_by_guild:
|
||||
new_channels = []
|
||||
for ch in channels:
|
||||
if ch.guild == guild:
|
||||
new_channels.append(ch)
|
||||
channels = new_channels
|
||||
|
||||
# Give priority to channels with the most people
|
||||
def people_count(c: discord.VoiceChannel):
|
||||
return len(c.members)
|
||||
|
||||
channels.sort(key=people_count, reverse=True)
|
||||
|
||||
channel = channels[0]
|
||||
|
||||
# Try to connect to the voice channel
|
||||
try:
|
||||
await channel.connect()
|
||||
except asyncio.TimeoutError:
|
||||
raise ExternalError("Timed out while trying to connect to the channel")
|
||||
except discord.opus.OpusNotLoaded:
|
||||
raise ConfigurationError("[c]libopus[/c] is not loaded in the serf")
|
||||
except discord.ClientException:
|
||||
# The bot is already connected to a voice channel
|
||||
# TODO: safely move the bot somewhere else
|
||||
raise CommandError("The bot is already connected in another channel.\n"
|
||||
" Please disconnect it before resummoning!")
|
||||
|
||||
|
||||
return {
|
||||
"connected": True
|
||||
}
|
||||
else:
|
||||
raise ValueError(f"Invalid operation received: {operation}")
|
0
royalnet/backpack/utils/__init__.py
Normal file
0
royalnet/backpack/utils/__init__.py
Normal file
|
@ -2,6 +2,7 @@ from .commandinterface import CommandInterface
|
|||
from .command import Command
|
||||
from .commanddata import CommandData
|
||||
from .commandargs import CommandArgs
|
||||
from .event import Event
|
||||
from .errors import CommandError, \
|
||||
InvalidInputError, \
|
||||
UnsupportedError, \
|
||||
|
@ -20,4 +21,5 @@ __all__ = [
|
|||
"ConfigurationError",
|
||||
"ExternalError",
|
||||
"UserError",
|
||||
"Event"
|
||||
]
|
||||
|
|
30
royalnet/commands/event.py
Normal file
30
royalnet/commands/event.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from serf import Serf
|
||||
|
||||
|
||||
class Event:
|
||||
"""A remote procedure call triggered by a :mod:`royalnet.herald` request."""
|
||||
|
||||
name = NotImplemented
|
||||
"""The event_name that will trigger this event."""
|
||||
|
||||
tables: set = set()
|
||||
"""A set of :mod:`royalnet.alchemy` tables that must exist for this event to work."""
|
||||
|
||||
def __init__(self, serf: Serf):
|
||||
"""Bind the event to a :class:`~royalnet.serf.Serf`."""
|
||||
self.serf: Serf = serf
|
||||
|
||||
@property
|
||||
def alchemy(self):
|
||||
"""A shortcut for :attr:`.serf.alchemy`."""
|
||||
return self.serf.alchemy
|
||||
|
||||
@property
|
||||
def loop(self):
|
||||
"""A shortcut for :attr:`.serf.loop`"""
|
||||
return self.serf.loop
|
||||
|
||||
async def run(self, data: dict):
|
||||
raise NotImplementedError()
|
|
@ -43,7 +43,7 @@ class DiscordSerf(Serf):
|
|||
network_config=network_config,
|
||||
secrets_name=secrets_name)
|
||||
|
||||
self.Client = self.bot_factory()
|
||||
self.Client = self.client_factory()
|
||||
"""The custom :class:`discord.Client` class that will be instantiated later."""
|
||||
|
||||
self.client = self.Client()
|
||||
|
@ -129,7 +129,7 @@ class DiscordSerf(Serf):
|
|||
if session is not None:
|
||||
await asyncify(session.close)
|
||||
|
||||
def bot_factory(self) -> Type["discord.Client"]:
|
||||
def client_factory(self) -> Type["discord.Client"]:
|
||||
"""Create a custom class inheriting from :py:class:`discord.Client`."""
|
||||
# noinspection PyMethodParameters
|
||||
class DiscordClient(discord.Client):
|
||||
|
|
|
@ -235,27 +235,27 @@ class Serf:
|
|||
|
||||
async def network_handler(self, message: Union[Request, Broadcast]) -> Response:
|
||||
try:
|
||||
network_handler = self.herald_handlers[message.handler]
|
||||
herald_handler = self.herald_handlers[message.handler]
|
||||
except KeyError:
|
||||
log.warning(f"Missing network_handler for {message.handler}")
|
||||
return ResponseFailure("no_handler", f"This bot is missing a network handler for {message.handler}.")
|
||||
else:
|
||||
log.debug(f"Using {network_handler} as handler for {message.handler}")
|
||||
log.debug(f"Using {herald_handler} as handler for {message.handler}")
|
||||
if isinstance(message, Request):
|
||||
try:
|
||||
response_data = await network_handler(self, **message.data)
|
||||
response_data = await herald_handler(self, **message.data)
|
||||
return ResponseSuccess(data=response_data)
|
||||
except Exception as e:
|
||||
sentry_sdk.capture_exception(e)
|
||||
log.error(f"Exception {e} in {network_handler}")
|
||||
log.error(f"Exception {e} in {herald_handler}")
|
||||
return ResponseFailure("exception_in_handler",
|
||||
f"An exception was raised in {network_handler} for {message.handler}.",
|
||||
f"An exception was raised in {herald_handler} for {message.handler}.",
|
||||
extra_info={
|
||||
"type": e.__class__.__name__,
|
||||
"message": str(e)
|
||||
})
|
||||
elif isinstance(message, Broadcast):
|
||||
await network_handler(self, **message.data)
|
||||
await herald_handler(self, **message.data)
|
||||
|
||||
@staticmethod
|
||||
def init_sentry(dsn):
|
||||
|
|
Loading…
Reference in a new issue