1515You should have received a copy of the GNU Affero General Public License
1616along with this program. If not, see <http://www.gnu.org/licenses/>.
1717"""
18+
1819from __future__ import annotations
1920
2021import asyncio
2122import logging
2223from collections .abc import Awaitable , Callable
23- from typing import TYPE_CHECKING , Any
24+ from typing import Any , TYPE_CHECKING
2425
2526import aiohttp
2627import aiohttp .web
2728from discord .backoff import ExponentialBackoff
2829from discord .ext .native_voice import _native_voice
2930
31+
3032if TYPE_CHECKING :
3133 from .app import App
3234
@@ -50,69 +52,123 @@ def __init__(
5052 self ._user_id : str = user_id
5153
5254 self ._connector : _native_voice .VoiceConnector = _native_voice .VoiceConnector ()
55+ self ._connector .user_id = int (user_id )
56+
5357 self ._connection : _native_voice .VoiceConnection | None = None
5458 self ._runner : asyncio .Task [None ] | None = None
5559
56- self ._connector . user_id = int ( user_id )
60+ self ._endpoint : str | None = None
5761
58- self .OP_HANDLERS : dict [str , Callable [[dict [str , Any ]], Awaitable [None ]]] = {
59- 'voice_update' : self ._voice_update ,
60- 'destroy' : self ._destroy ,
61- 'play' : self ._play ,
62- 'stop' : self ._stop ,
62+ self ._OP_HANDLERS : dict [str , Callable [[dict [str , Any ]], Awaitable [None ]]] = {
63+ 'voice_update' : self ._voice_update ,
64+ 'destroy' : self ._destroy ,
65+ 'play' : self ._play ,
66+ 'stop' : self ._stop ,
6367 'set_pause_state' : self ._set_pause_state ,
64- 'set_position' : self ._set_position ,
65- 'set_filter' : self ._set_filter ,
68+ 'set_position' : self ._set_position ,
69+ 'set_filter' : self ._set_filter ,
70+ 'debug' : self ._debug ,
6671 }
6772
73+ self ._LOG_PREFIX : str = f'<{ self ._websocket ["client_name" ]} > - Player \' { self ._guild_id } \' '
74+
75+ self ._NO_CONNECTION_MESSAGE : Callable [[str ], str ] = (
76+ lambda op : f'{ self ._LOG_PREFIX } attempted \' { op } \' op while internal connection is down.'
77+ )
78+ self ._MISSING_KEY_MESSAGE : Callable [[str , str ], str ] = (
79+ lambda op , key : f'{ self ._LOG_PREFIX } received \' { op } \' op with missing \' { key } \' key.'
80+ )
81+
6882 # websocket op handlers
6983
7084 async def handle_payload (self , payload : dict [str , Any ]) -> None :
7185
72- handler = self .OP_HANDLERS .get (payload ['op' ])
73- if not handler :
74- logger .error (f'Received payload with unknown "op" key from <{ self ._websocket ["client_name" ]} >. Discarding.' )
86+ op = payload ['op' ]
87+
88+ if not (handler := self ._OP_HANDLERS .get (op )):
89+ logger .error (f'{ self ._LOG_PREFIX } received payload with unknown \' op\' key.\n Payload: { payload } ' )
7590 return
7691
92+ logger .debug (f'{ self ._LOG_PREFIX } received payload with \' { op } \' op.\n Payload: { payload } ' )
7793 await handler (payload ['d' ])
7894
7995 async def _voice_update (self , data : dict [str , Any ]) -> None :
8096
8197 self ._connector .session_id = data ['session_id' ]
82- token = data ['token' ]
83- guild_id = data ['guild_id' ]
8498
85- if (endpoint := data .get ('endpoint' )) is None :
99+ if not (endpoint := data .get ('endpoint' )):
86100 return
87101
88102 endpoint , _ , _ = endpoint .rpartition (':' )
89103 endpoint = endpoint .removeprefix ('wss://' )
104+ self ._endpoint = endpoint
90105
91- self ._connector .update_socket (token , guild_id , endpoint )
106+ self ._connector .update_socket (
107+ data ['token' ],
108+ data ['guild_id' ],
109+ endpoint
110+ )
92111 await self ._connect ()
93112
94- async def _destroy (self , data : dict [str , Any ]) -> None :
95- raise NotImplementedError
113+ async def _destroy (self , _ : dict [str , Any ]) -> None :
114+
115+ await self ._disconnect ()
116+ logger .info (f'{ self ._LOG_PREFIX } has been disconnected.' )
96117
97118 async def _play (self , data : dict [str , Any ]) -> None :
98119
99- info = self ._app ._decode_track_id (data ['track_id' ])
120+ if not self ._connection :
121+ logger .error (self ._NO_CONNECTION_MESSAGE ('play' ))
122+ return
123+
124+ if not (track_id := data .get ('track_id' )):
125+ logger .error (self ._MISSING_KEY_MESSAGE ('play' , 'track_id' ))
126+ return
127+
128+ info = self ._app ._decode_track_id (track_id )
100129 url = await self ._app ._get_playback_url (info ['url' ])
101130
102- if self ._connection :
103- self . _connection . play ( url )
131+ self ._connection . play ( url )
132+ logger . info ( f' { self . _LOG_PREFIX } started playing track \' { info [ "title" ] } \' by \' { info [ "author" ] } \' .' )
104133
105- async def _stop (self , data : dict [str , Any ]) -> None :
106- raise NotImplementedError
134+ async def _stop (self , _ : dict [str , Any ]) -> None :
135+
136+ if not self ._connection :
137+ logger .error (self ._NO_CONNECTION_MESSAGE ('stop' ))
138+ return
139+
140+ self ._connection .stop ()
141+ logger .info (f'{ self ._LOG_PREFIX } stopped the current track.' )
107142
108143 async def _set_pause_state (self , data : dict [str , Any ]) -> None :
109- raise NotImplementedError
144+
145+ if not self ._connection :
146+ logger .error (self ._NO_CONNECTION_MESSAGE ('set_pause_state' ))
147+ return
148+ if not (state := data .get ('state' )):
149+ logger .error (self ._MISSING_KEY_MESSAGE ('set_pause_state' , 'state' ))
150+ return
151+
152+ self ._connection .pause () if state else self ._connection .resume ()
153+ logger .info (f'{ self ._LOG_PREFIX } set its paused state to \' { state } \' .' )
110154
111155 async def _set_position (self , data : dict [str , Any ]) -> None :
112- raise NotImplementedError
113156
114- async def _set_filter (self , data : dict [str , Any ]) -> None :
115- raise NotImplementedError
157+ if not self ._connection :
158+ logger .error (self ._NO_CONNECTION_MESSAGE ('set_position' ))
159+ return
160+ if not (position := data .get ('position' )):
161+ logger .error (self ._MISSING_KEY_MESSAGE ('set_position' , 'position' ))
162+ return
163+
164+ # TODO: implement
165+ logger .info (f'{ self ._LOG_PREFIX } set its position to \' { position } \' .' )
166+
167+ async def _set_filter (self , _ : dict [str , Any ]) -> None :
168+ logger .error (f'{ self ._LOG_PREFIX } received \' set_filter\' op which is not yet implemented.' )
169+
170+ async def _debug (self , _ : dict [str , Any ]) -> None :
171+ print (self ._debug_info ())
116172
117173 # internal connection handlers
118174
@@ -126,6 +182,7 @@ async def _connect(self) -> None:
126182 self ._runner = loop .create_task (self ._reconnect_handler ())
127183
128184 self ._websocket ['players' ][self ._guild_id ] = self
185+ logger .info (f'{ self ._LOG_PREFIX } connected to internal voice server \' { self ._endpoint } \' .' )
129186
130187 async def _reconnect_handler (self ) -> None :
131188
@@ -169,6 +226,7 @@ async def _disconnect(self) -> None:
169226 self ._connection = None
170227
171228 del self ._websocket ['players' ][self ._guild_id ]
229+ logger .info (f'{ self ._LOG_PREFIX } disconnected from internal voice server \' { self ._endpoint } \' .' )
172230
173231 # utility
174232
0 commit comments