From 33ad427670c7415676fc38ea3adec0ebe8bbab44 Mon Sep 17 00:00:00 2001 From: Choco <94597336+ChocoMeow@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:06:18 +0800 Subject: [PATCH] Added start and end position for each track --- cogs/basic.py | 39 +++++++++++++++++++++------------- ipc/methods.py | 24 ++++++++++++++++++--- langs/CH.json | 5 ++++- langs/DE.json | 5 ++++- langs/EN.json | 5 ++++- langs/ES.json | 5 ++++- langs/JA.json | 5 ++++- langs/KO.json | 5 ++++- langs/RU.json | 5 ++++- langs/UA.json | 5 ++++- update.py | 2 +- voicelink/objects.py | 5 ++++- voicelink/player.py | 50 ++++++++++++++++++++++++++++++-------------- 13 files changed, 117 insertions(+), 43 deletions(-) diff --git a/cogs/basic.py b/cogs/basic.py index 6e0f50b..682c378 100644 --- a/cogs/basic.py +++ b/cogs/basic.py @@ -30,7 +30,6 @@ from function import ( settings, send, time as ctime, - formatTime, format_time, get_source, get_user, @@ -122,10 +121,14 @@ class Basic(commands.Cog): await send(ctx, 'connect', player.channel) @commands.hybrid_command(name="play", aliases=get_aliases("play")) - @app_commands.describe(query="Input a query or a searchable link.") + @app_commands.describe( + query="Input a query or a searchable link.", + start="Specify a time you would like to start, e.g. 1:00", + end="Specify a time you would like to end, e.g. 4:00" + ) @app_commands.autocomplete(query=play_autocomplete) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) - async def play(self, ctx: commands.Context, *, query: str) -> None: + async def play(self, ctx: commands.Context, *, query: str, start: str = "0", end: str = "0") -> None: "Loads your input and added it to the queue." player: voicelink.Player = ctx.guild.voice_client if not player: @@ -140,10 +143,10 @@ class Basic(commands.Cog): try: if isinstance(tracks, voicelink.Playlist): - index = await player.add_track(tracks.tracks) + index = await player.add_track(tracks.tracks, start_time=format_time(start), end_time=format_time(end)) await send(ctx, "playlistLoad", tracks.name, index) else: - position = await player.add_track(tracks[0]) + position = await player.add_track(tracks[0], start_time=format_time(start), end_time=format_time(end)) texts = await get_lang(ctx.guild.id, "live", "trackLoad_pos", "trackLoad") await ctx.send((f"`{texts[0]}`" if tracks[0].is_stream else "") + (texts[1].format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length, position) if position >= 1 and player.is_playing else texts[2].format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length)), allowed_mentions=False) except voicelink.QueueFull as e: @@ -250,10 +253,14 @@ class Basic(commands.Cog): await player.do_next() @commands.hybrid_command(name="playtop", aliases=get_aliases("playtop")) - @app_commands.describe(query="Input a query or a searchable link.") + @app_commands.describe( + query="Input a query or a searchable link.", + start="Specify a time you would like to start, e.g. 1:00", + end="Specify a time you would like to end, e.g. 4:00" + ) @app_commands.autocomplete(query=play_autocomplete) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) - async def playtop(self, ctx: commands.Context, *, query: str): + async def playtop(self, ctx: commands.Context, *, query: str, start: str = "0", end: str = "0"): "Adds a song with the given url or query on the top of the queue." player: voicelink.Player = ctx.guild.voice_client if not player: @@ -268,10 +275,10 @@ class Basic(commands.Cog): try: if isinstance(tracks, voicelink.Playlist): - index = await player.add_track(tracks.tracks, at_font=True) + index = await player.add_track(tracks.tracks, start_time=format_time(start), end_time=format_time(end), at_font=True) await send(ctx, "playlistLoad", tracks.name, index) else: - position = await player.add_track(tracks[0], at_font=True) + position = await player.add_track(tracks[0], start_time=format_time(start), end_time=format_time(end), at_font=True) texts = await get_lang(ctx.guild.id, "live", "trackLoad_pos", "trackLoad") await ctx.send((f"`{texts[0]}`" if tracks[0].is_stream else "") + (texts[1].format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length, position) if position >= 1 and player.is_playing else texts[2].format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length)), allowed_mentions=False) @@ -283,9 +290,13 @@ class Basic(commands.Cog): await player.do_next() @commands.hybrid_command(name="forceplay", aliases=get_aliases("forceplay")) - @app_commands.describe(query="Input a query or a searchable link.") + @app_commands.describe( + query="Input a query or a searchable link.", + start="Specify a time you would like to start, e.g. 1:00", + end="Specify a time you would like to end, e.g. 4:00" + ) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) - async def forceplay(self, ctx: commands.Context, query: str): + async def forceplay(self, ctx: commands.Context, *, query: str, start: str = "0", end: str = "0"): "Enforce playback using the given URL or query." player: voicelink.Player = ctx.guild.voice_client if not player: @@ -300,11 +311,11 @@ class Basic(commands.Cog): try: if isinstance(tracks, voicelink.Playlist): - index = await player.add_track(tracks.tracks, at_font=True) + index = await player.add_track(tracks.tracks, start_time=format_time(start), end_time=format_time(end), at_font=True) await send(ctx, "playlistLoad", tracks.name, index) else: texts = await get_lang(ctx.guild.id, "live", "trackLoad") - await player.add_track(tracks[0], at_font=True) + await player.add_track(tracks[0], start_time=format_time(start), end_time=format_time(end), at_font=True) await ctx.send((f"`{texts[0]}`" if tracks[0].is_stream else "") + texts[1].format(tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length), allowed_mentions=False) except voicelink.QueueFull as e: @@ -404,7 +415,7 @@ class Basic(commands.Cog): return await send(ctx, "noPlayer", ephemeral=True) if not player.node._available: - return await send(ctx, "nnodeReconnectode") + return await send(ctx, "nodeReconnectode") if not player.is_privileged(ctx.author): if ctx.author in player.previous_votes: diff --git a/ipc/methods.py b/ipc/methods.py index e87b84c..06a4c82 100644 --- a/ipc/methods.py +++ b/ipc/methods.py @@ -1,4 +1,5 @@ import time +import time, re import function as func from datetime import timezone @@ -8,6 +9,7 @@ from typing import List, Dict, Union, Optional from discord import User, Member, VoiceChannel from discord.ext import commands from voicelink import Player, Track, Playlist, NodePool, decode, LoopType +from addons import lyricsPlatform RATELIMIT_COUNTER: Dict[int, Dict[str, float]] = {} SCOPES = { @@ -560,6 +562,21 @@ async def getSettings(bot: commands.Bot, data: Dict) -> Dict: "user_id": 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 lyricsPlatform: + platform = func.settings.lyrics_platform + + song: dict[str, str] = await lyricsPlatform.get(platform)().get_lyrics(title, artist) + payload = { + "op": "getLyrics", + "user_id": data.get("user_id"), + "lyrics": {_: re.findall(r'.*\n(?:.*\n){,22}', v) for _, v in song.items()} if song else {}, + "callback": data.get("callback") + } + + return payload + async def updateSettings(bot: commands.Bot, data: Dict) -> None: user_id = int(data.get("user_id")) guild_id = int(data.get("guild_id")) @@ -588,22 +605,23 @@ async def updateSettings(bot: commands.Bot, data: Dict) -> None: await func.update_settings(guild.id, {"$set": data}) -methods: Dict[str, Union[SystemMethod, PlayerMethod]] = { +METHODS: Dict[str, Union[SystemMethod, PlayerMethod]] = { "initBot": SystemMethod(initBot, credit=0), "initUser": SystemMethod(initUser, credit=0), "getPlaylist": SystemMethod(getPlaylist), "updatePlaylist": SystemMethod(updatePlaylist), "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), - "getTracks": PlayerMethod(getTracks, auto_connect=True), "shuffleTrack": PlayerMethod(shuffleTrack, credit=3), "repeatTrack": PlayerMethod(repeatTrack), "removeTrack": PlayerMethod(removeTrack), @@ -615,7 +633,7 @@ methods: Dict[str, Union[SystemMethod, PlayerMethod]] = { async def process_methods(ipc_client, bot: commands.Bot, data: Dict) -> None: op: str = data.get("op", "") - method = methods.get(op) + method = METHODS.get(op) if not method or not (user_id := data.get("user_id")): return diff --git a/langs/CH.json b/langs/CH.json index 5cef1eb..510bb5d 100644 --- a/langs/CH.json +++ b/langs/CH.json @@ -187,5 +187,8 @@ "voicelinkOutofList": "請提供有效的歌曲索引!", "voicelinkDuplicateTrack": "抱歉,此歌曲已在隊列中。", - "deocdeError": "解碼文件時出現問題!" + "deocdeError": "解碼文件時出現問題!", + "invalidStartTime": "無效的開始時間! 時間必須在 `00:00` 和 `{0}` 之間。", + "invalidEndTime": "無效的結束時間! 時間必須在 `00:00` 和 `{0}` 之間。", + "invalidTimeOrder": "結束時間不能小於或等於開始時間。" } \ No newline at end of file diff --git a/langs/DE.json b/langs/DE.json index b5602da..f636b3f 100644 --- a/langs/DE.json +++ b/langs/DE.json @@ -187,5 +187,8 @@ "voicelinkOutofList": "Bitte gib einen gültigen Track-Index an!", "voicelinkDuplicateTrack": "Entschuldigung, dieser Track ist bereits in der Warteschlange.", - "deocdeError": "Beim Dekodieren der Datei ist etwas schief gelaufen!" + "deocdeError": "Beim Dekodieren der Datei ist etwas schief gelaufen!", + "invalidStartTime": "Ungültiger Startzeit! Die Zeit muss innerhalb von `00:00` und `{0}` liegen.", + "invalidEndTime": "Ungültiger Endzeit! Die Zeit muss innerhalb von `00:00` und `{0}` liegen.", + "invalidTimeOrder": "Der Endzeit darf nicht kleiner oder gleich dem Startzeit sein." } \ No newline at end of file diff --git a/langs/EN.json b/langs/EN.json index ef0b595..1ad9501 100644 --- a/langs/EN.json +++ b/langs/EN.json @@ -187,5 +187,8 @@ "voicelinkOutofList": "Please provide a valid track index!", "voicelinkDuplicateTrack": "Sorry, this track is already in the queue.", - "deocdeError": "Something went wrong while decoding the file!" + "deocdeError": "Something went wrong while decoding the file!", + "invalidStartTime": "Invalid start time, it must be between `00:00` and `{0}`", + "invalidEndTime": "Invalid end time, it must be between `00:00` and `{0}`", + "invalidTimeOrder": "End time cannot be less than or equal to start time" } \ No newline at end of file diff --git a/langs/ES.json b/langs/ES.json index f9a81e9..9e28e6a 100644 --- a/langs/ES.json +++ b/langs/ES.json @@ -187,5 +187,8 @@ "voicelinkOutofList": "¡Proporcione un índice de pista válido!", "voicelinkDuplicateTrack": "Lo siento, esta canción ya está en la cola.", - "deocdeError": "¡Algo salió mal al decodificar el archivo!" + "deocdeError": "¡Algo salió mal al decodificar el archivo!", + "invalidStartTime": "Tiempo de inicio inválido! El tiempo debe estar entre `00:00` y `{0}`.", + "invalidEndTime": "Tiempo de finalización inválido! El tiempo debe estar entre `00:00` y `{0}`.", + "invalidTimeOrder": "El tiempo final no puede ser menor o igual que el tiempo de inicio." } \ No newline at end of file diff --git a/langs/JA.json b/langs/JA.json index d718e91..eae0727 100644 --- a/langs/JA.json +++ b/langs/JA.json @@ -187,5 +187,8 @@ "voicelinkOutofList": "有効なトラックインデックスを指定してください!", "voicelinkDuplicateTrack": "申し訳ありませんが、このトラックは既にキューに存在します。", - "deocdeError": "ファイルのデコード中に問題が発生しました!" + "deocdeError": "ファイルのデコード中に問題が発生しました!", + "invalidStartTime": "無効な開始時間!時間は `00:00` と `{0}` の間に設定する必要があります。", + "invalidEndTime": "無効な終了時間!時間は `00:00` と `{0}` の間に設定する必要があります。", + "invalidTimeOrder": "終了時間は開始時間より大きくない必要があります。" } \ No newline at end of file diff --git a/langs/KO.json b/langs/KO.json index 170b717..2b1c3cf 100644 --- a/langs/KO.json +++ b/langs/KO.json @@ -187,5 +187,8 @@ "voicelinkOutofList": "유효한 트랙 인덱스를 제공해주세요!", "voicelinkDuplicateTrack": "죄송합니다. 이 트랙은 이미 큐에 있습니다.", - "deocdeError": "파일 디코딩 중 문제가 발생했습니다!" + "deocdeError": "파일 디코딩 중 문제가 발생했습니다!", + "invalidStartTime": "효력 없는 시작 시간! 시간은 `00:00` 과 `{0}` 사이에 설정해야 합니다.", + "invalidEndTime": "효력 없는 종료 시간! 시간은 `00:00` 과 `{0}` 사이에 설정해야 합니다.", + "invalidTimeOrder": "종료 시간은 시작 시간보다 클수 있어야 합니다." } \ No newline at end of file diff --git a/langs/RU.json b/langs/RU.json index 18619b4..38155dd 100644 --- a/langs/RU.json +++ b/langs/RU.json @@ -186,5 +186,8 @@ "voicelinkOutofList": "Пожалуйста, предоставьте действительный индекс трека!", "voicelinkDuplicateTrack": "Извините, этот трек уже есть в очереди.", - "deocdeError": "Что-то пошло не так при декодировании файла!" + "deocdeError": "Что-то пошло не так при декодировании файла!", + "invalidStartTime": "Hевозможное время начала! Вход времени должен быть внутри `00:00` и `{0}`.", + "invalidEndTime": "Невозможное время конца! Вход времени должен быть внутри `00:00` и `{0}`.", + "invalidTimeOrder": "Время конца не может быть меньше или равно времени начала." } diff --git a/langs/UA.json b/langs/UA.json index 8cc2e69..c71dc0a 100644 --- a/langs/UA.json +++ b/langs/UA.json @@ -186,5 +186,8 @@ "voicelinkOutofList": "Будь ласка, надайте дійсний індекс треку!", "voicelinkDuplicateTrack": "Вибачте, цей трек уже є в черзі.", - "deocdeError": "Щось пішло не так під час декодування файлу!" + "deocdeError": "Щось пішло не так під час декодування файлу!", + "invalidStartTime": "Недійснений час початку! Час має бути в межах `00:00` та `{0}`.", + "invalidEndTime": "Недійснений час закінчення! Час має бути в межах `00:00` та `{0}`.", + "invalidTimeOrder": "Час закінчення не може бути меншим або рівним часу початку." } diff --git a/update.py b/update.py index 00dd72d..f8334db 100644 --- a/update.py +++ b/update.py @@ -2,7 +2,7 @@ import requests, zipfile, os, shutil, argparse from io import BytesIO ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -__version__ = "v2.6.9b3" +__version__ = "v2.6.9b4" GITHUB_API_URL = "https://api.github.com/repos/ChocoMeow/Vocard/releases/latest" VOCARD_URL = "https://github.com/ChocoMeow/Vocard/archive/" diff --git a/voicelink/objects.py b/voicelink/objects.py index ca7c13e..de5e1bd 100644 --- a/voicelink/objects.py +++ b/voicelink/objects.py @@ -62,7 +62,8 @@ class Track: "requester", "is_stream", "is_seekable", - "position" + "position", + "end_time" ) def __init__( @@ -102,6 +103,8 @@ class Track: self.is_seekable: bool = info.get("isSeekable", True) self.position: int = info.get("position", 0) + self.end_time: Optional[int] = None + def __eq__(self, other) -> bool: if not isinstance(other, Track): return False diff --git a/voicelink/player.py b/voicelink/player.py index 29ae273..d61f701 100644 --- a/voicelink/player.py +++ b/voicelink/player.py @@ -496,7 +496,7 @@ class Player(VoiceProtocol): self._node._players.pop(self.guild.id) await self.send(method=RequestMethod.delete) - + async def play( self, track: Track, @@ -511,41 +511,58 @@ class Player(VoiceProtocol): if track.spotify: if not track.original: - search: Track = (await self._node.get_tracks( - f"ytsearch:{track.author} - {track.title}", - requester=track.requester - )) - - if not search: - raise TrackLoadError("Can't not found a playable source!") + search_results = await self._node.get_tracks(f"ytmsearch:{track.author} - {track.title}", requester=track.requester) + if not search_results: + raise TrackLoadError("Can't find a playable source!") + track.original = search_results[0] - track.original = search[0] - data = { "encodedTrack": track.original.track_id if track.original else track.track_id, - "position": str(start if start else track.position), - "endTime": str(end if end else track.original.length) + "position": str(start if start else track.position) } + + if end or track.end_time: + data["endTime"] = str(end if end else track.end_time) + await self.send(method=RequestMethod.patch, query=f"noReplace={ignore_if_playing}", data=data) self._current = track if self.volume != 100: await self.set_volume(self.volume) - + self._logger.debug(f"Player in {self.guild.name}({self.guild.id}) playing {track.title} from uri {track.uri} with a length of {track.length}") return self._current - async def add_track(self, raw_tracks: Union[Track, List[Track]], *, at_font: bool = False, duplicate: bool = True) -> int: + def _validate_time(self, track: Track, start_time: int, end_time: int) -> None: + if start_time or end_time: + if not end_time: + end_time = track.length + + if start_time >= end_time: + raise VoicelinkException(self.get_msg("invalidStartTime")) + + track_length = track.length + if not 0 <= start_time <= track_length: + raise VoicelinkException(self.get_msg("invalidStartTime", func.time(track_length))) + if not 0 <= end_time <= track_length: + raise VoicelinkException(self.get_msg("invalidEndTime", func.time(track_length))) + + track.position = start_time + track.end_time = end_time + + async def add_track(self, raw_tracks: Union[Track, List[Track]], *, start_time: int = 0, end_time: int = 0, at_front: bool = False, duplicate: bool = True) -> int: tracks: List[Track] = [] _duplicate_tracks = [] if self.queue._allow_duplicate and duplicate else [track.uri for track in self.queue._queue] raw_tracks = raw_tracks[0] if isinstance(raw_tracks, List) and len(raw_tracks) == 1 else raw_tracks - + try: if (is_list := isinstance(raw_tracks, List)): for track in raw_tracks: if track.uri in _duplicate_tracks: continue + + self._validate_time(track, start_time, end_time) self.queue.put_at_front(track) if at_front else self.queue.put(track) tracks.append(track) _duplicate_tracks.append(track.uri) @@ -553,7 +570,8 @@ class Player(VoiceProtocol): if raw_tracks.uri in _duplicate_tracks: raise DuplicateTrack(self.get_msg("voicelinkDuplicateTrack")) - position = self.queue.put_at_front(raw_tracks) if at_font else self.queue.put(raw_tracks) + self._validate_time(raw_tracks, start_time, end_time) + position = self.queue.put_at_front(raw_tracks) if at_front else self.queue.put(raw_tracks) tracks.append(raw_tracks) finally: