Files
Vocard/voicelink/ipc/methods.py
Choco 56a6b67470 Make messages ephemeral and bump discord.py
Make certain bot responses ephemeral to avoid spamming channels (debug command and missing-permission reply). Bump discord.py from 2.7.0 to 2.7.1 in requirements. Remove an unused Config().get_playlist_config() assignment in updatePlaylist to clean up dead code.
2026-03-30 11:01:53 +08:00

801 lines
31 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import time, re
from typing import List, Dict, Union, Optional, TYPE_CHECKING
from discord import User, Member
from discord.ext import commands
from voicelink import Player, Track, Playlist, NodePool, LoopType, Filters, Config, MongoDBHandler, LangHandler, LYRICS_PLATFORMS
from voicelink.utils import TempCtx
if TYPE_CHECKING:
from .client import IPCClient
RATELIMIT_COUNTER: Dict[int, Dict[str, float]] = {}
SCOPES = {
"prefix": str,
"lang": str,
"queue_type": str,
"dj": int,
"controller": bool,
"controller_msg": bool,
"24/7": bool,
"disabled_vote": bool,
"duplicate_track": bool,
"silent_msg": bool,
"default_controller": dict,
"stage_announce_template": str
}
class SystemMethod:
def __init__(self, function: callable, *, credit: int = 1):
self.function: callable = function
self.params: List[str] = ["bot", "data"]
self.credit: int = credit
class PlayerMethod(SystemMethod):
def __init__(self, function, *, credit: int = 1, auto_connect: bool = False):
super().__init__(function, credit=credit)
self.params: List[str] = ["player", "member", "data"]
self.auto_connect: bool = auto_connect
def require_permission(only_admin: bool = False):
def decorator(func) -> callable:
async def wrapper(player: Player, member: Member, dict: Dict) -> Optional[Dict]:
if only_admin and not member.guild_permissions.manage_guild:
return error_msg("Only the admins may use this function!", user_id=member.id)
if not player.is_privileged(member):
return error_msg("Only the DJ or admins may use this function!", user_id=member.id)
return await func(player, member, dict)
return wrapper
return decorator
def error_msg(msg: str, *, user_id: int = None, guild_id: int = None, level: str = "info") -> Dict:
payload = {"op": "errorMsg", "level": level, "msg": msg}
if user_id:
payload["userId"] = str(user_id)
if guild_id:
payload["guildId"] = str(guild_id)
return payload
async def connect_channel(member: Member, bot: commands.Bot) -> Player:
if not member.voice:
return
channel = member.voice.channel
try:
settings = await MongoDBHandler.get_settings(channel.guild.id)
player: Player = await channel.connect(cls=Player(bot, channel, TempCtx(member, channel), settings))
await player.send_ws({"op": "createPlayer", "memberIds": [str(member.id) for member in channel.members]})
return player
except:
return
async def initBot(bot: commands.Bot, data: Dict) -> Dict:
user_id = int(data.get("userId"))
user = bot.get_user(user_id)
if not user:
user = await bot.fetch_user(user_id)
if user:
return {
"op": "initBot",
"userId": str(user_id),
"botName": bot.user.display_name,
"botAvatar": bot.user.display_avatar.url,
"botId": str(bot.user.id)
}
async def initUser(bot: commands.Bot, data: Dict) -> Dict:
user_id = int(data.get("userId"))
data = await MongoDBHandler.get_user(user_id)
for mail in data.get("inbox"):
sender = bot.get_user(mail.get("sender"))
if not sender:
sender = await bot.fetch_user(mail.get("sender"))
if not sender:
data.get("inbox").remove(mail)
mail["sender"] = {"avatarUrl": sender.display_avatar.url, "name": sender.display_name, "id": str(sender.id)}
return {
"op": "initUser",
"userId": str(user_id),
"data": data
}
async def initPlayer(player: Player, member: Member, data: Dict) -> Dict:
player._ipc_connection = True
available_filters = []
for name, filter_cls in Filters.get_available_filters().items():
filter = filter_cls()
available_filters.append({"tag": name, "scope": filter.scope, "payload": filter.payload})
return {
"op": "initPlayer",
"guildId": str(player.guild.id),
"userId": str(data.get("userId")),
"users": [{
"userId": str(member.id),
"avatarUrl": member.display_avatar.url,
"name": member.name
} for member in player.channel.members ],
"tracks": [ {"trackId": track.track_id, "requesterId": str(track.requester.id)} for track in player.queue._queue ],
"repeatMode": player.queue.repeat.lower(),
"channelName": player.channel.name,
"currentQueuePosition": player.queue._position + (0 if player.is_playing else 1),
"currentPosition": 0 or player.position if player.is_playing else 0,
"isPlaying": player.is_playing,
"isPaused": player.is_paused,
"isDj": player.is_privileged(member, check_user_join=False),
"autoplay": player.settings.get("autoplay", False),
"volume": player.volume,
"filters": [{"tag": filter.tag, "scope": filter.scope, "payload": filter.payload} for filter in player.filters.get_filters()],
"availableFilters": available_filters
}
async def closeConnection(bot: commands.Bot, data: Dict) -> None:
guild_id = int(data.get("guildId"))
guild = bot.get_guild(guild_id)
player: Player = guild.voice_client
if player:
player._ipc_connection = False
async def getRecommendation(bot: commands.Bot, data: Dict) -> None:
node = NodePool.get_node()
if not node:
return
track_data = Track.decode(track_id := data.get("trackId"))
track = Track(track_id=track_id, info=track_data, requester=bot.user)
tracks: List[Track] = await track.get_recommendations(node)
return {
"op": "getRecommendation",
"userId": str(data.get("userId")),
"callback": data.get("callback"),
"tracks": [track.track_id for track in tracks] if tracks else []
}
async def skipTo(player: Player, member: Member, data: Dict) -> None:
if not player.is_privileged(member):
if player.current and member == player.current.requester:
pass
elif member in player.skip_votes:
return error_msg(player.get_msg('voting.voted'), user_id=member.id)
else:
player.skip_votes.add(member)
if len(player.skip_votes) < (required := player.required()):
return error_msg(player.get_msg('player.controls.skip.vote').format(member, len(player.skip_votes), required), guild_id=player.guild.id)
index = data.get("index", 1)
if index > 1:
player.queue.skipto(index)
if player.queue._repeat.mode == LoopType.TRACK:
await player.set_repeat(LoopType.OFF)
await player.stop()
async def backTo(player: Player, member: Member, data: Dict) -> None:
if not player.is_privileged(member):
if player.current and member == player.current.requester:
pass
elif member in player.skip_votes:
return error_msg(player.get_msg('voting.voted'), user_id=member.id)
else:
player.skip_votes.add(member)
if len(player.skip_votes) < (required := player.required()):
return error_msg(player.get_msg('player.controls.back.vote').format(member, len(player.skip_votes), required), guild_id=player.guild.id)
index = data.get("index", 1)
if not player.is_playing:
player.queue.backto(index)
await player.do_next()
else:
player.queue.backto(index + 1)
await player.stop()
@require_permission()
async def moveTrack(player: Player, member: Member, data: Dict) -> None:
index = data.get("index")
new_index = data.get("newIndex")
if index == new_index:
return
await player.move_track(index, new_index, member)
async def addTracks(player: Player, member: Member, data: Dict) -> None:
_type = data.get("type", "addToQueue")
tracks = [Track(
track_id=track_id,
info=Track.decode(track_id),
requester=member
) for track_id in data.get("tracks", [])]
if _type == "addToQueue":
await player.add_track(tracks)
elif _type == "forcePlay":
await player.add_track(tracks, at_front=True)
if player.is_playing:
return await player.stop()
elif _type == "addNext":
await player.add_track(tracks, at_front=True)
if not player.is_playing:
await player.do_next()
async def getTracks(bot: commands.Bot, data: Dict) -> Dict:
query = data.get("query", None)
if query:
payload = {"op": "getTracks", "userId": data.get("userId"), "callback": data.get("callback")}
tracks = await NodePool.get_node().get_tracks(query=query, requester=None)
if not tracks:
return payload
payload["tracks"] = [ track.track_id for track in (tracks.tracks if isinstance(tracks, Playlist) else tracks ) ]
return payload
async def searchAndPlay(player: Player, member: Member, data: Dict) -> None:
payload = await getTracks(player.bot, data)
await addTracks(player, member, payload)
async def shuffleTrack(player: Player, member: Member, data: Dict) -> None:
if not player.is_privileged(member):
if member in player.shuffle_votes:
return error_msg(player.get_msg('voting.voted'), user_id=member.id)
player.shuffle_votes.add(member)
if len(player.shuffle_votes) < (required := player.required()):
return error_msg(player.get_msg('player.controls.shuffle.vote').format(member, len(player.skip_votes), required), guild_id=player.guild.id)
await player.shuffle(data.get("type", "queue"), member)
@require_permission()
async def repeatTrack(player: Player, member: Member, data: Dict) -> None:
await player.set_repeat(requester=member)
@require_permission()
async def removeTrack(player: Player, member: Member, data: Dict) -> None:
index, index2 = data.get("index"), data.get("index2")
await player.remove_track(index, index2, requester=member)
@require_permission()
async def clearQueue(player: Player, member: Member, data: Dict) -> None:
queue_type = data.get("queueType", "").lower()
await player.clear_queue(queue_type, member)
@require_permission(only_admin=True)
async def updateVolume(player: Player, member: Member, data: Dict) -> None:
volume = data.get("volume", 100)
await MongoDBHandler.update_settings(player.guild.id, {"$set": {"volume": volume}})
await player.set_volume(volume=volume, requester=member)
async def updatePause(player: Player, member: Member, data: Dict) -> None:
pause = data.get("pause", True)
if not player.is_privileged(member):
if pause:
if member in player.pause_votes:
return error_msg(player.get_msg('voting.voted'), user_id=member.id)
player.pause_votes.add(member)
if len(player.pause_votes) < (required := player.required()):
return error_msg(player.get_msg('player.controls.pause.vote').format(member, len(player.pause_votes), required), guild_id=player.guild.id)
else:
if member in player.resume_votes:
return error_msg(player.get_msg('voting.voted'), user_id=member.id)
player.resume_votes.add(member)
if len(player.resume_votes) < (required := player.required()):
return error_msg(player.get_msg('player.controls.resume.vote').format(member, len(player.resume_votes), required), guild_id=player.guild.id)
await player.set_pause(pause, member)
@require_permission()
async def updatePosition(player: Player, member: Member, data: Dict) -> None:
position = data.get("position");
await player.seek(position, member);
async def toggleAutoplay(player: Player, member: Member, data: Dict) -> Dict:
if not player.is_privileged(member):
return error_msg(player.get_msg('permissions.missingAutoPlay'))
check = data.get("status", False)
player.settings['autoplay'] = check
if not player.is_playing:
await player.do_next()
return {
"op": "toggleAutoplay",
"status": check,
"guildId": str(player.guild.id),
"requesterId": str(member.id)
}
@require_permission()
async def updateFilter(player: Player, member: Member, data: Dict) -> None:
updateType = data.get("type", "add")
filter_tag = data.get("tag")
if updateType == "add":
available_filters = Filters.get_available_filters()
filter_cls = available_filters.get(filter_tag)
if not filter_cls:
return
payload = {}
if "payload" in data:
payload = data.get("payload").get(list(data.get("payload").keys()[0]), {})
if player.filters.has_filter(filter_tag=filter_tag):
player.filters.remove_filter(filter_tag=filter_tag)
await player.add_filter(filter=filter_cls(**payload), requester=member)
elif updateType == "remove":
await player.remove_filter(filter_tag=filter_tag, requester=member)
else:
await player.reset_filter(requester=member)
async def _loadPlaylist(playlist: Dict) -> Optional[List[Track]]:
if playlist.get("type") == "link":
tracks: List[Track]= await NodePool.get_node().get_tracks(playlist.get("uri"), requester=None)
if tracks:
return [track.track_id for track in (tracks.tracks if isinstance(tracks, Playlist) else tracks)]
else:
return playlist.get("tracks", [])
def _assign_playlist_id(existed: list) -> str:
for i in range(200, 210):
if str(i) not in existed:
return str(i)
async def _getPlaylist(user_id: int, playlist_id: str) -> Dict:
playlists = await MongoDBHandler.get_user(user_id, d_type="playlist")
playlist = playlists.get(playlist_id)
if not playlist:
return
if playlist["type"] == "share":
target_user = await MongoDBHandler.get_user(playlist["user"], d_type="playlist")
target_playlist = target_user.get(playlist["referId"])
if target_playlist and user_id in target_playlist.get("perms", {}).get("read", []):
playlist["tracks"] = await _loadPlaylist(target_playlist)
else:
playlist["tracks"] = await _loadPlaylist(playlist)
return playlist
async def getPlaylist(bot: commands.Bot, data: Dict) -> Dict:
user_id = int(data.get("userId"))
playlist_id = str(data.get("playlistId"))
payload = {"op": "loadPlaylist", "playlistId": playlist_id, "userId": str(user_id)}
playlist = await _getPlaylist(user_id, playlist_id)
payload["tracks"] = playlist["tracks"] if playlist else []
return payload
async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict:
user_id = int(data.get("userId"))
playlist_id = str(data.get("playlistId"))
_type = data.get("type")
if not playlist_id and not _type == "createPlaylist":
return error_msg("Unable to process this request without a playlist ID.", user_id=user_id, level="error")
max_p, max_t, _ = Config().get_playlist_config()
if _type == "createPlaylist":
name, playlist_url = data.get("playlistName"), data.get("playlistUrl")
if not name:
return {
"op": "updatePlaylist",
"status": "error",
"msg": f"You must enter name for this field!",
"field": "playlistName",
"userId": str(user_id)
}
playlist = await MongoDBHandler.get_user(user_id, d_type="playlist")
if len(list(playlist.keys())) >= max_p:
return {
"op": "updatePlaylist",
"status": "error",
"msg": f"You cannot create more than '{max_p}' playlists!",
"field": "playlistName",
"userId": str(user_id)
}
for playlist_data in playlist.values():
if playlist_data['name'].lower() == name.lower():
return {
"op": "updatePlaylist",
"status": "error",
"msg": f"Playlist '{name}' already exists.",
"field": "playlistName",
"userId": str(user_id)
}
if playlist_url:
tracks = await NodePool.get_node().get_tracks(playlist_url, requester=None)
if not isinstance(tracks, Playlist):
return {
"op": "updatePlaylist",
"status": "error",
"msg": f"Please enter a valid link or public playlist link.",
"field": "playlistUrl",
"userId": str(user_id)
}
assigned_playlist_id = _assign_playlist_id(list(playlist.keys()))
data = {'uri': playlist_url, 'perms': {'read': []}, 'name': name, 'type': 'link'} if playlist_url else {'tracks': [], 'perms': {'read': [], 'write': [], 'remove': []}, 'name': name, 'type': 'playlist'}
await MongoDBHandler.update_user(user_id, {"$set": {f"playlist.{assigned_playlist_id}": data}})
return {
"op": "updatePlaylist",
"status": "created",
"playlistId": assigned_playlist_id,
"msg": f"You have created '{name}' playlist.",
"userId": str(user_id),
"data": data
}
elif _type == "removePlaylist":
playlist = await _getPlaylist(user_id, playlist_id)
if playlist:
if playlist['type'] == 'share':
await MongoDBHandler.update_user(playlist['user'], {"$pull": {f"playlist.{playlist['referId']}.perms.read": user_id}})
await MongoDBHandler.update_user(user_id, {"$unset": {f"playlist.{playlist_id}": 1}})
return {
"op": "updatePlaylist",
"status": "deleted",
"playlistId": playlist_id,
"msg": f"You have removed playlist '{playlist['name']}'",
"userId": str(user_id)
}
elif _type == "renamePlaylist":
name = data.get("name")
if not name:
return {
"op": "updatePlaylist",
"status": "error",
"msg": f"You must enter name for this field!",
"field": "playlistName",
"userId": str(user_id)
}
playlist = await MongoDBHandler.get_user(user_id, d_type="playlist")
for data in playlist.values():
if data['name'].lower() == name.lower():
return {
"op": "updatePlaylist",
"status": "error",
"msg": f"Playlist '{data['name']}' already exists.",
"field": "playlistName",
"userId": str(user_id)
}
await MongoDBHandler.update_user(user_id, {"$set": {f'playlist.{playlist_id}.name': name}})
return {
"op": "updatePlaylist",
"status": "renamed",
"name": name,
"playlistId": playlist_id,
"msg": f"You have renamed the playlist to '{name}'.",
"field": "playlistName",
"userId": str(user_id)
}
elif _type == "addTrack":
track_id = data.get("trackId")
if not track_id:
return error_msg("No track ID could be located.", user_id=user_id, level='error')
playlist = await _getPlaylist(user_id, playlist_id)
if playlist['type'] in ['share', 'link']:
return error_msg("You cannot add songs to a linked playlist through Vocard.", user_id=user_id, level='error')
if len(playlist['tracks']) >= max_t:
return error_msg(f"You have reached the limit! You can only add {max_t} songs to your playlist.", user_id=user_id)
decoded_track = Track(track_id=track_id, info=Track.decode(track_id), requester=None)
if decoded_track.is_stream:
return error_msg("You are not allowed to add streaming videos to your playlist.", user_id=user_id)
await MongoDBHandler.update_user(user_id, {"$push": {f'playlist.{playlist_id}.tracks': track_id}})
return {
"op": "updatePlaylist",
"status": "addTrack",
"playlistId": playlist_id,
"trackId": track_id,
"msg": f"Added {decoded_track.title} into '{playlist['name']}' playlist.",
"userId": str(user_id)
}
elif _type == "removeTrack":
track_id, track_position = data.get("trackId"), data.get("trackPosition", 0)
if not track_id:
return error_msg("No track ID could be located.", user_id=user_id, level='error')
playlist = await _getPlaylist(user_id, playlist_id)
if not playlist:
return error_msg("Playlist not found!", user_id=user_id, level='error')
if playlist['type'] in ['share', 'link']:
return error_msg("You cannot remove songs from a linked playlist through Vocard.", user_id=user_id, level='error')
if not 0 <= track_position < len(playlist['tracks']):
return error_msg("Cannot find the position from your playlist.", user_id=user_id, level="error")
if playlist['tracks'][track_position] != track_id:
return error_msg("Something wrong while removing the track from your playlist.", user_id=user_id, level='error')
await MongoDBHandler.update_user(user_id, {"$pull": {f'playlist.{playlist_id}.tracks': playlist['tracks'][track_position]}})
decoded_track = Track.decode(playlist['tracks'][track_position])
return {
"op": "updatePlaylist",
"status": "removeTrack",
"playlistId": playlist_id,
"trackPosition": track_position,
"trackId": track_id,
"msg": f"Removed '{decoded_track['title']}' from '{playlist['name']}' playlist.",
"userId": str(user_id)
}
elif _type == "updateInbox":
user = await MongoDBHandler.get_user(user_id)
is_accept = data.get("accept", False)
if is_accept and len(list(user.get("playlist").keys())) >= max_p:
return error_msg(f"You cannot create more than '{max_p}' playlists!", user_id=user_id, level = "error")
info = data.get("referId", "").split("-")
sender_id, refer_id = info[0], info[1]
inbox = user.get("inbox")
payload = {"op": "updatePlaylist", "status": "updateInbox", "userId": str(user_id), "accept": is_accept, "senderId": sender_id, "referId": refer_id}
for index, mail in enumerate(inbox.copy()):
if not (str(mail.get("sender")) == sender_id and mail.get("referId") == refer_id):
continue
del inbox[index]
if is_accept:
share_playlists = await MongoDBHandler.get_user(mail["sender"], d_type="playlist")
if refer_id not in share_playlists:
return error_msg("The shared playlist couldnt be found. Its possible that the user has already deleted it.", user_id=user_id)
assigned_playlist_id = _assign_playlist_id(list(user.get("playlist", []).keys()))
playlist_name = f"Share{time.strftime('%M%S', time.gmtime(int(mail['time'])))}"
share_playlist = share_playlists.get(refer_id)
share_playlist.update({
"name": playlist_name,
"type": "share"
})
await MongoDBHandler.update_user(mail['sender'], {"$push": {f"playlist.{mail['referId']}.perms.read": user_id}})
await MongoDBHandler.update_user(user_id, {"$set": {
f'playlist.{assigned_playlist_id}': {
'user': mail['sender'], 'referId': mail['referId'],
'name': playlist_name,
'type': 'share'
},
"inbox": inbox
}})
payload.update({
"playlistId": assigned_playlist_id,
"msg": f"You have created '{playlist_name}' playlist.",
"data": share_playlist,
})
await MongoDBHandler.update_user(user_id, {"$set": {"inbox": inbox}})
return payload
async def getMutualGuilds(bot: commands.Bot, data: Dict) -> Dict:
user_id = int(data.get("userId"))
payload = {"op": "getMutualGuilds", "mutualGuilds": {}, "inviteGuilds": {}, "userId": str(user_id)}
for guild_id, guild_info in data.get("guilds", {}).items():
if guild := bot.get_guild(int(guild_id)):
payload["mutualGuilds"][guild_id] = {
**guild_info,
"memberCount": guild.member_count
}
else:
payload["inviteGuilds"][guild_id] = {**guild_info}
return payload
async def getSettings(bot: commands.Bot, data: Dict) -> Dict:
user_id = int(data.get("userId"))
guild_id = int(data.get("guildId"))
guild = bot.get_guild(guild_id)
if not guild:
return error_msg("Vocard don't have access to requested guild.", user_id=user_id, level="error")
member = guild.get_member(user_id)
if not member:
return error_msg("You are not in the requested guild.", user_id=user_id, level="error")
if not member.guild_permissions.manage_guild:
return error_msg("You don't have permission to access the settings.", user_id=user_id, level='error')
settings = await MongoDBHandler.get_settings(guild_id)
if "dj" in settings:
role = guild.get_role(settings["dj"])
if role:
settings["dj"] = role.name
return {
"op": "getSettings",
"settings": settings,
"options": {
"languages": list(LangHandler.get_all_languages()),
"queueModes": ["Queue", "FairQueue"],
"roles": [role.name for role in guild.roles]
},
"guild": {
"avatar": guild.icon.url if guild.icon else None,
"name": guild.name,
"id": str(guild_id)
},
"userId": str(user_id)
}
async def getLyrics(bot: commands.Bot, data: Dict) -> Dict:
title, artist, platform = data.get("title", ""), data.get("artist", ""), data.get("platform", "")
if not platform or platform not in LYRICS_PLATFORMS:
platform = Config().lyrics_platform
lyrics_platform = LYRICS_PLATFORMS.get(platform)
if lyrics_platform:
lyrics: dict[str, str] = await lyrics_platform().get_lyrics(title, artist)
return {
"op": "getLyrics",
"userId": data.get("userId"),
"title": title,
"artist": artist,
"platform": platform,
"lyrics": {_: re.findall(r'.*\n(?:.*\n){,22}', v or "") for _, v in lyrics.items()} if lyrics else {},
"callback": data.get("callback")
}
async def updateSettings(bot: commands.Bot, data: Dict) -> None:
user_id = int(data.get("userId"))
guild_id = int(data.get("guildId"))
guild = bot.get_guild(guild_id)
if not guild:
return error_msg("Vocard don't have access to required guild.", user_id=user_id, level="error")
member = guild.get_member(user_id)
if not member:
return error_msg("You are not in the required guild.", user_id=user_id, level="error")
if not member.guild_permissions.manage_guild:
return error_msg("You don't have permission to change the settings.", user_id=user_id, level='error')
data = data.get("settings", {})
if "dj" in data:
for role in guild.roles:
if role.name.lower() == data["dj"]:
data["dj"] = role.id
break
for key, value in data.copy().items():
if key not in SCOPES or not isinstance(value, SCOPES[key]):
del data[key]
await MongoDBHandler.update_settings(guild.id, {"$set": data})
METHODS: Dict[str, Union[SystemMethod, PlayerMethod]] = {
"initBot": SystemMethod(initBot, credit=0),
"initUser": SystemMethod(initUser, credit=2),
"getPlaylist": SystemMethod(getPlaylist),
"updatePlaylist": SystemMethod(updatePlaylist, credit=2),
"getMutualGuilds": SystemMethod(getMutualGuilds),
"getSettings": SystemMethod(getSettings),
"getLyrics": SystemMethod(getLyrics),
"updateSettings": SystemMethod(updateSettings),
"getRecommendation": SystemMethod(getRecommendation, credit=5),
"closeConnection": SystemMethod(closeConnection, credit=0),
"getTracks": SystemMethod(getTracks, credit=5),
"initPlayer": PlayerMethod(initPlayer),
"skipTo": PlayerMethod(skipTo),
"backTo": PlayerMethod(backTo),
"moveTrack": PlayerMethod(moveTrack),
"addTracks": PlayerMethod(addTracks, auto_connect=True),
"shuffleTrack": PlayerMethod(shuffleTrack, credit=3),
"repeatTrack": PlayerMethod(repeatTrack),
"removeTrack": PlayerMethod(removeTrack),
"clearQueue": PlayerMethod(clearQueue),
"updateVolume": PlayerMethod(updateVolume, credit=2),
"updatePause": PlayerMethod(updatePause),
"updatePosition": PlayerMethod(updatePosition),
"toggleAutoplay": PlayerMethod(toggleAutoplay),
"updateFilter": PlayerMethod(updateFilter),
"searchAndPlay": PlayerMethod(searchAndPlay, credit=5, auto_connect=True)
}
async def process_methods(ipc_client: IPCClient, bot: commands.Bot, data: Dict) -> None:
op: str = data.get("op", "")
method = METHODS.get(op)
if not method or not (user_id := data.get("userId")):
return
user_id = int(user_id)
if user_id not in RATELIMIT_COUNTER or (time.time() - RATELIMIT_COUNTER[user_id]["time"]) >= 300:
RATELIMIT_COUNTER[user_id] = {"time": time.time(), "count": 0}
else:
if RATELIMIT_COUNTER[user_id]["count"] >= 100:
return await ipc_client.send({"op": "rateLimited", "userId": str(user_id)})
RATELIMIT_COUNTER[user_id]["count"] += method.credit
try:
env: Dict = {"bot": bot, "data": data}
args: List = []
params = method.params
if not (type(method) == SystemMethod):
if guild_id := data.get("guildId"):
if (guild := bot.get_guild(int(guild_id))):
env["guild"] = guild
else:
user: User = bot.get_user(user_id)
if not user:
return
for guild in user.mutual_guilds:
member = guild.get_member(user_id)
if member.voice and member.voice.channel:
env["guild"] = guild
env["member"] = member
break
if "member" in params and "member" not in env:
if not (guild := env.get("guild")) or not (member := guild.get_member(user_id)):
return
env["member"] = member
if "player" in params:
if not (guild := env.get("guild")) or not (player := guild.voice_client):
if not method.auto_connect or not (member := env.get("member")):
return
player = await connect_channel(member, bot)
if not player or player.channel.id != member.voice.channel.id:
return
env["player"] = player
for param in params:
args.append(env.get(param))
if resp := await method.function(*args):
await ipc_client.send(resp)
except Exception as e:
ipc_client._logger.error(f"Failed to process method '{op}' by user {user_id}", exc_info=e)
payload = {
"op": "errorMsg",
"level": "error",
"msg": str(e),
"userId": str(user_id)
}
await ipc_client.send(payload)