@the-convocation/twitter-scraper
Version:
A port of n0madic/twitter-scraper to Node.js.
1,528 lines (1,513 loc) • 157 kB
JavaScript
'use strict';
var debug = require('debug');
var toughCookie = require('tough-cookie');
var setCookie = require('set-cookie-parser');
var headersPolyfill = require('headers-polyfill');
var fetch = require('cross-fetch');
var typebox = require('@sinclair/typebox');
var value = require('@sinclair/typebox/value');
var OTPAuth = require('otpauth');
var stringify = require('json-stable-stringify');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var OTPAuth__namespace = /*#__PURE__*/_interopNamespaceDefault(OTPAuth);
class ApiError extends Error {
constructor(response, data) {
super(
`Response status: ${response.status} | headers: ${JSON.stringify(
headersToString(response.headers)
)} | data: ${typeof data === "string" ? data : JSON.stringify(data)}`
);
this.response = response;
this.data = data;
}
static async fromResponse(response) {
let data = void 0;
try {
if (response.headers.get("content-type")?.includes("application/json")) {
data = await response.json();
} else {
data = await response.text();
}
} catch {
try {
data = await response.text();
} catch {
}
}
return new ApiError(response, data);
}
}
function headersToString(headers) {
const result = [];
headers.forEach((value, key) => {
result.push(`${key}: ${value}`);
});
return result.join("\n");
}
class AuthenticationError extends Error {
constructor(message) {
super(message || "Authentication failed");
this.name = "AuthenticationError";
}
}
const log$8 = debug("twitter-scraper:rate-limit");
class WaitingRateLimitStrategy {
async onRateLimit({ response: res }) {
const xRateLimitLimit = res.headers.get("x-rate-limit-limit");
const xRateLimitRemaining = res.headers.get("x-rate-limit-remaining");
const xRateLimitReset = res.headers.get("x-rate-limit-reset");
log$8(
`Rate limit event: limit=${xRateLimitLimit}, remaining=${xRateLimitRemaining}, reset=${xRateLimitReset}`
);
if (xRateLimitRemaining == "0" && xRateLimitReset) {
const currentTime = (/* @__PURE__ */ new Date()).valueOf() / 1e3;
const timeDeltaMs = 1e3 * (parseInt(xRateLimitReset) - currentTime);
await new Promise((resolve) => setTimeout(resolve, timeDeltaMs));
}
}
}
class ErrorRateLimitStrategy {
async onRateLimit({ response: res }) {
throw await ApiError.fromResponse(res);
}
}
const log$7 = debug("twitter-scraper:castle");
var FieldEncoding = /* @__PURE__ */ ((FieldEncoding2) => {
FieldEncoding2[FieldEncoding2["Empty"] = -1] = "Empty";
FieldEncoding2[FieldEncoding2["Marker"] = 1] = "Marker";
FieldEncoding2[FieldEncoding2["Byte"] = 3] = "Byte";
FieldEncoding2[FieldEncoding2["EncryptedBytes"] = 4] = "EncryptedBytes";
FieldEncoding2[FieldEncoding2["CompactInt"] = 5] = "CompactInt";
FieldEncoding2[FieldEncoding2["RoundedByte"] = 6] = "RoundedByte";
FieldEncoding2[FieldEncoding2["RawAppend"] = 7] = "RawAppend";
return FieldEncoding2;
})(FieldEncoding || {});
const TWITTER_CASTLE_PK = "AvRa79bHyJSYSQHnRpcVtzyxetSvFerx";
const XXTEA_KEY = [1164413191, 3891440048, 185273099, 2746598870];
const PER_FIELD_KEY_TAIL = [
16373134,
643144773,
1762804430,
1186572681,
1164413191
];
const TS_EPOCH = 1535e6;
const SDK_VERSION = 27008;
const TOKEN_VERSION = 11;
const FP_PART = {
DEVICE: 0,
// Part 1: hardware/OS/rendering fingerprint
BROWSER: 4,
// Part 2: browser environment fingerprint
TIMING: 7
// Part 3: timing-based fingerprint
};
const DEFAULT_PROFILE = {
locale: "en-US",
language: "en",
timezone: "America/New_York",
screenWidth: 1920,
screenHeight: 1080,
availableWidth: 1920,
availableHeight: 1032,
// 1080 minus Windows taskbar (~48px)
gpuRenderer: "ANGLE (NVIDIA, NVIDIA GeForce GTX 1080 Ti Direct3D11 vs_5_0 ps_5_0, D3D11)",
deviceMemoryGB: 8,
hardwareConcurrency: 24,
colorDepth: 24,
devicePixelRatio: 1
};
const SCREEN_RESOLUTIONS = [
{ w: 1920, h: 1080, ah: 1032 },
{ w: 2560, h: 1440, ah: 1392 },
{ w: 1366, h: 768, ah: 720 },
{ w: 1536, h: 864, ah: 816 },
{ w: 1440, h: 900, ah: 852 },
{ w: 1680, h: 1050, ah: 1002 },
{ w: 3840, h: 2160, ah: 2112 }
];
const DEVICE_MEMORY_VALUES = [4, 8, 8, 16];
const HARDWARE_CONCURRENCY_VALUES = [4, 8, 8, 12, 16, 24];
function randomizeBrowserProfile() {
const screen = SCREEN_RESOLUTIONS[randInt(0, SCREEN_RESOLUTIONS.length - 1)];
return {
...DEFAULT_PROFILE,
screenWidth: screen.w,
screenHeight: screen.h,
availableWidth: screen.w,
availableHeight: screen.ah,
// gpuRenderer intentionally NOT randomized — see JSDoc above
deviceMemoryGB: DEVICE_MEMORY_VALUES[randInt(0, DEVICE_MEMORY_VALUES.length - 1)],
hardwareConcurrency: HARDWARE_CONCURRENCY_VALUES[randInt(0, HARDWARE_CONCURRENCY_VALUES.length - 1)]
};
}
function getRandomBytes(n) {
const buf = new Uint8Array(n);
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) {
globalThis.crypto.getRandomValues(buf);
} else {
for (let i = 0; i < n; i++) buf[i] = Math.floor(Math.random() * 256);
}
return buf;
}
function randInt(min, max) {
return min + Math.floor(Math.random() * (max - min + 1));
}
function randFloat(min, max) {
return min + Math.random() * (max - min);
}
function concat(...arrays) {
const len = arrays.reduce((s, a) => s + a.length, 0);
const out = new Uint8Array(len);
let off = 0;
for (const a of arrays) {
out.set(a, off);
off += a.length;
}
return out;
}
function toHex(input) {
return Array.from(input).map((b) => b.toString(16).padStart(2, "0")).join("");
}
function fromHex(hex) {
const out = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2)
out[i / 2] = parseInt(hex.substring(i, i + 2), 16);
return out;
}
function textEnc(s) {
return new TextEncoder().encode(s);
}
function u8(...vals) {
return new Uint8Array(vals);
}
function be16(v) {
return u8(v >>> 8 & 255, v & 255);
}
function be32(v) {
return u8(v >>> 24 & 255, v >>> 16 & 255, v >>> 8 & 255, v & 255);
}
function xorBytes(data, key) {
const out = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) out[i] = data[i] ^ key[i % key.length];
return out;
}
function xorNibbles(nibbles, keyNibble) {
const k = parseInt(keyNibble, 16);
return nibbles.split("").map((n) => (parseInt(n, 16) ^ k).toString(16)).join("");
}
function base64url(data) {
if (typeof Buffer !== "undefined") {
return Buffer.from(data).toString("base64url");
}
let bin = "";
for (let i = 0; i < data.length; i++) bin += String.fromCharCode(data[i]);
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
function xxteaEncrypt(data, key) {
const padLen = Math.ceil(data.length / 4) * 4;
const padded = new Uint8Array(padLen);
padded.set(data);
const n = padLen / 4;
const v = new Uint32Array(n);
for (let i = 0; i < n; i++) {
v[i] = (padded[i * 4] | padded[i * 4 + 1] << 8 | padded[i * 4 + 2] << 16 | padded[i * 4 + 3] << 24) >>> 0;
}
if (n <= 1) return padded;
const k = new Uint32Array(key.map((x) => x >>> 0));
const DELTA = 2654435769;
const u = n - 1;
let sum = 0;
let z = v[u];
let y;
let rounds = 6 + Math.floor(52 / (u + 1));
while (rounds-- > 0) {
sum = sum + DELTA >>> 0;
const e = sum >>> 2 & 3;
for (let p = 0; p < u; p++) {
y = v[p + 1];
const mx2 = ((z >>> 5 ^ y << 2) >>> 0) + ((y >>> 3 ^ z << 4) >>> 0) ^ ((sum ^ y) >>> 0) + ((k[p & 3 ^ e] ^ z) >>> 0);
v[p] = v[p] + mx2 >>> 0;
z = v[p];
}
y = v[0];
const mx = ((z >>> 5 ^ y << 2) >>> 0) + ((y >>> 3 ^ z << 4) >>> 0) ^ ((sum ^ y) >>> 0) + ((k[u & 3 ^ e] ^ z) >>> 0);
v[u] = v[u] + mx >>> 0;
z = v[u];
}
const out = new Uint8Array(n * 4);
for (let i = 0; i < n; i++) {
out[i * 4] = v[i] & 255;
out[i * 4 + 1] = v[i] >>> 8 & 255;
out[i * 4 + 2] = v[i] >>> 16 & 255;
out[i * 4 + 3] = v[i] >>> 24 & 255;
}
return out;
}
function fieldEncrypt(data, fieldIndex, initTime) {
return xxteaEncrypt(data, [
fieldIndex,
Math.floor(initTime),
...PER_FIELD_KEY_TAIL
]);
}
function encodeTimestampBytes(ms) {
let t = Math.floor(ms / 1e3 - TS_EPOCH);
t = Math.max(Math.min(t, 268435455), 0);
return be32(t);
}
function xorAndAppendKey(buf, key) {
const hex = toHex(buf);
const keyNib = (key & 15).toString(16);
return xorNibbles(hex.substring(1), keyNib) + keyNib;
}
function encodeTimestampEncrypted(ms) {
const tsBytes = encodeTimestampBytes(ms);
const slice = Math.floor(ms) % 1e3;
const sliceBytes = be16(slice);
const k = randInt(0, 15);
return xorAndAppendKey(tsBytes, k) + xorAndAppendKey(sliceBytes, k);
}
function deriveAndXor(keyHex, sliceLen, rotChar, data) {
const sub = keyHex.substring(0, sliceLen).split("");
if (sub.length === 0) return data;
const rot = parseInt(rotChar, 16) % sub.length;
const rotated = sub.slice(rot).concat(sub.slice(0, rot)).join("");
return xorBytes(data, fromHex(rotated));
}
function customFloatEncode(expBits, manBits, value) {
if (value === 0) return 0;
let n = Math.abs(value);
let exp = 0;
while (2 <= n) {
n /= 2;
exp++;
}
while (n < 1 && n > 0) {
n *= 2;
exp--;
}
exp = Math.min(exp, (1 << expBits) - 1);
const frac = n - Math.floor(n);
let mantissa = 0;
if (frac > 0) {
let pos = 1;
let tmp = frac;
while (tmp !== 0 && pos <= manBits) {
tmp *= 2;
const bit = Math.floor(tmp);
mantissa |= bit << manBits - pos;
tmp -= bit;
pos++;
}
}
return exp << manBits | mantissa;
}
function encodeFloatVal(v) {
const n = Math.max(v, 0);
if (n <= 15) return 64 | customFloatEncode(2, 4, n + 1);
return 128 | customFloatEncode(4, 3, n - 14);
}
function encodeField(index, encoding, val, initTime) {
const hdr = u8((31 & index) << 3 | 7 & encoding);
if (encoding === -1 /* Empty */ || encoding === 1 /* Marker */)
return hdr;
let body;
switch (encoding) {
case 3 /* Byte */:
body = u8(val);
break;
case 6 /* RoundedByte */:
body = u8(Math.round(val));
break;
case 5 /* CompactInt */: {
const v = val;
body = v <= 127 ? u8(v) : be16(1 << 15 | 32767 & v);
break;
}
case 4 /* EncryptedBytes */: {
if (initTime == null) {
throw new Error("initTime is required for EncryptedBytes encoding");
}
const enc = fieldEncrypt(val, index, initTime);
body = concat(u8(enc.length), enc);
break;
}
case 7 /* RawAppend */:
body = val instanceof Uint8Array ? val : u8(val);
break;
default:
body = new Uint8Array(0);
}
return concat(hdr, body);
}
function encodeBits(bits, byteSize) {
const numBytes = byteSize / 8;
const arr = new Uint8Array(numBytes);
for (const bit of bits) {
const bi = numBytes - 1 - Math.floor(bit / 8);
if (bi >= 0 && bi < numBytes) arr[bi] |= 1 << bit % 8;
}
return arr;
}
function screenDimBytes(screen, avail) {
const r = 32767 & screen;
const e = 65535 & avail;
return r === e ? be16(32768 | r) : concat(be16(r), be16(e));
}
function boolsToBin(arr, totalBits) {
const e = arr.length > totalBits ? arr.slice(0, totalBits) : arr;
const c = e.length;
let r = 0;
for (let i = c - 1; i >= 0; i--) {
if (e[i]) r |= 1 << c - i - 1;
}
if (c < totalBits) r <<= totalBits - c;
return r;
}
function encodeCodecPlayability() {
const codecs = {
webm: 2,
// VP8/VP9
mp4: 2,
// H.264
ogg: 0,
// Theora (Chrome dropped support)
aac: 2,
// AAC audio
xm4a: 1,
// M4A container
wav: 2,
// PCM audio
mpeg: 2,
// MP3 audio
ogg2: 2
// Vorbis audio
};
const bits = Object.values(codecs).map((c) => c.toString(2).padStart(2, "0")).join("");
return be16(parseInt(bits, 2));
}
const TIMEZONE_ENUM = {
"America/New_York": 0,
"America/Sao_Paulo": 1,
"America/Chicago": 2,
"America/Los_Angeles": 3,
"America/Mexico_City": 4,
"Asia/Shanghai": 5
};
function getTimezoneInfo(tz) {
const knownOffsets = {
"America/New_York": { offset: 20, dstDiff: 4 },
"America/Chicago": { offset: 24, dstDiff: 4 },
"America/Los_Angeles": { offset: 32, dstDiff: 4 },
"America/Denver": { offset: 28, dstDiff: 4 },
"America/Sao_Paulo": { offset: 12, dstDiff: 4 },
"America/Mexico_City": { offset: 24, dstDiff: 4 },
"Asia/Shanghai": { offset: 246, dstDiff: 0 },
"Asia/Tokyo": { offset: 220, dstDiff: 0 },
"Europe/London": { offset: 0, dstDiff: 4 },
"Europe/Berlin": { offset: 252, dstDiff: 4 },
UTC: { offset: 0, dstDiff: 0 }
};
try {
const now = /* @__PURE__ */ new Date();
const jan = new Date(now.getFullYear(), 0, 1);
const jul = new Date(now.getFullYear(), 6, 1);
const getOffset = (date, zone) => {
const utc = new Date(date.toLocaleString("en-US", { timeZone: "UTC" }));
const local = new Date(date.toLocaleString("en-US", { timeZone: zone }));
return (utc.getTime() - local.getTime()) / 6e4;
};
const currentOffset = getOffset(now, tz);
const janOffset = getOffset(jan, tz);
const julOffset = getOffset(jul, tz);
const dstDifference = Math.abs(janOffset - julOffset);
return {
offset: Math.floor(currentOffset / 15) & 255,
dstDiff: Math.floor(dstDifference / 15) & 255
};
} catch {
return knownOffsets[tz] || { offset: 20, dstDiff: 4 };
}
}
function buildDeviceFingerprint(initTime, profile, userAgent) {
const tz = getTimezoneInfo(profile.timezone);
const { Byte, EncryptedBytes, CompactInt, RoundedByte, RawAppend } = FieldEncoding;
const encryptedUA = fieldEncrypt(textEnc(userAgent), 12, initTime);
const uaPayload = concat(u8(1), u8(encryptedUA.length), encryptedUA);
const fields = [
encodeField(0, Byte, 1),
// Platform: Win32
encodeField(1, Byte, 0),
// Vendor: Google Inc.
encodeField(2, EncryptedBytes, textEnc(profile.locale), initTime),
// Locale
encodeField(3, RoundedByte, profile.deviceMemoryGB * 10),
// Device memory (GB * 10)
encodeField(
4,
RawAppend,
concat(
// Screen dimensions (width + height)
screenDimBytes(profile.screenWidth, profile.availableWidth),
screenDimBytes(profile.screenHeight, profile.availableHeight)
)
),
encodeField(5, CompactInt, profile.colorDepth),
// Screen color depth
encodeField(6, CompactInt, profile.hardwareConcurrency),
// CPU logical cores
encodeField(7, RoundedByte, profile.devicePixelRatio * 10),
// Pixel ratio (* 10)
encodeField(8, RawAppend, u8(tz.offset, tz.dstDiff)),
// Timezone offset info
// MIME type hash — captured from Chrome 144 on Windows 10.
// Source: yubie-re/castleio-gen (Python SDK, MIT license).
encodeField(9, RawAppend, u8(2, 125, 95, 201, 167)),
// Browser plugins hash — Chrome no longer exposes plugins to navigator.plugins,
// so this is a fixed hash. Source: yubie-re/castleio-gen (Python SDK, MIT license).
encodeField(10, RawAppend, u8(5, 114, 147, 2, 8)),
encodeField(
11,
RawAppend,
// Browser feature flags
concat(u8(12), encodeBits([0, 1, 2, 3, 4, 5, 6], 16))
),
encodeField(12, RawAppend, uaPayload),
// User agent (encrypted)
// Canvas font rendering hash — generated by Castle.io SDK's canvas fingerprinting (text rendering).
// Captured from Chrome 144 on Windows 10. Source: yubie-re/castleio-gen (Python SDK, MIT license).
encodeField(13, EncryptedBytes, textEnc("54b4b5cf"), initTime),
encodeField(
14,
RawAppend,
// Media input devices
concat(u8(3), encodeBits([0, 1, 2], 8))
),
// Fields 15 (DoNotTrack) and 16 (JavaEnabled) intentionally omitted
encodeField(17, Byte, 0),
// productSub type
// Canvas circle rendering hash — generated by Castle.io SDK's canvas fingerprinting (arc drawing).
// Captured from Chrome 144 on Windows 10. Source: yubie-re/castleio-gen (Python SDK, MIT license).
encodeField(18, EncryptedBytes, textEnc("c6749e76"), initTime),
encodeField(19, EncryptedBytes, textEnc(profile.gpuRenderer), initTime),
// WebGL renderer
encodeField(
20,
EncryptedBytes,
// Epoch locale string
textEnc("12/31/1969, 7:00:00 PM"),
initTime
),
encodeField(
21,
RawAppend,
// WebDriver flags (none set)
concat(u8(8), encodeBits([], 8))
),
encodeField(22, CompactInt, 33),
// eval.toString() length
// Field 23 (navigator.buildID) intentionally omitted (Chrome doesn't have it)
encodeField(24, CompactInt, 12549),
// Max recursion depth
encodeField(25, Byte, 0),
// Recursion error message type
encodeField(26, Byte, 1),
// Recursion error name type
encodeField(27, CompactInt, 4644),
// Stack trace string length
encodeField(28, RawAppend, u8(0)),
// Touch support metric
encodeField(29, Byte, 3),
// Undefined call error type
// Navigator properties hash — hash of enumerable navigator property names.
// Captured from Chrome 144 on Windows 10. Source: yubie-re/castleio-gen (Python SDK, MIT license).
encodeField(30, RawAppend, u8(93, 197, 171, 181, 136)),
encodeField(31, RawAppend, encodeCodecPlayability())
// Codec playability
];
const data = concat(...fields);
const sizeIdx = (7 & FP_PART.DEVICE) << 5 | 31 & fields.length;
return concat(u8(sizeIdx), data);
}
function buildBrowserFingerprint(profile, initTime) {
const { Byte, EncryptedBytes, CompactInt, Marker, RawAppend } = FieldEncoding;
const timezoneField = profile.timezone in TIMEZONE_ENUM ? encodeField(1, Byte, TIMEZONE_ENUM[profile.timezone]) : encodeField(1, EncryptedBytes, textEnc(profile.timezone), initTime);
const fields = [
encodeField(0, Byte, 0),
// Constant marker
timezoneField,
// Timezone
encodeField(
2,
EncryptedBytes,
// Language list
textEnc(`${profile.locale},${profile.language}`),
initTime
),
encodeField(6, CompactInt, 0),
// Expected property count
encodeField(
10,
RawAppend,
// Castle data bitfield
concat(u8(4), encodeBits([1, 2, 3], 8))
),
encodeField(12, CompactInt, 80),
// Negative error string length
encodeField(13, RawAppend, u8(9, 0, 0)),
// Driver check values
encodeField(
17,
RawAppend,
// Chrome feature flags
concat(u8(13), encodeBits([1, 5, 8, 9, 10], 16))
),
encodeField(18, Marker, 0),
// Device logic expected
encodeField(21, RawAppend, u8(0, 0, 0, 0)),
// Class properties count
encodeField(22, EncryptedBytes, textEnc(profile.locale), initTime),
// User locale (secondary)
encodeField(
23,
RawAppend,
// Worker capabilities
concat(u8(2), encodeBits([0], 8))
),
encodeField(
24,
RawAppend,
// Inner/outer dimension diff
concat(be16(0), be16(randInt(10, 30)))
)
];
const data = concat(...fields);
const sizeIdx = (7 & FP_PART.BROWSER) << 5 | 31 & fields.length;
return concat(u8(sizeIdx), data);
}
function buildTimingFingerprint(initTime) {
const minute = new Date(initTime).getUTCMinutes();
const fields = [
encodeField(3, 5 /* CompactInt */, 1),
// Time since window.open (ms)
encodeField(4, 5 /* CompactInt */, minute)
// Castle init time (minutes)
];
const data = concat(...fields);
const sizeIdx = (7 & FP_PART.TIMING) << 5 | 31 & fields.length;
return concat(u8(sizeIdx), data);
}
const EventType = {
CLICK: 0,
FOCUS: 5,
BLUR: 6,
ANIMATIONSTART: 18,
MOUSEMOVE: 21,
MOUSELEAVE: 25,
MOUSEENTER: 26,
RESIZE: 27
};
const HAS_TARGET_FLAG = 128;
const TARGET_UNKNOWN = 63;
function generateEventLog() {
const simpleEvents = [
EventType.MOUSEMOVE,
EventType.ANIMATIONSTART,
EventType.MOUSELEAVE,
EventType.MOUSEENTER,
EventType.RESIZE
];
const targetedEvents = [
EventType.CLICK,
EventType.BLUR,
EventType.FOCUS
];
const allEvents = [...simpleEvents, ...targetedEvents];
const count = randInt(30, 70);
const eventBytes = [];
for (let i = 0; i < count; i++) {
const eventId = allEvents[randInt(0, allEvents.length - 1)];
if (targetedEvents.includes(eventId)) {
eventBytes.push(eventId | HAS_TARGET_FLAG);
eventBytes.push(TARGET_UNKNOWN);
} else {
eventBytes.push(eventId);
}
}
const inner = concat(u8(0), be16(count), new Uint8Array(eventBytes));
return concat(be16(inner.length), inner);
}
function buildBehavioralBitfield() {
const flags = new Array(15).fill(false);
flags[2] = true;
flags[3] = true;
flags[5] = true;
flags[6] = true;
flags[9] = true;
flags[11] = true;
flags[12] = true;
const packedBits = boolsToBin(flags, 16);
const encoded = 6 << 20 | 2 << 16 | 65535 & packedBits;
return u8(encoded >>> 16 & 255, encoded >>> 8 & 255, encoded & 255);
}
const NO_DATA = -1;
function buildFloatMetrics() {
const metrics = [
// ── Mouse & key timing ──
randFloat(40, 50),
// 0: Mouse angle vector mean
NO_DATA,
// 1: Touch angle vector (no touch device)
randFloat(70, 80),
// 2: Key same-time difference
NO_DATA,
// 3: (unused)
randFloat(60, 70),
// 4: Mouse down-to-up time mean
NO_DATA,
// 5: (unused)
0,
// 6: (zero placeholder)
0,
// 7: Mouse click time difference
// ── Duration distributions ──
randFloat(60, 80),
// 8: Mouse down-up duration median
randFloat(5, 10),
// 9: Mouse down-up duration std deviation
randFloat(30, 40),
// 10: Key press duration median
randFloat(2, 5),
// 11: Key press duration std deviation
// ── Touch metrics (all disabled for desktop) ──
NO_DATA,
NO_DATA,
NO_DATA,
NO_DATA,
// 12-15
NO_DATA,
NO_DATA,
NO_DATA,
NO_DATA,
// 16-19
// ── Mouse trajectory analysis ──
randFloat(150, 180),
// 20: Mouse movement angle mean
randFloat(3, 6),
// 21: Mouse movement angle std deviation
randFloat(150, 180),
// 22: Mouse movement angle mean (500ms window)
randFloat(3, 6),
// 23: Mouse movement angle std (500ms window)
randFloat(0, 2),
// 24: Mouse position deviation X
randFloat(0, 2),
// 25: Mouse position deviation Y
0,
0,
// 26-27: (zero placeholders)
// ── Touch sequential/gesture metrics (disabled) ──
NO_DATA,
NO_DATA,
// 28-29
NO_DATA,
NO_DATA,
// 30-31
// ── Key pattern analysis ──
0,
0,
// 32-33: Letter-digit transition ratio
0,
0,
// 34-35: Digit-invalid transition ratio
0,
0,
// 36-37: Double-invalid transition ratio
// ── Mouse vector differences ──
1,
0,
// 38-39: Mouse vector diff (mean, std)
1,
0,
// 40-41: Mouse vector diff 2 (mean, std)
randFloat(0, 4),
// 42: Mouse vector diff (500ms mean)
randFloat(0, 3),
// 43: Mouse vector diff (500ms std)
// ── Rounded movement metrics ──
randFloat(25, 50),
// 44: Mouse time diff (rounded mean)
randFloat(25, 50),
// 45: Mouse time diff (rounded std)
randFloat(25, 50),
// 46: Mouse vector diff (rounded mean)
randFloat(25, 30),
// 47: Mouse vector diff (rounded std)
// ── Speed change analysis ──
randFloat(0, 2),
// 48: Mouse speed change mean
randFloat(0, 1),
// 49: Mouse speed change std
randFloat(0, 1),
// 50: Mouse vector 500ms aggregate
// ── Trailing ──
1,
// 51: Universal flag
0
// 52: Terminator
];
const out = new Uint8Array(metrics.length);
for (let i = 0; i < metrics.length; i++) {
out[i] = metrics[i] === NO_DATA ? 0 : encodeFloatVal(metrics[i]);
}
return out;
}
function buildEventCounts() {
const counts = [
randInt(100, 200),
// 0: mousemove events
randInt(1, 5),
// 1: keyup events
randInt(1, 5),
// 2: click events
0,
// 3: touchstart events (none on desktop)
randInt(0, 5),
// 4: keydown events
0,
// 5: touchmove events (none)
0,
// 6: mousedown-mouseup pairs
0,
// 7: vector diff samples
randInt(0, 5),
// 8: wheel events
randInt(0, 11),
// 9: (internal counter)
randInt(0, 1)
// 10: (internal counter)
];
return concat(new Uint8Array(counts), u8(counts.length));
}
function buildBehavioralData() {
return concat(
buildBehavioralBitfield(),
buildFloatMetrics(),
buildEventCounts()
);
}
function buildTokenHeader(uuid, publisherKey, initTime) {
const timestamp = fromHex(encodeTimestampEncrypted(initTime));
const version = be16(SDK_VERSION);
const pkBytes = textEnc(publisherKey);
const uuidBytes = fromHex(uuid);
return concat(timestamp, version, pkBytes, uuidBytes);
}
function generateLocalCastleToken(userAgent, profileOverride) {
const now = Date.now();
const profile = { ...DEFAULT_PROFILE, ...profileOverride };
const initTime = now - randFloat(2 * 60 * 1e3, 30 * 60 * 1e3);
log$7("Generating local Castle.io v11 token");
const deviceFp = buildDeviceFingerprint(initTime, profile, userAgent);
const browserFp = buildBrowserFingerprint(profile, initTime);
const timingFp = buildTimingFingerprint(initTime);
const eventLog = generateEventLog();
const behavioral = buildBehavioralData();
const fingerprintData = concat(
deviceFp,
browserFp,
timingFp,
eventLog,
behavioral,
u8(255)
);
const sendTime = Date.now();
const timestampKey = encodeTimestampEncrypted(sendTime);
const xorPass1 = deriveAndXor(
timestampKey,
4,
timestampKey[3],
fingerprintData
);
const tokenUuid = toHex(getRandomBytes(16));
const withTimestampPrefix = concat(fromHex(timestampKey), xorPass1);
const xorPass2 = deriveAndXor(
tokenUuid,
8,
tokenUuid[9],
withTimestampPrefix
);
const header = buildTokenHeader(tokenUuid, TWITTER_CASTLE_PK, initTime);
const plaintext = concat(header, xorPass2);
const encrypted = xxteaEncrypt(plaintext, XXTEA_KEY);
const paddingBytes = encrypted.length - plaintext.length;
const versioned = concat(u8(TOKEN_VERSION, paddingBytes), encrypted);
const randomByte = getRandomBytes(1)[0];
const checksum = versioned.length * 2 & 255;
const withChecksum = concat(versioned, u8(checksum));
const xored = xorBytes(withChecksum, u8(randomByte));
const finalPayload = concat(u8(randomByte), xored);
const token = base64url(finalPayload);
log$7(
`Generated castle token: ${token.length} chars, cuid: ${tokenUuid.substring(
0,
6
)}...`
);
return { token, cuid: tokenUuid };
}
const log$6 = debug("twitter-scraper:requests");
async function updateCookieJar(cookieJar, headers) {
let setCookieHeaders = [];
if (typeof headers.getSetCookie === "function") {
setCookieHeaders = headers.getSetCookie();
} else {
const setCookieHeader = headers.get("set-cookie");
if (setCookieHeader) {
setCookieHeaders = setCookie.splitCookiesString(setCookieHeader);
}
}
if (setCookieHeaders.length > 0) {
for (const cookieStr of setCookieHeaders) {
const cookie = toughCookie.Cookie.parse(cookieStr);
if (!cookie) {
log$6(`Failed to parse cookie: ${cookieStr.substring(0, 100)}`);
continue;
}
if (cookie.maxAge === 0 || cookie.expires && cookie.expires < /* @__PURE__ */ new Date()) {
if (cookie.key === "ct0") {
log$6(`Skipping deletion of ct0 cookie (Max-Age=0)`);
}
continue;
}
try {
const url = `${cookie.secure ? "https" : "http"}://${cookie.domain}${cookie.path}`;
await cookieJar.setCookie(cookie, url);
if (cookie.key === "ct0") {
log$6(
`Successfully set ct0 cookie with value: ${cookie.value.substring(
0,
20
)}...`
);
}
} catch (err) {
log$6(`Failed to set cookie ${cookie.key}: ${err}`);
if (cookie.key === "ct0") {
log$6(`FAILED to set ct0 cookie! Error: ${err}`);
}
}
}
} else if (typeof document !== "undefined") {
for (const cookie of document.cookie.split(";")) {
const hardCookie = toughCookie.Cookie.parse(cookie);
if (hardCookie) {
await cookieJar.setCookie(hardCookie, document.location.toString());
}
}
}
}
const log$5 = debug("twitter-scraper:xpff");
let isoCrypto = null;
async function getCrypto() {
if (isoCrypto != null) {
return isoCrypto;
}
if (typeof crypto === "undefined") {
log$5("Global crypto is undefined, importing from crypto module...");
const { webcrypto } = await import('crypto');
isoCrypto = webcrypto;
return webcrypto;
}
isoCrypto = crypto;
return crypto;
}
async function sha256(message) {
const msgBuffer = new TextEncoder().encode(message);
const crypto2 = await getCrypto();
const hashBuffer = await crypto2.subtle.digest("SHA-256", msgBuffer);
return new Uint8Array(hashBuffer);
}
function buf2hex(buffer) {
return [...new Uint8Array(buffer)].map((x) => x.toString(16).padStart(2, "0")).join("");
}
class XPFFHeaderGenerator {
constructor(seed) {
this.seed = seed;
}
async deriveKey(guestId) {
const combined = `${this.seed}${guestId}`;
const result = await sha256(combined);
return result;
}
async generateHeader(plaintext, guestId) {
log$5(`Generating XPFF key for guest ID: ${guestId}`);
const key = await this.deriveKey(guestId);
const crypto2 = await getCrypto();
const nonce = crypto2.getRandomValues(new Uint8Array(12));
const cipher = await crypto2.subtle.importKey(
"raw",
key,
{ name: "AES-GCM" },
false,
["encrypt"]
);
const encrypted = await crypto2.subtle.encrypt(
{
name: "AES-GCM",
iv: nonce
},
cipher,
new TextEncoder().encode(plaintext)
);
const combined = new Uint8Array(nonce.length + encrypted.byteLength);
combined.set(nonce);
combined.set(new Uint8Array(encrypted), nonce.length);
const result = buf2hex(combined.buffer);
log$5(`XPFF header generated for guest ID ${guestId}: ${result}`);
return result;
}
}
const xpffBaseKey = "0e6be1f1e21ffc33590b888fd4dc81b19713e570e805d4e5df80a493c9571a05";
function xpffPlain() {
const timestamp = Date.now();
return JSON.stringify({
navigator_properties: {
hasBeenActive: "true",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
webdriver: "false"
},
created_at: timestamp
});
}
async function generateXPFFHeader(guestId) {
const generator = new XPFFHeaderGenerator(xpffBaseKey);
const plaintext = xpffPlain();
return generator.generateHeader(plaintext, guestId);
}
const log$4 = debug("twitter-scraper:auth");
function withTransform(fetchFn, transform) {
return async (input, init) => {
const fetchArgs = await transform?.request?.(input, init) ?? [
input,
init
];
const res = await fetchFn(...fetchArgs);
return await transform?.response?.(res) ?? res;
};
}
class TwitterGuestAuth {
constructor(bearerToken, options) {
this.options = options;
this.fetch = withTransform(options?.fetch ?? fetch, options?.transform);
this.rateLimitStrategy = options?.rateLimitStrategy ?? new WaitingRateLimitStrategy();
this.bearerToken = bearerToken;
this.jar = new toughCookie.CookieJar();
}
async onRateLimit(event) {
await this.rateLimitStrategy.onRateLimit(event);
}
cookieJar() {
return this.jar;
}
isLoggedIn() {
return Promise.resolve(false);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
login(_username, _password, _email) {
return this.updateGuestToken();
}
logout() {
this.deleteToken();
this.jar = new toughCookie.CookieJar();
return Promise.resolve();
}
deleteToken() {
delete this.guestToken;
delete this.guestCreatedAt;
}
hasToken() {
return this.guestToken != null;
}
authenticatedAt() {
if (this.guestCreatedAt == null) {
return null;
}
return new Date(this.guestCreatedAt);
}
/**
* Install only authentication credentials (bearer token, guest token, cookies)
* without browser fingerprint or platform headers. Useful for callers that
* build their own header set (e.g. the login flow).
*/
async installAuthCredentials(headers, bearerTokenOverride) {
const tokenToUse = bearerTokenOverride ?? this.bearerToken;
headers.set("authorization", `Bearer ${tokenToUse}`);
if (!bearerTokenOverride) {
if (this.shouldUpdate()) {
await this.updateGuestToken();
}
if (this.guestToken) {
headers.set("x-guest-token", this.guestToken);
}
}
headers.set("cookie", await this.getCookieString());
}
async installTo(headers, url, bearerTokenOverride) {
await this.installAuthCredentials(headers, bearerTokenOverride);
headers.set("user-agent", CHROME_USER_AGENT);
if (!headers.has("accept")) {
headers.set("accept", "*/*");
}
headers.set("accept-language", "en-US,en;q=0.9");
headers.set("sec-ch-ua", CHROME_SEC_CH_UA);
headers.set("sec-ch-ua-mobile", "?0");
headers.set("sec-ch-ua-platform", '"Windows"');
headers.set("referer", "https://x.com/");
headers.set("origin", "https://x.com");
headers.set("sec-fetch-site", "same-site");
headers.set("sec-fetch-mode", "cors");
headers.set("sec-fetch-dest", "empty");
headers.set("priority", "u=1, i");
if (!headers.has("content-type") && (url.includes("api.x.com/graphql/") || url.includes("x.com/i/api/graphql/"))) {
headers.set("content-type", "application/json");
}
await this.installCsrfToken(headers);
if (this.options?.experimental?.xpff) {
const guestId = await this.guestId();
if (guestId != null) {
const xpffHeader = await generateXPFFHeader(guestId);
headers.set("x-xp-forwarded-for", xpffHeader);
}
}
}
async installCsrfToken(headers) {
const cookies = await this.getCookies();
const xCsrfToken = cookies.find((cookie) => cookie.key === "ct0");
if (xCsrfToken) {
headers.set("x-csrf-token", xCsrfToken.value);
}
}
async setCookie(key, value) {
const cookie = toughCookie.Cookie.parse(`${key}=${value}`);
if (!cookie) {
throw new Error("Failed to parse cookie.");
}
await this.jar.setCookie(cookie, this.getCookieJarUrl());
if (typeof document !== "undefined") {
document.cookie = cookie.toString();
}
}
async getCookies() {
return this.jar.getCookies(this.getCookieJarUrl());
}
async getCookieString() {
const cookies = await this.getCookies();
return cookies.map((cookie) => `${cookie.key}=${cookie.value}`).join("; ");
}
async removeCookie(key) {
const store = this.jar.store;
const cookies = await this.jar.getCookies(this.getCookieJarUrl());
for (const cookie of cookies) {
if (!cookie.domain || !cookie.path) continue;
await store.removeCookie(cookie.domain, cookie.path, key);
if (typeof document !== "undefined") {
document.cookie = `${cookie.key}=; Max-Age=0; path=${cookie.path}; domain=${cookie.domain}`;
}
}
}
getCookieJarUrl() {
return typeof document !== "undefined" ? document.location.toString() : "https://x.com";
}
async guestId() {
const cookies = await this.getCookies();
const guestIdCookie = cookies.find((cookie) => cookie.key === "guest_id");
return guestIdCookie ? guestIdCookie.value : null;
}
/**
* Updates the authentication state with a new guest token from the Twitter API.
*/
async updateGuestToken() {
try {
await this.updateGuestTokenCore();
} catch (err) {
log$4("Failed to update guest token; this may cause issues:", err);
}
}
async updateGuestTokenCore() {
const guestActivateUrl = "https://api.x.com/1.1/guest/activate.json";
const headers = new headersPolyfill.Headers({
authorization: `Bearer ${this.bearerToken}`,
"user-agent": CHROME_USER_AGENT,
accept: "*/*",
"accept-language": "en-US,en;q=0.9",
"content-type": "application/x-www-form-urlencoded",
"sec-ch-ua": CHROME_SEC_CH_UA,
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
origin: "https://x.com",
referer: "https://x.com/",
"sec-fetch-site": "same-site",
"sec-fetch-mode": "cors",
"sec-fetch-dest": "empty",
cookie: await this.getCookieString()
});
log$4(`Making POST request to ${guestActivateUrl}`);
const res = await this.fetch(guestActivateUrl, {
method: "POST",
headers
});
await updateCookieJar(this.jar, res.headers);
if (!res.ok) {
throw new AuthenticationError(await res.text());
}
const o = await flexParseJson(res);
if (o == null || o["guest_token"] == null) {
throw new AuthenticationError("guest_token not found.");
}
const newGuestToken = o["guest_token"];
if (typeof newGuestToken !== "string") {
throw new AuthenticationError("guest_token was not a string.");
}
this.guestToken = newGuestToken;
this.guestCreatedAt = /* @__PURE__ */ new Date();
await this.setCookie("gt", newGuestToken);
log$4(`Updated guest token (length: ${newGuestToken.length})`);
}
/**
* Returns if the authentication token needs to be updated or not.
* @returns `true` if the token needs to be updated; `false` otherwise.
*/
shouldUpdate() {
return !this.hasToken() || this.guestCreatedAt != null && this.guestCreatedAt < new Date((/* @__PURE__ */ new Date()).valueOf() - 3 * 60 * 60 * 1e3);
}
}
const genericPlatform = new class {
randomizeCiphers() {
return Promise.resolve();
}
}();
class Platform {
async randomizeCiphers() {
const platform = await Platform.importPlatform();
await platform?.randomizeCiphers();
}
static async importPlatform() {
return genericPlatform;
}
}
const log$3 = debug("twitter-scraper:xctxid");
let linkedom = null;
async function linkedomImport() {
if (!linkedom) {
const mod = await import('linkedom');
linkedom = mod;
return mod;
}
return linkedom;
}
async function parseHTML(html) {
if (typeof window !== "undefined") {
const { defaultView } = new DOMParser().parseFromString(html, "text/html");
if (!defaultView) {
throw new Error("Failed to get defaultView from parsed HTML.");
}
return defaultView;
} else {
const { DOMParser: DOMParser2 } = await linkedomImport();
return new DOMParser2().parseFromString(html, "text/html").defaultView;
}
}
async function handleXMigration(fetchFn) {
const headers = {
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-language": "ja",
"cache-control": "no-cache",
pragma: "no-cache",
priority: "u=0, i",
"sec-ch-ua": CHROME_SEC_CH_UA,
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
"user-agent": CHROME_USER_AGENT
};
const response = await fetchFn("https://x.com", {
headers
});
if (!response.ok) {
throw new Error(`Failed to fetch X homepage: ${response.statusText}`);
}
const htmlText = await response.text();
let dom = await parseHTML(htmlText);
let document = dom.window.document;
const migrationRedirectionRegex = new RegExp(
"(http(?:s)?://(?:www\\.)?(twitter|x){1}\\.com(/x)?/migrate([/?])?tok=[a-zA-Z0-9%\\-_]+)+",
"i"
);
const metaRefresh = document.querySelector("meta[http-equiv='refresh']");
const metaContent = metaRefresh ? metaRefresh.getAttribute("content") || "" : "";
const migrationRedirectionUrl = migrationRedirectionRegex.exec(metaContent) || migrationRedirectionRegex.exec(htmlText);
if (migrationRedirectionUrl) {
const redirectResponse = await fetchFn(migrationRedirectionUrl[0]);
if (!redirectResponse.ok) {
throw new Error(
`Failed to follow migration redirection: ${redirectResponse.statusText}`
);
}
const redirectHtml = await redirectResponse.text();
dom = await parseHTML(redirectHtml);
document = dom.window.document;
}
const migrationForm = document.querySelector("form[name='f']") || document.querySelector("form[action='https://x.com/x/migrate']");
if (migrationForm) {
const url = migrationForm.getAttribute("action") || "https://x.com/x/migrate";
const method = migrationForm.getAttribute("method") || "POST";
const requestPayload = new FormData();
const inputFields = migrationForm.querySelectorAll("input");
for (const element of Array.from(inputFields)) {
const name = element.getAttribute("name");
const value = element.getAttribute("value");
if (name && value) {
requestPayload.append(name, value);
}
}
const formResponse = await fetchFn(url, {
method,
body: requestPayload,
headers
});
if (!formResponse.ok) {
throw new Error(
`Failed to submit migration form: ${formResponse.statusText}`
);
}
const formHtml = await formResponse.text();
dom = await parseHTML(formHtml);
document = dom.window.document;
}
return document;
}
let cachedDocumentPromise = null;
let cachedDocumentTimestamp = 0;
const DOCUMENT_CACHE_TTL = 5 * 60 * 1e3;
async function getCachedDocument(fetchFn) {
const now = Date.now();
if (!cachedDocumentPromise || now - cachedDocumentTimestamp > DOCUMENT_CACHE_TTL) {
log$3("Fetching fresh x.com document for transaction ID generation");
cachedDocumentTimestamp = now;
cachedDocumentPromise = handleXMigration(fetchFn).catch((err) => {
cachedDocumentPromise = null;
throw err;
});
} else {
log$3("Using cached x.com document for transaction ID generation");
}
return cachedDocumentPromise;
}
let ClientTransaction = null;
async function clientTransaction() {
if (!ClientTransaction) {
const mod = await import('x-client-transaction-id');
ClientTransaction = mod.ClientTransaction;
return mod.ClientTransaction;
}
return ClientTransaction;
}
async function generateTransactionId(url, fetchFn, method) {
const parsedUrl = new URL(url);
const path = parsedUrl.pathname;
log$3(`Generating transaction ID for ${method} ${path}`);
const document = await getCachedDocument(fetchFn);
const ClientTransactionClass = await clientTransaction();
const transaction = await ClientTransactionClass.create(document);
const transactionId = await transaction.generateTransactionId(method, path);
log$3(`Transaction ID: ${transactionId}`);
return transactionId;
}
const CHROME_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36";
const CHROME_SEC_CH_UA = '"Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"';
const log$2 = debug("twitter-scraper:api");
const bearerToken = "AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF";
const bearerToken2 = "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA";
async function jitter(maxMs) {
const jitter2 = Math.random() * maxMs;
await new Promise((resolve) => setTimeout(resolve, jitter2));
}
async function requestApi(url, auth, method = "GET", platform = new Platform(), headers = new headersPolyfill.Headers(), bearerTokenOverride) {
log$2(`Making ${method} request to ${url}`);
await auth.installTo(headers, url, bearerTokenOverride);
await platform.randomizeCiphers();
if (auth instanceof TwitterGuestAuth && auth.options?.experimental?.xClientTransactionId) {
const transactionId = await generateTransactionId(
url,
auth.fetch.bind(auth),
method
);
headers.set("x-client-transaction-id", transactionId);
}
let res;
do {
const fetchParameters = [
url,
{
method,
headers,
credentials: "include"
}
];
try {
res = await auth.fetch(...fetchParameters);
} catch (err) {
if (!(err instanceof Error)) {
throw err;
}
return {
success: false,
err
};
}
await updateCookieJar(auth.cookieJar(), res.headers);
if (res.status === 429) {
log$2("Rate limit hit, waiting for retry...");
await auth.onRateLimit({
fetchParameters,
response: res
});
}
} while (res.status === 429);
if (!res.ok) {
return {
success: false,
err: await ApiError.fromResponse(res)
};
}
const value = await flexParseJson(res);
if (res.headers.get("x-rate-limit-incoming") == "0") {
auth.deleteToken();
return { success: true, value };
} else {
return { success: true, value };
}
}
async function flexParseJson(res) {
try {
return await res.json();
} catch {
log$2("Failed to parse response as JSON, trying text parse...");
const text = await res.text();
log$2("Response text:", text);
return JSON.parse(text);
}
}
function addApiFeatures(o) {
return {
...o,
rweb_lists_timeline_redesign_enabled: true,
responsive_web_graphql_exclude_directive_enabled: true,
verified_phone_label_enabled: false,
creator_subscriptions_tweet_preview_api_enabled: true,
responsive_web_graphql_timeline_navigation_enabled: true,
responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
tweetypie_unmention_optimization_enabled: true,
responsive_web_edit_tweet_api_enabled: true,
graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
view_counts_everywhere_api_enabled: true,
longform_notetweets_consumption_enabled: true,
tweet_awards_web_tipping_enabled: false,
freedom_of_speech_not_reach_fetch_enabled: true,
standardized_nudges_misinfo: true,
longform_notetweets_rich_text_read_enabled: true,
responsive_web_enhance_cards_enabled: false,
subscriptions_verification_info_enabled: true,
subscriptions_verification_info_reason_enabled: true,
subscriptions_verification_info_verified_since_enabled: true,
super_follow_badge_privacy_enabled: false,
super_follow_exclusive_tweet_notifications_enabled: false,
super_follow_tweet_api_enabled: false,
super_follow_user_api_enabled: false,
android_graphql_skip_api_media_color_palette: false,
creator_subscriptions_subscription_count_enabled: false,
blue_business_profile_image_shape_enabled: false,
unified_cards_ad_metadata_container_dynamic_card_content_query_enabled: false
};
}
function addApiParams(params, includeTweetReplies) {
params.set("include_profile_interstitial_type", "1");
params.set("include_blocking", "1");
params.set("include_blocked_by", "1");
params.set("include_followed_by", "1");
params.set("include_want_retweets", "1");
params.set("include_mute_edge", "1");
params.set("include_can_dm", "1");
params.set("include_can_media_tag", "1");
params.set("include_ext_has_nft_avatar", "1");
params.set("include_ext_is_blue_verified", "1");
params.set("include_ext_verified_type", "1");
params.set("skip_status", "1");
params.set("cards_platform", "Web-12");
params.set("include_cards", "1");
params.set("include_ext_alt_text", "true");
params.set("include_ext_limited_action_results", "false");
params.set("include_quote_count", "true");
params.set("include_reply_count", "1");
params.set("tweet_mode", "extended");
params.set("include_ext_collab_control", "true");
params.set("include_ext_views", "true");
params.set("include_entities", "true");
params.set("include_user_entities", "true");
params.set("include_ext_media_color", "true");
params.set("include_ext_media_availability", "true");
params.set("include_ext_sensitive_media_warning", "true");
params.set("include_ext_trusted_friends_metadata", "true");
params.set("send_error_codes", "true");
params.set("simple_quoted_tweet", "true");
params.set("include_tweet_replies", `${includeTweetReplies}`);
params.set(
"ext",
"mediaStats,highlightedLabel,hasNftAvatar,voiceInfo,birdwatchPivot,enrichments,superFollowMetadata,unmentionInfo,editControl,collab_control,vibe"
);
return params;
}
const log$1 = debug("twitter-scraper:auth-user");
const TwitterUserAuthSubtask = typebox.Type.Object({
subtask_id: typebox.Type.String(),
enter_text: typebox.Type.Optional(typebox.Type.Object({}))
});
const _TwitterUserAuth = class _TwitterUserAuth extends TwitterGuestAuth {
constructor(bearerToken, options) {
super(bearerToken, options);
this.subtaskHandlers = /* @__PURE__ */ new Map();
this.initializeDefaultHandlers();
}
/**
* Register a custom subtask handler or override an existing one
* @param subtaskId The ID of the subtask to handle
* @param handler The handler function that processes the subtask
*/
registerSubtaskHandler(subtaskId, handler) {
this.subtaskHandlers.set(subtaskId, handler);
}
initializeDefaultHandlers() {
this.subtaskHandlers.set(
"LoginJsInstrumentationSubtask",
this.handle