mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
stuff
This commit is contained in:
parent
71076a39a9
commit
bf70ceafe7
19 changed files with 121 additions and 234 deletions
|
@ -6,8 +6,10 @@ Here are some things that were found out while developing the bot.
|
||||||
Discord websocket undocumented error codes
|
Discord websocket undocumented error codes
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
====== ===================
|
====== =====================
|
||||||
Code Reason
|
Code Reason
|
||||||
====== ===================
|
====== =====================
|
||||||
1006 Heartbeat stopped
|
1006 Heartbeat stopped
|
||||||
====== ===================
|
------ ---------------------
|
||||||
|
1006 Failed authentication
|
||||||
|
====== =====================
|
||||||
|
|
|
@ -12,7 +12,3 @@ class NotFoundError(YtdlError):
|
||||||
|
|
||||||
class MultipleFilesError(YtdlError):
|
class MultipleFilesError(YtdlError):
|
||||||
"""The resource contains multiple media files."""
|
"""The resource contains multiple media files."""
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedError(BardError):
|
|
||||||
"""The method you tried to call on a :class:`DiscordBard` is not supported on that particular Bard."""
|
|
||||||
|
|
|
@ -3,9 +3,8 @@ import re
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from royalnet.utils import asyncify, MultiLock
|
from royalnet.utils import asyncify, MultiLock, FileAudioSource
|
||||||
from royalnet.bard import YtdlInfo, YtdlFile
|
from royalnet.bard import YtdlInfo, YtdlFile
|
||||||
from .fileaudiosource import FileAudioSource
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ffmpeg
|
import ffmpeg
|
|
@ -39,6 +39,8 @@ class CommandInterface:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.command: Optional[Command] = None # Will be bound after the command has been created
|
self.command: Optional[Command] = None # Will be bound after the command has been created
|
||||||
|
|
||||||
async def call_herald_event(self, destination: str, event_name: str, args: dict) -> dict:
|
async def call_herald_event(self, destination: str, event_name: str, **kwargs) -> dict:
|
||||||
# TODO: document this
|
"""Call an event function on a different :class:`Serf`.
|
||||||
|
|
||||||
|
For example, you can run a function on a :class:`DiscordSerf` from a :class:`TelegramSerf`."""
|
||||||
raise UnsupportedError(f"{self.call_herald_event.__name__} is not supported on this platform")
|
raise UnsupportedError(f"{self.call_herald_event.__name__} is not supported on this platform")
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
from .serf import Serf
|
from .serf import Serf
|
||||||
from .alchemyconfig import AlchemyConfig
|
from .alchemyconfig import AlchemyConfig
|
||||||
|
from .errors import SerfError
|
||||||
from . import telegram, discord
|
from . import telegram, discord
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Serf",
|
"Serf",
|
||||||
"AlchemyConfig",
|
"AlchemyConfig",
|
||||||
|
"SerfError",
|
||||||
"telegram",
|
"telegram",
|
||||||
"discord",
|
"discord",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
|
|
||||||
class AlchemyConfig:
|
class AlchemyConfig:
|
||||||
"""A helper class to configure :class:`Alchemy` in a :class:`Serf`."""
|
"""A helper class to configure :class:`Alchemy` in a :class:`Serf`."""
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
from typing import Dict, Any
|
|
||||||
from .discordbard import DiscordBard
|
|
||||||
|
|
||||||
try:
|
|
||||||
import discord
|
|
||||||
except ImportError:
|
|
||||||
discord = None
|
|
||||||
|
|
||||||
|
|
||||||
class BardsDict:
|
|
||||||
def __init__(self, client: "discord.Client"):
|
|
||||||
if discord is None:
|
|
||||||
raise ImportError("'discord' extra is not installed.")
|
|
||||||
self.client: "discord.Client" = client
|
|
||||||
self._dict: Dict["discord.Guild", DiscordBard] = dict()
|
|
||||||
|
|
||||||
def __getitem__(self, item: "discord.Guild") -> DiscordBard:
|
|
||||||
bard = self._dict[item]
|
|
||||||
if bard.voice_client not in self.client.voice_clients:
|
|
||||||
del self._dict[item]
|
|
||||||
raise KeyError("Requested bard is disconnected and was removed from the dict.")
|
|
||||||
return bard
|
|
||||||
|
|
||||||
def __setitem__(self, key: "discord.Guild", value):
|
|
||||||
if not isinstance(value, DiscordBard):
|
|
||||||
raise TypeError(f"Cannot __setitem__ with {value.__class__.__name__}.")
|
|
||||||
self._dict[key] = value
|
|
||||||
|
|
||||||
def get(self, item: "discord.Guild", default: Any = None) -> Any:
|
|
||||||
try:
|
|
||||||
return self[item]
|
|
||||||
except KeyError:
|
|
||||||
return default
|
|
|
@ -1,11 +0,0 @@
|
||||||
from .discordbard import DiscordBard
|
|
||||||
from .dbqueue import DBQueue
|
|
||||||
from .fileaudiosource import FileAudioSource
|
|
||||||
from .ytdldiscord import YtdlDiscord
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"DBQueue",
|
|
||||||
"DiscordBard",
|
|
||||||
"FileAudioSource",
|
|
||||||
"YtdlDiscord",
|
|
||||||
]
|
|
|
@ -1,4 +0,0 @@
|
||||||
from discord import Embed, Colour
|
|
||||||
from discord.embeds import EmptyEmbed
|
|
||||||
from royalnet.bard import YtdlInfo
|
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
from royalnet.bard import FileAudioSource
|
|
||||||
from typing import List, AsyncGenerator, Tuple, Any, Dict, Optional
|
|
||||||
from .discordbard import DiscordBard
|
|
||||||
from .ytdldiscord import YtdlDiscord
|
|
||||||
|
|
||||||
try:
|
|
||||||
import discord
|
|
||||||
except ImportError:
|
|
||||||
discord = None
|
|
||||||
|
|
||||||
|
|
||||||
class DBQueue(DiscordBard):
|
|
||||||
"""A First-In-First-Out music queue.
|
|
||||||
|
|
||||||
It is what was once called a ``playlist``."""
|
|
||||||
def __init__(self, voice_client: "discord.VoiceClient"):
|
|
||||||
super().__init__(voice_client)
|
|
||||||
self.list: List[YtdlDiscord] = []
|
|
||||||
|
|
||||||
async def _generator(self) -> AsyncGenerator[Optional[FileAudioSource], Tuple[Tuple[Any, ...], Dict[str, Any]]]:
|
|
||||||
yield
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
ytd = self.list.pop(0)
|
|
||||||
except IndexError:
|
|
||||||
yield None
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
async with ytd.spawn_audiosource() as fas:
|
|
||||||
yield fas
|
|
||||||
finally:
|
|
||||||
await ytd.delete_asap()
|
|
||||||
|
|
||||||
async def add(self, ytd: YtdlDiscord):
|
|
||||||
self.list.append(ytd)
|
|
||||||
|
|
||||||
async def peek(self) -> List[YtdlDiscord]:
|
|
||||||
return self.list
|
|
||||||
|
|
||||||
async def remove(self, ytd: YtdlDiscord):
|
|
||||||
self.list.remove(ytd)
|
|
||||||
|
|
||||||
async def cleanup(self) -> None:
|
|
||||||
for ytd in self.list:
|
|
||||||
await ytd.delete_asap()
|
|
||||||
await self.stop()
|
|
|
@ -1,97 +0,0 @@
|
||||||
from typing import Optional, AsyncGenerator, List, Tuple, Any, Dict
|
|
||||||
from royalnet.bard import UnsupportedError
|
|
||||||
from .fileaudiosource import FileAudioSource
|
|
||||||
from .ytdldiscord import YtdlDiscord
|
|
||||||
|
|
||||||
try:
|
|
||||||
import discord
|
|
||||||
except ImportError:
|
|
||||||
discord = None
|
|
||||||
|
|
||||||
|
|
||||||
class DiscordBard:
|
|
||||||
"""An abstract representation of a music sequence.
|
|
||||||
|
|
||||||
Possible implementation may be playlist, song pools, multilayered tracks, and so on."""
|
|
||||||
|
|
||||||
def __init__(self, voice_client: "discord.VoiceClient"):
|
|
||||||
"""Create manually a :class:`DiscordBard`.
|
|
||||||
|
|
||||||
Warning:
|
|
||||||
Avoid calling this method, please use :meth:`create` instead!"""
|
|
||||||
self.voice_client: "discord.VoiceClient" = voice_client
|
|
||||||
"""The voice client that this :class:`DiscordBard` refers to."""
|
|
||||||
|
|
||||||
self.now_playing: Optional[FileAudioSource] = None
|
|
||||||
"""The :class:`YtdlDiscord` that's currently being played."""
|
|
||||||
|
|
||||||
self.generator: \
|
|
||||||
AsyncGenerator[FileAudioSource, Tuple[Tuple[Any, ...], Dict[str, Any]]] = self._generator()
|
|
||||||
"""The AsyncGenerator responsible for deciding the next song that should be played."""
|
|
||||||
|
|
||||||
async def _generator(self) -> AsyncGenerator[Optional[FileAudioSource], Tuple[Tuple[Any, ...], Dict[str, Any]]]:
|
|
||||||
"""Create an async generator that returns the next source to be played;
|
|
||||||
it can take a args+kwargs tuple in input to optionally select a different source.
|
|
||||||
|
|
||||||
The generator should ``yield`` once before doing anything else."""
|
|
||||||
yield
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def create(cls, voice_client: "discord.VoiceClient") -> "DiscordBard":
|
|
||||||
"""Create an instance of the :class:`DiscordBard`, and initialize its async generator."""
|
|
||||||
bard = cls(voice_client=voice_client)
|
|
||||||
# noinspection PyTypeChecker
|
|
||||||
none = await bard.generator.asend(None)
|
|
||||||
assert none is None
|
|
||||||
return bard
|
|
||||||
|
|
||||||
async def next(self, *args, **kwargs) -> Optional[FileAudioSource]:
|
|
||||||
"""Get the next :class:`FileAudioSource` that should be played, and change :attr:`.now_playing`.
|
|
||||||
|
|
||||||
Args and kwargs can be passed to the generator to select differently."""
|
|
||||||
fas: Optional[FileAudioSource] = await self.generator.asend((args, kwargs,))
|
|
||||||
self.now_playing = fas
|
|
||||||
return fas
|
|
||||||
|
|
||||||
async def stop(self):
|
|
||||||
"""Stop the playback of the current song."""
|
|
||||||
if self.now_playing is not None:
|
|
||||||
self.now_playing.stop()
|
|
||||||
|
|
||||||
async def add(self, ytd: YtdlDiscord) -> None:
|
|
||||||
"""Add a new :class:`YtdlDiscord` to the :class:`DiscordBard`, if possible.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
UnsupportedError: If it isn't possible to add new :class:`YtdlDiscord` to the :class:`DiscordBard`.
|
|
||||||
"""
|
|
||||||
raise UnsupportedError()
|
|
||||||
|
|
||||||
async def peek(self) -> Optional[List[YtdlDiscord]]:
|
|
||||||
"""Return the contents of the :class:`DiscordBard` as a :class:`list`, if possible.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
UnsupportedError: If it isn't possible to display the :class:`DiscordBard` state as a :class:`list`.
|
|
||||||
"""
|
|
||||||
raise UnsupportedError()
|
|
||||||
|
|
||||||
async def remove(self, ytd: YtdlDiscord) -> None:
|
|
||||||
"""Remove a :class:`YtdlDiscord` from the :class:`DiscordBard`, if possible.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
UnsupportedError: If it isn't possible to remove the :class:`YtdlDiscord` from the :class:`DiscordBard`.
|
|
||||||
"""
|
|
||||||
raise UnsupportedError()
|
|
||||||
|
|
||||||
async def cleanup(self) -> None:
|
|
||||||
"""Enqueue the deletion of all :class:`YtdlDiscord` contained in the :class:`DiscordBard`, and return only once
|
|
||||||
all deletions are complete."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
async def length(self) -> int:
|
|
||||||
"""Return the length of the :class:`DiscordBard`.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
UnsupportedError: If :meth:`.peek` is unsupported."""
|
|
||||||
return len(await self.peek())
|
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Type, Optional, List, Union
|
from typing import Type, Optional, List, Union, Dict
|
||||||
from royalnet.commands import *
|
from royalnet.commands import *
|
||||||
from royalnet.utils import asyncify
|
from royalnet.utils import asyncify
|
||||||
from royalnet.serf import Serf
|
from royalnet.serf import Serf
|
||||||
from .escape import escape
|
from .escape import escape
|
||||||
from .discordbard import *
|
|
||||||
from .barddict import BardsDict
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -54,9 +52,6 @@ class DiscordSerf(Serf):
|
||||||
self.client = self.Client()
|
self.client = self.Client()
|
||||||
"""The custom :class:`discord.Client` instance."""
|
"""The custom :class:`discord.Client` instance."""
|
||||||
|
|
||||||
self.bards: BardsDict = BardsDict(self.client)
|
|
||||||
"""A dictionary containing all bards spawned by this :class:`DiscordSerf`."""
|
|
||||||
|
|
||||||
def interface_factory(self) -> Type[CommandInterface]:
|
def interface_factory(self) -> Type[CommandInterface]:
|
||||||
# noinspection PyPep8Naming
|
# noinspection PyPep8Naming
|
||||||
GenericInterface = super().interface_factory()
|
GenericInterface = super().interface_factory()
|
||||||
|
@ -201,7 +196,7 @@ class DiscordSerf(Serf):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
ch_guild: "discord.Guild" = ch.guild
|
ch_guild: "discord.Guild" = ch.guild
|
||||||
if ch.guild != ch_guild:
|
if guild is not None and guild != ch_guild:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for user in accessible_to:
|
for user in accessible_to:
|
||||||
|
@ -230,25 +225,6 @@ class DiscordSerf(Serf):
|
||||||
|
|
||||||
return channels[0]
|
return channels[0]
|
||||||
|
|
||||||
async def voice_connect(self, channel: "discord.VoiceChannel"):
|
|
||||||
"""Try to connect to a :class:`discord.VoiceChannel` and to create the corresponing :class:`DiscordBard`.
|
|
||||||
|
|
||||||
Info:
|
|
||||||
Command-compatible! This method will raise :exc:`CommandError`s for all its errors, so it can be called
|
|
||||||
inside a command!"""
|
|
||||||
try:
|
|
||||||
voice_client = 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!")
|
|
||||||
self.bards[channel.guild] = await DBQueue.create(voice_client=voice_client)
|
|
||||||
|
|
||||||
async def voice_run(self, guild: "discord.Guild"):
|
async def voice_run(self, guild: "discord.Guild"):
|
||||||
"""Send the data from the bard to the voice websocket for a specific client."""
|
"""Send the data from the bard to the voice websocket for a specific client."""
|
||||||
bard: Optional[DiscordBard] = self.bards.get(guild)
|
bard: Optional[DiscordBard] = self.bards.get(guild)
|
||||||
|
|
38
royalnet/serf/discord/errors.py
Normal file
38
royalnet/serf/discord/errors.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
from ..errors import SerfError
|
||||||
|
|
||||||
|
|
||||||
|
class DiscordSerfError(SerfError):
|
||||||
|
"""Base class for all :mod:`royalnet.serf.discord` errors."""
|
||||||
|
|
||||||
|
|
||||||
|
class VoicePlayerError(DiscordSerfError):
|
||||||
|
"""Base class for all :class:`VoicePlayer` errors."""
|
||||||
|
|
||||||
|
|
||||||
|
class AlreadyConnectedError(VoicePlayerError):
|
||||||
|
"""Base class for the "Already Connected" errors."""
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerAlreadyConnectedError(AlreadyConnectedError):
|
||||||
|
"""The :class:`VoicePlayer` is already connected to voice.
|
||||||
|
|
||||||
|
Access the :class:`discord.VoiceClient` through :attr:`VoicePlayer.voice_client`!"""
|
||||||
|
|
||||||
|
|
||||||
|
class GuildAlreadyConnectedError(AlreadyConnectedError):
|
||||||
|
"""The :class:`discord.Client` is already connected to voice in a channel of this guild."""
|
||||||
|
|
||||||
|
|
||||||
|
class OpusNotLoadedError(VoicePlayerError):
|
||||||
|
"""The Opus library hasn't been loaded `as required
|
||||||
|
<https://discordpy.readthedocs.io/en/latest/api.html#discord.VoiceClient>` by :mod:`discord`."""
|
||||||
|
|
||||||
|
|
||||||
|
class DiscordTimeoutError(VoicePlayerError):
|
||||||
|
"""The websocket didn't get a response from the Discord voice servers in time."""
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerNotConnectedError(VoicePlayerError):
|
||||||
|
"""The :class:`VoicePlayer` isn't connected to the Discord voice servers.
|
||||||
|
|
||||||
|
Use :meth:`VoicePlayer.connect` first!"""
|
62
royalnet/serf/discord/voiceplayer.py
Normal file
62
royalnet/serf/discord/voiceplayer.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import asyncio
|
||||||
|
from typing import Optional
|
||||||
|
from .errors import *
|
||||||
|
try:
|
||||||
|
import discord
|
||||||
|
except ImportError:
|
||||||
|
discord = None
|
||||||
|
|
||||||
|
|
||||||
|
class VoicePlayer:
|
||||||
|
def __init__(self):
|
||||||
|
self.voice_client: Optional["discord.VoiceClient"] = None
|
||||||
|
...
|
||||||
|
|
||||||
|
async def connect(self, channel: "discord.VoiceChannel") -> "discord.VoiceClient":
|
||||||
|
"""Connect the :class:`VoicePlayer` to a :class:`discord.VoiceChannel`, creating a :class:`discord.VoiceClient`
|
||||||
|
that handles the connection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel: The :class:`discord.VoiceChannel` to connect into.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The created :class:`discord.VoiceClient`.
|
||||||
|
(It will be stored in :attr:`VoicePlayer.voice_client` anyways!)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
PlayerAlreadyConnectedError:
|
||||||
|
DiscordTimeoutError:
|
||||||
|
GuildAlreadyConnectedError:
|
||||||
|
OpusNotLoadedError:
|
||||||
|
"""
|
||||||
|
if self.voice_client is not None:
|
||||||
|
raise PlayerAlreadyConnectedError()
|
||||||
|
try:
|
||||||
|
self.voice_client = await channel.connect()
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
raise DiscordTimeoutError()
|
||||||
|
except discord.ClientException:
|
||||||
|
raise GuildAlreadyConnectedError()
|
||||||
|
except discord.opus.OpusNotLoaded:
|
||||||
|
raise OpusNotLoadedError()
|
||||||
|
return self.voice_client
|
||||||
|
|
||||||
|
async def disconnect(self) -> None:
|
||||||
|
"""Disconnect the :class:`VoicePlayer` from the channel where it is currently connected, and set
|
||||||
|
:attr:`.voice_client` to :const:`None`.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
PlayerNotConnectedError:
|
||||||
|
"""
|
||||||
|
if self.voice_client is None:
|
||||||
|
raise PlayerNotConnectedError()
|
||||||
|
await self.voice_client.disconnect(force=True)
|
||||||
|
self.voice_client = None
|
||||||
|
|
||||||
|
async def move(self, channel: "discord.VoiceChannel"):
|
||||||
|
"""Move the :class:`VoicePlayer` to a different channel.
|
||||||
|
|
||||||
|
This requires the :class:`VoicePlayer` to already be connected, and for the passed :class:`discord.VoiceChannel`
|
||||||
|
to be in the same :class:`discord.Guild` as """
|
||||||
|
|
||||||
|
...
|
2
royalnet/serf/errors.py
Normal file
2
royalnet/serf/errors.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
class SerfError(Exception):
|
||||||
|
"""Base class for all :mod:`royalnet.serf` errors."""
|
|
@ -148,12 +148,12 @@ class Serf:
|
||||||
alchemy: Alchemy = self.alchemy
|
alchemy: Alchemy = self.alchemy
|
||||||
serf: "Serf" = self
|
serf: "Serf" = self
|
||||||
|
|
||||||
async def call_herald_event(ci, destination: str, event_name: str, args: Dict) -> Dict:
|
async def call_herald_event(ci, destination: str, event_name: str, **kwargs) -> Dict:
|
||||||
"""Send a :class:`royalherald.Request` to a specific destination, and wait for a
|
"""Send a :class:`royalherald.Request` to a specific destination, and wait for a
|
||||||
:class:`royalherald.Response`."""
|
:class:`royalherald.Response`."""
|
||||||
if self.herald is None:
|
if self.herald is None:
|
||||||
raise UnsupportedError("`royalherald` is not enabled on this bot.")
|
raise UnsupportedError("`royalherald` is not enabled on this bot.")
|
||||||
request: Request = Request(handler=event_name, data=args)
|
request: Request = Request(handler=event_name, data=kwargs)
|
||||||
response: Response = await self.herald.request(destination=destination, request=request)
|
response: Response = await self.herald.request(destination=destination, request=request)
|
||||||
if isinstance(response, ResponseFailure):
|
if isinstance(response, ResponseFailure):
|
||||||
# TODO: pretty sure there's a better way to do this
|
# TODO: pretty sure there's a better way to do this
|
||||||
|
|
|
@ -49,7 +49,7 @@ class TelegramSerf(Serf):
|
||||||
herald_config=herald_config,
|
herald_config=herald_config,
|
||||||
secrets_name=secrets_name)
|
secrets_name=secrets_name)
|
||||||
|
|
||||||
self.client = telegram.Bot(self.get_secret("telegram"), request=TRequest(5, read_timeout=30))
|
self.client = telegram.Bot(self.get_secret("telegram"), request=TRequest(50, read_timeout=30))
|
||||||
"""The :class:`telegram.Bot` instance that will be used from the Serf."""
|
"""The :class:`telegram.Bot` instance that will be used from the Serf."""
|
||||||
|
|
||||||
self.update_offset: int = -100
|
self.update_offset: int = -100
|
||||||
|
|
|
@ -4,6 +4,7 @@ from .sleep_until import sleep_until
|
||||||
from .formatters import andformat, underscorize, ytdldateformat, numberemojiformat, ordinalformat
|
from .formatters import andformat, underscorize, ytdldateformat, numberemojiformat, ordinalformat
|
||||||
from .urluuid import to_urluuid, from_urluuid
|
from .urluuid import to_urluuid, from_urluuid
|
||||||
from .multilock import MultiLock
|
from .multilock import MultiLock
|
||||||
|
from .fileaudiosource import FileAudioSource
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"asyncify",
|
"asyncify",
|
||||||
|
@ -17,4 +18,5 @@ __all__ = [
|
||||||
"to_urluuid",
|
"to_urluuid",
|
||||||
"from_urluuid",
|
"from_urluuid",
|
||||||
"MultiLock",
|
"MultiLock",
|
||||||
|
"FileAudioSource",
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue