Add a moderation queue for user submissions

Completes TODO #3. Submissions are gated behind ACCEPT_SUBMISSIONS,
which defaults to off: is_allowed() treats everyone as allowed when
ALLOWED_USERS is empty, so opening submissions implicitly would have
accepted media from anyone who found the bot.

- Schema v3 adds status, reviewed_by and reviewed_at. Existing rows
  default to approved, so upgrading changes nothing for a running bot.
- Search, /list and counts only return approved items; callers that
  need everything pass status=None.
- /review shows the oldest pending item with approve, reject, fix
  keywords and stop. No cursor is stored anywhere: each action just
  asks for the next pending item, which is what lets a moderator stop
  and resume later, and lets several moderators share one queue.
- set_status only updates rows still pending, so if two moderators open
  the same item the first decision wins and the second is told so.
- A failed send no longer wedges the queue; the item's details are
  shown anyway so it can still be rejected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
goyban
2026-09-01 21:32:35 +00:00
parent efc2be5f8a
commit 0a8c81db90
7 changed files with 331 additions and 27 deletions
+9 -1
View File
@@ -75,6 +75,13 @@ def test_every_stored_type_renders_inline():
print(" every stored media_type has a matching inline builder")
def test_every_stored_type_can_be_sent_back():
stored = {t for t, _ in B.MEDIA_EXTRACTORS}
missing = stored - set(B.MEDIA_SENDERS)
assert not missing, f"stored but not sendable for review: {sorted(missing)}"
print(" every stored media_type can be sent back for review")
def test_builders_construct():
for media_type, build in B.INLINE_BUILDERS.items():
build("rid", f"FID_{media_type}", "cat, funny")
@@ -86,7 +93,8 @@ def test_builders_construct():
def main():
for test in (test_every_type_extracts, test_animation_beats_document,
test_video_note_stored_as_video, test_non_media_ignored,
test_every_stored_type_renders_inline, test_builders_construct):
test_every_stored_type_renders_inline,
test_every_stored_type_can_be_sent_back, test_builders_construct):
test()
print("\nAll media tests passed.")