diff --git a/JJMumbleBot/core/bot_service.py b/JJMumbleBot/core/bot_service.py index 5663c1b2..b15f718a 100644 --- a/JJMumbleBot/core/bot_service.py +++ b/JJMumbleBot/core/bot_service.py @@ -1,4 +1,8 @@ import pymumble_py3 as pymumble +from pymumble_py3.constants import PYMUMBLE_CLBK_USERCREATED, PYMUMBLE_CLBK_CONNECTED, PYMUMBLE_CLBK_SOUNDRECEIVED, \ + PYMUMBLE_CLBK_TEXTMESSAGERECEIVED, PYMUMBLE_CLBK_DISCONNECTED, PYMUMBLE_CLBK_CHANNELUPDATED, \ + PYMUMBLE_CLBK_CHANNELREMOVED, PYMUMBLE_CLBK_CHANNELCREATED, PYMUMBLE_CLBK_USERREMOVED, PYMUMBLE_CLBK_USERUPDATED +from JJMumbleBot.core.callback_service import CallbackService from JJMumbleBot.lib.utils.web_utils import RemoteTextMessage from JJMumbleBot.settings import runtime_settings from JJMumbleBot.settings import global_settings @@ -26,6 +30,7 @@ class BotService: def __init__(self, serv_ip, serv_port, serv_pass): # Initialize bot services. global_settings.bot_service = self + global_settings.clbk_service = CallbackService() # Initialize user settings. BotServiceHelper.initialize_settings() # Initialize logging services. @@ -77,7 +82,7 @@ def __init__(self, serv_ip, serv_port, serv_pass): rprint("######### Initializing Mumble Client #########", origin=L_STARTUP) # Retrieve mumble client data from configs. mumble_login_data = BotServiceHelper.retrieve_mumble_data(serv_ip, serv_port, serv_pass) - BotService.initialize_mumble(mumble_login_data) + self.initialize_mumble(mumble_login_data) log(INFO, "######### Initialized Mumble Client #########", origin=L_STARTUP) rprint("######### Initialized Mumble Client #########", origin=L_STARTUP) # Initialize web interface @@ -91,13 +96,43 @@ def __init__(self, serv_ip, serv_port, serv_pass): # Start runtime loop. BotService.loop() - @staticmethod - def initialize_mumble(md: MumbleData): + def initialize_mumble(self, md: MumbleData): global_settings.mumble_inst = pymumble.Mumble(md.ip_address, port=md.port, user=md.user_id, reconnect=md.auto_reconnect, password=md.password, certfile=md.certificate, stereo=md.stereo) - global_settings.mumble_inst.callbacks.set_callback('text_received', BotService.message_received) - global_settings.mumble_inst.callbacks.set_callback('sound_received', BotService.sound_received) - global_settings.mumble_inst.callbacks.set_callback('connected', BotService.on_connected) + # Callback - message_received + global_settings.mumble_inst.callbacks.set_callback(PYMUMBLE_CLBK_TEXTMESSAGERECEIVED, + global_settings.clbk_service.message_received) + global_settings.core_callbacks.append_to_callback(PYMUMBLE_CLBK_TEXTMESSAGERECEIVED, self.message_received) + # Callback - sound_received + global_settings.mumble_inst.callbacks.set_callback(PYMUMBLE_CLBK_SOUNDRECEIVED, + global_settings.clbk_service.sound_received) + global_settings.core_callbacks.append_to_callback(PYMUMBLE_CLBK_SOUNDRECEIVED, self.sound_received) + # Callback - on_connected + global_settings.mumble_inst.callbacks.set_callback(PYMUMBLE_CLBK_CONNECTED, + global_settings.clbk_service.connected) + global_settings.core_callbacks.append_to_callback(PYMUMBLE_CLBK_CONNECTED, self.on_connected) + # Callback - disconnected + global_settings.mumble_inst.callbacks.set_callback(PYMUMBLE_CLBK_DISCONNECTED, + global_settings.clbk_service.disconnected) + # Callback - user_created + global_settings.mumble_inst.callbacks.set_callback(PYMUMBLE_CLBK_USERCREATED, + global_settings.clbk_service.user_created) + # Callback - user_updated + global_settings.mumble_inst.callbacks.set_callback(PYMUMBLE_CLBK_USERUPDATED, + global_settings.clbk_service.user_updated) + # Callback - user_removed + global_settings.mumble_inst.callbacks.set_callback(PYMUMBLE_CLBK_USERREMOVED, + global_settings.clbk_service.user_removed) + # Callback - channel_created + global_settings.mumble_inst.callbacks.set_callback(PYMUMBLE_CLBK_CHANNELCREATED, + global_settings.clbk_service.channel_created) + # Callback - channel_removed + global_settings.mumble_inst.callbacks.set_callback(PYMUMBLE_CLBK_CHANNELREMOVED, + global_settings.clbk_service.channel_removed) + # Callback - channel_updated + global_settings.mumble_inst.callbacks.set_callback(PYMUMBLE_CLBK_CHANNELUPDATED, + global_settings.clbk_service.channel_updated) + global_settings.mumble_inst.set_codec_profile('audio') global_settings.mumble_inst.set_receive_sound(True) global_settings.mumble_inst.start() @@ -110,8 +145,9 @@ def initialize_mumble(md: MumbleData): runtime_utils.mute() runtime_utils.get_channel(global_settings.cfg[C_CONNECTION_SETTINGS][P_CHANNEL_DEF]).move_in() - @staticmethod - def message_received(text, remote_cmd=False): + def message_received(self, message): + text = message[0] + remote_cmd = message[1] all_commands = runtime_utils.parse_message(text) if all_commands is None: return @@ -183,12 +219,12 @@ def message_received(text, remote_cmd=False): def process_command_queue(com): execute_cmd.execute_command(com) - @staticmethod - def on_connected(): + def on_connected(self): log(INFO, f"{runtime_utils.get_bot_name()} is Online.", origin=L_STARTUP) - @staticmethod - def sound_received(user, audio_chunk): + def sound_received(self, audio_data): + user = audio_data[0] + audio_chunk = audio_data[1] if audioop.rms(audio_chunk.pcm, 2) > global_settings.vlc_interface.status['ducking_threshold'] and global_settings.vlc_interface.status['duck_audio']: global_settings.vlc_interface.audio_utilities.duck_volume() global_settings.vlc_interface.status['duck_start'] = time() diff --git a/JJMumbleBot/core/callback_service.py b/JJMumbleBot/core/callback_service.py new file mode 100644 index 00000000..f5742658 --- /dev/null +++ b/JJMumbleBot/core/callback_service.py @@ -0,0 +1,49 @@ +from JJMumbleBot.settings import global_settings +from pymumble_py3.constants import PYMUMBLE_CLBK_USERCREATED, PYMUMBLE_CLBK_CONNECTED, PYMUMBLE_CLBK_SOUNDRECEIVED, \ + PYMUMBLE_CLBK_TEXTMESSAGERECEIVED, PYMUMBLE_CLBK_DISCONNECTED, PYMUMBLE_CLBK_CHANNELUPDATED, \ + PYMUMBLE_CLBK_CHANNELREMOVED, PYMUMBLE_CLBK_CHANNELCREATED, PYMUMBLE_CLBK_USERREMOVED, PYMUMBLE_CLBK_USERUPDATED + + +class CallbackService: + def __init__(self): + pass + + @staticmethod + def message_received(text, remote_cmd=False): + global_settings.core_callbacks.callback(PYMUMBLE_CLBK_TEXTMESSAGERECEIVED, text, remote_cmd) + + @staticmethod + def sound_received(user, audiochunk): + global_settings.core_callbacks.callback(PYMUMBLE_CLBK_SOUNDRECEIVED, user, audiochunk) + + @staticmethod + def connected(): + global_settings.core_callbacks.callback(PYMUMBLE_CLBK_CONNECTED) + + @staticmethod + def disconnected(): + global_settings.core_callbacks.callback(PYMUMBLE_CLBK_DISCONNECTED) + + @staticmethod + def channel_created(channel): + global_settings.core_callbacks.callback(PYMUMBLE_CLBK_CHANNELCREATED, channel) + + @staticmethod + def channel_updated(channel, modified_params): + global_settings.core_callbacks.callback(PYMUMBLE_CLBK_CHANNELUPDATED, channel, modified_params) + + @staticmethod + def channel_removed(channel): + global_settings.core_callbacks.callback(PYMUMBLE_CLBK_CHANNELREMOVED, channel) + + @staticmethod + def user_created(user): + global_settings.core_callbacks.callback(PYMUMBLE_CLBK_USERCREATED, user) + + @staticmethod + def user_updated(user, modified_params): + global_settings.core_callbacks.callback(PYMUMBLE_CLBK_USERUPDATED, user, modified_params) + + @staticmethod + def user_removed(user, message): + global_settings.core_callbacks.callback(PYMUMBLE_CLBK_USERREMOVED, user, message) diff --git a/JJMumbleBot/lib/callbacks.py b/JJMumbleBot/lib/callbacks.py index dd6b9316..7e6b7f8a 100644 --- a/JJMumbleBot/lib/callbacks.py +++ b/JJMumbleBot/lib/callbacks.py @@ -1,3 +1,7 @@ +from pymumble_py3 import constants +from JJMumbleBot.lib.utils.print_utils import dprint + + class Callbacks(dict): def __init__(self): super().__init__() @@ -20,11 +24,40 @@ def get_callback(self, callback): def callback(self, callback, *params): self[callback](params) - #if self[callback]: + # if self[callback]: # thr = Thread(target=self[callback], args=params) # thr.start() +class CoreCallbacks(Callbacks): + def __init__(self): + super().__init__() + self.update({ + constants.PYMUMBLE_CLBK_USERCREATED: [], + constants.PYMUMBLE_CLBK_TEXTMESSAGERECEIVED: [], + constants.PYMUMBLE_CLBK_SOUNDRECEIVED: [], + constants.PYMUMBLE_CLBK_CONNECTED: [], + constants.PYMUMBLE_CLBK_CHANNELCREATED: [], + constants.PYMUMBLE_CLBK_CHANNELREMOVED: [], + constants.PYMUMBLE_CLBK_CHANNELUPDATED: [], + constants.PYMUMBLE_CLBK_USERREMOVED: [], + constants.PYMUMBLE_CLBK_DISCONNECTED: [] + }) + + def register_callback(self, callback, dest: list): + self[callback] = dest + + def append_to_callback(self, callback, method): + self[callback].append(method) + + def callback(self, callback, *params): + for clbk in self[callback]: + try: + clbk(params) + except Exception as e: + dprint(e) + + class CommandCallbacks(dict): def __init__(self): super().__init__() diff --git a/JJMumbleBot/lib/helpers/bot_service_helper.py b/JJMumbleBot/lib/helpers/bot_service_helper.py index 8c6a692f..33364940 100644 --- a/JJMumbleBot/lib/helpers/bot_service_helper.py +++ b/JJMumbleBot/lib/helpers/bot_service_helper.py @@ -8,7 +8,7 @@ from JJMumbleBot.lib.utils.plugin_utils import PluginUtilityService from JJMumbleBot.lib.resources.strings import * from JJMumbleBot.lib.utils.logging_utils import log -from JJMumbleBot.lib.callbacks import Callbacks, CommandCallbacks +from JJMumbleBot.lib.callbacks import Callbacks, CommandCallbacks, CoreCallbacks class BotServiceHelper: @@ -32,6 +32,7 @@ def initialize_settings(): global_settings.mtd_callbacks = Callbacks() global_settings.cmd_callbacks = CommandCallbacks() global_settings.plugin_callbacks = Callbacks() + global_settings.core_callbacks = CoreCallbacks() runtime_settings.tick_rate = float(global_settings.cfg[C_MAIN_SETTINGS][P_CMD_TICK_RATE]) runtime_settings.cmd_hist_lim = int(global_settings.cfg[C_MAIN_SETTINGS][P_CMD_MULTI_LIM]) diff --git a/JJMumbleBot/lib/resources/strings.py b/JJMumbleBot/lib/resources/strings.py index 0a77e98f..2e98731d 100644 --- a/JJMumbleBot/lib/resources/strings.py +++ b/JJMumbleBot/lib/resources/strings.py @@ -104,5 +104,5 @@ ########################################################################### # BOT META INFORMATION STRINGS META_NAME = "JJMumbleBot" -META_VERSION = "4.0.0" +META_VERSION = "4.1.0" ########################################################################### diff --git a/JJMumbleBot/plugins/extensions/sound_board/metadata.ini b/JJMumbleBot/plugins/extensions/sound_board/metadata.ini index 1b066bd3..48d296d4 100644 --- a/JJMumbleBot/plugins/extensions/sound_board/metadata.ini +++ b/JJMumbleBot/plugins/extensions/sound_board/metadata.ini @@ -1,5 +1,5 @@ [Plugin Information] -PluginVersion = 4.0.0 +PluginVersion = 4.1.0 PluginName = Sound Board PluginDescription = The Sound Board plugin allows users to play saved sound clips in the channel. PluginLanguage = EN @@ -24,6 +24,10 @@ MaxSearchResults = 5 ; The fuzzy-search threshold between 0 to 100. Higher thresholds result in fewer, but more accurate search results. (default=80) ; A search threshold of 0 includes all files. FuzzySearchThreshold = 80 +; Play a selected audio clip when a user joins the server. +PlayAudioClipOnUserJoin = False +; If PlayAudioClipOnUserJoin is set to True, specify the track name (without file extension) below. +AudioClipToPlayOnUserJoin = ; List commands that need the core thread to wait for completion. ; This may include processes that require multiple commands in succession. ; For example: [Youtube Plugin - !yt -> !p] process requires 2 commands in that order. diff --git a/JJMumbleBot/plugins/extensions/sound_board/resources/strings.py b/JJMumbleBot/plugins/extensions/sound_board/resources/strings.py index 3d3e6c7e..2b6beea8 100644 --- a/JJMumbleBot/plugins/extensions/sound_board/resources/strings.py +++ b/JJMumbleBot/plugins/extensions/sound_board/resources/strings.py @@ -3,3 +3,5 @@ P_ENABLE_QUEUE = 'EnableQueue' P_MAX_SEARCH_RESULTS = 'MaxSearchResults' P_FUZZY_SEARCH_THRESHOLD = 'FuzzySearchThreshold' +P_PLAY_AUDIO_CLIP_ON_USER_JOIN = 'PlayAudioClipOnUserJoin' +P_AUDIO_CLIP_TO_PLAY_ON_USER_JOIN = 'AudioClipToPlayOnUserJoin' diff --git a/JJMumbleBot/plugins/extensions/sound_board/sound_board.py b/JJMumbleBot/plugins/extensions/sound_board/sound_board.py index 385a23df..631c8e53 100644 --- a/JJMumbleBot/plugins/extensions/sound_board/sound_board.py +++ b/JJMumbleBot/plugins/extensions/sound_board/sound_board.py @@ -10,9 +10,10 @@ from JJMumbleBot.lib.utils.runtime_utils import get_command_token from JJMumbleBot.lib.utils import dir_utils from JJMumbleBot.lib.vlc.vlc_api import TrackType, TrackInfo +from JJMumbleBot.lib.utils.runtime_utils import get_bot_name from os import path import random -from datetime import datetime, timedelta +from datetime import datetime from bs4 import BeautifulSoup @@ -27,6 +28,7 @@ def __init__(self): dir_utils.make_directory(f'{gs.cfg[C_MEDIA_SETTINGS][P_TEMP_MED_DIR]}/{self.plugin_name}/') sbu_settings.sound_board_metadata = self.metadata sbu_settings.plugin_name = self.plugin_name + self.register_callbacks() rprint( f"{self.metadata[C_PLUGIN_INFO][P_PLUGIN_NAME]} v{self.metadata[C_PLUGIN_INFO][P_PLUGIN_VERS]} Plugin Initialized.") @@ -38,6 +40,39 @@ def quit(self): dprint(f"Exiting {self.plugin_name} plugin...", origin=L_SHUTDOWN) log(INFO, f"Exiting {self.plugin_name} plugin...", origin=L_SHUTDOWN) + def register_callbacks(self): + from pymumble_py3.constants import PYMUMBLE_CLBK_USERCREATED + if self.metadata.getboolean(C_PLUGIN_SET, P_PLAY_AUDIO_CLIP_ON_USER_JOIN, fallback=False): + gs.core_callbacks.append_to_callback(PYMUMBLE_CLBK_USERCREATED, self.on_new_user_connected) + + def on_new_user_connected(self, user): + if gs.vlc_interface.check_dni(self.plugin_name, quiet=True): + gs.vlc_interface.set_dni(self.plugin_name, self.metadata[C_PLUGIN_INFO][P_PLUGIN_NAME]) + else: + return + + to_play = self.metadata[C_PLUGIN_SET][P_AUDIO_CLIP_TO_PLAY_ON_USER_JOIN] + if not to_play: + return + audio_clip = sbu.find_file(to_play) + if not path.exists(f"{dir_utils.get_perm_med_dir()}/{self.plugin_name}/{audio_clip}"): + gs.vlc_interface.clear_dni() + return + track_obj = TrackInfo( + uri=f'{dir_utils.get_perm_med_dir()}/{self.plugin_name}/{audio_clip}', + name=to_play, + sender=get_bot_name(), + duration=None, + track_type=TrackType.FILE, + quiet=True + ) + gs.vlc_interface.enqueue_track( + track_obj=track_obj, + to_front=False, + quiet=True + ) + gs.vlc_interface.play(override=True) + def cmd_sblist(self, data): internal_list = [] data_actor = gs.mumble_inst.users[data.actor] diff --git a/JJMumbleBot/settings/global_settings.py b/JJMumbleBot/settings/global_settings.py index bbded42d..0ba928e9 100644 --- a/JJMumbleBot/settings/global_settings.py +++ b/JJMumbleBot/settings/global_settings.py @@ -32,9 +32,11 @@ # Command Queue cmd_queue = None # Callbacks +clbk_service = None cmd_callbacks = None mtd_callbacks = None plugin_callbacks = None +core_callbacks = None # Aliases aliases = {} # Initialized Plugins. diff --git a/JJMumbleBot/web/web_helper.py b/JJMumbleBot/web/web_helper.py index 349e8a32..177a7926 100644 --- a/JJMumbleBot/web/web_helper.py +++ b/JJMumbleBot/web/web_helper.py @@ -12,6 +12,7 @@ from JJMumbleBot.lib import monitor_service from JJMumbleBot.lib.utils.web_utils import RemoteTextMessage from JJMumbleBot.lib.utils.runtime_utils import check_up_time, get_bot_name +from pymumble_py3.constants import PYMUMBLE_CLBK_TEXTMESSAGERECEIVED import json from os import urandom @@ -45,7 +46,7 @@ def post_message(): session=global_settings.mumble_inst.users.myself['session'], message=content, actor=global_settings.mumble_inst.users.myself['session']) - global_settings.bot_service.message_received(text=text, remote_cmd=True) + global_settings.core_callbacks.callback(PYMUMBLE_CLBK_TEXTMESSAGERECEIVED, text, True) # print(text.message) return content diff --git a/README.md b/README.md index 99b1f21f..0071497b 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ A plugin-based All-In-One mumble bot solution in python 3.7+ with extensive feat - Multi-Threaded Command Processing - Commands in the queue are handled in multiple threads for faster processing. - Reconfigurable Command Privileges - The user privileges required to execute commands can be completely reconfigured. - User Privileges System - Set user privileges to server users to limit the command usage on a per-user basis. +- Extensive Callback System - Create custom callbacks to mumble server events and more. ## Screenshots #### Audio Interface System (youtube plugin, sound board plugin, etc)