]> git.0d.be Git - botaradio.git/blob - command.py
also accept files according to their extension
[botaradio.git] / command.py
1 # coding=utf-8
2 import logging
3 import pymumble.pymumble_py3 as pymumble
4 import re
5
6 import constants
7 import media.system
8 import util
9 import variables as var
10 from librb import radiobrowser
11 from database import SettingsDatabase, MusicDatabase
12 from media.item import item_id_generators, dict_to_item, dicts_to_items
13 from media.cache import get_cached_wrapper_from_scrap, get_cached_wrapper_by_id, get_cached_wrappers_by_tags
14 from media.url_from_playlist import get_playlist_info
15
16 log = logging.getLogger("bot")
17
18
19 def register_all_commands(bot):
20     bot.register_command(constants.commands('joinme'), cmd_joinme, no_partial_match=False, access_outside_channel=True)
21     bot.register_command(constants.commands('user_ban'), cmd_user_ban, no_partial_match=True)
22     bot.register_command(constants.commands('user_unban'), cmd_user_unban, no_partial_match=True)
23     bot.register_command(constants.commands('url_ban_list'), cmd_url_ban_list, no_partial_match=True)
24     bot.register_command(constants.commands('url_ban'), cmd_url_ban, no_partial_match=True)
25     bot.register_command(constants.commands('url_unban'), cmd_url_unban, no_partial_match=True)
26     bot.register_command(constants.commands('play'), cmd_play)
27     bot.register_command(constants.commands('pause'), cmd_pause)
28     bot.register_command(constants.commands('play_file'), cmd_play_file)
29     bot.register_command(constants.commands('play_file_match'), cmd_play_file_match)
30     bot.register_command(constants.commands('play_url'), cmd_play_url)
31     bot.register_command(constants.commands('play_playlist'), cmd_play_playlist)
32     bot.register_command(constants.commands('play_radio'), cmd_play_radio)
33     bot.register_command(constants.commands('play_tag'), cmd_play_tags)
34     bot.register_command(constants.commands('rb_query'), cmd_rb_query)
35     bot.register_command(constants.commands('rb_play'), cmd_rb_play)
36     bot.register_command(constants.commands('yt_search'), cmd_yt_search)
37     bot.register_command(constants.commands('yt_play'), cmd_yt_play)
38     bot.register_command(constants.commands('help'), cmd_help, no_partial_match=False, access_outside_channel=True)
39     bot.register_command(constants.commands('stop'), cmd_stop)
40     bot.register_command(constants.commands('clear'), cmd_clear)
41     bot.register_command(constants.commands('kill'), cmd_kill)
42     bot.register_command(constants.commands('update'), cmd_update, no_partial_match=True)
43     bot.register_command(constants.commands('stop_and_getout'), cmd_stop_and_getout)
44     bot.register_command(constants.commands('volume'), cmd_volume)
45     bot.register_command(constants.commands('ducking'), cmd_ducking)
46     bot.register_command(constants.commands('ducking_threshold'), cmd_ducking_threshold)
47     bot.register_command(constants.commands('ducking_volume'), cmd_ducking_volume)
48     bot.register_command(constants.commands('current_music'), cmd_current_music)
49     bot.register_command(constants.commands('skip'), cmd_skip)
50     bot.register_command(constants.commands('last'), cmd_last)
51     bot.register_command(constants.commands('remove'), cmd_remove)
52     bot.register_command(constants.commands('list_file'), cmd_list_file)
53     bot.register_command(constants.commands('queue'), cmd_queue)
54     bot.register_command(constants.commands('random'), cmd_random)
55     bot.register_command(constants.commands('repeat'), cmd_repeat)
56     bot.register_command(constants.commands('mode'), cmd_mode)
57     bot.register_command(constants.commands('add_tag'), cmd_add_tag)
58     bot.register_command(constants.commands('remove_tag'), cmd_remove_tag)
59     bot.register_command(constants.commands('find_tagged'), cmd_find_tagged)
60     bot.register_command(constants.commands('search'), cmd_search_library)
61     bot.register_command(constants.commands('add_from_shortlist'), cmd_shortlist)
62     bot.register_command(constants.commands('delete_from_library'), cmd_delete_from_library)
63     bot.register_command(constants.commands('drop_database'), cmd_drop_database, no_partial_match=True)
64     bot.register_command(constants.commands('rescan'), cmd_refresh_cache, no_partial_match=True)
65
66     # Just for debug use
67     bot.register_command('rtrms', cmd_real_time_rms, True)
68     bot.register_command('loop', cmd_loop_state, True)
69     bot.register_command('item', cmd_item, True)
70
71
72 def send_multi_lines(bot, lines, text, linebreak="<br />"):
73     global log
74
75     msg = ""
76     br = ""
77     for newline in lines:
78         msg += br
79         br = linebreak
80         if bot.mumble.get_max_message_length()\
81                     and (len(msg) + len(newline)) > (bot.mumble.get_max_message_length() - 4):  # 4 == len("<br>")
82             bot.send_msg(msg, text)
83             msg = ""
84         msg += newline
85
86     bot.send_msg(msg, text)
87
88
89 # ---------------- Variables -----------------
90
91 song_shortlist = []
92
93
94 # ---------------- Commands ------------------
95
96 def cmd_joinme(bot, user, text, command, parameter):
97     global log
98
99     bot.mumble.users.myself.move_in(
100         bot.mumble.users[text.actor]['channel_id'], token=parameter)
101
102
103 def cmd_user_ban(bot, user, text, command, parameter):
104     global log
105
106     if bot.is_admin(user):
107         if parameter:
108             bot.mumble.users[text.actor].send_text_message(util.user_ban(parameter))
109         else:
110             bot.mumble.users[text.actor].send_text_message(util.get_user_ban())
111     else:
112         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
113     return
114
115
116 def cmd_user_unban(bot, user, text, command, parameter):
117     global log
118
119     if bot.is_admin(user):
120         if parameter:
121             bot.mumble.users[text.actor].send_text_message(util.user_unban(parameter))
122     else:
123         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
124     return
125
126
127 def cmd_url_ban(bot, user, text, command, parameter):
128     global log
129
130     if bot.is_admin(user):
131         if parameter:
132             bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(parameter)))
133
134             id = item_id_generators['url'](url=parameter)
135             var.cache.free_and_delete(id)
136             var.playlist.remove_by_id(id)
137         else:
138             if var.playlist.current_item() and var.playlist.current_item().type == 'url':
139                 item = var.playlist.current_item().item()
140                 bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(item.url)))
141                 var.cache.free_and_delete(item.id)
142                 var.playlist.remove_by_id(item.id)
143             else:
144                 bot.send_msg(constants.strings('bad_parameter', command=command))
145     else:
146         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
147     return
148
149
150 def cmd_url_ban_list(bot, user, text, command, parameter):
151     if bot.is_admin(user):
152         bot.mumble.users[text.actor].send_text_message(util.get_url_ban())
153     else:
154         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
155     return
156
157
158 def cmd_url_unban(bot, user, text, command, parameter):
159     global log
160
161     if bot.is_admin(user):
162         if parameter:
163             bot.mumble.users[text.actor].send_text_message(util.url_unban(util.get_url_from_input(parameter)))
164     else:
165         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
166     return
167
168
169 def cmd_play(bot, user, text, command, parameter):
170     global log
171
172     if len(var.playlist) > 0:
173         if parameter:
174             if parameter.isdigit() and 1 <= int(parameter) <= len(var.playlist):
175                 # First "-1" transfer 12345 to 01234, second "-1"
176                 # point to the previous item. the loop will next to
177                 # the one you want
178                 var.playlist.point_to(int(parameter) - 1 - 1)
179
180                 if not bot.is_pause:
181                     bot.interrupt()
182                 else:
183                     bot.is_pause = False
184             else:
185                 bot.send_msg(constants.strings('invalid_index', index=parameter), text)
186
187         elif bot.is_pause:
188             bot.resume()
189         else:
190             bot.send_msg(var.playlist.current_item().format_current_playing(), text)
191     else:
192         bot.is_pause = False
193         bot.send_msg(constants.strings('queue_empty'), text)
194
195
196 def cmd_pause(bot, user, text, command, parameter):
197     global log
198
199     bot.pause()
200     bot.send_msg(constants.strings('paused'))
201
202
203 def cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=False):
204     global log, song_shortlist
205
206     # if parameter is {index}
207     if parameter.isdigit():
208         files = var.cache.files
209         if int(parameter) < len(files):
210             music_wrapper = get_cached_wrapper_by_id(bot, var.cache.file_id_lookup[files[int(parameter)]], user)
211             var.playlist.append(music_wrapper)
212             log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
213             bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()))
214             return
215
216     # if parameter is {path}
217     else:
218         # sanitize "../" and so on
219         # path = os.path.abspath(os.path.join(var.music_folder, parameter))
220         # if not path.startswith(os.path.abspath(var.music_folder)):
221         #     bot.send_msg(constants.strings('no_file'), text)
222         #     return
223
224         if parameter in var.cache.files:
225             music_wrapper = get_cached_wrapper_from_scrap(bot, type='file', path=parameter, user=user)
226             var.playlist.append(music_wrapper)
227             log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
228             bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()))
229             return
230
231         # if parameter is {folder}
232         files = var.cache.dir.get_files(parameter)
233         if files:
234             msgs = [constants.strings('multiple_file_added')]
235             count = 0
236
237             for file in files:
238                 count += 1
239                 music_wrapper = get_cached_wrapper_by_id(bot, var.cache.file_id_lookup[file], user)
240                 var.playlist.append(music_wrapper)
241                 log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
242                 msgs.append("{} ({})".format(music_wrapper.item().title, music_wrapper.item().path))
243
244             if count != 0:
245                 send_multi_lines(bot, msgs, None)
246                 return
247
248         else:
249             # try to do a partial match
250             files = var.cache.files
251             matches = [file for file in files if parameter.lower() in file.lower()]
252             if len(matches) == 1:
253                 file = matches[0]
254                 music_wrapper = get_cached_wrapper_by_id(bot, var.cache.file_id_lookup[file], user)
255                 var.playlist.append(music_wrapper)
256                 log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
257                 bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()))
258                 return
259             elif len(matches) > 1:
260                 msgs = [constants.strings('multiple_matches')]
261                 song_shortlist = []
262                 for index, match in enumerate(matches):
263                     id = var.cache.file_id_lookup[match]
264                     music_dict = var.music_db.query_music_by_id(id)
265                     item = dict_to_item(bot, music_dict)
266
267                     song_shortlist.append(music_dict)
268
269                     msgs.append("<b>{:d}</b> - <b>{:s}</b> ({:s})".format(
270                         index + 1, item.title, match))
271                 send_multi_lines(bot, msgs, text)
272                 return
273
274     if do_not_refresh_cache:
275         bot.send_msg(constants.strings("no_file"), text)
276     else:
277         var.cache.build_dir_cache(bot)
278         cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=True)
279
280
281 def cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=False):
282     global log
283
284     if parameter:
285         files = var.cache.files
286         msgs = [constants.strings('multiple_file_added') + "<ul>"]
287         count = 0
288         try:
289             music_wrappers = []
290             for file in files:
291                 match = re.search(parameter, file)
292                 if match and match[0]:
293                     count += 1
294                     music_wrapper = get_cached_wrapper_by_id(bot, var.cache.file_id_lookup[file], user)
295                     music_wrappers.append(music_wrapper)
296                     log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
297                     msgs.append("<li><b>{}</b> ({})</li>".format(music_wrapper.item().title,
298                                                                  file[:match.span()[0]]
299                                                                  + "<b style='color:pink'>"
300                                                                  + file[match.span()[0]: match.span()[1]]
301                                                                  + "</b>"
302                                                                  + file[match.span()[1]:]
303                                                                  ))
304
305             if count != 0:
306                 msgs.append("</ul>")
307                 var.playlist.extend(music_wrappers)
308                 send_multi_lines(bot, msgs, None, "")
309             else:
310                 if do_not_refresh_cache:
311                     bot.send_msg(constants.strings("no_file"), text)
312                 else:
313                     var.cache.build_dir_cache(bot)
314                     cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=True)
315
316         except re.error as e:
317             msg = constants.strings('wrong_pattern', error=str(e))
318             bot.send_msg(msg, text)
319     else:
320         bot.send_msg(constants.strings('bad_parameter', command=command))
321
322
323 def cmd_play_url(bot, user, text, command, parameter):
324     global log
325
326     url = util.get_url_from_input(parameter)
327     if url:
328         music_wrapper = get_cached_wrapper_from_scrap(bot, type='url', url=url, user=user)
329         var.playlist.append(music_wrapper)
330
331         log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
332         bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()))
333         if len(var.playlist) == 2:
334             # If I am the second item on the playlist. (I am the next one!)
335             bot.async_download_next()
336     else:
337         bot.send_msg(constants.strings('bad_parameter', command=command))
338
339
340 def cmd_play_playlist(bot, user, text, command, parameter):
341     global log
342
343     offset = 0  # if you want to start the playlist at a specific index
344     try:
345         offset = int(parameter.split(" ")[-1])
346     except ValueError:
347         pass
348
349     url = util.get_url_from_input(parameter)
350     log.debug("cmd: fetching media info from playlist url %s" % url)
351     items = get_playlist_info(url=url, start_index=offset, user=user)
352     if len(items) > 0:
353         items = var.playlist.extend(list(map(
354             lambda item: get_cached_wrapper_from_scrap(bot, **item), items)))
355         for music in items:
356             log.info("cmd: add to playlist: " + music.format_debug_string())
357     else:
358         bot.send_msg(constants.strings("playlist_fetching_failed"), text)
359
360
361 def cmd_play_radio(bot, user, text, command, parameter):
362     global log
363
364     if not parameter:
365         all_radio = var.config.items('radio')
366         msg = constants.strings('preconfigurated_radio')
367         for i in all_radio:
368             comment = ""
369             if len(i[1].split(maxsplit=1)) == 2:
370                 comment = " - " + i[1].split(maxsplit=1)[1]
371             msg += "<br />" + i[0] + comment
372         bot.send_msg(msg, text)
373     else:
374         if var.config.has_option('radio', parameter):
375             parameter = var.config.get('radio', parameter)
376             parameter = parameter.split()[0]
377         url = util.get_url_from_input(parameter)
378         if url:
379             music_wrapper = get_cached_wrapper_from_scrap(bot, type='radio', url=url, user=user)
380
381             var.playlist.append(music_wrapper)
382             log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
383             bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()))
384         else:
385             bot.send_msg(constants.strings('bad_url'))
386
387
388 def cmd_rb_query(bot, user, text, command, parameter):
389     global log
390
391     log.info('cmd: Querying radio stations')
392     if not parameter:
393         log.debug('rbquery without parameter')
394         msg = constants.strings('rb_query_empty')
395         bot.send_msg(msg, text)
396     else:
397         log.debug('cmd: Found query parameter: ' + parameter)
398         # bot.send_msg('Searching for stations - this may take some seconds...', text)
399         rb_stations = radiobrowser.getstations_byname(parameter)
400         msg = constants.strings('rb_query_result')
401         msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th></tr>'
402         if not rb_stations:
403             log.debug('cmd: No matches found for rbquery ' + parameter)
404             bot.send_msg('Radio-Browser found no matches for ' + parameter, text)
405         else:
406             for s in rb_stations:
407                 stationid = s['id']
408                 stationname = s['stationname']
409                 country = s['country']
410                 codec = s['codec']
411                 bitrate = s['bitrate']
412                 genre = s['genre']
413                 # msg += f'<tr><td>{stationid}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td></tr>'
414                 msg += '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td></tr>' % (
415                     stationid, stationname, genre, codec, bitrate, country)
416             msg += '</table>'
417             # Full message as html table
418             if len(msg) <= 5000:
419                 bot.send_msg(msg, text)
420             # Shorten message if message too long (stage I)
421             else:
422                 log.debug('Result too long stage I')
423                 msg = constants.strings('rb_query_result') + ' (shortened L1)'
424                 msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th></tr>'
425                 for s in rb_stations:
426                     stationid = s['id']
427                     stationname = s['stationname']
428                     # msg += f'<tr><td>{stationid}</td><td>{stationname}</td>'
429                     msg += '<tr><td>%s</td><td>%s</td>' % (stationid, stationname)
430                 msg += '</table>'
431                 if len(msg) <= 5000:
432                     bot.send_msg(msg, text)
433                 # Shorten message if message too long (stage II)
434                 else:
435                     log.debug('Result too long stage II')
436                     msg = constants.strings('rb_query_result') + ' (shortened L2)'
437                     msg += '!rbplay ID - Station Name'
438                     for s in rb_stations:
439                         stationid = s['id']
440                         stationname = s['stationname'][:12]
441                         # msg += f'{stationid} - {stationname}'
442                         msg += '%s - %s' % (stationid, stationname)
443                     if len(msg) <= 5000:
444                         bot.send_msg(msg, text)
445                     # Message still too long
446                     else:
447                         bot.send_msg('Query result too long to post (> 5000 characters), please try another query.',
448                                      text)
449
450
451 def cmd_rb_play(bot, user, text, command, parameter):
452     global log
453
454     log.debug('cmd: Play a station by ID')
455     if not parameter:
456         log.debug('rbplay without parameter')
457         msg = constants.strings('rb_play_empty')
458         bot.send_msg(msg, text)
459     else:
460         log.debug('cmd: Retreiving url for station ID ' + parameter)
461         rstation = radiobrowser.getstationname_byid(parameter)
462         stationname = rstation[0]['name']
463         country = rstation[0]['country']
464         codec = rstation[0]['codec']
465         bitrate = rstation[0]['bitrate']
466         genre = rstation[0]['tags']
467         homepage = rstation[0]['homepage']
468         msg = 'Radio station added to playlist:'
469         # msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
470         #       f'<tr><td>{parameter}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td><td>{homepage}</td></tr></table>'
471         msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
472                '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td><td>%s</td></tr></table>' \
473                % (parameter, stationname, genre, codec, bitrate, country, homepage)
474         log.debug('cmd: Added station to playlist %s' % stationname)
475         bot.send_msg(msg, text)
476         url = radiobrowser.geturl_byid(parameter)
477         if url != "-1":
478             log.info('cmd: Found url: ' + url)
479             music_wrapper = get_cached_wrapper_from_scrap(bot, type='radio', url=url, name=stationname, user=user)
480             var.playlist.append(music_wrapper)
481             log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
482             bot.async_download_next()
483         else:
484             log.info('cmd: No playable url found.')
485             msg += "No playable url found for this station, please try another station."
486             bot.send_msg(msg, text)
487
488
489 yt_last_result = []
490 yt_last_page = 0  # TODO: if we keep adding global variables, we need to consider sealing all commands up into classes.
491
492
493 def cmd_yt_search(bot, user, text, command, parameter):
494     global log, yt_last_result, yt_last_page, song_shortlist
495     item_per_page = 5
496
497     if parameter:
498         # if next page
499         if parameter.startswith("-n"):
500             yt_last_page += 1
501             if len(yt_last_result) > yt_last_page * item_per_page:
502                 song_shortlist = [{'type': 'url',
503                                    'url': "https://www.youtube.com/watch?v=" + result[0],
504                                    'title': result[1]
505                                    } for result in yt_last_result[yt_last_page * item_per_page: item_per_page]]
506                 msg = _yt_format_result(yt_last_result, yt_last_page * item_per_page, item_per_page)
507                 bot.send_msg(constants.strings('yt_result', result_table=msg), text)
508             else:
509                 bot.send_msg(constants.strings('yt_no_more'))
510
511         # if query
512         else:
513             results = util.youtube_search(parameter)
514             if results:
515                 yt_last_result = results
516                 yt_last_page = 0
517                 song_shortlist = [{'type': 'url', 'url': "https://www.youtube.com/watch?v=" + result[0]}
518                                   for result in results[0: item_per_page]]
519                 msg = _yt_format_result(results, 0, item_per_page)
520                 bot.send_msg(constants.strings('yt_result', result_table=msg), text)
521             else:
522                 bot.send_msg(constants.strings('yt_query_error'))
523     else:
524         bot.send_msg(constants.strings('bad_parameter', command=command), text)
525
526
527 def _yt_format_result(results, start, count):
528     msg = '<table><tr><th width="10%">Index</th><th>Title</th><th width="20%">Uploader</th></tr>'
529     for index, item in enumerate(results[start:start+count]):
530         msg += '<tr><td>{index:d}</td><td>{title}</td><td>{uploader}</td></tr>'.format(
531             index=index + 1, title=item[1], uploader=item[2])
532     msg += '</table>'
533
534     return msg
535
536
537 def cmd_yt_play(bot, user, text, command, parameter):
538     global log, yt_last_result, yt_last_page
539
540     if parameter:
541         results = util.youtube_search(parameter)
542         if results:
543             yt_last_result = results
544             yt_last_page = 0
545             url = "https://www.youtube.com/watch?v=" + yt_last_result[0][0]
546             cmd_play_url(bot, user, text, command, url)
547         else:
548             bot.send_msg(constants.strings('yt_query_error'))
549     else:
550         bot.send_msg(constants.strings('bad_parameter', command=command), text)
551
552
553 def cmd_help(bot, user, text, command, parameter):
554     global log
555     bot.send_msg(constants.strings('help'), text)
556     if bot.is_admin(user):
557         bot.send_msg(constants.strings('admin_help'), text)
558
559
560 def cmd_stop(bot, user, text, command, parameter):
561     global log
562
563     bot.stop()
564     bot.send_msg(constants.strings('stopped'), text)
565
566
567 def cmd_clear(bot, user, text, command, parameter):
568     global log
569
570     bot.clear()
571     bot.send_msg(constants.strings('cleared'), text)
572
573
574 def cmd_kill(bot, user, text, command, parameter):
575     global log
576
577     if bot.is_admin(user):
578         bot.pause()
579         bot.exit = True
580     else:
581         bot.mumble.users[text.actor].send_text_message(
582             constants.strings('not_admin'))
583
584
585 def cmd_update(bot, user, text, command, parameter):
586     global log
587
588     if bot.is_admin(user):
589         bot.mumble.users[text.actor].send_text_message(
590             constants.strings('start_updating'))
591         msg = util.update(bot.version)
592         bot.mumble.users[text.actor].send_text_message(msg)
593     else:
594         bot.mumble.users[text.actor].send_text_message(
595             constants.strings('not_admin'))
596
597
598 def cmd_stop_and_getout(bot, user, text, command, parameter):
599     global log
600
601     bot.stop()
602     if var.playlist.mode == "one-shot":
603         var.playlist.clear()
604
605     if bot.channel:
606         bot.mumble.channels.find_by_name(bot.channel).move_in()
607
608
609 def cmd_volume(bot, user, text, command, parameter):
610     global log
611
612     # The volume is a percentage
613     if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:
614         bot.volume_set = float(float(parameter) / 100)
615         bot.send_msg(constants.strings('change_volume',
616                      volume=int(bot.volume_set * 100), user=bot.mumble.users[text.actor]['name']))
617         var.db.set('bot', 'volume', str(bot.volume_set))
618         log.info('cmd: volume set to %d' % (bot.volume_set * 100))
619     else:
620         bot.send_msg(constants.strings('current_volume', volume=int(bot.volume_set * 100)), text)
621
622
623 def cmd_ducking(bot, user, text, command, parameter):
624     global log
625
626     if parameter == "" or parameter == "on":
627         bot.is_ducking = True
628         var.db.set('bot', 'ducking', True)
629         bot.ducking_volume = var.config.getfloat("bot", "ducking_volume", fallback=0.05)
630         bot.ducking_threshold = var.config.getint("bot", "ducking_threshold", fallback=5000)
631         bot.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED,
632                                           bot.ducking_sound_received)
633         bot.mumble.set_receive_sound(True)
634         log.info('cmd: ducking is on')
635         msg = "Ducking on."
636         bot.send_msg(msg, text)
637     elif parameter == "off":
638         bot.is_ducking = False
639         bot.mumble.set_receive_sound(False)
640         var.db.set('bot', 'ducking', False)
641         msg = "Ducking off."
642         log.info('cmd: ducking is off')
643         bot.send_msg(msg, text)
644
645
646 def cmd_ducking_threshold(bot, user, text, command, parameter):
647     global log
648
649     if parameter and parameter.isdigit():
650         bot.ducking_threshold = int(parameter)
651         var.db.set('bot', 'ducking_threshold', str(bot.ducking_threshold))
652         msg = "Ducking threshold set to %d." % bot.ducking_threshold
653         bot.send_msg(msg, text)
654     else:
655         msg = "Current ducking threshold is %d." % bot.ducking_threshold
656         bot.send_msg(msg, text)
657
658
659 def cmd_ducking_volume(bot, user, text, command, parameter):
660     global log
661
662     # The volume is a percentage
663     if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:
664         bot.ducking_volume = float(float(parameter) / 100)
665         bot.send_msg(constants.strings('change_ducking_volume',
666                      volume=int(bot.ducking_volume * 100), user=bot.mumble.users[text.actor]['name']), text)
667         # var.db.set('bot', 'volume', str(bot.volume_set))
668         var.db.set('bot', 'ducking_volume', str(bot.ducking_volume))
669         log.info('cmd: volume on ducking set to %d' % (bot.ducking_volume * 100))
670     else:
671         bot.send_msg(constants.strings('current_ducking_volume', volume=int(bot.ducking_volume * 100)), text)
672
673
674 def cmd_current_music(bot, user, text, command, parameter):
675     global log
676
677     if len(var.playlist) > 0:
678         bot.send_msg(var.playlist.current_item().format_current_playing(), text)
679     else:
680         bot.send_msg(constants.strings('not_playing'), text)
681
682
683 def cmd_skip(bot, user, text, command, parameter):
684     global log
685
686     bot.interrupt()
687
688     if len(var.playlist) == 0:
689         bot.send_msg(constants.strings('queue_empty'), text)
690
691
692 def cmd_last(bot, user, text, command, parameter):
693     global log
694
695     if len(var.playlist) > 0:
696         bot.interrupt()
697         var.playlist.point_to(len(var.playlist) - 1 - 1)
698     else:
699         bot.send_msg(constants.strings('queue_empty'), text)
700
701
702 def cmd_remove(bot, user, text, command, parameter):
703     global log
704
705     # Allow to remove specific music into the queue with a number
706     if parameter and parameter.isdigit() and 0 < int(parameter) <= len(var.playlist):
707
708         index = int(parameter) - 1
709
710         if index == var.playlist.current_index:
711             removed = var.playlist[index]
712             bot.send_msg(constants.strings('removing_item',
713                                            item=removed.format_short_string()), text)
714             log.info("cmd: delete from playlist: " + removed.format_debug_string())
715
716             var.playlist.remove(index)
717
718             if index < len(var.playlist):
719                 if not bot.is_pause:
720                     bot.interrupt()
721                     var.playlist.current_index -= 1
722                     # then the bot will move to next item
723
724             else:  # if item deleted is the last item of the queue
725                 var.playlist.current_index -= 1
726                 if not bot.is_pause:
727                     bot.interrupt()
728         else:
729             var.playlist.remove(index)
730
731     else:
732         bot.send_msg(constants.strings('bad_parameter', command=command), text)
733
734
735 def cmd_list_file(bot, user, text, command, parameter):
736     global log
737
738     files = var.cache.files
739     msgs = [constants.strings("multiple_file_found")]
740     try:
741         count = 0
742         for index, file in enumerate(files):
743             if parameter:
744                 match = re.search(parameter, file)
745                 if not match:
746                     continue
747
748             count += 1
749             msgs.append("<b>{:0>3d}</b> - {:s}".format(index, file))
750
751         if count != 0:
752             send_multi_lines(bot, msgs, text)
753         else:
754             bot.send_msg(constants.strings('no_file'), text)
755
756     except re.error as e:
757         msg = constants.strings('wrong_pattern', error=str(e))
758         bot.send_msg(msg, text)
759
760
761 def cmd_queue(bot, user, text, command, parameter):
762     global log
763
764     if len(var.playlist) == 0:
765         msg = constants.strings('queue_empty')
766         bot.send_msg(msg, text)
767     else:
768         msgs = [constants.strings('queue_contents')]
769         for i, music in enumerate(var.playlist):
770             tags = ''
771             if len(music.item().tags) > 0:
772                 tags = "<sup>{}</sup>".format(", ".join(music.item().tags))
773             if i == var.playlist.current_index:
774                 newline = "<b style='color:orange'>{} ({}) {} </b> {}".format(i + 1, music.display_type(),
775                                                                               music.format_short_string(), tags)
776             else:
777                 newline = '<b>{}</b> ({}) {} {}'.format(i + 1, music.display_type(),
778                                                         music.format_short_string(), tags)
779
780             msgs.append(newline)
781
782         send_multi_lines(bot, msgs, text)
783
784
785 def cmd_random(bot, user, text, command, parameter):
786     global log
787
788     bot.interrupt()
789     var.playlist.randomize()
790
791
792 def cmd_repeat(bot, user, text, command, parameter):
793     global log
794
795     repeat = 1
796     if parameter and parameter.isdigit():
797         repeat = int(parameter)
798
799     music = var.playlist.current_item()
800     for _ in range(repeat):
801         var.playlist.insert(
802             var.playlist.current_index + 1,
803             music
804         )
805         log.info("bot: add to playlist: " + music.format_debug_string())
806
807     bot.send_msg(constants.strings("repeat", song=music.format_song_string(), n=str(repeat)), text)
808
809
810 def cmd_mode(bot, user, text, command, parameter):
811     global log
812
813     if not parameter:
814         bot.send_msg(constants.strings("current_mode", mode=var.playlist.mode), text)
815         return
816     if parameter not in ["one-shot", "repeat", "random", "autoplay"]:
817         bot.send_msg(constants.strings('unknown_mode', mode=parameter), text)
818     else:
819         var.db.set('playlist', 'playback_mode', parameter)
820         var.playlist = media.playlist.get_playlist(parameter, var.playlist)
821         log.info("command: playback mode changed to %s." % parameter)
822         bot.send_msg(constants.strings("change_mode", mode=var.playlist.mode,
823                                        user=bot.mumble.users[text.actor]['name']), text)
824         if parameter == "random":
825             bot.interrupt()
826             bot.launch_music()
827
828
829 def cmd_play_tags(bot, user, text, command, parameter):
830     if not parameter:
831         bot.send_msg(constants.strings('bad_parameter', command=command), text)
832         return
833
834     msgs = [constants.strings('multiple_file_added') + "<ul>"]
835     count = 0
836
837     tags = parameter.split(",")
838     tags = list(map(lambda t: t.strip(), tags))
839     music_wrappers = get_cached_wrappers_by_tags(bot, tags, user)
840     for music_wrapper in music_wrappers:
841         count += 1
842         log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
843         msgs.append("<li><b>{}</b> (<i>{}</i>)</li>".format(music_wrapper.item().title, ", ".join(music_wrapper.item().tags)))
844
845     if count != 0:
846         msgs.append("</ul>")
847         var.playlist.extend(music_wrappers)
848         send_multi_lines(bot, msgs, None, "")
849     else:
850         bot.send_msg(constants.strings("no_file"), text)
851
852
853 def cmd_add_tag(bot, user, text, command, parameter):
854     global log
855
856     params = parameter.split()
857     if len(params) == 2:
858         index = params[0]
859         tags = list(map(lambda t: t.strip(), params[1].split(",")))
860     elif len(params) == 1:
861         index = str(var.playlist.current_index + 1)
862         tags = list(map(lambda t: t.strip(), params[0].split(",")))
863     else:
864         bot.send_msg(constants.strings('bad_parameter', command=command), text)
865         return
866
867     if tags[0]:
868         if index.isdigit() and 1 <= int(index) <= len(var.playlist):
869             var.playlist[int(index) - 1].add_tags(tags)
870             log.info("cmd: add tags %s to song %s" % (", ".join(tags),
871                                                       var.playlist[int(index) - 1].format_debug_string()))
872             bot.send_msg(constants.strings("added_tags",
873                                            tags=", ".join(tags),
874                                            song=var.playlist[int(index) - 1].format_short_string()), text)
875             return
876
877         elif index == "*":
878             for item in var.playlist:
879                 item.add_tags(tags)
880                 log.info("cmd: add tags %s to song %s" % (", ".join(tags),
881                                                           item.format_debug_string()))
882             bot.send_msg(constants.strings("added_tags_to_all", tags=", ".join(tags)), text)
883             return
884
885     bot.send_msg(constants.strings('bad_parameter', command=command), text)
886
887
888 def cmd_remove_tag(bot, user, text, command, parameter):
889     global log
890
891     params = parameter.split()
892
893     if len(params) == 2:
894         index = params[0]
895         tags = list(map(lambda t: t.strip(), params[1].split(",")))
896     elif len(params) == 1:
897         index = str(var.playlist.current_index + 1)
898         tags = list(map(lambda t: t.strip(), params[0].split(",")))
899     else:
900         bot.send_msg(constants.strings('bad_parameter', command=command), text)
901         return
902
903     if tags[0]:
904         if index.isdigit() and 1 <= int(index) <= len(var.playlist):
905             if tags[0] != "*":
906                 var.playlist[int(index) - 1].remove_tags(tags)
907                 log.info("cmd: remove tags %s from song %s" % (", ".join(tags),
908                                                                var.playlist[int(index) - 1].format_debug_string()))
909                 bot.send_msg(constants.strings("removed_tags",
910                                                tags=", ".join(tags),
911                                                song=var.playlist[int(index) - 1].format_short_string()), text)
912                 return
913             else:
914                 var.playlist[int(index) - 1].clear_tags()
915                 log.info("cmd: clear tags from song %s" % (var.playlist[int(index) - 1].format_debug_string()))
916                 bot.send_msg(constants.strings("cleared_tags",
917                                                song=var.playlist[int(index) - 1].format_short_string()), text)
918                 return
919
920         elif index == "*":
921             if tags[0] != "*":
922                 for item in var.playlist:
923                     item.remove_tags(tags)
924                     log.info("cmd: remove tags %s from song %s" % (", ".join(tags),
925                                                                    item.format_debug_string()))
926                 bot.send_msg(constants.strings("removed_tags_from_all", tags=", ".join(tags)), text)
927                 return
928             else:
929                 for item in var.playlist:
930                     item.clear_tags()
931                     log.info("cmd: clear tags from song %s" % (item.format_debug_string()))
932                 bot.send_msg(constants.strings("cleared_tags_from_all"), text)
933                 return
934
935     bot.send_msg(constants.strings('bad_parameter', command=command), text)
936
937
938 def cmd_find_tagged(bot, user, text, command, parameter):
939     global song_shortlist
940
941     if not parameter:
942         bot.send_msg(constants.strings('bad_parameter', command=command), text)
943         return
944
945     msgs = [constants.strings('multiple_file_found') + "<ul>"]
946     count = 0
947
948     tags = parameter.split(",")
949     tags = list(map(lambda t: t.strip(), tags))
950
951     music_dicts = var.music_db.query_music_by_tags(tags)
952     song_shortlist = music_dicts
953     items = dicts_to_items(bot, music_dicts)
954
955     for i, item in enumerate(items):
956         count += 1
957         msgs.append("<li><b>{:d}</b> - <b>{}</b> (<i>{}</i>)</li>".format(i+1, item.title, ", ".join(item.tags)))
958
959     if count != 0:
960         msgs.append("</ul>")
961         msgs.append(constants.strings("shortlist_instruction"))
962         send_multi_lines(bot, msgs, text, "")
963     else:
964         bot.send_msg(constants.strings("no_file"), text)
965
966
967 def cmd_search_library(bot, user, text, command, parameter):
968     global song_shortlist
969     if not parameter:
970         bot.send_msg(constants.strings('bad_parameter', command=command), text)
971         return
972
973     msgs = [constants.strings('multiple_file_found') + "<ul>"]
974     count = 0
975
976     _keywords = parameter.split(" ")
977     keywords = []
978     for kw in _keywords:
979         if kw:
980             keywords.append(kw)
981
982     music_dicts = var.music_db.query_music_by_keywords(keywords)
983     if music_dicts:
984         items = dicts_to_items(bot, music_dicts)
985         song_shortlist = music_dicts
986
987         for item in items:
988             count += 1
989             if len(item.tags) > 0:
990                 msgs.append("<li><b>{:d}</b> - [{}] <b>{}</b> (<i>{}</i>)</li>".format(count, item.display_type(), item.title, ", ".join(item.tags)))
991             else:
992                 msgs.append("<li><b>{:d}</b> - [{}] <b>{}</b> </li>".format(count, item.display_type(), item.title, ", ".join(item.tags)))
993
994         if count != 0:
995             msgs.append("</ul>")
996             msgs.append(constants.strings("shortlist_instruction"))
997             send_multi_lines(bot, msgs, text, "")
998         else:
999             bot.send_msg(constants.strings("no_file"), text)
1000     else:
1001         bot.send_msg(constants.strings("no_file"), text)
1002
1003
1004 def cmd_shortlist(bot, user, text, command, parameter):
1005     global song_shortlist, log
1006     try:
1007         indexes = [int(i) for i in parameter.split(" ")]
1008     except ValueError:
1009         bot.send_msg(constants.strings('bad_parameter', command=command), text)
1010         return
1011
1012     if len(indexes) > 1:
1013         msgs = [constants.strings('multiple_file_added') + "<ul>"]
1014         for index in indexes:
1015             if 1 <= index <= len(song_shortlist):
1016                 kwargs = song_shortlist[index - 1]
1017                 kwargs['user'] = user
1018                 music_wrapper = get_cached_wrapper_from_scrap(bot, **kwargs)
1019                 var.playlist.append(music_wrapper)
1020                 log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
1021                 msgs.append("<li>[{}] <b>{}</b></li>".format(music_wrapper.item().type, music_wrapper.item().title))
1022             else:
1023                 bot.send_msg(constants.strings('bad_parameter', command=command), text)
1024                 return
1025
1026         msgs.append("</ul>")
1027         send_multi_lines(bot, msgs, None, "")
1028         return
1029     elif len(indexes) == 1:
1030         index = indexes[0]
1031         if 1 <= index <= len(song_shortlist):
1032             kwargs = song_shortlist[index - 1]
1033             kwargs['user'] = user
1034             music_wrapper = get_cached_wrapper_from_scrap(bot, **kwargs)
1035             var.playlist.append(music_wrapper)
1036             log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
1037             bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()))
1038             return
1039
1040     bot.send_msg(constants.strings('bad_parameter', command=command), text)
1041
1042
1043 def cmd_delete_from_library(bot, user, text, command, parameter):
1044     global song_shortlist, log
1045     try:
1046         indexes = [int(i) for i in parameter.split(" ")]
1047     except ValueError:
1048         bot.send_msg(constants.strings('bad_parameter', command=command), text)
1049         return
1050
1051     if len(indexes) > 1:
1052         msgs = [constants.strings('multiple_file_added') + "<ul>"]
1053         count = 0
1054         for index in indexes:
1055             if 1 <= index <= len(song_shortlist):
1056                 music_dict = song_shortlist[index - 1]
1057                 if 'id' in music_dict:
1058                     music_wrapper = get_cached_wrapper_by_id(bot, music_dict['id'], user)
1059                     log.info("cmd: remove from library: " + music_wrapper.format_debug_string())
1060                     msgs.append("<li>[{}] <b>{}</b></li>".format(music_wrapper.item().type, music_wrapper.item().title))
1061                     var.playlist.remove_by_id(music_dict['id'])
1062                     var.cache.free_and_delete(music_dict['id'])
1063                     count += 1
1064             else:
1065                 bot.send_msg(constants.strings('bad_parameter', command=command), text)
1066                 return
1067
1068         if count == 0:
1069             bot.send_msg(constants.strings('bad_parameter', command=command), text)
1070             return
1071
1072         msgs.append("</ul>")
1073         send_multi_lines(bot, msgs, None, "")
1074         return
1075     elif len(indexes) == 1:
1076         index = indexes[0]
1077         if 1 <= index <= len(song_shortlist):
1078             music_dict = song_shortlist[index - 1]
1079             if 'id' in music_dict:
1080                 music_wrapper = get_cached_wrapper_by_id(bot, music_dict['id'], user)
1081                 bot.send_msg(constants.strings('file_deleted', item=music_wrapper.format_song_string()), text)
1082                 log.info("cmd: remove from library: " + music_wrapper.format_debug_string())
1083                 var.playlist.remove_by_id(music_dict['id'])
1084                 var.cache.free_and_delete(music_dict['id'])
1085                 return
1086
1087     bot.send_msg(constants.strings('bad_parameter', command=command), text)
1088
1089
1090 def cmd_drop_database(bot, user, text, command, parameter):
1091     global log
1092
1093     if bot.is_admin(user):
1094         var.db.drop_table()
1095         var.db = SettingsDatabase(var.dbfile)
1096         var.music_db.drop_table()
1097         var.music_db = MusicDatabase(var.dbfile)
1098         log.info("command: database dropped.")
1099         bot.send_msg(constants.strings('database_dropped'), text)
1100     else:
1101         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
1102
1103
1104 def cmd_refresh_cache(bot, user, text, command, parameter):
1105     global log
1106     if bot.is_admin(user):
1107         var.cache.build_dir_cache(bot)
1108         log.info("command: Local file cache refreshed.")
1109         bot.send_msg(constants.strings('cache_refreshed'), text)
1110     else:
1111         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
1112
1113
1114 # Just for debug use
1115 def cmd_real_time_rms(bot, user, text, command, parameter):
1116     bot._display_rms = not bot._display_rms
1117
1118
1119 def cmd_loop_state(bot, user, text, command, parameter):
1120     print(bot._loop_status)
1121
1122
1123 def cmd_item(bot, user, text, command, parameter):
1124     print(bot.wait_for_downloading)
1125     print(var.playlist.current_item().to_dict())