Treat Telegram references as seller signals

This commit is contained in:
jjbrunton committed 2026-08-09 17:09:41 +01:00
1 parent dc3fc6373b
commit f98c5c732d
6 files changed
+27 -2

No files matched your search

+1 -1
View File
@@ -33,7 +33,7 @@ Never document real values. `.env` is ignored and may contain production credent
- `pollIntervalMs`: legacy/scheduled polling cadence. - `pollIntervalMs`: legacy/scheduled polling cadence.
- `userAgent`: Reddit JSON request identifier. - `userAgent`: Reddit JSON request identifier.
- `targetGenders`: allowed parsed values. - `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. - `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. - `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.
+1 -1
View File
@@ -30,7 +30,7 @@ Configured subreddits have a source type:
- `image`: image-led posts are downloaded and analyzed before an opener is generated. - `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. - `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. 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.
+3
View File
@@ -66,6 +66,7 @@ export const ConfigSchema = z.object({
'cashapp', 'cashapp',
'venmo', 'venmo',
'paypal', 'paypal',
'telegram',
'subscribe', 'subscribe',
'premium snap', 'premium snap',
'premiumsnap', 'premiumsnap',
@@ -102,6 +103,8 @@ export const ConfigSchema = z.object({
'allmylinks.com', 'allmylinks.com',
'beacons.ai', 'beacons.ai',
'beacons.page', 'beacons.page',
't.me',
'telegram.me',
'throne.com', 'throne.com',
'throne.me', 'throne.me',
'fan.ly', 'fan.ly',
+3
View File
@@ -155,6 +155,9 @@ describe('ConfigSchema', () => {
if (result.success) { if (result.success) {
expect(result.data.reddit.sellerKeywords.length).toBeGreaterThan(0); expect(result.data.reddit.sellerKeywords.length).toBeGreaterThan(0);
expect(result.data.reddit.sellerKeywords).toContain('onlyfans'); 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');
} }
}); });
+3
View File
@@ -249,6 +249,9 @@ describe('Prompt Testing', () => {
expect(keywords).toContain('manyvids'); expect(keywords).toContain('manyvids');
expect(keywords).toContain('cash.app'); expect(keywords).toContain('cash.app');
expect(keywords).toContain('venmo'); expect(keywords).toContain('venmo');
expect(keywords).toContain('telegram');
expect(config.reddit.sellerDomains).toContain('t.me');
expect(config.reddit.sellerDomains).toContain('telegram.me');
}); });
}); });
}); });
+16
View File
@@ -304,6 +304,22 @@ describe('RedditApiClient', () => {
expect(get).not.toHaveBeenCalled(); 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 () => { it('detects seller signals in recent profile activity when the bio is clean', async () => {
const get = vi.fn() const get = vi.fn()
.mockResolvedValueOnce({ data: { data: { subreddit: { public_description: 'Just me' } } } }) .mockResolvedValueOnce({ data: { data: { subreddit: { public_description: 'Just me' } } } })