aboutsummaryrefslogtreecommitdiff
path: root/scripts/chat.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/chat.py')
-rwxr-xr-xscripts/chat.py39
1 files changed, 33 insertions, 6 deletions
diff --git a/scripts/chat.py b/scripts/chat.py
index 5b44b64..821bb31 100755
--- a/scripts/chat.py
+++ b/scripts/chat.py
@@ -24,11 +24,22 @@ def now_ms():
def load(path):
- try:
- with open(path) as f:
- return json.load(f)
- except Exception:
+ """Read a thread. Missing is empty; corrupt is an error.
+
+ Returning [] for both let reply() rewrite a damaged thread with a single message —
+ history gone, and nothing said so.
+ """
+ if not os.path.exists(path):
return []
+ with open(path) as f:
+ raw = f.read()
+ try:
+ data = json.loads(raw)
+ except json.JSONDecodeError as exc:
+ raise SystemExit('thread file is corrupt (%s): %s' % (path, exc))
+ if not isinstance(data, list):
+ raise SystemExit('thread file is not a list: ' + path)
+ return data
def threads():
@@ -38,7 +49,14 @@ def threads():
for name in sorted(os.listdir(CHAT_DIR)):
if not name.endswith('.json') or name.startswith('.'):
continue
- msgs = load(os.path.join(CHAT_DIR, name))
+ try:
+ msgs = load(os.path.join(CHAT_DIR, name))
+ except SystemExit as exc:
+ # one unreadable file must not make the whole listing fail: that is how
+ # awaiting messages silently stop being listed
+ out.append({'thread': name[:-5], 'count': -1, 'last_ts': '', 'last_author': 'corrupt',
+ 'awaiting': True, 'preview': 'CORRUPT: ' + str(exc)[:90]})
+ continue
if not msgs:
continue
last = msgs[-1]
@@ -62,6 +80,10 @@ def reply(thread, text):
raise SystemExit('no such thread: ' + thread)
lockpath = os.path.join(CHAT_DIR, '.lock-' + thread)
with open(lockpath, 'a+') as lock:
+ # created here as darkbot (0644 under umask 022); PHP opens the same lock as
+ # www-data, so group write is mandatory or later posts to this thread 500
+ try: os.chmod(lockpath, 0o664)
+ except OSError: pass
fcntl.flock(lock, fcntl.LOCK_EX)
try:
msgs = load(path)
@@ -102,10 +124,15 @@ def main():
return 0
for t in rows:
flag = 'AWAITING' if t['awaiting'] else 'replied '
- print(f"{t['thread']} {flag} {t['count']:>3} msg {t['last_ts']} {t['preview']}")
+ if t['count'] == -1:
+ flag = 'CORRUPT '
+ print(f"{t['thread']} {flag} {max(t['count'], 0):>3} msg {t['last_ts']} {t['preview']}")
return 0
if cmd == 'show' and len(sys.argv) >= 3:
+ # reply() validated the id, show() did not: '../../x' joined straight onto CHAT_DIR
+ if not THREAD_RE.match(sys.argv[2]):
+ raise SystemExit('invalid thread id (expected 16 lowercase hex chars)')
msgs = load(os.path.join(CHAT_DIR, sys.argv[2] + '.json'))
if not msgs:
print('(empty or unknown thread)')