-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add programmatic Twitter follow via reverse-engineered web API #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds a Twitter follow feature using web API credentials from Redis, updates env-to-Redis to include TWITTER_COOKIE, and introduces a manual test script to exercise followUser. Implements credential decryption, HTTP POST to X.com friendships/create, timeout handling, and detailed error reporting. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Dev as Dev Script
participant Test as testTwitterFollow.ts
participant API as followUser()
participant Cred as getLatestTwitterCredentialsFromRedis()
participant Redis as Redis
participant X as X.com API (friendships/create)
Dev->>Test: run node src/testTwitterFollow.ts
Test->>API: followUser(userId)
alt options not provided
API->>Cred: fetch latest credentials
Cred->>Redis: GET "twitter-accounts"
Redis-->>Cred: encrypted accounts JSON
Cred->>Cred: parse + decrypt bearer/cookie/csrf
Cred-->>API: TwitterAuthOptions
end
API->>X: POST /i/api/1.1/friendships/create.json<br/>headers: auth, cookie, x-csrf-token
X-->>API: 200 JSON or error
alt success
API-->>Test: follow result JSON
Test-->>Dev: "Follow successful!" + result
else error
API-->>Test: throw Error (status/text)
Test-->>Dev: log message/status/response.data
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (7)
src/utils/moveEnvToRedis.ts (1)
24-24
: Adding TWITTER_COOKIE increases secret sensitivity; add TTL/rotation planCookies are live session credentials. Consider storing with TTL and metadata to enable rotation/dedup, e.g., hset + expire, and include createdAt.
Example (outside current hunk):
- Use setEx for 'twitter-accounts:' and maintain a capped list index.
- Add { createdAt, label } fields to each account object.
src/testTwitterFollow.ts (3)
6-9
: Avoid hard‑coded live user ID in repoSource-controlled ID '44196397' can cause unintended follows. Read from env and fail clearly if missing.
- const testUserId = '44196397'; // This was the user ID from your earlier test + const testUserId = process.env.TEST_TWITTER_USER_ID; + if (!testUserId) throw new Error('Set TEST_TWITTER_USER_ID to run this test');
17-30
: Simplify error handling; remove axios‑style branches and ts‑ignoresfollowUser throws a plain Error with message; error.response is never set here. Also set non‑zero exit code on failure.
- } catch (error: unknown) { - if (typeof error === 'object' && error !== null) { - // @ts-ignore - console.error('Follow failed:', (error as any).message || error); - // @ts-ignore - if ('response' in error && error.response) { - // @ts-ignore - console.error('Status:', error.response.status); - // @ts-ignore - console.error('Response:', error.response.data); - } - } else { - console.error('Follow failed:', error); - } - } + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : String(error); + console.error('Follow failed:', msg); + process.exitCode = 1; + }
34-34
: Gate manual test executionPrevent accidental runs in non‑dev contexts.
-testFollowUser(); +if (process.env.RUN_TWITTER_FOLLOW_TEST === '1') { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + testFollowUser(); +}src/twitterApiFollow.ts (3)
2-5
: Use global fetch and URLSearchParams; drop node-fetch importAvoid ESM/CJS pitfalls and extra dependency.
- -import fetch from 'node-fetch'; -import { URLSearchParams } from 'url'; import { createClient } from 'redis'; import { decrypt } from './lib/encryption'; +// Node 18+: global fetch and URLSearchParams are available
44-53
: Input validation and explicit options precedenceReject empty userId early and simplify options branching.
export async function followUser( userId: string, options?: TwitterAuthOptions ): Promise<any> { - let creds: TwitterAuthOptions; - if (options) { - creds = options; - } else { - creds = await getLatestTwitterCredentialsFromRedis(); - } + if (!userId || !userId.trim()) throw new Error('userId is required'); + const creds = options ?? await getLatestTwitterCredentialsFromRedis();
82-86
: Return typed JSON and include response text on JSON parse failureImprove debuggability by parsing conditionally and typing the success case.
- if (!res.ok) { - const error = await res.text(); - throw new Error(`Twitter follow failed: ${res.status} ${error}`); - } - return res.json(); + if (!res.ok) { + const text = await res.text(); + throw new Error(`Twitter follow failed: ${res.status} ${text}`); + } + const contentType = res.headers.get('content-type') || ''; + return contentType.includes('application/json') ? res.json() : res.text();Add a lightweight type to the module (outside this hunk) for callers:
export interface FollowResponse { // minimal shape; extend as needed id_str?: string; following?: boolean; // ... }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.json
is excluded by!**/package-lock.json
📒 Files selected for processing (4)
package.json
(1 hunks)src/testTwitterFollow.ts
(1 hunks)src/twitterApiFollow.ts
(1 hunks)src/utils/moveEnvToRedis.ts
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/testTwitterFollow.ts (1)
src/twitterApiFollow.ts (1)
followUser
(44-87)
src/twitterApiFollow.ts (1)
src/lib/encryption.ts (1)
decrypt
(29-41)
…andling in Twitter API functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (3)
src/twitterApiFollow.ts (3)
32-38
: Missing credential validation and CSRF token verification.The current implementation doesn't validate that decrypted credentials are non-empty or verify that the CSRF token matches the ct0 cookie value, which can lead to authentication failures.
Apply this fix to validate credentials:
const latest = arr[arr.length - 1]; - // Decrypt all fields - return { - bearerToken: decrypt(latest.TWITTER_BEARER), - cookie: decrypt(latest.TWITTER_COOKIE), - csrfToken: decrypt(latest.TWITTER_CSRF_TOKEN), - }; + const bearerToken = decrypt(latest.TWITTER_BEARER); + const cookie = decrypt(latest.TWITTER_COOKIE); + const csrfToken = decrypt(latest.TWITTER_CSRF_TOKEN); + + if (!bearerToken || !cookie || !csrfToken) { + throw new Error('Incomplete Twitter credentials in Redis (bearerToken/cookie/csrfToken)'); + } + + // Verify ct0 cookie matches CSRF token + const ct0 = /(?:^|;\s*)ct0=([^;]+)/.exec(cookie)?.[1]; + if (ct0 && ct0 !== csrfToken) { + throw new Error('CSRF token mismatch: x-csrf-token does not match ct0 cookie'); + } + + return { bearerToken, cookie, csrfToken };
47-56
: Add safety controls for reverse-engineered web API usage.This function uses a reverse-engineered Twitter web endpoint with real session cookies. The official Twitter API documentation shows that HTTP 403 may be returned for various reasons, and operations are asynchronous with eventual consistency. Without proper safeguards, this could violate Twitter's Terms of Service or cause account suspensions.
Add the following safety controls:
export async function followUser( userId: string, options?: TwitterAuthOptions ): Promise<any> { + // Gate behind explicit feature flag + if (process.env.ALLOW_TWITTER_WEB_AUTOMATION !== '1') { + throw new Error('Twitter web automation is disabled. Set ALLOW_TWITTER_WEB_AUTOMATION=1 to enable.'); + } + let creds: TwitterAuthOptions;Additionally, implement rate limiting and retry logic with exponential backoff for 429/420 responses.
92-96
: Add retry logic for rate limiting and improve error handling.The current error handling doesn't account for Twitter's rate limiting responses (429, 420) or implement retry logic. Additionally, error messages could expose sensitive response data.
Implement retry logic with exponential backoff:
- if (!res.ok) { - const error = await res.text(); - throw new Error(`Twitter follow failed: ${res.status} ${error}`); - } - return res.json(); + if (!res.ok) { + if (res.status === 429 || res.status === 420) { + // Rate limited - implement exponential backoff + const retryAfter = res.headers.get('retry-after'); + const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.min(1000 * Math.pow(2, attempt), 30000); + throw new Error(`Twitter API rate limited: ${res.status}. Retry after ${delay}ms`); + } + + // Log error details internally, return sanitized message + const errorText = await res.text(); + console.error(`Twitter follow failed for user ${userId}: ${res.status} ${errorText}`); + throw new Error(`Twitter follow failed: ${res.status}`); + } + return res.json();Consider implementing a full retry wrapper function with exponential backoff and jitter.
🧹 Nitpick comments (1)
src/twitterApiFollow.ts (1)
1-1
: Consider runtime dependency implications.The comment indicates reliance on global fetch and URLSearchParams in Node 18+. While Node 18+ includes global fetch, it's marked as experimental in some versions. For production use, consider explicitly importing a fetch implementation for better stability.
-// Use global fetch and URLSearchParams (Node 18+) +import fetch from 'node-fetch'; import { createClient } from 'redis'; import { decrypt } from './lib/encryption';
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/twitterApiFollow.ts
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/twitterApiFollow.ts (1)
src/lib/encryption.ts (1)
decrypt
(29-41)
🔇 Additional comments (3)
src/twitterApiFollow.ts (3)
14-23
: Good improvement implementing Redis client cleanup.The implementation properly handles Redis client cleanup in a finally block as suggested in the previous review. This ensures the client is closed even if connection or data retrieval fails.
74-90
: Good implementation of timeout and browser headers.The code properly implements the AbortController timeout mechanism and includes appropriate browser headers (origin, referer, user-agent) that make the request resemble a legitimate web client. This addresses the previous review feedback.
57-72
: Verify follow request parameters against current Twitter/X web client (src/twitterApiFollow.ts:57-72)
Capture the actual network request sent by X.com in your browser’s DevTools when following a user and confirm that the URLSearchParams keys exactly match those sent by the web client to avoid automated-request detection.
Description:
This enables automated, scalable following of Twitter accounts without manual browser interaction.
Summary by CodeRabbit
New Features
Chores
Tests