chatgpt-plugin/utils/openai-auth.js

281 lines
7.2 KiB
JavaScript

import { Config } from '../utils/config.js'
import delay from 'delay'
import random from 'random'
let hasRecaptchaPlugin = !!Config['2captchaToken']
export async function getOpenAIAuth (opt) {
let {
email,
password,
browser,
page,
timeoutMs = Config.chromeTimeoutMS,
isGoogleLogin = false,
captchaToken = Config['2captchaToken'],
executablePath = Config.chromePath
} = opt
const origBrowser = browser
const origPage = page
try {
const userAgent = await browser.userAgent()
if (!page) {
page = (await browser.pages())[0] || (await browser.newPage())
page.setDefaultTimeout(timeoutMs)
}
await page.goto('https://chat.openai.com/auth/login', {
waitUntil: 'networkidle2'
})
logger.mark('chatgpt checkForChatGPTAtCapacity')
await checkForChatGPTAtCapacity(page)
// NOTE: this is where you may encounter a CAPTCHA
if (hasRecaptchaPlugin) {
logger.mark('RecaptchaPlugin key exists, try to solve recaptchas')
await page.solveRecaptchas()
}
logger.mark('chatgpt checkForChatGPTAtCapacity again')
await checkForChatGPTAtCapacity(page)
// once we get to this point, the Cloudflare cookies should be available
// login as well (optional)
if (email && password) {
let retry = 3
while (retry > 0) {
try {
await waitForConditionOrAtCapacity(page, () =>
page.waitForSelector('#__next .btn-primary', { timeout: timeoutMs / 3 })
)
} catch (e) {
await checkForChatGPTAtCapacity(page)
}
retry--
}
await waitForConditionOrAtCapacity(page, () =>
page.waitForSelector('#__next .btn-primary', { timeout: timeoutMs / 3 })
)
await delay(500)
// click login button and wait for navigation to finish
do {
await Promise.all([
page.waitForNavigation({
waitUntil: 'networkidle2',
timeout: timeoutMs
}),
page.click('#__next .btn-primary')
])
await delay(1000)
} while (page.url().endsWith('/auth/login'))
logger.mark('进入登录页面')
await checkForChatGPTAtCapacity(page)
let submitP
if (isGoogleLogin) {
await page.click('button[data-provider="google"]')
await page.waitForSelector('input[type="email"]')
await page.type('input[type="email"]', email, { delay: 10 })
await Promise.all([
page.waitForNavigation(),
await page.keyboard.press('Enter')
])
await page.waitForSelector('input[type="password"]', { visible: true })
await page.type('input[type="password"]', password, { delay: 10 })
submitP = () => page.keyboard.press('Enter')
} else {
await page.waitForSelector('#username')
await page.type('#username', email, { delay: 20 })
await delay(100)
if (hasRecaptchaPlugin) {
// console.log('solveRecaptchas()')
const res = await page.solveRecaptchas()
// console.log('solveRecaptchas result', res)
}
await page.click('button[type="submit"]')
await page.waitForSelector('#password', { timeout: timeoutMs })
await page.type('#password', password, { delay: 10 })
submitP = () => page.click('button[type="submit"]')
}
await Promise.all([
waitForConditionOrAtCapacity(page, () =>
page.waitForNavigation({
waitUntil: 'networkidle2',
timeout: timeoutMs
})
),
submitP()
])
} else {
await delay(2000)
await checkForChatGPTAtCapacity(page)
}
const pageCookies = await page.cookies()
await redis.set('CHATGPT:RAW_COOKIES', JSON.stringify(pageCookies))
const cookies = pageCookies.reduce(
(map, cookie) => ({ ...map, [cookie.name]: cookie }),
{}
)
const authInfo = {
userAgent,
clearanceToken: cookies.cf_clearance?.value,
sessionToken: cookies['__Secure-next-auth.session-token']?.value,
cookies
}
logger.info('chatgpt登录成功')
return authInfo
} catch (err) {
throw err
} finally {
await page.screenshot({
type: 'png',
path: './error.png'
})
if (origBrowser) {
if (page && page !== origPage) {
await page.close()
}
} else if (browser) {
await browser.close()
}
page = null
browser = null
}
}
async function checkForChatGPTAtCapacity (page, opts = {}) {
const {
timeoutMs = Config.chromeTimeoutMS, // 2 minutes
pollingIntervalMs = 3000,
retries = 10
} = opts
// console.log('checkForChatGPTAtCapacity', page.url())
let isAtCapacity = false
let numTries = 0
do {
try {
await solveSimpleCaptchas(page)
const res = await page.$x("//div[contains(., 'ChatGPT is at capacity')]")
isAtCapacity = !!res?.length
if (isAtCapacity) {
if (++numTries >= retries) {
break
}
// try refreshing the page if chatgpt is at capacity
await page.reload({
waitUntil: 'networkidle2',
timeout: timeoutMs
})
await delay(pollingIntervalMs)
}
} catch (err) {
// ignore errors likely due to navigation
++numTries
break
}
} while (isAtCapacity)
if (isAtCapacity) {
const error = new Error('ChatGPT is at capacity')
error.statusCode = 503
throw error
}
}
async function waitForConditionOrAtCapacity (
page,
condition,
opts = {}
) {
const { pollingIntervalMs = 500 } = opts
return new Promise((resolve, reject) => {
let resolved = false
async function waitForCapacityText () {
if (resolved) {
return
}
try {
await checkForChatGPTAtCapacity(page)
if (!resolved) {
setTimeout(waitForCapacityText, pollingIntervalMs)
}
} catch (err) {
if (!resolved) {
resolved = true
return reject(err)
}
}
}
condition()
.then(() => {
if (!resolved) {
resolved = true
resolve()
}
})
.catch((err) => {
if (!resolved) {
resolved = true
reject(err)
}
})
setTimeout(waitForCapacityText, pollingIntervalMs)
})
}
async function solveSimpleCaptchas (page) {
try {
const verifyYouAreHuman = await page.$('text=Verify you are human')
if (verifyYouAreHuman) {
logger.mark('encounter cloudflare simple captcha "Verify you are human"')
await delay(2000)
await verifyYouAreHuman.click({
delay: random.int(5, 25)
})
await delay(1000)
}
const verifyYouAreHumanCN = await page.$('text=确认您是真人')
if (verifyYouAreHumanCN) {
logger.mark('encounter cloudflare simple captcha "确认您是真人"')
await delay(2000)
await verifyYouAreHumanCN.click({
delay: random.int(5, 25)
})
await delay(1000)
}
const cloudflareButton = await page.$('.hcaptcha-box')
if (cloudflareButton) {
await delay(2000)
await cloudflareButton.click({
delay: random.int(5, 25)
})
await delay(1000)
}
} catch (err) {
// ignore errors
}
}