radioboss-telegram-bot
/
rbbot.py
474 строки · 20.6 Кб
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# Телеграм бот для связи c RadioBoss
5# работает на hyperadio.retroscene.org -> @hyperadio_bot
6
7from __future__ import print_function, unicode_literals
8
9import logging
10import os
11import sys
12import requests
13import xmltodict
14import telegram
15from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
16
17import sqlite3
18import datetime
19from sqlite3 import Error
20import configtb
21
22__version__ = '0.0.1' # не забываем ставиь версию
23
24
25TOKEN = configtb.token # токен нашего бота
26URL = configtb.URL # URL к API телеграма
27#PROXY_URL = 'socks5://163.172.152.192:1080' # здесь можно поставить свой прокси
28
29RB_PASS = configtb.rbPas # пароль к API RadioBoss
30RB_PORT = configtb.rbPort # порт RadioBoss
31ALBUM_ART_PATH = 'INSERT-HERE-PATH-TO-ALBUM-ARTWORK-FILE' # Example 'd:\\MYRADIO\\ALBUMART\\artwork.png' путь до файла-картинки, которую выгружает RadioBoss (Albumart)
32
33######################## текст сообщений бота ##############################
34
35TEXT_HELP = """
36Send me some commands:
37/np — Get info about current playing track
38/like — Add current track to playlist on request
39
40/plus — Raise current track rating
41/minus — Drop current track rating
42
43/dl — Download current track
44/dln — Download track by number in current playlist
45Example: «/dln 1» or «/dln 25 100»
46/art — Download album art for current track
47
48/last — Get info about 5 last played tracks
49/time — Get timetable
50/help — This help
51
52The delay for commands processing can be up to 10 seconds, so be patient, please. Do not spam me!
53Also I can convert some chiptunes, so upload it to me ;)
54"""
55# стартовое сообщение
56TEXT_START = """
57Hi! I am a RadioBoss bot from github.com/nodeus/radioboss-telegram-bot/ (ver {:s})
58{:s}
59""".format(__version__, TEXT_HELP)
60# текст расписания
61TEXT_TIMETABLE = """
62We broadcast 24 hours a day with some special music blocks:
63
6408.00 - 08.30 msk XM tracked music
6509.00 - 10.00 msk BitJam podcast
6610.00 - 10.30 msk ZX Spectrum music
6715.00 - 16.00 msk DEMOVIBES
6817.00 - 17.30 msk ZX Spectrum music
6918.00 - 18.30 msk XM tracked music
7020.00 - 20.30 msk ZX Spectrum music
7121.00 - 23.00 msk Music on your request
72"""
73# шаблон сообщения "сейчас иргает"
74NOWPLAYNG_TPL = """
75github.com/nodeus/radioboss-telegram-bot/
76
77Now playing: {t_casttitle!s}
78
79Duration: {t_duration!s}. Play position: {mins!s} min {secs!s} sec
80
81Next track: {nt_artist!s} — {nt_title!s} ({nt_duration!s})
82Last played: {nt_lastplayed!s}
83
84Current listeners: {t_listeners!s}
85"""
86# шаблон сообщения "запрос трека"
87TRACK_REQUEST_TPL = """
88\U00002764 Thanks {user_name}.
89
90Track «{t_casttitle}» added to playlist on request.
91
92Listen to this track from 21 to 23 msk this evening.
93"""
94# шаблон сообщения "инфо по треку"
95TRACK_INFO_TPL = """
96Time (msk+2): {@LASTPLAYED}
97Track: {@ARTIST} - {@TITLE} - {@ALBUM}
98Playlist item №{playlist_pos}
99"""
100# шаблон сообщения "рейтингование"
101RATE_TEXT_TPL = """
102\U0001F44D Thanks {user_name}.
103You {rate_str} the rating for «{t_casttitle}» track.
104
105Current rating: {tag_rating} \U00002197
106"""
107
108# Enable logging
109logging.basicConfig(format='%(levelname)-8s [%(asctime)s] %(message)s', level=logging.INFO, filename='mylog.log')
110logging.root.addHandler(logging.StreamHandler(sys.stdout))
111logger = logging.getLogger(__name__)
112
113def radio_query(**kwargs):
114"""функция соединения с RadioBoss"""
115# команда к API RadioBoss
116params = dict(kwargs)
117params['pass'] = RB_PASS
118response = requests.get('http://hyperadio.ru:' + RB_PORT + '/', params=params)
119logger.info('Request to radioboss API — %s: %s', kwargs.get('action'), response.status_code)
120return response
121
122def get_username(update, context):
123"""функция получения имени пользователя"""
124user_name = update.message.from_user['username']
125if user_name == None:
126user_name = update.message.from_user['first_name'] + ' ' + update.message.from_user['last_name']
127return user_name
128
129def get_np():
130"""функция отправки запроса на получение информации от RadioBoss — action playbackinfo возвращает словарь nowpl"""
131# команда к API RadioBoss
132
133r = radio_query(action='playbackinfo')
134info = xmltodict.parse(r.content)['Info']
135cur_track = info['CurrentTrack']['TRACK']
136next_track = info['NextTrack']['TRACK']
137prev_track = info['PrevTrack']['TRACK']
138playback = info['Playback']
139streaming = info['Streaming']
140return {
141't_artist': cur_track['@ARTIST'],
142't_title': cur_track['@TITLE'],
143't_album': cur_track['@ALBUM'],
144't_year': cur_track['@YEAR'],
145't_genre': cur_track['@GENRE'],
146't_comment': cur_track['@COMMENT'],
147't_filename': cur_track['@FILENAME'],
148't_duration': cur_track['@DURATION'],
149't_playcount': cur_track['@PLAYCOUNT'],
150't_lastplayed': cur_track['@LASTPLAYED'],
151't_intro': cur_track['@INTRO'],
152't_outro': cur_track['@OUTRO'],
153't_language': cur_track['@LANGUAGE'],
154't_f1': cur_track['@F1'],
155't_f2': cur_track['@F2'],
156't_f3': cur_track['@F3'],
157't_f4': cur_track['@F4'],
158't_f5': cur_track['@F5'],
159't_casttitle': cur_track['@ITEMTITLE'],
160't_listeners': cur_track['@LISTENERS'],
161
162'pt_artist': prev_track['@ARTIST'],
163'pt_title': prev_track['@TITLE'],
164'pt_album': prev_track['@ALBUM'],
165'pt_year': prev_track['@YEAR'],
166'pt_genre': prev_track['@GENRE'],
167'pt_comment': prev_track['@COMMENT'],
168'pt_filename': prev_track['@FILENAME'],
169'pt_duration': prev_track['@DURATION'],
170'pt_playcount': prev_track['@PLAYCOUNT'],
171'pt_lastplayed': prev_track['@LASTPLAYED'],
172'pt_intro': prev_track['@INTRO'],
173'pt_outro': prev_track['@OUTRO'],
174'pt_language': prev_track['@LANGUAGE'],
175'pt_f1': prev_track['@F1'],
176'pt_f2': prev_track['@F2'],
177'pt_f3': prev_track['@F3'],
178'pt_f4': prev_track['@F4'],
179'pt_f5': prev_track['@F5'],
180'pt_casttitle': prev_track['@ITEMTITLE'],
181
182'nt_artist': next_track['@ARTIST'],
183'nt_title': next_track['@TITLE'],
184'nt_album': next_track['@ALBUM'],
185'nt_year': next_track['@YEAR'],
186'nt_genre': next_track['@GENRE'],
187'nt_comment': next_track['@COMMENT'],
188'nt_filename': next_track['@FILENAME'],
189'nt_duration': next_track['@DURATION'],
190'nt_playcount': next_track['@PLAYCOUNT'],
191'nt_lastplayed': next_track['@LASTPLAYED'],
192'nt_intro': next_track['@INTRO'],
193'nt_outro': next_track['@OUTRO'],
194'nt_language': next_track['@LANGUAGE'],
195'nt_f1': next_track['@F1'],
196'nt_f2': next_track['@F2'],
197'nt_f3': next_track['@F3'],
198'nt_f4': next_track['@F4'],
199'nt_f5': next_track['@F5'],
200'nt_casttitle': next_track['@ITEMTITLE'],
201
202'play_pos': playback['@pos'],
203'play_len': playback['@len'],
204'play_state': playback['@state'],
205'playlist_pos': playback['@playlistpos'],
206'play_streams': playback['@streams'],
207'listeners': streaming['@listeners']
208}
209
210def nowplay_string(nowpl):
211"""создаём строку ответа для запроса /np и возвращаем её"""
212secs = int(nowpl['play_pos']) // 1000 # считаем минуты / секунды
213mins = secs // 60
214secs = secs - mins * 60
215return NOWPLAYNG_TPL.format(mins=mins, secs=secs, **nowpl)
216
217def request_song(user_name):
218"""функция добавления трека в плейлист заказа"""
219nowpl = get_np()
220radio_query(action='songrequest', filename=nowpl['t_filename'], message=user_name)
221return None
222
223def start(update, context):
224"""отправляем сообщение приветствия когда команда /start запрошена"""
225update.message.reply_text(TEXT_START)
226user_name = get_username(update, context)
227logger.info('--- %s start interaction with bot ---', user_name)
228
229def helpme(update, context):
230"""отправляем сообщение помощи когда команда /help запрошена"""
231update.message.reply_text(TEXT_HELP)
232user_name = get_username(update, context)
233logger.info('%s request help', user_name)
234
235def dl_track(update, context):
236"""отправляем текущий трек когда команда /dl запрошена"""
237# TODO сделать проверку на уже отправленные файлы в телеграм и отдавать ссылкой на telegram-id файла, если уже были закачаны
238# нужна база отправленных файлов
239nowpl = get_np()
240title = str(nowpl['t_casttitle'])
241filename = nowpl['t_filename']
242context.bot.send_chat_action(chat_id=update.message.chat_id, action=telegram.ChatAction.UPLOAD_DOCUMENT)
243context.bot.send_audio(timeout=120, caption=title, chat_id=update.message.chat_id, audio=open(filename, 'rb'))
244user_name = get_username(update, context)
245logger.info('%s download %s', user_name, filename)
246
247def dl_number(update, context):
248"""отправляем трек из базы по запрошенному номеру с текущего плейлиста"""
249# TODO сделать проверку на уже отправленные файлы в телеграм и отдавать ссылкой на telegram-id файла, если уже были закачаны
250# нужна база отправленных файлов
251user_name = get_username(update, context)
252if not context.args:
253update.message.reply_text('Please, type track numbers after command.\nExample: «/dln 1 2 3»')
254logger.info('%s use /dln command without args.', user_name)
255else:
256for track_number in context.args:
257track_number.strip(", ")
258if track_number.isdigit():
259response = radio_query(action='trackinfo', pos=track_number)
260try:
261trinfo = xmltodict.parse(response.content)
262track = trinfo['Info']['Track']['TRACK']
263file_name = track['@FILENAME']
264track_title = track['@ARTIST'] + ' — ' + track['@TITLE']
265context.bot.send_chat_action(chat_id=update.message.chat_id, action=telegram.ChatAction.UPLOAD_DOCUMENT)
266context.bot.send_document(timeout=120, filename=file_name, caption=track_title,
267chat_id=update.message.chat_id, document=open(file_name, 'rb'))
268
269logger.info('%s download track №%s file: %s', user_name, track_number, file_name)
270except Exception as e:
271logger.info('Wrong track number %s', track_number, '\n', file_name)
272update.message.reply_text('Wrong track number {!s}. Please try again.'.format(track_number))
273else:
274update.message.reply_text(track_number + '%s — isn`t number of track i know...')
275logger.info('%s type wrong track number — %s', user_name, track_number)
276
277def dl_art(update, context):
278"""отправляем обложку трека/альбома когда команда /art запрошена"""
279user_name = get_username(update, context)
280nowpl = get_np()
281if os.path.exists(ALBUM_ART_PATH):
282context.bot.send_photo(chat_id=update.message.chat_id, photo=open(ALBUM_ART_PATH, 'rb'))
283logger.info('%s download %s album art.', user_name, nowpl['t_filename'])
284else:
285update.message.reply_text('Sorry, no album art for this track.')
286logger.info('%s request %s album art, but it is not found.', user_name, nowpl['t_filename'])
287
288def np(update, context):
289"""отправляем nowplay с сервера RadioBoss в телеграм"""
290nowpl = get_np()
291update.message.reply_text(nowplay_string(nowpl))
292user_name = get_username(update, context)
293logger.info('%s request Nowplay for %s', user_name, nowpl['t_casttitle'])
294
295def like(update, context):
296"""отправляем like на сервер radioboss и сообщение в телеграм"""
297user_name = get_username(update, context)
298nowpl = get_np()
299request_song(user_name)
300update.message.reply_text(TRACK_REQUEST_TPL.format(user_name=user_name, **nowpl))
301logger.info('%s liked %s', user_name, nowpl['t_casttitle'])
302
303def timetable(update, context):
304"""отправляем расписание в телеграм"""
305update.message.reply_text(TEXT_TIMETABLE)
306user_name = get_username(update, context)
307logger.info('%s request timetable', user_name)
308
309def last(update, context):
310"""отправляем информацию по 5 последним проигранным трекам"""
311user_name = get_username(update, context)
312nowpl = get_np()
313infopos = int(nowpl['playlist_pos'])
314
315for x in range(0, min(infopos, 5)):
316response = radio_query(action='trackinfo', pos=str(infopos - x))
317trinfo = xmltodict.parse(response.content)
318track_info = trinfo['Info']['Track']['TRACK']
319update.message.reply_text(TRACK_INFO_TPL.format(playlist_pos=infopos - x, **track_info))
320
321logger.info('%s request last played', user_name)
322update.message.reply_text('Nowplay: ' + nowpl['t_casttitle'] + '\nPlaylist item №: ' + nowpl['playlist_pos'])
323
324def error(update, context):
325"""логгируем ошибки и отправляем сообщение в телеграм, если что-то пошло не так"""
326logger.warning('Update "%s" caused error "%s"', update, context.error)
327update.message.reply_text('Ooops, something went wrong. Sorry...')
328
329# соединение с бд sqlite
330def sql_connection():
331try:
332con = sqlite3.connect('rating.db')
333print ("Connection is established")
334logger.info('Connection is established')
335except Error:
336print(Error)
337logger.info(Error)
338finally:
339con.close()
340logger.info('connection is closed')
341
342def sql_insert(con, entities):
343"""добавляем в таблицу id пользователя, имя пользователя, название трека, дату голосования"""
344cursor = con.cursor()
345cursor.execute('INSERT INTO rating (userid, username, ratedtrack, ratedate) VALUES(?,?,?,?)', entities)
346con.commit()
347
348def sql_fetch(con,user_id,rated_track):
349"""возвращаем дату голосования по имени пользователя и названию трека"""
350cursor = con.cursor()
351cursor.execute('SELECT ratedate FROM rating WHERE userid = :uid AND ratedtrack = :rtrack', {'uid': user_id, 'rtrack': rated_track})
352row = cursor.fetchone()
353return row
354
355def change_rating(update, context):
356"""изменение рейтинга трека"""
357try:
358# запрос информации от hyperadio сервера
359nowpl = get_np()
360
361# имя пользователя запроса
362user_name = get_username(update, context)
363
364# id пользователя запроса
365user_id = update.message.from_user['id']
366
367# текущая дата
368rate_date = datetime.date.today()
369
370tagxml = radio_query(action='readtag', fn=nowpl['t_filename'])
371tagdoc = xmltodict.parse(tagxml.content)
372
373file = tagdoc['TagInfo']['File']
374taginfo = {'tag_filename': file['@FN'],
375'tag_duration': file['@Duration'],
376'tag_artist': file['@Artist'],
377'tag_title': file['@Title'],
378'tag_album': file['@Album'],
379'tag_year': file['@Year'],
380'tag_genre': file['@Genre'],
381'tag_comment': file['@Comment'],
382'tag_bpm': file['@BPM'],
383'tag_rating': file['@Rating'],
384'tag_playcount': file['@Playcount'],
385'tag_lastplayed': file['@LastPlayed']}
386
387# полный путь запрошенного файла
388rated_track = taginfo['tag_filename']
389
390# рейтинг запрошенного файла
391rating = int(taginfo['tag_rating'])
392
393if context.direction == 1 and rating == 10:
394update.message.reply_text('This track has the highest rating — 10.')
395return
396elif context.direction == -1 and rating == 0:
397update.message.reply_text('This track has the lowest rating — 0.')
398return
399
400# подключаемся к базе
401con = sqlite3.connect('rating.db')
402sql_connection()
403
404# запрос из базы, если совпадение с текущим id пользователя и именем файла
405# получаем None — нет совпадений, или дату — есть совпадение
406get_date = sql_fetch(con,user_id,rated_track)
407
408if get_date is None:
409rating = max(min(rating + context.direction, 10), 0)
410taginfo['tag_rating'] = str(rating)
411rate_str = 'increased' if context.direction == 1 else 'dropped'
412update.message.reply_text(RATE_TEXT_TPL.format(user_name=user_name, rate_str=rate_str, tag_rating=rating, **nowpl))
413file['@Rating'] = str(rating)
414newxml = xmltodict.unparse(tagdoc)
415radio_query(action='writetag', fn=taginfo['tag_filename'], data=newxml)
416logger.info('%s %s the rating for %s — %s to %s', user_name, rate_str, taginfo['tag_artist'], taginfo['tag_title'], rating)
417
418# данные для записи в базу
419entities = (user_id, user_name, rated_track, rate_date)
420# пишем в базу
421sql_insert(con,entities)
422return
423else:
424update.message.reply_text('Sorry, you can not vote for this track twice...\nRating for «' + taginfo['tag_artist'] + ' – ' + taginfo['tag_title'] + '» has been changed by you at: ' + get_date[0])
425logger.info('%s tried to voting twice for %s – %s.', user_name, taginfo['tag_artist'], taginfo['tag_title'] )
426return
427
428except Exception as e:
429logger.exception(e)
430
431def ratingplus(update, context):
432"""добавление 1 к рейтингу текущего трека"""
433context.direction = 1
434return change_rating(update, context)
435
436def ratingminus(update, context):
437"""вычитание 1 из рейтинга текущего трека"""
438context.direction = -1
439return change_rating(update, context)
440
441def main():
442"""запуск бота"""
443# раскомментировать если используется прокси
444#if PROXY_URL:
445# request_kwargs = {'proxy_url': PROXY_URL}
446#else:
447request_kwargs = {}
448
449updater = Updater(configtb.token, use_context=True)
450dp = updater.dispatcher
451
452# команды, обрабатываемые ботом
453dp.add_handler(CommandHandler("start", start))
454dp.add_handler(CommandHandler("help", helpme))
455dp.add_handler(CommandHandler("like", like))
456dp.add_handler(CommandHandler("plus", ratingplus))
457dp.add_handler(CommandHandler("minus", ratingminus))
458dp.add_handler(CommandHandler("np", np))
459dp.add_handler(CommandHandler("dl", dl_track))
460dp.add_handler(CommandHandler("dln", dl_number, pass_args=True))
461dp.add_handler(CommandHandler("art", dl_art))
462dp.add_handler(CommandHandler("last", last))
463dp.add_handler(CommandHandler("time", timetable))
464
465# логгирование ошибок
466dp.add_error_handler(error)
467
468# старт бота
469updater.start_polling(poll_interval=2.0, timeout=10000)
470
471updater.idle()
472
473if __name__ == '__main__':
474main()
475