From e3481a4a35091b32b6fbee80c1c9ba2b6d7b50d6 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Sun, 22 Dec 2024 21:53:57 +0100 Subject: Rework of halfnarp and fullnarp into a self contained repository. Still WIP --- halfnarp2.py | 289 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100755 halfnarp2.py (limited to 'halfnarp2.py') diff --git a/halfnarp2.py b/halfnarp2.py new file mode 100755 index 0000000..ebd6f6b --- /dev/null +++ b/halfnarp2.py @@ -0,0 +1,289 @@ +#!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 html_sanitizer import Sanitizer +from hashlib import sha256 + +app = Flask(__name__) +app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql://halfnarp@localhost:5432/halfnarp" +app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False +app.config["SERVER_NAME"] = "halfnarp.events.ccc.de" +app.config["SECRET_KEY"] = "" +app.jinja_env.trim_blocks = True +app.jinja_env.lstrip_blocks = True +CORS(app) + +db = SQLAlchemy(app) + + +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(): + print(request.json) + 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(submission["abstract"], enable_attributes=False) + abstract_clean_html = Sanitizer().sanitize(abstract_html) + slot = submission["slot"] + + return { + "title": submission.get("title", "!!! NO TITLE !!!"), + "duration": 60 * submission.get("duration", 40), + "event_id": submission["code"], + "language": submission.get("content_locale", "de"), + "track_id": submission["track_id"], + "speaker_names": ", ".join( + [ + speaker.get("name", "unnamed") + for speaker in submission.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 = submission["slot"] + + speaker_info = [] + for speaker in submission.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/submissions/" + 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": submission.get("title", "!!! NO TITLE !!!"), + "duration": 60 * submission.get("duration", 40), + "event_id": submission["code"], + "language": submission.get("content_locale", "de"), + "track_id": submission["track_id"], + "speaker_names": ", ".join( + [ + speaker.get("name", "unnamed") + for speaker in submission.get("speakers", {}) + ] + ), + "abstract": abstract_clean_html, + "room_id": slot.get("room_id", "room_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(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"] + ) + + 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")), + ) -- cgit v1.2.3