From 679c01dd56050970917da23d937280da698187d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jason=20=5B=EC=A0=9C=EC=9D=B4=EC=8A=A8=20=EC=A0=9C?=
=?UTF-8?q?=EB=A1=AC=5D?= <20238115+DuckBoss@users.noreply.github.com>
Date: Sat, 12 Sep 2020 18:30:08 -0400
Subject: [PATCH 1/3] Added callback system for mumble interfacing
- Added new callback system that can allow plugins to have custom callback methods for mumble events (such as receiving audio, messages, user connection/disconnection, etc).
- Ported built-in core bot methods to use new callback system for consistency.
- Updated web interface to use new callback system for remote commands.
---
JJMumbleBot/core/bot_service.py | 60 +++++++++++++++----
JJMumbleBot/core/callback_service.py | 49 +++++++++++++++
JJMumbleBot/lib/callbacks.py | 35 ++++++++++-
JJMumbleBot/lib/helpers/bot_service_helper.py | 3 +-
JJMumbleBot/settings/global_settings.py | 2 +
JJMumbleBot/web/web_helper.py | 3 +-
README.md | 1 +
7 files changed, 138 insertions(+), 15 deletions(-)
create mode 100644 JJMumbleBot/core/callback_service.py
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/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)
From a01c249bfaeada81c00a89f7d2f9dabfe89f262b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jason=20=5B=EC=A0=9C=EC=9D=B4=EC=8A=A8=20=EC=A0=9C?=
=?UTF-8?q?=EB=A1=AC=5D?= <20238115+DuckBoss@users.noreply.github.com>
Date: Sat, 12 Sep 2020 18:31:19 -0400
Subject: [PATCH 2/3] Implemented new callback system feature to Sound Board
- The sound board is now capable of playing a sound clip whenever a user connects to the server. This is configurable in the sound board plugins config.ini file.
This is an optional feature.
---
.../extensions/sound_board/metadata.ini | 4 ++
.../sound_board/resources/strings.py | 2 +
.../extensions/sound_board/sound_board.py | 37 ++++++++++++++++++-
3 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/JJMumbleBot/plugins/extensions/sound_board/metadata.ini b/JJMumbleBot/plugins/extensions/sound_board/metadata.ini
index 1b066bd3..c05b79cb 100644
--- a/JJMumbleBot/plugins/extensions/sound_board/metadata.ini
+++ b/JJMumbleBot/plugins/extensions/sound_board/metadata.ini
@@ -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]
From ae53027808ff03707ee5f51c744ae36869b392e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jason=20=5B=EC=A0=9C=EC=9D=B4=EC=8A=A8=20=EC=A0=9C?=
=?UTF-8?q?=EB=A1=AC=5D?= <20238115+DuckBoss@users.noreply.github.com>
Date: Sat, 12 Sep 2020 18:47:10 -0400
Subject: [PATCH 3/3] Updated bot version to v4.1.0
---
JJMumbleBot/lib/resources/strings.py | 2 +-
JJMumbleBot/plugins/extensions/sound_board/metadata.ini | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
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 c05b79cb..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