Seller detection on replies was a case-insensitive substring match over the configured keyword list, and every hit landed in `human-required` alongside genuine prospects that need a person. Off-platform pushes were only checked for r4r policy modes, as were the under-18 signals. Three layers now: - New `off-platform-signals` module matches on normalized text (unicode folding, zero-width stripping, leet mapping, separator removal), so `0nlyf4ns`, `t3l3gr4m` and `t e l e g r a m` are caught. Ambiguous terms need context: `snap` on its own stays a photo request, never a kill. - New terminal `killed` automation status. It records the evidence, sets the durable seller flag, supersedes the pending claim, and resolves triage to `done`, so killed conversations leave every actionable queue. Off-platform kills are independent of `reddit.filterSellers`. - Optional AI screening of inbound replies behind `conversations.aiReplyScreening` (off by default). Runs only after the deterministic gates pass, memoizes per inbound event, and fails open. Under-18 and in-person/contact-detail signals now apply to every policy mode rather than r4r only, and remain human handoffs rather than kills. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
133 lines
5.1 KiB
TypeScript
133 lines
5.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import {
|
||
findOffPlatformSignal,
|
||
foldSignalText,
|
||
normalizeSignalText,
|
||
offPlatformSignalReason,
|
||
} from '../src/services/off-platform-signals.js';
|
||
|
||
describe('normalizeSignalText', () => {
|
||
it('lowercases, folds unicode, and strips zero-width characters', () => {
|
||
expect(normalizeSignalText('Telegram ME')).toBe('telegram me');
|
||
});
|
||
|
||
it('normalizes curly apostrophes so contraction patterns match', () => {
|
||
expect(normalizeSignalText('What’s your snap')).toBe("what's your snap");
|
||
});
|
||
});
|
||
|
||
describe('foldSignalText', () => {
|
||
it('maps leet characters back to letters', () => {
|
||
expect(foldSignalText('0nly F4n5')).toBe('onlyfans');
|
||
});
|
||
|
||
it('removes separators used to break up a word', () => {
|
||
expect(foldSignalText('t e l e g r a m')).toBe('telegram');
|
||
expect(foldSignalText('t.e.l.e.g.r.a.m')).toBe('telegram');
|
||
expect(foldSignalText('t-e-l-e-g-r-a-m')).toBe('telegram');
|
||
});
|
||
|
||
it('collapses padded character runs', () => {
|
||
expect(foldSignalText('snaaaapchat')).toBe('snapchat');
|
||
});
|
||
});
|
||
|
||
describe('findOffPlatformSignal', () => {
|
||
it('returns null for an empty or whitespace message', () => {
|
||
expect(findOffPlatformSignal('')).toBeNull();
|
||
expect(findOffPlatformSignal(' ')).toBeNull();
|
||
});
|
||
|
||
it('detects named messaging platforms', () => {
|
||
for (const text of [
|
||
'im on telegram if you want to chat there',
|
||
'do you have whatsapp?',
|
||
'my snapchat is jess_xo',
|
||
'i have a discord',
|
||
'kik me',
|
||
'wickr?',
|
||
]) {
|
||
expect(findOffPlatformSignal(text)?.kind, text).toBe('contact-platform');
|
||
}
|
||
});
|
||
|
||
it('detects obfuscated platform names', () => {
|
||
expect(findOffPlatformSignal('t3l3gr4m @jess')?.label).toBe('Telegram');
|
||
expect(findOffPlatformSignal('s n a p c h a t?')?.label).toBe('Snapchat');
|
||
expect(findOffPlatformSignal('whatsapp me')?.label).toBe('WhatsApp');
|
||
expect(findOffPlatformSignal('0nlyf4ns.com/jess')?.label).toBe('OnlyFans');
|
||
});
|
||
|
||
it('detects platform link shorteners without the platform name', () => {
|
||
expect(findOffPlatformSignal('here: t.me/jessxo')?.label).toBe('Telegram');
|
||
expect(findOffPlatformSignal('wa.me/447700900123')?.label).toBe('WhatsApp');
|
||
});
|
||
|
||
it('detects seller funnels and link-in-bio pushes', () => {
|
||
expect(findOffPlatformSignal('my linktr.ee/jess has everything')?.kind).toBe('seller-link');
|
||
expect(findOffPlatformSignal('all my links are in my bio')?.kind).toBe('seller-link');
|
||
expect(findOffPlatformSignal('subs are on fansly now')?.kind).toBe('seller-link');
|
||
});
|
||
|
||
it('detects payment rails and cashtags', () => {
|
||
expect(findOffPlatformSignal('cashapp me and ill send more')?.kind).toBe('payment');
|
||
expect(findOffPlatformSignal('my tag is $jessxo')?.kind).toBe('payment');
|
||
expect(findOffPlatformSignal('paypal works too')?.kind).toBe('payment');
|
||
});
|
||
|
||
it('does not read a plain dollar amount as a cashtag', () => {
|
||
expect(findOffPlatformSignal('it cost me $200 lol')).toBeNull();
|
||
});
|
||
|
||
it('detects the age and bot verification scam', () => {
|
||
expect(findOffPlatformSignal('i need you to do age verification first')?.kind).toBe('verification-scam');
|
||
expect(findOffPlatformSignal('just verify you are not a bot on this site')?.kind).toBe('verification-scam');
|
||
expect(findOffPlatformSignal('send the verification code from the link')?.kind).toBe('verification-scam');
|
||
});
|
||
|
||
it('detects generic requests to continue somewhere else', () => {
|
||
expect(findOffPlatformSignal('add me on the other app')?.kind).toBe('handle-ask');
|
||
expect(findOffPlatformSignal('whats your handle?')?.kind).toBe('handle-ask');
|
||
expect(findOffPlatformSignal('lets talk somewhere else')?.kind).toBe('handle-ask');
|
||
expect(findOffPlatformSignal('wanna take this off reddit?')?.kind).toBe('handle-ask');
|
||
});
|
||
|
||
it('treats "snap" as a photo rather than the app', () => {
|
||
for (const text of [
|
||
'send me a snap',
|
||
'ill snap something for you later',
|
||
'got any snaps of you',
|
||
'heres a snap from this morning',
|
||
]) {
|
||
expect(findOffPlatformSignal(text), text).toBeNull();
|
||
}
|
||
});
|
||
|
||
it('leaves other ordinary messages alone', () => {
|
||
for (const text of [
|
||
'hey, hows your week going?',
|
||
'i signal my turns like a proper driver',
|
||
'that discount was insane',
|
||
'im 32 and from manchester',
|
||
'i work in insurance, its as dull as it sounds',
|
||
'i dont really send pics to strangers sorry',
|
||
'we should get a drink sometime',
|
||
'i barely use this app, im hardly ever off reddit',
|
||
]) {
|
||
expect(findOffPlatformSignal(text), text).toBeNull();
|
||
}
|
||
});
|
||
|
||
it('keeps an on-platform reply from matching the contact-request rule', () => {
|
||
expect(findOffPlatformSignal('just message me on reddit whenever')).toBeNull();
|
||
});
|
||
|
||
it('builds a reason that names the kind and the evidence', () => {
|
||
const signal = findOffPlatformSignal('my telegram is @jess');
|
||
expect(signal).not.toBeNull();
|
||
expect(offPlatformSignalReason(signal!)).toBe(
|
||
'Off-platform contact push detected (Telegram: "telegram").',
|
||
);
|
||
});
|
||
});
|