Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add event dispatching #28

Merged
merged 1 commit into from
Jan 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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