~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
53 lines · 2.0 KB Raw History
import { defineClassifier } from "$hatk";
import { BRIDGED_HIDDEN_COLLECTIONS, isBridgedAccount } from "../helpers/bridged.ts";

/**
 * Labels every post from a Bridgy Fed account `spam`, which hides it; see
 * `helpers/bridged.ts`. The handle or the profile's Bridgy footer is the whole
 * signal, so this asks no model.
 *
 * Unlike the other classifiers this acts rather than reports. It backs up the
 * label rule in `labels/spam.ts`, which never sees the records a backfill
 * writes, and a bridged account's first posts always arrive by backfill.
 *
 * A post that already carries a spam label row, positive or negated, is left
 * alone, so an admin who unhides one is not overruled on the next scan.
 */
export default defineClassifier({
  // No threshold, so nothing is reported: the labels are the action, and a
  // report per account would fill the queue with decisions already made.
  subject: "account",

  async buildState({ db, subject }) {
    if (!(await isBridgedAccount(db, subject.did, subject.handle ?? null))) return null;
    const handle = subject.handle?.toLowerCase() ?? null;

    // The unlabelled posts are the state, so each new post changes the
    // fingerprint and is scored again, and a fully labelled account is skipped.
    const unlabelled: string[] = [];
    for (const collection of BRIDGED_HIDDEN_COLLECTIONS) {
      const rows = (await db.query(
        `SELECT t.uri FROM "${collection}" t
         WHERE t.did = $1
           AND NOT EXISTS (SELECT 1 FROM _labels l WHERE l.uri = t.uri AND l.val = 'spam')`,
        [subject.did],
      )) as { uri: string }[];
      unlabelled.push(...rows.map((r) => r.uri));
    }
    if (!unlabelled.length) return null;

    return { handle, unlabelled };
  },

  async classify(state, { db }) {
    const now = new Date().toISOString();
    for (const uri of state.unlabelled as string[]) {
      await db.run(
        `INSERT INTO _labels (src, uri, val, neg, cts) VALUES ('self', $1, 'spam', false, $2)`,
        uri,
        now,
      );
    }
    return { bridged_account: 1 };
  },
});