diff --git a/bot/TeamTalk/structs.py b/bot/TeamTalk/structs.py index fc6caeb..85532b5 100644 --- a/bot/TeamTalk/structs.py +++ b/bot/TeamTalk/structs.py @@ -2,8 +2,6 @@ from enum import Enum, Flag import TeamTalkPy -major, minor, patch, build = TeamTalkPy.ttstr(TeamTalkPy.getVersion()).split(".") - class State(Enum): NOT_CONNECTED = 0 @@ -147,7 +145,7 @@ class UserStatusMode(Flag): N = 4096 -class UserRightPre15(Flag): +class UserRight(Flag): Null = TeamTalkPy.UserRight.USERRIGHT_NONE MultiLogin = TeamTalkPy.UserRight.USERRIGHT_MULTI_LOGIN ViewAllUsers = TeamTalkPy.UserRight.USERRIGHT_VIEW_ALL_USERS @@ -174,75 +172,6 @@ class UserRightPre15(Flag): ViewHiddenChannels = TeamTalkPy.UserRight.USERRIGHT_VIEW_HIDDEN_CHANNELS -if major == "5" and minor >= "15": - class UserRight15(Flag): - Null = TeamTalkPy.UserRight.USERRIGHT_NONE - MultiLogin = TeamTalkPy.UserRight.USERRIGHT_MULTI_LOGIN - ViewAllUsers = TeamTalkPy.UserRight.USERRIGHT_VIEW_ALL_USERS - CreateTemporaryChannel = TeamTalkPy.UserRight.USERRIGHT_CREATE_TEMPORARY_CHANNEL - ModifyChannels = TeamTalkPy.UserRight.USERRIGHT_MODIFY_CHANNELS - BroadcastTextMessage = TeamTalkPy.UserRight.USERRIGHT_TEXTMESSAGE_BROADCAST - KickUsers = TeamTalkPy.UserRight.USERRIGHT_KICK_USERS - BanUsers = TeamTalkPy.UserRight.USERRIGHT_BAN_USERS - MoveUsers = TeamTalkPy.UserRight.USERRIGHT_MOVE_USERS - OperatorEnable = TeamTalkPy.UserRight.USERRIGHT_OPERATOR_ENABLE - UploadFiles = TeamTalkPy.UserRight.USERRIGHT_UPLOAD_FILES - DownloadFiles = TeamTalkPy.UserRight.USERRIGHT_DOWNLOAD_FILES - UpdateServerProperties = TeamTalkPy.UserRight.USERRIGHT_UPDATE_SERVERPROPERTIES - TransmitVoice = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_VOICE - TransmitVideoCapture = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_VIDEOCAPTURE - TransmitDesktop = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_DESKTOP - TransmitDesktopInput = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_DESKTOPINPUT - TransmitMediaFileAudio = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_MEDIAFILE_AUDIO - TransmitMediaFileVideo = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_MEDIAFILE_VIDEO - TransmitMediaFile = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_MEDIAFILE - LockedNickname = TeamTalkPy.UserRight.USERRIGHT_LOCKED_NICKNAME - LockedStatus = TeamTalkPy.UserRight.USERRIGHT_LOCKED_STATUS - RecordVoice = TeamTalkPy.UserRight.USERRIGHT_RECORD_VOICE - ViewHiddenChannels = TeamTalkPy.UserRight.USERRIGHT_VIEW_HIDDEN_CHANNELS - textMessageUser = TeamTalkPy.UserRight.USERRIGHT_TEXTMESSAGE_USER - textMessageChannel = TeamTalkPy.UserRight.USERRIGHT_TEXTMESSAGE_CHANNEL - - UserRight = UserRight15 -else: - UserRight = UserRightPre15 - - -class UserRight15(Flag): - Null = TeamTalkPy.UserRight.USERRIGHT_NONE - MultiLogin = TeamTalkPy.UserRight.USERRIGHT_MULTI_LOGIN - ViewAllUsers = TeamTalkPy.UserRight.USERRIGHT_VIEW_ALL_USERS - CreateTemporaryChannel = TeamTalkPy.UserRight.USERRIGHT_CREATE_TEMPORARY_CHANNEL - ModifyChannels = TeamTalkPy.UserRight.USERRIGHT_MODIFY_CHANNELS - BroadcastTextMessage = TeamTalkPy.UserRight.USERRIGHT_TEXTMESSAGE_BROADCAST - KickUsers = TeamTalkPy.UserRight.USERRIGHT_KICK_USERS - BanUsers = TeamTalkPy.UserRight.USERRIGHT_BAN_USERS - MoveUsers = TeamTalkPy.UserRight.USERRIGHT_MOVE_USERS - OperatorEnable = TeamTalkPy.UserRight.USERRIGHT_OPERATOR_ENABLE - UploadFiles = TeamTalkPy.UserRight.USERRIGHT_UPLOAD_FILES - DownloadFiles = TeamTalkPy.UserRight.USERRIGHT_DOWNLOAD_FILES - UpdateServerProperties = TeamTalkPy.UserRight.USERRIGHT_UPDATE_SERVERPROPERTIES - TransmitVoice = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_VOICE - TransmitVideoCapture = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_VIDEOCAPTURE - TransmitDesktop = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_DESKTOP - TransmitDesktopInput = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_DESKTOPINPUT - TransmitMediaFileAudio = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_MEDIAFILE_AUDIO - TransmitMediaFileVideo = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_MEDIAFILE_VIDEO - TransmitMediaFile = TeamTalkPy.UserRight.USERRIGHT_TRANSMIT_MEDIAFILE - LockedNickname = TeamTalkPy.UserRight.USERRIGHT_LOCKED_NICKNAME - LockedStatus = TeamTalkPy.UserRight.USERRIGHT_LOCKED_STATUS - RecordVoice = TeamTalkPy.UserRight.USERRIGHT_RECORD_VOICE - ViewHiddenChannels = TeamTalkPy.UserRight.USERRIGHT_VIEW_HIDDEN_CHANNELS - textMessageUser = TeamTalkPy.UserRight.USERRIGHT_TEXTMESSAGE_USER - textMessageChannel = TeamTalkPy.UserRight.USERRIGHT_TEXTMESSAGE_CHANNEL - - -if major == "5" and minor >= "15": - UserRight = UserRight15 -else: - UserRight = UserRightPre15 - - class UserAccount: def __init__( self, diff --git a/bot/app_vars.py b/bot/app_vars.py index 429da1e..3bb87df 100644 --- a/bot/app_vars.py +++ b/bot/app_vars.py @@ -1,75 +1,26 @@ from __future__ import annotations import os -import platform from typing import Callable, TYPE_CHECKING if TYPE_CHECKING: from bot.translator import Translator -app_name = "pandorafox♾" -app_version = "2.4.1" - -def get_system_info() -> str: - system = platform.system() - release = platform.release() - version = platform.version() - machine = platform.machine() or "unknown" - processor = platform.processor() or "unknown" - architecture = platform.architecture()[0] # '64bit' or '32bit' - - if system == "Windows": - try: - import winreg - key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion") - product_name = winreg.QueryValueEx(key, "ProductName")[0] - release_id = winreg.QueryValueEx(key, "ReleaseId")[0] - system_str = f"{product_name} {release_id}" - except Exception: - system_str = f"Windows {release}" - elif system == "Linux": - try: - # Trying to get CPU info from /proc/cpuinfo - with open("/proc/cpuinfo") as f: - cpu_info = f.read() - # Get the processor information - processor = next((line.split(":")[1].strip() for line in cpu_info.splitlines() if "model name" in line), "unknown") - os_info = {} - with open("/etc/os-release") as f: - for line in f: - if "=" in line: - k, v = line.strip().split("=", 1) - os_info[k] = v.strip('"') - name = os_info.get("PRETTY_NAME") or os_info.get("NAME", "Linux") - system_str = name - except Exception: - system_str = f"Linux {release}" - elif system == "Darwin": - mac_ver = platform.mac_ver()[0] - system_str = f"macOS {mac_ver or release}" - else: - system_str = f"{system} {release}" - - return f"{system_str} | Arch: {architecture} | Machine: {machine} | CPU: {processor}" - -client_name = f"{app_name}-Version{app_version}-{get_system_info()}" - +app_name = "WalkMan" +app_version = "2.3.5" +client_name = app_name + "-V (Version)" + app_version about_text: Callable[[Translator], str] = lambda translator: translator.translate( """\ A media streaming bot for TeamTalk. Authors: Amir Gumerov, Vladislav Kopylov, Beqa Gozalishvili, Kirill Belousov. Home page: https://github.com/gumerov-amir/TTMediaBot -Pandorabox writer: Rexya, Muhammad. -Extended from pandoraBox (https://www.dropbox.com/scl/fi/w59od6p43v474cdqfllt1/PandoraBox.zip?rlkey=sghktp7rbuxknbz9b3v9lqfii&dl=1) -Currently maintain by Rafli On Techlabs git -Visit us on: https://git.techlabs.lol/radiant_code/teamtalkbot +Currently Running under: techlabs.lol (Leap over limits!) License: MIT License\ """ ) - start_bottt: Callable[[Translator], str] = lambda translator: translator.translate( """\ Hello there! -I'm PandoraFox, your go-to companion for discovering amazing songs and audio through YouTube, Yandex Music (Currently unavailable), and VK. +I'm PandoraBox, your go-to companion for discovering amazing songs and audio through YouTube, Yandex Music, and VK. Hosted by TechLabsStudio, I'm all set to bring audio magic to your TeamTalk experience. To get started, simply send me a private message with a specific command. Here's how you can interact with me: @@ -80,17 +31,25 @@ If you encounter any issues or want to chat with us, feel free to reach out. Thank you for choosing our service, and have a fantastic day!\ """ ) - contacts_bot: Callable[[Translator], str] = lambda translator: translator.translate( """\ If you encounter any issues with this bot, please reach out to our dedicated technicians: -🦊 Rafli: -rafli@techlabs.lol -t.me/rafli_ir + +- Muhammad: + - WhatsApp: https://api.whatsapp.com/send?phone=6282156978782 + - Telegram: https://t.me/muha_aku + +- Rexya: + - WhatsApp: https://api.whatsapp.com/send?phone=6288222553434 + - Email: rexya@infiartt.com + + - rafli: + - email: rafli@techlabs.lol + - telegram: rafli_ir + Join the TTMediaBot Official Group on Telegram: https://t.me/TTMediaBot_chat\ """ ) - fallback_service = "yt" loop_timeout = 0.01 max_message_length = 512 diff --git a/bot/cache.py b/bot/cache.py index 3032e23..152403b 100644 --- a/bot/cache.py +++ b/bot/cache.py @@ -12,37 +12,25 @@ import portalocker if TYPE_CHECKING: from bot.player.track import Track + cache_data_type = Dict[str, Any] class Cache: def __init__(self, cache_data: cache_data_type): - self.cache_version = cache_data.get("cache_version", CacheManager.version) - self.recents: deque[Track] = deque( - cache_data.get("recents", []), - maxlen=app_vars.recents_max_lenth + self.cache_version = cache_data["cache_version"] if "cache_version" in cache_data else CacheManager.version + self.recents: deque[Track] = ( + cache_data["recents"] + if "recents" in cache_data + else deque(maxlen=app_vars.recents_max_lenth) + ) + self.favorites: Dict[str, List[Track]] = ( + cache_data["favorites"] if "favorites" in cache_data else {} ) - self.favorites: Dict[str, List[Track]] = cache_data.get("favorites", {}) @property def data(self): - # Pastikan semua track di-recents & favorites adalah versi raw (non-stream) - sanitized_recents = deque( - (track.get_raw() if hasattr(track, "get_raw") else track) - for track in self.recents - ) - sanitized_favorites = { - user: [ - track.get_raw() if hasattr(track, "get_raw") else track - for track in tracks - ] - for user, tracks in self.favorites.items() - } - return { - "cache_version": self.cache_version, - "recents": sanitized_recents, - "favorites": sanitized_favorites - } + return {"cache_version": self.cache_version, "recents": self.recents, "favorites": self.favorites} class CacheManager: diff --git a/bot/commands/__init__.py b/bot/commands/__init__.py index 5de936d..7567b7c 100644 --- a/bot/commands/__init__.py +++ b/bot/commands/__init__.py @@ -40,7 +40,8 @@ class CommandProcessor: "contacts": user_commands.ContactsBot, "help": user_commands.HelpCommand, "p": user_commands.PlayPauseCommand, - "e": user_commands.QueueCommand, + "e": user_commands.EnqueueCommand, + "q": user_commands.QueueCommand, "u": user_commands.PlayUrlCommand, "sv": user_commands.ServiceCommand, "s": user_commands.StopCommand, diff --git a/bot/commands/user_commands.py b/bot/commands/user_commands.py index e34e692..88ffe68 100644 --- a/bot/commands/user_commands.py +++ b/bot/commands/user_commands.py @@ -1,7 +1,6 @@ from __future__ import annotations from typing import List, Optional, TYPE_CHECKING import os -import re from bot.commands.command import Command from bot.player.enums import Mode, State, TrackType @@ -116,6 +115,51 @@ class PlayUrlCommand(Command): else: raise errors.InvalidArgumentError +class EnqueueCommand(Command): + @property + def help(self) -> str: + return self.translator.translate( + "QUERY Finds a matching track and adds it to the queue" + ) + + def __call__(self, arg: str, user: User) -> Optional[str]: + if not arg: + raise errors.InvalidArgumentError + + self.run_async( + self.ttclient.send_message, + self.translator.translate("Searching and adding to the queue..."), + user, + ) + try: + track_list = self.service_manager.service.search(arg) + + # Take only the first track from the search results to enqueue + track_to_queue = [track_list[0]] + + # Check if the player was stopped to provide the correct response message + was_stopped = self.player.state == State.Stopped + + # Call the enqueue function with the single track + self.run_async(self.player.enqueue, track_to_queue) + + # Create the response message + if not was_stopped: + # If already playing, confirm the track was added to the queue + return self.translator.translate("Added to queue: {}").format( + track_to_queue[0].name + ) + else: + # If it was stopped, the track will start playing immediately + return self.translator.translate("Playing {}").format( + track_to_queue[0].name + ) + except errors.NothingFoundError: + return self.translator.translate("Nothing was found for your query") + except errors.ServiceError: + return self.translator.translate( + "The selected service is currently unavailable" + ) class StopCommand(Command): @property @@ -583,67 +627,6 @@ class DownloadCommand(Command): else: return self.translator.translate("Nothing is playing") -class QueueCommand(Command): - @property - def help(self) -> str: - return self.translator.translate( - "QUERY Adds a track to the queue. If no track is playing, plays immediately." - ) - - def __call__(self, arg: str, user: User) -> Optional[str]: - if not arg: - raise errors.InvalidArgumentError - - self.run_async( - self.ttclient.send_message, - self.translator.translate("Searching..."), - user, - ) - - try: - if re.match(r'http[s]?://', arg): - # Kalau URL, langsung stream - tracks = self.module_manager.streamer.get(arg, user.is_admin if user is not None else True) - self.player.add_to_queue(tracks) - else: - # Kalau bukan URL, cari lagu dari service - tracks = self.service_manager.service.search(arg) - self.player.add_to_queue(tracks) - - # Kalau gak lagi main apa-apa, langsung play - if self.player.state != State.Playing: - self.player.play_next() - - if self.config.general.send_channel_messages: - if re.match(r'http[s]?://', arg): - message = self.translator.translate( - "{nickname} added a stream URL to the queue." - ).format(nickname=user.nickname) - else: - message = self.translator.translate( - "{nickname} added {request} to the queue." - ).format(nickname=user.nickname, request=arg) - - self.run_async( - self.ttclient.send_message, - message, - type=2, - ) - - if re.match(r'http[s]?://', arg): - return self.translator.translate("Added stream URL to the queue.") - else: - return self.translator.translate("Added {} to the queue.").format( - tracks[0].name - ) - - except errors.NothingFoundError: - return self.translator.translate("Nothing is found for your query") - except errors.ServiceError: - return self.translator.translate( - "The selected service is currently unavailable" - ) - class ChangeLogCommand(Command): @property def help(self) -> str: @@ -689,3 +672,31 @@ class DefaultSeekStepCommand(Command): else: return self.translator.translate("Default Seek step can not be blank, Please specify default Seek step!") +class QueueCommand(Command): + @property + def help(self) -> str: + return self.translator.translate("Displays the current song queue") + + def __call__(self, arg: str, user: User) -> Optional[str]: + if not self.player.track_list or self.player.state == State.Stopped: + return self.translator.translate("The queue is empty.") + + # Get the currently playing track + now_playing = self.player.track + response = self.translator.translate("Now Playing: {track_name}\n\nQueue:\n").format(track_name=now_playing.name) + + # Get the rest of the queue (all tracks after the current one) + queue_list = self.player.track_list[self.player.track_index + 1:] + + if not queue_list: + response += self.translator.translate("No more tracks in the queue.") + return response + + for i, track in enumerate(queue_list[:10]): # Show max 10 tracks + response += f"{i + 1}. {track.name}\n" + + if len(queue_list) > 10: + remaining = len(queue_list) - 10 + response += self.translator.translate("\n...and {count} more.").format(count=remaining) + + return response \ No newline at end of file diff --git a/bot/player/__init__.py b/bot/player/__init__.py index 2e8c815..20f74aa 100644 --- a/bot/player/__init__.py +++ b/bot/player/__init__.py @@ -43,6 +43,7 @@ class Player: self.state = State.Stopped self.mode = Mode.TrackList self.volume = self.config.default_volume + self.manual_queue = False def initialize(self) -> None: logging.debug("Initializing player") @@ -55,6 +56,21 @@ class Player: self._player.observe_property("media-title", self.on_metadata_update) logging.debug("Player callbacks registered") + def enqueue(self, tracks: List[Track]) -> None: + """Adds tracks to the queue, clearing the old playlist if needed.""" + if self.state == State.Stopped or not self.track_list: + self.play(tracks) + self.manual_queue = True + else: + if not self.manual_queue: + now_playing_track = self.track_list[self.track_index] + self.track_list = [now_playing_track] + self.track_index = 0 # Reset index + self.manual_queue = True # Activate manual queue mode + + # Now, just add the new track(s) to the end of the (possibly new) list. + self.track_list.extend(tracks) + def close(self) -> None: logging.debug("Closing player") if self.state != State.Stopped: @@ -68,6 +84,7 @@ class Player: start_track_index: Optional[int] = None, ) -> None: if tracks != None: + self.manual_queue = False self.track_list = tracks if not start_track_index and self.mode == Mode.Random: self.shuffle(True) @@ -105,7 +122,6 @@ class Player: self.cache_manager.save() self._player.pause = False self._player.play(arg) - self._player.volume = self.volume def next(self) -> None: track_index = self.track_index @@ -157,7 +173,7 @@ class Player: def play_by_index(self, index: int) -> None: if index < len(self.track_list) and index >= (0 - len(self.track_list)): self.track = self.track_list[index] - self.track_index = index + self.track_index = self.track_list.index(self.track) self._play(self.track.url) self.state = State.Playing else: @@ -182,25 +198,6 @@ class Player: raise ValueError() self._player.speed = arg - def add_to_queue(self, tracks: List[Track]) -> None: - """Adds tracks to the queue.""" - self.track_list.extend(tracks) - logging.debug(f"Added {len(tracks)} track(s) to the queue.") - - # If nothing is playing, start playing the next track - if self.state == State.Stopped and len(self.track_list) > 0: - self.play_next() - - def play_next(self) -> None: - """Play the next track in the queue.""" - if len(self.track_list) > 0: - self.track_index = 0 # Start with the first track - self.track = self.track_list[self.track_index] - self._play(self.track.url) - self.state = State.Playing - else: - self.state = State.Stopped - def seek_back(self, step: Optional[float] = None) -> None: step = step if step else self.config.seek_step if step <= 0: diff --git a/changelog.txt b/changelog.txt index 2f9c890..bcbaf42 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,9 @@ This change log is written to find out the changes that have been made by Pandora, and the source code still refers to TTMediaBot. +24-08-2025 +1. Added queue. + 5/17/2025 -rollback to TTSDk5.12 due 5.8 is gone. +rollback to TTSDk5.12 due to 5.8 is gone. 5/14/2025 Added entry for scheduler command on readme.md Fixed file caching: