Add suspicious-signup sweep: suspend flagged signups with no activity
Watches every account flagged by IP-scrutiny or email-domain scrutiny at signup and, once it goes live, records a baseline post/follow count. A scheduled sweep (app/suspicious_sweep.py, run via cron on admin.yttrx.com) suspends any watch past SUSPICIOUS_GRACE_HOURS with zero new posts and zero new follows since that baseline; any activity clears the watch. Works whether yttrx is open-registration or requires moderator approval, since the watch starts at whichever event actually makes the account live (account.created vs account.approved), same dual handling the welcome flow already uses. Needs ABUSE_BOT_TOKEN re-minted with admin:read:accounts.
This commit is contained in:
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
# welcomebot-suspicious — inspect the suspicious-sweep watch list for yttrx-welcomebot.
|
||||
#
|
||||
# Installed at /usr/local/bin/welcomebot-suspicious on admin.yttrx.com.
|
||||
# Source of truth: ~/yttrx-welcomebot/bin/welcomebot-suspicious (workstation).
|
||||
# Reads the persistent `suspicious_watch` sqlite table — see app/main.py's
|
||||
# maybe_start_suspicious_watch / app/suspicious_sweep.py.
|
||||
set -euo pipefail
|
||||
|
||||
CONTAINER="${WELCOMEBOT_CONTAINER:-yttrx-welcomebot}"
|
||||
limit=20
|
||||
pending_only=0
|
||||
acct_filter=""
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: welcomebot-suspicious [options]
|
||||
|
||||
Print suspicious-watch records (acct, flag reasons, baseline post/follow
|
||||
counts, status, timestamps) from the welcomebot's persistent sqlite store,
|
||||
newest first.
|
||||
|
||||
Options:
|
||||
-n, --limit N Show the last N rows (default: $limit; 'all' for everything).
|
||||
--pending Only rows still waiting out the grace period.
|
||||
-a, --acct ACCT Only rows for this acct (exact match).
|
||||
-h, --help Show this help.
|
||||
|
||||
Examples:
|
||||
welcomebot-suspicious # last $limit watch records
|
||||
welcomebot-suspicious --pending # still in the grace window
|
||||
welcomebot-suspicious -n all
|
||||
welcomebot-suspicious -a someuser
|
||||
|
||||
Run the sweep itself (normally invoked by cron, see CLAUDE.md):
|
||||
ssh admin.yttrx.com 'docker exec yttrx-welcomebot python -m app.suspicious_sweep'
|
||||
EOF
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-n|--limit) shift; limit="${1:?--limit needs a value}" ;;
|
||||
--pending) pending_only=1 ;;
|
||||
-a|--acct) shift; acct_filter="${1:?--acct needs a value}" ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "welcomebot-suspicious: unknown option: $1" >&2; usage >&2; exit 2 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "welcomebot-suspicious: docker not found in PATH" >&2; exit 1
|
||||
fi
|
||||
if ! docker inspect "$CONTAINER" >/dev/null 2>&1; then
|
||||
echo "welcomebot-suspicious: container '$CONTAINER' not found (is it deployed/running?)" >&2; exit 1
|
||||
fi
|
||||
|
||||
docker exec -i \
|
||||
-e SUS_LIMIT="$limit" \
|
||||
-e SUS_PENDING_ONLY="$pending_only" \
|
||||
-e SUS_ACCT="$acct_filter" \
|
||||
"$CONTAINER" python3 <<'PYEOF'
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect("/data/welcomed.db")
|
||||
where, params = [], []
|
||||
if os.environ.get("SUS_PENDING_ONLY") == "1":
|
||||
where.append("status = 'pending'")
|
||||
if os.environ.get("SUS_ACCT"):
|
||||
where.append("acct = ?")
|
||||
params.append(os.environ["SUS_ACCT"])
|
||||
|
||||
sql = ("SELECT acct, reasons, baseline_statuses_count, baseline_following_count, "
|
||||
"status, started_at, resolved_at FROM suspicious_watch")
|
||||
if where:
|
||||
sql += " WHERE " + " AND ".join(where)
|
||||
sql += " ORDER BY started_at DESC"
|
||||
|
||||
limit = os.environ.get("SUS_LIMIT", "20")
|
||||
if limit != "all":
|
||||
sql += f" LIMIT {int(limit)}"
|
||||
|
||||
rows = conn.execute(sql, params).fetchall()
|
||||
if not rows:
|
||||
print("(no matching suspicious-watch records)")
|
||||
for acct, reasons, base_statuses, base_following, status, started_at, resolved_at in rows:
|
||||
acct = acct or ""
|
||||
reasons = reasons or ""
|
||||
baseline = f"posts={base_statuses} following={base_following}"
|
||||
resolved = resolved_at or "-"
|
||||
print(f"{started_at} {acct:20s} {reasons:10s} {baseline:24s} {status:20s} resolved={resolved}")
|
||||
PYEOF
|
||||
Reference in New Issue
Block a user