From 22562c84133e29ae4d25972180606ef026733916 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 17:34:11 +0200 Subject: [PATCH 01/66] Start --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5647b9a..3be6ef1 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,5 @@ venv.bak/ # mypy .mypy_cache/ +configuration.ini +.vscode/settings.json From 376a354aab3c1c1477c437a4ce81a90430a57e66 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 17:45:52 +0200 Subject: [PATCH 02/66] Enable ptvsd --- mumbleBot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index a82b386..a0b52e7 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- - +import ptvsd import threading import time import sys @@ -54,7 +54,7 @@ type : file duration user """ - +ptvsd.enable_attach() version = 2 From 0e886c0e58eda7158688db74e90d9591fc928d5a Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 17:48:38 +0200 Subject: [PATCH 03/66] remove ptvsd --- mumbleBot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index a0b52e7..a82b386 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import ptvsd + import threading import time import sys @@ -54,7 +54,7 @@ type : file duration user """ -ptvsd.enable_attach() + version = 2 From 2081f76505f432217b7d8c9ad5d77a107abf865f Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 17:50:31 +0200 Subject: [PATCH 04/66] Add RB API helpers --- rbConstants.py | 16 +++++ rbRadios.py | 183 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 rbConstants.py create mode 100644 rbRadios.py diff --git a/rbConstants.py b/rbConstants.py new file mode 100644 index 0000000..bd0f121 --- /dev/null +++ b/rbConstants.py @@ -0,0 +1,16 @@ +BASE_URL = "http://www.radio-browser.info/webservice/" + +endpoints = { + "countries": {1: "{fmt}/countries", 2: "{fmt}/countries/{filter}"}, + "codecs": {1: "{fmt}/codecs", 2: "{fmt}/codecs/{filter}"}, + "states": { + 1: "{fmt}/states", + 2: "{fmt}/states/{filter}", + 3: "{fmt}/states/{country}/{filter}", + }, + "languages": {1: "{fmt}/languages", 2: "{fmt}/languages/{filter}"}, + "tags": {1: "{fmt}/tags", 2: "{fmt}/tags/{filter}"}, + "stations": {1: "{fmt}/stations", 3: "{fmt}/stations/{by}/{search_term}"}, + "playable_station": {3: "{ver}/{fmt}/url/{station_id}"}, + "station_search": {1: "{fmt}/stations/search"}, +} diff --git a/rbRadios.py b/rbRadios.py new file mode 100644 index 0000000..7fc5bd4 --- /dev/null +++ b/rbRadios.py @@ -0,0 +1,183 @@ +import requests + +from xml.etree import ElementTree +from urllib.parse import urljoin + +from rbConstants import endpoints, BASE_URL + + +def request(endpoint, **kwargs): + + fmt = kwargs.get("format", "json") + + if fmt == "xml": + content_type = f"application/{fmt}" + else: + content_type = f"application/{fmt}" + + headers = {"content-type": content_type, "User-Agent": "pyradios/dev"} + + params = kwargs.get("params", {}) + + url = BASE_URL + endpoint + + resp = requests.get(url, headers=headers, params=params) + + if resp.status_code == 200: + if fmt == "xml": + return resp.text + return resp.json() + + return resp.raise_for_status() + + +class EndPointBuilder: + def __init__(self, fmt="json"): + self.fmt = fmt + self._option = None + self._endpoint = None + + @property + def endpoint(self): + return endpoints[self._endpoint][self._option] + + def produce_endpoint(self, **parts): + self._option = len(parts) + self._endpoint = parts["endpoint"] + parts.update({"fmt": self.fmt}) + return self.endpoint.format(**parts) + + +class RadioBrowser: + def __init__(self, fmt="json"): + self.fmt = fmt + self.builder = EndPointBuilder(fmt=self.fmt) + + def countries(self, filter=""): + endpoint = self.builder.produce_endpoint(endpoint="countries") + return request(endpoint) + + def codecs(self, filter=""): + endpoint = self.builder.produce_endpoint(endpoint="codecs") + return request(endpoint) + + def states(self, country="", filter=""): + endpoint = self.builder.produce_endpoint( + endpoint="states", country=country, filter=filter + ) + return request(endpoint) + + def languages(self, filter=""): + endpoint = self.builder.produce_endpoint(endpoint="languages", filter=filter) + return request(endpoint) + + def tags(self, filter=""): + endpoint = self.builder.produce_endpoint(endpoint="tags", filter=filter) + return request(endpoint) + + def stations(self, **params): + endpoint = self.builder.produce_endpoint(endpoint="stations") + kwargs = {} + if params: + kwargs.update({"params": params}) + return request(endpoint, **kwargs) + + def stations_byid(self, id): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="byid", search_term=id + ) + return request(endpoint) + + def stations_byuuid(self, uuid): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="byuuid", search_term=uuid + ) + return request(endpoint) + + def stations_byname(self, name): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="byname", search_term=name + ) + return request(endpoint) + + def stations_bynameexact(self, nameexact): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bynameexact", search_term=nameexact + ) + return request(endpoint) + + def stations_bycodec(self, codec): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bycodec", search_term=codec + ) + return request(endpoint) + + def stations_bycodecexact(self, codecexact): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bycodecexact", search_term=codecexact + ) + return request(endpoint) + + def stations_bycountry(self, country): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bycountry", search_term=country + ) + return request(endpoint) + + def stations_bycountryexact(self, countryexact): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bycountryexact", search_term=countryexact + ) + return request(endpoint) + + def stations_bystate(self, state): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bystate", search_term=state + ) + return request(endpoint) + + def stations_bystateexact(self, stateexact): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bystateexact", search_term=stateexact + ) + return request(endpoint) + + # + def stations_bylanguage(self, language): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bylanguage", search_term=language + ) + return request(endpoint) + + def stations_bylanguageexact(self, languageexact): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bylanguageexact", search_term=languageexact + ) + return request(endpoint) + + def stations_bytag(self, tag): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bytag", search_term=tag + ) + return request(endpoint) + + def stations_bytagexact(self, tagexact): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bytagexact", search_term=tagexact + ) + return request(endpoint) + + def playable_station(self, station_id): + endpoint = self.builder.produce_endpoint( + endpoint="playable_station", station_id=station_id, ver="v2" + ) + + return request(endpoint) + + def station_search(self, params, **kwargs): + # http://www.radio-browser.info/webservice#Advanced_station_search + assert isinstance(params, dict), "params is not a dict" + kwargs["params"] = params + endpoint = self.builder.produce_endpoint(endpoint="station_search") + return request(endpoint, **kwargs) + \ No newline at end of file From 0d3063c96501e4fbd0146d0ee8cae6f362e1fc05 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 17:54:46 +0200 Subject: [PATCH 05/66] Add rb_query (logging only) --- configuration.default.ini | 3 +++ mumbleBot.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/configuration.default.ini b/configuration.default.ini index b018616..2c03e5c 100644 --- a/configuration.default.ini +++ b/configuration.default.ini @@ -50,6 +50,9 @@ play_url = url play_radio = radio play_playlist = playlist +rb_query = rbquery +rb_play = rbplay + help = help stop = stop list = list diff --git a/mumbleBot.py b/mumbleBot.py index a82b386..ed26ab5 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -330,6 +330,8 @@ class MumbleBot: 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') elif command == var.config.get('command', 'help'): self.send_msg(var.config.get('strings', 'help'), text) From 9b3bcb3907f52bcc4677c0734fe3b263bd3e55e4 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 17:59:25 +0200 Subject: [PATCH 06/66] log rbquery parameter --- mumbleBot.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mumbleBot.py b/mumbleBot.py index ed26ab5..83b9689 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -332,6 +332,12 @@ class MumbleBot: 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.debug('rbquery without parameter') + else: + parameter = var.config.get('rbquery', parameter) + parameter = parameter.split()[0] + logging.debug('Found query parameter: ' + parameter) elif command == var.config.get('command', 'help'): self.send_msg(var.config.get('strings', 'help'), text) From 026043a9dfaa8b2182f121d9a4998af480432655 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 18:01:18 +0200 Subject: [PATCH 07/66] Log plain parameter --- mumbleBot.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index 83b9689..f47609f 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -335,8 +335,6 @@ class MumbleBot: if not parameter: logging.debug('rbquery without parameter') else: - parameter = var.config.get('rbquery', parameter) - parameter = parameter.split()[0] logging.debug('Found query parameter: ' + parameter) elif command == var.config.get('command', 'help'): From 92412bb29f085cc8cc4d67161d0b0bba4a992a3b Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 18:04:23 +0200 Subject: [PATCH 08/66] Change loglevel --- mumbleBot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index f47609f..acba28e 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -333,9 +333,9 @@ class MumbleBot: elif command == var.config.get('command', 'rb_query'): logging.info('Querying radio stations') if not parameter: - logging.debug('rbquery without parameter') + logging.info('rbquery without parameter') else: - logging.debug('Found query parameter: ' + parameter) + logging.info('Found query parameter: ' + parameter) elif command == var.config.get('command', 'help'): self.send_msg(var.config.get('strings', 'help'), text) From f62cb1afe45064abba25f5346e6f767a37cf8367 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 18:18:07 +0200 Subject: [PATCH 09/66] Send query result to channel --- mumbleBot.py | 221 +++++++++----- mumbleBot.py.bak | 732 +++++++++++++++++++++++++++++++++++++++++++++++ radiobrowser.py | 20 ++ 3 files changed, 901 insertions(+), 72 deletions(-) create mode 100644 mumbleBot.py.bak create mode 100644 radiobrowser.py 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 From 52015af275413fef8a3e093a827fe226c90396c6 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 18:19:06 +0200 Subject: [PATCH 10/66] Add requests --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 0315e18..878d03e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ youtube-dl python-magic Pillow mutagen +requests From fa3243b77d32b84ad0af48f8e20a675cf34a021e Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 18:22:24 +0200 Subject: [PATCH 11/66] Add rbquery string --- configuration.default.ini | 1 + mumbleBot.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/configuration.default.ini b/configuration.default.ini index 2c03e5c..62a46f6 100644 --- a/configuration.default.ini +++ b/configuration.default.ini @@ -104,6 +104,7 @@ no_possible = it's not possible to do that removing_item = Removing entry %s from queue user_ban = You are ban, not allowed to do that ! url_ban = This url isn't allowed ! +rbqueryresult = This is the result of your query help = Command available:
!file [path] diff --git a/mumbleBot.py b/mumbleBot.py index a7b403a..4e834c1 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -363,6 +363,8 @@ class MumbleBot: self.send_msg(var.config.get('strings', 'bad_url')) elif command == var.config.get('command', 'rb_query'): logging.info('Querying radio stations') + msg = var.config.get( + 'strings', 'rbqueryresult') + " :" if not parameter: logging.info('rbquery without parameter') else: From 27d10e593a14268ce00f178d738d59d74f976cb0 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 18:30:10 +0200 Subject: [PATCH 12/66] Send query result to channel --- mumbleBot.py | 2 +- radiobrowser.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index 4e834c1..accdfcb 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -371,7 +371,7 @@ class MumbleBot: logging.info('Found query parameter: ' + parameter) stations = getstations_byname(parameter) for s in stations: - msg += "
" + s + msg += "
" + s['id'] + s['stationname'] + ': ' self.send_msg(msg, text) elif command == var.config.get('command', 'help'): diff --git a/radiobrowser.py b/radiobrowser.py index 1dd9e0c..61cf468 100644 --- a/radiobrowser.py +++ b/radiobrowser.py @@ -1,4 +1,4 @@ -from rbRadios import RadioBrowser +from radios import RadioBrowser rb = RadioBrowser() @@ -8,7 +8,7 @@ def getstations_byname(query): for st in results: try: url = rb.playable_station(st['id'])['url'] - station = {'stationname': st['name'], 'url': url} + station = {'stationname': st['name'], 'url': url, 'id':st['id']} stations.append(station) except: pass @@ -17,4 +17,4 @@ def getstations_byname(query): if __name__ == "__main__": r = getstations_byname('r.sh') - pass + pass \ No newline at end of file From 0e0a4f8bc2f5c3978683955bda5b9bd9e19fc6fd Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 18:30:40 +0200 Subject: [PATCH 13/66] Format --- mumbleBot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index accdfcb..be343ef 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -371,7 +371,7 @@ class MumbleBot: logging.info('Found query parameter: ' + parameter) stations = getstations_byname(parameter) for s in stations: - msg += "
" + s['id'] + s['stationname'] + ': ' + msg += "
" + s['id'] + '\t\t' + s['stationname'] + ': ' self.send_msg(msg, text) elif command == var.config.get('command', 'help'): From 9290307e21501b052f59a99b116fc8ce53cdae22 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 18:31:18 +0200 Subject: [PATCH 14/66] Fix import --- radiobrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radiobrowser.py b/radiobrowser.py index 61cf468..14df93a 100644 --- a/radiobrowser.py +++ b/radiobrowser.py @@ -1,4 +1,4 @@ -from radios import RadioBrowser +from rbRadios import RadioBrowser rb = RadioBrowser() From 547395c268c377246690eeaf5a2d5e19a10d9a23 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 18:37:23 +0200 Subject: [PATCH 15/66] Query result as html table --- mumbleBot.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index be343ef..fc9d944 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -370,8 +370,10 @@ class MumbleBot: else: logging.info('Found query parameter: ' + parameter) stations = getstations_byname(parameter) + msg += '\n' for s in stations: - msg += "
" + s['id'] + '\t\t' + s['stationname'] + ': ' + msg += '' + msg += '
IDStation Name
' + s['id'] + '' + s['stationname'] + '
' self.send_msg(msg, text) elif command == var.config.get('command', 'help'): From 70bbfb22ccbbbeca436640f9a8c49f7fa0ca2a33 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 18:43:44 +0200 Subject: [PATCH 16/66] Expand help text with radiobrowser commands --- configuration.default.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/configuration.default.ini b/configuration.default.ini index 62a46f6..151652a 100644 --- a/configuration.default.ini +++ b/configuration.default.ini @@ -104,13 +104,15 @@ no_possible = it's not possible to do that removing_item = Removing entry %s from queue user_ban = You are ban, not allowed to do that ! url_ban = This url isn't allowed ! -rbqueryresult = This is the result of your query +rbqueryresult = This is the result of your query, send !rbplay to play a station help = Command available:
!file [path]
!url [url] - youtube or soundcloud
!playlist [url] [offset] - youtube or soundcloud playlist (the offset is the track number the bot will start to play - 1 by default)
!radio [url] - url of a stream +
!rbquery - Search http://www.radio-browser.info for a radio station +
!rbplay - Play a radio station from rbquery serach results
!list - display list of available tracks
!queue - display items in queue
!np - display the current music From ae0f11a349484a9c87125ba20cee9daa23a20097 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 18:59:15 +0200 Subject: [PATCH 17/66] Try to play radio --- mumbleBot.py | 29 ++++++++++++++++++++++++++--- radiobrowser.py | 7 +++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index fc9d944..e9e9904 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -27,7 +27,7 @@ import media.file import media.playlist import media.radio import media.system -from radiobrowser import getstations_byname +from radiobrowser import getstations_byname, geturl_byid """ FORMAT OF A MUSIC INTO THE PLAYLIST @@ -361,20 +361,43 @@ class MumbleBot: self.async_download_next() else: self.send_msg(var.config.get('strings', 'bad_url')) + # query http://www.radio-browser.info API for a radio station elif command == var.config.get('command', 'rb_query'): logging.info('Querying radio stations') msg = var.config.get( 'strings', 'rbqueryresult') + " :" if not parameter: logging.info('rbquery without parameter') + msg += 'You have to add a query text to search for a specific radio station.' + self.send_msg(msg, text) else: logging.info('Found query parameter: ' + parameter) - stations = getstations_byname(parameter) + rb_stations = getstations_byname(parameter) msg += '\n' - for s in stations: + for s in rb_stations: msg += '' msg += '
IDStation Name
' + s['id'] + '' + s['stationname'] + '
' self.send_msg(msg, text) + # Play a secific station (by id) from http://www.radio-browser.info API + elif command == var.config.get('command', 'rb_play'): + logging.info('Play a station by ID') + if not parameter: + logging.info('rbplay withou parameter') + msg += 'Please enter a station ID from rbquery. Example: !rbplay 96748' + self.send_msg(msg, text) + else: + logging.info('Retreiving url for station ID ' + parameter) + url = geturl_byid(parameter) + if url != "-1": + logging.info('Found url: ' + url) + music = {'type': 'radio', + 'url': url, + 'user': user} + var.playlist.append(music) + self.async_download_next() + else: + logging.info('No playable url found.') + pass elif command == var.config.get('command', 'help'): self.send_msg(var.config.get('strings', 'help'), text) diff --git a/radiobrowser.py b/radiobrowser.py index 14df93a..c9bf04f 100644 --- a/radiobrowser.py +++ b/radiobrowser.py @@ -14,6 +14,13 @@ def getstations_byname(query): pass return stations +def geturl_byid(id): + url = rb.playable_station(id)['url'] + if url != None: + return url + else: + return "-1" + if __name__ == "__main__": r = getstations_byname('r.sh') From ffa727c00f7daba65ac068b6474653f7bb9db252 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 19:03:28 +0200 Subject: [PATCH 18/66] Add support for radio-browser API --- mumbleBot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index e9e9904..8ec3d5a 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -382,7 +382,7 @@ class MumbleBot: elif command == var.config.get('command', 'rb_play'): logging.info('Play a station by ID') if not parameter: - logging.info('rbplay withou parameter') + logging.info('rbplay without parameter') msg += 'Please enter a station ID from rbquery. Example: !rbplay 96748' self.send_msg(msg, text) else: @@ -397,7 +397,8 @@ class MumbleBot: self.async_download_next() else: logging.info('No playable url found.') - pass + msg += "No playable url found for this station, please try another station." + self.send_msg(msg, text) elif command == var.config.get('command', 'help'): self.send_msg(var.config.get('strings', 'help'), text) From 8116f1b3880db806b2ddb44f36c63df0616f7d63 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 19:06:55 +0200 Subject: [PATCH 19/66] =?UTF-8?q?We=20don=C2=B4t=20need=20the=20url=20here?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- radiobrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radiobrowser.py b/radiobrowser.py index c9bf04f..c52010c 100644 --- a/radiobrowser.py +++ b/radiobrowser.py @@ -7,7 +7,7 @@ def getstations_byname(query): stations = [] for st in results: try: - url = rb.playable_station(st['id'])['url'] + # url = rb.playable_station(st['id'])['url'] station = {'stationname': st['name'], 'url': url, 'id':st['id']} stations.append(station) except: From 6f2e9e763859347cff1597f5058dec2171b6f9a0 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 19:07:31 +0200 Subject: [PATCH 20/66] Remove url in station --- radiobrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radiobrowser.py b/radiobrowser.py index c52010c..2318cbe 100644 --- a/radiobrowser.py +++ b/radiobrowser.py @@ -8,7 +8,7 @@ def getstations_byname(query): for st in results: try: # url = rb.playable_station(st['id'])['url'] - station = {'stationname': st['name'], 'url': url, 'id':st['id']} + station = {'stationname': st['name'], 'id':st['id']} stations.append(station) except: pass From 94131a1679c82bda679e91e397dfc441a2dd87c7 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 19:25:28 +0200 Subject: [PATCH 21/66] New radio-browser commands added --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 837b454..fce68d2 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ you have the section : - webinterface : basic configuration about the interface (disabled by default) - command : you can customize the command you want for each action (if you put `help = helpme` , the bot will response to `!helpme` ) - radio : here you can have a list of default radio ( I can play a jazz radio with the command `!radio jazz`) +- rbquery : search http://www.radio-browser.info API for listed radio stations - eg: `!rbquery nora` +- rbplay : Play a specific radio station by ID (from rbquery) - eg: `!rbplay 96748` - strings : you can customize all string the bot can say. - debug : option to active ffmpeg or pymumble debug. (Can be very verbose) From 15b457fb51a327d9b36caad458f6ee6aa240b375 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 19:44:48 +0200 Subject: [PATCH 22/66] Moved rb files to lib, changed query msgs --- mumbleBot.py | 6 +- mumbleBot.py.bak | 732 ----------------------------------------------- radiobrowser.py | 27 -- rbConstants.py | 16 -- rbRadios.py | 183 ------------ 5 files changed, 3 insertions(+), 961 deletions(-) delete mode 100644 mumbleBot.py.bak delete mode 100644 radiobrowser.py delete mode 100644 rbConstants.py delete mode 100644 rbRadios.py diff --git a/mumbleBot.py b/mumbleBot.py index 8ec3d5a..f228be1 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -27,7 +27,7 @@ import media.file import media.playlist import media.radio import media.system -from radiobrowser import getstations_byname, geturl_byid +from lib.radiobrowser import getstations_byname, geturl_byid """ FORMAT OF A MUSIC INTO THE PLAYLIST @@ -364,15 +364,15 @@ class MumbleBot: # query http://www.radio-browser.info API for a radio station elif command == var.config.get('command', 'rb_query'): logging.info('Querying radio stations') - msg = var.config.get( - 'strings', 'rbqueryresult') + " :" if not parameter: logging.info('rbquery without parameter') msg += 'You have to add a query text to search for a specific radio station.' self.send_msg(msg, text) else: logging.info('Found query parameter: ' + parameter) + self.send_msg('Searching for stations - this may take some seconds...', text) rb_stations = getstations_byname(parameter) + msg = var.config.get('strings', 'rbqueryresult') + " :" msg += '\n' for s in rb_stations: msg += '' diff --git a/mumbleBot.py.bak b/mumbleBot.py.bak deleted file mode 100644 index 2326f53..0000000 --- a/mumbleBot.py.bak +++ /dev/null @@ -1,732 +0,0 @@ -#!/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 deleted file mode 100644 index 2318cbe..0000000 --- a/radiobrowser.py +++ /dev/null @@ -1,27 +0,0 @@ -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'], 'id':st['id']} - stations.append(station) - except: - pass - return stations - -def geturl_byid(id): - url = rb.playable_station(id)['url'] - if url != None: - return url - else: - return "-1" - - -if __name__ == "__main__": - r = getstations_byname('r.sh') - pass \ No newline at end of file diff --git a/rbConstants.py b/rbConstants.py deleted file mode 100644 index bd0f121..0000000 --- a/rbConstants.py +++ /dev/null @@ -1,16 +0,0 @@ -BASE_URL = "http://www.radio-browser.info/webservice/" - -endpoints = { - "countries": {1: "{fmt}/countries", 2: "{fmt}/countries/{filter}"}, - "codecs": {1: "{fmt}/codecs", 2: "{fmt}/codecs/{filter}"}, - "states": { - 1: "{fmt}/states", - 2: "{fmt}/states/{filter}", - 3: "{fmt}/states/{country}/{filter}", - }, - "languages": {1: "{fmt}/languages", 2: "{fmt}/languages/{filter}"}, - "tags": {1: "{fmt}/tags", 2: "{fmt}/tags/{filter}"}, - "stations": {1: "{fmt}/stations", 3: "{fmt}/stations/{by}/{search_term}"}, - "playable_station": {3: "{ver}/{fmt}/url/{station_id}"}, - "station_search": {1: "{fmt}/stations/search"}, -} diff --git a/rbRadios.py b/rbRadios.py deleted file mode 100644 index 7fc5bd4..0000000 --- a/rbRadios.py +++ /dev/null @@ -1,183 +0,0 @@ -import requests - -from xml.etree import ElementTree -from urllib.parse import urljoin - -from rbConstants import endpoints, BASE_URL - - -def request(endpoint, **kwargs): - - fmt = kwargs.get("format", "json") - - if fmt == "xml": - content_type = f"application/{fmt}" - else: - content_type = f"application/{fmt}" - - headers = {"content-type": content_type, "User-Agent": "pyradios/dev"} - - params = kwargs.get("params", {}) - - url = BASE_URL + endpoint - - resp = requests.get(url, headers=headers, params=params) - - if resp.status_code == 200: - if fmt == "xml": - return resp.text - return resp.json() - - return resp.raise_for_status() - - -class EndPointBuilder: - def __init__(self, fmt="json"): - self.fmt = fmt - self._option = None - self._endpoint = None - - @property - def endpoint(self): - return endpoints[self._endpoint][self._option] - - def produce_endpoint(self, **parts): - self._option = len(parts) - self._endpoint = parts["endpoint"] - parts.update({"fmt": self.fmt}) - return self.endpoint.format(**parts) - - -class RadioBrowser: - def __init__(self, fmt="json"): - self.fmt = fmt - self.builder = EndPointBuilder(fmt=self.fmt) - - def countries(self, filter=""): - endpoint = self.builder.produce_endpoint(endpoint="countries") - return request(endpoint) - - def codecs(self, filter=""): - endpoint = self.builder.produce_endpoint(endpoint="codecs") - return request(endpoint) - - def states(self, country="", filter=""): - endpoint = self.builder.produce_endpoint( - endpoint="states", country=country, filter=filter - ) - return request(endpoint) - - def languages(self, filter=""): - endpoint = self.builder.produce_endpoint(endpoint="languages", filter=filter) - return request(endpoint) - - def tags(self, filter=""): - endpoint = self.builder.produce_endpoint(endpoint="tags", filter=filter) - return request(endpoint) - - def stations(self, **params): - endpoint = self.builder.produce_endpoint(endpoint="stations") - kwargs = {} - if params: - kwargs.update({"params": params}) - return request(endpoint, **kwargs) - - def stations_byid(self, id): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="byid", search_term=id - ) - return request(endpoint) - - def stations_byuuid(self, uuid): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="byuuid", search_term=uuid - ) - return request(endpoint) - - def stations_byname(self, name): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="byname", search_term=name - ) - return request(endpoint) - - def stations_bynameexact(self, nameexact): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="bynameexact", search_term=nameexact - ) - return request(endpoint) - - def stations_bycodec(self, codec): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="bycodec", search_term=codec - ) - return request(endpoint) - - def stations_bycodecexact(self, codecexact): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="bycodecexact", search_term=codecexact - ) - return request(endpoint) - - def stations_bycountry(self, country): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="bycountry", search_term=country - ) - return request(endpoint) - - def stations_bycountryexact(self, countryexact): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="bycountryexact", search_term=countryexact - ) - return request(endpoint) - - def stations_bystate(self, state): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="bystate", search_term=state - ) - return request(endpoint) - - def stations_bystateexact(self, stateexact): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="bystateexact", search_term=stateexact - ) - return request(endpoint) - - # - def stations_bylanguage(self, language): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="bylanguage", search_term=language - ) - return request(endpoint) - - def stations_bylanguageexact(self, languageexact): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="bylanguageexact", search_term=languageexact - ) - return request(endpoint) - - def stations_bytag(self, tag): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="bytag", search_term=tag - ) - return request(endpoint) - - def stations_bytagexact(self, tagexact): - endpoint = self.builder.produce_endpoint( - endpoint="stations", by="bytagexact", search_term=tagexact - ) - return request(endpoint) - - def playable_station(self, station_id): - endpoint = self.builder.produce_endpoint( - endpoint="playable_station", station_id=station_id, ver="v2" - ) - - return request(endpoint) - - def station_search(self, params, **kwargs): - # http://www.radio-browser.info/webservice#Advanced_station_search - assert isinstance(params, dict), "params is not a dict" - kwargs["params"] = params - endpoint = self.builder.produce_endpoint(endpoint="station_search") - return request(endpoint, **kwargs) - \ No newline at end of file From 569bab9b8fce063c57fa76365186151d563529fd Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 19:45:24 +0200 Subject: [PATCH 23/66] Change import --- mumbleBot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index f228be1..35b4b12 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -27,7 +27,7 @@ import media.file import media.playlist import media.radio import media.system -from lib.radiobrowser import getstations_byname, geturl_byid +from radiobrowser import getstations_byname, geturl_byid """ FORMAT OF A MUSIC INTO THE PLAYLIST From 72f384528ef53e9a28a97a8046e13964ab061728 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 19:46:23 +0200 Subject: [PATCH 24/66] Import from lib. --- mumbleBot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index 35b4b12..f228be1 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -27,7 +27,7 @@ import media.file import media.playlist import media.radio import media.system -from radiobrowser import getstations_byname, geturl_byid +from lib.radiobrowser import getstations_byname, geturl_byid """ FORMAT OF A MUSIC INTO THE PLAYLIST From 65b480969a22b63a6bfee1be1994b58125d0a17c Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 19:49:46 +0200 Subject: [PATCH 25/66] Chaned import again --- mumbleBot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index f228be1..15e2f8a 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -27,7 +27,7 @@ import media.file import media.playlist import media.radio import media.system -from lib.radiobrowser import getstations_byname, geturl_byid +from lib import radiobrowser """ FORMAT OF A MUSIC INTO THE PLAYLIST @@ -371,7 +371,7 @@ class MumbleBot: else: logging.info('Found query parameter: ' + parameter) self.send_msg('Searching for stations - this may take some seconds...', text) - rb_stations = getstations_byname(parameter) + rb_stations = radiobrowser.getstations_byname(parameter) msg = var.config.get('strings', 'rbqueryresult') + " :" msg += '\n
IDStation Name
' + s['id'] + '' + s['stationname'] + '
' for s in rb_stations: @@ -387,7 +387,7 @@ class MumbleBot: self.send_msg(msg, text) else: logging.info('Retreiving url for station ID ' + parameter) - url = geturl_byid(parameter) + url = radiobrowser.geturl_byid(parameter) if url != "-1": logging.info('Found url: ' + url) music = {'type': 'radio', From 0db5edae833812a9bbcdbe2c7617588e2bdde669 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 19:53:16 +0200 Subject: [PATCH 26/66] Moved rb files to librb --- librb/__init__.py | 0 librb/radiobrowser.py | 27 +++++++ librb/rbConstants.py | 16 ++++ librb/rbRadios.py | 183 ++++++++++++++++++++++++++++++++++++++++++ mumbleBot.py | 2 +- 5 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 librb/__init__.py create mode 100644 librb/radiobrowser.py create mode 100644 librb/rbConstants.py create mode 100644 librb/rbRadios.py diff --git a/librb/__init__.py b/librb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/librb/radiobrowser.py b/librb/radiobrowser.py new file mode 100644 index 0000000..2318cbe --- /dev/null +++ b/librb/radiobrowser.py @@ -0,0 +1,27 @@ +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'], 'id':st['id']} + stations.append(station) + except: + pass + return stations + +def geturl_byid(id): + url = rb.playable_station(id)['url'] + if url != None: + return url + else: + return "-1" + + +if __name__ == "__main__": + r = getstations_byname('r.sh') + pass \ No newline at end of file diff --git a/librb/rbConstants.py b/librb/rbConstants.py new file mode 100644 index 0000000..bd0f121 --- /dev/null +++ b/librb/rbConstants.py @@ -0,0 +1,16 @@ +BASE_URL = "http://www.radio-browser.info/webservice/" + +endpoints = { + "countries": {1: "{fmt}/countries", 2: "{fmt}/countries/{filter}"}, + "codecs": {1: "{fmt}/codecs", 2: "{fmt}/codecs/{filter}"}, + "states": { + 1: "{fmt}/states", + 2: "{fmt}/states/{filter}", + 3: "{fmt}/states/{country}/{filter}", + }, + "languages": {1: "{fmt}/languages", 2: "{fmt}/languages/{filter}"}, + "tags": {1: "{fmt}/tags", 2: "{fmt}/tags/{filter}"}, + "stations": {1: "{fmt}/stations", 3: "{fmt}/stations/{by}/{search_term}"}, + "playable_station": {3: "{ver}/{fmt}/url/{station_id}"}, + "station_search": {1: "{fmt}/stations/search"}, +} diff --git a/librb/rbRadios.py b/librb/rbRadios.py new file mode 100644 index 0000000..7fc5bd4 --- /dev/null +++ b/librb/rbRadios.py @@ -0,0 +1,183 @@ +import requests + +from xml.etree import ElementTree +from urllib.parse import urljoin + +from rbConstants import endpoints, BASE_URL + + +def request(endpoint, **kwargs): + + fmt = kwargs.get("format", "json") + + if fmt == "xml": + content_type = f"application/{fmt}" + else: + content_type = f"application/{fmt}" + + headers = {"content-type": content_type, "User-Agent": "pyradios/dev"} + + params = kwargs.get("params", {}) + + url = BASE_URL + endpoint + + resp = requests.get(url, headers=headers, params=params) + + if resp.status_code == 200: + if fmt == "xml": + return resp.text + return resp.json() + + return resp.raise_for_status() + + +class EndPointBuilder: + def __init__(self, fmt="json"): + self.fmt = fmt + self._option = None + self._endpoint = None + + @property + def endpoint(self): + return endpoints[self._endpoint][self._option] + + def produce_endpoint(self, **parts): + self._option = len(parts) + self._endpoint = parts["endpoint"] + parts.update({"fmt": self.fmt}) + return self.endpoint.format(**parts) + + +class RadioBrowser: + def __init__(self, fmt="json"): + self.fmt = fmt + self.builder = EndPointBuilder(fmt=self.fmt) + + def countries(self, filter=""): + endpoint = self.builder.produce_endpoint(endpoint="countries") + return request(endpoint) + + def codecs(self, filter=""): + endpoint = self.builder.produce_endpoint(endpoint="codecs") + return request(endpoint) + + def states(self, country="", filter=""): + endpoint = self.builder.produce_endpoint( + endpoint="states", country=country, filter=filter + ) + return request(endpoint) + + def languages(self, filter=""): + endpoint = self.builder.produce_endpoint(endpoint="languages", filter=filter) + return request(endpoint) + + def tags(self, filter=""): + endpoint = self.builder.produce_endpoint(endpoint="tags", filter=filter) + return request(endpoint) + + def stations(self, **params): + endpoint = self.builder.produce_endpoint(endpoint="stations") + kwargs = {} + if params: + kwargs.update({"params": params}) + return request(endpoint, **kwargs) + + def stations_byid(self, id): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="byid", search_term=id + ) + return request(endpoint) + + def stations_byuuid(self, uuid): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="byuuid", search_term=uuid + ) + return request(endpoint) + + def stations_byname(self, name): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="byname", search_term=name + ) + return request(endpoint) + + def stations_bynameexact(self, nameexact): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bynameexact", search_term=nameexact + ) + return request(endpoint) + + def stations_bycodec(self, codec): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bycodec", search_term=codec + ) + return request(endpoint) + + def stations_bycodecexact(self, codecexact): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bycodecexact", search_term=codecexact + ) + return request(endpoint) + + def stations_bycountry(self, country): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bycountry", search_term=country + ) + return request(endpoint) + + def stations_bycountryexact(self, countryexact): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bycountryexact", search_term=countryexact + ) + return request(endpoint) + + def stations_bystate(self, state): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bystate", search_term=state + ) + return request(endpoint) + + def stations_bystateexact(self, stateexact): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bystateexact", search_term=stateexact + ) + return request(endpoint) + + # + def stations_bylanguage(self, language): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bylanguage", search_term=language + ) + return request(endpoint) + + def stations_bylanguageexact(self, languageexact): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bylanguageexact", search_term=languageexact + ) + return request(endpoint) + + def stations_bytag(self, tag): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bytag", search_term=tag + ) + return request(endpoint) + + def stations_bytagexact(self, tagexact): + endpoint = self.builder.produce_endpoint( + endpoint="stations", by="bytagexact", search_term=tagexact + ) + return request(endpoint) + + def playable_station(self, station_id): + endpoint = self.builder.produce_endpoint( + endpoint="playable_station", station_id=station_id, ver="v2" + ) + + return request(endpoint) + + def station_search(self, params, **kwargs): + # http://www.radio-browser.info/webservice#Advanced_station_search + assert isinstance(params, dict), "params is not a dict" + kwargs["params"] = params + endpoint = self.builder.produce_endpoint(endpoint="station_search") + return request(endpoint, **kwargs) + \ No newline at end of file diff --git a/mumbleBot.py b/mumbleBot.py index 15e2f8a..3333b22 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -27,7 +27,7 @@ import media.file import media.playlist import media.radio import media.system -from lib import radiobrowser +from librb import radiobrowser """ FORMAT OF A MUSIC INTO THE PLAYLIST From 7eca10f87dea17660bf01a1ecd94fa3b012ee668 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 19:54:14 +0200 Subject: [PATCH 27/66] Changed import --- librb/radiobrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/librb/radiobrowser.py b/librb/radiobrowser.py index 2318cbe..cc96889 100644 --- a/librb/radiobrowser.py +++ b/librb/radiobrowser.py @@ -1,4 +1,4 @@ -from rbRadios import RadioBrowser +from librb.rbRadios import RadioBrowser rb = RadioBrowser() From 5a2f0fe30cf29e06a35616508152e213c53fd3d4 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 19:54:51 +0200 Subject: [PATCH 28/66] Chenged import --- librb/rbRadios.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/librb/rbRadios.py b/librb/rbRadios.py index 7fc5bd4..a018215 100644 --- a/librb/rbRadios.py +++ b/librb/rbRadios.py @@ -3,7 +3,7 @@ import requests from xml.etree import ElementTree from urllib.parse import urljoin -from rbConstants import endpoints, BASE_URL +from librb.rbConstants import endpoints, BASE_URL def request(endpoint, **kwargs): From 9cd001497482cbac8deac71f3f0e95cf25be4fe7 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 19:59:38 +0200 Subject: [PATCH 29/66] Changed some log levels to debug --- mumbleBot.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index 3333b22..3241959 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -365,11 +365,11 @@ class MumbleBot: elif command == var.config.get('command', 'rb_query'): logging.info('Querying radio stations') if not parameter: - logging.info('rbquery without parameter') + logging.debug('rbquery without parameter') msg += 'You have to add a query text to search for a specific radio station.' self.send_msg(msg, text) else: - logging.info('Found query parameter: ' + parameter) + logging.debug('Found query parameter: ' + parameter) self.send_msg('Searching for stations - this may take some seconds...', text) rb_stations = radiobrowser.getstations_byname(parameter) msg = var.config.get('strings', 'rbqueryresult') + " :" @@ -380,13 +380,13 @@ class MumbleBot: self.send_msg(msg, text) # Play a secific station (by id) from http://www.radio-browser.info API elif command == var.config.get('command', 'rb_play'): - logging.info('Play a station by ID') + logging.debug('Play a station by ID') if not parameter: - logging.info('rbplay without parameter') + logging.debug('rbplay without parameter') msg += 'Please enter a station ID from rbquery. Example: !rbplay 96748' self.send_msg(msg, text) else: - logging.info('Retreiving url for station ID ' + parameter) + logging.debug('Retreiving url for station ID ' + parameter) url = radiobrowser.geturl_byid(parameter) if url != "-1": logging.info('Found url: ' + url) From 23b10a05f7914c2f3f2aa9949a49ab50efad2314 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 20:10:48 +0200 Subject: [PATCH 30/66] Radio-Browser --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fce68d2..87a629d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ Botamusique is a mumble bot which goal is to allow users to listen music togethe Predicted functionalities will be ones you could expect from any classic music player. Bot the can play : -- Radio url +- Radio station from url +- Radio station from http://www.radio-browser.info API (query from > 24.000 stations) - Youtube/Soundcloud URL (everything supported by youtube-dl) - Local folder (disabled, I need to work on the web interface) From e9a46a14f881e89097370bb6f9ef7537fecfce01 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 21:39:08 +0200 Subject: [PATCH 31/66] Reply if query is empty --- mumbleBot.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index 3241959..97d356c 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -374,10 +374,13 @@ class MumbleBot: rb_stations = radiobrowser.getstations_byname(parameter) msg = var.config.get('strings', 'rbqueryresult') + " :" msg += '\n
IDStation Name
' - for s in rb_stations: - msg += '' - msg += '
IDStation Name
' + s['id'] + '' + s['stationname'] + '
' - self.send_msg(msg, text) + if not rb_stations: + self.send_msg('No results for ' + parameter, text) + else: + for s in rb_stations: + msg += '' + s['id'] + '' + s['stationname'] + '' + msg += '' + self.send_msg(msg, text) # Play a secific station (by id) from http://www.radio-browser.info API elif command == var.config.get('command', 'rb_play'): logging.debug('Play a station by ID') From 37a1805153bf360e8a7cbaf63ad58bd70913e266 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 21:41:16 +0200 Subject: [PATCH 32/66] Text --- mumbleBot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index 97d356c..3967196 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -375,7 +375,7 @@ class MumbleBot: msg = var.config.get('strings', 'rbqueryresult') + " :" msg += '\n' if not rb_stations: - self.send_msg('No results for ' + parameter, text) + self.send_msg('Radio-Browser found no matches for ' + parameter, text) else: for s in rb_stations: msg += '' From 1baadcb829650a7cd1070b1d0bf28f64e5dc48a1 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 21:42:49 +0200 Subject: [PATCH 33/66] Logging --- mumbleBot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mumbleBot.py b/mumbleBot.py index 3967196..72d0330 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -375,6 +375,7 @@ class MumbleBot: msg = var.config.get('strings', 'rbqueryresult') + " :" msg += '\n
IDStation Name
' + s['id'] + '' + s['stationname'] + '
' if not rb_stations: + logging.debug('No matches found for rbquery ' + parameter) self.send_msg('Radio-Browser found no matches for ' + parameter, text) else: for s in rb_stations: From 6bff8214ca5d88606c1d9043193ca45aaf97b1f2 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 21:47:41 +0200 Subject: [PATCH 34/66] Add Now playing msg --- librb/radiobrowser.py | 4 +++- mumbleBot.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/librb/radiobrowser.py b/librb/radiobrowser.py index cc96889..f970a01 100644 --- a/librb/radiobrowser.py +++ b/librb/radiobrowser.py @@ -21,7 +21,9 @@ def geturl_byid(id): else: return "-1" - +def getstationname_byid(id): + return rb.stations_byid(id) + if __name__ == "__main__": r = getstations_byname('r.sh') pass \ No newline at end of file diff --git a/mumbleBot.py b/mumbleBot.py index 72d0330..defe915 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -398,6 +398,7 @@ class MumbleBot: 'url': url, 'user': user} var.playlist.append(music) + self.send_msg('Now playing ' + radiobrowser.getstationname_byid(parameter), text) self.async_download_next() else: logging.info('No playable url found.') From 20ddfa23a414ad21a4eed512d95c14864188246e Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 21:49:23 +0200 Subject: [PATCH 35/66] Now playing --- mumbleBot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index defe915..9a9182b 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -398,8 +398,8 @@ class MumbleBot: 'url': url, 'user': user} var.playlist.append(music) - self.send_msg('Now playing ' + radiobrowser.getstationname_byid(parameter), text) self.async_download_next() + self.send_msg('Now playing radio station: ' + radiobrowser.getstationname_byid(parameter), text) else: logging.info('No playable url found.') msg += "No playable url found for this station, please try another station." From c87dbb1bd6afcb2831c3f0fc3243e946202194c6 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 21:51:28 +0200 Subject: [PATCH 36/66] Now playing --- mumbleBot.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index 9a9182b..0787c3f 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -391,6 +391,9 @@ class MumbleBot: self.send_msg(msg, text) else: logging.debug('Retreiving url for station ID ' + parameter) + msg = 'Start playing radio station: ' + radiobrowser.getstationname_byid(parameter) + logging.info(' Play radio station ' + msg) + self.send_msg(msg, text) url = radiobrowser.geturl_byid(parameter) if url != "-1": logging.info('Found url: ' + url) @@ -399,7 +402,6 @@ class MumbleBot: 'user': user} var.playlist.append(music) self.async_download_next() - self.send_msg('Now playing radio station: ' + radiobrowser.getstationname_byid(parameter), text) else: logging.info('No playable url found.') msg += "No playable url found for this station, please try another station." From a759191b9bfea5f58232989a21263a0b9108e535 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 22:02:00 +0200 Subject: [PATCH 37/66] Radio play information message --- librb/radiobrowser.py | 1 + mumbleBot.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/librb/radiobrowser.py b/librb/radiobrowser.py index f970a01..6fe6aaf 100644 --- a/librb/radiobrowser.py +++ b/librb/radiobrowser.py @@ -26,4 +26,5 @@ def getstationname_byid(id): if __name__ == "__main__": r = getstations_byname('r.sh') + name = getstationname_byid(96748) pass \ No newline at end of file diff --git a/mumbleBot.py b/mumbleBot.py index 0787c3f..686b210 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -391,7 +391,12 @@ class MumbleBot: self.send_msg(msg, text) else: logging.debug('Retreiving url for station ID ' + parameter) - msg = 'Start playing radio station: ' + radiobrowser.getstationname_byid(parameter) + rstation = radiobrowser.getstationname_byid(parameter) + stationname = rstation['name'] + country = rstation['country'] + codec = rstation['codec'] + bitrate = rstation['bitrate'] + msg = f'Start playing radio station: {stationname} from {country} ({codec}/{bitrate} kbit)' logging.info(' Play radio station ' + msg) self.send_msg(msg, text) url = radiobrowser.geturl_byid(parameter) From 4e1e59951e1791b56ef81fe2052c0b099295f73d Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 22:04:51 +0200 Subject: [PATCH 38/66] Index 0 --- librb/radiobrowser.py | 2 +- mumbleBot.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/librb/radiobrowser.py b/librb/radiobrowser.py index 6fe6aaf..f934e6f 100644 --- a/librb/radiobrowser.py +++ b/librb/radiobrowser.py @@ -26,5 +26,5 @@ def getstationname_byid(id): if __name__ == "__main__": r = getstations_byname('r.sh') - name = getstationname_byid(96748) + stationinfo = getstationname_byid(96748) pass \ No newline at end of file diff --git a/mumbleBot.py b/mumbleBot.py index 686b210..5a304ae 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -392,10 +392,10 @@ class MumbleBot: else: logging.debug('Retreiving url for station ID ' + parameter) rstation = radiobrowser.getstationname_byid(parameter) - stationname = rstation['name'] - country = rstation['country'] - codec = rstation['codec'] - bitrate = rstation['bitrate'] + stationname = rstation[0]['name'] + country = rstation[0]['country'] + codec = rstation[0]['codec'] + bitrate = rstation[0]['bitrate'] msg = f'Start playing radio station: {stationname} from {country} ({codec}/{bitrate} kbit)' logging.info(' Play radio station ' + msg) self.send_msg(msg, text) From de1f3d9df53d72c4810d65475b226d5c335b0832 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 22:16:11 +0200 Subject: [PATCH 39/66] add homepage and image --- mumbleBot.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index 5a304ae..038d097 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -395,9 +395,14 @@ class MumbleBot: stationname = rstation[0]['name'] country = rstation[0]['country'] codec = rstation[0]['codec'] - bitrate = rstation[0]['bitrate'] - msg = f'Start playing radio station: {stationname} from {country} ({codec}/{bitrate} kbit)' - logging.info(' Play radio station ' + msg) + bitrate = rstation[0]['bitrate'] + genre = rstation[0]['tags'] + imageurl = rstation[0]['favicon'] + homepage = rstation[0]['homepage'] + msg = f'Start playing radio station: {stationname} ({codec}/{bitrate} kbit, country: {country}, genre: {genre})' + if homepage and imageurl: + msg += f'' + logging.debug(f'Play radio station {stationname}') self.send_msg(msg, text) url = radiobrowser.geturl_byid(parameter) if url != "-1": From 2ed64758875d8b05435261fed24cb48791bc01dc Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 22:21:44 +0200 Subject: [PATCH 40/66] some logging --- mumbleBot.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index 038d097..b32e5d6 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -399,7 +399,9 @@ class MumbleBot: genre = rstation[0]['tags'] imageurl = rstation[0]['favicon'] homepage = rstation[0]['homepage'] - msg = f'Start playing radio station: {stationname} ({codec}/{bitrate} kbit, country: {country}, genre: {genre})' + logging.info(f'hp: {homepage}') + logging.info(f'img: {imageurl}') + msg = f'Start playing radio station: {stationname} ({codec}/{bitrate} kbit, Country: {country}, Genre: {genre})' if homepage and imageurl: msg += f'' logging.debug(f'Play radio station {stationname}') From 81569cdaaadd5659f7787299f0de4b676eb2b38d Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 22:37:47 +0200 Subject: [PATCH 41/66] Message as html table --- mumbleBot.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index b32e5d6..a6dc766 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -397,13 +397,10 @@ class MumbleBot: codec = rstation[0]['codec'] bitrate = rstation[0]['bitrate'] genre = rstation[0]['tags'] - imageurl = rstation[0]['favicon'] homepage = rstation[0]['homepage'] - logging.info(f'hp: {homepage}') - logging.info(f'img: {imageurl}') - msg = f'Start playing radio station: {stationname} ({codec}/{bitrate} kbit, Country: {country}, Genre: {genre})' - if homepage and imageurl: - msg += f'' + msg = f'Start playing radio station:' + msg += '
IDStation Name
' + \ + f'
IDStation NameGenreCodec/BitrateCountryHomepage
{parameter}{stationname}{genre}{codec}, {bitrate}{country}{homepage}
' logging.debug(f'Play radio station {stationname}') self.send_msg(msg, text) url = radiobrowser.geturl_byid(parameter) From 422200e2bd5158d2524c9110138ce08b8ffa3965 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 22:41:41 +0200 Subject: [PATCH 42/66] No header line in table --- mumbleBot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index a6dc766..85e171f 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -399,8 +399,8 @@ class MumbleBot: genre = rstation[0]['tags'] homepage = rstation[0]['homepage'] msg = f'Start playing radio station:' - msg += '' + \ - f'
IDStation NameGenreCodec/BitrateCountryHomepage
{parameter}{stationname}{genre}{codec}, {bitrate}{country}{homepage}
' + msg += '' + \ + f'
IDStation NameGenreCodec/BitrateCountryHomepage
{parameter}{stationname}{genre}{codec}/{bitrate}{country}{homepage}
' logging.debug(f'Play radio station {stationname}') self.send_msg(msg, text) url = radiobrowser.geturl_byid(parameter) From fa1ec8a52ea9f6d39aa2fa2475a25e21a3f74b7a Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sat, 27 Jul 2019 22:43:24 +0200 Subject: [PATCH 43/66] Add table header line --- mumbleBot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index 85e171f..8af13e8 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -399,7 +399,7 @@ class MumbleBot: genre = rstation[0]['tags'] homepage = rstation[0]['homepage'] msg = f'Start playing radio station:' - msg += '' + \ + msg += '
IDStation NameGenreCodec/BitrateCountryHomepage
' + \ f'
IDStation NameGenreCodec/BitrateCountryHomepage
{parameter}{stationname}{genre}{codec}/{bitrate}{country}{homepage}
' logging.debug(f'Play radio station {stationname}') self.send_msg(msg, text) From 8ae6e6b399ef1b8966e94209ebbdd563c9e2ed90 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 10:10:10 +0200 Subject: [PATCH 44/66] More information in query results --- .gitignore | 1 + librb/radiobrowser.py | 6 +++--- mumbleBot.py | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 3be6ef1..916a549 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,4 @@ venv.bak/ configuration.ini .vscode/settings.json +2019-07-27 22_09_08-radiobrowser.py - botamusique - Visual Studio Code.png diff --git a/librb/radiobrowser.py b/librb/radiobrowser.py index f934e6f..c908823 100644 --- a/librb/radiobrowser.py +++ b/librb/radiobrowser.py @@ -1,6 +1,6 @@ -from librb.rbRadios import RadioBrowser +from rbRadios import RadioBrowser -rb = RadioBrowser() +rb = librb.RadioBrowser() def getstations_byname(query): results = rb.stations_byname(query) @@ -8,7 +8,7 @@ def getstations_byname(query): for st in results: try: # url = rb.playable_station(st['id'])['url'] - station = {'stationname': st['name'], 'id':st['id']} + station = {'stationname': st['name'], 'id':st['id'], 'codec':st['codec'], 'bitrate':st['bitrate'], 'country':st['country'], 'homepage':st['homepage'], 'genre':st['tags']} stations.append(station) except: pass diff --git a/mumbleBot.py b/mumbleBot.py index 8af13e8..433bb7b 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -373,13 +373,13 @@ class MumbleBot: self.send_msg('Searching for stations - this may take some seconds...', text) rb_stations = radiobrowser.getstations_byname(parameter) msg = var.config.get('strings', 'rbqueryresult') + " :" - msg += '\n' + msg += '\n
IDStation Name
' if not rb_stations: logging.debug('No matches found for rbquery ' + parameter) self.send_msg('Radio-Browser found no matches for ' + parameter, text) else: for s in rb_stations: - msg += '' + msg += f'
!rbplay IDStation NameGenreCodec/BitrateCountryHomepage
' + s['id'] + '' + s['stationname'] + '
{s['id']}{s['stationname']}{s['genre']}{s['codec']}/{s['bitrate']}{s['country']}{s['homepage']}/
' msg += '' self.send_msg(msg, text) # Play a secific station (by id) from http://www.radio-browser.info API From 6e624ba67fa89eae5283049968944f43445c9a79 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 10:13:33 +0200 Subject: [PATCH 45/66] Table --- mumbleBot.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index 433bb7b..6bd5875 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -379,7 +379,14 @@ class MumbleBot: self.send_msg('Radio-Browser found no matches for ' + parameter, text) else: for s in rb_stations: - msg += f'{s['id']}{s['stationname']}{s['genre']}{s['codec']}/{s['bitrate']}{s['country']}{s['homepage']}/' + stationid = s['id'] + stationname = s['name'] + country = s['country'] + codec = s['codec'] + bitrate = s['bitrate'] + genre = s['tags'] + homepage = s['homepage'] + msg += f'{stationid}{stationname}{genre}{codec}/{bitrate}{country}{homepage}/' msg += '' self.send_msg(msg, text) # Play a secific station (by id) from http://www.radio-browser.info API From 0dd0e50ee3b04bf2ac0058ff44244b5311dbb9a3 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 10:14:34 +0200 Subject: [PATCH 46/66] Import path --- librb/radiobrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/librb/radiobrowser.py b/librb/radiobrowser.py index c908823..bd59eac 100644 --- a/librb/radiobrowser.py +++ b/librb/radiobrowser.py @@ -1,4 +1,4 @@ -from rbRadios import RadioBrowser +from librb.rbRadios import RadioBrowser rb = librb.RadioBrowser() From 542a7547564c12a2beba6adc25a27889b863d4e1 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 10:15:44 +0200 Subject: [PATCH 47/66] Push2remote --- librb/radiobrowser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/librb/radiobrowser.py b/librb/radiobrowser.py index bd59eac..d2b55d6 100644 --- a/librb/radiobrowser.py +++ b/librb/radiobrowser.py @@ -1,13 +1,12 @@ from librb.rbRadios import RadioBrowser -rb = librb.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'], 'id':st['id'], 'codec':st['codec'], 'bitrate':st['bitrate'], 'country':st['country'], 'homepage':st['homepage'], 'genre':st['tags']} stations.append(station) except: From c3a6f848a54e475e19f9c487e26ac636a0d0749e Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 10:19:29 +0200 Subject: [PATCH 48/66] Fix KeyError: 'name' --- mumbleBot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index 6bd5875..59d2041 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -380,7 +380,7 @@ class MumbleBot: else: for s in rb_stations: stationid = s['id'] - stationname = s['name'] + stationname = s['stationname'] country = s['country'] codec = s['codec'] bitrate = s['bitrate'] From c612e4889883fd2e2837f9b7118de589160a4a10 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 10:20:46 +0200 Subject: [PATCH 49/66] Fix another KeyError: 'tags' --- mumbleBot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index 59d2041..30cc647 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -384,7 +384,7 @@ class MumbleBot: country = s['country'] codec = s['codec'] bitrate = s['bitrate'] - genre = s['tags'] + genre = s['genre'] homepage = s['homepage'] msg += f'{stationid}{stationname}{genre}{codec}/{bitrate}{country}{homepage}/' msg += '' From 9201937728f66836bc75a14ae4d846da24aa25ad Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 10:23:25 +0200 Subject: [PATCH 50/66] Fix table format --- mumbleBot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index 30cc647..4e2dd6c 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -386,7 +386,7 @@ class MumbleBot: bitrate = s['bitrate'] genre = s['genre'] homepage = s['homepage'] - msg += f'{stationid}{stationname}{genre}{codec}/{bitrate}{country}{homepage}/' + msg += f'{stationid}{stationname}{genre}{codec}/{bitrate}{country}{homepage}/' msg += '' self.send_msg(msg, text) # Play a secific station (by id) from http://www.radio-browser.info API From 9df4d98d1209e8c85ef5e0fab19bfadd2211f8f4 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 10:27:23 +0200 Subject: [PATCH 51/66] Handle result message > 5000 chars --- mumbleBot.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index 4e2dd6c..9d19548 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -388,7 +388,10 @@ class MumbleBot: homepage = s['homepage'] msg += f'{stationid}{stationname}{genre}{codec}/{bitrate}{country}{homepage}/' msg += '' - self.send_msg(msg, text) + if len(msg) < 5000: + self.send_msg(msg, text) + else: + self.send_msg('Query result too long to post (> 5000 characters), please try another query.', text) # Play a secific station (by id) from http://www.radio-browser.info API elif command == var.config.get('command', 'rb_play'): logging.debug('Play a station by ID') From a68e9b683d7e4e5ea594e5b14160158adfa68ec5 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 10:32:51 +0200 Subject: [PATCH 52/66] Remove homepage from result to keep it shorter --- mumbleBot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index 9d19548..df9b07a 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -373,7 +373,7 @@ class MumbleBot: self.send_msg('Searching for stations - this may take some seconds...', text) rb_stations = radiobrowser.getstations_byname(parameter) msg = var.config.get('strings', 'rbqueryresult') + " :" - msg += '\n' + msg += '\n
!rbplay IDStation NameGenreCodec/BitrateCountryHomepage
' if not rb_stations: logging.debug('No matches found for rbquery ' + parameter) self.send_msg('Radio-Browser found no matches for ' + parameter, text) @@ -385,8 +385,7 @@ class MumbleBot: codec = s['codec'] bitrate = s['bitrate'] genre = s['genre'] - homepage = s['homepage'] - msg += f'' + msg += f'' msg += '
!rbplay IDStation NameGenreCodec/BitrateCountry
{stationid}{stationname}{genre}{codec}/{bitrate}{country}{homepage}/
{stationid}{stationname}{genre}{codec}/{bitrate}{country}
' if len(msg) < 5000: self.send_msg(msg, text) From d78c48070943afaca635e3f76ea2cf2c50331cac Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 11:43:19 +0200 Subject: [PATCH 53/66] Shorten query results if too long --- mumbleBot.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index df9b07a..94fb8e5 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -387,10 +387,28 @@ class MumbleBot: genre = s['genre'] msg += f'{stationid}{stationname}{genre}{codec}/{bitrate}{country}' msg += '' + # Shorten table if message too long if len(msg) < 5000: - self.send_msg(msg, text) + msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L1)' + msg += '\n' else: - self.send_msg('Query result too long to post (> 5000 characters), please try another query.', text) + for s in rb_stations: + stationid = s['id'] + stationname = s['stationname'] + msg += f'' + if len(msg) < 5000: + self.send_msg(msg, text) + else: + msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L2)' + msg += '!rbplay ID - Station Name' + for s in rb_stations: + stationid = s['id'] + stationname = s['stationname'] + msg += f'{stationid} - {stationname}' + if len(msg) < 5000: + self.send_msg(msg, text) + else: + self.send_msg('Query result too long to post (> 5000 characters), please try another query.', text) # Play a secific station (by id) from http://www.radio-browser.info API elif command == var.config.get('command', 'rb_play'): logging.debug('Play a station by ID') From 0da6f2d263b67d92d00dd97a2e4e230469b9dd27 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 11:49:50 +0200 Subject: [PATCH 54/66] Add temporary logging --- mumbleBot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index 94fb8e5..3d5d9d6 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -389,9 +389,9 @@ class MumbleBot: msg += '
!rbplay IDStation Name
{stationid}{stationname}
' # Shorten table if message too long if len(msg) < 5000: + logging.info('Result too long stage I') msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L1)' msg += '\n' - else: for s in rb_stations: stationid = s['id'] stationname = s['stationname'] @@ -399,6 +399,7 @@ class MumbleBot: if len(msg) < 5000: self.send_msg(msg, text) else: + logging.info('Result too long stage II') msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L2)' msg += '!rbplay ID - Station Name' for s in rb_stations: From 03c3219996a6051a2fe0cda3da9cd238f7fe720c Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 11:51:09 +0200 Subject: [PATCH 55/66] Change log level --- mumbleBot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index 3d5d9d6..74de357 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -389,7 +389,7 @@ class MumbleBot: msg += '
!rbplay IDStation Name
' # Shorten table if message too long if len(msg) < 5000: - logging.info('Result too long stage I') + logging.debug('Result too long stage I') msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L1)' msg += '\n' for s in rb_stations: @@ -399,7 +399,7 @@ class MumbleBot: if len(msg) < 5000: self.send_msg(msg, text) else: - logging.info('Result too long stage II') + logging.debug('Result too long stage II') msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L2)' msg += '!rbplay ID - Station Name' for s in rb_stations: From 68b789fc58f3f30de6c13c863878dd74e6eac50c Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 12:00:16 +0200 Subject: [PATCH 56/66] Shorten station name to 12 chars --- mumbleBot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mumbleBot.py b/mumbleBot.py index 74de357..bf9e00f 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -404,7 +404,7 @@ class MumbleBot: msg += '!rbplay ID - Station Name' for s in rb_stations: stationid = s['id'] - stationname = s['stationname'] + stationname = s['stationname'][:12] msg += f'{stationid} - {stationname}' if len(msg) < 5000: self.send_msg(msg, text) From b355a604be35889d3dbb44364a38dfdd609c97a5 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 15:14:00 +0200 Subject: [PATCH 57/66] Changed string formatting to %s --- mumbleBot.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index bf9e00f..763f048 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -385,7 +385,8 @@ class MumbleBot: codec = s['codec'] bitrate = s['bitrate'] genre = s['genre'] - msg += f'' + # msg += f'' + msg += '' % (stationid, stationname, genre, codec, bitrate, country) msg += '
!rbplay IDStation Name
{stationid}{stationname}{genre}{codec}/{bitrate}{country}
{stationid}{stationname}{genre}{codec}/{bitrate}{country}
%s%s%s%s/%s%s
' # Shorten table if message too long if len(msg) < 5000: @@ -395,7 +396,8 @@ class MumbleBot: for s in rb_stations: stationid = s['id'] stationname = s['stationname'] - msg += f'{stationid}{stationname}' + # msg += f'{stationid}{stationname}' + msg += '%s%s' % (stationid, stationname) if len(msg) < 5000: self.send_msg(msg, text) else: @@ -405,7 +407,8 @@ class MumbleBot: for s in rb_stations: stationid = s['id'] stationname = s['stationname'][:12] - msg += f'{stationid} - {stationname}' + # msg += f'{stationid} - {stationname}' + msg += '%s - %s' % (stationid, stationname) if len(msg) < 5000: self.send_msg(msg, text) else: @@ -426,10 +429,13 @@ class MumbleBot: bitrate = rstation[0]['bitrate'] genre = rstation[0]['tags'] homepage = rstation[0]['homepage'] - msg = f'Start playing radio station:' + msg = 'Start playing radio station:' + # msg += '' + \ + # f'
IDStation NameGenreCodec/BitrateCountryHomepage
{parameter}{stationname}{genre}{codec}/{bitrate}{country}{homepage}
' msg += '' + \ - f'
IDStation NameGenreCodec/BitrateCountryHomepage
{parameter}{stationname}{genre}{codec}/{bitrate}{country}{homepage}
' - logging.debug(f'Play radio station {stationname}') + '%s%s%s%s/%s%s%s' \ + % (parameter, stationname, genre, codec, bitrate, country, homepage) + logging.debug('Play radio station %s' % stationname) self.send_msg(msg, text) url = radiobrowser.geturl_byid(parameter) if url != "-1": From 42f9ef0f207186aeb949793268f3530b7432f543 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 15:16:05 +0200 Subject: [PATCH 58/66] Change string formatting --- librb/rbRadios.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/librb/rbRadios.py b/librb/rbRadios.py index a018215..25af8d5 100644 --- a/librb/rbRadios.py +++ b/librb/rbRadios.py @@ -11,9 +11,9 @@ def request(endpoint, **kwargs): fmt = kwargs.get("format", "json") if fmt == "xml": - content_type = f"application/{fmt}" + content_type = "application/%s" % fmt else: - content_type = f"application/{fmt}" + content_type = "application/%s" % fmt headers = {"content-type": content_type, "User-Agent": "pyradios/dev"} From 715e27cc4369e966d2da0a7bbb5ed7f977a699b9 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 15:23:19 +0200 Subject: [PATCH 59/66] Fixed wrong identation --- mumbleBot.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index 763f048..e669f71 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -389,7 +389,7 @@ class MumbleBot: msg += '%s%s%s%s/%s%s' % (stationid, stationname, genre, codec, bitrate, country) msg += '' # Shorten table if message too long - if len(msg) < 5000: + if len(msg) <= 5000: logging.debug('Result too long stage I') msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L1)' msg += '\n' @@ -398,21 +398,20 @@ class MumbleBot: stationname = s['stationname'] # msg += f'' msg += '' % (stationid, stationname) - if len(msg) < 5000: + self.send_msg(msg, text) + else: + logging.debug('Result too long stage II') + msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L2)' + msg += '!rbplay ID - Station Name' + for s in rb_stations: + stationid = s['id'] + stationname = s['stationname'][:12] + # msg += f'{stationid} - {stationname}' + msg += '%s - %s' % (stationid, stationname) + if len(msg) <= 5000: self.send_msg(msg, text) else: - logging.debug('Result too long stage II') - msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L2)' - msg += '!rbplay ID - Station Name' - for s in rb_stations: - stationid = s['id'] - stationname = s['stationname'][:12] - # msg += f'{stationid} - {stationname}' - msg += '%s - %s' % (stationid, stationname) - if len(msg) < 5000: - self.send_msg(msg, text) - else: - self.send_msg('Query result too long to post (> 5000 characters), please try another query.', text) + self.send_msg('Query result too long to post (> 5000 characters), please try another query.', text) # Play a secific station (by id) from http://www.radio-browser.info API elif command == var.config.get('command', 'rb_play'): logging.debug('Play a station by ID') From 83c458261cf07752e80a7446ac2a7a0e62d4079b Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 15:29:56 +0200 Subject: [PATCH 60/66] Fix message shortening --- mumbleBot.py | 54 +++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index e669f71..f72ae6d 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -387,31 +387,37 @@ class MumbleBot: genre = s['genre'] # msg += f'' msg += '' % (stationid, stationname, genre, codec, bitrate, country) - msg += '
!rbplay IDStation Name
{stationid}{stationname}
%s%s
{stationid}{stationname}{genre}{codec}/{bitrate}{country}
%s%s%s%s/%s%s
' - # Shorten table if message too long - if len(msg) <= 5000: - logging.debug('Result too long stage I') - msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L1)' - msg += '\n' - for s in rb_stations: - stationid = s['id'] - stationname = s['stationname'] - # msg += f'' - msg += '' % (stationid, stationname) + msg += '
!rbplay IDStation Name
{stationid}{stationname}
%s%s
' + # Full message as html table + if len(msg) <= 5000: self.send_msg(msg, text) - else: - logging.debug('Result too long stage II') - msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L2)' - msg += '!rbplay ID - Station Name' - for s in rb_stations: - stationid = s['id'] - stationname = s['stationname'][:12] - # msg += f'{stationid} - {stationname}' - msg += '%s - %s' % (stationid, stationname) - if len(msg) <= 5000: - self.send_msg(msg, text) - else: - self.send_msg('Query result too long to post (> 5000 characters), please try another query.', text) + # Shorten message if message too long (stage I) + else: + logging.debug('Result too long stage I') + msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L1)' + msg += '\n' + for s in rb_stations: + stationid = s['id'] + stationname = s['stationname'] + # msg += f'' + msg += '' % (stationid, stationname) + if len(msg) <= 5000: + self.send_msg(msg, text) + # Shorten message if message too long (stage II) + else: + logging.debug('Result too long stage II') + msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L2)' + msg += '!rbplay ID - Station Name' + for s in rb_stations: + stationid = s['id'] + stationname = s['stationname'][:12] + # msg += f'{stationid} - {stationname}' + msg += '%s - %s' % (stationid, stationname) + if len(msg) <= 5000: + self.send_msg(msg, text) + # Message still too long + else: + self.send_msg('Query result too long to post (> 5000 characters), please try another query.', text) # Play a secific station (by id) from http://www.radio-browser.info API elif command == var.config.get('command', 'rb_play'): logging.debug('Play a station by ID') From 58a654199ae613f2b499f951586e3ca03d16dcaa Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 15:32:58 +0200 Subject: [PATCH 61/66] Fixed identation (again) --- mumbleBot.py | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index f72ae6d..efa8268 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -389,35 +389,35 @@ class MumbleBot: msg += '' % (stationid, stationname, genre, codec, bitrate, country) msg += '
!rbplay IDStation Name
{stationid}{stationname}
%s%s
%s%s%s%s/%s%s
' # Full message as html table + if len(msg) <= 5000: + self.send_msg(msg, text) + # Shorten message if message too long (stage I) + else: + logging.debug('Result too long stage I') + msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L1)' + msg += '\n' + for s in rb_stations: + stationid = s['id'] + stationname = s['stationname'] + # msg += f'' + msg += '' % (stationid, stationname) if len(msg) <= 5000: self.send_msg(msg, text) - # Shorten message if message too long (stage I) + # Shorten message if message too long (stage II) else: - logging.debug('Result too long stage I') - msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L1)' - msg += '\n
!rbplay IDStation Name
{stationid}{stationname}
%s%s
' + logging.debug('Result too long stage II') + msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L2)' + msg += '!rbplay ID - Station Name' for s in rb_stations: stationid = s['id'] - stationname = s['stationname'] - # msg += f'' - msg += '' % (stationid, stationname) - if len(msg) <= 5000: - self.send_msg(msg, text) - # Shorten message if message too long (stage II) - else: - logging.debug('Result too long stage II') - msg = var.config.get('strings', 'rbqueryresult') + " :" + ' (shortened L2)' - msg += '!rbplay ID - Station Name' - for s in rb_stations: - stationid = s['id'] - stationname = s['stationname'][:12] - # msg += f'{stationid} - {stationname}' - msg += '%s - %s' % (stationid, stationname) - if len(msg) <= 5000: - self.send_msg(msg, text) - # Message still too long - else: - self.send_msg('Query result too long to post (> 5000 characters), please try another query.', text) + stationname = s['stationname'][:12] + # msg += f'{stationid} - {stationname}' + msg += '%s - %s' % (stationid, stationname) + if len(msg) <= 5000: + self.send_msg(msg, text) + # Message still too long + else: + self.send_msg('Query result too long to post (> 5000 characters), please try another query.', text) # Play a secific station (by id) from http://www.radio-browser.info API elif command == var.config.get('command', 'rb_play'): logging.debug('Play a station by ID') From fc22e5487dc7bfdba7707d420c5f465d41a03133 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 15:37:46 +0200 Subject: [PATCH 62/66] Fix wrong placed
!rbplay IDStation Name
{stationid}{stationname}
%s%s
--- mumbleBot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mumbleBot.py b/mumbleBot.py index efa8268..3ad75af 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -387,8 +387,8 @@ class MumbleBot: genre = s['genre'] # msg += f'{stationid}{stationname}{genre}{codec}/{bitrate}{country}' msg += '%s%s%s%s/%s%s' % (stationid, stationname, genre, codec, bitrate, country) - msg += '' - # Full message as html table + msg += '' + # Full message as html table if len(msg) <= 5000: self.send_msg(msg, text) # Shorten message if message too long (stage I) @@ -401,6 +401,7 @@ class MumbleBot: stationname = s['stationname'] # msg += f'{stationid}{stationname}' msg += '%s%s' % (stationid, stationname) + msg += '' if len(msg) <= 5000: self.send_msg(msg, text) # Shorten message if message too long (stage II) From 4dec52562729a820fa69724de109085fee311ac9 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 15:43:12 +0200 Subject: [PATCH 63/66] Minor text changes --- configuration.default.ini | 2 +- mumbleBot.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configuration.default.ini b/configuration.default.ini index 151652a..844d78a 100644 --- a/configuration.default.ini +++ b/configuration.default.ini @@ -104,7 +104,7 @@ no_possible = it's not possible to do that removing_item = Removing entry %s from queue user_ban = You are ban, not allowed to do that ! url_ban = This url isn't allowed ! -rbqueryresult = This is the result of your query, send !rbplay to play a station +rbqueryresult = This is the result of your query, send !rbplay ID to play a station help = Command available:
!file [path] diff --git a/mumbleBot.py b/mumbleBot.py index 3ad75af..45de372 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -366,11 +366,11 @@ class MumbleBot: logging.info('Querying radio stations') if not parameter: logging.debug('rbquery without parameter') - msg += 'You have to add a query text to search for a specific radio station.' + msg += 'You have to add a query text to search for a matching radio stations.' self.send_msg(msg, text) else: logging.debug('Found query parameter: ' + parameter) - self.send_msg('Searching for stations - this may take some seconds...', text) + # self.send_msg('Searching for stations - this may take some seconds...', text) rb_stations = radiobrowser.getstations_byname(parameter) msg = var.config.get('strings', 'rbqueryresult') + " :" msg += '\n' From daeda480bbff81bcd89d4f4b72b9ad6e95f98d42 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 16:26:06 +0200 Subject: [PATCH 64/66] Minor text changes --- configuration.default.ini | 2 +- mumbleBot.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configuration.default.ini b/configuration.default.ini index 844d78a..04f3d52 100644 --- a/configuration.default.ini +++ b/configuration.default.ini @@ -104,7 +104,7 @@ no_possible = it's not possible to do that removing_item = Removing entry %s from queue user_ban = You are ban, not allowed to do that ! url_ban = This url isn't allowed ! -rbqueryresult = This is the result of your query, send !rbplay ID to play a station +rbqueryresult = This is the result of your query, send !rbplay 'ID' to play a station help = Command available:
!file [path] diff --git a/mumbleBot.py b/mumbleBot.py index 45de372..c6eb560 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -435,13 +435,13 @@ class MumbleBot: bitrate = rstation[0]['bitrate'] genre = rstation[0]['tags'] homepage = rstation[0]['homepage'] - msg = 'Start playing radio station:' + msg = 'Radio station added to playlist:' # msg += '
!rbplay IDStation NameGenreCodec/BitrateCountry
' + \ # f'
IDStation NameGenreCodec/BitrateCountryHomepage
{parameter}{stationname}{genre}{codec}/{bitrate}{country}{homepage}
' msg += '' + \ '
IDStation NameGenreCodec/BitrateCountryHomepage
%s%s%s%s/%s%s%s
' \ % (parameter, stationname, genre, codec, bitrate, country, homepage) - logging.debug('Play radio station %s' % stationname) + logging.debug('Added station to playlist %s' % stationname) self.send_msg(msg, text) url = radiobrowser.geturl_byid(parameter) if url != "-1": From 2de69f9a877a6ec900b5784fd2ede7a45b97bfc9 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 16:28:50 +0200 Subject: [PATCH 65/66] Minor text changes --- configuration.default.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration.default.ini b/configuration.default.ini index 04f3d52..552eb24 100644 --- a/configuration.default.ini +++ b/configuration.default.ini @@ -112,7 +112,7 @@ help = Command available:
!playlist [url] [offset] - youtube or soundcloud playlist (the offset is the track number the bot will start to play - 1 by default)
!radio [url] - url of a stream
!rbquery - Search http://www.radio-browser.info for a radio station -
!rbplay - Play a radio station from rbquery serach results +
!rbplay - Play a radio station from !rbquery search results (eg. !rbplay 96746)
!list - display list of available tracks
!queue - display items in queue
!np - display the current music From aa9d0647d9aa3bf4db4313494e3e67225fd1e098 Mon Sep 17 00:00:00 2001 From: elpatron68 Date: Sun, 28 Jul 2019 16:32:21 +0200 Subject: [PATCH 66/66] Minor text changes --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 87a629d..48de322 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Predicted functionalities will be ones you could expect from any classic music p Bot the can play : - Radio station from url -- Radio station from http://www.radio-browser.info API (query from > 24.000 stations) +- Radio station from http://www.radio-browser.info API (query from > 24k stations) - Youtube/Soundcloud URL (everything supported by youtube-dl) - Local folder (disabled, I need to work on the web interface) @@ -84,10 +84,10 @@ you have the section : - rbquery : search http://www.radio-browser.info API for listed radio stations - eg: `!rbquery nora` - rbplay : Play a specific radio station by ID (from rbquery) - eg: `!rbplay 96748` - strings : you can customize all string the bot can say. -- debug : option to active ffmpeg or pymumble debug. (Can be very verbose) +- debug : option to activate ffmpeg or pymumble debug. (Can be very verbose) ### Contributors -If you want to participate, You're welcome to fork and pull requests Fix et new features. +If you want to participate, You're welcome to fork and pull requests (fixes and new features). The following people joined the collaborators for a faster development, big thanks: - @slipenbois