diff --git a/mumbleBot.py b/mumbleBot.py
index acba28e..a7b403a 100644
--- a/mumbleBot.py
+++ b/mumbleBot.py
@@ -27,6 +27,7 @@ import media.file
import media.playlist
import media.radio
import media.system
+from radiobrowser import getstations_byname
"""
FORMAT OF A MUSIC INTO THE PLAYLIST
@@ -70,13 +71,16 @@ class MumbleBot:
# Set specific format for the log
FORMAT = '%(asctime)s: %(message)s'
if args.verbose:
- logging.basicConfig(format=FORMAT, level=logging.DEBUG, datefmt='%Y-%m-%d %H:%M:%S')
+ logging.basicConfig(
+ format=FORMAT, level=logging.DEBUG, datefmt='%Y-%m-%d %H:%M:%S')
logging.debug("Starting in DEBUG loglevel")
elif args.quiet:
- logging.basicConfig(format=FORMAT, level=logging.ERROR, datefmt='%Y-%m-%d %H:%M:%S')
+ logging.basicConfig(
+ format=FORMAT, level=logging.ERROR, datefmt='%Y-%m-%d %H:%M:%S')
logging.error("Starting in ERROR loglevel")
else:
- logging.basicConfig(format=FORMAT, level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S')
+ logging.basicConfig(
+ format=FORMAT, level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S')
logging.info("Starting in INFO loglevel")
# the playlist is... a list (Surprise !!)
@@ -84,7 +88,8 @@ class MumbleBot:
var.user = args.user
var.music_folder = var.config.get('bot', 'music_folder')
- var.is_proxified = var.config.getboolean("webinterface", "is_web_proxified")
+ var.is_proxified = var.config.getboolean(
+ "webinterface", "is_web_proxified")
self.exit = False
self.nb_exit = 0
self.thread = None
@@ -94,7 +99,8 @@ class MumbleBot:
wi_addr = var.config.get("webinterface", "listening_addr")
wi_port = var.config.getint("webinterface", "listening_port")
interface.init_proxy()
- tt = threading.Thread(target=start_web_interface, args=(wi_addr, wi_port))
+ tt = threading.Thread(
+ target=start_web_interface, args=(wi_addr, wi_port))
tt.daemon = True
tt.start()
@@ -131,7 +137,8 @@ class MumbleBot:
self.mumble = pymumble.Mumble(host, user=self.username, port=port, password=password, tokens=tokens,
debug=var.config.getboolean('debug', 'mumbleConnection'), certfile=certificate)
- self.mumble.callbacks.set_callback("text_received", self.message_received)
+ self.mumble.callbacks.set_callback(
+ "text_received", self.message_received)
self.mumble.set_codec_profile("audio")
self.mumble.start() # start the mumble thread
@@ -146,7 +153,8 @@ class MumbleBot:
# Set the CTRL+C shortcut
def ctrl_caught(self, signal, frame):
- logging.info("\nSIGINT caught, quitting, {} more to kill".format(2 - self.nb_exit))
+ logging.info(
+ "\nSIGINT caught, quitting, {} more to kill".format(2 - self.nb_exit))
self.exit = True
self.stop()
if self.nb_exit > 1:
@@ -181,16 +189,19 @@ class MumbleBot:
logging.info(command + ' - ' + parameter + ' by ' + user)
if command == var.config.get('command', 'joinme'):
- self.mumble.users.myself.move_in(self.mumble.users[text.actor]['channel_id'], token=parameter)
+ self.mumble.users.myself.move_in(
+ self.mumble.users[text.actor]['channel_id'], token=parameter)
return
# Anti stupid guy function
if not self.is_admin(user) and not var.config.getboolean('bot', 'allow_other_channel_message') and self.mumble.users[text.actor]['channel_id'] != self.mumble.users.myself['channel_id']:
- self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_in_my_channel'))
+ self.mumble.users[text.actor].send_text_message(
+ var.config.get('strings', 'not_in_my_channel'))
return
if not self.is_admin(user) and not var.config.getboolean('bot', 'allow_private_message') and text.session:
- self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'pm_not_allowed'))
+ self.mumble.users[text.actor].send_text_message(
+ var.config.get('strings', 'pm_not_allowed'))
return
###
@@ -198,49 +209,61 @@ class MumbleBot:
###
for i in var.db.items("user_ban"):
if user.lower() == i[0]:
- self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'user_ban'))
+ self.mumble.users[text.actor].send_text_message(
+ var.config.get('strings', 'user_ban'))
return
if command == var.config.get('command', 'user_ban'):
if self.is_admin(user):
if parameter:
- self.mumble.users[text.actor].send_text_message(util.user_ban(parameter))
+ self.mumble.users[text.actor].send_text_message(
+ util.user_ban(parameter))
else:
- self.mumble.users[text.actor].send_text_message(util.get_user_ban())
+ self.mumble.users[text.actor].send_text_message(
+ util.get_user_ban())
else:
- self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_admin'))
+ self.mumble.users[text.actor].send_text_message(
+ var.config.get('strings', 'not_admin'))
return
elif command == var.config.get('command', 'user_unban'):
if self.is_admin(user):
if parameter:
- self.mumble.users[text.actor].send_text_message(util.user_unban(parameter))
+ self.mumble.users[text.actor].send_text_message(
+ util.user_unban(parameter))
else:
- self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_admin'))
+ self.mumble.users[text.actor].send_text_message(
+ var.config.get('strings', 'not_admin'))
return
elif command == var.config.get('command', 'url_ban'):
if self.is_admin(user):
if parameter:
- self.mumble.users[text.actor].send_text_message(util.url_ban(self.get_url_from_input(parameter)))
+ self.mumble.users[text.actor].send_text_message(
+ util.url_ban(self.get_url_from_input(parameter)))
else:
- self.mumble.users[text.actor].send_text_message(util.get_url_ban())
+ self.mumble.users[text.actor].send_text_message(
+ util.get_url_ban())
else:
- self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_admin'))
+ self.mumble.users[text.actor].send_text_message(
+ var.config.get('strings', 'not_admin'))
return
elif command == var.config.get('command', 'url_unban'):
if self.is_admin(user):
if parameter:
- self.mumble.users[text.actor].send_text_message(util.url_unban(self.get_url_from_input(parameter)))
+ self.mumble.users[text.actor].send_text_message(
+ util.url_unban(self.get_url_from_input(parameter)))
else:
- self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_admin'))
+ self.mumble.users[text.actor].send_text_message(
+ var.config.get('strings', 'not_admin'))
return
if parameter:
for i in var.db.items("url_ban"):
if self.get_url_from_input(parameter.lower()) == i[0]:
- self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'url_ban'))
+ self.mumble.users[text.actor].send_text_message(
+ var.config.get('strings', 'url_ban'))
return
###
@@ -259,16 +282,19 @@ class MumbleBot:
var.playlist.append(music)
else:
# try to do a partial match
- matches = [file for file in util.get_recursive_filelist_sorted(music_folder) if parameter.lower() in file.lower()]
+ matches = [file for file in util.get_recursive_filelist_sorted(
+ music_folder) if parameter.lower() in file.lower()]
if len(matches) == 0:
- self.send_msg(var.config.get('strings', 'no_file'), text)
+ self.send_msg(var.config.get(
+ 'strings', 'no_file'), text)
elif len(matches) == 1:
music = {'type': 'file',
'path': matches[0],
'user': user}
var.playlist.append(music)
else:
- msg = var.config.get('strings', 'multiple_matches') + '
'
+ msg = var.config.get(
+ 'strings', 'multiple_matches') + '
'
msg += '
'.join(matches)
self.send_msg(msg, text)
else:
@@ -277,7 +303,8 @@ class MumbleBot:
elif command == var.config.get('command', 'play_url') and parameter:
music = {'type': 'url',
- 'url': self.get_url_from_input(parameter), # grab the real URL
+ # grab the real URL
+ 'url': self.get_url_from_input(parameter),
'user': user,
'ready': 'validation'}
var.playlist.append(music)
@@ -285,18 +312,21 @@ class MumbleBot:
if media.url.get_url_info():
if var.playlist[-1]['duration'] > var.config.getint('bot', 'max_track_duration'):
var.playlist.pop()
- self.send_msg(var.config.get('strings', 'too_long'), text)
+ self.send_msg(var.config.get(
+ 'strings', 'too_long'), text)
else:
for i in var.db.options("url_ban"):
if var.playlist[-1]['url'] == i:
- self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'url_ban'))
+ self.mumble.users[text.actor].send_text_message(
+ var.config.get('strings', 'url_ban'))
var.playlist.pop()
return
var.playlist[-1]['ready'] = "no"
self.async_download_next()
else:
var.playlist.pop()
- self.send_msg(var.config.get('strings', 'unable_download'), text)
+ self.send_msg(var.config.get(
+ 'strings', 'unable_download'), text)
elif command == var.config.get('command', 'play_playlist') and parameter:
offset = 1 # if you want to start the playlist at a specific index
@@ -310,7 +340,8 @@ class MumbleBot:
elif command == var.config.get('command', 'play_radio'):
if not parameter:
all_radio = var.config.items('radio')
- msg = var.config.get('strings', 'preconfigurated_radio') + " :"
+ msg = var.config.get(
+ 'strings', 'preconfigurated_radio') + " :"
for i in all_radio:
comment = ""
if len(i[1].split(maxsplit=1)) == 2:
@@ -336,11 +367,16 @@ class MumbleBot:
logging.info('rbquery without parameter')
else:
logging.info('Found query parameter: ' + parameter)
+ stations = getstations_byname(parameter)
+ for s in stations:
+ msg += "
" + s
+ self.send_msg(msg, text)
elif command == var.config.get('command', 'help'):
self.send_msg(var.config.get('strings', 'help'), text)
if self.is_admin(user):
- self.send_msg(var.config.get('strings', 'admin_help'), text)
+ self.send_msg(var.config.get(
+ 'strings', 'admin_help'), text)
elif command == var.config.get('command', 'stop'):
self.stop()
@@ -350,16 +386,19 @@ class MumbleBot:
self.stop()
self.exit = True
else:
- self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_admin'))
+ self.mumble.users[text.actor].send_text_message(
+ var.config.get('strings', 'not_admin'))
elif command == var.config.get('command', 'update'):
if self.is_admin(user):
- self.mumble.users[text.actor].send_text_message("Starting the update")
+ self.mumble.users[text.actor].send_text_message(
+ "Starting the update")
# Need to be improved
msg = util.update(version)
self.mumble.users[text.actor].send_text_message(msg)
else:
- self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_admin'))
+ self.mumble.users[text.actor].send_text_message(
+ var.config.get('strings', 'not_admin'))
elif command == var.config.get('command', 'stop_and_getout'):
self.stop()
@@ -374,14 +413,16 @@ class MumbleBot:
int(self.volume * 100), self.mumble.users[text.actor]['name']), text)
var.db.set('bot', 'volume', str(self.volume))
else:
- self.send_msg(var.config.get('strings', 'current_volume') % int(self.volume * 100), text)
+ self.send_msg(var.config.get(
+ 'strings', 'current_volume') % int(self.volume * 100), text)
elif command == var.config.get('command', 'current_music'):
if len(var.playlist) > 0:
source = var.playlist[0]["type"]
if source == "radio":
reply = "[radio] {title} on {url} by {user}".format(
- title=media.radio.get_radio_title(var.playlist[0]["url"]),
+ title=media.radio.get_radio_title(
+ var.playlist[0]["url"]),
url=var.playlist[0]["title"],
user=var.playlist[0]["user"]
)
@@ -411,19 +452,23 @@ class MumbleBot:
self.send_msg(reply, text)
elif command == var.config.get('command', 'skip'):
- if parameter is not None and parameter.isdigit() and int(parameter) > 0: # Allow to remove specific music into the queue with a number
+ # Allow to remove specific music into the queue with a number
+ if parameter is not None and parameter.isdigit() and int(parameter) > 0:
if int(parameter) < len(var.playlist):
removed = var.playlist.pop(int(parameter))
# the Title isn't here if the music wasn't downloaded
- self.send_msg(var.config.get('strings', 'removing_item') % (removed['title'] if 'title' in removed else removed['url']), text)
+ self.send_msg(var.config.get('strings', 'removing_item') % (
+ removed['title'] if 'title' in removed else removed['url']), text)
else:
- self.send_msg(var.config.get('strings', 'no_possible'), text)
+ self.send_msg(var.config.get(
+ 'strings', 'no_possible'), text)
elif self.next(): # Is no number send, just skip the current music
self.launch_music()
self.async_download_next()
else:
- self.send_msg(var.config.get('strings', 'queue_empty'), text)
+ self.send_msg(var.config.get(
+ 'strings', 'queue_empty'), text)
self.stop()
elif command == var.config.get('command', 'list'):
@@ -439,10 +484,12 @@ class MumbleBot:
if len(var.playlist) <= 1:
msg = var.config.get('strings', 'queue_empty')
else:
- msg = var.config.get('strings', 'queue_contents') + '
'
+ msg = var.config.get(
+ 'strings', 'queue_contents') + '
'
i = 1
for value in var.playlist[1:]:
- msg += '[{}] ({}) {}
'.format(i, value['type'], value['title'] if 'title' in value else value['url'])
+ msg += '[{}] ({}) {}
'.format(i, value['type'],
+ value['title'] if 'title' in value else value['url'])
i += 1
self.send_msg(msg, text)
@@ -451,7 +498,8 @@ class MumbleBot:
var.playlist.append(var.playlist[0])
else:
- self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'bad_command'))
+ self.mumble.users[text.actor].send_text_message(
+ var.config.get('strings', 'bad_command'))
@staticmethod
def is_admin(user):
@@ -478,7 +526,8 @@ class MumbleBot:
logging.debug("launch_music asked" + str(var.playlist[0]))
if var.playlist[0]["type"] == "url":
# Delete older music is the tmp folder is too big
- media.system.clear_tmp_folder(var.config.get('bot', 'tmp_folder'), var.config.getint('bot', 'tmp_folder_max_size'))
+ media.system.clear_tmp_folder(var.config.get(
+ 'bot', 'tmp_folder'), var.config.getint('bot', 'tmp_folder_max_size'))
# Check if the music is ready to be played
if var.playlist[0]["ready"] == "downloading":
@@ -493,9 +542,11 @@ class MumbleBot:
audio = EasyID3(uri)
title = ""
if audio["title"]:
- title = audio["title"][0] # take the title from the file tag
+ # take the title from the file tag
+ title = audio["title"][0]
- path_thumbnail = var.playlist[0]['path'][:-4] + '.jpg' # Remove .mp3 and add .jpg
+ # Remove .mp3 and add .jpg
+ path_thumbnail = var.playlist[0]['path'][:-4] + '.jpg'
thumbnail_html = ""
if os.path.isfile(path_thumbnail):
# Create the image message
@@ -504,33 +555,39 @@ class MumbleBot:
buffer = BytesIO()
im.save(buffer, format="JPEG")
thumbnail_base64 = base64.b64encode(buffer.getvalue())
- thumbnail_html = '
'
+ thumbnail_html = '
'
logging.debug("Thunbail data " + thumbnail_html)
if var.config.getboolean('bot', 'announce_current_music'):
- self.send_msg(var.config.get('strings', 'now_playing') % (title, thumbnail_html))
+ self.send_msg(var.config.get(
+ 'strings', 'now_playing') % (title, thumbnail_html))
else:
logging.error("Error with the path during launch_music")
pass
elif var.playlist[0]["type"] == "file":
- uri = var.config.get('bot', 'music_folder') + var.playlist[0]["path"]
+ uri = var.config.get('bot', 'music_folder') + \
+ var.playlist[0]["path"]
elif var.playlist[0]["type"] == "radio":
uri = var.playlist[0]["url"]
title = media.radio.get_radio_server_description(uri)
var.playlist[0]["title"] = title
if var.config.getboolean('bot', 'announce_current_music'):
- self.send_msg(var.config.get('strings', 'now_playing') % (title, "URL : " + uri))
+ self.send_msg(var.config.get('strings', 'now_playing') %
+ (title, "URL : " + uri))
if var.config.getboolean('debug', 'ffmpeg'):
ffmpeg_debug = "debug"
else:
ffmpeg_debug = "warning"
- command = ("ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-i', uri, '-ac', '1', '-f', 's16le', '-ar', '48000', '-')
+ command = ("ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-i',
+ uri, '-ac', '1', '-f', 's16le', '-ar', '48000', '-')
logging.info("FFmpeg command : " + " ".join(command))
- self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480) # The ffmpeg process is a thread
+ # The ffmpeg process is a thread
+ self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480)
self.is_playing = True
def download_music(self, index):
@@ -539,7 +596,8 @@ class MumbleBot:
if var.playlist[index]['duration'] > var.config.getint('bot', 'max_track_duration'):
# Check the length, useful in case of playlist, it wasn't checked before)
var.playlist.pop()
- logging.info("the music " + var.playlist[index]["url"] + " has a duration of " + var.playlist[index]['duration'] + "s -- too long")
+ logging.info(
+ "the music " + var.playlist[index]["url"] + " has a duration of " + var.playlist[index]['duration'] + "s -- too long")
self.send_msg(var.config.get('strings', 'too_long'))
return
else:
@@ -580,9 +638,11 @@ class MumbleBot:
'preferredquality': '192'},
{'key': 'FFmpegMetadata'}]
}
- self.send_msg(var.config.get('strings', "download_in_progress") % var.playlist[index]['title'])
+ self.send_msg(var.config.get(
+ 'strings', "download_in_progress") % var.playlist[index]['title'])
- logging.info("Information before start downloading :" + str(var.playlist[index]))
+ logging.info("Information before start downloading :" +
+ str(var.playlist[index]))
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
for i in range(2): # Always try 2 times
try:
@@ -600,7 +660,8 @@ class MumbleBot:
# Do nothing in case the next music is already downloaded
logging.info("Async download next asked")
if len(var.playlist) > 1 and var.playlist[1]['type'] == 'url' and var.playlist[1]['ready'] in ["no", "validation"]:
- th = threading.Thread(target=self.download_music, kwargs={'index': 1})
+ th = threading.Thread(
+ target=self.download_music, kwargs={'index': 1})
else:
return
logging.info("Start downloading next in thread")
@@ -632,7 +693,8 @@ class MumbleBot:
raw_music = self.thread.stdout.read(480)
if raw_music:
# Adjust the volume and send it to mumble
- self.mumble.sound_output.add_sound(audioop.mul(raw_music, 2, self.volume))
+ self.mumble.sound_output.add_sound(
+ audioop.mul(raw_music, 2, self.volume))
else:
time.sleep(0.1)
else:
@@ -686,30 +748,44 @@ def start_web_interface(addr, port):
if __name__ == '__main__':
- parser = argparse.ArgumentParser(description='Bot for playing music on Mumble')
+ parser = argparse.ArgumentParser(
+ description='Bot for playing music on Mumble')
# General arguments
- parser.add_argument("--config", dest='config', type=str, default='configuration.ini', help='Load configuration from this file. Default: configuration.ini')
- parser.add_argument("--db", dest='db', type=str, default='db.ini', help='database file. Default db.ini')
+ parser.add_argument("--config", dest='config', type=str, default='configuration.ini',
+ help='Load configuration from this file. Default: configuration.ini')
+ parser.add_argument("--db", dest='db', type=str,
+ default='db.ini', help='database file. Default db.ini')
- parser.add_argument("-q", "--quiet", dest="quiet", action="store_true", help="Only Error logs")
- parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Show debug log")
+ parser.add_argument("-q", "--quiet", dest="quiet",
+ action="store_true", help="Only Error logs")
+ parser.add_argument("-v", "--verbose", dest="verbose",
+ action="store_true", help="Show debug log")
# Mumble arguments
- parser.add_argument("-s", "--server", dest="host", type=str, help="Hostname of the Mumble server")
- parser.add_argument("-u", "--user", dest="user", type=str, help="Username for the bot")
- parser.add_argument("-P", "--password", dest="password", type=str, help="Server password, if required")
- parser.add_argument("-T", "--tokens", dest="tokens", type=str, help="Server tokens, if required")
- parser.add_argument("-p", "--port", dest="port", type=int, help="Port for the Mumble server")
- parser.add_argument("-c", "--channel", dest="channel", type=str, help="Default channel for the bot")
- parser.add_argument("-C", "--cert", dest="certificate", type=str, default=None, help="Certificate file")
+ parser.add_argument("-s", "--server", dest="host",
+ type=str, help="Hostname of the Mumble server")
+ parser.add_argument("-u", "--user", dest="user",
+ type=str, help="Username for the bot")
+ parser.add_argument("-P", "--password", dest="password",
+ type=str, help="Server password, if required")
+ parser.add_argument("-T", "--tokens", dest="tokens",
+ type=str, help="Server tokens, if required")
+ parser.add_argument("-p", "--port", dest="port",
+ type=int, help="Port for the Mumble server")
+ parser.add_argument("-c", "--channel", dest="channel",
+ type=str, help="Default channel for the bot")
+ parser.add_argument("-C", "--cert", dest="certificate",
+ type=str, default=None, help="Certificate file")
args = parser.parse_args()
var.dbfile = args.db
config = configparser.ConfigParser(interpolation=None, allow_no_value=True)
- parsed_configs = config.read(['configuration.default.ini', args.config], encoding='latin-1')
+ parsed_configs = config.read(
+ ['configuration.default.ini', args.config], encoding='latin-1')
- db = configparser.ConfigParser(interpolation=None, allow_no_value=True, delimiters='²')
+ db = configparser.ConfigParser(
+ interpolation=None, allow_no_value=True, delimiters='²')
db.read(var.dbfile, encoding='latin-1')
if 'url_ban' not in db.sections():
@@ -720,7 +796,8 @@ if __name__ == '__main__':
db.add_section('user_ban')
if len(parsed_configs) == 0:
- logging.error('Could not read configuration from file \"{}\"'.format(args.config), file=sys.stderr)
+ logging.error('Could not read configuration from file \"{}\"'.format(
+ args.config), file=sys.stderr)
sys.exit()
var.config = config
diff --git a/mumbleBot.py.bak b/mumbleBot.py.bak
new file mode 100644
index 0000000..2326f53
--- /dev/null
+++ b/mumbleBot.py.bak
@@ -0,0 +1,732 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import threading
+import time
+import sys
+import signal
+import configparser
+import audioop
+import subprocess as sp
+import argparse
+import os.path
+import pymumble.pymumble_py3 as pymumble
+import interface
+import variables as var
+import hashlib
+import youtube_dl
+import logging
+import util
+import base64
+from PIL import Image
+from io import BytesIO
+from mutagen.easyid3 import EasyID3
+import re
+import media.url
+import media.file
+import media.playlist
+import media.radio
+import media.system
+from radiobrowser import getstations_byname
+
+"""
+FORMAT OF A MUSIC INTO THE PLAYLIST
+type : url
+ url
+ title
+ path
+ duration
+ thundnail
+ user
+ ready (validation, no, downloading, yes)
+ from_playlist (yes,no)
+ playlist_title
+ playlist_url
+
+type : radio
+ url
+ name
+ current_title
+ user
+
+type : file
+ path
+ title
+ duration
+ user
+"""
+
+version = 2
+
+
+class MumbleBot:
+ def __init__(self, args):
+ signal.signal(signal.SIGINT, self.ctrl_caught)
+ self.volume = var.config.getfloat('bot', 'volume')
+ if db.has_option('bot', 'volume'):
+ self.volume = var.db.getfloat('bot', 'volume')
+
+ self.channel = args.channel
+
+ # Set specific format for the log
+ FORMAT = '%(asctime)s: %(message)s'
+ if args.verbose:
+ logging.basicConfig(format=FORMAT, level=logging.DEBUG, datefmt='%Y-%m-%d %H:%M:%S')
+ logging.debug("Starting in DEBUG loglevel")
+ elif args.quiet:
+ logging.basicConfig(format=FORMAT, level=logging.ERROR, datefmt='%Y-%m-%d %H:%M:%S')
+ logging.error("Starting in ERROR loglevel")
+ else:
+ logging.basicConfig(format=FORMAT, level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S')
+ logging.info("Starting in INFO loglevel")
+
+ # the playlist is... a list (Surprise !!)
+ var.playlist = []
+
+ var.user = args.user
+ var.music_folder = var.config.get('bot', 'music_folder')
+ var.is_proxified = var.config.getboolean("webinterface", "is_web_proxified")
+ self.exit = False
+ self.nb_exit = 0
+ self.thread = None
+ self.is_playing = False
+
+ if var.config.getboolean("webinterface", "enabled"):
+ wi_addr = var.config.get("webinterface", "listening_addr")
+ wi_port = var.config.getint("webinterface", "listening_port")
+ interface.init_proxy()
+ tt = threading.Thread(target=start_web_interface, args=(wi_addr, wi_port))
+ tt.daemon = True
+ tt.start()
+
+ if args.host:
+ host = args.host
+ else:
+ host = var.config.get("server", "host")
+
+ if args.port:
+ port = args.port
+ else:
+ port = var.config.getint("server", "port")
+
+ if args.password:
+ password = args.password
+ else:
+ password = var.config.get("server", "password")
+
+ if args.certificate:
+ certificate = args.certificate
+ else:
+ certificate = var.config.get("server", "certificate")
+
+ if args.tokens:
+ tokens = args.tokens
+ else:
+ tokens = var.config.get("server", "tokens")
+ tokens = tokens.split(',')
+
+ if args.user:
+ self.username = args.user
+ else:
+ self.username = var.config.get("bot", "username")
+
+ self.mumble = pymumble.Mumble(host, user=self.username, port=port, password=password, tokens=tokens,
+ debug=var.config.getboolean('debug', 'mumbleConnection'), certfile=certificate)
+ self.mumble.callbacks.set_callback("text_received", self.message_received)
+
+ self.mumble.set_codec_profile("audio")
+ self.mumble.start() # start the mumble thread
+ self.mumble.is_ready() # wait for the connection
+ self.set_comment()
+ self.mumble.users.myself.unmute() # by sure the user is not muted
+ if self.channel:
+ self.mumble.channels.find_by_name(self.channel).move_in()
+ self.mumble.set_bandwidth(200000)
+
+ self.loop()
+
+ # Set the CTRL+C shortcut
+ def ctrl_caught(self, signal, frame):
+ logging.info("\nSIGINT caught, quitting, {} more to kill".format(2 - self.nb_exit))
+ self.exit = True
+ self.stop()
+ if self.nb_exit > 1:
+ logging.info("Forced Quit")
+ sys.exit(0)
+ self.nb_exit += 1
+
+ # All text send to the chat is analysed by this function
+ def message_received(self, text):
+
+ message = text.message.strip()
+ user = self.mumble.users[text.actor]['name']
+
+ if var.config.getboolean('command', 'split_username_at_space'):
+ # in can you use https://github.com/Natenom/mumblemoderator-module-collection/tree/master/os-suffixes , you want to split the username
+ user = user.split()[0]
+
+ if message[0] == var.config.get('command', 'command_symbol'):
+ # remove the symbol from the message
+ message = message[1:].split(' ', 1)
+
+ # use the first word as a command, the others one as parameters
+ if len(message) > 0:
+ command = message[0]
+ parameter = ''
+ if len(message) > 1:
+ parameter = message[1]
+
+ else:
+ return
+
+ logging.info(command + ' - ' + parameter + ' by ' + user)
+
+ if command == var.config.get('command', 'joinme'):
+ self.mumble.users.myself.move_in(self.mumble.users[text.actor]['channel_id'], token=parameter)
+ return
+
+ # Anti stupid guy function
+ if not self.is_admin(user) and not var.config.getboolean('bot', 'allow_other_channel_message') and self.mumble.users[text.actor]['channel_id'] != self.mumble.users.myself['channel_id']:
+ self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_in_my_channel'))
+ return
+
+ if not self.is_admin(user) and not var.config.getboolean('bot', 'allow_private_message') and text.session:
+ self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'pm_not_allowed'))
+ return
+
+ ###
+ # Admin command
+ ###
+ for i in var.db.items("user_ban"):
+ if user.lower() == i[0]:
+ self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'user_ban'))
+ return
+
+ if command == var.config.get('command', 'user_ban'):
+ if self.is_admin(user):
+ if parameter:
+ self.mumble.users[text.actor].send_text_message(util.user_ban(parameter))
+ else:
+ self.mumble.users[text.actor].send_text_message(util.get_user_ban())
+ else:
+ self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_admin'))
+ return
+
+ elif command == var.config.get('command', 'user_unban'):
+ if self.is_admin(user):
+ if parameter:
+ self.mumble.users[text.actor].send_text_message(util.user_unban(parameter))
+ else:
+ self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_admin'))
+ return
+
+ elif command == var.config.get('command', 'url_ban'):
+ if self.is_admin(user):
+ if parameter:
+ self.mumble.users[text.actor].send_text_message(util.url_ban(self.get_url_from_input(parameter)))
+ else:
+ self.mumble.users[text.actor].send_text_message(util.get_url_ban())
+ else:
+ self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_admin'))
+ return
+
+ elif command == var.config.get('command', 'url_unban'):
+ if self.is_admin(user):
+ if parameter:
+ self.mumble.users[text.actor].send_text_message(util.url_unban(self.get_url_from_input(parameter)))
+ else:
+ self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_admin'))
+ return
+
+ if parameter:
+ for i in var.db.items("url_ban"):
+ if self.get_url_from_input(parameter.lower()) == i[0]:
+ self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'url_ban'))
+ return
+
+ ###
+ # everyday commands
+ ###
+ if command == var.config.get('command', 'play_file') and parameter:
+ music_folder = var.config.get('bot', 'music_folder')
+ # sanitize "../" and so on
+ path = os.path.abspath(os.path.join(music_folder, parameter))
+ if path.startswith(music_folder):
+ if os.path.isfile(path):
+ filename = path.replace(music_folder, '')
+ music = {'type': 'file',
+ 'path': filename,
+ 'user': user}
+ var.playlist.append(music)
+ else:
+ # try to do a partial match
+ matches = [file for file in util.get_recursive_filelist_sorted(music_folder) if parameter.lower() in file.lower()]
+ if len(matches) == 0:
+ self.send_msg(var.config.get('strings', 'no_file'), text)
+ elif len(matches) == 1:
+ music = {'type': 'file',
+ 'path': matches[0],
+ 'user': user}
+ var.playlist.append(music)
+ else:
+ msg = var.config.get('strings', 'multiple_matches') + '
'
+ msg += '
'.join(matches)
+ self.send_msg(msg, text)
+ else:
+ self.send_msg(var.config.get('strings', 'bad_file'), text)
+ self.async_download_next()
+
+ elif command == var.config.get('command', 'play_url') and parameter:
+ music = {'type': 'url',
+ 'url': self.get_url_from_input(parameter), # grab the real URL
+ 'user': user,
+ 'ready': 'validation'}
+ var.playlist.append(music)
+
+ if media.url.get_url_info():
+ if var.playlist[-1]['duration'] > var.config.getint('bot', 'max_track_duration'):
+ var.playlist.pop()
+ self.send_msg(var.config.get('strings', 'too_long'), text)
+ else:
+ for i in var.db.options("url_ban"):
+ if var.playlist[-1]['url'] == i:
+ self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'url_ban'))
+ var.playlist.pop()
+ return
+ var.playlist[-1]['ready'] = "no"
+ self.async_download_next()
+ else:
+ var.playlist.pop()
+ self.send_msg(var.config.get('strings', 'unable_download'), text)
+
+ elif command == var.config.get('command', 'play_playlist') and parameter:
+ offset = 1 # if you want to start the playlist at a specific index
+ try:
+ offset = int(parameter.split(" ")[-1])
+ except ValueError:
+ pass
+ if media.playlist.get_playlist_info(url=self.get_url_from_input(parameter), start_index=offset, user=user):
+ self.async_download_next()
+
+ elif command == var.config.get('command', 'play_radio'):
+ if not parameter:
+ all_radio = var.config.items('radio')
+ msg = var.config.get('strings', 'preconfigurated_radio') + " :"
+ for i in all_radio:
+ comment = ""
+ if len(i[1].split(maxsplit=1)) == 2:
+ comment = " - " + i[1].split(maxsplit=1)[1]
+ msg += "
" + i[0] + comment
+ self.send_msg(msg, text)
+ else:
+ if var.config.has_option('radio', parameter):
+ parameter = var.config.get('radio', parameter)
+ parameter = parameter.split()[0]
+ url = self.get_url_from_input(parameter)
+ if url:
+ music = {'type': 'radio',
+ 'url': url,
+ 'user': user}
+ var.playlist.append(music)
+ self.async_download_next()
+ else:
+ self.send_msg(var.config.get('strings', 'bad_url'))
+ elif command == var.config.get('command', 'rb_query'):
+ logging.info('Querying radio stations')
+ if not parameter:
+ logging.info('rbquery without parameter')
+ else:
+ logging.info('Found query parameter: ' + parameter)
+ stations = getstations_byname(parameter)
+
+
+
+ elif command == var.config.get('command', 'help'):
+ self.send_msg(var.config.get('strings', 'help'), text)
+ if self.is_admin(user):
+ self.send_msg(var.config.get('strings', 'admin_help'), text)
+
+ elif command == var.config.get('command', 'stop'):
+ self.stop()
+
+ elif command == var.config.get('command', 'kill'):
+ if self.is_admin(user):
+ self.stop()
+ self.exit = True
+ else:
+ self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_admin'))
+
+ elif command == var.config.get('command', 'update'):
+ if self.is_admin(user):
+ self.mumble.users[text.actor].send_text_message("Starting the update")
+ # Need to be improved
+ msg = util.update(version)
+ self.mumble.users[text.actor].send_text_message(msg)
+ else:
+ self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'not_admin'))
+
+ elif command == var.config.get('command', 'stop_and_getout'):
+ self.stop()
+ if self.channel:
+ self.mumble.channels.find_by_name(self.channel).move_in()
+
+ elif command == var.config.get('command', 'volume'):
+ # The volume is a percentage
+ if parameter is not None and parameter.isdigit() and 0 <= int(parameter) <= 100:
+ self.volume = float(float(parameter) / 100)
+ self.send_msg(var.config.get('strings', 'change_volume') % (
+ int(self.volume * 100), self.mumble.users[text.actor]['name']), text)
+ var.db.set('bot', 'volume', str(self.volume))
+ else:
+ self.send_msg(var.config.get('strings', 'current_volume') % int(self.volume * 100), text)
+
+ elif command == var.config.get('command', 'current_music'):
+ if len(var.playlist) > 0:
+ source = var.playlist[0]["type"]
+ if source == "radio":
+ reply = "[radio] {title} on {url} by {user}".format(
+ title=media.radio.get_radio_title(var.playlist[0]["url"]),
+ url=var.playlist[0]["title"],
+ user=var.playlist[0]["user"]
+ )
+ elif source == "url" and 'from_playlist' in var.playlist[0]:
+ reply = "[playlist] {title} (from the playlist {playlist} by {user}".format(
+ title=var.playlist[0]["title"],
+ url=var.playlist[0]["playlist_url"],
+ playlist=var.playlist[0]["playlist_title"],
+ user=var.playlist[0]["user"]
+ )
+ elif source == "url":
+ reply = "[url] {title} ({url}) by {user}".format(
+ title=var.playlist[0]["title"],
+ url=var.playlist[0]["url"],
+ user=var.playlist[0]["user"]
+ )
+ elif source == "file":
+ reply = "[file] {title} by {user}".format(
+ title=var.playlist[0]["title"],
+ user=var.playlist[0]["user"])
+ else:
+ reply = "ERROR"
+ logging.error(var.playlist)
+ else:
+ reply = var.config.get('strings', 'not_playing')
+
+ self.send_msg(reply, text)
+
+ elif command == var.config.get('command', 'skip'):
+ if parameter is not None and parameter.isdigit() and int(parameter) > 0: # Allow to remove specific music into the queue with a number
+ if int(parameter) < len(var.playlist):
+ removed = var.playlist.pop(int(parameter))
+
+ # the Title isn't here if the music wasn't downloaded
+ self.send_msg(var.config.get('strings', 'removing_item') % (removed['title'] if 'title' in removed else removed['url']), text)
+ else:
+ self.send_msg(var.config.get('strings', 'no_possible'), text)
+ elif self.next(): # Is no number send, just skip the current music
+ self.launch_music()
+ self.async_download_next()
+ else:
+ self.send_msg(var.config.get('strings', 'queue_empty'), text)
+ self.stop()
+
+ elif command == var.config.get('command', 'list'):
+ folder_path = var.config.get('bot', 'music_folder')
+
+ files = util.get_recursive_filelist_sorted(folder_path)
+ if files:
+ self.send_msg('
'.join(files), text)
+ else:
+ self.send_msg(var.config.get('strings', 'no_file'), text)
+
+ elif command == var.config.get('command', 'queue'):
+ if len(var.playlist) <= 1:
+ msg = var.config.get('strings', 'queue_empty')
+ else:
+ msg = var.config.get('strings', 'queue_contents') + '
'
+ i = 1
+ for value in var.playlist[1:]:
+ msg += '[{}] ({}) {}
'.format(i, value['type'], value['title'] if 'title' in value else value['url'])
+ i += 1
+
+ self.send_msg(msg, text)
+
+ elif command == var.config.get('command', 'repeat'):
+ var.playlist.append(var.playlist[0])
+
+ else:
+ self.mumble.users[text.actor].send_text_message(var.config.get('strings', 'bad_command'))
+
+ @staticmethod
+ def is_admin(user):
+ list_admin = var.config.get('bot', 'admin').split(';')
+ if user in list_admin:
+ return True
+ else:
+ return False
+
+ @staticmethod
+ def next():
+ logging.debug("Next into the queue")
+ if len(var.playlist) > 1:
+ var.playlist.pop(0)
+ return True
+ elif len(var.playlist) == 1:
+ var.playlist.pop(0)
+ return False
+ else:
+ return False
+
+ def launch_music(self):
+ uri = ""
+ logging.debug("launch_music asked" + str(var.playlist[0]))
+ if var.playlist[0]["type"] == "url":
+ # Delete older music is the tmp folder is too big
+ media.system.clear_tmp_folder(var.config.get('bot', 'tmp_folder'), var.config.getint('bot', 'tmp_folder_max_size'))
+
+ # Check if the music is ready to be played
+ if var.playlist[0]["ready"] == "downloading":
+ return
+ elif var.playlist[0]["ready"] != "yes":
+ logging.info("Current music wasn't ready, Downloading...")
+ self.download_music(index=0)
+
+ # get the Path
+ uri = var.playlist[0]['path']
+ if os.path.isfile(uri):
+ audio = EasyID3(uri)
+ title = ""
+ if audio["title"]:
+ title = audio["title"][0] # take the title from the file tag
+
+ path_thumbnail = var.playlist[0]['path'][:-4] + '.jpg' # Remove .mp3 and add .jpg
+ thumbnail_html = ""
+ if os.path.isfile(path_thumbnail):
+ # Create the image message
+ im = Image.open(path_thumbnail)
+ im.thumbnail((100, 100), Image.ANTIALIAS)
+ buffer = BytesIO()
+ im.save(buffer, format="JPEG")
+ thumbnail_base64 = base64.b64encode(buffer.getvalue())
+ thumbnail_html = '
'
+
+ logging.debug("Thunbail data " + thumbnail_html)
+ if var.config.getboolean('bot', 'announce_current_music'):
+ self.send_msg(var.config.get('strings', 'now_playing') % (title, thumbnail_html))
+ else:
+ logging.error("Error with the path during launch_music")
+ pass
+
+ elif var.playlist[0]["type"] == "file":
+ uri = var.config.get('bot', 'music_folder') + var.playlist[0]["path"]
+
+ elif var.playlist[0]["type"] == "radio":
+ uri = var.playlist[0]["url"]
+ title = media.radio.get_radio_server_description(uri)
+ var.playlist[0]["title"] = title
+ if var.config.getboolean('bot', 'announce_current_music'):
+ self.send_msg(var.config.get('strings', 'now_playing') % (title, "URL : " + uri))
+
+ if var.config.getboolean('debug', 'ffmpeg'):
+ ffmpeg_debug = "debug"
+ else:
+ ffmpeg_debug = "warning"
+
+ command = ("ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-i', uri, '-ac', '1', '-f', 's16le', '-ar', '48000', '-')
+ logging.info("FFmpeg command : " + " ".join(command))
+ self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480) # The ffmpeg process is a thread
+ self.is_playing = True
+
+ def download_music(self, index):
+ if var.playlist[index]['type'] == 'url' and var.playlist[index]['ready'] == "validation":
+ if media.url.get_url_info(index=index):
+ if var.playlist[index]['duration'] > var.config.getint('bot', 'max_track_duration'):
+ # Check the length, useful in case of playlist, it wasn't checked before)
+ var.playlist.pop()
+ logging.info("the music " + var.playlist[index]["url"] + " has a duration of " + var.playlist[index]['duration'] + "s -- too long")
+ self.send_msg(var.config.get('strings', 'too_long'))
+ return
+ else:
+ var.playlist[index]['ready'] = "no"
+ else:
+ var.playlist.pop(index)
+ logging.error("Error while fetching info from the URL")
+ self.send_msg(var.config.get('strings', 'unable_download'))
+
+ if var.playlist[index]['type'] == 'url' and var.playlist[index]['ready'] == "no":
+ # download the music
+ var.playlist[index]['ready'] = "downloading"
+
+ logging.debug("Download index:" + str(index))
+ logging.debug(var.playlist[index])
+
+ url = var.playlist[index]['url']
+ url_hash = hashlib.md5(url.encode()).hexdigest()
+
+ path = var.config.get('bot', 'tmp_folder') + url_hash + ".%(ext)s"
+ mp3 = path.replace(".%(ext)s", ".mp3")
+ var.playlist[index]['path'] = mp3
+
+ # if os.path.isfile(mp3):
+ # audio = EasyID3(mp3)
+ # var.playlist[index]['title'] = audio["title"][0]
+ ydl_opts = ""
+
+ ydl_opts = {
+ 'format': 'bestaudio/best',
+ 'outtmpl': path,
+ 'noplaylist': True,
+ 'writethumbnail': True,
+ 'updatetime': False,
+ 'postprocessors': [{
+ 'key': 'FFmpegExtractAudio',
+ 'preferredcodec': 'mp3',
+ 'preferredquality': '192'},
+ {'key': 'FFmpegMetadata'}]
+ }
+ self.send_msg(var.config.get('strings', "download_in_progress") % var.playlist[index]['title'])
+
+ logging.info("Information before start downloading :" + str(var.playlist[index]))
+ with youtube_dl.YoutubeDL(ydl_opts) as ydl:
+ for i in range(2): # Always try 2 times
+ try:
+ ydl.extract_info(url)
+ if 'ready' in var.playlist[index] and var.playlist[index]['ready'] == "downloading":
+ var.playlist[index]['ready'] = "yes"
+ except youtube_dl.utils.DownloadError:
+ pass
+ else:
+ break
+ return
+
+ def async_download_next(self):
+ # Function start if the next music isn't ready
+ # Do nothing in case the next music is already downloaded
+ logging.info("Async download next asked")
+ if len(var.playlist) > 1 and var.playlist[1]['type'] == 'url' and var.playlist[1]['ready'] in ["no", "validation"]:
+ th = threading.Thread(target=self.download_music, kwargs={'index': 1})
+ else:
+ return
+ logging.info("Start downloading next in thread")
+ th.daemon = True
+ th.start()
+
+ @staticmethod
+ # Parse the html from the message to get the URL
+ def get_url_from_input(string):
+ if string.startswith('http'):
+ return string
+ p = re.compile('href="(.+?)"', re.IGNORECASE)
+ res = re.search(p, string)
+ if res:
+ return res.group(1)
+ else:
+ return False
+
+ # Main loop of the Bot
+ def loop(self):
+ raw_music = ""
+ while not self.exit and self.mumble.isAlive():
+
+ while self.mumble.sound_output.get_buffer_size() > 0.5 and not self.exit:
+ # If the buffer isn't empty, I cannot send new music part, so I wait
+ time.sleep(0.01)
+ if self.thread:
+ # I get raw from ffmpeg thread
+ raw_music = self.thread.stdout.read(480)
+ if raw_music:
+ # Adjust the volume and send it to mumble
+ self.mumble.sound_output.add_sound(audioop.mul(raw_music, 2, self.volume))
+ else:
+ time.sleep(0.1)
+ else:
+ time.sleep(0.1)
+
+ if self.thread is None or not raw_music:
+ # Not music into the buffet
+ if self.is_playing:
+ # get next music
+ self.is_playing = False
+ self.next()
+ if len(var.playlist) > 0:
+ if var.playlist[0]['type'] in ['radio', 'file'] \
+ or (var.playlist[0]['type'] == 'url' and var.playlist[0]['ready'] not in ['validation', 'downloading']):
+ # Check if the music can be start before launch the music
+ self.launch_music()
+ self.async_download_next()
+
+ while self.mumble.sound_output.get_buffer_size() > 0:
+ # Empty the buffer before exit
+ time.sleep(0.01)
+ time.sleep(0.5)
+
+ if self.exit:
+ # The db is not fixed config like url/user ban and volume
+ util.write_db()
+
+ def stop(self):
+ # Kill the ffmpeg thread and empty the playlist
+ if self.thread:
+ self.thread.kill()
+ self.thread = None
+ var.playlist = []
+ self.is_playing = False
+
+ def set_comment(self):
+ self.mumble.users.myself.comment(var.config.get('bot', 'comment'))
+
+ def send_msg(self, msg, text=None):
+ # text if the object message, contain information if direct message or channel message
+ if not text or not text.session:
+ own_channel = self.mumble.channels[self.mumble.users.myself['channel_id']]
+ own_channel.send_text_message(msg)
+ else:
+ self.mumble.users[text.actor].send_text_message(msg)
+
+
+def start_web_interface(addr, port):
+ logging.info('Starting web interface on {}:{}'.format(addr, port))
+ interface.web.run(port=port, host=addr)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description='Bot for playing music on Mumble')
+
+ # General arguments
+ parser.add_argument("--config", dest='config', type=str, default='configuration.ini', help='Load configuration from this file. Default: configuration.ini')
+ parser.add_argument("--db", dest='db', type=str, default='db.ini', help='database file. Default db.ini')
+
+ parser.add_argument("-q", "--quiet", dest="quiet", action="store_true", help="Only Error logs")
+ parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Show debug log")
+
+ # Mumble arguments
+ parser.add_argument("-s", "--server", dest="host", type=str, help="Hostname of the Mumble server")
+ parser.add_argument("-u", "--user", dest="user", type=str, help="Username for the bot")
+ parser.add_argument("-P", "--password", dest="password", type=str, help="Server password, if required")
+ parser.add_argument("-T", "--tokens", dest="tokens", type=str, help="Server tokens, if required")
+ parser.add_argument("-p", "--port", dest="port", type=int, help="Port for the Mumble server")
+ parser.add_argument("-c", "--channel", dest="channel", type=str, help="Default channel for the bot")
+ parser.add_argument("-C", "--cert", dest="certificate", type=str, default=None, help="Certificate file")
+
+ args = parser.parse_args()
+ var.dbfile = args.db
+ config = configparser.ConfigParser(interpolation=None, allow_no_value=True)
+ parsed_configs = config.read(['configuration.default.ini', args.config], encoding='latin-1')
+
+ db = configparser.ConfigParser(interpolation=None, allow_no_value=True, delimiters='²')
+ db.read(var.dbfile, encoding='latin-1')
+
+ if 'url_ban' not in db.sections():
+ db.add_section('url_ban')
+ if 'bot' not in db.sections():
+ db.add_section('bot')
+ if 'user_ban' not in db.sections():
+ db.add_section('user_ban')
+
+ if len(parsed_configs) == 0:
+ logging.error('Could not read configuration from file \"{}\"'.format(args.config), file=sys.stderr)
+ sys.exit()
+
+ var.config = config
+ var.db = db
+ botamusique = MumbleBot(args)
diff --git a/radiobrowser.py b/radiobrowser.py
new file mode 100644
index 0000000..1dd9e0c
--- /dev/null
+++ b/radiobrowser.py
@@ -0,0 +1,20 @@
+from rbRadios import RadioBrowser
+
+rb = RadioBrowser()
+
+def getstations_byname(query):
+ results = rb.stations_byname(query)
+ stations = []
+ for st in results:
+ try:
+ url = rb.playable_station(st['id'])['url']
+ station = {'stationname': st['name'], 'url': url}
+ stations.append(station)
+ except:
+ pass
+ return stations
+
+
+if __name__ == "__main__":
+ r = getstations_byname('r.sh')
+ pass