summaryrefslogtreecommitdiff
path: root/halfnarp2.py
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2024-12-22 21:53:57 +0100
committererdgeist <erdgeist@erdgeist.org>2024-12-22 21:53:57 +0100
commite3481a4a35091b32b6fbee80c1c9ba2b6d7b50d6 (patch)
tree58f90b32cbd89599acfaab07377cc0447f1190c1 /halfnarp2.py
Rework of halfnarp and fullnarp into a self contained repository. Still WIP
Diffstat (limited to 'halfnarp2.py')
-rwxr-xr-xhalfnarp2.py289
1 files changed, 289 insertions, 0 deletions
diff --git a/halfnarp2.py b/halfnarp2.py
new file mode 100755
index 0000000..ebd6f6b
--- /dev/null
+++ b/halfnarp2.py
@@ -0,0 +1,289 @@
1#!venv/bin/python
2
3from flask import Flask, render_template, jsonify, request, abort, send_file, url_for
4from flask_sqlalchemy import SQLAlchemy
5from flask_cors import CORS
6from lxml import etree
7from argparse import ArgumentParser
8import requests
9import json
10import uuid
11import markdown
12from html_sanitizer import Sanitizer
13from hashlib import sha256
14
15app = Flask(__name__)
16app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql://halfnarp@localhost:5432/halfnarp"
17app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
18app.config["SERVER_NAME"] = "halfnarp.events.ccc.de"
19app.config["SECRET_KEY"] = "<YOUR SERVER SECRET HERE>"
20app.jinja_env.trim_blocks = True
21app.jinja_env.lstrip_blocks = True
22CORS(app)
23
24db = SQLAlchemy(app)
25
26
27class TalkPreference(db.Model):
28 """A preference of halfnarp frontend. An array of strings"""
29
30 uid = db.Column(db.String, primary_key=True)
31 public_uid = db.Column(db.String, index=True)
32 talk_ids = db.Column(db.String)
33
34
35@app.route("/")
36def root():
37 return render_template("index.html")
38
39
40@app.route("/-/talkpreferences", methods=["GET"])
41def sessions():
42 return send_file("var/talks_local", mimetype="application/json")
43
44
45@app.route("/-/talkpreferences/<uid>", methods=["GET"])
46def get_own_preferences(uid):
47 pref = db.session.get(TalkPreference, uid)
48 if pref == None:
49 abort(404)
50
51 return jsonify(
52 {
53 "hashed_uid": pref.public_uid,
54 "public_url": url_for(
55 "get_preferences",
56 public_uid=public_uid,
57 _external=True,
58 _scheme="https",
59 ),
60 "talk_ids": json.loads(pref.talk_ids),
61 "uid": pref.uid,
62 }
63 )
64
65
66@app.route("/-/talkpreferences/", methods=["POST"])
67def store_preferences():
68 print(request.json)
69 try:
70 content = request.json
71 talk_ids = content["talk_ids"]
72 except:
73 abort(400)
74
75 if not all(isinstance(elem, str) for elem in talk_ids):
76 abort(400)
77
78 uid = str(uuid.uuid4())
79 public_uid = str(sha256(uid.encode("utf-8")).hexdigest())
80
81 db.session.add(
82 TalkPreference(uid=uid, public_uid=public_uid, talk_ids=json.dumps(talk_ids))
83 )
84 db.session.commit()
85 return jsonify(
86 {
87 "uid": uid,
88 "hashed_uid": str(public_uid),
89 "public_url": url_for(
90 "get_preferences",
91 public_uid=public_uid,
92 _external=True,
93 _scheme="https",
94 ),
95 "update_url": url_for(
96 "update_preferences", uid=uid, _external=True, _scheme="https"
97 ),
98 }
99 )
100
101
102@app.route("/-/talkpreferences/<uid>", methods=["POST", "PUT"])
103def update_preferences(uid):
104 pref = db.session.get(TalkPreference, uid)
105 if pref == None:
106 abort(404)
107
108 content = request.json
109 pref.talk_ids = json.dumps(content["talk_ids"])
110 db.session.commit()
111
112 return jsonify({"uid": pref.uid, "hashed_uid": pref.public_uid})
113
114
115@app.route("/-/talkpreferences/public/<public_uid>", methods=["GET"])
116def get_preferences(public_uid):
117 pref = (
118 db.session.query(TalkPreference)
119 .filter(TalkPreference.public_uid == public_uid)
120 .first()
121 )
122 if pref == None:
123 abort(404)
124
125 return jsonify({"hash": pref.public_uid, "talk_ids": json.loads(pref.talk_ids)})
126
127
128def filter_keys_halfnarp(session):
129 abstract_html = markdown.markdown(submission["abstract"], enable_attributes=False)
130 abstract_clean_html = Sanitizer().sanitize(abstract_html)
131 slot = submission["slot"]
132
133 return {
134 "title": submission.get("title", "!!! NO TITLE !!!"),
135 "duration": 60 * submission.get("duration", 40),
136 "event_id": submission["code"],
137 "language": submission.get("content_locale", "de"),
138 "track_id": submission["track_id"],
139 "speaker_names": ", ".join(
140 [
141 speaker.get("name", "unnamed")
142 for speaker in submission.get("speakers", {})
143 ]
144 ),
145 "abstract": abstract_clean_html,
146 "room_id": slot.get("room_id", "room_unknown"),
147 "start_time": slot.get("start", "1970-01-01"),
148 }
149
150
151def filter_keys_fullnarp(session, speakers):
152 abstract_html = markdown.markdown(session["abstract"], enable_attributes=False)
153 abstract_clean_html = Sanitizer().sanitize(abstract_html)
154 slot = submission["slot"]
155
156 speaker_info = []
157 for speaker in submission.get("speakers", {}):
158 speaker_info.append(speakers[speaker["code"]])
159 # if len(speakers[speaker['code']]['availabilities']) == 0:
160 # 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'] )
161
162 """This fixes availabilites ranging to or from exactly midnight to the more likely start of availibility at 8am and end of availibility at 3am"""
163
164 for avail in speakers[speaker["code"]]["availabilities"]:
165 start_new = datetime.fromisoformat(avail["start"])
166 end_new = datetime.fromisoformat(avail["end"])
167
168 start = datetime.fromisoformat(avail["start"])
169 end = datetime.fromisoformat(avail["end"])
170 if start.time() == time(0, 0):
171 start_new = start + timedelta(hours=10)
172 if end.time() == time(0, 0):
173 end_new = end + timedelta(hours=3)
174
175 if start != start_new or end != end_new:
176 print(
177 "Fixing "
178 + str(start)
179 + " - "
180 + str(end)
181 + " to "
182 + str(start_new)
183 + " - "
184 + str(end_new)
185 )
186 avail["start"] = str(start_new)
187 avail["end"] = str(end_new)
188
189 return {
190 "title": submission.get("title", "!!! NO TITLE !!!"),
191 "duration": 60 * submission.get("duration", 40),
192 "event_id": submission["code"],
193 "language": submission.get("content_locale", "de"),
194 "track_id": submission["track_id"],
195 "speaker_names": ", ".join(
196 [
197 speaker.get("name", "unnamed")
198 for speaker in submission.get("speakers", {})
199 ]
200 ),
201 "abstract": abstract_clean_html,
202 "room_id": slot.get("room_id", "room_unknown"),
203 "start_time": slot.get("start", "1970-01-01"),
204 }
205
206
207def fetch_talks(config):
208 sess = requests.Session()
209
210 response = sess.get(
211 config["pretalx-api-url"] + "/submissions/?format=json&limit=20000",
212 stream=True,
213 headers={"Authorization": "Token " + config["pretalx-token"]},
214 )
215 # with open('dump.txt', mode='wb') as localfile:
216 # localfile.write(response.content)
217 talks_json = json.loads(response.text)
218
219 response = sess.get(
220 config["pretalx-api-url"] + "/speakers/?format=json&limit=20000",
221 stream=True,
222 headers={"Authorization": "Token " + config["pretalx-token"]},
223 )
224 speakers_json = json.loads(response.text)
225 speakers = dict((speaker["code"], speaker) for speaker in speakers_json["results"])
226
227 sessions = [
228 filter_keys(submission)
229 for submission in talks_json["results"]
230 if submission["state"] == "confirmed"
231 and not "non-public" in submission.get("tags", {})
232 ]
233 with open("var/talks_local", mode="w", encoding="utf8") as sessionsfile:
234 json.dump(sessions, sessionsfile)
235
236 sessions = [
237 filter_keys_fullnarp(submission, speakers)
238 for submission in talks_json["results"]
239 if submission["state"] == "confirmed" or submission["state"] == "accepted"
240 ]
241 with open("var/talks_local_fullnarp", mode="w", encoding="utf8") as sessionsfile:
242 json.dump(sessions, sessionsfile)
243
244
245def export_prefs(config):
246 print("[")
247 for preference in TalkPreference.query.all():
248 print(preference.talk_ids + ",")
249 print("[]]")
250
251
252if __name__ == "__main__":
253 parser = ArgumentParser(description="halfnarp2")
254 parser.add_argument(
255 "-i",
256 action="store_true",
257 dest="pretalx_import",
258 default=False,
259 help="import events from pretalx",
260 )
261 parser.add_argument(
262 "-e",
263 action="store_true",
264 dest="fullnarp_export",
265 default=False,
266 help="export preferences to json",
267 )
268 parser.add_argument(
269 "-c", "--config", help="Config file location", default="./config.json"
270 )
271 args = parser.parse_args()
272
273 with open(args.config, mode="r", encoding="utf-8") as json_file:
274 config = json.load(json_file)
275 config["pretalx-api-url"] = (
276 config["pretalx-url"] + "api/events/" + config["pretalx-conference"]
277 )
278
279 with app.app_context():
280 db.create_all()
281 if args.pretalx_import:
282 fetch_talks(config)
283 elif args.fullnarp_export:
284 export_prefs(config)
285 else:
286 app.run(
287 host=config.get("host", "127.0.0.1"),
288 port=int(config.get("port", "8080")),
289 )