diff options
Diffstat (limited to 'halfnarp2.py')
-rwxr-xr-x | halfnarp2.py | 289 |
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 | |||
3 | from flask import Flask, render_template, jsonify, request, abort, send_file, url_for | ||
4 | from flask_sqlalchemy import SQLAlchemy | ||
5 | from flask_cors import CORS | ||
6 | from lxml import etree | ||
7 | from argparse import ArgumentParser | ||
8 | import requests | ||
9 | import json | ||
10 | import uuid | ||
11 | import markdown | ||
12 | from html_sanitizer import Sanitizer | ||
13 | from hashlib import sha256 | ||
14 | |||
15 | app = Flask(__name__) | ||
16 | app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql://halfnarp@localhost:5432/halfnarp" | ||
17 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False | ||
18 | app.config["SERVER_NAME"] = "halfnarp.events.ccc.de" | ||
19 | app.config["SECRET_KEY"] = "<YOUR SERVER SECRET HERE>" | ||
20 | app.jinja_env.trim_blocks = True | ||
21 | app.jinja_env.lstrip_blocks = True | ||
22 | CORS(app) | ||
23 | |||
24 | db = SQLAlchemy(app) | ||
25 | |||
26 | |||
27 | class 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("/") | ||
36 | def root(): | ||
37 | return render_template("index.html") | ||
38 | |||
39 | |||
40 | @app.route("/-/talkpreferences", methods=["GET"]) | ||
41 | def sessions(): | ||
42 | return send_file("var/talks_local", mimetype="application/json") | ||
43 | |||
44 | |||
45 | @app.route("/-/talkpreferences/<uid>", methods=["GET"]) | ||
46 | def 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"]) | ||
67 | def 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"]) | ||
103 | def 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"]) | ||
116 | def 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 | |||
128 | def 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 | |||
151 | def 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 | |||
207 | def 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 | |||
245 | def export_prefs(config): | ||
246 | print("[") | ||
247 | for preference in TalkPreference.query.all(): | ||
248 | print(preference.talk_ids + ",") | ||
249 | print("[]]") | ||
250 | |||
251 | |||
252 | if __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 | ) | ||