First git commit
This commit is contained in:
305
public_html/bbs/screens.py
Normal file
305
public_html/bbs/screens.py
Normal file
@ -0,0 +1,305 @@
|
||||
"""Profile + admin screens for the BBS."""
|
||||
import time
|
||||
from bbsdb import q, q1, ex, user_by_name, user_by_id, hash_password, ago
|
||||
|
||||
PER_PAGE = 10
|
||||
|
||||
|
||||
# ================================================================= profile
|
||||
def profile_menu(t, con, me):
|
||||
while True:
|
||||
u = user_by_id(con, me["id"])
|
||||
posts = q1(con, "SELECT COUNT(*) c FROM posts WHERE user_id=?", (u["id"],))["c"]
|
||||
t.header("YOUR PROFILE")
|
||||
t.line(" User : %s%s" % (u["username"], " [admin]" if u["is_admin"] else ""))
|
||||
t.line(" Tagline : %s" % (u["tagline"] or "-"))
|
||||
t.line(" Location: %s" % (u["location"] or "-"))
|
||||
t.line(" Joined : %s" % time.strftime("%d/%m/%Y",
|
||||
time.localtime(u["created_at"])))
|
||||
t.line(" Posts : %d" % posts)
|
||||
best = q(con, "SELECT game, MAX(score) sc FROM scores WHERE user_id=?"
|
||||
" GROUP BY game", (u["id"],))
|
||||
for b in best:
|
||||
t.line(" Best %-6s: %d" % (b["game"], b["sc"]))
|
||||
t.line("\n [T]agline [L]ocation [P]assword [W]ho's online [Q]back")
|
||||
c = t.ask("\nprofile> ").lower()
|
||||
if c.startswith("t"):
|
||||
v = t.ask(" New tagline: ", maxlen=80)
|
||||
ex(con, "UPDATE users SET tagline=? WHERE id=?", (v, u["id"]))
|
||||
elif c.startswith("l"):
|
||||
v = t.ask(" New location: ", maxlen=40)
|
||||
ex(con, "UPDATE users SET location=? WHERE id=?", (v, u["id"]))
|
||||
elif c.startswith("p"):
|
||||
change_password(t, con, u)
|
||||
elif c.startswith("w"):
|
||||
who(t, con)
|
||||
elif c.startswith("q") or c == "":
|
||||
return
|
||||
|
||||
|
||||
def change_password(t, con, u):
|
||||
from bbsdb import verify_password
|
||||
old = t.secret(" Current password: ")
|
||||
if not verify_password(old, u["pass_hash"]):
|
||||
t.line(" Wrong password.")
|
||||
return
|
||||
n1 = t.secret(" New password: ")
|
||||
if len(n1) < 4:
|
||||
t.line(" Too short (min 4).")
|
||||
return
|
||||
if n1 != t.secret(" Repeat new: "):
|
||||
t.line(" Did not match.")
|
||||
return
|
||||
h = hash_password(n1)
|
||||
if not h:
|
||||
t.line(" Could not hash password - unchanged.")
|
||||
return
|
||||
ex(con, "UPDATE users SET pass_hash=? WHERE id=?", (h, u["id"]))
|
||||
t.line(" Password changed. It works on the website too.")
|
||||
|
||||
|
||||
def who(t, con):
|
||||
rows = q(con, "SELECT username,last_seen,is_admin FROM users "
|
||||
"ORDER BY last_seen DESC LIMIT 15")
|
||||
t.header("RECENTLY SEEN")
|
||||
for r in rows:
|
||||
t.line(" %-16s %s ago%s" % (r["username"][:16], ago(r["last_seen"]),
|
||||
" [admin]" if r["is_admin"] else ""))
|
||||
t.pause()
|
||||
|
||||
|
||||
def userlist(t, con):
|
||||
page = 0
|
||||
while True:
|
||||
rows = q(con, "SELECT username,tagline,last_seen FROM users "
|
||||
"ORDER BY username LIMIT ? OFFSET ?",
|
||||
(PER_PAGE + 1, page * PER_PAGE))
|
||||
more = len(rows) > PER_PAGE
|
||||
rows = rows[:PER_PAGE]
|
||||
t.header("MEMBER LIST")
|
||||
for r in rows:
|
||||
t.line(" %-16s %-24s %s" % (r["username"][:16],
|
||||
(r["tagline"] or "")[:24],
|
||||
ago(r["last_seen"])))
|
||||
t.line("\n [N]ext [P]rev [Q]back")
|
||||
c = t.ask("\nusers> ").lower()
|
||||
if c.startswith("n") and more:
|
||||
page += 1
|
||||
elif c.startswith("p") and page > 0:
|
||||
page -= 1
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
# ================================================================== admin
|
||||
def admin_menu(t, con, me):
|
||||
if not me["is_admin"]:
|
||||
t.line(" Admin only.")
|
||||
return
|
||||
while True:
|
||||
t.header("ADMIN TOOLS")
|
||||
t.line(" [U]sers [F]orums [T]opics [M]OTD [S]tats [Q]back")
|
||||
c = t.ask("\nadmin> ").lower()
|
||||
if c.startswith("u"):
|
||||
admin_users(t, con, me)
|
||||
elif c.startswith("f"):
|
||||
admin_forums(t, con)
|
||||
elif c.startswith("t"):
|
||||
admin_topics(t, con)
|
||||
elif c.startswith("m"):
|
||||
v = t.ask(" New MOTD: ", maxlen=200)
|
||||
ex(con, "INSERT INTO settings (k,v) VALUES ('motd',?) "
|
||||
"ON CONFLICT(k) DO UPDATE SET v=excluded.v", (v,))
|
||||
t.line(" MOTD updated (site + BBS).")
|
||||
elif c.startswith("s"):
|
||||
admin_stats(t, con)
|
||||
elif c.startswith("q") or c == "":
|
||||
return
|
||||
|
||||
|
||||
def admin_stats(t, con):
|
||||
t.header("STATS")
|
||||
for label, sql in [
|
||||
("Users", "SELECT COUNT(*) c FROM users"),
|
||||
("Banned", "SELECT COUNT(*) c FROM users WHERE is_banned=1"),
|
||||
("Forums", "SELECT COUNT(*) c FROM forums"),
|
||||
("Topics", "SELECT COUNT(*) c FROM topics"),
|
||||
("Posts", "SELECT COUNT(*) c FROM posts"),
|
||||
("Messages", "SELECT COUNT(*) c FROM messages"),
|
||||
("Matches", "SELECT COUNT(*) c FROM matches"),
|
||||
]:
|
||||
t.line(" %-10s %d" % (label, q1(con, sql)["c"]))
|
||||
t.pause()
|
||||
|
||||
|
||||
def admin_users(t, con, me):
|
||||
while True:
|
||||
t.header("ADMIN: USERS")
|
||||
t.line(" [L]ist [F]ind [A]dd [E]dit [Q]back")
|
||||
c = t.ask("\nadmin/users> ").lower()
|
||||
if c.startswith("l"):
|
||||
rows = q(con, "SELECT id,username,is_admin,is_banned FROM users"
|
||||
" ORDER BY id LIMIT 40")
|
||||
for r in rows:
|
||||
t.line(" #%-4d %-16s %s%s" % (
|
||||
r["id"], r["username"][:16],
|
||||
"admin " if r["is_admin"] else "",
|
||||
"BANNED" if r["is_banned"] else ""))
|
||||
t.pause()
|
||||
elif c.startswith("f"):
|
||||
term = t.ask(" Username contains: ", maxlen=16)
|
||||
rows = q(con, "SELECT id,username,is_admin,is_banned FROM users"
|
||||
" WHERE username LIKE ? ORDER BY id LIMIT 40",
|
||||
("%" + term + "%",))
|
||||
if not rows:
|
||||
t.line(" (no matches)")
|
||||
for r in rows:
|
||||
t.line(" #%-4d %-16s %s%s" % (
|
||||
r["id"], r["username"][:16],
|
||||
"admin " if r["is_admin"] else "",
|
||||
"BANNED" if r["is_banned"] else ""))
|
||||
t.pause()
|
||||
elif c.startswith("a"):
|
||||
name = t.ask(" New username: ", maxlen=16)
|
||||
if not name.replace("_", "").isalnum() or not 3 <= len(name) <= 16:
|
||||
t.line(" Bad username (3-16 alnum/underscore).")
|
||||
continue
|
||||
if user_by_name(con, name):
|
||||
t.line(" Already taken.")
|
||||
continue
|
||||
pw = t.secret(" Password: ")
|
||||
if len(pw) < 4:
|
||||
t.line(" Too short.")
|
||||
continue
|
||||
h = hash_password(pw)
|
||||
if not h:
|
||||
t.line(" Hashing failed.")
|
||||
continue
|
||||
now = int(time.time())
|
||||
ex(con, "INSERT INTO users (username,pass_hash,is_admin,created_at)"
|
||||
" VALUES (?,?,0,?)", (name, h, now))
|
||||
t.line(" Created %s." % name)
|
||||
elif c.startswith("e"):
|
||||
admin_edit_user(t, con, me)
|
||||
elif c.startswith("q") or c == "":
|
||||
return
|
||||
|
||||
|
||||
def admin_edit_user(t, con, me):
|
||||
name = t.ask(" Username to edit: ", maxlen=16)
|
||||
u = user_by_name(con, name)
|
||||
if not u:
|
||||
t.line(" No such user.")
|
||||
return
|
||||
while True:
|
||||
u = user_by_id(con, u["id"])
|
||||
t.header("EDIT: %s (#%d)" % (u["username"], u["id"]))
|
||||
t.line(" admin=%d banned=%d tagline=%s" %
|
||||
(u["is_admin"], u["is_banned"], u["tagline"] or "-"))
|
||||
t.line("\n [A]dmin toggle [B]an toggle [R]eset pw [D]elete [Q]back")
|
||||
c = t.ask("\nedit> ").lower()
|
||||
if c.startswith("a"):
|
||||
if u["id"] == me["id"] and u["is_admin"]:
|
||||
t.line(" You cannot remove your own admin rights.")
|
||||
continue
|
||||
ex(con, "UPDATE users SET is_admin=1-is_admin WHERE id=?", (u["id"],))
|
||||
elif c.startswith("b"):
|
||||
if u["id"] == me["id"]:
|
||||
t.line(" You cannot ban yourself.")
|
||||
continue
|
||||
ex(con, "UPDATE users SET is_banned=1-is_banned WHERE id=?", (u["id"],))
|
||||
elif c.startswith("r"):
|
||||
pw = t.secret(" New password: ")
|
||||
if len(pw) < 4:
|
||||
t.line(" Too short.")
|
||||
continue
|
||||
h = hash_password(pw)
|
||||
if h:
|
||||
ex(con, "UPDATE users SET pass_hash=? WHERE id=?", (h, u["id"]))
|
||||
t.line(" Reset.")
|
||||
elif c.startswith("d"):
|
||||
if u["id"] == me["id"]:
|
||||
t.line(" You cannot delete yourself.")
|
||||
continue
|
||||
if t.ask(" Type DELETE to confirm: ") == "DELETE":
|
||||
ex(con, "DELETE FROM users WHERE id=?", (u["id"],))
|
||||
t.line(" Deleted.")
|
||||
return
|
||||
elif c.startswith("q") or c == "":
|
||||
return
|
||||
|
||||
|
||||
def admin_forums(t, con):
|
||||
while True:
|
||||
rows = q(con, "SELECT f.*, (SELECT COUNT(*) FROM topics x "
|
||||
"WHERE x.forum_id=f.id) tc FROM forums f "
|
||||
"ORDER BY f.sort_order, f.id")
|
||||
t.header("ADMIN: FORUMS")
|
||||
for r in rows:
|
||||
t.line(" #%-3d %-24s %3dt %s" % (
|
||||
r["id"], r["name"][:24], r["tc"],
|
||||
"[locked]" if r["is_locked"] else ""))
|
||||
t.line("\n [A]dd [L]ock toggle [R]ename [D]elete [Q]back")
|
||||
c = t.ask("\nadmin/forums> ").lower()
|
||||
if c.startswith("a"):
|
||||
name = t.ask(" Forum name: ", maxlen=60)
|
||||
if not name:
|
||||
continue
|
||||
descr = t.ask(" Description: ", maxlen=120)
|
||||
order = t.ask(" Sort order (number): ", maxlen=4)
|
||||
ex(con, "INSERT INTO forums (name,descr,sort_order) VALUES (?,?,?)",
|
||||
(name, descr, int(order) if order.isdigit() else 0))
|
||||
t.line(" Added.")
|
||||
elif c.startswith("l"):
|
||||
i = t.ask(" Forum id: ", maxlen=6)
|
||||
if i.isdigit():
|
||||
ex(con, "UPDATE forums SET is_locked=1-is_locked WHERE id=?", (int(i),))
|
||||
elif c.startswith("r"):
|
||||
i = t.ask(" Forum id: ", maxlen=6)
|
||||
if i.isdigit():
|
||||
name = t.ask(" New name: ", maxlen=60)
|
||||
if name:
|
||||
ex(con, "UPDATE forums SET name=? WHERE id=?", (name, int(i)))
|
||||
elif c.startswith("d"):
|
||||
i = t.ask(" Forum id to DELETE (with all topics): ", maxlen=6)
|
||||
if i.isdigit() and t.ask(" Type DELETE to confirm: ") == "DELETE":
|
||||
ex(con, "DELETE FROM forums WHERE id=?", (int(i),))
|
||||
t.line(" Deleted.")
|
||||
elif c.startswith("q") or c == "":
|
||||
return
|
||||
|
||||
|
||||
def admin_topics(t, con):
|
||||
while True:
|
||||
rows = q(con, "SELECT t.*, f.name fname, u.username FROM topics t "
|
||||
"JOIN forums f ON f.id=t.forum_id "
|
||||
"LEFT JOIN users u ON u.id=t.user_id "
|
||||
"ORDER BY t.bumped_at DESC LIMIT 20")
|
||||
t.header("ADMIN: TOPICS (latest 20)")
|
||||
for r in rows:
|
||||
t.line(" #%-4d [%-10s] %-26s %s%s" % (
|
||||
r["id"], r["fname"][:10], r["title"][:26],
|
||||
(r["username"] or "?")[:10],
|
||||
" LOCKED" if r["is_locked"] else ""))
|
||||
t.line("\n [L]ock toggle [M]ove [D]elete [Q]back")
|
||||
c = t.ask("\nadmin/topics> ").lower()
|
||||
if c.startswith("l"):
|
||||
i = t.ask(" Topic id: ", maxlen=6)
|
||||
if i.isdigit():
|
||||
ex(con, "UPDATE topics SET is_locked=1-is_locked WHERE id=?", (int(i),))
|
||||
elif c.startswith("m"):
|
||||
i = t.ask(" Topic id: ", maxlen=6)
|
||||
f = t.ask(" Move to forum id: ", maxlen=6)
|
||||
if i.isdigit() and f.isdigit():
|
||||
if q1(con, "SELECT 1 x FROM forums WHERE id=?", (int(f),)):
|
||||
ex(con, "UPDATE topics SET forum_id=? WHERE id=?", (int(f), int(i)))
|
||||
t.line(" Moved.")
|
||||
else:
|
||||
t.line(" No such forum.")
|
||||
elif c.startswith("d"):
|
||||
i = t.ask(" Topic id to DELETE: ", maxlen=6)
|
||||
if i.isdigit() and t.ask(" Type DELETE to confirm: ") == "DELETE":
|
||||
ex(con, "DELETE FROM topics WHERE id=?", (int(i),))
|
||||
t.line(" Deleted.")
|
||||
elif c.startswith("q") or c == "":
|
||||
return
|
||||
Reference in New Issue
Block a user