]> git.0d.be Git - botaradio.git/blob - command.py
fix: get_recursive_file_list_sorted not completed replaced in other files
[botaradio.git] / command.py
1 # coding=utf-8
2 import logging
3 import os.path
4 import pymumble.pymumble_py3 as pymumble
5 import re
6
7 import constants
8 import media.file
9 import media.playlist
10 import media.radio
11 import media.system
12 import media.url
13 import util
14 import variables as var
15 from librb import radiobrowser
16 from database import Database
17
18 log = logging.getLogger("bot")
19
20 def register_all_commands(bot):
21     bot.register_command(constants.commands('joinme'), cmd_joinme)
22     bot.register_command(constants.commands('user_ban'), cmd_user_ban)
23     bot.register_command(constants.commands('user_unban'), cmd_user_unban)
24     bot.register_command(constants.commands('url_ban'), cmd_url_ban)
25     bot.register_command(constants.commands('url_unban'), cmd_url_unban)
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('rb_query'), cmd_rb_query)
34     bot.register_command(constants.commands('rb_play'), cmd_rb_play)
35     bot.register_command(constants.commands('help'), cmd_help)
36     bot.register_command(constants.commands('stop'), cmd_stop)
37     bot.register_command(constants.commands('clear'), cmd_clear)
38     bot.register_command(constants.commands('kill'), cmd_kill)
39     bot.register_command(constants.commands('update'), cmd_update)
40     bot.register_command(constants.commands('stop_and_getout'), cmd_stop_and_getout)
41     bot.register_command(constants.commands('volume'), cmd_volume)
42     bot.register_command(constants.commands('ducking'), cmd_ducking)
43     bot.register_command(constants.commands('ducking_threshold'), cmd_ducking_threshold)
44     bot.register_command(constants.commands('ducking_volume'), cmd_ducking_volume)
45     bot.register_command(constants.commands('current_music'), cmd_current_music)
46     bot.register_command(constants.commands('skip'), cmd_skip)
47     bot.register_command(constants.commands('remove'), cmd_remove)
48     bot.register_command(constants.commands('list_file'), cmd_list_file)
49     bot.register_command(constants.commands('queue'), cmd_queue)
50     bot.register_command(constants.commands('random'), cmd_random)
51     bot.register_command(constants.commands('repeat'), cmd_repeat)
52     bot.register_command(constants.commands('mode'), cmd_mode)
53     bot.register_command(constants.commands('drop_database'), cmd_drop_database)
54
55 def send_multi_lines(bot, lines, text):
56     global log
57
58     msg = ""
59     br = ""
60     for newline in lines:
61         msg += br
62         br = "<br>"
63         if len(msg) + len(newline) > 5000:
64             bot.send_msg(msg, text)
65             msg = ""
66         msg += newline
67
68     bot.send_msg(msg, text)
69
70 # ---------------- Commands ------------------
71
72
73 def cmd_joinme(bot, user, text, command, parameter):
74     global log
75
76     bot.mumble.users.myself.move_in(
77         bot.mumble.users[text.actor]['channel_id'], token=parameter)
78
79
80 def cmd_user_ban(bot, user, text, command, parameter):
81     global log
82
83     if bot.is_admin(user):
84         if parameter:
85             bot.mumble.users[text.actor].send_text_message(util.user_ban(parameter))
86         else:
87             bot.mumble.users[text.actor].send_text_message(util.get_user_ban())
88     else:
89         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
90     return
91
92
93 def cmd_user_unban(bot, user, text, command, parameter):
94     global log
95
96     if bot.is_admin(user):
97         if parameter:
98             bot.mumble.users[text.actor].send_text_message(util.user_unban(parameter))
99     else:
100         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
101     return
102
103
104 def cmd_url_ban(bot, user, text, command, parameter):
105     global log
106
107     if bot.is_admin(user):
108         if parameter:
109             bot.mumble.users[text.actor].send_text_message(util.url_ban(util.get_url_from_input(parameter)))
110         else:
111             bot.mumble.users[text.actor].send_text_message(util.get_url_ban())
112     else:
113         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
114     return
115
116
117 def cmd_url_unban(bot, user, text, command, parameter):
118     global log
119
120     if bot.is_admin(user):
121         if parameter:
122             bot.mumble.users[text.actor].send_text_message(util.url_unban(util.get_url_from_input(parameter)))
123     else:
124         bot.mumble.users[text.actor].send_text_message(constants.strings('not_admin'))
125     return
126
127
128 def cmd_play(bot, user, text, command, parameter):
129     global log
130
131     if var.playlist.length() > 0:
132         if parameter:
133             if parameter.isdigit() and int(parameter) > 0 and int(parameter) <= len(var.playlist):
134                 bot.interrupt_playing()
135                 bot.launch_music(int(parameter) - 1)
136             else:
137                 bot.send_msg(constants.strings('invalid_index', index=parameter), text)
138
139         elif bot.is_pause:
140             bot.resume()
141         else:
142             bot.send_msg(util.format_current_playing(), text)
143     else:
144         bot.is_pause = False
145         bot.send_msg(constants.strings('queue_empty'), text)
146
147
148 def cmd_pause(bot, user, text, command, parameter):
149     global log
150
151     bot.pause()
152     bot.send_msg(constants.strings('paused'))
153
154
155 def cmd_play_file(bot, user, text, command, parameter):
156     global log
157
158     # if parameter is {index}
159     if parameter.isdigit():
160         files = util.get_recursive_file_list_sorted(var.music_folder)
161         if int(parameter) < len(files):
162             filename = files[int(parameter)].replace(var.music_folder, '')
163             music = {'type': 'file',
164                      'path': filename,
165                      'user': user}
166             music = var.playlist.append(music)
167             log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
168             bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
169
170     # if parameter is {path}
171     else:
172         # sanitize "../" and so on
173         path = os.path.abspath(os.path.join(var.music_folder, parameter))
174         if not path.startswith(os.path.abspath(var.music_folder)):
175             bot.send_msg(constants.strings('no_file'), text)
176             return
177
178         if os.path.isfile(path):
179             music = {'type': 'file',
180                      'path': parameter,
181                      'user': user}
182             music = var.playlist.append(music)
183             log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
184             bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
185             return
186
187         # if parameter is {folder}
188         elif os.path.isdir(path):
189             if parameter != '.' and parameter != './':
190                 if not parameter.endswith("/"):
191                     parameter += "/"
192             else:
193                 parameter = ""
194
195             files = util.get_recursive_file_list_sorted(var.music_folder)
196             music_library = util.Dir(var.music_folder)
197             for file in files:
198                 music_library.add_file(file)
199
200             files = music_library.get_files(parameter)
201             msgs = [constants.strings('multiple_file_added')]
202             count = 0
203
204             for file in files:
205                 count += 1
206                 music = {'type': 'file',
207                          'path': file,
208                          'user': user}
209                 music = var.playlist.append(music)
210                 log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
211                 msgs.append("{} ({})".format(music['title'], music['path']))
212
213             if count != 0:
214                 send_multi_lines(bot, msgs, text)
215             else:
216                 bot.send_msg(constants.strings('no_file'), text)
217
218         else:
219             # try to do a partial match
220             files = util.get_recursive_file_list_sorted(var.music_folder)
221             matches = [(index, file) for index, file in enumerate(files) if parameter.lower() in file.lower()]
222             if len(matches) == 0:
223                 bot.send_msg(constants.strings('no_file'), text)
224             elif len(matches) == 1:
225                 music = {'type': 'file',
226                          'path': matches[0][1],
227                          'user': user}
228                 music = var.playlist.append(music)
229                 log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
230                 bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
231             else:
232                 msgs = [ constants.strings('multiple_matches')]
233                 for match in matches:
234                     msgs.append("<b>{:0>3d}</b> - {:s}".format(match[0], match[1]))
235                 send_multi_lines(bot, msgs, text)
236
237
238 def cmd_play_file_match(bot, user, text, command, parameter):
239     global log
240
241     music_folder = var.music_folder
242     if parameter:
243         files = util.get_recursive_file_list_sorted(music_folder)
244         msgs = [ constants.strings('multiple_file_added')]
245         count = 0
246         try:
247             for file in files:
248                 match = re.search(parameter, file)
249                 if match:
250                     count += 1
251                     music = {'type': 'file',
252                              'path': file,
253                              'user': user}
254                     music = var.playlist.append(music)
255                     log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
256
257                     msgs.append("{} ({})".format(music['title'], music['path']))
258
259             if count != 0:
260                 send_multi_lines(bot, msgs, text)
261             else:
262                 bot.send_msg(constants.strings('no_file'), text)
263
264         except re.error as e:
265             msg = constants.strings('wrong_pattern', error=str(e))
266             bot.send_msg(msg, text)
267     else:
268         bot.send_msg(constants.strings('bad_parameter', command))
269
270
271 def cmd_play_url(bot, user, text, command, parameter):
272     global log
273
274     music = {'type': 'url',
275              # grab the real URL
276              'url': util.get_url_from_input(parameter),
277              'user': user,
278              'ready': 'validation'}
279
280     music = bot.validate_music(music)
281     if music:
282         music = var.playlist.append(music)
283         log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
284         bot.send_msg(constants.strings('file_added', item=util.format_song_string(music)), text)
285         if var.playlist.length() == 2:
286             # If I am the second item on the playlist. (I am the next one!)
287             bot.async_download_next()
288     else:
289         bot.send_msg(constants.strings('unable_download'), text)
290
291
292 def cmd_play_playlist(bot, user, text, command, parameter):
293     global log
294
295     offset = 0  # if you want to start the playlist at a specific index
296     try:
297         offset = int(parameter.split(" ")[-1])
298     except ValueError:
299         pass
300
301     url = util.get_url_from_input(parameter)
302     log.debug("cmd: fetching media info from playlist url %s" % url)
303     items = media.playlist.get_playlist_info(url=url, start_index=offset, user=user)
304     if len(items) > 0:
305         var.playlist.extend(items)
306         for music in items:
307             log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
308
309
310 def cmd_play_radio(bot, user, text, command, parameter):
311     global log
312
313     if not parameter:
314         all_radio = var.config.items('radio')
315         msg = constants.strings('preconfigurated_radio')
316         for i in all_radio:
317             comment = ""
318             if len(i[1].split(maxsplit=1)) == 2:
319                 comment = " - " + i[1].split(maxsplit=1)[1]
320             msg += "<br />" + i[0] + comment
321         bot.send_msg(msg, text)
322     else:
323         if var.config.has_option('radio', parameter):
324             parameter = var.config.get('radio', parameter)
325             parameter = parameter.split()[0]
326         url = util.get_url_from_input(parameter)
327         if url:
328             music = {'type': 'radio',
329                      'url': url,
330                      'user': user}
331             var.playlist.append(music)
332             log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
333             bot.async_download_next()
334         else:
335             bot.send_msg(constants.strings('bad_url'))
336
337
338 def cmd_rb_query(bot, user, text, command, parameter):
339     global log
340
341     log.info('cmd: Querying radio stations')
342     if not parameter:
343         log.debug('rbquery without parameter')
344         msg = constants.strings('rb_query_empty')
345         bot.send_msg(msg, text)
346     else:
347         log.debug('cmd: Found query parameter: ' + parameter)
348         # bot.send_msg('Searching for stations - this may take some seconds...', text)
349         rb_stations = radiobrowser.getstations_byname(parameter)
350         msg = constants.strings('rb_query_result')
351         msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th></tr>'
352         if not rb_stations:
353             log.debug('cmd: No matches found for rbquery ' + parameter)
354             bot.send_msg('Radio-Browser found no matches for ' + parameter, text)
355         else:
356             for s in rb_stations:
357                 stationid = s['id']
358                 stationname = s['stationname']
359                 country = s['country']
360                 codec = s['codec']
361                 bitrate = s['bitrate']
362                 genre = s['genre']
363                 # msg += f'<tr><td>{stationid}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td></tr>'
364                 msg += '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td></tr>' % (
365                     stationid, stationname, genre, codec, bitrate, country)
366             msg += '</table>'
367             # Full message as html table
368             if len(msg) <= 5000:
369                 bot.send_msg(msg, text)
370             # Shorten message if message too long (stage I)
371             else:
372                 log.debug('Result too long stage I')
373                 msg = constants.strings('rb_query_result') + ' (shortened L1)'
374                 msg += '\n<table><tr><th>!rbplay ID</th><th>Station Name</th></tr>'
375                 for s in rb_stations:
376                     stationid = s['id']
377                     stationname = s['stationname']
378                     # msg += f'<tr><td>{stationid}</td><td>{stationname}</td>'
379                     msg += '<tr><td>%s</td><td>%s</td>' % (stationid, stationname)
380                 msg += '</table>'
381                 if len(msg) <= 5000:
382                     bot.send_msg(msg, text)
383                 # Shorten message if message too long (stage II)
384                 else:
385                     log.debug('Result too long stage II')
386                     msg = constants.strings('rb_query_result') + ' (shortened L2)'
387                     msg += '!rbplay ID - Station Name'
388                     for s in rb_stations:
389                         stationid = s['id']
390                         stationname = s['stationname'][:12]
391                         # msg += f'{stationid} - {stationname}'
392                         msg += '%s - %s' % (stationid, stationname)
393                     if len(msg) <= 5000:
394                         bot.send_msg(msg, text)
395                     # Message still too long
396                     else:
397                         bot.send_msg('Query result too long to post (> 5000 characters), please try another query.',
398                                      text)
399
400
401 def cmd_rb_play(bot, user, text, command, parameter):
402     global log
403
404     log.debug('cmd: Play a station by ID')
405     if not parameter:
406         log.debug('rbplay without parameter')
407         msg = constants.strings('rb_play_empty')
408         bot.send_msg(msg, text)
409     else:
410         log.debug('cmd: Retreiving url for station ID ' + parameter)
411         rstation = radiobrowser.getstationname_byid(parameter)
412         stationname = rstation[0]['name']
413         country = rstation[0]['country']
414         codec = rstation[0]['codec']
415         bitrate = rstation[0]['bitrate']
416         genre = rstation[0]['tags']
417         homepage = rstation[0]['homepage']
418         msg = 'Radio station added to playlist:'
419         # msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
420         #       f'<tr><td>{parameter}</td><td>{stationname}</td><td>{genre}</td><td>{codec}/{bitrate}</td><td>{country}</td><td>{homepage}</td></tr></table>'
421         msg += '<table><tr><th>ID</th><th>Station Name</th><th>Genre</th><th>Codec/Bitrate</th><th>Country</th><th>Homepage</th></tr>' + \
422                '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s/%s</td><td>%s</td><td>%s</td></tr></table>' \
423                % (parameter, stationname, genre, codec, bitrate, country, homepage)
424         log.debug('cmd: Added station to playlist %s' % stationname)
425         bot.send_msg(msg, text)
426         url = radiobrowser.geturl_byid(parameter)
427         if url != "-1":
428             log.info('cmd: Found url: ' + url)
429             music = {'type': 'radio',
430                      'title': stationname,
431                      'artist': homepage,
432                      'url': url,
433                      'user': user}
434             var.playlist.append(music)
435             log.info("cmd: add to playlist: " + util.format_debug_song_string(music))
436             bot.async_download_next()
437         else:
438             log.info('cmd: No playable url found.')
439             msg += "No playable url found for this station, please try another station."
440             bot.send_msg(msg, text)
441
442
443 def cmd_help(bot, user, text, command, parameter):
444     global log
445
446     bot.send_msg(constants.strings('help'), text)
447     if bot.is_admin(user):
448         bot.send_msg(constants.strings('admin_help'), text)
449
450
451 def cmd_stop(bot, user, text, command, parameter):
452     global log
453
454     bot.stop()
455     bot.send_msg(constants.strings('stopped'), text)
456
457
458 def cmd_clear(bot, user, text, command, parameter):
459     global log
460
461     bot.clear()
462     bot.send_msg(constants.strings('cleared'), text)
463
464
465 def cmd_kill(bot, user, text, command, parameter):
466     global log
467
468     if bot.is_admin(user):
469         bot.pause()
470         bot.exit = True
471     else:
472         bot.mumble.users[text.actor].send_text_message(
473             constants.strings('not_admin'))
474
475
476 def cmd_update(bot, user, text, command, parameter):
477     global log
478
479     if bot.is_admin(user):
480         bot.mumble.users[text.actor].send_text_message(
481             constants.strings('start_updating'))
482         msg = util.update(bot.version)
483         bot.mumble.users[text.actor].send_text_message(msg)
484     else:
485         bot.mumble.users[text.actor].send_text_message(
486             constants.strings('not_admin'))
487
488
489 def cmd_stop_and_getout(bot, user, text, command, parameter):
490     global log
491
492     bot.stop()
493     if bot.channel:
494         bot.mumble.channels.find_by_name(bot.channel).move_in()
495
496
497 def cmd_volume(bot, user, text, command, parameter):
498     global log
499
500     # The volume is a percentage
501     if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:
502         bot.volume_set = float(float(parameter) / 100)
503         bot.send_msg(constants.strings('change_volume',
504             volume=int(bot.volume_set * 100), user=bot.mumble.users[text.actor]['name']), text)
505         var.db.set('bot', 'volume', str(bot.volume_set))
506         log.info('cmd: volume set to %d' % (bot.volume_set * 100))
507     else:
508         bot.send_msg(constants.strings('current_volume', volume=int(bot.volume_set * 100)), text)
509
510
511 def cmd_ducking(bot, user, text, command, parameter):
512     global log
513
514     if parameter == "" or parameter == "on":
515         bot.is_ducking = True
516         var.db.set('bot', 'ducking', True)
517         bot.ducking_volume = var.config.getfloat("bot", "ducking_volume", fallback=0.05)
518         bot.ducking_threshold = var.config.getint("bot", "ducking_threshold", fallback=5000)
519         bot.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_SOUNDRECEIVED,
520                                           bot.ducking_sound_received)
521         bot.mumble.set_receive_sound(True)
522         log.info('cmd: ducking is on')
523         msg = "Ducking on."
524         bot.send_msg(msg, text)
525     elif parameter == "off":
526         bot.is_ducking = False
527         bot.mumble.set_receive_sound(False)
528         var.db.set('bot', 'ducking', False)
529         msg = "Ducking off."
530         log.info('cmd: ducking is off')
531         bot.send_msg(msg, text)
532
533
534 def cmd_ducking_threshold(bot, user, text, command, parameter):
535     global log
536
537     if parameter and parameter.isdigit():
538         bot.ducking_threshold = int(parameter)
539         var.db.set('bot', 'ducking_threshold', str(bot.ducking_threshold))
540         msg = "Ducking threshold set to %d." % bot.ducking_threshold
541         bot.send_msg(msg, text)
542     else:
543         msg = "Current ducking threshold is %d." % bot.ducking_threshold
544         bot.send_msg(msg, text)
545
546
547 def cmd_ducking_volume(bot, user, text, command, parameter):
548     global log
549
550     # The volume is a percentage
551     if parameter and parameter.isdigit() and 0 <= int(parameter) <= 100:
552         bot.ducking_volume = float(float(parameter) / 100)
553         bot.send_msg(constants.strings('change_ducking_volume',
554             volume=int(bot.ducking_volume * 100), user=bot.mumble.users[text.actor]['name']), text)
555         # var.db.set('bot', 'volume', str(bot.volume_set))
556         var.db.set('bot', 'ducking_volume', str(bot.ducking_volume))
557         log.info('cmd: volume on ducking set to %d' % (bot.ducking_volume * 100))
558     else:
559         bot.send_msg(constants.strings('current_ducking_volume', volume=int(bot.ducking_volume * 100)), text)
560
561
562 def cmd_current_music(bot, user, text, command, parameter):
563     global log
564
565     reply = ""
566     if var.playlist.length() > 0:
567         bot.send_msg(util.format_current_playing())
568     else:
569         reply = constants.strings('not_playing')
570     bot.send_msg(reply, text)
571
572
573 def cmd_skip(bot, user, text, command, parameter):
574     global log
575
576     if var.playlist.length() > 0:
577         bot.stop()
578         bot.launch_music()
579         bot.async_download_next()
580     else:
581         bot.send_msg(constants.strings('queue_empty'), text)
582
583
584 def cmd_remove(bot, user, text, command, parameter):
585     global log
586
587     # Allow to remove specific music into the queue with a number
588     if parameter and parameter.isdigit() and int(parameter) > 0 \
589             and int(parameter) <= var.playlist.length():
590
591         index = int(parameter) - 1
592
593         removed = None
594         if index == var.playlist.current_index:
595             removed = var.playlist.remove(index)
596
597             if index < len(var.playlist):
598                 if not bot.is_pause:
599                     bot.interrupt_playing()
600                     var.playlist.current_index -= 1
601                     # then the bot will move to next item
602
603             else: # if item deleted is the last item of the queue
604                 var.playlist.current_index -= 1
605                 if not bot.is_pause:
606                     bot.interrupt_playing()
607         else:
608             removed = var.playlist.remove(index)
609
610         # the Title isn't here if the music wasn't downloaded
611         bot.send_msg(constants.strings('removing_item',
612             item=removed['title'] if 'title' in removed else removed['url']), text)
613
614         log.info("cmd: delete from playlist: " + str(removed['path'] if 'path' in removed else removed['url']))
615     else:
616         bot.send_msg(constants.strings('bad_parameter', command=command))
617
618
619 def cmd_list_file(bot, user, text, command, parameter):
620     global log
621
622     folder_path = var.music_folder
623
624     files = util.get_recursive_file_list_sorted(folder_path)
625     msgs = [ "<br> <b>Files available:</b>" if not parameter else "<br> <b>Matched files:</b>" ]
626     try:
627         count = 0
628         for index, file in enumerate(files):
629             if parameter:
630                 match = re.search(parameter, file)
631                 if not match:
632                     continue
633
634             count += 1
635             msgs.append("<b>{:0>3d}</b> - {:s}".format(index, file))
636
637         if count != 0:
638             send_multi_lines(bot, msgs, text)
639         else:
640             bot.send_msg(constants.strings('no_file'), text)
641
642     except re.error as e:
643         msg = constants.strings('wrong_pattern', error=str(e))
644         bot.send_msg(msg, text)
645
646
647 def cmd_queue(bot, user, text, command, parameter):
648     global log
649
650     if len(var.playlist) == 0:
651         msg = constants.strings('queue_empty')
652         bot.send_msg(msg, text)
653     else:
654         msgs = [ constants.strings('queue_contents')]
655         for i, value in enumerate(var.playlist):
656             newline = ''
657             if i == var.playlist.current_index:
658                 newline = '<b>{} ▶ ({}) {} ◀</b>'.format(i + 1, value['type'],
659                                                            value['title'] if 'title' in value else value['url'])
660             else:
661                 newline = '<b>{}</b> ({}) {}'.format(i + 1, value['type'],
662                                                      value['title'] if 'title' in value else value['url'])
663
664             msgs.append(newline)
665
666         send_multi_lines(bot, msgs, text)
667
668 def cmd_random(bot, user, text, command, parameter):
669     global log
670
671     bot.interrupt_playing()
672     var.playlist.randomize()
673
674 def cmd_repeat(bot, user, text, command, parameter):
675     global log
676
677     repeat = 1
678     if parameter and parameter.isdigit():
679         repeat = int(parameter)
680
681     music = var.playlist.current_item()
682     for _ in range(repeat):
683         var.playlist.insert(
684             var.playlist.current_index + 1,
685             music
686         )
687         log.info("bot: add to playlist: " + util.format_debug_song_string(music))
688
689     bot.send_msg(constants.strings("repeat", song=util.format_song_string(music), n=str(repeat)), text)
690
691 def cmd_mode(bot, user, text, command, parameter):
692     global log
693
694     if not parameter:
695         bot.send_msg(constants.strings("current_mode", mode=var.playlist.mode), text)
696         return
697     if not parameter in ["one-shot", "repeat", "random"]:
698         bot.send_msg(constants.strings('unknown_mode', mode=parameter), text)
699     else:
700         var.db.set('playlist', 'playback_mode', parameter)
701         var.playlist.set_mode(parameter)
702         log.info("command: playback mode changed to %s." % parameter)
703         bot.send_msg(constants.strings("change_mode", mode=var.playlist.mode,
704                                        user=bot.mumble.users[text.actor]['name']), text)
705         if parameter == "random":
706             bot.stop()
707             var.playlist.randomize()
708             bot.launch_music(0)
709
710
711 def cmd_drop_database(bot, user, text, command, parameter):
712     global log
713
714     var.db.drop_table()
715     var.db = Database(var.dbfile)
716     bot.send_msg(constants.strings('database_dropped'), text)