Improving the Playlist Function Implementation

This commit is contained in:
Choco
2024-02-11 19:56:38 +08:00
parent 95ec0970eb
commit cc4ccd968f
3 changed files with 113 additions and 74 deletions

View File

@@ -31,7 +31,6 @@ from function import (
get_playlist,
check_roles,
update_playlist,
update_inbox,
get_lang,
settings,
get_aliases,
@@ -46,19 +45,19 @@ def assign_playlistId(existed: list) -> str:
if str(i) not in existed:
return str(i)
async def check_playlist_perms(userid: int, authorid: int, dId: str) -> dict:
playlist = await get_playlist(authorid, 'playlist', dId)
if not playlist or userid not in playlist['perms']['read']:
async def check_playlist_perms(user_id: int, author_id: int, d_id: str) -> dict:
playlist = await get_playlist(author_id, 'playlist', d_id)
if not playlist or user_id not in playlist['perms']['read']:
return {}
return playlist
async def check_playlist(ctx: commands.Context, name: str = None, full: bool = False, share: bool = True) -> dict:
user = await get_playlist(ctx.author.id, 'playlist')
if not user:
return None
await ctx.defer()
if full:
return user
if not name:
return {'playlist': user['200'], 'position': 1, 'id': "200"}
@@ -77,11 +76,10 @@ async def search_playlist(url: str, requester: discord.Member, timeNeed=True):
tracks = await voicelink.NodePool.get_node().get_tracks(url, requester=requester)
tracks = {"name": tracks.name, "tracks": tracks.tracks}
if timeNeed:
time = 0
for track in tracks['tracks']:
time += track.length
time = sum([track for track in tracks["tracks"]])
except:
return None
return tracks | ({'time': ctime(time)} if timeNeed else {})
class Playlists(commands.Cog, name="playlist"):
@@ -90,11 +88,8 @@ class Playlists(commands.Cog, name="playlist"):
self.description = "This is the Vocard playlist system. You can save your favorites and use Vocard to play on any server."
async def playlist_autocomplete(self, interaction: discord.Interaction, current: str) -> list:
playlists = PLAYLIST_NAME.get(str(interaction.user.id), None)
if not playlists:
playlists_raw = await get_playlist(interaction.user.id, 'playlist')
playlists = PLAYLIST_NAME[str(interaction.user.id)] = [
value['name'] for value in playlists_raw.values()] if playlists_raw else []
playlists_raw: dict[str, dict] = await get_playlist(interaction.user.id, 'playlist')
playlists = [value['name'] for value in playlists_raw.values()] if playlists_raw else []
if current:
return [app_commands.Choice(name=p, value=p) for p in playlists if current in p]
return [app_commands.Choice(name=p, value=p) for p in playlists]
@@ -168,16 +163,19 @@ class Playlists(commands.Cog, name="playlist"):
if playlist['type'] == 'link':
tracks = await search_playlist(playlist['uri'], requester=ctx.author)
results.append({'emoji': ('🔒' if max_p < index else '🔗'), 'id': data, 'time': tracks['time'], 'name': playlist['name'], 'tracks': tracks['tracks'], 'perms': playlist['perms'], 'type': playlist['type']})
else:
if share := playlist['type'] == 'share':
playlist = await check_playlist_perms(ctx.author.id, playlist['user'], playlist['referId'])
if not playlist:
await update_playlist(ctx.author.id, {f"playlist.{data}": 1}, mode="unset")
await update_playlist(ctx.author.id, {"$unset": {f"playlist.{data}": 1}})
continue
if playlist['type'] == 'link':
tracks = await search_playlist(playlist['uri'], requester=ctx.author)
results.append({'emoji': ('🔒' if max_p < index else '🤝'), 'id': data, 'time': tracks['time'], 'name': user[data]['name'], 'tracks': tracks['tracks'], 'perms': playlist['perms'], 'owner': user[data]['user'], 'type': 'share'})
continue
init = []
for track in playlist['tracks']:
dt = voicelink.decode(track)
@@ -186,12 +184,14 @@ class Playlists(commands.Cog, name="playlist"):
playlist['tracks'] = init
results.append({'emoji': ('🔒' if max_p < index else ('🤝' if share else '❤️')), 'id': data, 'time': ctime(time), 'name': user[data]['name'], 'tracks': playlist['tracks'], 'perms': playlist['perms'], 'owner': user[data].get('user', None), 'type': user[data]['type']})
except Exception as e:
except:
results.append({'emoji': '', 'id': data, 'time': '00:00', 'name': 'Error', 'tracks': [], 'type': 'error'})
embed = discord.Embed(title=get_lang(ctx.guild.id, 'playlistViewTitle').format(ctx.author.name),
description='```%0s %4s %10s %10s %10s\n' % tuple(get_lang(ctx.guild.id, 'playlistViewHeaders')) + '\n'.join('%0s %3s. %10s %10s %10s' % (info['emoji'], info['id'], f"[{info['time']}]", info['name'], len(info['tracks'])) for info in results) + '```',
color=settings.embed_color)
embed = discord.Embed(
title=get_lang(ctx.guild.id, 'playlistViewTitle').format(ctx.author.display_name),
description='```%0s %4s %10s %10s %10s\n' % tuple(get_lang(ctx.guild.id, 'playlistViewHeaders')) + '\n'.join('%0s %3s. %10s %10s %10s' % (info['emoji'], info['id'], f"[{info['time']}]", info['name'], len(info['tracks'])) for info in results) + '```',
color=settings.embed_color
)
embed.add_field(name=get_lang(ctx.guild.id, 'playlistMaxP'), value=f"{len(user)}/{max_p}", inline=True)
embed.add_field(name=get_lang(ctx.guild.id, 'playlistMaxT'), value=f"{max_t}", inline=True)
@@ -226,7 +226,7 @@ class Playlists(commands.Cog, name="playlist"):
return await ctx.send(get_lang(ctx.guild.id, 'playlistNotInvaildUrl'), ephemeral=True)
data = {'uri': link, 'perms': {'read': []}, 'name': name, 'type': 'link'} if link else {'tracks': [], 'perms': {'read': [], 'write': [], 'remove': []}, 'name': name, 'type': 'playlist'}
await update_playlist(ctx.author.id, {f"playlist.{assign_playlistId([data for data in user])}": data}, update_cache=True)
await update_playlist(ctx.author.id, {"$set": {f"playlist.{assign_playlistId([data for data in user])}": data}})
await ctx.send(get_lang(ctx.guild.id, 'playlistCreated').format(name))
@playlist.command(name="delete", aliases=get_aliases("delete"))
@@ -242,9 +242,9 @@ class Playlists(commands.Cog, name="playlist"):
return await ctx.send(get_lang(ctx.guild.id, 'playlistDeleteError'), ephemeral=True)
if result['playlist']['type'] == 'share':
await update_playlist(result['playlist']['user'], {f"playlist.{result['playlist']['referId']}.perms.read": ctx.author.id}, mode="pull")
await update_playlist(result['playlist']['user'], {"$pull": {f"playlist.{result['playlist']['referId']}.perms.read": ctx.author.id}})
await update_playlist(ctx.author.id, {f"playlist.{result['id']}": 1}, mode="unset", update_cache=True)
await update_playlist(ctx.author.id, {"$unset": {f"playlist.{result['id']}": 1}})
return await ctx.send(get_lang(ctx.guild.id, 'playlistRemove').format(result['playlist']['name']))
@playlist.command(name="share", aliases=get_aliases("share"))
@@ -278,7 +278,17 @@ class Playlists(commands.Cog, name="playlist"):
if len(receiver['inbox']) >= 10:
return await ctx.send(get_lang(ctx.guild.id, 'inboxFull').format(member), ephemeral=True)
await update_inbox(member.id, {'sender': ctx.author.id, 'referId': result['id'], 'time': datetime.now(), 'title': f'Playlist invitation from {ctx.author}', 'description': f"You are invited to use this playlist.\nPlaylist Name: {result['playlist']['name']}\nPlaylist type: {result['playlist']['type']}", 'type': 'invite'})
await update_playlist(
member.id,
{"$push": {"inbox": {
'sender': ctx.author.id,
'referId': result['id'],
'time': datetime.now(),
'title': f'Playlist invitation from {ctx.author}',
'description': f"You are invited to use this playlist.\nPlaylist Name: {result['playlist']['name']}\nPlaylist type: {result['playlist']['type']}",
'type': 'invite'
}}}
)
return await ctx.send(get_lang(ctx.guild.id, 'invitationSent').format(member))
@playlist.command(name="rename", aliases=get_aliases("rename"))
@@ -305,7 +315,7 @@ class Playlists(commands.Cog, name="playlist"):
if not found:
return await ctx.send(get_lang(ctx.guild.id, 'playlistNotFound').format(name), ephemeral=True)
await update_playlist(ctx.author.id, {f'playlist.{id}.name': newname}, update_cache=True)
await update_playlist(ctx.author.id, {"$set": {f'playlist.{id}.name': newname}})
await ctx.send(get_lang(ctx.guild.id, 'playlistRenamed').format(name, newname))
@playlist.command(name="inbox", aliases=get_aliases("inbox"))
@@ -313,6 +323,8 @@ class Playlists(commands.Cog, name="playlist"):
async def inbox(self, ctx: commands.Context) -> None:
"Show your playlist invitation."
user = await get_playlist(ctx.author.id)
rank, max_p, max_t = check_roles()
if not user['inbox']:
return await ctx.send(get_lang(ctx.guild.id, 'inboxNoMsg'), ephemeral=True)
@@ -323,15 +335,20 @@ class Playlists(commands.Cog, name="playlist"):
if inbox == user['inbox']:
return
updateData, dId = {}, {dId for dId in user["playlist"]}
for data in view.newplaylist[:(5 - len(user['playlist']))]:
update_data, dId = {}, {dId for dId in user["playlist"]}
for data in view.newplaylist[:(max_p - len(user['playlist']))]:
addId = assign_playlistId(dId)
await update_playlist(data['sender'], {f"playlist.{data['referId']}.perms.read": ctx.author.id}, mode="push")
updateData[f'playlist.{addId}'] = {'user': data['sender'], 'referId': data['referId'],
'name': f"Share{data['time'].strftime('%M%S')}", 'type': 'share'}
await update_playlist(data['sender'], {"$push": {f"playlist.{data['referId']}.perms.read": ctx.author.id}})
update_data[f'playlist.{addId}'] = {
'user': data['sender'], 'referId': data['referId'],
'name': f"Share{data['time'].strftime('%M%S')}", 'type': 'share'
}
update_data["inbox"] = view.inbox
dId.add(addId)
await update_playlist(ctx.author.id, updateData | {'inbox': view.inbox}, update_cache=True)
if update_data:
await update_playlist(ctx.author.id, {"$set": update_data})
@playlist.command(name="add", aliases=get_aliases("add"))
@commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild)
@@ -362,7 +379,7 @@ class Playlists(commands.Cog, name="playlist"):
if results[0].is_stream:
return await ctx.send(get_lang(ctx.guild.id, 'playlistStream'), ephemeral=True)
await update_playlist(ctx.author.id, {f'playlist.{result["id"]}.tracks': results[0].track_id}, mode="push")
await update_playlist(ctx.author.id, {"$push": {f'playlist.{result["id"]}.tracks': results[0].track_id}})
await ctx.send(get_lang(ctx.guild.id, 'playlistAdded').format(results[0].title, ctx.author, result['playlist']['name']))
@playlist.command(name="remove", aliases=get_aliases("remove"))
@@ -382,7 +399,7 @@ class Playlists(commands.Cog, name="playlist"):
if not 0 < position <= len(result['playlist']['tracks']):
return await ctx.send(get_lang(ctx.guild.id, 'playlistPositionNotFound').format(position, name))
await update_playlist(ctx.author.id, {f'playlist.{result["id"]}.tracks': result['playlist']['tracks'][position - 1]}, mode="pull")
await update_playlist(ctx.author.id, {"$pull": {f'playlist.{result["id"]}.tracks': result['playlist']['tracks'][position - 1]}})
track = voicelink.decode(result['playlist']['tracks'][position - 1])
await ctx.send(get_lang(ctx.guild.id, 'playlistRemoved').format(track.get("title"), ctx.author, name))
@@ -399,7 +416,7 @@ class Playlists(commands.Cog, name="playlist"):
if result['playlist']['type'] in ['link', 'share']:
return await ctx.send(get_lang(ctx.guild.id, 'playlistNotAllow'), ephemeral=True)
await update_playlist(ctx.author.id, {f'playlist.{result["id"]}.tracks': []})
await update_playlist(ctx.author.id, {"$set": {f'playlist.{result["id"]}.tracks': []}})
await ctx.send(get_lang(ctx.guild.id, 'playlistClear').format(name))
@playlist.command(name="export", aliases=get_aliases("export"))
@@ -408,8 +425,6 @@ class Playlists(commands.Cog, name="playlist"):
async def export(self, ctx: commands.Context, name: str) -> None:
"Exports the entire playlist to a text file"
result = await check_playlist(ctx, name.lower())
if not result:
return await create_account(ctx)
if not result['playlist']:
return await ctx.send(get_lang(ctx.guild.id, 'playlistNotFound').format(name), ephemeral=True)
@@ -472,7 +487,7 @@ class Playlists(commands.Cog, name="playlist"):
track_ids = track_ids.decode().split(",")
data = {'tracks': track_ids, 'perms': {'read': [], 'write': [], 'remove': []}, 'name': name, 'type': 'playlist'}
await update_playlist(ctx.author.id, {f"playlist.{assign_playlistId([data for data in user])}": data}, update_cache=True)
await update_playlist(ctx.author.id, {"$set": {f"playlist.{assign_playlistId([data for data in user])}": data}})
await ctx.send(get_lang(ctx.guild.id, 'playlistCreated').format(name))
except:

View File

@@ -111,7 +111,7 @@ class Task(commands.Cog):
@tasks.loop(hours=12.0)
async def cache_cleaner(self):
func.SETTINGS_BUFFER.clear()
func.PLAYLIST_NAME.clear()
func.PLAYLISTS_BUFFER.clear()
errorFile = func.gen_report()
if errorFile:

View File

@@ -1,10 +1,10 @@
import discord, json, os
import discord, json, os, copy
from discord.ext import commands
from datetime import datetime
from time import strptime
from io import BytesIO
from typing import Optional, Union, Any
from typing import Optional, Dict, Any
from addons import Settings, TOKENS
from motor.motor_asyncio import (
@@ -29,7 +29,19 @@ ERROR_LOGS: dict[int, dict[int, str]] = {} #Stores error that not a Voicelink Ex
LANGS: dict[str, dict[str, str]] = {} #Stores all the languages in ./langs
SETTINGS_BUFFER: dict[int, dict[str, Any]] = {} #Cache guild language
LOCAL_LANGS: dict[str, dict[str, str]] = {} #Stores all the localization languages in ./local_langs
PLAYLIST_NAME: dict[str, list[str]] = {} #Cache the user's playlist name
PLAYLISTS_BUFFER: dict[str, dict] = {}
PLAYLIST_BASE: dict[str, Any] = {
'playlist': {
'200': {
'tracks':[],
'perms': {'read': [], 'write':[], 'remove': []},
'name':'Favourite',
'type':'playlist'
}
},
'inbox':[]
}
#-------------- Vocard Functions --------------
def open_json(path: str) -> dict:
@@ -50,7 +62,7 @@ def update_json(path: str, new_data: dict) -> None:
json.dump(data, json_file, indent=4)
def get_lang(guild_id:int, key:str) -> str:
lang = SETTINGS_BUFFER.get(guild_id).get("lang", "EN")
lang = SETTINGS_BUFFER.get(guild_id, {}).get("lang", "EN")
if lang in LANGS and not LANGS[lang]:
LANGS[lang] = open_json(os.path.join("langs", f"{lang}.json"))
@@ -122,23 +134,12 @@ def get_aliases(name: str) -> list:
def check_roles() -> tuple[str, int, int]:
return 'Normal', 5, 500
async def get_settings(guild_id:int) -> dict[str, Any]:
settings = SETTINGS_BUFFER.get(guild_id, None)
if not settings:
settings = await SETTINGS_DB.find_one({"_id": guild_id})
if not settings:
await SETTINGS_DB.insert_one({"_id": guild_id})
settings = SETTINGS_BUFFER[guild_id] = settings or {}
return settings
async def update_settings(guild_id: int, data: dict[str, dict[str, Any]]) -> bool:
settings = await get_settings(guild_id)
async def update_db(db: AsyncIOMotorCollection, tempStore: dict, filter: dict, data: dict) -> bool:
for mode, action in data.items():
for key, value in action.items():
cursors = key.split(".")
nested_data = settings
nested_data = tempStore
for c in cursors[:-1]:
nested_data = nested_data.setdefault(c, {})
@@ -151,32 +152,55 @@ async def update_settings(guild_id: int, data: dict[str, dict[str, Any]]) -> boo
elif mode == "$unset":
nested_data.pop(cursors[-1], None)
elif mode == "$inc":
nested_data[cursors[-1]] = nested_data.get(cursors[-1], 0) + value
elif mode == "$push":
nested_data.setdefault(cursors[-1], []).extend([value])
elif mode == "$pull":
if cursors[-1] in nested_data:
value = value.get("$in", []) if isinstance(value, dict) else [value]
nested_data[cursors[-1]] = [item for item in nested_data[cursors[-1]] if item not in value]
else:
return False
result = await SETTINGS_DB.update_one({"_id": guild_id}, data)
result = await db.update_one(filter, data)
return result.modified_count > 0
async def create_account(ctx: Union[commands.Context, discord.Interaction]) -> None:
async def get_settings(guild_id:int) -> dict[str, Any]:
settings = SETTINGS_BUFFER.get(guild_id, None)
if not settings:
settings = await SETTINGS_DB.find_one({"_id": guild_id})
if not settings:
await SETTINGS_DB.insert_one({"_id": guild_id})
async def get_playlist(user_id:int, dType:str=None, dId:str=None) -> bool:
user = await PLAYLISTS_DB.find_one({"_id":user_id}, {"_id": 0})
if not user:
return None
if dType:
if dId and dType == "playlist":
return user[dType][dId] if dId in user[dType] else None
return user[dType]
return user
settings = SETTINGS_BUFFER[guild_id] = settings or {}
return settings
async def update_playlist(user_id:int, data:dict, *, mode:str="set", update_cache: bool=False) -> None:
if update_cache:
PLAYLIST_NAME.pop(str(user_id), None)
result = await PLAYLISTS_DB.update_one({"_id":user_id}, {f"${mode}": data})
return result.modified_count > 0
async def update_settings(guild_id: int, data: dict[str, dict[str, Any]]) -> bool:
settings = await get_settings(guild_id)
return await update_db(SETTINGS_DB, settings, {"_id": guild_id}, data)
async def get_playlist(user_id: int, d_type: Optional[str] = None, d_id: Optional[str] = None, need_copy: bool = True) -> Dict[str, Any]:
playlist = PLAYLISTS_BUFFER.get(user_id)
if not playlist:
playlist = await PLAYLISTS_DB.find_one({"_id": user_id})
if not playlist:
playlist = {"_id": user_id, **PLAYLIST_BASE}
await PLAYLISTS_DB.insert_one(playlist)
PLAYLISTS_BUFFER[user_id] = playlist
async def update_inbox(user_id:int, data:dict) -> bool:
result = await PLAYLISTS_DB.update_one({"_id":user_id}, {"$push":{'inbox':data}})
return result.modified_count > 0
if d_type:
if d_id and d_type == "playlist":
playlist = playlist[d_type].get(d_id)
else:
playlist = playlist.get(d_type)
return copy.deepcopy(playlist) if need_copy else playlist
async def update_playlist(user_id:int, data:dict) -> None:
playlist = await get_playlist(user_id, need_copy=False)
return await update_db(PLAYLISTS_DB, playlist, {"_id": user_id}, data)