diff --git a/config/config.json b/config/config.json index 65da9af..a1c947f 100644 --- a/config/config.json +++ b/config/config.json @@ -416,7 +416,9 @@ "porn", "algorithm", "loud" - ] + ], + "search_cmd": "!mis", + "max_results": 10 }, "description": "Search MyInstants.com and play a random sound (e.g. !mi faah)." }, diff --git a/src/torchlight/CommandHandler.py b/src/torchlight/CommandHandler.py index 3ff1a35..785bbe9 100644 --- a/src/torchlight/CommandHandler.py +++ b/src/torchlight/CommandHandler.py @@ -74,13 +74,13 @@ def Reload(self) -> None: # @profile async def HandleCommand(self, line: str, player: Player, from_menu: bool = False) -> int | None: - if from_menu: - message = line.split(sep=" ", maxsplit=2) # 2 because the !search command requires another arg for page - else: - message = line.split(sep=" ", maxsplit=1) + message = line.split(sep=" ", maxsplit=1) + if not message[0].startswith(("!", "#", "_", "$", "@", "%", "^", "&", "*", "-")): + return None - if len(message) < 2: + if len(message) == 1 or not message[1]: message.append("") + message[1] = message[1].strip() if message[1] and self.torchlight.last_url: @@ -89,9 +89,6 @@ async def HandleCommand(self, line: str, player: Player, from_menu: bool = False level = player.admin.level - if not message[0].startswith(("!", "#", "_", "$", "@", "%", "^", "&", "*", "-")): - return None - ret_message: str | None = None ret: int | None = None for command in self.commands: @@ -130,7 +127,7 @@ async def HandleCommand(self, line: str, player: Player, from_menu: bool = False ret = ret_temp else: ret = await command._func(message, player) - if from_menu and command.__class__.__name__ == "VoiceTrigger" and ret: + if from_menu and command.__class__.__name__ in ("VoiceTrigger", "MyInstantsSearch") and ret: self.torchlight.SayChat(f"{{olive}}{player.name}: {{default}}{line}") except Exception as e: diff --git a/src/torchlight/Commands.py b/src/torchlight/Commands.py index 51faeaf..87e0281 100644 --- a/src/torchlight/Commands.py +++ b/src/torchlight/Commands.py @@ -100,6 +100,46 @@ def check_disabled(self, player: Player) -> bool: return True return False + def get_menu_page_content( + self, + cmd: str, + search: str | None, + res: dict[str, str], + page: int, + max_items: int, + max_pages: int, + ) -> tuple[dict[str, str], int, int]: + start = (page - 1) * max_items if page else 0 + end = len(res) + if end > max_items: + end = start + max_items + + start = (page - 1) * max_items + end = start + max_items + command_items = dict(list(res.items())[start:end]) + + if not search: + line = cmd + else: + line = f"{cmd} {search}" + + items: dict[str, str] = {} + last_item_info: str = "" + last_item_display: str = "" + if page < max_pages: + last_item_info = f"{line} {page + 1}" + last_item_display = "> Next Page" + items[last_item_info] = last_item_display + if page > 1: + last_item_info = f"{line} {page - 1}" + last_item_display = "> Previous Page" + items[last_item_info] = last_item_display + + if last_item_info and last_item_display: + items[last_item_info] = last_item_display + "\n " + + return {**items, **command_items}, start, end + async def _func(self, message: list[str], player: Player) -> int: self.logger.debug(sys._getframe().f_code.co_name) return 0 @@ -751,52 +791,23 @@ def get_sound_path(self, player: Player, voice_trigger: str, trigger_number: str class Search(BaseCommand): - def get_menu_page_content( - self, - cmd: str, - search: str, - res: dict[str, str], - page: int, - max_items: int, - max_pages: int, - ) -> dict[str, str]: - start = (page - 1) * max_items - end = start + max_items - soundsItems = dict(list(res.items())[start:end]) - - if search == "": - line = cmd - else: - line = f"{cmd} {search}" - - items: dict[str, str] = {} - last_item_info: str = "" - last_item_display: str = "" - if page < max_pages: - last_item_info = f"{line} {page + 1}" - last_item_display = "> Next Page" - items[last_item_info] = last_item_display - if page > 1: - last_item_info = f"{line} {page - 1}" - last_item_display = "> Previous Page" - items[last_item_info] = last_item_display - - if last_item_info and last_item_display: - items[last_item_info] = last_item_display + "\n " - - return {**items, **soundsItems} - async def _func(self, message: list[str], player: Player) -> int: self.logger.debug(sys._getframe().f_code.co_name + " " + str(message)) - voice_trigger = message[1].lower() + # we need to specify the actual page and result if there is any. + page: int = 1 + voice_trigger: str = "" + voice_trigger_parts: list[str] = [] + if message[1]: + parts = message[1].split(" ") + for part in parts: + if part.isdigit(): + page = int(part) + break + voice_trigger_parts.append(part) - page = 1 - if voice_trigger.isdigit(): - page = int(voice_trigger) - voice_trigger = "" - if len(message) > 2 and message[2].isdigit(): - page = int(message[2]) + if voice_trigger_parts: + voice_trigger = " ".join(voice_trigger_parts) res: dict[str, str] = {} @@ -827,20 +838,14 @@ async def _func(self, message: list[str], player: Player) -> int: if page < 1: page = 1 - start = (page - 1) * max if page else 0 - end = actual_count - - if actual_count > max: - end = start + max - - res = self.get_menu_page_content( - cmd=message[0], - search=voice_trigger, - res=res, - page=page, - max_items=max, - max_pages=max_pages, - ) + res, start, end = self.get_menu_page_content( + cmd=message[0], + search=voice_trigger, + res=res, + page=page, + max_items=max, + max_pages=max_pages, + ) title: str | None = None if voice_trigger: @@ -1432,11 +1437,27 @@ async def _func(self, message: list[str], player: Player) -> int: ) return 1 - search = message[1] - if search: - search = search.lower() - + search_only: bool = False command_config = self.get_config() + if "search_cmd" in command_config["parameters"] and message[0] == command_config["parameters"]["search_cmd"]: + search_only = True + + # we need to specify the actual page and result if there is any. + page: int = 1 + search_parts: list[str] = [] + if message[1]: + parts = message[1].split(" ") + for part in parts: + if part.isdigit(): + page = int(part) + break + + search_parts.append(part) + + search: str | None = None + + if search_parts: + search = " ".join(search_parts) keywords_banned: list[str] = [] @@ -1465,55 +1486,69 @@ async def _func(self, message: list[str], player: Player) -> int: if self.torchlight.config["VoiceServer"]["Proxy"]: proxy = self.torchlight.config["VoiceServer"]["Proxy"] - url = await asyncio.to_thread(myinstants_get_random_sound, search, proxy) + urls: dict[str, str] | str | None = None + + urls = await asyncio.to_thread(myinstants_get_random_sound, search, proxy, search_only) - if url is None: + if urls is None: if search: self.torchlight.SayPrivate(player, f"{{darkred}}[MyInstants]{{default}} No sound found for {search}") else: self.torchlight.SayPrivate(player, "{{darkred}}[MyInstants]{{default}} No sounds found") return 1 - audio_clip = self.audio_manager.AudioClip(player, url) - if not audio_clip: - return 1 + if isinstance(urls, str): + audio_clip = self.audio_manager.AudioClip(player, urls) + if not audio_clip: + return 1 - self.torchlight.last_url = url - return audio_clip.Play() + self.torchlight.last_url = urls + return audio_clip.Play() + elif isinstance(urls, dict): + # get the play cmd + play_cmd: str = "" + for cmd in self.triggers: + if cmd == message[0]: + continue + play_cmd = cast(str, cmd) + break + if not play_cmd: + return 0 -class Help(BaseCommand): - def get_menu_page_content( - self, - cmd: str, - res: dict[str, str], - page: int, - max_items: int, - max_pages: int, - ) -> dict[str, str]: - start = (page - 1) * max_items - end = start + max_items - command_items = dict(list(res.items())[start:end]) + urls = {f"{play_cmd} {k}": v for k, v in urls.items()} + max = 10 + if "parameters" in command_config and "max_results" in command_config["parameters"]: + max = command_config["parameters"]["max_results"] - line = cmd + actual_count = len(urls) + max_pages = (actual_count + max - 1) // max + if page > max_pages: + page = max_pages + if page < 1: + page = 1 - items: dict[str, str] = {} - last_item_info: str = "" - last_item_display: str = "" - if page < max_pages: - last_item_info = f"{line} {page + 1}" - last_item_display = "> Next Page" - items[last_item_info] = last_item_display - if page > 1: - last_item_info = f"{line} {page - 1}" - last_item_display = "> Previous Page" - items[last_item_info] = last_item_display + res, start, end = self.get_menu_page_content( + cmd=message[0], + search=search, + res=urls, + page=page, + max_items=max, + max_pages=max_pages, + ) - if last_item_info and last_item_display: - items[last_item_info] = last_item_display + "\n " + title = "[Torchlight] [MyInstants] Search results" + (f" for {search}." if search else ".") + title += f"\nDisplaying {start + 1}-{min(end, actual_count)} of {actual_count} results." + title += f"\nPlease wait {int(cooldown)}s before you click on any item." + self.torchlight.CreateMenu( + player=player, + title=title, + options=res, + ) + return 0 - return {**items, **command_items} +class Help(BaseCommand): async def _func(self, message: list[str], player: Player) -> int: self.logger.debug(sys._getframe().f_code.co_name + " " + str(message)) @@ -1570,18 +1605,14 @@ async def _func(self, message: list[str], player: Player) -> int: if page < 1: page = 1 - start = (page - 1) * max if page else 0 - end = actual_count - - if actual_count > max: - end = start + max - res = self.get_menu_page_content( - cmd=message[0], - res=res, - page=page, - max_items=max, - max_pages=max_pages, - ) + res, start, end = self.get_menu_page_content( + cmd=message[0], + search=None, + res=res, + page=page, + max_items=max, + max_pages=max_pages, + ) title = "[Torchlight] Commands List" diff --git a/src/torchlight/MyInstants.py b/src/torchlight/MyInstants.py index ea092cd..c4936f1 100644 --- a/src/torchlight/MyInstants.py +++ b/src/torchlight/MyInstants.py @@ -10,7 +10,11 @@ HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"} -def myinstants_get_random_sound(query: str | None, proxy: str | None) -> str | None: +def myinstants_get_random_sound( + query: str | None, + proxy: str | None, + search_only: bool = False, +) -> dict[str, str] | str | None: if not query: search_url = f"{MYINSTANTS_URL}/en/index/us/" else: @@ -36,17 +40,36 @@ def myinstants_get_random_sound(query: str | None, proxy: str | None) -> str | N soup = BeautifulSoup(r.text, "html.parser") buttons = soup.find_all("button", onclick=True) - mp3_paths = [] + mp3_paths: dict[str, str] | list[str] + if search_only: + mp3_paths = {} + else: + mp3_paths = [] for btn in buttons: onclick_value = btn["onclick"] if "play(" in onclick_value: match = re.search(r"play\('(.+?\.mp3)'", onclick_value) if match: - mp3_paths.append(match.group(1)) + if isinstance(mp3_paths, list): + mp3_paths.append(match.group(1)) + elif isinstance(mp3_paths, dict): + name = btn["title"] + name = name.removeprefix("Play ") + name = name.removesuffix(" sound") + # for secuirty purpose... + if len(name) > 20: + continue + + mp3_paths[name] = name if not mp3_paths: return None - mp3_url = urljoin(MYINSTANTS_URL, secrets.choice(mp3_paths)) - return mp3_url + mp3_urls: str | dict[str, str] + if isinstance(mp3_paths, list): + mp3_urls = urljoin(MYINSTANTS_URL, secrets.choice(mp3_paths)) + elif isinstance(mp3_paths, dict): + mp3_urls = mp3_paths + + return mp3_urls diff --git a/src/torchlight/__init__.py b/src/torchlight/__init__.py index 0f228f2..e4adfb8 100644 --- a/src/torchlight/__init__.py +++ b/src/torchlight/__init__.py @@ -1 +1 @@ -__version__ = "1.5.1" +__version__ = "1.6.0"