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.
801 lines
31 KiB
Python
801 lines
31 KiB
Python
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 couldn’t be found. It’s 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) |