@the-convocation/twitter-scraper
Advanced tools
Comparing version 0.10.1 to 0.11.0
@@ -9,7 +9,16 @@ import { TwitterAuthOptions, TwitterGuestAuth } from './auth'; | ||
isLoggedIn(): Promise<boolean>; | ||
login(username: string, password: string, email?: string): Promise<void>; | ||
login(username: string, password: string, email?: string, twoFactorSecret?: string): Promise<void>; | ||
logout(): Promise<void>; | ||
installCsrfToken(headers: Headers): Promise<void>; | ||
installTo(headers: Headers, url: string): Promise<void>; | ||
private initLogin; | ||
private handleJsInstrumentationSubtask; | ||
private handleEnterUserIdentifierSSO; | ||
private handleEnterPassword; | ||
private handleAccountDuplicationCheck; | ||
private handleTwoFactorAuthChallenge; | ||
private handleAcid; | ||
private handleSuccessSubtask; | ||
private executeFlowTask; | ||
} | ||
//# sourceMappingURL=auth-user.d.ts.map |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -9,2 +32,9 @@ exports.TwitterUserAuth = void 0; | ||
const headers_polyfill_1 = require("headers-polyfill"); | ||
const typebox_1 = require("@sinclair/typebox"); | ||
const value_1 = require("@sinclair/typebox/value"); | ||
const OTPAuth = __importStar(require("otpauth")); | ||
const TwitterUserAuthSubtask = typebox_1.Type.Object({ | ||
subtask_id: typebox_1.Type.String(), | ||
enter_text: typebox_1.Type.Optional(typebox_1.Type.Object({})), | ||
}); | ||
/** | ||
@@ -25,34 +55,62 @@ * A user authentication token manager. | ||
} | ||
async login(username, password, email) { | ||
async login(username, password, email, twoFactorSecret) { | ||
await this.updateGuestToken(); | ||
// Executes the potential acid step in the login flow | ||
const executeFlowAcid = (ft) => this.executeFlowTask({ | ||
flow_token: ft, | ||
subtask_inputs: [ | ||
{ | ||
subtask_id: 'LoginAcid', | ||
enter_text: { | ||
text: email, | ||
link: 'next_link', | ||
}, | ||
}, | ||
], | ||
}); | ||
// Handles the result of a flow task | ||
const handleFlowTokenResult = async (p) => { | ||
const result = await p; | ||
const { status } = result; | ||
if (status === 'error') { | ||
throw result.err; | ||
let next = await this.initLogin(); | ||
while ('subtask' in next && next.subtask) { | ||
if (next.subtask.subtask_id === 'LoginJsInstrumentationSubtask') { | ||
next = await this.handleJsInstrumentationSubtask(next); | ||
} | ||
else if (status === 'acid') { | ||
return await handleFlowTokenResult(executeFlowAcid(result.flowToken)); | ||
else if (next.subtask.subtask_id === 'LoginEnterUserIdentifierSSO') { | ||
next = await this.handleEnterUserIdentifierSSO(next, username); | ||
} | ||
else if (next.subtask.subtask_id === 'LoginEnterPassword') { | ||
next = await this.handleEnterPassword(next, password); | ||
} | ||
else if (next.subtask.subtask_id === 'AccountDuplicationCheck') { | ||
next = await this.handleAccountDuplicationCheck(next); | ||
} | ||
else if (next.subtask.subtask_id === 'LoginTwoFactorAuthChallenge') { | ||
if (twoFactorSecret) { | ||
next = await this.handleTwoFactorAuthChallenge(next, twoFactorSecret); | ||
} | ||
else { | ||
throw new Error('Requested two factor authentication code but no secret provided'); | ||
} | ||
} | ||
else if (next.subtask.subtask_id === 'LoginAcid') { | ||
next = await this.handleAcid(next, email); | ||
} | ||
else if (next.subtask.subtask_id === 'LoginSuccessSubtask') { | ||
next = await this.handleSuccessSubtask(next); | ||
} | ||
else { | ||
return result.flowToken; | ||
throw new Error(`Unknown subtask ${next.subtask.subtask_id}`); | ||
} | ||
}; | ||
// Executes a flow subtask and handles the result | ||
const executeFlowSubtask = (data) => handleFlowTokenResult(this.executeFlowTask(data)); | ||
await executeFlowSubtask({ | ||
} | ||
if ('err' in next) { | ||
throw next.err; | ||
} | ||
} | ||
async logout() { | ||
if (!this.isLoggedIn()) { | ||
return; | ||
} | ||
await (0, api_1.requestApi)('https://api.twitter.com/1.1/account/logout.json', this, 'POST'); | ||
this.deleteToken(); | ||
this.jar = new tough_cookie_1.CookieJar(); | ||
} | ||
async installCsrfToken(headers) { | ||
const cookies = await this.jar.getCookies('https://twitter.com'); | ||
const xCsrfToken = cookies.find((cookie) => cookie.key === 'ct0'); | ||
if (xCsrfToken) { | ||
headers.set('x-csrf-token', xCsrfToken.value); | ||
} | ||
} | ||
async installTo(headers, url) { | ||
headers.set('authorization', `Bearer ${this.bearerToken}`); | ||
headers.set('cookie', await this.jar.getCookieString(url)); | ||
await this.installCsrfToken(headers); | ||
} | ||
async initLogin() { | ||
return await this.executeFlowTask({ | ||
flow_name: 'login', | ||
@@ -67,5 +125,7 @@ input_flow_data: { | ||
}, | ||
}) | ||
.then((ft) => executeFlowSubtask({ | ||
flow_token: ft, | ||
}); | ||
} | ||
async handleJsInstrumentationSubtask(prev) { | ||
return await this.executeFlowTask({ | ||
flow_token: prev.flowToken, | ||
subtask_inputs: [ | ||
@@ -80,5 +140,7 @@ { | ||
], | ||
})) | ||
.then((ft) => executeFlowSubtask({ | ||
flow_token: ft, | ||
}); | ||
} | ||
async handleEnterUserIdentifierSSO(prev, username) { | ||
return await this.executeFlowTask({ | ||
flow_token: prev.flowToken, | ||
subtask_inputs: [ | ||
@@ -100,5 +162,7 @@ { | ||
], | ||
})) | ||
.then((ft) => executeFlowSubtask({ | ||
flow_token: ft, | ||
}); | ||
} | ||
async handleEnterPassword(prev, password) { | ||
return await this.executeFlowTask({ | ||
flow_token: prev.flowToken, | ||
subtask_inputs: [ | ||
@@ -113,5 +177,7 @@ { | ||
], | ||
})) | ||
.then((ft) => executeFlowSubtask({ | ||
flow_token: ft, | ||
}); | ||
} | ||
async handleAccountDuplicationCheck(prev) { | ||
return await this.executeFlowTask({ | ||
flow_token: prev.flowToken, | ||
subtask_inputs: [ | ||
@@ -125,21 +191,49 @@ { | ||
], | ||
})); | ||
}); | ||
} | ||
async logout() { | ||
if (!this.isLoggedIn()) { | ||
return; | ||
async handleTwoFactorAuthChallenge(prev, secret) { | ||
const totp = new OTPAuth.TOTP({ secret }); | ||
let error; | ||
for (let attempts = 1; attempts < 4; attempts += 1) { | ||
try { | ||
return await this.executeFlowTask({ | ||
flow_token: prev.flowToken, | ||
subtask_inputs: [ | ||
{ | ||
subtask_id: 'LoginTwoFactorAuthChallenge', | ||
enter_text: { | ||
link: 'next_link', | ||
text: totp.generate(), | ||
}, | ||
}, | ||
], | ||
}); | ||
} | ||
catch (err) { | ||
error = err; | ||
await new Promise((resolve) => setTimeout(resolve, 2000 * attempts)); | ||
} | ||
} | ||
await (0, api_1.requestApi)('https://api.twitter.com/1.1/account/logout.json', this, 'POST'); | ||
this.deleteToken(); | ||
this.jar = new tough_cookie_1.CookieJar(); | ||
throw error; | ||
} | ||
async installTo(headers, url) { | ||
headers.set('authorization', `Bearer ${this.bearerToken}`); | ||
headers.set('cookie', await this.jar.getCookieString(url)); | ||
const cookies = await this.jar.getCookies(url); | ||
const xCsrfToken = cookies.find((cookie) => cookie.key === 'ct0'); | ||
if (xCsrfToken) { | ||
headers.set('x-csrf-token', xCsrfToken.value); | ||
} | ||
async handleAcid(prev, email) { | ||
return await this.executeFlowTask({ | ||
flow_token: prev.flowToken, | ||
subtask_inputs: [ | ||
{ | ||
subtask_id: 'LoginAcid', | ||
enter_text: { | ||
text: email, | ||
link: 'next_link', | ||
}, | ||
}, | ||
], | ||
}); | ||
} | ||
async handleSuccessSubtask(prev) { | ||
return await this.executeFlowTask({ | ||
flow_token: prev.flowToken, | ||
subtask_inputs: [], | ||
}); | ||
} | ||
async executeFlowTask(data) { | ||
@@ -161,2 +255,3 @@ const onboardingTaskUrl = 'https://api.twitter.com/1.1/onboarding/task.json'; | ||
}); | ||
await this.installCsrfToken(headers); | ||
const res = await this.fetch(onboardingTaskUrl, { | ||
@@ -187,30 +282,13 @@ method: 'POST', | ||
} | ||
if (flow.subtasks?.length) { | ||
if (flow.subtasks[0].subtask_id === 'LoginEnterAlternateIdentifierSubtask') { | ||
return { | ||
status: 'error', | ||
err: new Error('Authentication error: LoginEnterAlternateIdentifierSubtask'), | ||
}; | ||
} | ||
else if (flow.subtasks[0].subtask_id === 'LoginAcid') { | ||
return { | ||
status: 'acid', | ||
flowToken: flow.flow_token, | ||
}; | ||
} | ||
else if (flow.subtasks[0].subtask_id === 'LoginTwoFactorAuthChallenge') { | ||
return { | ||
status: 'error', | ||
err: new Error('Authentication error: LoginTwoFactorAuthChallenge'), | ||
}; | ||
} | ||
else if (flow.subtasks[0].subtask_id === 'DenyLoginSubtask') { | ||
return { | ||
status: 'error', | ||
err: new Error('Authentication error: DenyLoginSubtask'), | ||
}; | ||
} | ||
const subtask = flow.subtasks?.length ? flow.subtasks[0] : undefined; | ||
(0, value_1.Check)(TwitterUserAuthSubtask, subtask); | ||
if (subtask && subtask.subtask_id === 'DenyLoginSubtask') { | ||
return { | ||
status: 'error', | ||
err: new Error('Authentication error: DenyLoginSubtask'), | ||
}; | ||
} | ||
return { | ||
status: 'success', | ||
subtask, | ||
flowToken: flow.flow_token, | ||
@@ -217,0 +295,0 @@ }; |
@@ -24,5 +24,6 @@ import { CookieJar } from 'tough-cookie'; | ||
* @param password The password to log in with. | ||
* @param email The password to log in with, if you have email confirmation enabled. | ||
* @param email The email to log in with, if you have email confirmation enabled. | ||
* @param twoFactorSecret The secret to generate two factor authentication tokens with, if you have two factor authentication enabled. | ||
*/ | ||
login(username: string, password: string, email?: string): Promise<void>; | ||
login(username: string, password: string, email?: string, twoFactorSecret?: string): Promise<void>; | ||
/** | ||
@@ -29,0 +30,0 @@ * Logs out of the current session. |
@@ -207,5 +207,6 @@ import { Cookie } from 'tough-cookie'; | ||
* @param password The password of the Twitter account to login with. | ||
* @param email The password to log in with, if you have email confirmation enabled. | ||
* @param email The email to log in with, if you have email confirmation enabled. | ||
* @param twoFactorSecret The secret to generate two factor authentication tokens with, if you have two factor authentication enabled. | ||
*/ | ||
login(username: string, password: string, email?: string): Promise<void>; | ||
login(username: string, password: string, email?: string, twoFactorSecret?: string): Promise<void>; | ||
/** | ||
@@ -212,0 +213,0 @@ * Log out of Twitter. |
@@ -258,8 +258,9 @@ "use strict"; | ||
* @param password The password of the Twitter account to login with. | ||
* @param email The password to log in with, if you have email confirmation enabled. | ||
* @param email The email to log in with, if you have email confirmation enabled. | ||
* @param twoFactorSecret The secret to generate two factor authentication tokens with, if you have two factor authentication enabled. | ||
*/ | ||
async login(username, password, email) { | ||
async login(username, password, email, twoFactorSecret) { | ||
// Swap in a real authorizer for all requests | ||
const userAuth = new auth_user_1.TwitterUserAuth(this.token, this.getAuthOptions()); | ||
await userAuth.login(username, password, email); | ||
await userAuth.login(username, password, email, twoFactorSecret); | ||
this.auth = userAuth; | ||
@@ -266,0 +267,0 @@ this.authTrends = userAuth; |
{ | ||
"name": "@the-convocation/twitter-scraper", | ||
"description": "A port of n0madic/twitter-scraper to Node.js.", | ||
"keywords": ["x", "twitter", "scraper", "crawler"], | ||
"version": "0.10.1", | ||
"keywords": [ | ||
"x", | ||
"twitter", | ||
"scraper", | ||
"crawler" | ||
], | ||
"version": "0.11.0", | ||
"main": "dist/_module.js", | ||
@@ -24,5 +29,7 @@ "repository": "https://github.com/the-convocation/twitter-scraper.git", | ||
"dependencies": { | ||
"@sinclair/typebox": "^0.32.20", | ||
"cross-fetch": "^4.0.0-alpha.5", | ||
"headers-polyfill": "^3.1.2", | ||
"json-stable-stringify": "^1.0.2", | ||
"otpauth": "^9.2.2", | ||
"set-cookie-parser": "^2.6.0", | ||
@@ -29,0 +36,0 @@ "tough-cookie": "^4.1.2", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
256596
3504
8
+ Added@sinclair/typebox@^0.32.20
+ Addedotpauth@^9.2.2
+ Added@noble/hashes@1.6.1(transitive)
+ Added@sinclair/typebox@0.32.35(transitive)
+ Addedotpauth@9.3.6(transitive)