From 8f24b8ac9818859cee2e0f9c4a7dfefd177f78c6 Mon Sep 17 00:00:00 2001 From: Kevin Chang <52964758+kevin0216@users.noreply.github.com> Date: Sun, 11 Aug 2024 23:46:58 +0800 Subject: [PATCH] m.20240318.5-s --- main.py | 6 +- music_comp/player.py | 51 +++++++-- music_comp/ui_comp/changelog.py | 11 +- music_comp/ui_comp/info.py | 9 +- music_comp/ui_comp/leave.py | 5 +- music_comp/ui_comp/player_control.py | 18 ++-- music_comp/utils/cache.py | 4 + music_comp/utils/storage.py | 16 +-- music_comp/utils/track_helper.py | 153 +++++++++++++++++++++------ 9 files changed, 208 insertions(+), 65 deletions(-) diff --git a/main.py b/main.py index fc90fdf..9e8cc22 100644 --- a/main.py +++ b/main.py @@ -19,7 +19,7 @@ status = discord.Status.online production_status = "s" # ce for cutting edge, s for stable test_subject = "wl3.0_test" - bot_version = "m.20240318.4{}-{}".format(f".{test_subject}" if production_status != "s" else "", production_status) + bot_version = "m.20240318.5{}-{}".format(f".{test_subject}" if production_status != "s" else "", production_status) else: status = discord.Status.dnd bot_version = f"LOCAL DEVELOPMENT / {branch} Branch\nMusic Function" @@ -30,7 +30,7 @@ intents = discord.Intents.default() intents.message_content = False bot = commands.AutoShardedBot( - command_prefix=prefix, intents=intents, help_command=None, status=status + command_prefix=prefix, intents=intents, help_command=None, status=status, shard_count=10 ) from music_comp import * @@ -50,7 +50,6 @@ async def precense_update(): await bot.change_presence(activity=precense) await asyncio.sleep(10) - async def count_total(): total_count = 0 for guild in bot.guilds: @@ -103,7 +102,6 @@ async def on_wavelink_node_ready(payload: wavelink.NodeReadyEventPayload): """ ) - try: bot.run(TOKEN) except AttributeError: diff --git a/music_comp/player.py b/music_comp/player.py index bc86c18..0fc360c 100644 --- a/music_comp/player.py +++ b/music_comp/player.py @@ -4,6 +4,7 @@ import asyncio, os import dotenv import validators +import random import discord from discord.ext import commands @@ -33,6 +34,7 @@ async def _create_daemon(self): # Env value setup # Wavelink needed value TW_HOST = os.getenv("WAVELINK_TW_HOST") + BILI_HOST = os.getenv("WAVELINK_BILI_HOST") LOCAL_SEARCH_HOST_1 = os.getenv("WAVELINK_SEARCH_HOST_1") LOCAL_SEARCH_HOST_2 = os.getenv("WAVELINK_SEARCH_HOST_2") PORT = os.getenv("WAVELINK_PORT") @@ -46,6 +48,11 @@ async def _create_daemon(self): uri=f"http://{TW_HOST}:{PORT}", password=PASSWORD, ) + bilibili_host = wavelink.Node( + identifier="BilibiliNode", + uri=f"http://{BILI_HOST}:{PORT}", + password=PASSWORD, + ) searchhost_1 = wavelink.Node( identifier="SearchNode_1", uri=f"http://{LOCAL_SEARCH_HOST_1}:{SEARCH_PORT_1}", @@ -59,7 +66,7 @@ async def _create_daemon(self): # Wavelink connection establishing await wavelink.Pool.connect( - nodes=[mainplayhost, searchhost_1, searchhost_2], + nodes=[mainplayhost, bilibili_host, searchhost_1, searchhost_2], cache_capacity=100, client=self.bot, ) @@ -71,7 +78,7 @@ async def _join(self, channel: discord.VoiceChannel): voice_client = channel.guild.voice_client mainplayhost = wavelink.Pool.get_node("TW_PlayBackNode") if voice_client is None: - player = wavelink.Player() + player = wavelink.Player(nodes=[mainplayhost]) player.inactive_timeout = 600 await channel.connect(cls=player) @@ -158,6 +165,12 @@ async def _play(self, guild: discord.Guild, channel: discord.TextChannel): self[guild.id].text_channel = channel voice_client: wavelink.Player = guild.voice_client + if self._playlist[guild.id].current() is not None and self._playlist[guild.id].current().source == "http": + voice_client.switch_node(wavelink.Pool.get_node("BilibiliNode")) + else: + if voice_client.node.identifier != "TW_PlayBackNode": + voice_client.switch_node(wavelink.Pool.get_node("TW_PlayBackNode")) + if (not voice_client.paused) and (voice_client.current is None) and (len(self._playlist[guild.id].order) > 0): await voice_client.play(self._playlist[guild.id].current()) @@ -580,7 +593,7 @@ async def _i_play(self, interaction: discord.Interaction, search: str): return else: tracks = await self.track_helper.get_track(interaction, search) - if isinstance(tracks, Exception) or tracks is None: + if isinstance(tracks, Exception) or tracks is [None] or tracks is None: await self.ui.Search.SearchFailed(interaction, search) await self.play(interaction, tracks) else: @@ -647,10 +660,10 @@ async def on_wavelink_track_end(self, payload: wavelink.TrackEndEventPayload): await self.ui.PlayerControl.DonePlaying(self[guild.id].text_channel) return else: - await self.track_helper.process_suggestion(guild, self.ui_guild_info(guild.id)), - player: wavelink.Player = guild.voice_client + self.bot.loop.create_task(self.track_helper.process_suggestion(guild, self.ui_guild_info(guild.id))) + song = self._playlist[guild.id].current() try: await player.play(song) @@ -659,6 +672,19 @@ async def on_wavelink_track_end(self, payload: wavelink.TrackEndEventPayload): await self.ui.PlayerControl.PlayingError(self[guild.id].text_channel, e) pass + @commands.Cog.listener() + async def on_wavelink_node_disconnected(self, payload: wavelink.NodeDisconnectedEventPayload) -> None: + node: wavelink.Node = payload.node + new_node: wavelink.Node = wavelink.Pool.get_node() # You can get a specific node if you want... + if not new_node: + return + + players: dict[int, wavelink.Player] = node.players + for guild_id, player in players.items(): + try: + await player.switch_node(new_node) + except RuntimeError: + await player.disconnect() # Error handler @commands.Cog.listener() @@ -692,8 +718,14 @@ async def _pause_on_being_alone( ): try: voice_client: wavelink.Player = member.guild.voice_client - if len(voice_client.channel.members) == 1 and member != self.bot.user: - await self.ui._InfoGenerator._UpdateSongInfo(member.guild.id) + if len(voice_client.channel.members) == 1 and member != self.bot.user \ + and len(self._playlist[member.guild.id].order) > 0: + + if not self.ui_guild_info(member.guild.id).playinfo is None: + await self.ui._InfoGenerator._UpdateSongInfo(member.guild.id) + else: + await self.ui.PlayerControl.PlayingMsg(self[member.guild.id].text_channel) + self.ui_guild_info(member.guild.id).playinfo_view.playorpause.emoji = discord.PartialEmoji.from_str("▶️") self.ui_guild_info(member.guild.id).playinfo_view.playorpause.disabled = True self.ui_guild_info(member.guild.id).playinfo_view.playorpause.style = discord.ButtonStyle.gray @@ -714,7 +746,10 @@ async def _pause_on_being_alone( and member != self.bot.user and self.ui_guild_info(member.guild.id).playinfo_view.playorpause.disabled ): - await self.ui._InfoGenerator._UpdateSongInfo(member.guild.id) + if not self.ui_guild_info(member.guild.id).playinfo is None: + await self.ui._InfoGenerator._UpdateSongInfo(member.guild.id) + else: + await self.ui.PlayerControl.PlayingMsg(self[member.guild.id].text_channel) self.ui_guild_info(member.guild.id).playinfo_view.playorpause.disabled = False self.ui_guild_info(member.guild.id).playinfo_view.playorpause.style = discord.ButtonStyle.blurple diff --git a/music_comp/ui_comp/changelog.py b/music_comp/ui_comp/changelog.py index faff339..f878c7c 100644 --- a/music_comp/ui_comp/changelog.py +++ b/music_comp/ui_comp/changelog.py @@ -31,7 +31,7 @@ def ChangeLogsDefine(self) -> None: # Define if this version is inherit from specfic test version self.inherit_from_version = "" - self.picture = "https://cdn.discordapp.com/attachments/642704558996586496/1262838299735621684/image.png?ex=66980d51&is=6696bbd1&hm=0443c66aa4de144678a868d73eb39936a27de92e5fe8aa65d204aa3b75762eea&" + self.picture = "" self.emergency_build = False self.github_link = "https://github.com/TK-Entertainment/tkablent_music/releases/tag/m.20240318-s" @@ -40,10 +40,11 @@ def ChangeLogsDefine(self) -> None: # Index 1 means changelog summary # Index 2 means changelog description self.changelogs = [ - ["+", "【新增】新增群組最愛點播/近期點播選項 (可參圖)", "=> 在上次意見回饋中,我們發現有些群組希望能夠快速點播最愛或近期點播過的歌曲\n=> 現在您可以在點播選單中選擇「這群ㄉ最愛!」或「最近播放」來點播\n=> 請注意,最愛歌曲的結果會在該群組點播歌曲至一定數量時出現 (不含自動播放)"], - ["+", "【新增】全新新增歌曲介面 (由播放介面進入)", "=> 現在最愛點播/近期點播選項也在點選介面上可用!\n=> 使用手機的你也可以點播最愛點播/近期點播的歌了"], - ["!", "【修復】修復上個版本無法透過介面暫停的問題", "=> 此版本已修復此問題"], - ["!", "【改進】一些有的沒的穩定性及可靠性修復", "=> 我也不知道修了啥,但應該是有的 (*°∀°)"], + ["!", "【改進】針對一些嚴重影響播放的問題進行穩定性改進", "=> 此版本已修復此問題"], + ["!", "【修復】嘗試改進快速搜尋推薦的速度", "=> 此版本嘗試改善此問題導致的部分群組無法使用的問題"], + ["(!)", "【緊急修復】嘗試修復因近期 API 限制影響,造成機器人當機的問題", "=> 此版本嘗試修復此問題,若仍舊發生請至支援群組回報"], + ["(!)", "【緊急修復】修復因近期 API 限制影響,有部分伺服器無法使用推薦候選功能", "=> 此版本已修復此問題,但可能造成部分歌曲不會列入選項內"], + ["(!)", "【緊急修復】修復上個版本後,快速搜尋功能失效的問題", "=> 此版本已修復此問題"], ] async def SendChangelogs(self, interaction: discord.Interaction) -> None: diff --git a/music_comp/ui_comp/info.py b/music_comp/ui_comp/info.py index 2c1e1cb..551c123 100644 --- a/music_comp/ui_comp/info.py +++ b/music_comp/ui_comp/info.py @@ -268,13 +268,16 @@ def _SongInfo( ) and color_code != "red" ): - if self.guild_info(guild_id).skip or self.guild_info(guild_id).suggestion_processing: + if self.guild_info(guild_id).suggestion_failure: + queuelist += f"**推薦歌曲載入失敗**" + elif self.guild_info(guild_id).skip or self.guild_info(guild_id).suggestion_processing: queuelist += f"**推薦歌曲載入中**" else: queuelist += f"**:bulb:** {playlist[1].title}" embed.add_field( name="{}即將播放".format( - f":hourglass: | " if self.guild_info(guild_id).skip or self.guild_info(guild_id).suggestion_processing else "" + f":hourglass: | " if self.guild_info(guild_id).skip or self.guild_info(guild_id).suggestion_processing + else ":x: | " if self.guild_info(guild_id).suggestion_failure else "" ), value=queuelist, inline=False, @@ -389,7 +392,7 @@ def _PlaylistInfo( ) if (playlist[0].uri is not None) and ("spotify" in playlist[0].uri): - embed.set_thumbnail(url=playlist.artwork) + embed.set_thumbnail(url=playlist[0].artwork) embed = discord.Embed.from_dict(dict(**embed.to_dict(), **self.embed_opt)) diff --git a/music_comp/ui_comp/leave.py b/music_comp/ui_comp/leave.py index 450344d..eade5a6 100644 --- a/music_comp/ui_comp/leave.py +++ b/music_comp/ui_comp/leave.py @@ -20,8 +20,11 @@ def __init__(self, exception_handler, info_generator): self.musicbot = musicbot async def refresh_and_reset(self, guild: discord.Guild): + guild_info = self.guild_info(guild.id) + await asyncio.sleep(3) - await self.info_generator._UpdateSongInfo(guild.id) + if not guild_info.playinfo is None: + await self.info_generator._UpdateSongInfo(guild.id) self.reset_value(guild) def reset_value(self, guild): diff --git a/music_comp/ui_comp/player_control.py b/music_comp/ui_comp/player_control.py index c00dc52..8ce0452 100644 --- a/music_comp/ui_comp/player_control.py +++ b/music_comp/ui_comp/player_control.py @@ -102,22 +102,26 @@ def __init__( break if len(result[currentindex].title) > 100: - result[currentindex].title = ( + title = ( result[currentindex].title[:95] + "..." ) + else: + title = result[currentindex].title if len(result[currentindex].author) > 85: - result[currentindex].author = ( + author = ( result[currentindex].author[:70] + "..." ) + else: + author = result[currentindex].author length = _sec_to_hms( seconds=(result[currentindex].length) / 1000, format="symbol" ) self.add_option( - label=result[currentindex].title, + label=title, value=currentindex, - description=f"{result[currentindex].author} / {length}", + description=f"{author} / {length}", ) self.max_values = len(self.options) @@ -427,7 +431,8 @@ async def on_timeout(self): async def stop_refresh(self, guild): await asyncio.sleep(3) - await self.info_generator._UpdateSongInfo(guild.id) + if not self.guild_info(guild.id).playinfo is None: + await self.info_generator._UpdateSongInfo(guild.id) self.guild_info(guild.id).playinfo = None self.guild_info(guild.id).playinfo_view = None self.guild_info(guild.id).leaveoperation = False @@ -550,7 +555,8 @@ async def suggestion_control(self, interaction, button): ).playinfo_view.skip.disabled = False else: self.guild_info(channel.guild.id).suggestion_processing = True - await self.info_generator._UpdateSongInfo(interaction.guild.id) + if self.guild_info(channel.guild.id).playinfo is not None: + await self.info_generator._UpdateSongInfo(interaction.guild.id) await interaction.response.edit_message(view=view) await self.toggle(interaction, button, "done") if self.guild_info(channel.guild.id).music_suggestion: diff --git a/music_comp/utils/cache.py b/music_comp/utils/cache.py index f4ebf55..e905b50 100644 --- a/music_comp/utils/cache.py +++ b/music_comp/utils/cache.py @@ -31,6 +31,7 @@ def __init__(self): shutil.copyfile(self._bak_cache_path, self._cache_path) async def update_cache(self, new_data: dict) -> None: + await asyncio.sleep(2) """update database""" try: with open(self._cache_path, "rb") as f: @@ -49,7 +50,10 @@ async def update_cache(self, new_data: dict) -> None: shutil.copyfile(self._bak_cache_path, self._cache_path) if debug: print("[DEBUG | Cache Module] Overwriting main cache file with backup") + await asyncio.sleep(0.02) + for identifier in new_data.keys(): + await asyncio.sleep(0.02) if debug: print(f"[DEBUG | Cache Module] Fetched {identifier}") if data.get(identifier) is not None: if debug: print(f"[DEBUG | Cache Module] Updating {identifier}") diff --git a/music_comp/utils/storage.py b/music_comp/utils/storage.py index 01e918d..657fc8b 100644 --- a/music_comp/utils/storage.py +++ b/music_comp/utils/storage.py @@ -7,6 +7,9 @@ import json import os import wavelink +from msgspec.json import decode as json_decode +from msgspec import DecodeError +from msgspec.json import encode as json_encode class GuildInfo: def __init__(self, guild_id): @@ -101,8 +104,8 @@ def changelogs_latestversion(self, value: str): def fetch(self, key: str) -> None: """fetch from database""" - with open(self._database, "r") as f: - data: dict = json.load(f) + with open(self._database, "rb") as f: + data: dict = json_decode(f.read()) if ( data.get(str(self.guild_id)) is None or data[str(self.guild_id)].get(key) is None @@ -113,13 +116,13 @@ def fetch(self, key: str) -> None: def update(self, key: str, value: str) -> None: """update database""" - with open(self._database, "r") as f: - data: dict = json.load(f) + with open(self._database, "rb") as f: + data: dict = json_decode(f.read()) if data.get(str(self.guild_id)) is None: data[str(self.guild_id)] = dict() data[str(self.guild_id)][key] = value - with open(self._database, "w") as f: - json.dump(data, f) + with open(self._database, "wb") as f: + f.write(json_encode(data)) class GuildUIInfo: def __init__(self, guild_id): @@ -146,6 +149,7 @@ def __init__(self, guild_id): # Indicate that the suggestion is under processing # Will be reseted to False after process is done self.suggestion_processing: bool = False + self.suggestion_failure: bool = False self.lasterrorinfo: dict = {} diff --git a/music_comp/utils/track_helper.py b/music_comp/utils/track_helper.py index b5d039c..54b8727 100644 --- a/music_comp/utils/track_helper.py +++ b/music_comp/utils/track_helper.py @@ -11,7 +11,9 @@ import asyncio import time import os +import random from enum import Enum, auto +from difflib import SequenceMatcher from music_comp.ui import UI, _sec_to_hms from music_comp.playlist import LoopState, Playlist, PlaylistBase @@ -49,10 +51,10 @@ def __init__(self, ui_comp: UI, playlist: Playlist): sessdata=SESSDATA, bili_jct=BILI_JCT, buvid3=BUVID3, - dedeuserid=DEDEUSERID, + dedeuserid=DEDEUSERID ) - def __getitem__(self, guild_id: int) -> PlaylistBase: + def __getitem__(self, guild_id: int=None) -> PlaylistBase: return self._playlist[guild_id] def check_current_suggest_support(self, guild_id) -> bool: @@ -99,8 +101,11 @@ async def _search_suggest_processing(self, result: list, track: wavelink.Playabl ) left_name_length = 70 - len(f" | {length}") + if with_arrow: + left_name_length -= len(">> ") + - if len(title) >= left_name_length + len(" ..."): + if len(vtitle) >= left_name_length + len(" ..."): title = vtitle[:left_name_length] + " ..." else: title = vtitle @@ -119,6 +124,16 @@ async def _search_suggest_processing(self, result: list, track: wavelink.Playabl except Exception as e: return None + async def _fetch_fast_suggestion(self, interaction: discord.Interaction, trackid: Union[list, str], tracks: list): + try: + if isinstance(trackid, str): + track = await self.get_track(interaction, f"sid=>{trackid}", quick_search=True) + else: + track = await self.get_track(interaction, f"sid=>{trackid[0]}", quick_search=True) + except wavelink.exceptions.LavalinkLoadException: + return None + tracks.extend(track) + # Main part for search suggestion system async def get_search_suggest( self, interaction: discord.Interaction, current: str, guild_info: GuildInfo @@ -138,9 +153,11 @@ async def get_search_suggest( else: tracks = [] result = [] - for i, trackid in enumerate(guild_info.mostly_played): - tracks.extend(await self.get_track(interaction, f"sid=>{trackid[0]}")) - if i == 4: break + async with asyncio.TaskGroup() as taskgroup: + for i, trackid in enumerate(guild_info.mostly_played): + if i >= 4: + break + taskgroup.create_task(self._fetch_fast_suggestion(interaction, trackid, tracks)) async with asyncio.TaskGroup() as taskgroup: for i in range(len(tracks)): taskgroup.create_task(self._search_suggest_processing(result, tracks[i], data, with_arrow=True)) @@ -162,8 +179,9 @@ async def get_search_suggest( else: tracks = [] result = [] - for trackid in guild_info.recently_played: - tracks.extend(await self.get_track(interaction, f"sid=>{trackid}")) + async with asyncio.TaskGroup() as taskgroup: + for trackid in guild_info.recently_played: + taskgroup.create_task(self._fetch_fast_suggestion(interaction, trackid, tracks)) async with asyncio.TaskGroup() as taskgroup: for i in range(len(tracks)): taskgroup.create_task(self._search_suggest_processing(result, tracks[i], data, with_arrow=True)) @@ -234,23 +252,20 @@ async def _get_bilibili_track(self, interaction: discord.Interaction, search: st data = detector.detect_all() data.reverse() for t in data: - if isinstance(t, bilibili.video.AudioStreamDownloadURL): - #raw_url = t.url.replace("&", "%26") - raw_url = t.url - try: - trackinfo = await wavelink.Pool.fetch_tracks(raw_url) - except Exception as e: - raw_url = None - continue - break - else: - break + #raw_url = t.url.replace("&", "%26") + raw_url = t.url + try: + trackinfo = await wavelink.Pool.fetch_tracks(raw_url, node=wavelink.Pool.get_node("BilibiliNode")) + except Exception as e: + raw_url = None + continue + break if raw_url == None: return None try: - trackinfo = await wavelink.Pool.fetch_tracks(raw_url) + trackinfo = await wavelink.Pool.fetch_tracks(raw_url, node=wavelink.Pool.get_node("BilibiliNode")) except Exception as e: return e @@ -280,7 +295,7 @@ def _parse_url(self, raw_url: str, choice: Literal["videoonly", "playlist"]) -> url = raw_url elif "sid=>" in raw_url: vid = raw_url.split("=>")[1] - if "BV" in vid or isinstance(vid, int): + if vid.startswith("BV") or isinstance(vid, int): url = f"https://www.bilibili.com/video/{vid}" else: url = f"https://www.youtube.com/watch?v={vid}" @@ -301,6 +316,10 @@ async def get_track( await interaction.response.defer(ephemeral=True, thinking=True) tracks = [] + nodes = [ + wavelink.Pool.get_node("SearchNode_1"), + wavelink.Pool.get_node("SearchNode_2"), + ] if ("bilibili" in search or "b23.tv" in search) and validators.url(search): track = await self._get_bilibili_track(interaction, search) @@ -316,8 +335,13 @@ async def get_track( if "bilibili" in url: callback = [await self._get_bilibili_track(interaction, url)] else: - callback = await wavelink.Playable.search(url) - if isinstance(callback, list): + try: + callback = await wavelink.Playable.search(url, node=random.choice(nodes)) + except wavelink.exceptions.LavalinkLoadException: + callback = None + if callback is None: + return [None] + elif isinstance(callback, list): tracks.extend(callback) elif isinstance(callback, wavelink.Playlist): tracks.append(callback) @@ -337,7 +361,7 @@ async def get_track( for source in sources: try: - data = await wavelink.Playable.search(search, source=source) + data = await wavelink.Playable.search(search, source=source, node=random.choice(nodes)) if data is not None: tracks.extend(data) except Exception: @@ -364,9 +388,15 @@ async def _get_suggest_track( ) -> Optional[wavelink.Playable]: suggested_track = None + nodes = [ + wavelink.Pool.get_node("SearchNode_1"), + wavelink.Pool.get_node("SearchNode_2"), + ] + try: suggested_track = await wavelink.Playable.search( - "https://www.youtube.com/watch?v={}".format(suggestion["tracks"][index]["videoId"]) + "https://www.youtube.com/watch?v={}".format(suggestion["tracks"][index]["videoId"]), + node=random.choice(nodes), ) suggested_track = suggested_track[0] except: @@ -401,14 +431,29 @@ async def _process_resuggestion( suggested_track = None if len(ui_guild_info.suggestions) != 0: + resuggested_required = False # check first one first if ui_guild_info.suggestions[0].title in ui_guild_info.previous_titles: print( f"[{guild.id} | Suggestion] {ui_guild_info.suggestions[0].title} has played before, resuggested" ) ui_guild_info.suggestions.pop(0) - - while suggested_track is None: + resuggested_required = True + else: + for previous_titles in ui_guild_info.previous_titles: + match_ratio = SequenceMatcher(None, ui_guild_info.suggestions[0].title, previous_titles).ratio() + if match_ratio >= 0.87: + print( + f"[{guild.id} | Suggestion] {ui_guild_info.suggestions[0].title} has played before, resuggested" + ) + print("[DEBUG ONLY] ", ui_guild_info.previous_titles) + print(f"[DEBUG ONLY] {previous_titles} is detected match with {ui_guild_info.suggestions[0].title} with ratio {match_ratio}") + ui_guild_info.suggestions.pop(0) + resuggested_required = True + + if resuggested_required: + tried = 0 + while suggested_track is None and tried < 20: suggested_track = await self._get_suggest_track( suggestion, suggestion["index"], ui_guild_info, pre_process=True ) @@ -418,6 +463,12 @@ async def _process_resuggestion( else: suggestion = await self._get_suggest_list(guild, playlist_index) playlist_index += 1 + tried += 1 + if suggested_track is None: + ui_guild_info.suggestion_failure = True + await self.ui._InfoGenerator._UpdateSongInfo(guild.id) + return + self[guild.id]._suggest_search_task = await asyncio.wait_for( self._search_for_suggestion(guild, suggestion, ui_guild_info), None @@ -426,12 +477,28 @@ async def _process_resuggestion( # wait for rest of suggestions to be processed, and check them for i, track in enumerate(ui_guild_info.suggestions): + resuggested_required = False if track.title in ui_guild_info.previous_titles: print( f"[{guild.id} | Suggestion] {track.title} has played before, resuggested" ) ui_guild_info.suggestions.pop(i) - while suggested_track is None: + resuggested_required = True + else: + for previous_titles in ui_guild_info.previous_titles: + match_ratio = SequenceMatcher(None, track.title, previous_titles).ratio() + if match_ratio >= 0.87: + print( + f"[{guild.id} | Suggestion] {track.title} has played before, resuggested" + ) + print("[DEBUG ONLY] ", ui_guild_info.previous_titles) + print(f"[DEBUG ONLY] {previous_titles} is detected match with {track.title} with ratio {match_ratio}") + ui_guild_info.suggestions.pop(i) + resuggested_required = True + + if resuggested_required: + tried = 0 + while suggested_track is None and tried < 20: suggested_track = await self._get_suggest_track( suggestion, suggestion["index"], ui_guild_info, pre_process=True ) @@ -441,6 +508,11 @@ async def _process_resuggestion( else: suggestion = await self._get_suggest_list(guild, playlist_index) playlist_index += 1 + tried += 1 + if suggested_track is None: + ui_guild_info.suggestion_failure = True + await self.ui._InfoGenerator._UpdateSongInfo(guild.id) + return # This part maintain suggestion fetching async def _search_for_suggestion( @@ -452,7 +524,9 @@ async def _search_for_suggestion( if len(ui_guild_info.suggestions) == 0: print(f"[{guild.id} | Suggestion] Started to fetch 12 suggestions") - while suggested_track is None: + tried = 0 + + while suggested_track is None and tried < 20: for index in range(2, 13, 1): suggested_track = await self._get_suggest_track( suggestion, index, ui_guild_info, pre_process=False @@ -463,6 +537,12 @@ async def _search_for_suggestion( else: suggestion = await self._get_suggest_list(guild, playlist_index) playlist_index += 1 + tried += 1 + + if suggested_track is None: + ui_guild_info.suggestion_failure = True + await self.ui._InfoGenerator._UpdateSongInfo(guild.id) + return print(f"[{guild.id} | Suggestion] Fetched 12 suggestions\n{suggested_track}") @@ -471,6 +551,7 @@ async def _search_for_suggestion( async def process_suggestion( self, guild: discord.Guild, ui_guild_info: GuildUIInfo ): + ui_guild_info.suggestion_failure = False # Check whether current song is supported in suggestion system or not if ( (ui_guild_info.music_suggestion) @@ -498,7 +579,7 @@ async def process_suggestion( ui_guild_info.previous_titles.remove( self[guild.id].order[-1].title ) - self.pop(guild.id, -1) + self._playlist.pop(guild.id, -1) return # Case of playlist loop else: @@ -518,7 +599,7 @@ async def process_suggestion( ui_guild_info.previous_titles.remove( self[guild.id].order[-1].title ) - self.pop(guild.id, -1) + self._playlist.pop(guild.id, -1) return suggested_track = None @@ -536,13 +617,21 @@ async def process_suggestion( ) ui_guild_info.suggestions_source["index"] = 13 - while suggested_track is None: + tried = 0 + + while suggested_track is None and tried < 20: suggested_track = await self._get_suggest_track( suggestion, index, ui_guild_info, pre_process=False ) if suggested_track is None: index += 1 + tried += 1 + + if suggested_track is None: + ui_guild_info.suggestion_failure = True + await self.ui._InfoGenerator._UpdateSongInfo(guild.id) + return if self[guild.id]._resuggest_task is not None: self[guild.id]._resuggest_task.cancel()