1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 11:34:18 +00:00

what a mess

This commit is contained in:
Steffo 2019-11-18 19:44:30 +01:00
parent 7bbf7f334b
commit b2d5039c76
9 changed files with 178 additions and 94 deletions

View file

@ -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",
]

View file

@ -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.")

View 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]

View 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}")

View file

View 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"
]

View 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()

View file

@ -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):

View file

@ -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):