import json import queue from flask import Blueprint, Response, render_template from sqlalchemy import event from CTFd.models import Solves, Challenges, Teams from CTFd.utils.scores import get_standings presenter_blueprint = Blueprint( "presenter", __name__, template_folder="templates" ) listeners = [] def sse_format(data): return f"data: {json.dumps(data)}\n\n" @presenter_blueprint.route("/presenter") def presenter_view(): return render_template("presenter.html") @presenter_blueprint.route("/presenter/events") def presenter_events(): def stream(): q = queue.Queue() listeners.append(q) try: while True: data = q.get() yield sse_format(data) except GeneratorExit: listeners.remove(q) return Response(stream(), mimetype="text/event-stream") def broadcast(event): for q in listeners: q.put(event) @event.listens_for(Solves, "after_insert") def on_solve(mapper, connection, solve): challenge = Challenges.query.get(solve.challenge_id) team = Teams.query.get(solve.team_id) if not challenge or not team: return first_blood = False if Solves.query.filter_by(challenge_id=solve.challenge_id).count() == 1: first_blood = True broadcast( { "type": "solve", "team": team.name, "challenge": challenge.name, "value": challenge.value, "first": first_blood } )