aboutsummaryrefslogtreecommitdiffstats
path: root/server.py
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2026-05-28 17:27:49 -0700
committerPinapelz <yukais@pinapelz.com>2026-05-28 17:27:49 -0700
commitf64d5d678bc69fa429e99eab87abca787f10a9ca (patch)
treed3d949f3e930d8e33b8c0b510a1fa2e146474433 /server.py
init commitmain
Diffstat (limited to 'server.py')
-rw-r--r--server.py94
1 files changed, 94 insertions, 0 deletions
diff --git a/server.py b/server.py
new file mode 100644
index 0000000..1d277ab
--- /dev/null
+++ b/server.py
@@ -0,0 +1,94 @@
+from flask import Flask, request, jsonify, render_template
+from flask_cors import CORS
+import sqlite3
+import uuid
+import time
+import base64
+import os
+from dotenv import load_dotenv
+from captcha import generate_captcha_image
+
+load_dotenv()
+
+app = Flask(__name__)
+CORS(app)
+DB_FILE = os.getenv("DB_FILE", "captchas.db")
+CAPTCHA_DIR = os.getenv("CAPTCHA_DIR", "data/captcha_data")
+FONT_PATH = os.getenv("FONT_PATH", "captcha.ttf")
+EXPIRY_SECONDS = int(os.getenv("CAPTCHA_EXPIRY_SECONDS", "90"))
+
+def init_db():
+ with sqlite3.connect(DB_FILE) as conn:
+ conn.execute('''
+ CREATE TABLE IF NOT EXISTS captchas (
+ token TEXT PRIMARY KEY,
+ answers TEXT,
+ expires_at REAL
+ )
+ ''')
+init_db()
+
+
+@app.route('/')
+def index():
+ return render_template("index.html")
+
+@app.route('/generate', methods=['GET', 'POST'])
+def generate():
+ try:
+ positions, blob = generate_captcha_image(
+ captcha_dir=CAPTCHA_DIR,
+ font_path=FONT_PATH
+ )
+ except Exception as e:
+ return jsonify({"error": str(e)}), 500
+
+ token = str(uuid.uuid4())
+ answers_str = ",".join(map(str, sorted(positions)))
+ expires_at = time.time() + EXPIRY_SECONDS
+ with sqlite3.connect(DB_FILE) as conn:
+ conn.execute('INSERT INTO captchas (token, answers, expires_at) VALUES (?, ?, ?)',
+ (token, answers_str, expires_at))
+
+ b64_img = base64.b64encode(blob).decode('utf-8')
+
+ return jsonify({
+ "token": token,
+ "image": f"data:image/jpeg;base64,{b64_img}",
+ "expires_in": EXPIRY_SECONDS
+ })
+
+@app.route('/verify', methods=['POST'])
+def verify():
+ data = request.json
+ if not data or 'token' not in data or 'answers' not in data:
+ return jsonify({"error": "Missing token or answers"}), 400
+
+ token = data['token']
+ user_answers = data['answers']
+
+ if not isinstance(user_answers, list):
+ return jsonify({"error": "answers must be a list of integers"}), 400
+
+ with sqlite3.connect(DB_FILE) as conn:
+ cursor = conn.cursor()
+ conn.execute('pragma journal_mode=wal')
+ cursor.execute('SELECT answers, expires_at FROM captchas WHERE token = ?', (token,))
+ row = cursor.fetchone()
+ if not row:
+ return jsonify({"error": "Invalid token"}), 404
+ answers_str, expires_at = row
+ cursor.execute('DELETE FROM captchas WHERE token = ?', (token,))
+
+ if time.time() > expires_at:
+ return jsonify({"error": "Captcha expired"}), 400
+ correct_answers = [int(x) for x in answers_str.split(',')]
+ user_answers_sorted = sorted([int(x) for x in user_answers])
+
+ if correct_answers == user_answers_sorted:
+ return jsonify({"success": True})
+ else:
+ return jsonify({"success": False, "error": "Incorrect answers"})
+
+if __name__ == '__main__':
+ app.run(debug=True, port=int(os.getenv("PORT", 5000)))
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage