Skip to content

Commit

Permalink
feat: add event dispatching (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
ooliver1 committed Jan 28, 2023
1 parent 9c3fc44 commit d4a837f
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 38 deletions.
4 changes: 2 additions & 2 deletions .flake8
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[flake8]
max-line-length = 88
per-file-ignores =
__init__.py: F401,F403
__init__.py: F401
# Black already handles E501 - line too long, ignored for docstring anomolies.
# W503 - line break before binary operator, ignored for Black, flake8 cannot decide what style lmao.
extend-ignore = E501,W503
extend-ignore = E501,W503,F403,F405
1 change: 1 addition & 0 deletions mafic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from . import __libraries
from .errors import *
from .events import *
from .filter import *
from .ip import *
from .node import *
Expand Down
16 changes: 10 additions & 6 deletions mafic/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
if TYPE_CHECKING:
from typing_extensions import Self

from .typings import ExceptionSeverity, FriendlyException
from .typings import ExceptionSeverity, LavalinkException

__all__ = (
"LibraryCompatibilityError",
Expand Down Expand Up @@ -68,14 +68,16 @@ class TrackLoadException(PlayerException):
The severity of the error.
"""

def __init__(self, *, message: str, severity: ExceptionSeverity) -> None:
def __init__(
self, *, message: str, severity: ExceptionSeverity, cause: str
) -> None:
super().__init__(f"The track could not be loaded: {message} ({severity} error)")

self.message = message
self.severity = severity
self.message: str = message
self.severity: ExceptionSeverity = severity

@classmethod
def from_data(cls, data: FriendlyException) -> Self:
def from_data(cls, data: LavalinkException) -> Self:
"""Construct a new TrackLoadException from raw Lavalink data.
Parameters
Expand All @@ -89,7 +91,9 @@ def from_data(cls, data: FriendlyException) -> Self:
The constructed exception.
"""

return cls(message=data["message"], severity=data["severity"])
return cls(
message=data["message"], severity=data["severity"], cause=data["cause"]
)


class PlayerNotConnected(PlayerException):
Expand Down
190 changes: 190 additions & 0 deletions mafic/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# SPDX-License-Identifier: MIT
# pyright: reportImportCycles=false
# Player import.

from __future__ import annotations

from enum import Enum
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from .player import Player
from .track import Track
from .type_variables import ClientT
from .typings import (
LavalinkException,
TrackEndEvent as TrackEndEventPayload,
TrackExceptionEvent as TrackExceptionEventPayload,
TrackStuckEvent as TrackStuckEventPayload,
WebSocketClosedEvent as WebSocketClosedEventPayload,
)

__all__ = (
"EndReason",
"TrackEndEvent",
"TrackExceptionEvent",
"TrackStartEvent",
"TrackStuckEvent",
"WebSocketClosedEvent",
)


class EndReason(str, Enum):
"""Represents the reason why a track ended."""

FINISHED = "FINISHED"
"""The track finished playing."""

LOAD_FAILED = "LOAD_FAILED"
"""The track failed to load."""

STOPPED = "STOPPED"
"""The track was stopped."""

REPLACED = "REPLACED"
"""The track was replaced."""

CLEANUP = "CLEANUP"
"""The track was cleaned up."""


class WebSocketClosedEvent:
"""Represents an event when the connection to Discord is lost.
Attributes
----------
code: :class:`int`
The close code.
Find what this can be in the Discord `docs`_.
.. _docs: https://discord.com/developers/docs/topics/opcodes-and-status-codes#close-event-codes.
reason: :class:`str`
The close reason.
by_discord: :class:`bool`
Whether the close was initiated by Discord.
player: :class:`Player`
The player that the event was dispatched from.
"""

__slots__ = ("code", "reason", "by_discord", "player")

def __init__(
self, *, payload: WebSocketClosedEventPayload, player: Player[ClientT]
):
self.code: int = payload["code"]
self.reason: str = payload["reason"]
self.by_discord: bool = payload["byRemote"]
self.player: Player[ClientT] = player

def __repr__(self) -> str:
return (
f"<WebSocketClosedEvent code={self.code} reason={self.reason!r} "
f"by_discord={self.by_discord}>"
)


class TrackStartEvent:
"""Represents an event when a track starts playing.
Attributes
----------
track: :class:`Track`
The track that started playing.
player: :class:`Player`
The player that the event was dispatched from.
"""

__slots__ = ("track", "player")

def __init__(self, *, track: Track, player: Player[ClientT]):
self.track: Track = track
self.player: Player[ClientT] = player

def __repr__(self) -> str:
return f"<TrackStartEvent track={self.track!r}>"


class TrackEndEvent:
"""Represents an event when a track ends.
Attributes
----------
track: :class:`Track`
The track that ended.
reason: :class:`EndReason`
The reason why the track ended.
player: :class:`Player`
The player that the event was dispatched from.
"""

__slots__ = ("track", "reason", "player")

def __init__(
self, *, track: Track, payload: TrackEndEventPayload, player: Player[ClientT]
):
self.track: Track = track
self.reason: EndReason = EndReason(payload["reason"])
self.player: Player[ClientT] = player

def __repr__(self) -> str:
return f"<TrackEndEvent track={self.track!r} reason={self.reason!r}>"


class TrackExceptionEvent:
"""Represents an event when an exception occurs while playing a track.
Attributes
----------
track: :class:`Track`
The track that caused the exception.
exception: :class:`Exception`
The exception that was raised.
player: :class:`Player`
The player that the event was dispatched from.
"""

__slots__ = ("track", "exception", "player")

def __init__(
self,
*,
track: Track,
payload: TrackExceptionEventPayload,
player: Player[ClientT],
):
self.track: Track = track
self.exception: LavalinkException = payload["exception"]
self.player: Player[ClientT] = player

def __repr__(self) -> str:
return (
f"<TrackExceptionEvent track={self.track!r} exception={self.exception!r}>"
)


class TrackStuckEvent:
"""Represents an event when a track gets stuck.
Attributes
----------
track: :class:`Track`
The track that got stuck.
threshold_ms: :class:`int`
The threshold in milliseconds that was exceeded.
player: :class:`Player`
The player that the event was dispatched from.
"""

__slots__ = ("track", "threshold_ms", "player")

def __init__(
self, *, track: Track, payload: TrackStuckEventPayload, player: Player[ClientT]
):
self.track: Track = track
self.threshold_ms: int = payload["thresholdMs"]
self.player: Player[ClientT] = player

def __repr__(self) -> str:
return (
f"<TrackStuckEvent track={self.track!r} threshold_ms={self.threshold_ms}>"
)
24 changes: 5 additions & 19 deletions mafic/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,27 +626,13 @@ async def _handle_event(self, data: EventPayload) -> None:
The data to handle.
"""

if data["type"] == "WebSocketClosedEvent":
# TODO:
...
elif data["type"] == "TrackStartEvent":
# We do not care about track starts, the user is already aware of it.
if not (player := self.players.get(int(data["guildId"]))):
_log.error(
"Could not find player for guild %s, discarding event.", data["guildId"]
)
return
elif data["type"] == "TrackEndEvent":
# TODO:
...
elif data["type"] == "TrackExceptionEvent":
# TODO:
...
elif data["type"] == "TrackStuckEvent":
# TODO:
...
else:
# Pyright expects this to never happen, so do I, I really hope.
# Nobody expects the Spanish Inquisition, neither does pyright.

event_type = cast(str, data["type"])
_log.warning("Unknown incoming event type %s", event_type)
player.dispatch_event(data)

def voice_update(
self,
Expand Down
Loading

0 comments on commit d4a837f

Please sign in to comment.