#!venv/bin/python from flask import Flask, render_template, jsonify, request, abort, send_file, url_for from flask_sqlalchemy import SQLAlchemy from flask_cors import CORS from lxml import etree from argparse import ArgumentParser import requests import json import uuid import markdown from datetime import datetime, time, timedelta from html_sanitizer import Sanitizer from hashlib import sha256 app = Flask(__name__) db = SQLAlchemy() class TalkPreference(db.Model): """A preference of halfnarp frontend. An array of strings""" uid = db.Column(db.String, primary_key=True) public_uid = db.Column(db.String, index=True) talk_ids = db.Column(db.String) """ @app.route("/") def root(): return render_template("index.html") """ @app.route("/-/talkpreferences", methods=["GET"]) def sessions(): return send_file("var/talks_local", mimetype="application/json") @app.route("/-/talkpreferences/", methods=["GET"]) def get_own_preferences(uid): pref = db.session.get(TalkPreference, uid) if pref == None: abort(404) return jsonify( { "hashed_uid": pref.public_uid, "public_url": url_for( "get_preferences", public_uid=public_uid, _external=True, _scheme="https", ), "talk_ids": json.loads(pref.talk_ids), "uid": pref.uid, } ) @app.route("/-/talkpreferences/", methods=["POST"]) def store_preferences(): try: content = request.json talk_ids = content["talk_ids"] except: abort(400) if not all(isinstance(elem, str) for elem in talk_ids): abort(400) uid = str(uuid.uuid4()) public_uid = str(sha256(uid.encode("utf-8")).hexdigest()) db.session.add( TalkPreference(uid=uid, public_uid=public_uid, talk_ids=json.dumps(talk_ids)) ) db.session.commit() return jsonify( { "uid": uid, "hashed_uid": str(public_uid), "public_url": url_for( "get_preferences", public_uid=public_uid, _external=True, _scheme="https", ), "update_url": url_for( "update_preferences", uid=uid, _external=True, _scheme="https" ), } ) @app.route("/-/talkpreferences/", methods=["POST", "PUT"]) def update_preferences(uid): pref = db.session.get(TalkPreference, uid) if pref == None: abort(404) content = request.json pref.talk_ids = json.dumps(content["talk_ids"]) db.session.commit() return jsonify({"uid": pref.uid, "hashed_uid": pref.public_uid}) @app.route("/-/talkpreferences/public/", methods=["GET"]) def get_preferences(public_uid): pref = ( db.session.query(TalkPreference) .filter(TalkPreference.public_uid == public_uid) .first() ) if pref == None: abort(404) return jsonify({"hash": pref.public_uid, "talk_ids": json.loads(pref.talk_ids)}) def filter_keys_halfnarp(session): abstract_html = markdown.markdown(session["abstract"], enable_attributes=False) abstract_clean_html = Sanitizer().sanitize(abstract_html) slot = session["slot"] return { "title": session.get("title", "!!! NO TITLE !!!"), "duration": 60 * session.get("duration", 40), "event_id": session["code"], "language": session.get("content_locale", "de"), "track_id": session["track_id"], "speaker_names": ", ".join( [speaker.get("name", "unnamed") for speaker in session.get("speakers", {})] ), "abstract": abstract_clean_html, "room_id": slot.get("room_id", "room_unknown"), "start_time": slot.get("start", "1970-01-01"), } def filter_keys_fullnarp(session, speakers): abstract_html = markdown.markdown(session["abstract"], enable_attributes=False) abstract_clean_html = Sanitizer().sanitize(abstract_html) slot = session["slot"] speaker_info = [] for speaker in session.get("speakers", {}): speaker_info.append(speakers[speaker["code"]]) # if len(speakers[speaker['code']]['availabilities']) == 0: # print ( "Track " + str(submission['track_id']) + ": Speaker " + speaker.get('name', 'unname') + " on session: " + session.get('title', '!!! NO TITLE !!!') + " without availability. https://cfp.cccv.de/orga/event/38c3/session/" + session['code'] ) """This fixes availabilites ranging to or from exactly midnight to the more likely start of availibility at 8am and end of availibility at 3am""" for avail in speakers[speaker["code"]]["availabilities"]: start_new = datetime.fromisoformat(avail["start"]) end_new = datetime.fromisoformat(avail["end"]) start = datetime.fromisoformat(avail["start"]) end = datetime.fromisoformat(avail["end"]) if start.time() == time(0, 0): start_new = start + timedelta(hours=10) if end.time() == time(0, 0): end_new = end + timedelta(hours=3) if start != start_new or end != end_new: print( "Fixing " + str(start) + " - " + str(end) + " to " + str(start_new) + " - " + str(end_new) ) avail["start"] = str(start_new) avail["end"] = str(end_new) return { "title": session.get("title", "!!! NO TITLE !!!"), "duration": 60 * session.get("duration", 40), "event_id": session["code"], "language": session.get("content_locale", "de"), "track_id": session["track_id"], "speakers": speaker_info, "speaker_names": ", ".join( [speaker.get("name", "unnamed") for speaker in session.get("speakers", {})] ), "abstract": abstract_clean_html, "room_id": "room" + str(slot.get("room_id", "_unknown")), "start_time": slot.get("start", "1970-01-01"), } def fetch_talks(config): sess = requests.Session() response = sess.get( config["pretalx-api-url"] + "/submissions/?format=json&limit=20000", stream=True, headers={"Authorization": "Token " + config["pretalx-token"]}, ) # with open('dump.txt', mode='wb') as localfile: # localfile.write(response.content) talks_json = json.loads(response.text) response = sess.get( config["pretalx-api-url"] + "/speakers/?format=json&limit=20000", stream=True, headers={"Authorization": "Token " + config["pretalx-token"]}, ) speakers_json = json.loads(response.text) speakers = dict((speaker["code"], speaker) for speaker in speakers_json["results"]) sessions = [ filter_keys_halfnarp(submission) for submission in talks_json["results"] if submission["state"] == "confirmed" and not "non-public" in submission.get("tags", {}) ] with open("var/talks_local", mode="w", encoding="utf8") as sessionsfile: json.dump(sessions, sessionsfile) sessions = [ filter_keys_fullnarp(submission, speakers) for submission in talks_json["results"] if submission["state"] == "confirmed" or submission["state"] == "accepted" ] with open("var/talks_local_fullnarp", mode="w", encoding="utf8") as sessionsfile: json.dump(sessions, sessionsfile) def export_prefs(config): print("[") for preference in TalkPreference.query.all(): print(preference.talk_ids + ",") print("[]]") if __name__ == "__main__": parser = ArgumentParser(description="halfnarp2") parser.add_argument( "-i", action="store_true", dest="pretalx_import", default=False, help="import events from pretalx", ) parser.add_argument( "-e", action="store_true", dest="fullnarp_export", default=False, help="export preferences to json", ) parser.add_argument( "-c", "--config", help="Config file location", default="./config.json" ) args = parser.parse_args() with open(args.config, mode="r", encoding="utf-8") as json_file: config = json.load(json_file) config["pretalx-api-url"] = ( config["pretalx-url"] + "api/events/" + config["pretalx-conference"] ) app.config["SQLALCHEMY_DATABASE_URI"] = config.get( "database-uri", "sqlite:///test.db" ) app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False app.config["SERVER_NAME"] = config.get("server-name", "localhost") app.config["SECRET_KEY"] = config.get("server-secret", "") if app.config["SECRET_KEY"] == "": print("You must set the server-secret in your config.json") sys.exit(1) app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True CORS() db.init_app(app) with app.app_context(): db.create_all() if args.pretalx_import: fetch_talks(config) elif args.fullnarp_export: export_prefs(config) else: app.run( host=config.get("host", "127.0.0.1"), port=int(config.get("port", "8080")), )