diff --git a/.gitignore b/.gitignore index 6bcf1fe..b2f3174 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -src/__pycache__ +__pycache__ *.swp -src/.token -src/server.conf +.token +server.conf .*.log diff --git a/main.py b/main.py new file mode 100755 index 0000000..ff42003 --- /dev/null +++ b/main.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from twitch import TwitchClient + +import json +import cherrypy +from cherrypy.process.plugins import Daemonizer + +ver = '1.03' + + +repl = {'"':'"', '&':'&', '<':'<', '>':'>' } + +class FleastServer(object): + def __init__(self): + try: + with open('.token', 'r') as reader: + self.twitch_token = reader.read().strip() + with open('./web/fl.html', 'r') as reader: + self.index_page = reader.read() + with open('./web/fl_template_main.html', 'r') as reader: + self.templ_main = reader.read() + with open('./web/fl_template_stream.html', 'r') as reader: + self.templ_stream = reader.read() + with open('./web/fl_template_lang.html', 'r') as reader: + self.templ_lang = reader.read().splitlines() + self.client = TwitchClient(self.twitch_token, freq = 1) + except: + print("Cannot read token for twitch app or templates, abort.") + exit(1) + + def set_templ_lang(self, lang): + templ = '' + end = False + for l in self.templ_lang: + if not end and 'option value="{}"'.format(lang) in l: + templ += l.format('selected') + '\n' + end = True + continue + templ += l.format(' ') + '\n' + return templ.rstrip() + + def to_html(self, text): + #return ''.join(repl.get(s,s) for s in text) + for i in repl: + text = text.replace(i, repl[i]) + return text + + + @cherrypy.expose + def index(self, game=None, lang=None): + return self.fleast(game, lang) + + @cherrypy.expose + def fleast(self, game=None, lang=None): + if game is None or game == '': + return self.index_page.format(_version_ = ver, _opt_langs_ = self.set_templ_lang('ru')) + + game = game.rstrip() + cherrypy.log('Getting game:"%s" language:%s' % (game, lang)) + data = self.client.get_live_streams(game, lang) + + if data is None: + return 'Internal Error
Tell me more at https://twitter.com/alexvanin' + + if data['_total'] == 0: + return self.templ_main.format( _stream_num_ = data['_total'], _game_name_ = game, \ + _opt_langs_ = self.set_templ_lang(lang), _stream_list_ = '', \ + _version_ = ver) + + cherrypy.log('Found %d streams' % data['_total']) + + streams = sorted(data['streams'], key=lambda k: k['viewers']) + result_str = '' + for s in streams: + result_str += self.templ_stream.format(s['channel']['url'], s['preview']['medium'], self.to_html(s['channel']['status']), \ + s['channel']['display_name'], s['viewers']) +'\n' + + return self.templ_main.format(_stream_num_ = data['_total'], _game_name_ = game, \ + _opt_langs_ = self.set_templ_lang(lang), _stream_list_ = result_str,\ + _version_ = ver) + + +def main(): + server = FleastServer() + d = Daemonizer(cherrypy.engine).subscribe() + cherrypy.quickstart(server, '/', './server.conf') + + +if __name__ == '__main__': + main() + diff --git a/src/main.py b/src/main.py deleted file mode 100755 index 563954c..0000000 --- a/src/main.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from twitch import TwitchClient - -import json -import cherrypy -from cherrypy.process.plugins import Daemonizer - -ver = '1.03' - - -repl = {'"':'"', '&':'&', '<':'<', '>':'>' } - -class FleastServer(object): - def __init__(self): - try: - with open('.token', 'r') as reader: - self.twitch_token = reader.read().strip() - with open('./web/fl.html', 'r') as reader: - self.index_page = reader.read() - with open('./web/fl_template_main.html', 'r') as reader: - self.templ_main = reader.read() - with open('./web/fl_template_stream.html', 'r') as reader: - self.templ_stream = reader.read() - with open('./web/fl_template_lang.html', 'r') as reader: - self.templ_lang = reader.read().splitlines() - self.client = TwitchClient(self.twitch_token, freq = 1) - except: - print("Cannot read token for twitch app or templates, abort.") - exit(1) - - def set_templ_lang(self, lang): - templ = '' - end = False - for l in self.templ_lang: - if not end and 'option value="{}"'.format(lang) in l: - templ += l.format('selected') + '\n' - end = True - continue - templ += l.format(' ') + '\n' - return templ.rstrip() - - def to_html(self, text): - #return ''.join(repl.get(s,s) for s in text) - for i in repl: - text = text.replace(i, repl[i]) - return text - - - @cherrypy.expose - def index(self, game=None, lang=None): - return self.fleast(game, lang) - - @cherrypy.expose - def fleast(self, game=None, lang=None): - if game is None or game == '': - return self.index_page.format(_version_ = ver, _opt_langs_ = self.set_templ_lang('ru')) - - game = game.rstrip() - cherrypy.log('Getting game:"%s" language:%s' % (game, lang)) - data = self.client.get_live_streams(game, lang) - - if data is None: - return 'Internal Error
Tell me more at https://twitter.com/alexvanin' - - if data['_total'] == 0: - return self.templ_main.format( _stream_num_ = data['_total'], _game_name_ = game, \ - _opt_langs_ = self.set_templ_lang(lang), _stream_list_ = '', \ - _version_ = ver) - - cherrypy.log('Found %d streams' % data['_total']) - - streams = sorted(data['streams'], key=lambda k: k['viewers']) - result_str = '' - for s in streams: - result_str += self.templ_stream.format(s['channel']['url'], s['preview']['medium'], self.to_html(s['channel']['status']), \ - s['channel']['display_name'], s['viewers']) +'\n' - - return self.templ_main.format(_stream_num_ = data['_total'], _game_name_ = game, \ - _opt_langs_ = self.set_templ_lang(lang), _stream_list_ = result_str,\ - _version_ = ver) - - -def main(): - server = FleastServer() - d = Daemonizer(cherrypy.engine).subscribe() - cherrypy.quickstart(server, '/', './server.conf') - - -if __name__ == '__main__': - main() diff --git a/src/twitch.py b/src/twitch.py deleted file mode 100644 index d134426..0000000 --- a/src/twitch.py +++ /dev/null @@ -1,77 +0,0 @@ -import requests -import time -import threading - -import cherrypy - -class TwitchClient: - def __init__(self, token, freq=2): - self.token = token - self.lock = threading.Lock() - - self.header_v5 = {'Client-ID': self.token, 'Accept': 'application/vnd.twitchtv.v5+json'} - self.header_v6 = {'Client-ID': self.token} - self.urlbase_v5 = 'https://api.twitch.tv/kraken' - self.urlbase_v6 = 'https://api.twitch.tv/helix' - - self.last_q = time.time() - self.delay = 1/freq - - - def do_q(self, base, header): - self.lock.acquire() - try: - cherrypy.log('Request: %s' % base) - delta = time.time() - self.last_q - if delta < self.delay: - time.sleep(delta) - r = requests.get(base, headers=header).json() - self.last_q = time.time() - cherrypy.log('Request: OK') - except: - cherrypy.log('Request: FAIL') - r = None - finally: - self.lock.release() - return r - - def get_base(self, ver): - if ver == 'v5': return (self.header_v5, self.urlbase_v5) - elif ver == 'v6': return (self.header_v6, self.urlbase_v6) - else: return None - - # - # - # - - def raw_query_v6(self, q): - header, base = self.get_base('v6') - return self.do_q(base+q, header) - - def raw_query_v5(self, q): - header, base = self.get_base('v5') - return self.do_q(base+q, header) - - # Returns (ID, GAMENAME) or None - def get_game_id(self, name): - header, base = self.get_base('v5') - r = self.do_q('%s/search/games?query=%s' % (base, name), header) - if r and r.get('games'): return (r['games'][0]['_id'], r['games'][0]['name']) - return None - - def get_live_streams(self, name, lang): - header, base = self.get_base('v5') - data = self.do_q('%s/streams/?game=%s&language=%s&limit=%s&stream_type=live' % (base, name, lang, 100), header) - if data is None: return None - total = data['_total']; streams = len(data['streams']) - - while total > streams: - r = self.do_q('%s/streams/?game=%s&language=%s&limit=%s&stream_type=live&offset=%s' % (base, name, lang, 100, streams), header) - if r is None: return None - data['streams'].extend(r['streams']) - total = r['_total']; streams = len(data['streams']) - - # Tweak for getting only live sterams - data['streams'] = [x for x in data['streams'] if x['stream_type'] == 'live'] - data['_total'] = len(data['streams']) - return data - - diff --git a/twitch.py b/twitch.py new file mode 100644 index 0000000..8164aa1 --- /dev/null +++ b/twitch.py @@ -0,0 +1,76 @@ +import requests +import time +import threading + +import cherrypy + +class TwitchClient: + def __init__(self, token, freq=2): + self.token = token + self.lock = threading.Lock() + + self.header_v5 = {'Client-ID': self.token, 'Accept': 'application/vnd.twitchtv.v5+json'} + self.header_v6 = {'Client-ID': self.token} + self.urlbase_v5 = 'https://api.twitch.tv/kraken' + self.urlbase_v6 = 'https://api.twitch.tv/helix' + + self.last_q = time.time() + self.delay = 1/freq + + + def do_q(self, base, header): + self.lock.acquire() + try: + cherrypy.log('Request: %s' % base) + delta = time.time() - self.last_q + if delta < self.delay: + time.sleep(delta) + r = requests.get(base, headers=header).json() + self.last_q = time.time() + cherrypy.log('Request: OK') + except: + cherrypy.log('Request: FAIL') + r = None + finally: + self.lock.release() + return r + + def get_base(self, ver): + if ver == 'v5': return (self.header_v5, self.urlbase_v5) + elif ver == 'v6': return (self.header_v6, self.urlbase_v6) + else: return None + + # - # - # + + def raw_query_v6(self, q): + header, base = self.get_base('v6') + return self.do_q(base+q, header) + + def raw_query_v5(self, q): + header, base = self.get_base('v5') + return self.do_q(base+q, header) + + # Returns (ID, GAMENAME) or None + def get_game_id(self, name): + header, base = self.get_base('v5') + r = self.do_q('%s/search/games?query=%s' % (base, name), header) + if r and r.get('games'): return (r['games'][0]['_id'], r['games'][0]['name']) + return None + + def get_live_streams(self, name, lang): + header, base = self.get_base('v5') + data = self.do_q('%s/streams/?game=%s&language=%s&limit=%s&stream_type=live' % (base, name, lang, 100), header) + if data is None: return None + total = data['_total']; streams = len(data['streams']) + + while total > streams: + r = self.do_q('%s/streams/?game=%s&language=%s&limit=%s&stream_type=live&offset=%s' % (base, name, lang, 100, streams), header) + if r is None: return None + data['streams'].extend(r['streams']) + total = r['_total']; streams = len(data['streams']) + + # Tweak for getting only live sterams + data['streams'] = [x for x in data['streams'] if x['stream_type'] == 'live'] + data['_total'] = len(data['streams']) + return data + diff --git a/src/web/favicon.ico b/web/favicon.ico similarity index 100% rename from src/web/favicon.ico rename to web/favicon.ico diff --git a/src/web/fl.html b/web/fl.html similarity index 100% rename from src/web/fl.html rename to web/fl.html diff --git a/src/web/fl_template_lang.html b/web/fl_template_lang.html similarity index 100% rename from src/web/fl_template_lang.html rename to web/fl_template_lang.html diff --git a/src/web/fl_template_main.html b/web/fl_template_main.html similarity index 100% rename from src/web/fl_template_main.html rename to web/fl_template_main.html diff --git a/src/web/fl_template_stream.html b/web/fl_template_stream.html similarity index 100% rename from src/web/fl_template_stream.html rename to web/fl_template_stream.html diff --git a/src/web/style.css b/web/style.css similarity index 100% rename from src/web/style.css rename to web/style.css