commit cda0c4ce37bbcb1c3e12883b4326e40c14853a34
parent 4c788fc68109d3a809ba441a40d192fd493b961f
Author: alex wennerberg <alex@alexwennerberg.com>
Date: Sun, 4 Jan 2026 14:16:53 -0800
fixes
Diffstat:
4 files changed, 35 insertions(+), 17 deletions(-)
diff --git a/3cardblind b/3cardblind
@@ -3,12 +3,24 @@ name="3cardblind"
description="3 Card Blind Flask Web Application"
user="web"
-command="/usr/bin/python3"
-command_args="app.py"
+command="/usr/bin/gunicorn"
+command_args="--bind 0.0.0.0:5000 --workers 4 app:app"
command_background="yes"
command_user="${user}"
directory="/opt/3cardblind"
pidfile="/var/run/${name}.pid"
+output_log="/var/log/3cardblind.log"
+error_log="/var/log/3cardblind.log"
+
+export FLASK_ENV=production
+
+start_pre() {
+ touch "${output_log}"
+ chown "${user}:${user}" "${output_log}"
+ # Initialize the materialized view
+ cd "${directory}"
+ su -s /bin/sh -c "python3 -c 'from app import init_db; init_db()'" "${user}"
+}
depend() {
need net
diff --git a/app.py b/app.py
@@ -221,10 +221,14 @@ left join mtg m3 on deck.card3 = m3.name
group by 1,2
"""
-if __name__ == '__main__':
- # setup "materialized view"
+def init_db():
+ """Initialize the materialized view"""
db = get_db()
db.execute("drop table if exists round_score")
db.execute(create_mview)
db.close()
+
+if __name__ == '__main__':
+ # setup "materialized view"
+ init_db()
app.run(debug=False)
diff --git a/deploy.sh b/deploy.sh
@@ -1,16 +1,16 @@
#!/bin/bash
-REMOTE_HOST="192.168.1.214"
+REMOTE_HOST="pi"
REMOTE_USER="root"
git ls-files > /tmp/git_files_list
-rsync -avz --delete --files-from=/tmp/git_files_list \
+echo "3cb.db" >> /tmp/git_files_list
+rsync -avz --delete --chown=web:web --files-from=/tmp/git_files_list \
./ ${REMOTE_USER}@${REMOTE_HOST}:/opt/3cardblind/
rm /tmp/git_files_list
# Deploy
ssh ${REMOTE_USER}@${REMOTE_HOST} "
mv /opt/3cardblind/3cardblind /etc/init.d/
- chown -R web:web /opt/3cardblind
rc-service 3cardblind restart
"
diff --git a/getdata.py b/getdata.py
@@ -1,23 +1,23 @@
# set GOOG_KEY env variable to a valid api key
# all rounds available at https://sites.google.com/view/3cb-metashape/pairings-results/past-results?authuser=0
-import requests, os, csv, re, io, sqlite3, titlecase, string, openpyxl, urllib, json
+import requests, os, re, io, sqlite3, titlecase, string, openpyxl, json
from datetime import datetime, timedelta
from Levenshtein import distance
from bs4 import BeautifulSoup
con = sqlite3.connect("3cb.db")
-con.set_trace_callback(print)
-allcards= [a[0] for a in con.cursor().execute("select name from mtg").fetchall()]
-def bans_from_db():
- return [a[0] for a in con.cursor().execute("select name from bans").fetchall()]
+def getall(query):
+ return [a[0] for a in con.cursor().execute(query).fetchall()]
+
+allcards=getall("select name from mtg")
def run_rounds():
- file_ids_in_db = [a[0] for a in con.cursor().execute("select fileid from round").fetchall()]
+ file_ids_in_db = getall("select fileid from round")
for n, file in enumerate(get_round_fileids()):
if file in file_ids_in_db:
continue
- if file not in cache and file != "1LGHvTrQz2zhBjz1PhlW61ZYmwRWJyWkEHj-png7ZKdw": # misc file
+ if file != "1LGHvTrQz2zhBjz1PhlW61ZYmwRWJyWkEHj-png7ZKdw": # misc file
print(f"analyzing round {n+1}...")
save_round(n+1, file)
@@ -58,7 +58,7 @@ def get_file_created_date(fileid):
return None
def update_bans():
- banlist_page = requests.get("https://sites.google.com/view/3cb-metashape/dynamic-banlist")
+ banlist_page = requests.get("https://sites.google.com/view/4cb-metashape/dynamic-banlist")
soup = BeautifulSoup(banlist_page.text, 'html.parser')
# Find all list items containing banned cards
@@ -138,6 +138,7 @@ def save_round(n, fileid):
save_sheet(cur, final, n, True)
cur.execute("insert into round values (?, ?, ?);", (n, fileid, date_str));
con.commit()
+
# fixing some data issues
def best_guess(card):
m = 6
@@ -159,7 +160,7 @@ replacement = {
"You Guessed It, Magus of the Moooon": "Magus of the Moon",
"Yera and Oski, Weaver and Guide": "Yera and Oski, Weaver and Guide",
"Yera and Oski, Weaver and Guide": "Arachne, Psionic Weaver",
- "Witch-Blessed Meadow": "Witch's Cottage",
+ "Witch-Blessed Meadow": "Witch's Cottage", # Wrong
"Urborg Tomb Yawgy": "Urborg, Tomb of Yawgmoth",
"Urborg (The One That Makes All Lands Swamps)": "Urborg, Tomb of Yawgmoth",
"Ulamog the Infinite Gyre (Borderless) (Foil)": "Ulamog, the Infinite Gyre",
@@ -170,6 +171,7 @@ replacement = {
"Swamp (DOM #258)": "Swamp",
"Swamp (BRB 18)": "Swamp",
"Swamp (8ED #339)": "Swamp",
+ "Specter's Shriek (Plz Let It Get Banned This Time)": "Specter's Shriek",
"Speaker of the Heavens!?!?!?!": "Speaker of the Heavens",
"Simispiriguide": "Simian Spirit Guide",
"Signaling Roar": "Riling Dawnbreaker // Signaling Roar",
@@ -210,7 +212,7 @@ replacement = {
"Chronomatonton (The 1 Cost 1/1 That Taps to Get Bigger)": "Chronomaton",
"Chaplain of Arms": "Chaplain of Alms // Chapel Shieldgeist",
"Chancelor of the Tangle (Sic)": "Chancellor of the Tangle",
- "Bottomless Depths": "Bottomless Vault",
+ "Bottomless Depths": "Bottomless Vault", # wrong?
"Boseiju, Who Destroys Target Artifact, Enchantment, or Nonbasic Land (Who Endures)": "Boseiju, Who Endures",
"Bayou - Not Legal": "Bayou",
"Basic Plains": "Plains",