zynko 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
zynko/__init__.py ADDED
@@ -0,0 +1,74 @@
1
+ """zynko: the official library for Zynko bots (docs.zynko.org/bots).
2
+
3
+ API and gateway on asyncio and aiohttp; voice through the ``livekit`` package, installed with
4
+ ``pip install "zynko[voice]"`` and loaded only when a bot joins a voice room.
5
+ """
6
+
7
+ from .bits import ALL_PERMISSIONS, Intents, Permissions, id_time
8
+ from .client import Client
9
+ from .gateway import GatewayClosed
10
+ from .interaction import Interaction
11
+ from .message import Message
12
+ from .models import (
13
+ Attachment,
14
+ Button,
15
+ ButtonStyle,
16
+ Card,
17
+ CardField,
18
+ Channel,
19
+ ChannelType,
20
+ Command,
21
+ CommandOption,
22
+ Components,
23
+ Emoji,
24
+ Guild,
25
+ Member,
26
+ Overwrite,
27
+ Role,
28
+ User,
29
+ VoiceState,
30
+ )
31
+ from .permissions import compute_permissions
32
+ from .player import PCM, AudioInput, AudioPlayer, Stream, Track, tone
33
+ from .rest import File, Rest, ZynkoError
34
+
35
+ __version__ = "0.1.0"
36
+
37
+ __all__ = [
38
+ "ALL_PERMISSIONS",
39
+ "Attachment",
40
+ "AudioInput",
41
+ "AudioPlayer",
42
+ "Button",
43
+ "ButtonStyle",
44
+ "Card",
45
+ "CardField",
46
+ "Channel",
47
+ "ChannelType",
48
+ "Client",
49
+ "Command",
50
+ "CommandOption",
51
+ "Components",
52
+ "Emoji",
53
+ "File",
54
+ "GatewayClosed",
55
+ "Guild",
56
+ "Intents",
57
+ "Interaction",
58
+ "Member",
59
+ "Message",
60
+ "Overwrite",
61
+ "PCM",
62
+ "Permissions",
63
+ "Rest",
64
+ "Role",
65
+ "Stream",
66
+ "Track",
67
+ "User",
68
+ "VoiceState",
69
+ "ZynkoError",
70
+ "compute_permissions",
71
+ "id_time",
72
+ "tone",
73
+ "__version__",
74
+ ]
zynko/_events.py ADDED
@@ -0,0 +1,123 @@
1
+ """A small event emitter: sync or async handlers, errors routed instead of raised."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import inspect
7
+ import logging
8
+ from collections.abc import Awaitable, Callable
9
+ from typing import Any, TypeVar, overload
10
+
11
+ log = logging.getLogger("zynko")
12
+
13
+ F = TypeVar("F", bound=Callable[..., Any])
14
+
15
+
16
+ class Emitter:
17
+ """Calls handlers by event name.
18
+
19
+ A handler may be a plain function or a coroutine function; coroutines run as their own
20
+ tasks, so a slow handler never holds up the next event. A handler that raises becomes an
21
+ ``error`` event (or a log line, when nobody listens to ``error``).
22
+ """
23
+
24
+ def __init__(self, name: str = "zynko") -> None:
25
+ self._name = name
26
+ self._handlers: dict[str, list[Callable[..., Any]]] = {}
27
+ self._once: set[tuple[str, Callable[..., Any]]] = set()
28
+ self._tasks: set[asyncio.Task[Any]] = set()
29
+
30
+ @overload
31
+ def on(self, event: str) -> Callable[[F], F]: ...
32
+ @overload
33
+ def on(self, event: str, handler: F) -> F: ...
34
+ def on(self, event: str, handler: F | None = None) -> Any:
35
+ """Registers a handler. Works as a call or as a decorator: ``@x.on("event")``."""
36
+ if handler is None:
37
+ return lambda f: self.on(event, f)
38
+ self._handlers.setdefault(event, []).append(handler)
39
+ return handler
40
+
41
+ def once(self, event: str, handler: F) -> F:
42
+ """Registers a handler that runs once."""
43
+ self.on(event, handler)
44
+ self._once.add((event, handler))
45
+ return handler
46
+
47
+ def off(self, event: str, handler: Callable[..., Any]) -> None:
48
+ handlers = self._handlers.get(event, [])
49
+ if handler in handlers:
50
+ handlers.remove(handler)
51
+ self._once.discard((event, handler))
52
+
53
+ def listeners(self, event: str) -> int:
54
+ return len(self._handlers.get(event, []))
55
+
56
+ def emit(self, event: str, *args: Any) -> None:
57
+ for handler in list(self._handlers.get(event, [])):
58
+ if (event, handler) in self._once:
59
+ self.off(event, handler)
60
+ self._call(event, handler, args)
61
+
62
+ def _call(self, event: str, handler: Callable[..., Any], args: tuple[Any, ...]) -> None:
63
+ try:
64
+ result = handler(*args)
65
+ except Exception as e: # noqa: BLE001 - every handler error is reported, none raised
66
+ self._failed(event, e)
67
+ return
68
+ if inspect.isawaitable(result):
69
+ task = asyncio.ensure_future(_await(result))
70
+ self._tasks.add(task)
71
+ task.add_done_callback(lambda t: self._done(event, t))
72
+
73
+ def _done(self, event: str, task: asyncio.Task[Any]) -> None:
74
+ self._tasks.discard(task)
75
+ if task.cancelled():
76
+ return
77
+ e = task.exception()
78
+ if e is not None:
79
+ self._failed(event, e)
80
+
81
+ def _failed(self, event: str, error: BaseException) -> None:
82
+ if event != "error" and self.listeners("error"):
83
+ self.emit("error", error)
84
+ else:
85
+ log.error("%s: erro num handler de %r", self._name, event, exc_info=error)
86
+
87
+ async def wait_for(
88
+ self,
89
+ event: str,
90
+ check: Callable[..., bool] | None = None,
91
+ timeout: float | None = 30.0,
92
+ ) -> Any:
93
+ """Waits for the next ``event`` whose arguments pass ``check``.
94
+
95
+ Returns the single argument, or a tuple when the event has several. Raises
96
+ ``asyncio.TimeoutError`` after ``timeout`` seconds (None: wait forever).
97
+ """
98
+ loop = asyncio.get_running_loop()
99
+ future: asyncio.Future[Any] = loop.create_future()
100
+
101
+ def handler(*args: Any) -> None:
102
+ if future.done():
103
+ return
104
+ if check is not None:
105
+ try:
106
+ if not check(*args):
107
+ return
108
+ except Exception as e: # noqa: BLE001
109
+ future.set_exception(e)
110
+ return
111
+ future.set_result(args[0] if len(args) == 1 else args)
112
+
113
+ self.on(event, handler)
114
+ try:
115
+ return await asyncio.wait_for(future, timeout)
116
+ except asyncio.TimeoutError:
117
+ raise asyncio.TimeoutError(f"Esperando {event}: tempo esgotado") from None
118
+ finally:
119
+ self.off(event, handler)
120
+
121
+
122
+ async def _await(a: Awaitable[Any]) -> Any:
123
+ return await a
zynko/bits.py ADDED
@@ -0,0 +1,62 @@
1
+ """Intents, permission bits and snowflake times (docs.zynko.org/api/gateway#intents)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import IntFlag
6
+
7
+ __all__ = ["Intents", "Permissions", "ALL_PERMISSIONS", "id_time"]
8
+
9
+
10
+ class Intents(IntFlag):
11
+ """The kinds of event a bot asks the gateway for.
12
+
13
+ READY, GUILD_CREATE, GUILD_DELETE, CHANNEL_CREATE and USER_UPDATE always arrive.
14
+ """
15
+
16
+ #: Guilds changing, and their channels, roles and emojis.
17
+ GUILDS = 1 << 0
18
+ #: Members joining, leaving or changing.
19
+ GUILD_MEMBERS = 1 << 1
20
+ #: Who is online.
21
+ PRESENCES = 1 << 2
22
+ #: Messages created, edited and deleted, in guilds and DMs.
23
+ MESSAGES = 1 << 3
24
+ REACTIONS = 1 << 4
25
+ TYPING = 1 << 5
26
+ #: Who is in which voice room. A bot that joins voice needs it.
27
+ VOICE_STATES = 1 << 6
28
+ ALL = (1 << 7) - 1
29
+
30
+
31
+ class Permissions(IntFlag):
32
+ """Role permissions, as bits (docs.zynko.org/api/permissoes)."""
33
+
34
+ ADMINISTRATOR = 1 << 0
35
+ MANAGE_GUILD = 1 << 1
36
+ MANAGE_CHANNELS = 1 << 2
37
+ MANAGE_ROLES = 1 << 3
38
+ KICK_MEMBERS = 1 << 4
39
+ BAN_MEMBERS = 1 << 5
40
+ MANAGE_MESSAGES = 1 << 6
41
+ MANAGE_NICKNAMES = 1 << 7
42
+ CREATE_INVITE = 1 << 8
43
+ SEND_MESSAGES = 1 << 9
44
+ ATTACH_FILES = 1 << 10
45
+ CONNECT = 1 << 11
46
+ SPEAK = 1 << 12
47
+ MANAGE_EMOJIS = 1 << 13
48
+ VIEW_CHANNEL = 1 << 14
49
+ ADD_REACTIONS = 1 << 15
50
+ READ_MESSAGE_HISTORY = 1 << 16
51
+ STREAM = 1 << 17
52
+
53
+
54
+ #: Every permission bit set: what owners and admins get.
55
+ ALL_PERMISSIONS = (1 << 18) - 1
56
+
57
+ _EPOCH_MS = 1_767_225_600_000
58
+
59
+
60
+ def id_time(id: str | int) -> int:
61
+ """When an id was made, in milliseconds since 1970 (ids are snowflakes)."""
62
+ return (int(id) >> 22) + _EPOCH_MS