import json import logging import os from datetime import datetime from pathlib import Path from threading import Thread import pandas as pd from flask import Flask, Response, cli, render_template, request from TwitchChannelPointsMiner.classes.Settings import Settings cli.show_server_banner = lambda *_: None logger = logging.getLogger(__name__) def streamers_available(): path = Settings.analytics_path return [ f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and f.endswith(".json") ] def aggregate(df, freq="30Min"): df_base_events = df[(df.z == "Watch") | (df.z == "Claim")] df_other_events = df[(df.z != "Watch") & (df.z != "Claim")] be = df_base_events.groupby([pd.Grouper(freq=freq, key="datetime"), "z"]).max() be = be.reset_index() oe = df_other_events.groupby([pd.Grouper(freq=freq, key="datetime"), "z"]).max() oe = oe.reset_index() result = pd.concat([be, oe]) return result def filter_datas(start_date, end_date, datas): # Note: https://stackoverflow.com/questions/4676195/why-do-i-need-to-multiply-unix-timestamps-by-1000-in-javascript start_date = ( datetime.strptime(start_date, "%Y-%m-%d") if start_date is not None else 0 ) end_date = ( datetime.strptime(end_date, "%Y-%m-%d") if end_date is not None else datetime.now() ) if "series" in datas: df = pd.DataFrame(datas["series"]) df["datetime"] = pd.to_datetime(df.x // 1000, unit="s") df = df[(df["datetime"] > start_date) & (df["datetime"] <= end_date)] df = aggregate(df) datas["series"] = ( df.drop(columns="datetime") .sort_values(by="x", ascending=True) .to_dict("records") ) else: datas["series"] = [] if "annotations" in datas: df = pd.DataFrame(datas["annotations"]) df["datetime"] = pd.to_datetime(df.x // 1000, unit="s") df = df[(df["datetime"] > start_date) & (df["datetime"] <= end_date)] datas["annotations"] = ( df.drop(columns="datetime") .sort_values(by="x", ascending=True) .to_dict("records") ) else: datas["annotations"] = [] return datas def read_json(streamer, return_response=True): start_date = request.args.get("startDate", type=str) end_date = request.args.get("endDate", type=str) path = Settings.analytics_path streamer = streamer if streamer.endswith(".json") else f"{streamer}.json" if return_response is True: return Response( json.dumps( filter_datas( start_date, end_date, json.load(open(os.path.join(path, streamer))) ) ) if streamer in streamers_available() else [], status=200, mimetype="application/json", ) return ( json.load(open(os.path.join(path, streamer))) if streamer in streamers_available() else [] ) def get_challenge_points(streamer): datas = read_json(streamer, return_response=False) if datas != {}: return datas["series"][-1]["y"] return 0 def json_all(): return Response( json.dumps( [ { "name": streamer.strip(".json"), "data": read_json(streamer, return_response=False), } for streamer in streamers_available() ] ), status=200, mimetype="application/json", ) def index(refresh=5, days_ago=7): return render_template( "charts.html", refresh=(refresh * 60 * 1000), daysAgo=days_ago, ) def streamers(): return Response( json.dumps( [ {"name": s, "points": get_challenge_points(s)} for s in sorted(streamers_available()) ] ), status=200, mimetype="application/json", ) class AnalyticsServer(Thread): def __init__( self, host: str = "127.0.0.1", port: int = 5000, refresh: int = 5, days_ago: int = 7, ): super(AnalyticsServer, self).__init__() self.host = host self.port = port self.refresh = refresh self.days_ago = days_ago self.app = Flask( __name__, template_folder=os.path.join(Path().absolute(), "assets"), static_folder=os.path.join(Path().absolute(), "assets"), ) self.app.add_url_rule( "/", "index", index, defaults={"refresh": refresh, "days_ago": days_ago}, methods=["GET"], ) self.app.add_url_rule("/streamers", "streamers", streamers, methods=["GET"]) self.app.add_url_rule( "/json/", "json", read_json, methods=["GET"] ) self.app.add_url_rule("/json_all", "json_all", json_all, methods=["GET"]) def run(self): logger.info( f"Analytics running on http://{self.host}:{self.port}/", extra={"emoji": ":globe_with_meridians:"}, ) self.app.run(host=self.host, port=self.port, threaded=True, debug=False)