remarkbox/scripts/spam/disable_spam.py
russell@unturf.com 66964f736a Add spam prevention, superuser system, and LLM relevance checking
- Thread creation rate limit: 1 per 7 min per user/IP via API
- is_superuser column on User model with alembic migration
- Superusers bypass namespace-scoped is_moderator() checks
- super_fly_required now checks is_superuser instead of hardcoded names
- /topsecret/users admin page to promote/demote superusers
- Spam scoring module with 6 signals (link density, patterns, duplicates,
  new account velocity, IP reputation, content length)
- Hard threshold (0.8) rejects, soft threshold (0.5) holds for moderation
- LLM relevance checking via Hermes (hermes.ai.unturf.com) on every
  message when enabled, including embed mode parent page URL context
- Admin API endpoints: GET /api/v1/admin/namespaces, recent-nodes
- Spam hunting scripts: scan.py, disable_spam.py, promote_superuser.py
- Updated Python client with admin methods
2026-02-02 09:19:16 -05:00

80 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""
Bulk disable spam nodes on Remarkbox.
Requires a superuser session cookie.
Usage:
python scripts/spam/disable_spam.py node_id [node_id ...]
python scripts/spam/scan.py --json | python scripts/spam/disable_spam.py --from-json
Examples:
python scripts/spam/disable_spam.py abc123 def456
python scripts/spam/scan.py --json --threshold=0.8 | python scripts/spam/disable_spam.py --from-json
"""
import argparse
import json
import os
import sys
import time
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "remarkbox", "api"))
from remarkbox_client import RemarkboxClient
def main():
parser = argparse.ArgumentParser(description="Bulk disable spam nodes")
parser.add_argument("node_ids", nargs="*", help="Node IDs to disable")
parser.add_argument("--from-json", action="store_true",
help="Read node IDs from JSON on stdin (from scan.py --json)")
parser.add_argument("--url", default="https://my.remarkbox.com", help="Remarkbox URL")
parser.add_argument("--cookie", default=os.path.expanduser("~/.config/remarkbox/cookies.txt"),
help="Cookie file path")
parser.add_argument("--dry-run", action="store_true", help="Just print what would be disabled")
args = parser.parse_args()
node_ids = list(args.node_ids)
if args.from_json:
data = json.load(sys.stdin)
if isinstance(data, list):
node_ids.extend(item["id"] for item in data if "id" in item)
elif isinstance(data, dict) and "nodes" in data:
node_ids.extend(item["id"] for item in data["nodes"] if "id" in item)
if not node_ids:
print("No node IDs provided. Use positional args or --from-json.", file=sys.stderr)
sys.exit(1)
# Deduplicate
node_ids = list(dict.fromkeys(node_ids))
client = RemarkboxClient(args.url, cookie_file=args.cookie)
disabled = 0
failed = 0
skipped = 0
for node_id in node_ids:
if args.dry_run:
print("[dry-run] would disable {}".format(node_id))
continue
try:
client.disable_node(node_id)
print("[ok] disabled {}".format(node_id))
disabled += 1
time.sleep(0.5)
except Exception as e:
print("[FAIL] {}: {}".format(node_id, e), file=sys.stderr)
failed += 1
if args.dry_run:
print("\nDry run: {} nodes would be disabled.".format(len(node_ids)))
else:
print("\nDone. {} disabled, {} failed.".format(disabled, failed))
if __name__ == "__main__":
main()