@gonzih/of-scraper 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/PLAN.md ADDED
@@ -0,0 +1,50 @@
1
+ # PLAN.md — of-scraper
2
+
3
+ ## Task Understanding
4
+ Build a Node.js + TypeScript service that:
5
+ 1. Maintains a persistent Puppeteer browser session logged into OnlyFans
6
+ 2. Polls the chats page every 30s for new messages
7
+ 3. For each new message, scrapes sender profile + financial data
8
+ 4. Publishes all data to Redis Streams (of:messages, of:profiles, of:financials)
9
+ 5. Avoids duplicates via Redis SET of:seen_messages
10
+ 6. Handles session save/restore across restarts
11
+
12
+ ## Approaches
13
+
14
+ ### Approach A: Monolithic single-file
15
+ - Everything in index.ts
16
+ - Simple, but hard to test and maintain
17
+ - Not chosen — doesn't match the required modular structure
18
+
19
+ ### Approach B: Modular TypeScript (chosen)
20
+ - Separate modules: browser.ts, messages.ts, profiles.ts, financials.ts, redis.ts, index.ts
21
+ - Each module has a clear single responsibility
22
+ - Easy to test each part independently
23
+ - Matches the spec exactly
24
+
25
+ ### Approach C: Worker threads / separate processes
26
+ - Each scraping task runs in a worker thread
27
+ - Better concurrency, but massively over-engineered for a single-user scraper
28
+ - Not chosen — unnecessary complexity
29
+
30
+ ## Chosen Approach: B (Modular TypeScript)
31
+
32
+ ## Files to Create
33
+ - `package.json` — dependencies, scripts, bin entry
34
+ - `tsconfig.json` — TypeScript config
35
+ - `.npmrc` — npm auth token (gitignored)
36
+ - `src/browser.ts` — Puppeteer launch, session save/restore
37
+ - `src/messages.ts` — Scrape chat list
38
+ - `src/profiles.ts` — Scrape profile page
39
+ - `src/financials.ts` — Scrape financial data from chat thread
40
+ - `src/redis.ts` — Redis client + XADD helpers
41
+ - `src/index.ts` — Main loop + orchestration
42
+ - `README.md` — Setup docs
43
+
44
+ ## Risks & Unknowns
45
+ - OnlyFans DOM structure changes frequently — selectors may need adjustment
46
+ - puppeteer-extra-plugin-stealth availability/compatibility with latest puppeteer
47
+ - Session serialization completeness (cookies alone may not be sufficient)
48
+ - Rate limiting by OnlyFans if scraping too aggressively
49
+ - npm package name `@gonzih/of-scraper` must be available on npm registry
50
+ - .npmrc must be gitignored to avoid GitHub push protection blocking the token
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @gonzih/of-scraper
2
+
3
+ A Node.js + TypeScript service that maintains a live Puppeteer browser session logged into OnlyFans, scrapes incoming messages, and feeds them into Redis Streams.
4
+
5
+ ## Features
6
+
7
+ - Persistent browser sessions via `session.json` (cookies + localStorage)
8
+ - Polls `/my/chats` every 30s for new messages
9
+ - For each new message, scrapes:
10
+ - Sender profile (displayName, bio, profilePic, subscription status)
11
+ - Financial data (tips, purchases, lifetime value)
12
+ - All data published to Redis Streams
13
+ - Duplicate prevention via Redis SET `of:seen_messages`
14
+ - Stealth mode via `puppeteer-extra-plugin-stealth`
15
+
16
+ ## Prerequisites
17
+
18
+ - Node.js 18+
19
+ - Redis 6+ (running locally or via `REDIS_URL`)
20
+
21
+ ## Setup
22
+
23
+ ```bash
24
+ npx @gonzih/of-scraper
25
+ ```
26
+
27
+ Or install globally:
28
+
29
+ ```bash
30
+ npm install -g @gonzih/of-scraper
31
+ of-scraper
32
+ ```
33
+
34
+ ## Initial Login
35
+
36
+ On first run (or when the session has expired), the service opens a browser window and waits for you to log in manually:
37
+
38
+ 1. Set `HEADLESS=false` to see the browser
39
+ 2. Log in to OnlyFans in the browser window
40
+ 3. The service detects the successful login, saves `session.json`, and starts polling
41
+
42
+ Subsequent runs will restore the saved session automatically.
43
+
44
+ ## Environment Variables
45
+
46
+ | Variable | Default | Description |
47
+ |----------|---------|-------------|
48
+ | `REDIS_URL` | `redis://localhost:6379` | Redis connection URL |
49
+ | `POLL_INTERVAL_MS` | `30000` | Polling interval in milliseconds |
50
+ | `SESSION_FILE` | `./session.json` | Path to session persistence file |
51
+ | `HEADLESS` | `true` | Run browser headlessly (`false` for initial login) |
52
+
53
+ ## Redis Stream Schemas
54
+
55
+ ### `of:messages`
56
+
57
+ | Field | Type | Description |
58
+ |-------|------|-------------|
59
+ | `messageId` | string | Unique message identifier |
60
+ | `fromUsername` | string | Sender's OnlyFans username |
61
+ | `text` | string | Message text content |
62
+ | `timestamp` | string | ISO 8601 timestamp |
63
+ | `threadId` | string | Chat thread ID |
64
+
65
+ ### `of:profiles`
66
+
67
+ | Field | Type | Description |
68
+ |-------|------|-------------|
69
+ | `username` | string | OnlyFans username |
70
+ | `displayName` | string | Display name |
71
+ | `isSubscribed` | string | `"true"` or `"false"` |
72
+ | `profilePic` | string | URL of profile picture |
73
+ | `bio` | string | Profile bio text |
74
+ | `fetchedAt` | string | ISO 8601 fetch timestamp |
75
+
76
+ ### `of:financials`
77
+
78
+ | Field | Type | Description |
79
+ |-------|------|-------------|
80
+ | `username` | string | OnlyFans username |
81
+ | `totalTips` | string | Total tips received from this fan |
82
+ | `totalPurchases` | string | Total PPV/content purchases |
83
+ | `subscriptionStatus` | string | Current subscription status |
84
+ | `lastPaymentDate` | string | Date of last payment |
85
+ | `lifetimeValue` | string | Total lifetime spend |
86
+ | `updatedAt` | string | ISO 8601 update timestamp |
87
+
88
+ ## Consuming Streams
89
+
90
+ ```bash
91
+ # Read all messages from the beginning
92
+ redis-cli XREAD COUNT 10 STREAMS of:messages 0-0
93
+
94
+ # Read new messages since last read (consumer group pattern)
95
+ redis-cli XGROUP CREATE of:messages myapp $ MKSTREAM
96
+ redis-cli XREADGROUP GROUP myapp consumer1 COUNT 10 STREAMS of:messages >
97
+ ```
98
+
99
+ ## Error Handling
100
+
101
+ If the service cannot verify the login state, it saves a screenshot to `/tmp/of_login_required.png` for debugging. Delete `session.json` and restart with `HEADLESS=false` to re-authenticate.
102
+
103
+ ## Architecture
104
+
105
+ ```
106
+ src/
107
+ browser.ts — Puppeteer session management (launch, save, restore)
108
+ messages.ts — Scrape chat list, extract new messages
109
+ profiles.ts — Scrape profile details for a username
110
+ financials.ts — Scrape transaction/tip history for a username
111
+ redis.ts — Redis client + XADD stream helpers
112
+ index.ts — Main loop + orchestration
113
+ ```
package/TODO.md ADDED
@@ -0,0 +1,27 @@
1
+ # TODO.md — of-scraper
2
+
3
+ ## Setup
4
+ - [x] Create package.json with all dependencies
5
+ - [x] Create tsconfig.json
6
+ - [x] Create .npmrc with auth token (gitignored)
7
+
8
+ ## Source Files
9
+ - [x] src/redis.ts — Redis client + XADD stream helpers
10
+ - [x] src/browser.ts — Puppeteer session management
11
+ - [x] src/messages.ts — Scrape chat list for new messages
12
+ - [x] src/profiles.ts — Scrape profile page for a username
13
+ - [x] src/financials.ts — Scrape financial data from chat thread
14
+ - [x] src/index.ts — Main loop + orchestration
15
+
16
+ ## Documentation
17
+ - [x] README.md
18
+
19
+ ## Build & Publish
20
+ - [x] Run npm install
21
+ - [x] Run npm run build (tsc)
22
+ - [ ] git checkout -b feat/mvp
23
+ - [ ] git add + commit
24
+ - [ ] git push
25
+ - [ ] gh pr create
26
+ - [ ] gh pr merge
27
+ - [ ] npm version 1.0.0 && npm publish
@@ -0,0 +1,8 @@
1
+ import type { Browser, Page } from 'puppeteer';
2
+ export declare function launchBrowser(): Promise<Browser>;
3
+ export declare function saveSession(page: Page): Promise<void>;
4
+ export declare function restoreSession(page: Page): Promise<void>;
5
+ export declare function sessionFileExists(): Promise<boolean>;
6
+ export declare function ensureLoggedIn(browser: Browser): Promise<Page>;
7
+ export declare function takeErrorScreenshot(page: Page): Promise<void>;
8
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAe/C,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAWtD;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB3D;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB9D;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE1D;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAkCpE;AAmBD,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAOnE"}
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.takeErrorScreenshot = exports.ensureLoggedIn = exports.sessionFileExists = exports.restoreSession = exports.saveSession = exports.launchBrowser = void 0;
30
+ const puppeteer_extra_1 = __importDefault(require("puppeteer-extra"));
31
+ const puppeteer_extra_plugin_stealth_1 = __importDefault(require("puppeteer-extra-plugin-stealth"));
32
+ const fs = __importStar(require("fs"));
33
+ const path = __importStar(require("path"));
34
+ puppeteer_extra_1.default.use((0, puppeteer_extra_plugin_stealth_1.default)());
35
+ const SESSION_FILE = process.env.SESSION_FILE ?? './session.json';
36
+ const HEADLESS = process.env.HEADLESS !== 'false';
37
+ async function launchBrowser() {
38
+ const browser = await puppeteer_extra_1.default.launch({
39
+ headless: HEADLESS,
40
+ args: [
41
+ '--no-sandbox',
42
+ '--disable-setuid-sandbox',
43
+ '--disable-blink-features=AutomationControlled',
44
+ ],
45
+ });
46
+ console.log(`[${new Date().toISOString()}] Browser launched (headless=${HEADLESS})`);
47
+ return browser;
48
+ }
49
+ exports.launchBrowser = launchBrowser;
50
+ async function saveSession(page) {
51
+ const cookies = await page.cookies();
52
+ const localStorage = await page.evaluate(() => {
53
+ const data = {};
54
+ for (let i = 0; i < window.localStorage.length; i++) {
55
+ const key = window.localStorage.key(i);
56
+ if (key !== null) {
57
+ data[key] = window.localStorage.getItem(key) ?? '';
58
+ }
59
+ }
60
+ return data;
61
+ });
62
+ const session = { cookies, localStorage };
63
+ const sessionPath = path.resolve(SESSION_FILE);
64
+ fs.writeFileSync(sessionPath, JSON.stringify(session, null, 2));
65
+ console.log(`[${new Date().toISOString()}] Session saved to ${sessionPath}`);
66
+ }
67
+ exports.saveSession = saveSession;
68
+ async function restoreSession(page) {
69
+ const sessionPath = path.resolve(SESSION_FILE);
70
+ if (!fs.existsSync(sessionPath)) {
71
+ console.log(`[${new Date().toISOString()}] No session file found at ${sessionPath}`);
72
+ return;
73
+ }
74
+ const raw = fs.readFileSync(sessionPath, 'utf-8');
75
+ const session = JSON.parse(raw);
76
+ await page.goto('https://onlyfans.com', { waitUntil: 'domcontentloaded' });
77
+ await page.setCookie(...session.cookies);
78
+ await page.evaluate((lsData) => {
79
+ for (const [key, value] of Object.entries(lsData)) {
80
+ window.localStorage.setItem(key, value);
81
+ }
82
+ }, session.localStorage);
83
+ console.log(`[${new Date().toISOString()}] Session restored from ${sessionPath}`);
84
+ }
85
+ exports.restoreSession = restoreSession;
86
+ async function sessionFileExists() {
87
+ return fs.existsSync(path.resolve(SESSION_FILE));
88
+ }
89
+ exports.sessionFileExists = sessionFileExists;
90
+ async function ensureLoggedIn(browser) {
91
+ const page = await browser.newPage();
92
+ await page.setViewport({ width: 1280, height: 900 });
93
+ await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
94
+ if (await sessionFileExists()) {
95
+ console.log(`[${new Date().toISOString()}] Restoring existing session...`);
96
+ await restoreSession(page);
97
+ await page.goto('https://onlyfans.com/my/chats', { waitUntil: 'networkidle2', timeout: 30000 });
98
+ const isLoggedIn = await checkLoggedIn(page);
99
+ if (isLoggedIn) {
100
+ console.log(`[${new Date().toISOString()}] Session valid — logged in`);
101
+ return page;
102
+ }
103
+ console.log(`[${new Date().toISOString()}] Session invalid or expired`);
104
+ }
105
+ // Need to log in manually
106
+ await page.goto('https://onlyfans.com/login', { waitUntil: 'networkidle2', timeout: 30000 });
107
+ console.log(`[${new Date().toISOString()}] Waiting for manual login at https://onlyfans.com/login ...`);
108
+ // Wait for navigation away from /login (indicates successful login)
109
+ await page.waitForFunction(() => !window.location.href.includes('/login'), { timeout: 300000 } // 5 minute timeout for human to log in
110
+ );
111
+ console.log(`[${new Date().toISOString()}] Login detected — saving session`);
112
+ await saveSession(page);
113
+ return page;
114
+ }
115
+ exports.ensureLoggedIn = ensureLoggedIn;
116
+ async function checkLoggedIn(page) {
117
+ try {
118
+ const result = await page.evaluate(() => {
119
+ return (document.querySelector('.b-header__user') !== null ||
120
+ document.querySelector('[data-type="messages"]') !== null ||
121
+ document.querySelector('.g-header__left .b-ddmenu') !== null ||
122
+ document.querySelector('.b-chats') !== null ||
123
+ document.querySelector('.l-sidebar__user') !== null);
124
+ });
125
+ return result;
126
+ }
127
+ catch {
128
+ return false;
129
+ }
130
+ }
131
+ async function takeErrorScreenshot(page) {
132
+ try {
133
+ await page.screenshot({ path: '/tmp/of_login_required.png', fullPage: true });
134
+ console.log(`[${new Date().toISOString()}] Screenshot saved to /tmp/of_login_required.png`);
135
+ }
136
+ catch (err) {
137
+ console.error(`[${new Date().toISOString()}] Failed to take screenshot:`, err);
138
+ }
139
+ }
140
+ exports.takeErrorScreenshot = takeErrorScreenshot;
141
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Page } from 'puppeteer';
2
+ export interface Financials {
3
+ username: string;
4
+ totalTips: string;
5
+ totalPurchases: string;
6
+ subscriptionStatus: string;
7
+ lastPaymentDate: string;
8
+ lifetimeValue: string;
9
+ updatedAt: string;
10
+ }
11
+ export declare function scrapeFinancials(page: Page, username: string, threadId: string): Promise<Financials | null>;
12
+ //# sourceMappingURL=financials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"financials.d.ts","sourceRoot":"","sources":["../src/financials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA2FjH"}
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scrapeFinancials = void 0;
4
+ async function scrapeFinancials(page, username, threadId) {
5
+ console.log(`[${new Date().toISOString()}] Fetching financials for @${username} (thread ${threadId})`);
6
+ const chatUrl = `https://onlyfans.com/my/chats/chat/${threadId}`;
7
+ try {
8
+ await page.goto(chatUrl, { waitUntil: 'networkidle2', timeout: 30000 });
9
+ }
10
+ catch (err) {
11
+ console.error(`[${new Date().toISOString()}] Failed to navigate to thread for financials:`, err);
12
+ return null;
13
+ }
14
+ // Try to open the statistics/info panel
15
+ try {
16
+ const statsBtnSelectors = [
17
+ 'button[class*="statistic"]',
18
+ 'button[class*="stat"]',
19
+ '[data-type="stats"]',
20
+ 'button[title*="Statistic"]',
21
+ 'button[aria-label*="statistic"]',
22
+ '.b-chat__info-btn',
23
+ '[class*="infoBtn"]',
24
+ ];
25
+ for (const sel of statsBtnSelectors) {
26
+ const btn = await page.$(sel);
27
+ if (btn) {
28
+ await btn.click();
29
+ await new Promise((r) => setTimeout(r, 1500));
30
+ break;
31
+ }
32
+ }
33
+ }
34
+ catch {
35
+ // Stats panel may already be visible or not available
36
+ }
37
+ const financials = await page.evaluate((uname) => {
38
+ const getText = (selectors) => {
39
+ for (const sel of selectors) {
40
+ const el = document.querySelector(sel);
41
+ if (el?.textContent?.trim())
42
+ return el.textContent.trim();
43
+ }
44
+ return '0';
45
+ };
46
+ const totalTips = getText([
47
+ '[class*="tips"] [class*="amount"]',
48
+ '[class*="tip"] [class*="total"]',
49
+ '[data-type="tips"]',
50
+ '.b-stat__tips',
51
+ ]);
52
+ const totalPurchases = getText([
53
+ '[class*="purchase"] [class*="amount"]',
54
+ '[class*="purchase"] [class*="total"]',
55
+ '[data-type="purchase"]',
56
+ '.b-stat__purchases',
57
+ ]);
58
+ const subscriptionStatus = getText([
59
+ '[class*="subscription"] [class*="status"]',
60
+ '[class*="subscriptionStatus"]',
61
+ '[data-type="subscriptionStatus"]',
62
+ ]) || 'unknown';
63
+ const lastPaymentDate = getText([
64
+ '[class*="lastPayment"]',
65
+ '[class*="last-payment"]',
66
+ '[data-type="lastPayment"]',
67
+ ]);
68
+ const lifetimeValue = getText([
69
+ '[class*="lifetimeValue"]',
70
+ '[class*="lifetime"]',
71
+ '[class*="totalSpent"]',
72
+ '[data-type="lifetimeValue"]',
73
+ ]);
74
+ return {
75
+ username: uname,
76
+ totalTips,
77
+ totalPurchases,
78
+ subscriptionStatus,
79
+ lastPaymentDate,
80
+ lifetimeValue,
81
+ updatedAt: new Date().toISOString(),
82
+ };
83
+ }, username);
84
+ console.log(`[${new Date().toISOString()}] Financials for @${username}: tips=${financials.totalTips}, purchases=${financials.totalPurchases}`);
85
+ return financials;
86
+ }
87
+ exports.scrapeFinancials = scrapeFinancials;
88
+ //# sourceMappingURL=financials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"financials.js","sourceRoot":"","sources":["../src/financials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const redis_1 = require("./redis");
5
+ const browser_1 = require("./browser");
6
+ const messages_1 = require("./messages");
7
+ const profiles_1 = require("./profiles");
8
+ const financials_1 = require("./financials");
9
+ const POLL_INTERVAL_MS = parseInt(process.env.POLL_INTERVAL_MS ?? '30000', 10);
10
+ const RATE_LIMIT_MS = 2000;
11
+ async function sleep(ms) {
12
+ return new Promise((resolve) => setTimeout(resolve, ms));
13
+ }
14
+ async function processMessage(page, messageId, fromUsername, text, timestamp, threadId) {
15
+ const ts = new Date().toISOString();
16
+ console.log(`[${ts}] Processing message ${messageId} from @${fromUsername}`);
17
+ await (0, redis_1.xadd)('of:messages', {
18
+ messageId,
19
+ fromUsername,
20
+ text,
21
+ timestamp,
22
+ threadId,
23
+ });
24
+ await sleep(RATE_LIMIT_MS);
25
+ const profile = await (0, profiles_1.scrapeProfile)(page, fromUsername);
26
+ if (profile) {
27
+ await (0, redis_1.xadd)('of:profiles', {
28
+ username: profile.username,
29
+ displayName: profile.displayName,
30
+ isSubscribed: String(profile.isSubscribed),
31
+ profilePic: profile.profilePic,
32
+ bio: profile.bio,
33
+ fetchedAt: profile.fetchedAt,
34
+ });
35
+ }
36
+ await sleep(RATE_LIMIT_MS);
37
+ const financials = await (0, financials_1.scrapeFinancials)(page, fromUsername, threadId);
38
+ if (financials) {
39
+ await (0, redis_1.xadd)('of:financials', {
40
+ username: financials.username,
41
+ totalTips: financials.totalTips,
42
+ totalPurchases: financials.totalPurchases,
43
+ subscriptionStatus: financials.subscriptionStatus,
44
+ lastPaymentDate: financials.lastPaymentDate,
45
+ lifetimeValue: financials.lifetimeValue,
46
+ updatedAt: financials.updatedAt,
47
+ });
48
+ }
49
+ }
50
+ async function pollLoop(browser, page) {
51
+ while (true) {
52
+ const loopStart = new Date().toISOString();
53
+ console.log(`[${loopStart}] Starting poll cycle`);
54
+ try {
55
+ const messages = await (0, messages_1.scrapeNewMessages)(page);
56
+ for (const msg of messages) {
57
+ const seen = await (0, redis_1.isSeen)(msg.messageId);
58
+ if (seen) {
59
+ continue;
60
+ }
61
+ await (0, redis_1.markSeen)(msg.messageId);
62
+ await processMessage(page, msg.messageId, msg.fromUsername, msg.text, msg.timestamp, msg.threadId);
63
+ }
64
+ }
65
+ catch (err) {
66
+ console.error(`[${new Date().toISOString()}] Poll cycle error:`, err);
67
+ try {
68
+ await (0, browser_1.takeErrorScreenshot)(page);
69
+ }
70
+ catch {
71
+ // ignore screenshot errors
72
+ }
73
+ }
74
+ console.log(`[${new Date().toISOString()}] Poll cycle complete — sleeping ${POLL_INTERVAL_MS}ms`);
75
+ await sleep(POLL_INTERVAL_MS);
76
+ }
77
+ }
78
+ async function main() {
79
+ console.log(`[${new Date().toISOString()}] of-scraper starting up`);
80
+ console.log(`[${new Date().toISOString()}] POLL_INTERVAL_MS=${POLL_INTERVAL_MS}`);
81
+ await (0, redis_1.connectRedis)();
82
+ let browser = null;
83
+ let page = null;
84
+ try {
85
+ browser = await (0, browser_1.launchBrowser)();
86
+ page = await (0, browser_1.ensureLoggedIn)(browser);
87
+ }
88
+ catch (err) {
89
+ console.error(`[${new Date().toISOString()}] Failed to launch browser or log in:`, err);
90
+ if (browser)
91
+ await browser.close().catch(() => { });
92
+ process.exit(1);
93
+ }
94
+ const shutdown = async () => {
95
+ console.log(`[${new Date().toISOString()}] Shutting down...`);
96
+ if (browser)
97
+ await browser.close().catch(() => { });
98
+ process.exit(0);
99
+ };
100
+ process.on('SIGINT', () => { shutdown().catch(console.error); });
101
+ process.on('SIGTERM', () => { shutdown().catch(console.error); });
102
+ await pollLoop(browser, page);
103
+ }
104
+ main().catch((err) => {
105
+ console.error(`[${new Date().toISOString()}] Fatal error:`, err);
106
+ process.exit(1);
107
+ });
108
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Page } from 'puppeteer';
2
+ export interface Message {
3
+ messageId: string;
4
+ fromUsername: string;
5
+ text: string;
6
+ timestamp: string;
7
+ threadId: string;
8
+ }
9
+ export declare function scrapeNewMessages(page: Page): Promise<Message[]>;
10
+ export declare function scrapeThreadMessages(page: Page, threadId: string): Promise<Message[]>;
11
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,WAAW,OAAO;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAmFtE;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAmE3F"}