From f98c5c732d7650aad3816e4e0a906c8968f6fb91 Mon Sep 17 00:00:00 2001 From: jjbrunton Date: Sun, 9 Aug 2026 17:09:41 +0100 Subject: [PATCH] Treat Telegram references as seller signals --- docs/configuration.md | 2 +- docs/product-and-safety.md | 2 +- src/config/index.ts | 3 +++ tests/config.test.ts | 3 +++ tests/prompts.test.ts | 3 +++ tests/reddit-api.test.ts | 16 ++++++++++++++++ 6 files changed, 27 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index cf36b85..235a3d2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -33,7 +33,7 @@ Never document real values. `.env` is ignored and may contain production credent - `pollIntervalMs`: legacy/scheduled polling cadence. - `userAgent`: Reddit JSON request identifier. - `targetGenders`: allowed parsed values. -- `filterSellers`, `sellerKeywords`, `sellerDomains`: seller policy inputs. +- `filterSellers`, `sellerKeywords`, `sellerDomains`: seller policy inputs. The defaults treat Telegram references, including `t.me` and `telegram.me` links, as seller signals alongside paid-content platforms and payment prompts. - `prioritizeRecentPosts`, `recentWindowMinutes`: recency/fallback behavior. - `maxReadyCandidates`: hard ceiling for the active candidate target; valid range 1–500. Runtime lowers the actual target to what authenticated running accounts can send within `recentWindowMinutes` under current rolling-hour and message-spacing limits. diff --git a/docs/product-and-safety.md b/docs/product-and-safety.md index 5e7eeaa..b113355 100644 --- a/docs/product-and-safety.md +++ b/docs/product-and-safety.md @@ -30,7 +30,7 @@ Configured subreddits have a source type: - `image`: image-led posts are downloaded and analyzed before an opener is generated. - `chat`: text-led posts require a parseable self-declared gender and adult age, then use the chat-opener prompt. -Seller filtering combines the candidate post with Reddit profile/about and recent activity evidence. The configured keyword/domain lists are policy inputs, not a substitute for the surrounding screening flow. A profile lookup failure is not itself proof that a person is a seller; network and rate-limit failures must remain distinguishable from a rejection. +Seller filtering combines the candidate post with Reddit profile/about and recent activity evidence. The default policy treats Telegram names and links as seller signals alongside paid-content platforms and payment prompts. The configured keyword/domain lists are policy inputs, not a substitute for the surrounding screening flow. A profile lookup failure is not itself proof that a person is a seller; network and rate-limit failures must remain distinguishable from a rejection. The system must not manufacture personal facts. Generated messages must not claim shared location or proximity, invite an in-person meeting, invent identity/history, or ignore a conversation boundary. The final outbound safety check is intentionally separate from prompt wording. diff --git a/src/config/index.ts b/src/config/index.ts index 05188c1..aceaafe 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -66,6 +66,7 @@ export const ConfigSchema = z.object({ 'cashapp', 'venmo', 'paypal', + 'telegram', 'subscribe', 'premium snap', 'premiumsnap', @@ -102,6 +103,8 @@ export const ConfigSchema = z.object({ 'allmylinks.com', 'beacons.ai', 'beacons.page', + 't.me', + 'telegram.me', 'throne.com', 'throne.me', 'fan.ly', diff --git a/tests/config.test.ts b/tests/config.test.ts index 645190f..1815080 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -155,6 +155,9 @@ describe('ConfigSchema', () => { if (result.success) { expect(result.data.reddit.sellerKeywords.length).toBeGreaterThan(0); expect(result.data.reddit.sellerKeywords).toContain('onlyfans'); + expect(result.data.reddit.sellerKeywords).toContain('telegram'); + expect(result.data.reddit.sellerDomains).toContain('t.me'); + expect(result.data.reddit.sellerDomains).toContain('telegram.me'); } }); diff --git a/tests/prompts.test.ts b/tests/prompts.test.ts index cd91bf1..d99d062 100644 --- a/tests/prompts.test.ts +++ b/tests/prompts.test.ts @@ -249,6 +249,9 @@ describe('Prompt Testing', () => { expect(keywords).toContain('manyvids'); expect(keywords).toContain('cash.app'); expect(keywords).toContain('venmo'); + expect(keywords).toContain('telegram'); + expect(config.reddit.sellerDomains).toContain('t.me'); + expect(config.reddit.sellerDomains).toContain('telegram.me'); }); }); }); diff --git a/tests/reddit-api.test.ts b/tests/reddit-api.test.ts index c547060..49deff4 100644 --- a/tests/reddit-api.test.ts +++ b/tests/reddit-api.test.ts @@ -304,6 +304,22 @@ describe('RedditApiClient', () => { expect(get).not.toHaveBeenCalled(); }); + it('treats a Telegram reference in the current post as a seller signal', async () => { + const get = vi.fn(); + vi.mocked(axios.create).mockReturnValue({ get } as any); + const client2 = new RedditApiClient(getTestConfig(), logger); + const post = { + id: 'post-telegram', name: 't3_post-telegram', subreddit: 'test', author: 'testuser', + title: 'Message me there', selftext: 'More content on t.me/example', + url: 'https://reddit.com/post-telegram', permalink: '/r/test/post-telegram', thumbnail: '', + is_self: true, created_utc: 1, over_18: true, spoiler: false, + }; + + await expect(client2.isSellerProfile('testuser', ['telegram'], ['t.me', 'telegram.me'], post)) + .resolves.toBe(true); + expect(get).not.toHaveBeenCalled(); + }); + it('detects seller signals in recent profile activity when the bio is clean', async () => { const get = vi.fn() .mockResolvedValueOnce({ data: { data: { subreddit: { public_description: 'Just me' } } } })