251 lines
9.2 KiB
Python
251 lines
9.2 KiB
Python
"""Private mail + forum screens for the BBS."""
|
|
import time
|
|
from bbsdb import q, q1, ex, user_by_name, unread, ago
|
|
|
|
PER_PAGE = 10
|
|
|
|
|
|
# ==================================================================== mail
|
|
def mail_menu(t, con, me):
|
|
while True:
|
|
n = unread(con, me["id"])
|
|
t.header("MAIL (%d unread)" % n)
|
|
t.line(" [I]nbox [S]ent [W]rite [Q]uit to main")
|
|
c = t.ask("\nmail> ").lower()
|
|
if c.startswith("i"):
|
|
mail_list(t, con, me, "in")
|
|
elif c.startswith("s"):
|
|
mail_list(t, con, me, "out")
|
|
elif c.startswith("w"):
|
|
mail_write(t, con, me)
|
|
elif c.startswith("q") or c == "":
|
|
return
|
|
|
|
|
|
def mail_list(t, con, me, box):
|
|
page = 0
|
|
while True:
|
|
off = page * PER_PAGE
|
|
if box == "in":
|
|
rows = q(con, "SELECT m.*, u.username who FROM messages m "
|
|
"LEFT JOIN users u ON u.id=m.from_id "
|
|
"WHERE m.to_id=? ORDER BY m.id DESC LIMIT ? OFFSET ?",
|
|
(me["id"], PER_PAGE + 1, off))
|
|
else:
|
|
rows = q(con, "SELECT m.*, u.username who FROM messages m "
|
|
"LEFT JOIN users u ON u.id=m.to_id "
|
|
"WHERE m.from_id=? ORDER BY m.id DESC LIMIT ? OFFSET ?",
|
|
(me["id"], PER_PAGE + 1, off))
|
|
more = len(rows) > PER_PAGE
|
|
rows = rows[:PER_PAGE]
|
|
|
|
t.header("INBOX" if box == "in" else "SENT")
|
|
if not rows:
|
|
t.line(" (empty)")
|
|
for i, r in enumerate(rows, 1):
|
|
flag = "*" if (box == "in" and not r["is_read"]) else " "
|
|
t.line(" %s%2d. %-12s %-28s %s" % (
|
|
flag, i, (r["who"] or "[gone]")[:12], r["subject"][:28],
|
|
ago(r["created_at"])))
|
|
t.line("\n number=read [N]ext [P]rev [Q]back")
|
|
c = t.ask("\nmail> ").lower()
|
|
if c.isdigit() and 1 <= int(c) <= len(rows):
|
|
mail_read(t, con, me, rows[int(c) - 1]["id"], box)
|
|
elif c.startswith("n") and more:
|
|
page += 1
|
|
elif c.startswith("p") and page > 0:
|
|
page -= 1
|
|
elif c.startswith("q") or c == "":
|
|
return
|
|
|
|
|
|
def mail_read(t, con, me, mid, box):
|
|
m = q1(con, "SELECT m.*, f.username fromname, o.username toname FROM messages m "
|
|
"LEFT JOIN users f ON f.id=m.from_id "
|
|
"LEFT JOIN users o ON o.id=m.to_id WHERE m.id=?", (mid,))
|
|
if not m or me["id"] not in (m["to_id"], m["from_id"]):
|
|
t.line("Not found.")
|
|
return
|
|
if m["to_id"] == me["id"] and not m["is_read"]:
|
|
ex(con, "UPDATE messages SET is_read=1 WHERE id=?", (mid,))
|
|
|
|
t.header("MSG: " + m["subject"][:40])
|
|
t.line(" From: %s" % (m["fromname"] or "[gone]"))
|
|
t.line(" To : %s" % (m["toname"] or "[gone]"))
|
|
t.line(" Date: %s" % time.strftime("%d/%m/%Y %H:%M",
|
|
time.localtime(m["created_at"])))
|
|
t.rule()
|
|
for ln in m["body"].splitlines() or [""]:
|
|
t.line(" " + ln)
|
|
t.rule()
|
|
t.line(" [R]eply [D]elete [Q]back")
|
|
c = t.ask("\nmsg> ").lower()
|
|
if c.startswith("r") and m["fromname"]:
|
|
subj = m["subject"]
|
|
if not subj.lower().startswith("re:"):
|
|
subj = "Re: " + subj
|
|
mail_write(t, con, me, to=m["fromname"], subject=subj)
|
|
elif c.startswith("d"):
|
|
ex(con, "DELETE FROM messages WHERE id=? AND (to_id=? OR from_id=?)",
|
|
(mid, me["id"], me["id"]))
|
|
t.line("Deleted.")
|
|
|
|
|
|
def mail_write(t, con, me, to=None, subject=None):
|
|
t.header("WRITE MAIL")
|
|
if to is None:
|
|
to = t.ask(" To (username): ", maxlen=16)
|
|
else:
|
|
t.line(" To: " + to)
|
|
if not to:
|
|
return
|
|
dest = user_by_name(con, to)
|
|
if not dest:
|
|
t.line(" No such user.")
|
|
return
|
|
if dest["id"] == me["id"]:
|
|
t.line(" You cannot mail yourself.")
|
|
return
|
|
if subject is None:
|
|
subject = t.ask(" Subject: ", maxlen=60)
|
|
else:
|
|
t.line(" Subject: " + subject)
|
|
if not subject:
|
|
return
|
|
t.line(" Body - end with a single '.' on its own line:")
|
|
body = read_body(t)
|
|
if not body.strip():
|
|
t.line(" Aborted (empty).")
|
|
return
|
|
ex(con, "INSERT INTO messages (from_id,to_id,subject,body,created_at) "
|
|
"VALUES (?,?,?,?,?)",
|
|
(me["id"], dest["id"], subject, body[:4000], int(time.time())))
|
|
t.line(" Sent to %s." % dest["username"])
|
|
|
|
|
|
def read_body(t, maxlines=40):
|
|
"""Multi-line entry terminated by a lone '.' - the classic BBS editor."""
|
|
lines = []
|
|
while len(lines) < maxlines:
|
|
ln = t.read(" | ", maxlen=200)
|
|
if ln == ".":
|
|
break
|
|
lines.append(ln)
|
|
return "\n".join(lines)
|
|
|
|
|
|
# ==================================================================== forum
|
|
def forum_menu(t, con, me):
|
|
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("FORUMS")
|
|
for i, r in enumerate(rows, 1):
|
|
t.line(" %2d. %-24s %3d topics%s" % (
|
|
i, r["name"][:24], r["tc"], " [locked]" if r["is_locked"] else ""))
|
|
t.line("\n number=open [Q]back")
|
|
c = t.ask("\nforum> ").lower()
|
|
if c.isdigit() and 1 <= int(c) <= len(rows):
|
|
topic_list(t, con, me, rows[int(c) - 1])
|
|
elif c.startswith("q") or c == "":
|
|
return
|
|
|
|
|
|
def topic_list(t, con, me, forum):
|
|
page = 0
|
|
while True:
|
|
off = page * PER_PAGE
|
|
rows = q(con, "SELECT t.*, u.username, "
|
|
"(SELECT COUNT(*) FROM posts p WHERE p.topic_id=t.id) pc "
|
|
"FROM topics t LEFT JOIN users u ON u.id=t.user_id "
|
|
"WHERE t.forum_id=? ORDER BY t.bumped_at DESC LIMIT ? OFFSET ?",
|
|
(forum["id"], PER_PAGE + 1, off))
|
|
more = len(rows) > PER_PAGE
|
|
rows = rows[:PER_PAGE]
|
|
|
|
t.header("FORUM: " + forum["name"])
|
|
if not rows:
|
|
t.line(" (no topics yet)")
|
|
for i, r in enumerate(rows, 1):
|
|
t.line(" %2d.%s %-30s %3dp %-10s %s" % (
|
|
i, "L" if r["is_locked"] else " ", r["title"][:30], r["pc"],
|
|
(r["username"] or "?")[:10], ago(r["bumped_at"])))
|
|
t.line("\n number=read [N]ew topic [M]ore [P]rev [Q]back")
|
|
c = t.ask("\ntopics> ").lower()
|
|
if c.isdigit() and 1 <= int(c) <= len(rows):
|
|
topic_read(t, con, me, rows[int(c) - 1]["id"])
|
|
elif c.startswith("n"):
|
|
new_topic(t, con, me, forum)
|
|
elif c.startswith("m") and more:
|
|
page += 1
|
|
elif c.startswith("p") and page > 0:
|
|
page -= 1
|
|
elif c.startswith("q") or c == "":
|
|
return
|
|
|
|
|
|
def new_topic(t, con, me, forum):
|
|
if forum["is_locked"] and not me["is_admin"]:
|
|
t.line(" That forum is locked.")
|
|
return
|
|
title = t.ask(" Topic title: ", maxlen=80)
|
|
if not title:
|
|
return
|
|
t.line(" Body - end with '.' on its own line:")
|
|
body = read_body(t)
|
|
if not body.strip():
|
|
t.line(" Aborted.")
|
|
return
|
|
now = int(time.time())
|
|
tid = ex(con, "INSERT INTO topics (forum_id,user_id,title,created_at,bumped_at)"
|
|
" VALUES (?,?,?,?,?)", (forum["id"], me["id"], title, now, now))
|
|
ex(con, "INSERT INTO posts (topic_id,user_id,body,created_at) VALUES (?,?,?,?)",
|
|
(tid, me["id"], body[:4000], now))
|
|
t.line(" Topic posted.")
|
|
|
|
|
|
def topic_read(t, con, me, tid):
|
|
page = 0
|
|
while True:
|
|
top = q1(con, "SELECT t.*, f.name fname, f.is_locked flocked FROM topics t "
|
|
"JOIN forums f ON f.id=t.forum_id WHERE t.id=?", (tid,))
|
|
if not top:
|
|
t.line(" Gone.")
|
|
return
|
|
locked = top["is_locked"] or top["flocked"]
|
|
off = page * PER_PAGE
|
|
rows = q(con, "SELECT p.*, u.username FROM posts p "
|
|
"LEFT JOIN users u ON u.id=p.user_id "
|
|
"WHERE p.topic_id=? ORDER BY p.id LIMIT ? OFFSET ?",
|
|
(tid, PER_PAGE + 1, off))
|
|
more = len(rows) > PER_PAGE
|
|
rows = rows[:PER_PAGE]
|
|
|
|
t.header(top["title"][:50] + (" [LOCKED]" if locked else ""))
|
|
for r in rows:
|
|
t.line(" --- %s, %s ago ---" % ((r["username"] or "[gone]"),
|
|
ago(r["created_at"])))
|
|
for ln in r["body"].splitlines() or [""]:
|
|
t.line(" " + ln)
|
|
t.line("\n [R]eply [M]ore [P]rev [Q]back")
|
|
c = t.ask("\ntopic> ").lower()
|
|
if c.startswith("r"):
|
|
if locked and not me["is_admin"]:
|
|
t.line(" Topic is locked.")
|
|
continue
|
|
t.line(" Reply - end with '.' on its own line:")
|
|
body = read_body(t)
|
|
if body.strip():
|
|
now = int(time.time())
|
|
ex(con, "INSERT INTO posts (topic_id,user_id,body,created_at)"
|
|
" VALUES (?,?,?,?)", (tid, me["id"], body[:4000], now))
|
|
ex(con, "UPDATE topics SET bumped_at=? WHERE id=?", (now, tid))
|
|
t.line(" Posted.")
|
|
elif c.startswith("m") and more:
|
|
page += 1
|
|
elif c.startswith("p") and page > 0:
|
|
page -= 1
|
|
elif c.startswith("q") or c == "":
|
|
return
|