Commit 88efa647 authored by Travis Fischer's avatar Travis Fischer Committed by GitHub

Merge pull request #155 from transitive-bullshit/feature/api-redesign

parents 24f51ac3 df1db264
......@@ -4,3 +4,4 @@ dist/
node_modules/
.next/
.vercel/
third-party/
\ No newline at end of file
......@@ -16,40 +16,63 @@ async function main() {
const email = process.env.OPENAI_EMAIL
const password = process.env.OPENAI_PASSWORD
const api = new ChatGPTAPIBrowser({ email, password, debug: true })
await api.init()
const api = new ChatGPTAPIBrowser({
email,
password,
debug: false,
minimize: true
})
await api.initSession()
const prompt = 'What is OpenAI?'
const prompt = 'Write a poem about cats.'
const response = await oraPromise(api.sendMessage(prompt), {
let res = await oraPromise(api.sendMessage(prompt), {
text: prompt
})
console.log(response)
console.log('\n' + res.response + '\n')
const prompt2 = 'Did they made OpenGPT?'
const prompt2 = 'Can you make it cuter and shorter?'
console.log(
await oraPromise(api.sendMessage(prompt2), {
res = await oraPromise(
api.sendMessage(prompt2, {
conversationId: res.conversationId,
parentMessageId: res.messageId
}),
{
text: prompt2
})
}
)
console.log('\n' + res.response + '\n')
const prompt3 = 'Who founded this institute?'
const prompt3 = 'Now write it in French.'
console.log(
await oraPromise(api.sendMessage(prompt3), {
res = await oraPromise(
api.sendMessage(prompt3, {
conversationId: res.conversationId,
parentMessageId: res.messageId
}),
{
text: prompt3
})
}
)
console.log('\n' + res.response + '\n')
const prompt4 = 'Who is that?'
const prompt4 = 'What were we talking about again?'
console.log(
await oraPromise(api.sendMessage(prompt4), {
res = await oraPromise(
api.sendMessage(prompt4, {
conversationId: res.conversationId,
parentMessageId: res.messageId
}),
{
text: prompt4
})
}
)
console.log('\n' + res.response + '\n')
// close the browser at the end
await api.closeSession()
}
main().catch((err) => {
......
......@@ -22,41 +22,56 @@ async function main() {
})
const api = new ChatGPTAPI({ ...authInfo })
await api.ensureAuth()
await api.initSession()
const conversation = api.getConversation()
const prompt = 'Write a poem about cats.'
const prompt = 'What is OpenAI?'
const response = await oraPromise(conversation.sendMessage(prompt), {
let res = await oraPromise(api.sendMessage(prompt), {
text: prompt
})
console.log(response)
console.log('\n' + res.response + '\n')
const prompt2 = 'Did they made OpenGPT?'
const prompt2 = 'Can you make it cuter and shorter?'
console.log(
await oraPromise(conversation.sendMessage(prompt2), {
res = await oraPromise(
api.sendMessage(prompt2, {
conversationId: res.conversationId,
parentMessageId: res.messageId
}),
{
text: prompt2
})
}
)
console.log('\n' + res.response + '\n')
const prompt3 = 'Who founded this institute?'
const prompt3 = 'Now write it in French.'
console.log(
await oraPromise(conversation.sendMessage(prompt3), {
res = await oraPromise(
api.sendMessage(prompt3, {
conversationId: res.conversationId,
parentMessageId: res.messageId
}),
{
text: prompt3
})
}
)
console.log('\n' + res.response + '\n')
const prompt4 = 'Who is that?'
const prompt4 = 'What were we talking about again?'
console.log(
await oraPromise(conversation.sendMessage(prompt4), {
res = await oraPromise(
api.sendMessage(prompt4, {
conversationId: res.conversationId,
parentMessageId: res.messageId
}),
{
text: prompt4
})
}
)
console.log('\n' + res.response + '\n')
await api.closeSession()
}
main().catch((err) => {
......
import delay from 'delay'
import dotenv from 'dotenv-safe'
import { oraPromise } from 'ora'
......@@ -23,24 +22,21 @@ async function main() {
debug: false,
minimize: true
})
await api.init()
await api.initSession()
const prompt =
'Write a python version of bubble sort. Do not include example usage.'
const response = await oraPromise(api.sendMessage(prompt), {
const res = await oraPromise(api.sendMessage(prompt), {
text: prompt
})
console.log(res.response)
await api.close()
return response
// close the browser at the end
await api.closeSession()
}
main()
.then((res) => {
console.log(res)
})
.catch((err) => {
console.error(err)
process.exit(1)
})
main().catch((err) => {
console.error(err)
process.exit(1)
})
[chatgpt](../readme.md) / [Exports](../modules.md) / AChatGPTAPI
# Class: AChatGPTAPI
## Hierarchy
- **`AChatGPTAPI`**
[`ChatGPTAPI`](ChatGPTAPI.md)
[`ChatGPTAPIBrowser`](ChatGPTAPIBrowser.md)
## Table of contents
### Constructors
- [constructor](AChatGPTAPI.md#constructor)
### Methods
- [closeSession](AChatGPTAPI.md#closesession)
- [getIsAuthenticated](AChatGPTAPI.md#getisauthenticated)
- [initSession](AChatGPTAPI.md#initsession)
- [refreshSession](AChatGPTAPI.md#refreshsession)
- [resetSession](AChatGPTAPI.md#resetsession)
- [sendMessage](AChatGPTAPI.md#sendmessage)
## Constructors
### constructor
**new AChatGPTAPI**()
## Methods
### closeSession
`Abstract` **closeSession**(): `Promise`<`void`\>
Closes the active session.
**`Throws`**
An error if it fails.
#### Returns
`Promise`<`void`\>
#### Defined in
[src/abstract-chatgpt-api.ts:69](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/abstract-chatgpt-api.ts#L69)
___
### getIsAuthenticated
`Abstract` **getIsAuthenticated**(): `Promise`<`boolean`\>
#### Returns
`Promise`<`boolean`\>
`true` if the client is authenticated with a valid session or `false`
otherwise.
#### Defined in
[src/abstract-chatgpt-api.ts:39](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/abstract-chatgpt-api.ts#L39)
___
### initSession
`Abstract` **initSession**(): `Promise`<`void`\>
Performs any async initialization work required to ensure that this API is
properly authenticated.
**`Throws`**
An error if the session failed to initialize properly.
#### Returns
`Promise`<`void`\>
#### Defined in
[src/abstract-chatgpt-api.ts:10](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/abstract-chatgpt-api.ts#L10)
___
### refreshSession
`Abstract` **refreshSession**(): `Promise`<`any`\>
Refreshes the current ChatGPT session.
Useful for bypassing 403 errors when Cloudflare clearance tokens expire.
**`Throws`**
An error if it fails.
#### Returns
`Promise`<`any`\>
Access credentials for the new session.
#### Defined in
[src/abstract-chatgpt-api.ts:49](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/abstract-chatgpt-api.ts#L49)
___
### resetSession
**resetSession**(): `Promise`<`any`\>
Closes the current ChatGPT session and starts a new one.
Useful for bypassing 401 errors when sessions expire.
**`Throws`**
An error if it fails.
#### Returns
`Promise`<`any`\>
Access credentials for the new session.
#### Defined in
[src/abstract-chatgpt-api.ts:59](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/abstract-chatgpt-api.ts#L59)
___
### sendMessage
`Abstract` **sendMessage**(`message`, `opts?`): `Promise`<[`ChatResponse`](../modules.md#chatresponse)\>
Sends a message to ChatGPT, waits for the response to resolve, and returns
the response.
If you want to receive a stream of partial responses, use `opts.onProgress`.
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `message` | `string` | The prompt message to send |
| `opts?` | [`SendMessageOptions`](../modules.md#sendmessageoptions) | - |
#### Returns
`Promise`<[`ChatResponse`](../modules.md#chatresponse)\>
The response from ChatGPT, including `conversationId`, `messageId`, and
the `response` text.
#### Defined in
[src/abstract-chatgpt-api.ts:30](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/abstract-chatgpt-api.ts#L30)
......@@ -2,6 +2,12 @@
# Class: ChatGPTAPI
## Hierarchy
- [`AChatGPTAPI`](AChatGPTAPI.md)
**`ChatGPTAPI`**
## Table of contents
### Constructors
......@@ -17,10 +23,11 @@
### Methods
- [ensureAuth](ChatGPTAPI.md#ensureauth)
- [getConversation](ChatGPTAPI.md#getconversation)
- [closeSession](ChatGPTAPI.md#closesession)
- [getIsAuthenticated](ChatGPTAPI.md#getisauthenticated)
- [refreshAccessToken](ChatGPTAPI.md#refreshaccesstoken)
- [initSession](ChatGPTAPI.md#initsession)
- [refreshSession](ChatGPTAPI.md#refreshsession)
- [resetSession](ChatGPTAPI.md#resetsession)
- [sendMessage](ChatGPTAPI.md#sendmessage)
- [sendModeration](ChatGPTAPI.md#sendmoderation)
......@@ -51,9 +58,13 @@ to obtain your `clearanceToken`.
| `opts.sessionToken` | `string` | = **Required** OpenAI session token which can be found in a valid session's cookies (see readme for instructions) |
| `opts.userAgent?` | `string` | **`Default Value`** `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36'` * |
#### Overrides
[AChatGPTAPI](AChatGPTAPI.md).[constructor](AChatGPTAPI.md#constructor)
#### Defined in
[src/chatgpt-api.ts:45](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api.ts#L45)
[src/chatgpt-api.ts:45](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api.ts#L45)
## Accessors
......@@ -69,7 +80,7 @@ Gets the current Cloudflare clearance token (`cf_clearance` cookie value).
#### Defined in
[src/chatgpt-api.ts:137](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api.ts#L137)
[src/chatgpt-api.ts:143](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api.ts#L143)
___
......@@ -85,7 +96,7 @@ Gets the current session token.
#### Defined in
[src/chatgpt-api.ts:132](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api.ts#L132)
[src/chatgpt-api.ts:138](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api.ts#L138)
___
......@@ -101,7 +112,7 @@ Gets the currently signed-in user, if authenticated, `null` otherwise.
#### Defined in
[src/chatgpt-api.ts:127](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api.ts#L127)
[src/chatgpt-api.ts:133](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api.ts#L133)
___
......@@ -117,74 +128,79 @@ Gets the current user agent.
#### Defined in
[src/chatgpt-api.ts:142](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api.ts#L142)
[src/chatgpt-api.ts:148](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api.ts#L148)
## Methods
### ensureAuth
### closeSession
**ensureAuth**(): `Promise`<`string`\>
**closeSession**(): `Promise`<`void`\>
Refreshes the client's access token which will succeed only if the session
is still valid.
Closes the active session.
**`Throws`**
An error if it fails.
#### Returns
`Promise`<`string`\>
`Promise`<`void`\>
#### Overrides
[AChatGPTAPI](AChatGPTAPI.md).[closeSession](AChatGPTAPI.md#closesession)
#### Defined in
[src/chatgpt-api.ts:359](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api.ts#L359)
[src/chatgpt-api.ts:470](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api.ts#L470)
___
### getConversation
**getConversation**(`opts?`): [`ChatGPTConversation`](ChatGPTConversation.md)
### getIsAuthenticated
Gets a new ChatGPTConversation instance, which can be used to send multiple
messages as part of a single conversation.
**getIsAuthenticated**(): `Promise`<`boolean`\>
#### Parameters
#### Returns
| Name | Type | Description |
| :------ | :------ | :------ |
| `opts` | `Object` | - |
| `opts.conversationId?` | `string` | Optional ID of the previous message in a conversation |
| `opts.parentMessageId?` | `string` | Optional ID of the previous message in a conversation |
`Promise`<`boolean`\>
#### Returns
`true` if the client has a valid acces token or `false` if refreshing
the token fails.
[`ChatGPTConversation`](ChatGPTConversation.md)
#### Overrides
The new conversation instance
[AChatGPTAPI](AChatGPTAPI.md).[getIsAuthenticated](AChatGPTAPI.md#getisauthenticated)
#### Defined in
[src/chatgpt-api.ts:465](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api.ts#L465)
[src/chatgpt-api.ts:367](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api.ts#L367)
___
### getIsAuthenticated
### initSession
**getIsAuthenticated**(): `Promise`<`boolean`\>
**initSession**(): `Promise`<`void`\>
Refreshes the client's access token which will succeed only if the session
is valid.
#### Returns
`Promise`<`boolean`\>
`Promise`<`void`\>
`true` if the client has a valid acces token or `false` if refreshing
the token fails.
#### Overrides
[AChatGPTAPI](AChatGPTAPI.md).[initSession](AChatGPTAPI.md#initsession)
#### Defined in
[src/chatgpt-api.ts:346](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api.ts#L346)
[src/chatgpt-api.ts:156](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api.ts#L156)
___
### refreshAccessToken
### refreshSession
**refreshAccessToken**(): `Promise`<`string`\>
**refreshSession**(): `Promise`<`string`\>
Attempts to refresh the current access token using the ChatGPT
`sessionToken` cookie.
......@@ -202,15 +218,47 @@ An error if refreshing the access token fails.
A valid access token
#### Overrides
[AChatGPTAPI](AChatGPTAPI.md).[refreshSession](AChatGPTAPI.md#refreshsession)
#### Defined in
[src/chatgpt-api.ts:373](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api.ts#L373)
[src/chatgpt-api.ts:386](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api.ts#L386)
___
### resetSession
**resetSession**(): `Promise`<`any`\>
Closes the current ChatGPT session and starts a new one.
Useful for bypassing 401 errors when sessions expire.
**`Throws`**
An error if it fails.
#### Returns
`Promise`<`any`\>
Access credentials for the new session.
#### Inherited from
[AChatGPTAPI](AChatGPTAPI.md).[resetSession](AChatGPTAPI.md#resetsession)
#### Defined in
[src/abstract-chatgpt-api.ts:59](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/abstract-chatgpt-api.ts#L59)
___
### sendMessage
**sendMessage**(`message`, `opts?`): `Promise`<`string`\>
**sendMessage**(`message`, `opts?`): `Promise`<[`ChatResponse`](../modules.md#chatresponse)\>
Sends a message to ChatGPT, waits for the response to resolve, and returns
the response.
......@@ -229,13 +277,17 @@ helper.
#### Returns
`Promise`<`string`\>
`Promise`<[`ChatResponse`](../modules.md#chatresponse)\>
The response from ChatGPT
#### Overrides
[AChatGPTAPI](AChatGPTAPI.md).[sendMessage](AChatGPTAPI.md#sendmessage)
#### Defined in
[src/chatgpt-api.ts:167](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api.ts#L167)
[src/chatgpt-api.ts:180](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api.ts#L180)
___
......@@ -255,4 +307,4 @@ ___
#### Defined in
[src/chatgpt-api.ts:303](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api.ts#L303)
[src/chatgpt-api.ts:324](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api.ts#L324)
......@@ -2,20 +2,31 @@
# Class: ChatGPTAPIBrowser
## Hierarchy
- [`AChatGPTAPI`](AChatGPTAPI.md)
**`ChatGPTAPIBrowser`**
## Table of contents
### Constructors
- [constructor](ChatGPTAPIBrowser.md#constructor)
### Accessors
- [isChatPage](ChatGPTAPIBrowser.md#ischatpage)
### Methods
- [\_onRequest](ChatGPTAPIBrowser.md#_onrequest)
- [\_onResponse](ChatGPTAPIBrowser.md#_onresponse)
- [close](ChatGPTAPIBrowser.md#close)
- [closeSession](ChatGPTAPIBrowser.md#closesession)
- [getIsAuthenticated](ChatGPTAPIBrowser.md#getisauthenticated)
- [handle403Error](ChatGPTAPIBrowser.md#handle403error)
- [init](ChatGPTAPIBrowser.md#init)
- [initSession](ChatGPTAPIBrowser.md#initsession)
- [refreshSession](ChatGPTAPIBrowser.md#refreshsession)
- [resetSession](ChatGPTAPIBrowser.md#resetsession)
- [resetThread](ChatGPTAPIBrowser.md#resetthread)
- [sendMessage](ChatGPTAPIBrowser.md#sendmessage)
......@@ -25,25 +36,43 @@
**new ChatGPTAPIBrowser**(`opts`)
Creates a new client wrapper for automating the ChatGPT webapp.
Creates a new client for automating the ChatGPT webapp.
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `opts` | `Object` | - |
| `opts.browserPath?` | `string` | **`Default Value`** `undefined` * |
| `opts.captchaToken?` | `string` | **`Default Value`** `undefined` * |
| `opts.debug?` | `boolean` | **`Default Value`** `false` * |
| `opts.email` | `string` | - |
| `opts.executablePath?` | `string` | **`Default Value`** `undefined` * |
| `opts.isGoogleLogin?` | `boolean` | **`Default Value`** `false` * |
| `opts.markdown?` | `boolean` | **`Default Value`** `true` * |
| `opts.minimize?` | `boolean` | **`Default Value`** `true` * |
| `opts.password` | `string` | - |
#### Overrides
[AChatGPTAPI](AChatGPTAPI.md).[constructor](AChatGPTAPI.md#constructor)
#### Defined in
[src/chatgpt-api-browser.ts:32](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api-browser.ts#L32)
[src/chatgpt-api-browser.ts:36](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api-browser.ts#L36)
## Accessors
### isChatPage
`get` **isChatPage**(): `boolean`
#### Returns
`boolean`
#### Defined in
[src/chatgpt-api-browser.ts:524](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api-browser.ts#L524)
## Methods
......@@ -63,7 +92,7 @@ Creates a new client wrapper for automating the ChatGPT webapp.
#### Defined in
[src/chatgpt-api-browser.ts:153](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api-browser.ts#L153)
[src/chatgpt-api-browser.ts:173](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api-browser.ts#L173)
___
......@@ -83,21 +112,31 @@ ___
#### Defined in
[src/chatgpt-api-browser.ts:190](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api-browser.ts#L190)
[src/chatgpt-api-browser.ts:210](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api-browser.ts#L210)
___
### close
### closeSession
**closeSession**(): `Promise`<`void`\>
Closes the active session.
**`Throws`**
**close**(): `Promise`<`void`\>
An error if it fails.
#### Returns
`Promise`<`void`\>
#### Overrides
[AChatGPTAPI](AChatGPTAPI.md).[closeSession](AChatGPTAPI.md#closesession)
#### Defined in
[src/chatgpt-api-browser.ts:453](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api-browser.ts#L453)
[src/chatgpt-api-browser.ts:512](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api-browser.ts#L512)
___
......@@ -109,37 +148,81 @@ ___
`Promise`<`boolean`\>
`true` if the client is authenticated with a valid session or `false`
otherwise.
#### Overrides
[AChatGPTAPI](AChatGPTAPI.md).[getIsAuthenticated](AChatGPTAPI.md#getisauthenticated)
#### Defined in
[src/chatgpt-api-browser.ts:257](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api-browser.ts#L257)
[src/chatgpt-api-browser.ts:302](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api-browser.ts#L302)
___
### handle403Error
### initSession
**initSession**(): `Promise`<`void`\>
Performs any async initialization work required to ensure that this API is
properly authenticated.
**handle403Error**(): `Promise`<`void`\>
**`Throws`**
An error if the session failed to initialize properly.
#### Returns
`Promise`<`void`\>
#### Overrides
[AChatGPTAPI](AChatGPTAPI.md).[initSession](AChatGPTAPI.md#initsession)
#### Defined in
[src/chatgpt-api-browser.ts:238](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api-browser.ts#L238)
[src/chatgpt-api-browser.ts:94](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api-browser.ts#L94)
___
### init
### refreshSession
**init**(): `Promise`<`boolean`\>
**refreshSession**(): `Promise`<`void`\>
Attempts to handle 403 errors by refreshing the page.
#### Returns
`Promise`<`boolean`\>
`Promise`<`void`\>
#### Overrides
[AChatGPTAPI](AChatGPTAPI.md).[refreshSession](AChatGPTAPI.md#refreshsession)
#### Defined in
[src/chatgpt-api-browser.ts:282](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api-browser.ts#L282)
___
### resetSession
**resetSession**(): `Promise`<`void`\>
Attempts to handle 401 errors by re-authenticating.
#### Returns
`Promise`<`void`\>
#### Overrides
[AChatGPTAPI](AChatGPTAPI.md).[resetSession](AChatGPTAPI.md#resetsession)
#### Defined in
[src/chatgpt-api-browser.ts:76](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api-browser.ts#L76)
[src/chatgpt-api-browser.ts:263](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api-browser.ts#L263)
___
......@@ -153,25 +236,37 @@ ___
#### Defined in
[src/chatgpt-api-browser.ts:445](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api-browser.ts#L445)
[src/chatgpt-api-browser.ts:504](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api-browser.ts#L504)
___
### sendMessage
**sendMessage**(`message`, `opts?`): `Promise`<`string`\>
**sendMessage**(`message`, `opts?`): `Promise`<[`ChatResponse`](../modules.md#chatresponse)\>
Sends a message to ChatGPT, waits for the response to resolve, and returns
the response.
If you want to receive a stream of partial responses, use `opts.onProgress`.
#### Parameters
| Name | Type |
| :------ | :------ |
| `message` | `string` |
| `opts` | [`SendMessageOptions`](../modules.md#sendmessageoptions) |
| Name | Type | Description |
| :------ | :------ | :------ |
| `message` | `string` | The prompt message to send |
| `opts` | [`SendMessageOptions`](../modules.md#sendmessageoptions) | - |
#### Returns
`Promise`<`string`\>
`Promise`<[`ChatResponse`](../modules.md#chatresponse)\>
The response from ChatGPT, including `conversationId`, `messageId`, and
the `response` text.
#### Overrides
[AChatGPTAPI](AChatGPTAPI.md).[sendMessage](AChatGPTAPI.md#sendmessage)
#### Defined in
[src/chatgpt-api-browser.ts:330](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-api-browser.ts#L330)
[src/chatgpt-api-browser.ts:379](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/chatgpt-api-browser.ts#L379)
[chatgpt](../readme.md) / [Exports](../modules.md) / ChatGPTConversation
# Class: ChatGPTConversation
A conversation wrapper around the ChatGPTAPI. This allows you to send
multiple messages to ChatGPT and receive responses, without having to
manually pass the conversation ID and parent message ID for each message.
## Table of contents
### Constructors
- [constructor](ChatGPTConversation.md#constructor)
### Properties
- [api](ChatGPTConversation.md#api)
- [conversationId](ChatGPTConversation.md#conversationid)
- [parentMessageId](ChatGPTConversation.md#parentmessageid)
### Methods
- [sendMessage](ChatGPTConversation.md#sendmessage)
## Constructors
### constructor
**new ChatGPTConversation**(`api`, `opts?`)
Creates a new conversation wrapper around the ChatGPT API.
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `api` | [`ChatGPTAPI`](ChatGPTAPI.md) | The ChatGPT API instance to use |
| `opts` | `Object` | - |
| `opts.conversationId?` | `string` | Optional ID of a conversation to continue |
| `opts.parentMessageId?` | `string` | Optional ID of the previous message in the conversation |
#### Defined in
[src/chatgpt-conversation.ts:21](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-conversation.ts#L21)
## Properties
### api
**api**: [`ChatGPTAPI`](ChatGPTAPI.md)
#### Defined in
[src/chatgpt-conversation.ts:10](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-conversation.ts#L10)
___
### conversationId
**conversationId**: `string` = `undefined`
#### Defined in
[src/chatgpt-conversation.ts:11](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-conversation.ts#L11)
___
### parentMessageId
**parentMessageId**: `string` = `undefined`
#### Defined in
[src/chatgpt-conversation.ts:12](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-conversation.ts#L12)
## Methods
### sendMessage
**sendMessage**(`message`, `opts?`): `Promise`<`string`\>
Sends a message to ChatGPT, waits for the response to resolve, and returns
the response.
If this is the first message in the conversation, the conversation ID and
parent message ID will be automatically set.
This allows you to send multiple messages to ChatGPT and receive responses,
without having to manually pass the conversation ID and parent message ID
for each message.
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `message` | `string` | The prompt message to send |
| `opts` | [`SendConversationMessageOptions`](../modules.md#sendconversationmessageoptions) | - |
#### Returns
`Promise`<`string`\>
The response from ChatGPT
#### Defined in
[src/chatgpt-conversation.ts:48](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/chatgpt-conversation.ts#L48)
......@@ -66,7 +66,7 @@ node_modules/.pnpm/typescript@4.9.3/node_modules/typescript/lib/lib.es2022.error
#### Defined in
[src/types.ts:298](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/types.ts#L298)
[src/types.ts:297](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/types.ts#L297)
___
......@@ -76,7 +76,7 @@ ___
#### Defined in
[src/types.ts:297](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/types.ts#L297)
[src/types.ts:296](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/types.ts#L296)
___
......@@ -86,7 +86,7 @@ ___
#### Defined in
[src/types.ts:295](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/types.ts#L295)
[src/types.ts:294](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/types.ts#L294)
___
......@@ -96,4 +96,4 @@ ___
#### Defined in
[src/types.ts:296](https://github.com/transitive-bullshit/chatgpt-api/blob/d685b78/src/types.ts#L296)
[src/types.ts:295](https://github.com/transitive-bullshit/chatgpt-api/blob/2937409/src/types.ts#L295)
This diff is collapsed.
......@@ -19,9 +19,10 @@ const api = new ChatGPTAPIBrowser({
email: process.env.OPENAI_EMAIL,
password: process.env.OPENAI_PASSWORD
})
await api.init()
await api.initSession()
const response = await api.sendMessage('Hello World!')
const result = await api.sendMessage('Hello World!')
console.log(result.response)
```
Note that this solution is not lightweight, but it does work a lot more consistently than the REST API-based versions. I'm currently using this solution to power 10 OpenAI accounts concurrently across 10 minimized Chrome windows for my [Twitter bot](https://github.com/transitive-bullshit/chatgpt-twitter-bot). 😂
......@@ -88,15 +89,13 @@ async function example() {
})
const api = new ChatGPTAPI({ ...openAIAuth })
await api.ensureAuth()
await api.initSession()
// send a message and wait for the response
const response = await api.sendMessage(
'Write a python version of bubble sort.'
)
const result = await api.sendMessage('Write a python version of bubble sort.')
// response is a markdown-formatted string
console.log(response)
// result.response is a markdown-formatted string
console.log(result.response)
}
```
......@@ -112,10 +111,10 @@ async function example() {
password: process.env.OPENAI_PASSWORD
})
await api.init()
await api.initSession()
const response = await api.sendMessage('Hello World!')
console.log(response)
const result = await api.sendMessage('Hello World!')
console.log(result.response)
}
```
......@@ -125,21 +124,30 @@ ChatGPT responses are formatted as markdown by default. If you want to work with
const api = new ChatGPTAPI({ ...openAIAuth, markdown: false })
```
If you want to automatically track the conversation, you can use `ChatGPTAPI.getConversation()`:
If you want to track the conversation, use the `conversationId` and `messageId` in the result object, and pass them to `sendMessage` as `conversationId` and `parentMessageId` respectively.
```ts
const api = new ChatGPTAPI({ ...openAIAuth, markdown: false })
const conversation = api.getConversation()
await api.initSession()
// send a message and wait for the response
const response0 = await conversation.sendMessage('What is OpenAI?')
let res = await conversation.sendMessage('What is OpenAI?')
console.log(res.response)
// send a follow-up
const response1 = await conversation.sendMessage('Can you expand on that?')
res = await conversation.sendMessage('Can you expand on that?', {
conversationId: res.conversationId,
parentMessageId: res.messageId
})
console.log(res.response)
// send another follow-up
const response2 = await conversation.sendMessage('Oh cool; thank you')
// send a follow-up
res = await conversation.sendMessage('What were we talking about?', {
conversationId: res.conversationId,
parentMessageId: res.messageId
})
console.log(res.response)
```
Sometimes, ChatGPT will hang for an extended period of time before beginning to respond. This may be due to rate limiting or it may be due to OpenAI's servers being overloaded.
......@@ -153,8 +161,6 @@ const response = await api.sendMessage('this is a timeout test', {
})
```
You can stream responses using the `onProgress` or `onConversationResponse` callbacks. See the [docs](./docs/classes/ChatGPTAPI.md) for more details.
<details>
<summary>Usage in CommonJS (Dynamic import)</summary>
......@@ -169,10 +175,10 @@ async function example() {
})
const api = new ChatGPTAPI({ ...openAIAuth })
await api.ensureAuth()
await api.initSession()
const response = await api.sendMessage('Hello World!')
console.log(response)
const result = await api.sendMessage('Hello World!')
console.log(result)
}
```
......
......@@ -16,7 +16,8 @@
}
},
"files": [
"build"
"build",
"third-party"
],
"engines": {
"node": ">=18"
......
......@@ -17,9 +17,10 @@ const api = new ChatGPTAPIBrowser({
email: process.env.OPENAI_EMAIL,
password: process.env.OPENAI_PASSWORD
})
await api.init()
await api.initSession()
const response = await api.sendMessage('Hello World!')
const result = await api.sendMessage('Hello World!')
console.log(result.response)
```
Note that this solution is not lightweight, but it does work a lot more consistently than the REST API-based versions. I'm currently using this solution to power 10 OpenAI accounts concurrently across 10 minimized Chrome windows for my [Twitter bot](https://github.com/transitive-bullshit/chatgpt-twitter-bot). 😂
......@@ -86,15 +87,13 @@ async function example() {
})
const api = new ChatGPTAPI({ ...openAIAuth })
await api.ensureAuth()
await api.initSession()
// send a message and wait for the response
const response = await api.sendMessage(
'Write a python version of bubble sort.'
)
const result = await api.sendMessage('Write a python version of bubble sort.')
// response is a markdown-formatted string
console.log(response)
// result.response is a markdown-formatted string
console.log(result.response)
}
```
......@@ -110,10 +109,10 @@ async function example() {
password: process.env.OPENAI_PASSWORD
})
await api.init()
await api.initSession()
const response = await api.sendMessage('Hello World!')
console.log(response)
const result = await api.sendMessage('Hello World!')
console.log(result.response)
}
```
......@@ -123,21 +122,30 @@ ChatGPT responses are formatted as markdown by default. If you want to work with
const api = new ChatGPTAPI({ ...openAIAuth, markdown: false })
```
If you want to automatically track the conversation, you can use `ChatGPTAPI.getConversation()`:
If you want to track the conversation, use the `conversationId` and `messageId` in the result object, and pass them to `sendMessage` as `conversationId` and `parentMessageId` respectively.
```ts
const api = new ChatGPTAPI({ ...openAIAuth, markdown: false })
const conversation = api.getConversation()
await api.initSession()
// send a message and wait for the response
const response0 = await conversation.sendMessage('What is OpenAI?')
let res = await conversation.sendMessage('What is OpenAI?')
console.log(res.response)
// send a follow-up
const response1 = await conversation.sendMessage('Can you expand on that?')
res = await conversation.sendMessage('Can you expand on that?', {
conversationId: res.conversationId,
parentMessageId: res.messageId
})
console.log(res.response)
// send another follow-up
const response2 = await conversation.sendMessage('Oh cool; thank you')
// send a follow-up
res = await conversation.sendMessage('What were we talking about?', {
conversationId: res.conversationId,
parentMessageId: res.messageId
})
console.log(res.response)
```
Sometimes, ChatGPT will hang for an extended period of time before beginning to respond. This may be due to rate limiting or it may be due to OpenAI's servers being overloaded.
......@@ -151,8 +159,6 @@ const response = await api.sendMessage('this is a timeout test', {
})
```
You can stream responses using the `onProgress` or `onConversationResponse` callbacks. See the [docs](./docs/classes/ChatGPTAPI.md) for more details.
<details>
<summary>Usage in CommonJS (Dynamic import)</summary>
......@@ -167,10 +173,10 @@ async function example() {
})
const api = new ChatGPTAPI({ ...openAIAuth })
await api.ensureAuth()
await api.initSession()
const response = await api.sendMessage('Hello World!')
console.log(response)
const result = await api.sendMessage('Hello World!')
console.log(result)
}
```
......
import * as types from './types'
export abstract class AChatGPTAPI {
/**
* Performs any async initialization work required to ensure that this API is
* properly authenticated.
*
* @throws An error if the session failed to initialize properly.
*/
abstract initSession(): Promise<void>
/**
* Sends a message to ChatGPT, waits for the response to resolve, and returns
* the response.
*
* If you want to receive a stream of partial responses, use `opts.onProgress`.
*
* @param message - The prompt message to send
* @param opts.conversationId - Optional ID of a conversation to continue
* @param opts.parentMessageId - Optional ID of the previous message in the conversation
* @param opts.messageId - Optional ID of the message to send (defaults to a random UUID)
* @param opts.action - Optional ChatGPT `action` (either `next` or `variant`)
* @param opts.timeoutMs - Optional timeout in milliseconds (defaults to no timeout)
* @param opts.onProgress - Optional callback which will be invoked every time the partial response is updated
* @param opts.abortSignal - Optional callback used to abort the underlying `fetch` call using an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
*
* @returns The response from ChatGPT, including `conversationId`, `messageId`, and
* the `response` text.
*/
abstract sendMessage(
message: string,
opts?: types.SendMessageOptions
): Promise<types.ChatResponse>
/**
* @returns `true` if the client is authenticated with a valid session or `false`
* otherwise.
*/
abstract getIsAuthenticated(): Promise<boolean>
/**
* Refreshes the current ChatGPT session.
*
* Useful for bypassing 403 errors when Cloudflare clearance tokens expire.
*
* @returns Access credentials for the new session.
* @throws An error if it fails.
*/
abstract refreshSession(): Promise<any>
/**
* Closes the current ChatGPT session and starts a new one.
*
* Useful for bypassing 401 errors when sessions expire.
*
* @returns Access credentials for the new session.
* @throws An error if it fails.
*/
async resetSession(): Promise<any> {
await this.closeSession()
return this.initSession()
}
/**
* Closes the active session.
*
* @throws An error if it fails.
*/
abstract closeSession(): Promise<void>
}
......@@ -3,15 +3,19 @@ import type { Browser, HTTPRequest, HTTPResponse, Page } from 'puppeteer'
import { v4 as uuidv4 } from 'uuid'
import * as types from './types'
import { AChatGPTAPI } from './abstract-chatgpt-api'
import { getBrowser, getOpenAIAuth } from './openai-auth'
import {
browserPostEventStream,
isRelevantRequest,
markdownToText,
maximizePage,
minimizePage
} from './utils'
export class ChatGPTAPIBrowser {
const CHAT_PAGE_URL = 'https://chat.openai.com/chat'
export class ChatGPTAPIBrowser extends AChatGPTAPI {
protected _markdown: boolean
protected _debug: boolean
protected _minimize: boolean
......@@ -27,7 +31,7 @@ export class ChatGPTAPIBrowser {
protected _page: Page
/**
* Creates a new client wrapper for automating the ChatGPT webapp.
* Creates a new client for automating the ChatGPT webapp.
*/
constructor(opts: {
email: string
......@@ -51,6 +55,8 @@ export class ChatGPTAPIBrowser {
/** @defaultValue `undefined` **/
executablePath?: string
}) {
super()
const {
email,
password,
......@@ -71,14 +77,23 @@ export class ChatGPTAPIBrowser {
this._minimize = !!minimize
this._captchaToken = captchaToken
this._executablePath = executablePath
if (!this._email) {
const error = new types.ChatGPTError('ChatGPT invalid email')
error.statusCode = 401
throw error
}
if (!this._password) {
const error = new types.ChatGPTError('ChatGPT invalid password')
error.statusCode = 401
throw error
}
}
async init() {
override async initSession() {
if (this._browser) {
await this._browser.close()
this._page = null
this._browser = null
this._accessToken = null
await this.closeSession()
}
try {
......@@ -89,6 +104,15 @@ export class ChatGPTAPIBrowser {
this._page =
(await this._browser.pages())[0] || (await this._browser.newPage())
// bypass annoying popup modals
this._page.evaluateOnNewDocument(() => {
window.localStorage.setItem('oai/apps/hasSeenOnboarding/chat', 'true')
window.localStorage.setItem(
'oai/apps/hasSeenReleaseAnnouncement/2022-12-15',
'true'
)
})
await maximizePage(this._page)
this._page.on('request', this._onRequest.bind(this))
......@@ -113,11 +137,8 @@ export class ChatGPTAPIBrowser {
throw err
}
const chatUrl = 'https://chat.openai.com/chat'
const url = this._page.url().replace(/\/$/, '')
if (url !== chatUrl) {
await this._page.goto(chatUrl, {
if (!this.isChatPage) {
await this._page.goto(CHAT_PAGE_URL, {
waitUntil: 'networkidle2'
})
}
......@@ -140,15 +161,13 @@ export class ChatGPTAPIBrowser {
await delay(300)
} while (true)
if (!this.getIsAuthenticated()) {
return false
if (!(await this.getIsAuthenticated())) {
throw new types.ChatGPTError('Failed to authenticate session')
}
if (this._minimize) {
await minimizePage(this._page)
return minimizePage(this._page)
}
return true
}
_onRequest = (request: HTTPRequest) => {
......@@ -221,11 +240,13 @@ export class ChatGPTAPIBrowser {
if (url.endsWith('/conversation')) {
if (status === 403) {
await this.handle403Error()
await this.refreshSession()
}
} else if (url.endsWith('api/auth/session')) {
if (status === 403) {
await this.handle403Error()
if (status === 401) {
await this.resetSession()
} else if (status === 403) {
await this.refreshSession()
} else {
const session: types.SessionResult = body
......@@ -236,17 +257,40 @@ export class ChatGPTAPIBrowser {
}
}
async handle403Error() {
console.log(`ChatGPT "${this._email}" session expired; refreshing...`)
/**
* Attempts to handle 401 errors by re-authenticating.
*/
async resetSession() {
console.log(
`ChatGPT "${this._email}" session expired; re-authenticating...`
)
try {
await this.closeSession()
await this.initSession()
console.log(`ChatGPT "${this._email}" re-authenticated successfully`)
} catch (err) {
console.error(
`ChatGPT "${this._email}" error re-authenticating`,
err.toString()
)
}
}
/**
* Attempts to handle 403 errors by refreshing the page.
*/
async refreshSession() {
console.log(`ChatGPT "${this._email}" session expired (403); refreshing...`)
try {
await maximizePage(this._page)
await this._page.reload({
waitUntil: 'networkidle2',
timeout: 2 * 60 * 1000 // 2 minutes
})
if (this._minimize) {
if (this._minimize && this.isChatPage) {
await minimizePage(this._page)
}
console.log(`ChatGPT "${this._email}" refreshed session successfully`)
} catch (err) {
console.error(
`ChatGPT "${this._email}" error refreshing session`,
......@@ -257,6 +301,10 @@ export class ChatGPTAPIBrowser {
async getIsAuthenticated() {
try {
if (!this._accessToken) {
return false
}
const inputBox = await this._getInputBox()
return !!inputBox
} catch (err) {
......@@ -328,28 +376,25 @@ export class ChatGPTAPIBrowser {
// }
// }
async sendMessage(
override async sendMessage(
message: string,
opts: types.SendMessageOptions = {}
): Promise<string> {
): Promise<types.ChatResponse> {
const {
conversationId,
parentMessageId = uuidv4(),
messageId = uuidv4(),
action = 'next',
timeoutMs
// TODO
timeoutMs,
// onProgress,
onConversationResponse
// onProgress
} = opts
const inputBox = await this._getInputBox()
if (!inputBox || !this._accessToken) {
if (!(await this.getIsAuthenticated())) {
console.log(`chatgpt re-authenticating ${this._email}`)
let isAuthenticated = false
try {
isAuthenticated = await this.init()
await this.resetSession()
} catch (err) {
console.warn(
`chatgpt error re-authenticating ${this._email}`,
......@@ -357,7 +402,7 @@ export class ChatGPTAPIBrowser {
)
}
if (!isAuthenticated || !this._accessToken) {
if (!(await this.getIsAuthenticated())) {
const error = new types.ChatGPTError('Not signed in')
error.statusCode = 401
throw error
......@@ -395,25 +440,24 @@ export class ChatGPTAPIBrowser {
)
// console.log('<<< EVALUATE', result)
if (result.error) {
if ('error' in result) {
const error = new types.ChatGPTError(result.error.message)
error.statusCode = result.error.statusCode
error.statusText = result.error.statusText
if (error.statusCode === 403) {
await this.handle403Error()
await this.refreshSession()
}
throw error
}
} else {
if (!this._markdown) {
result.response = markdownToText(result.response)
}
// TODO: support sending partial response events
if (onConversationResponse) {
onConversationResponse(result.conversationResponse)
return result
}
return result.response
// const lastMessage = await this.getLastMessage()
// await inputBox.focus()
......@@ -465,14 +509,24 @@ export class ChatGPTAPIBrowser {
}
}
async close() {
override async closeSession() {
await this._browser.close()
this._page = null
this._browser = null
this._accessToken = null
}
protected async _getInputBox() {
// [data-id="root"]
return this._page.$('textarea')
}
get isChatPage(): boolean {
try {
const url = this._page?.url().replace(/\/$/, '')
return url === CHAT_PAGE_URL
} catch (err) {
return false
}
}
}
......@@ -21,7 +21,7 @@ test('ChatGPTAPI invalid session token', async (t) => {
sessionToken: 'invalid',
clearanceToken: 'invalid'
})
await chatgpt.ensureAuth()
await chatgpt.initSession()
},
{
instanceOf: types.ChatGPTError,
......@@ -52,7 +52,7 @@ test('ChatGPTAPI valid session token', async (t) => {
// Don't make any real API calls using our session token if we're running on CI
if (!isCI) {
await chatgpt.ensureAuth()
await chatgpt.initSession()
const response = await chatgpt.sendMessage('test')
console.log('chatgpt response', response)
......@@ -74,7 +74,7 @@ if (!isCI) {
sessionToken: expiredSessionToken,
clearanceToken: 'invalid'
})
await chatgpt.ensureAuth()
await chatgpt.initSession()
},
{
instanceOf: types.ChatGPTError,
......
......@@ -3,7 +3,7 @@ import pTimeout from 'p-timeout'
import { v4 as uuidv4 } from 'uuid'
import * as types from './types'
import { ChatGPTConversation } from './chatgpt-conversation'
import { AChatGPTAPI } from './abstract-chatgpt-api'
import { fetch } from './fetch'
import { fetchSSE } from './fetch-sse'
import { markdownToText } from './utils'
......@@ -12,7 +12,7 @@ const KEY_ACCESS_TOKEN = 'accessToken'
const USER_AGENT =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36'
export class ChatGPTAPI {
export class ChatGPTAPI extends AChatGPTAPI {
protected _sessionToken: string
protected _clearanceToken: string
protected _markdown: boolean
......@@ -71,6 +71,8 @@ export class ChatGPTAPI {
/** @defaultValue `false` **/
debug?: boolean
}) {
super()
const {
sessionToken,
clearanceToken,
......@@ -113,11 +115,15 @@ export class ChatGPTAPI {
}
if (!this._sessionToken) {
throw new types.ChatGPTError('ChatGPT invalid session token')
const error = new types.ChatGPTError('ChatGPT invalid session token')
error.statusCode = 401
throw error
}
if (!this._clearanceToken) {
throw new types.ChatGPTError('ChatGPT invalid clearance token')
const error = new types.ChatGPTError('ChatGPT invalid clearance token')
error.statusCode = 401
throw error
}
}
......@@ -143,6 +149,14 @@ export class ChatGPTAPI {
return this._userAgent
}
/**
* Refreshes the client's access token which will succeed only if the session
* is valid.
*/
override async initSession() {
await this.refreshSession()
}
/**
* Sends a message to ChatGPT, waits for the response to resolve, and returns
* the response.
......@@ -159,23 +173,21 @@ export class ChatGPTAPI {
* @param opts.action - Optional ChatGPT `action` (either `next` or `variant`)
* @param opts.timeoutMs - Optional timeout in milliseconds (defaults to no timeout)
* @param opts.onProgress - Optional callback which will be invoked every time the partial response is updated
* @param opts.onConversationResponse - Optional callback which will be invoked every time the partial response is updated with the full conversation response
* @param opts.abortSignal - Optional callback used to abort the underlying `fetch` call using an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
*
* @returns The response from ChatGPT
*/
async sendMessage(
override async sendMessage(
message: string,
opts: types.SendMessageOptions = {}
): Promise<string> {
): Promise<types.ChatResponse> {
const {
conversationId,
parentMessageId = uuidv4(),
messageId = uuidv4(),
action = 'next',
timeoutMs,
onProgress,
onConversationResponse
onProgress
} = opts
let { abortSignal } = opts
......@@ -186,7 +198,7 @@ export class ChatGPTAPI {
abortSignal = abortController.signal
}
const accessToken = await this.refreshAccessToken()
const accessToken = await this.refreshSession()
const body: types.ConversationJSONBody = {
action,
......@@ -208,9 +220,13 @@ export class ChatGPTAPI {
body.conversation_id = conversationId
}
let response = ''
const result: types.ChatResponse = {
conversationId,
messageId,
response: ''
}
const responseP = new Promise<string>((resolve, reject) => {
const responseP = new Promise<types.ChatResponse>((resolve, reject) => {
const url = `${this._backendApiBaseUrl}/conversation`
const headers = {
...this._headers,
......@@ -231,17 +247,22 @@ export class ChatGPTAPI {
signal: abortSignal,
onMessage: (data: string) => {
if (data === '[DONE]') {
return resolve(response)
return resolve(result)
}
try {
const parsedData: types.ConversationResponseEvent = JSON.parse(data)
if (onConversationResponse) {
onConversationResponse(parsedData)
const convoResponseEvent: types.ConversationResponseEvent =
JSON.parse(data)
if (convoResponseEvent.conversation_id) {
result.conversationId = convoResponseEvent.conversation_id
}
const message = parsedData.message
// console.log('event', JSON.stringify(parsedData, null, 2))
if (convoResponseEvent.message?.id) {
result.messageId = convoResponseEvent.message.id
}
const message = convoResponseEvent.message
// console.log('event', JSON.stringify(convoResponseEvent, null, 2))
if (message) {
let text = message?.content?.parts?.[0]
......@@ -251,10 +272,10 @@ export class ChatGPTAPI {
text = markdownToText(text)
}
response = text
result.response = text
if (onProgress) {
onProgress(text)
onProgress(result)
}
}
}
......@@ -267,7 +288,7 @@ export class ChatGPTAPI {
const errMessageL = err.toString().toLowerCase()
if (
response &&
result.response &&
(errMessageL === 'error: typeerror: terminated' ||
errMessageL === 'typeerror: terminated')
) {
......@@ -275,7 +296,7 @@ export class ChatGPTAPI {
// the HTTP request has resolved cleanly. In my testing, these cases tend to
// happen when OpenAI has already send the last `response`, so we can ignore
// the `fetch` error in this case.
return resolve(response)
return resolve(result)
} else {
return reject(err)
}
......@@ -301,7 +322,7 @@ export class ChatGPTAPI {
}
async sendModeration(input: string) {
const accessToken = await this.refreshAccessToken()
const accessToken = await this.refreshSession()
const url = `${this._backendApiBaseUrl}/moderations`
const headers = {
...this._headers,
......@@ -343,23 +364,15 @@ export class ChatGPTAPI {
* @returns `true` if the client has a valid acces token or `false` if refreshing
* the token fails.
*/
async getIsAuthenticated() {
override async getIsAuthenticated() {
try {
void (await this.refreshAccessToken())
void (await this.refreshSession())
return true
} catch (err) {
return false
}
}
/**
* Refreshes the client's access token which will succeed only if the session
* is still valid.
*/
async ensureAuth() {
return await this.refreshAccessToken()
}
/**
* Attempts to refresh the current access token using the ChatGPT
* `sessionToken` cookie.
......@@ -370,7 +383,7 @@ export class ChatGPTAPI {
* @returns A valid access token
* @throws An error if refreshing the access token fails.
*/
async refreshAccessToken(): Promise<string> {
override async refreshSession(): Promise<string> {
const cachedAccessToken = this._accessTokenCache.get(KEY_ACCESS_TOKEN)
if (cachedAccessToken) {
return cachedAccessToken
......@@ -454,17 +467,7 @@ export class ChatGPTAPI {
}
}
/**
* Gets a new ChatGPTConversation instance, which can be used to send multiple
* messages as part of a single conversation.
*
* @param opts.conversationId - Optional ID of the previous message in a conversation
* @param opts.parentMessageId - Optional ID of the previous message in a conversation
* @returns The new conversation instance
*/
getConversation(
opts: { conversationId?: string; parentMessageId?: string } = {}
) {
return new ChatGPTConversation(this, opts)
override async closeSession(): Promise<void> {
this._accessTokenCache.delete(KEY_ACCESS_TOKEN)
}
}
import * as types from './types'
import { type ChatGPTAPI } from './chatgpt-api'
/**
* A conversation wrapper around the ChatGPTAPI. This allows you to send
* multiple messages to ChatGPT and receive responses, without having to
* manually pass the conversation ID and parent message ID for each message.
*/
export class ChatGPTConversation {
api: ChatGPTAPI
conversationId: string = undefined
parentMessageId: string = undefined
/**
* Creates a new conversation wrapper around the ChatGPT API.
*
* @param api - The ChatGPT API instance to use
* @param opts.conversationId - Optional ID of a conversation to continue
* @param opts.parentMessageId - Optional ID of the previous message in the conversation
*/
constructor(
api: ChatGPTAPI,
opts: { conversationId?: string; parentMessageId?: string } = {}
) {
this.api = api
this.conversationId = opts.conversationId
this.parentMessageId = opts.parentMessageId
}
/**
* Sends a message to ChatGPT, waits for the response to resolve, and returns
* the response.
*
* If this is the first message in the conversation, the conversation ID and
* parent message ID will be automatically set.
*
* This allows you to send multiple messages to ChatGPT and receive responses,
* without having to manually pass the conversation ID and parent message ID
* for each message.
*
* @param message - The prompt message to send
* @param opts.onProgress - Optional callback which will be invoked every time the partial response is updated
* @param opts.onConversationResponse - Optional callback which will be invoked every time the partial response is updated with the full conversation response
* @param opts.abortSignal - Optional callback used to abort the underlying `fetch` call using an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
*
* @returns The response from ChatGPT
*/
async sendMessage(
message: string,
opts: types.SendConversationMessageOptions = {}
): Promise<string> {
const { onConversationResponse, ...rest } = opts
return this.api.sendMessage(message, {
...rest,
conversationId: this.conversationId,
parentMessageId: this.parentMessageId,
onConversationResponse: (response) => {
if (response.conversation_id) {
this.conversationId = response.conversation_id
}
if (response.message?.id) {
this.parentMessageId = response.message.id
}
if (onConversationResponse) {
return onConversationResponse(response)
}
}
})
}
}
export * from './chatgpt-api'
export * from './chatgpt-api-browser'
export * from './chatgpt-conversation'
export * from './abstract-chatgpt-api'
export * from './types'
export * from './utils'
export * from './openai-auth'
import * as fs from 'node:fs'
import * as os from 'node:os'
import * as path from 'node:path'
import * as url from 'node:url'
import delay from 'delay'
import { TimeoutError } from 'p-timeout'
import type { Browser, Page, Protocol, PuppeteerLaunchOptions } from 'puppeteer'
import puppeteer from 'puppeteer-extra'
import RecaptchaPlugin from 'puppeteer-extra-plugin-recaptcha'
......@@ -12,6 +15,10 @@ import * as types from './types'
puppeteer.use(StealthPlugin())
let hasRecaptchaPlugin = false
let hasNopechaExtension = false
const __filename = url.fileURLToPath(import.meta.url)
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
/**
* Represents everything that's required to pass into `ChatGPTAPI` in order
......@@ -122,13 +129,21 @@ export async function getOpenAIAuth({
await page.type('#username', email, { delay: 20 })
await delay(100)
if (hasRecaptchaPlugin) {
// console.log('solveRecaptchas()')
if (hasNopechaExtension) {
await waitForRecaptcha(page, { timeoutMs })
} else if (hasRecaptchaPlugin) {
const res = await page.solveRecaptchas()
// console.log('solveRecaptchas result', res)
console.log('solveRecaptchas result', res)
}
await page.click('button[type="submit"]')
await delay(1200)
const frame = page.mainFrame()
const submit = await page.waitForSelector('button[type="submit"]', {
timeout: timeoutMs
})
frame.focus('button[type="submit"]')
await submit.focus()
await submit.click()
await page.waitForSelector('#password', { timeout: timeoutMs })
await page.type('#password', password, { delay: 10 })
submitP = () => page.click('button[type="submit"]')
......@@ -186,10 +201,12 @@ export async function getOpenAIAuth({
export async function getBrowser(
opts: PuppeteerLaunchOptions & {
captchaToken?: string
nopechaKey?: string
} = {}
) {
const {
captchaToken = process.env.CAPTCHA_TOKEN,
nopechaKey = process.env.NOPECHA_KEY,
executablePath = defaultChromeExecutablePath(),
...launchOptions
} = opts
......@@ -209,13 +226,71 @@ export async function getBrowser(
)
}
return puppeteer.launch({
const puppeteerArgs = [
'--no-sandbox',
'--disable-infobars',
'--disable-dev-shm-usage',
'--disable-blink-features=AutomationControlled',
'--no-first-run',
'--no-service-autorun',
'--password-store=basic',
'--system-developer-mode'
]
if (nopechaKey) {
const nopechaPath = path.join(
__dirname,
'..',
'third-party',
'nopecha-chrome-extension'
)
puppeteerArgs.push(`--disable-extensions-except=${nopechaPath}`)
puppeteerArgs.push(`--load-extension=${nopechaPath}`)
hasNopechaExtension = true
}
const browser = await puppeteer.launch({
headless: false,
args: ['--no-sandbox', '--exclude-switches', 'enable-automation'],
// https://peter.sh/experiments/chromium-command-line-switches/
args: puppeteerArgs,
ignoreDefaultArgs: [
'--disable-extensions',
'--enable-automation',
'--disable-component-extensions-with-background-pages'
],
ignoreHTTPSErrors: true,
executablePath,
...launchOptions
})
// TOdO: this is a really hackity hack way of setting the API key...
if (hasNopechaExtension) {
const page = (await browser.pages())[0] || (await browser.newPage())
await page.goto(`https://nopecha.com/setup#${nopechaKey}`)
await delay(1000)
const page3 = await browser.newPage()
await page.close()
const extensionId = 'npgnhlnhpphdlkfdnggbdpbhoopefaai'
const extensionUrl = `chrome-extension://${extensionId}/popup.html`
await page3.goto(extensionUrl, { waitUntil: 'networkidle2' })
await delay(500)
const editKey = await page3.waitForSelector('#edit_key .clickable')
await editKey.click()
const settingsInput = await page3.$('input.settings_text')
await settingsInput.type(nopechaKey)
await settingsInput.evaluate((el, value) => {
el.value = value
}, nopechaKey)
await settingsInput.press('Enter')
await delay(500)
await editKey.click()
await delay(2000)
}
return browser
}
/**
......@@ -249,13 +324,6 @@ async function checkForChatGPTAtCapacity(page: Page) {
try {
res = await page.$x("//div[contains(., 'ChatGPT is at capacity')]")
// console.log('capacity1', els)
// if (els?.length) {
// res = await Promise.all(
// els.map((a) => a.evaluate((el) => el.textContent))
// )
// console.log('capacity2', res)
// }
} catch (err) {
// ignore errors likely due to navigation
}
......@@ -315,3 +383,40 @@ async function waitForConditionOrAtCapacity(
setTimeout(waitForCapacityText, pollingIntervalMs)
})
}
async function waitForRecaptcha(
page: Page,
opts: {
pollingIntervalMs?: number
timeoutMs?: number
} = {}
) {
if (!hasNopechaExtension) {
return
}
const { pollingIntervalMs = 100, timeoutMs } = opts
const captcha = await page.$('textarea#g-recaptcha-response')
const startTime = Date.now()
if (captcha) {
console.log('waiting to solve recaptcha...')
do {
const value = (await captcha.evaluate((el) => el.value))?.trim()
if (value?.length) {
// recaptcha has been solved!
break
}
if (timeoutMs) {
const now = Date.now()
if (now - startTime >= timeoutMs) {
throw new TimeoutError('Timed out waiting to solve Recaptcha')
}
}
await delay(pollingIntervalMs)
} while (true)
}
}
......@@ -281,8 +281,7 @@ export type SendMessageOptions = {
messageId?: string
action?: MessageActionType
timeoutMs?: number
onProgress?: (partialResponse: string) => void
onConversationResponse?: (response: ConversationResponseEvent) => void
onProgress?: (partialResponse: ChatResponse) => void
abortSignal?: AbortSignal
}
......@@ -300,16 +299,12 @@ export class ChatGPTError extends Error {
export type ChatError = {
error: { message: string; statusCode?: number; statusText?: string }
response: null
conversationId?: string
messageId?: string
conversationResponse?: ConversationResponseEvent
}
export type ChatResponse = {
error: null
response: string
conversationId: string
messageId: string
conversationResponse?: ConversationResponseEvent
}
......@@ -37,7 +37,7 @@ export async function maximizePage(page: Page) {
}
export function isRelevantRequest(url: string): boolean {
let pathname
let pathname: string
try {
const parsedUrl = new URL(url)
......@@ -102,7 +102,6 @@ export async function browserPostEventStream(
const BOM = [239, 187, 191]
let conversationResponse: types.ConversationResponseEvent
let conversationId: string = body?.conversation_id
let messageId: string = body?.messages?.[0]?.id
let response = ''
......@@ -136,7 +135,6 @@ export async function browserPostEventStream(
statusCode: res.status,
statusText: res.statusText
},
response: null,
conversationId,
messageId
}
......@@ -147,18 +145,15 @@ export async function browserPostEventStream(
function onMessage(data: string) {
if (data === '[DONE]') {
return resolve({
error: null,
response,
conversationId,
messageId,
conversationResponse
messageId
})
}
try {
const convoResponseEvent: types.ConversationResponseEvent =
JSON.parse(data)
conversationResponse = convoResponseEvent
if (convoResponseEvent.conversation_id) {
conversationId = convoResponseEvent.conversation_id
}
......@@ -220,11 +215,9 @@ export async function browserPostEventStream(
// happen when OpenAI has already send the last `response`, so we can ignore
// the `fetch` error in this case.
return {
error: null,
response,
conversationId,
messageId,
conversationResponse
messageId
}
}
......@@ -234,10 +227,8 @@ export async function browserPostEventStream(
statusCode: err.statusCode || err.status || err.response?.statusCode,
statusText: err.statusText || err.response?.statusText
},
response: null,
conversationId,
messageId,
conversationResponse
messageId
}
}
......@@ -456,7 +447,7 @@ export async function browserPostEventStream(
customTimers = { setTimeout, clearTimeout }
} = options
let timer
let timer: number
const cancelablePromise = new Promise((resolve, reject) => {
if (typeof milliseconds !== 'number' || Math.sign(milliseconds) !== 1) {
......
const VERSION="chrome",browser=globalThis.chrome;function reconnect_scripts(){browser.runtime.onInstalled.addListener(async()=>{for(const e of browser.runtime.getManifest().content_scripts)for(const r of await browser.tabs.query({url:e.matches}))browser.scripting.executeScript({target:{tabId:r.id},files:e.js})})}function register_language(){browser.declarativeNetRequest.updateDynamicRules({addRules:[{id:1,priority:1,action:{type:"redirect",redirect:{transform:{queryTransform:{addOrReplaceParams:[{key:"hl",value:"en-US"}]}}}},condition:{regexFilter:"^(http|https)://[^\\.]*\\.(google\\.com|recaptcha\\.net)/recaptcha",resourceTypes:["sub_frame"]}},{id:2,priority:1,action:{type:"redirect",redirect:{transform:{queryTransform:{addOrReplaceParams:[{key:"lang",value:"en"}]}}}},condition:{regexFilter:"^(http|https)://[^\\.]*\\.(funcaptcha\\.(co|com)|arkoselabs\\.(com|cn)|arkose\\.com\\.cn)",resourceTypes:["sub_frame"]}}],removeRuleIds:[1,2]})}export{VERSION,browser,reconnect_scripts,register_language};
(async()=>{let i=null;function a(a=500){return new Promise(t=>{let c=!1;const n=setInterval(async()=>{if(!c){c=!0;var a=await BG.exec("Settings.get");if(a.enabled&&a.awscaptcha_auto_solve){a=document.querySelector('input[placeholder="Answer"]');if(a&&""===a.value){var e=function(){try{return document.querySelector("audio").src.replace("data:audio/aac;base64,","")}catch(a){}return null}();if(e&&i!==e)return i=e,clearInterval(n),c=!1,t({input:a,audio_data:e})}c=!1}}},a)})}for(;;){await Time.sleep(1e3);var e=await BG.exec("Settings.get");if(e&&e.enabled){var t,c,n,o,l=await Location.hostname();if(!e.disabled_hosts.includes(l))if(e.awscaptcha_auto_open&&null!==document.querySelector("#captcha-container > #root #amzn-captcha-verify-button")){l=void 0;try{var l=document.querySelector("#captcha-container > #root #amzn-captcha-verify-button");l&&l.click()}catch(a){}await 0}else if(e.hcaptcha_auto_solve&&null!==document.querySelector('#captcha-container > #root #amzn-btn-audio-internal > img[title="Audio problem"]')){l=void 0;try{l=document.querySelector("#captcha-container > #root #amzn-btn-audio-internal");l&&l.click()}catch(a){}await 0}else e.hcaptcha_auto_solve&&null!==document.querySelector('#captcha-container > #root #amzn-btn-audio-internal > img[title="Visual problem"]')&&(n=c=t=o=e=l=void 0,{input:l,audio_data:e}=await a(),await!(null!==l&&null!==e&&(o=await BG.exec("Settings.get")).enabled&&o.awscaptcha_auto_solve&&(t=Time.time(),{job_id:c,data:e}=await NopeCHA.post({captcha_type:IS_DEVELOPMENT?"awscaptcha_dev":"awscaptcha",audio_data:[e],key:o.key}),!e||0===e.length||(n=(n=parseInt(o.awscaptcha_solve_delay_time))||1e3,0<(o=o.awscaptcha_solve_delay?n-(Time.time()-t):0)&&await Time.sleep(o),0===e[0].length)?(document.querySelector("#amzn-btn-refresh-internal")?.click(),await Time.sleep(200),i=null):(l.value=e[0],await Time.sleep(200),document.querySelector("#amzn-btn-verify-internal")?.click()))))}}})();
import{deep_copy,SettingsManager,Time}from"./utils.mjs";import*as bapi from"./api.js";class API{static endpoints={};static register(t,e){var a=t.name+"."+e;const s=t[e];this.endpoints[a]=function(){return s.apply(t,[{tab_id:arguments[0].tab_id,frame_id:arguments[0].frame_id,...arguments[0].data}])}}}class Cache{static cache={};static async set({tab_id:t,name:e,value:a,tab_specific:s}={tab_specific:!1}){return s&&(e=t+"_"+e),Cache.cache[e]=a,Cache.cache[e]}static async get({tab_id:t,name:e,tab_specific:a}={tab_specific:!1}){return a&&(e=t+"_"+e),Cache.cache[e]}static async remove({tab_id:t,name:e,tab_specific:a}={tab_specific:!1}){a&&(e=t+"_"+e);a=Cache.cache[e];return delete Cache.cache[e],a}static async append({tab_id:t,name:e,value:a,tab_specific:s}={tab_specific:!1}){return(e=s?t+"_"+e:e)in Cache.cache||(Cache.cache[e]=[]),Cache.cache[e].push(a),Cache.cache[e]}static async empty({tab_id:t,name:e,tab_specific:a}={tab_specific:!1}){a&&(e=t+"_"+e);a=Cache.cache[e];return Cache.cache[e]=[],a}static async inc({tab_id:t,name:e,tab_specific:a}={tab_specific:!1}){return(e=a?t+"_"+e:e)in Cache.cache||(Cache.cache[e]=0),Cache.cache[e]++,Cache.cache[e]}static async dec({tab_id:t,name:e,tab_specific:a}={tab_specific:!1}){return(e=a?t+"_"+e:e)in Cache.cache||(Cache.cache[e]=0),Cache.cache[e]--,Cache.cache[e]}static async zero({tab_id:t,name:e,tab_specific:a}={tab_specific:!1}){return a&&(e=t+"_"+e),Cache.cache[e]=0,Cache.cache[e]}static{API.register(this,"set"),API.register(this,"get"),API.register(this,"remove"),API.register(this,"append"),API.register(this,"empty"),API.register(this,"inc"),API.register(this,"dec"),API.register(this,"zero")}}class Settings{static data={};static _save(){return new Promise(t=>{bapi.browser.storage.sync.set({settings:Settings.data},t)})}static _get_settings(){return new Promise(e=>{bapi.browser.storage.sync.get(["settings"],({settings:t})=>{e(t)})})}static async load(){for(let t=0;t<4;t++){var e=await Settings._get_settings();if(e)return Settings.data=e,void(Settings.data.version!==SettingsManager.DEFAULT.version&&(e=Settings.data.key,await Settings.reset(),Settings.data.key=e))}await Settings.reset()}static async get(){return Settings.data}static async set({id:t,value:e}){Settings.data[t]=e,await Settings._save()}static async update({settings:t}){for(var[e,a]of Object.entries(t))Settings.data[e]=a;await Settings._save()}static async replace({settings:t}){Settings.data=t,await Settings._save()}static async reset(){Settings.data=deep_copy(SettingsManager.DEFAULT),await Settings._save()}static{API.register(this,"get"),API.register(this,"set"),API.register(this,"update"),API.register(this,"replace"),API.register(this,"reset")}}class Net{static async fetch({url:t,options:e}={options:{}}){try{return await(await fetch(t,e)).text()}catch(t){return null}}static{API.register(this,"fetch")}}class Tab{static reloads={};static _reload({tab_id:e}){return new Promise(t=>bapi.browser.tabs.reload(e,{bypassCache:!0},t))}static async reload({tab_id:t,delay:e,overwrite:a}={delay:0,overwrite:!0}){e=parseInt(e);var s=Tab.reloads[t]?.delay-(Date.now()-Tab.reloads[t]?.start),s=isNaN(s)||s<0?0:s;return!!(a||0==s||e<=s)&&(clearTimeout(Tab.reloads[t]?.timer),Tab.reloads[t]={delay:e,start:Date.now(),timer:setTimeout(()=>Tab._reload({tab_id:t}),e)},!0)}static close({tab_id:e}){return new Promise(t=>bapi.browser.tabs.remove(e,t))}static open({url:e}={url:null}){return new Promise(t=>bapi.browser.tabs.create({url:e},t))}static navigate({tab_id:e,url:a}){return new Promise(t=>bapi.browser.tabs.update(e,{url:a},t))}static info({tab_id:t}){return new Promise(e=>{try{bapi.browser.tabs.get(t,t=>e(t))}catch(t){e(!1)}})}static active(){return new Promise(async e=>{var t;if("firefox"!==bapi.VERSION)return[t]=await bapi.browser.tabs.query({active:!0,lastFocusedWindow:!0}),e(t);bapi.browser.tabs.query({active:!0,lastFocusedWindow:!0},([t])=>{bapi.browser.runtime.lastError,e(t)})})}static{API.register(this,"reload"),API.register(this,"close"),API.register(this,"open"),API.register(this,"navigate"),API.register(this,"info"),API.register(this,"active")}}class Inject{static async _inject(e){e.target.tabId||(t=await Tab.active(),e.target.tabId=t.id);var t=new Promise(t=>bapi.browser.scripting.executeScript(e,t));return t}static async func({tab_id:t,func:e,args:a}={args:[]}){t={target:{tabId:t,allFrames:!0},world:"MAIN",injectImmediately:!0,func:e,args:a};return Inject._inject(t)}static async files({tab_id:t,frame_id:e,files:a}){t={target:{tabId:t,frameIds:[e]},world:"MAIN",injectImmediately:!0,files:a};return"firefox"===bapi.VERSION&&delete t.world,Inject._inject(t)}static{API.register(this,"func"),API.register(this,"files")}}class Recaptcha{static async reset({tab_id:t}){return await Inject.func({tab_id:t,data:{func:()=>{try{window.grecaptcha?.reset()}catch{}},args:[]}}),!0}static{API.register(this,"reset")}}class Server{static ENDPOINT="https://api.nopecha.com/status?v="+bapi.browser.runtime.getManifest().version;static is_fetching_plan=!1;static async get_plan({key:t}){if(Server.is_fetching_plan)return!1;Server.is_fetching_plan=!0;let e={plan:"Unknown",credit:0};try{"undefined"===t&&(t="");var a=await fetch(Server.ENDPOINT+"&key="+t);e=JSON.parse(await a.text())}catch{}return Server.is_fetching_plan=!1,e}static{API.register(this,"get_plan")}}class Image{static encode({url:t}){return new Promise(a=>{fetch(t).then(t=>t.blob()).then(t=>{const e=new FileReader;e.onload=()=>a(e.result),e.readAsDataURL(t)})})}static{API.register(this,"encode")}}class Relay{static async send({tab_id:t,data:e}){t=t||(await Tab.active()).id,bapi.browser.tabs.sendMessage(t,e)}static{API.register(this,"send")}}class Icon{static set({status:a}){return new Promise(t=>{var e="firefox"===bapi.VERSION?bapi.browser.browserAction:bapi.browser.action;"on"===a?e.setIcon({path:{16:"/icon/16.png",32:"/icon/32.png",48:"/icon/48.png",128:"/icon/128.png"}},t):"off"===a?e.setIcon({path:{16:"/icon/16g.png",32:"/icon/32g.png",48:"/icon/48g.png",128:"/icon/128g.png"}},t):t(!1)})}static set_badge_text({tab_id:a,data:s}){return new Promise(t=>{var e={text:s};a&&(e.tabId=a),bapi.browser.action.setBadgeText(e,t)})}static set_badge_color({tab_id:a,data:s}){return new Promise(t=>{var e={color:s};a&&(e.tabId=a),bapi.browser.action.setBadgeBackgroundColor(e,t)})}static async set_badge({tab_id:t,data:{global:e,text:a,color:s}}){t||e||(t=(await Tab.active()).id),e&&(t=null);e=[Icon.set_badge_text({tab_id:t,data:a})];return s&&e.push(Icon.set_badge_color({tab_id:t,data:s})),Promise.all(e)}static{API.register(this,"set")}}class Browser{static async version(){return bapi.VERSION}static async log(){}static{API.register(this,"version"),API.register(this,"log")}}class ContextMenu{static listen(){bapi.browser.contextMenus.onClicked.addListener(function(e,t){if("nopecha_disable_host"===e.menuItemId){e=e.pageUrl;if(e){e=e.replace(/^(.*:)\/\/([A-Za-z0-9\-\.]+)(:[0-9]+)?(.*)$/,"$2");let t=new Set;for(const a of Settings.data.disabled_hosts)t.add(a.trim());t.add(e),t=[...t],Settings.set({id:"disabled_hosts",value:t})}}})}static create(){bapi.browser.contextMenus.create({title:"Disable NopeCHA on this site",id:"nopecha_disable_host"})}static{bapi.browser.runtime.onInstalled.addListener(ContextMenu.create),ContextMenu.listen()}}(async()=>{bapi.register_language(),await Settings.load(),await Icon.set({status:Settings.data.enabled?"on":"off"}),bapi.browser.runtime.onMessage.addListener((t,e,a)=>{const s=t[0];let i=null;t=(i=1<t.length?2===t.length?t[1]:t.slice(1):i)&&"tab_id"in i?i.tab_id:e?.tab?.id,e=e?.frameId;try{API.endpoints[s]({tab_id:t,frame_id:e,data:i}).then(t=>{["Browser.log","Settings.get","Settings.set","Cache.get","Cache.set","Tab.info"].includes(s);try{a(t)}catch(t){}}).catch(t=>{})}catch(t){}return!0})})();
class BG {
static exec() {
return new Promise((a) => {
try {
chrome.runtime.sendMessage([...arguments], a)
} catch (e) {
a(null)
}
})
}
}
class Net {
static async fetch(e, a) {
return BG.exec('Net.fetch', { url: e, options: a })
}
}
class Script {
static inject_file(t) {
return new Promise((e) => {
var a = document.createElement('script')
;(a.src = chrome.runtime.getURL(t)),
(a.onload = e),
(document.head || document.documentElement).appendChild(a)
})
}
}
class Location {
static parse_hostname(e) {
return e.replace(/^(.*:)\/\/([A-Za-z0-9\-\.]+)(:[0-9]+)?(.*)$/, '$2')
}
static async hostname() {
var e = await BG.exec('Tab.info'),
e = e.url || 'Unknown Host'
return Location.parse_hostname(e)
}
}
class Image {
static encode(a) {
return new Promise((t) => {
if (null === a) return t(null)
const e = new XMLHttpRequest()
;(e.onload = () => {
const a = new FileReader()
;(a.onloadend = () => {
let e = a.result
if (e.startsWith('data:text/html;base64,')) return t(null)
;(e = e.replace('data:image/jpeg;base64,', '')), t(e)
}),
a.readAsDataURL(e.response)
}),
(e.onerror = () => {
t(null)
}),
(e.onreadystatechange = () => {
4 == this.readyState && 200 != this.status && t(null)
}),
e.open('GET', a),
(e.responseType = 'blob'),
e.send()
})
}
}
class NopeCHA {
static INFERENCE_URL = 'https://api.nopecha.com'
static MAX_WAIT_POST = 60
static MAX_WAIT_GET = 60
static ERRORS = {
UNKNOWN: 9,
INVALID_REQUEST: 10,
RATE_LIIMTED: 11,
BANNED_USER: 12,
NO_JOB: 13,
INCOMPLETE_JOB: 14,
INVALID_KEY: 15,
NO_CREDIT: 16,
UPDATE_REQUIRED: 17
}
static async post({
captcha_type: e,
task: a,
image_urls: t,
image_data: r,
grid: n,
audio_data: o,
key: i
}) {
for (
var s = Date.now(), c = await BG.exec('Tab.info');
!(Date.now() - s > 1e3 * NopeCHA.MAX_WAIT_POST);
) {
var d = {
type: e,
task: a,
key: i,
v: chrome.runtime.getManifest().version,
url: c ? c.url : window.location.href
}
t && (d.image_urls = t),
r && (d.image_data = r),
n && (d.grid = n),
o && (d.audio_data = o)
try {
var l = { 'Content-Type': 'application/json' },
u =
(i && 'undefined' !== i && (l.Authorization = 'Bearer ' + i),
await Net.fetch(NopeCHA.INFERENCE_URL, {
method: 'POST',
headers: l,
body: JSON.stringify(d)
})),
p = JSON.parse(u)
if (!p) {
break
}
if ('error' in p) {
if (p.error === NopeCHA.ERRORS.RATE_LIMITED) {
await Time.sleep(2e3)
continue
}
if (p.error === NopeCHA.ERRORS.INVALID_KEY) break
if (p.error === NopeCHA.ERRORS.NO_CREDIT) break
break
}
var _ = p.data
return await NopeCHA.get({ job_id: _, key: i })
} catch (e) {}
await Time.sleep(1e3)
}
return { job_id: null, data: null }
}
static async get({ job_id: e, key: a }) {
for (var t = Date.now(); !(Date.now() - t > 1e3 * NopeCHA.MAX_WAIT_GET); ) {
await Time.sleep(1e3)
var r = {},
r =
(a && 'undefined' !== a && (r.Authorization = 'Bearer ' + a),
await Net.fetch(NopeCHA.INFERENCE_URL + `?id=${e}&key=` + a, {
headers: r
}))
try {
var n = JSON.parse(r)
if (!('error' in n))
return { job_id: e, data: n.data, metadata: n.metadata }
if (n.error !== NopeCHA.ERRORS.INCOMPLETE_JOB)
return { job_id: e, data: null, metadata: null }
} catch (e) {}
}
return { job_id: e, data: null, metadata: null }
}
}
(async()=>{function o(){return null!==(document.querySelector('button[aria-describedby="descriptionVerify"]')||document.querySelector("#wrong_children_button")||document.querySelector("#wrongTimeout_children_button"))}function r(){try{var e=document.querySelector('button[aria-describedby="descriptionVerify"]'),t=(e&&(window.parent.postMessage({nopecha:!0,action:"clear"},"*"),e.click()),document.querySelector("#wrong_children_button")),a=(t&&(window.parent.postMessage({nopecha:!0,action:"clear"},"*"),t.click()),document.querySelector("#wrongTimeout_children_button"));a&&(window.parent.postMessage({nopecha:!0,action:"clear"},"*"),a.click())}catch(e){}}function u(){return document.querySelector("#game_children_text > h2")?.innerText?.trim()}function s(){return document.querySelector("img#game_challengeItem_image")?.src?.split(";base64,")[1]}let d=null;async function e(){e=500;var e,{task:t,cells:a,image_data:n}=await new Promise(n=>{let c=!1;const i=setInterval(async()=>{if(!c){c=!0;var e=await BG.exec("Settings.get");if(e.enabled&&e.funcaptcha_auto_solve){e.funcaptcha_auto_open&&o()&&await r();e=u();if(e){var t=document.querySelectorAll("#game_children_challenge ul > li > a");if(6===t.length){var a=s();if(a&&d!==a)return d=a,clearInterval(i),c=!1,n({task:e,cells:t,image_data:a})}}c=!1}}},e)});if(null!==t&&null!==a&&null!==n){var c=await BG.exec("Settings.get");if(c.enabled&&c.funcaptcha_auto_solve){var i=Time.time(),l=(await NopeCHA.post({captcha_type:IS_DEVELOPMENT?"funcaptcha_dev":"funcaptcha",task:t,image_data:[n],key:c.key}))["data"];if(l){t=parseInt(c.funcaptcha_solve_delay_time)||1e3,n=c.funcaptcha_solve_delay?t-(Time.time()-i):0;0<n&&await Time.sleep(n);for(let e=0;e<l.length;e++)!1!==l[e]&&a[e].click()}d=null}}}if(window.location.pathname.startsWith("/fc/assets/tile-game-ui/"))for(;;){await Time.sleep(1e3);var t,a=await BG.exec("Settings.get");a&&a.enabled&&(t=await Location.hostname(),a.disabled_hosts.includes(t)||(a.funcaptcha_auto_open&&o()?await r():a.funcaptcha_auto_solve&&null!==u()&&null!==s()&&await e()))}})();
(async()=>{const u={linkedin:["3117BF26-4762-4F5A-8ED9-A85E69209A46",!1],rockstar:["A5A70501-FCDE-4065-AF18-D9FAF06EF479",!1],github:["20782B4C-05D0-45D7-97A0-41641055B6F6",!1],paypal:["9409E63B-D2A5-9CBD-DBC0-5095707D0090",!1],blizzard:["E8A75615-1CBA-5DFF-8032-D16BCF234E10",!1],twitch:["E5554D43-23CC-1982-971D-6A2262A2CA24",!1],demo1:["804380F4-6844-FFA1-ED4E-5877CA1F1EA4",!1],demo2:["D39B0EE3-2973-4147-98EF-C92F93451E2D",!1],"ea signup":["73BEC076-3E53-30F5-B1EB-84F494D43DBA",!1],"ea signin":["0F5FE186-B3CA-4EDB-A39B-9B9A3397D01D",!1],myprepaidcenter:["0F941BF0-7303-D94B-B76A-EAA2E2048124",!1],twitter:["2CB16598-CB82-4CF7-B332-5990DB66F3AB",!0],discoveryplus:["FE296399-FDEA-2EA2-8CD5-50F6E3157ECA",!1],minecraft:["D39B0EE3-2973-4147-98EF-C92F93451E2D",!1],imvu:["0C2B415C-D772-47D4-A183-34934F786C7E",!1],adobe:["430FF2C3-1AB1-40B7-8BE7-44FC683FE02C",!1]},h={outlook:["https://iframe.arkoselabs.com/B7D8911C-5CC8-A9A3-35B0-554ACEE604DA/index.html?mkt=en",!1],"outlook auth":["https://iframe-auth.arkoselabs.com/B7D8911C-5CC8-A9A3-35B0-554ACEE604DA/index.html?mkt=en",!1]};let E=18;function w(){g("linkedin",0,1),g("rockstar",0,1),g("demo1",0,1),g("blizzard",0,1),g("twitch",0,1),g("paypal",0,1),A("outlook auth",0,1),g("github",0,1),g("demo2",0,1),A("outlook",0,1),g("ea signup",0,1),g("ea signin",0,1),g("twitter",0,1),g("minecraft",0,1),g("imvu",0,1),g("adobe",0,1)}function g(t,o,n){n=n||E;for(let e=0;e<n;e++)!async function(e,t){var o=u[e][0],n="https://api.funcaptcha.com/fc/gt2/public_key/"+o,n=await Net.fetch(n,{headers:{accept:"*/*","accept-language":"en-US,en;q=0.9","cache-control":"no-cache","content-type":"application/x-www-form-urlencoded; charset=UTF-8",pragma:"no-cache","sec-ch-ua":'"Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"',"sec-ch-ua-mobile":"?0","sec-ch-ua-platform":'"Linux"',"sec-fetch-dest":"empty","sec-fetch-mode":"cors","sec-fetch-site":"cross-site"},referrer:"",referrerPolicy:"strict-origin-when-cross-origin",body:`bda=&public_key=${o}&site=${encodeURIComponent("")}&language=en&userbrowser=Mozilla%2F5.0%20(X11%3B%20Linux%20x86_64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F105.0.0.0%20Safari%2F537.36&rnd=`+Math.random(),method:"POST",mode:"cors",credentials:"omit"}),o=JSON.parse(n),r={};for(const i of o.token.split("|")){var a=i.split("=");let e=a[0],t=a[1];a[1]||(e="token",t=a[0]),e.endsWith("url")&&(t=decodeURIComponent(t)),r[e]=t}n=new URLSearchParams(r).toString(),o="https://api.funcaptcha.com/fc/gc/?"+n;c(e,t,o,u[e][1])}(t,o)}function A(t,o,n){n=n||E;for(let e=0;e<n;e++)c(t,o,h[t][0],h[t][1])}function c(e,t,o,n=!1){var r=document.createElement("div"),a=(r.classList.add("iframe_wrap"),document.createElement("iframe"));n&&a.classList.add("small"),r.append(a),a.frameborder=0,a.scrolling="no",a.src=o;let i=document.querySelector("#iframe_row_"+t);i||((i=document.createElement("div")).classList.add("iframe_row"),i.id="iframe_row_"+t,document.body.append(i));n=document.createElement("div"),n.classList.add("name"),n.innerHTML=e,a=document.createElement("div");a.append(n),a.append(r),i.append(a)}!function e(){document.body.innerHTML="";const t=[`body, html {
background-color: #212121;
}`,`.input_row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}`,`.input_row > * {
height: 20px;
line-height: 20px;
padding: 0;
border: 0;
font-size: 12px;
}`,`.input_row > input[type="button"] {
width: 100px;
cursor: pointer;
transition: 200ms all;
}`,`.input_row > input[type="button"]:hover {
opacity: 0.8;
}`,`#nframes_label {
background-color: #fff;
color: #222;
width: 70px;
text-align: center;
}`,`#nframes, #nframes:active {
width: 30px;
border: none;
outline: none;
}`,`.name {
color: #fff;
}`,`.iframe_row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}`,`.iframe_wrap {
background-color: #eee;
width: 275px;
height: 275px;
padding: 0;
overflow: hidden;
}`,`iframe {
border: none !important;
width: 400px !important;
height: 400px !important;
-ms-zoom: 0.75 !important;
-moz-transform: scale(0.75) !important;
-moz-transform-origin: 0 0 !important;
-o-transform: scale(0.75) !important;
-o-transform-origin: 0 0 !important;
-webkit-transform: scale(0.75) !important;
-webkit-transform-origin: 0 0 !important;
}`,`iframe.small {
width: 550px !important;
height: 550px !important;
-ms-zoom: 0.5 !important;
-moz-transform: scale(0.5) !important;
-moz-transform-origin: 0 0 !important;
-o-transform: scale(0.5) !important;
-o-transform-origin: 0 0 !important;
-webkit-transform: scale(0.5) !important;
-webkit-transform-origin: 0 0 !important;
}`];const o=document.body.appendChild(document.createElement("style")).sheet;for(const n in t)o.insertRule(t[n],n);let n=0;let r=1;const a={};a[0]=document.createElement("div");a[0].classList.add("input_row");document.body.append(a[0]);const i=document.createElement("div");i.id="nframes_label";i.innerText="# iframes";a[0].append(i);const c=document.createElement("input");c.id="nframes";c.placeholder="Number of iframes";c.value=E;c.addEventListener("input",()=>{E=parseInt(c.value)});a[0].append(c);const m={reset:{row:0,fn:e,args:[]},all:{row:0,fn:w,args:[]}};for(const s in u)n++%9==0&&r++,m[s]={row:r,fn:g,args:[s,0]};for(const d in h)n++%9==0&&r++,m[d]={row:r,fn:A,args:[d,0]};for(const[p,l]of Object.entries(m)){const r=l.row,f=(l.row in a||(a[l.row]=document.createElement("div"),a[l.row].classList.add("input_row"),document.body.append(a[l.row])),document.createElement("input"));f.type="button",f.value=p,f.addEventListener("click",()=>{e(),l.fn(...l.args)}),a[l.row].append(f)}}(),g("imvu",0,E)})();
(async()=>{window.addEventListener("load",()=>{var t=document.body.appendChild(document.createElement("style")).sheet;t.insertRule("* {transition-duration: 0s !important}",0),t.insertRule("li > a::after {border: 8px solid rgba(0, 255, 0, 0.6) !important}",1),t.insertRule("#interstitial {backdrop-filter: none !important}",2),t.insertRule("#interstitial {background-color: transparent !important}",3),t.insertRule("#interstitial_wrapper {background-color: transparent !important}",4)})})();
(async()=>{var e=IS_DEVELOPMENT;const o="lazy";window.nopecha=[];var a={};async function t(e){var a=(document.querySelector("#game_children_text > h2")||document.querySelector("#game-header"))?.innerText?.trim(),t=(document.querySelector("img#game_challengeItem_image")||document.querySelector("#challenge-image"))?.src?.split(";base64,")[1];a&&t&&(a={task:a,image:t,index:e,url:(await BG.exec("Tab.info"))?.url},o.startsWith("l")&&window.parent.postMessage({nopecha:!0,action:"append",data:a},"*"),o.startsWith("e"))&&await Net.fetch("https://api.nopecha.com/upload",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})}var n=window.addEventListener?"addEventListener":"attachEvent";for(window[n]("attachEvent"==n?"onmessage":"message",async e=>{e=e[e.message?"message":"data"];e&&!0===e.nopecha&&("append"===e.action?window.nopecha.push(e.data):"clear"===e.action?window.nopecha=[]:"reload"===e.action&&(window.parent.postMessage({nopecha:!0,action:"reload"},"*"),window.location.reload(!0)))},!1);;){await Time.sleep(1e3);try{if(document.querySelector("body.victory")){var i=[];for(const s of window.nopecha){var c=Net.fetch("https://api.nopecha.com/upload",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});i.push(c)}await Promise.all(i),window.nopecha=[],e&&(window.parent.postMessage({nopecha:!0,action:"reload"},"*"),window.location.reload(!0))}"block"===document.querySelector("#timeout_widget")?.style?.display&&(window.parent.postMessage({nopecha:!0,action:"reload"},"*"),window.location.reload(!0));var r=document.querySelectorAll("#game_children_challenge ul > li > a");for(const l in r){var d=r[l];l in a&&d.removeEventListener("click",a[l]),a[l]=t.bind(this,parseInt(l)),d.addEventListener("click",a[l])}}catch(e){}}})();
(async()=>{function u(e){e=e?.style.background?.trim()?.match(/(?!^)".*?"/g);return e&&0!==e.length?e[0].replaceAll('"',""):null}async function h(){var e=document.querySelector("h2.prompt-text")?.innerText?.replace(/\s+/g," ")?.trim();if(!e)return null;var t={"0430":"a","0441":"c","0501":"d","0065":"e","0435":"e","04bb":"h","0069":"i","0456":"i","0458":"j","03f3":"j","04cf":"l","03bf":"o","043e":"o","0440":"p","0455":"s","0445":"x","0443":"y","0335":"-"};var a=[];for(const i of e){var c=function(e,t,a){for(;(""+e).length<a;)e=""+t+e;return e}(i.charCodeAt(0).toString(16),"0",4);a.push(c in t?t[c]:i)}return a.join("")}let d=null;async function e(){"block"===document.querySelector("div.check")?.style.display?a=a||!0:(a=!1,await Time.sleep(500),document.querySelector("#checkbox")?.click())}async function t(){"EN"!==document.querySelector(".display-language .text").textContent&&(document.querySelector(".language-selector .option:nth-child(23)").click(),await Time.sleep(500));e=500;var e,{task:t,cells:a,urls:c}=await new Promise(o=>{let r=!1;const s=setInterval(async()=>{if(!r){r=!0;var e=await h();if(e){var t=u(document.querySelector(".challenge-example > .image > .image"));if(t&&""!==t){var a=document.querySelectorAll(".task-image");if(9===a.length){var c=[],i=[];for(const l of a){var n=l.querySelector("div.image");if(!n)return void(r=!1);n=u(n);if(!n||""===n)return void(r=!1);c.push(l),i.push(n)}a=JSON.stringify(i);if(d!==a)return d=a,clearInterval(s),r=!1,o({task:e,task_url:t,cells:c,urls:i})}}}r=!1}},e)}),i=await BG.exec("Settings.get");if(i.enabled&&i.hcaptcha_auto_solve){var n=Time.time(),{data:l,metadata:t}=await NopeCHA.post({captcha_type:IS_DEVELOPMENT?"hcaptcha_dev":"hcaptcha",task:t,image_urls:c,key:i.key});if(l){o&&o.postMessage({event:"NopeCHA.metadata",metadata:t});c=parseInt(i.hcaptcha_solve_delay_time)||3e3,t=i.hcaptcha_solve_delay?c-(Time.time()-n):0;0<t&&await Time.sleep(t);for(let e=0;e<l.length;e++)!1!==l[e]&&"true"!==a[e].getAttribute("aria-pressed")&&a[e].click();await Time.sleep(200);try{document.querySelector(".button-submit").click()}catch(e){}}}}let a=!1,c=!1,o=null;for(;;){await Time.sleep(1e3);var i,n=await BG.exec("Settings.get");n&&n.enabled&&(i=await Location.hostname(),n.disabled_hosts.includes(i)||(c||null!==o||(window.addEventListener("message",e=>{"NopeCHA.hook"===e.data.event&&(o=e.source)}),window.location.hash.includes("frame=challenge")&&(c=!0,"firefox"===await BG.exec("Browser.version")?await Script.inject_file("hcaptcha_hook.js"):await BG.exec("Inject.files",{files:["hcaptcha_hook.js"]}))),n.hcaptcha_auto_open&&0!==document.body.getBoundingClientRect()?.width&&0!==document.body.getBoundingClientRect()?.height&&null!==document.querySelector("div.check")?await e():n.hcaptcha_auto_solve&&null!==document.querySelector("h2.prompt-text")&&await t()))}})();
(async()=>{let a=null,t=!1,r=!1;function n(e,t,r=!1){e&&(r||a!==e)&&(!0===t&&"false"===e.getAttribute("aria-pressed")||!1===t&&"true"===e.getAttribute("aria-pressed"))&&e.click()}document.addEventListener("mousedown",e=>{"false"===e?.target?.parentNode?.getAttribute("aria-pressed")?(t=!0,r=!0):"true"===e?.target?.parentNode?.getAttribute("aria-pressed")&&(t=!0,r=!1),a=e?.target?.parentNode}),document.addEventListener("mouseup",e=>{t=!1,a=null}),document.addEventListener("mousemove",e=>{t&&(a!==e?.target?.parentNode&&null!==a&&n(a,r,!0),n(e?.target?.parentNode,r))}),window.addEventListener("load",()=>{document.body.appendChild(document.createElement("style")).sheet.insertRule('[aria-pressed="true"] > .border-focus {background-color: #0f0 !important; opacity: 0.3 !important}',0)})})();
(()=>{var e=function(){"use strict";Array.prototype.indexOf||(Array.prototype.indexOf=function(t){for(let e=0;e<this.length;e++)if(this[e]===t)return e;return-1});const i=(e,t)=>Array.prototype.slice.call(e,t);let e=null;"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?e=self:"undefined"!=typeof global?e=global:window&&(e=window);const t=e,u=e.document;var n="undefined"!=typeof navigator&&navigator.useragent?navigator.userAgent:"";let g=null;(/msie (\d+)/.test(n.toLowerCase())||/trident\/.*; rv:(\d+)/.test(n.toLowerCase()))&&(g=parseInt(RegExp.$1,10));function E(e,t){for(var n in e)if(!c(n)){var r=e[n];try{t[n]=r}catch(e){}}return t}function m(e,a,s){var t;for(t of Array.from(e))s._has(t)&&(a["on"+t]=(o=>function(e){var t,n,r={};for(t in e)c(t)||(n=e[t],r[t]=n===a?s:n);return s.dispatchEvent(o,r)})(t))}function b(o){let r={};const a=e=>r[e]||[],s={addEventListener:function(e,t,n){r[e]=a(e),0<=r[e].indexOf(t)||(n=void 0===n?r[e].length:n,r[e].splice(n,0,t))},removeEventListener:function(e,t){void 0===e?r={}:(void 0===t&&(r[e]=[]),-1!==(t=a(e).indexOf(t))&&a(e).splice(t,1))}};return s.dispatchEvent=function(){var t=i(arguments),e=t.shift(),n=(o||(t[0]=E(t[0],function(t){var e;if(u&&null!=u.createEventObject)return(e=u.createEventObject()).type=t,e;try{return new Event(t)}catch(e){return{type:t}}}(e))),s["on"+e]),r=(n&&n.apply(s,t),a(e).concat(a("*")));for(let e=0;e<r.length;e++)r[e].apply(s,t)},s._has=e=>!(!r[e]&&!s["on"+e]),o&&(s.listeners=e=>i(a(e)),s.on=s.addEventListener,s.off=s.removeEventListener,s.fire=s.dispatchEvent,s.once=function(e,t){var n=function(){return s.off(e,n),t.apply(null,arguments)};return s.on(e,n)},s.destroy=()=>r={}),s}const L=["load","loadend","loadstart"],w=["progress","abort","error","timeout"],c=e=>["returnValue","totalSize","position"].includes(e);var x=function(e,t){let n;switch(null==t&&(t={}),typeof e){case"object":var r,o=[];for(r in e){var a=e[r];n=r.toLowerCase(),o.push(n+`: `+a)}return o.join("\n")+"\n";case"string":o=e.split("\n");for(var s of Array.from(o))/([^:]+):\s*(.+)/.test(s)&&(n=null!=RegExp.$1?RegExp.$1.toLowerCase():void 0,s=RegExp.$2,null==t[n])&&(t[n]=s);return t}return[]};function r(){const i=new R,u={};let o=null,a=void 0,c=void 0,l=void 0;var s=0;function t(){if(l.status=o||i.status,-1===o&&g<10||(l.statusText=i.statusText),-1!==o){var e,t=x(i.getAllResponseHeaders());for(e in t){var n,r=t[e];l.headers[e]||(n=e.toLowerCase(),l.headers[n]=r)}}}function r(e){for(;s<e&&s<4;)y.readyState=++s,1===s&&y.dispatchEvent("loadstart",{}),2===s&&n(),4===s&&(n(),d()),y.dispatchEvent("readystatechange",{}),4===s&&(!1===u.async?p():setTimeout(p,0))}function f(e){if(4!==e)r(e);else{const n=N.listeners("after");var t=function(){var e;0<n.length?2===(e=n.shift()).length?(e(u,l),t()):3===e.length&&u.async?e(u,l,t):t():r(4)};t()}}const n=function(){y.status=l.status,y.statusText=l.statusText},d=function(){"text"in l&&(y.responseText=l.text),"xml"in l&&(y.responseXML=l.xml),"data"in l&&(y.response=l.data),"finalUrl"in l&&(y.responseURL=l.finalUrl)},p=function(){a||y.dispatchEvent("load",{}),y.dispatchEvent("loadend",{}),a&&(y.readyState=0)};function e(){a=!0}var h,v,y=b();u.xhr=y,i.onreadystatechange=function(e){try{2===i.readyState&&t()}catch(e){}if(4===i.readyState){if(c=!1,t(),i.responseType&&"text"!==i.responseType)"document"===i.responseType?(l.xml=i.responseXML,l.data=i.responseXML):l.data=i.response;else{l.text=i.responseText,l.data=i.responseText;try{l.xml=i.responseXML}catch(e){}}"responseURL"in i&&(l.finalUrl=i.responseURL)}f(i.readyState)},y.addEventListener("error",e),y.addEventListener("timeout",e),y.addEventListener("abort",e),y.addEventListener("progress",function(e){s<3?f(3):i.readyState<=3&&y.dispatchEvent("readystatechange",{})}),"withCredentials"in i&&(y.withCredentials=!1),y.status=0;for(h of Array.from(w.concat(L)))y["on"+h]=null;return y.open=function(e,t,n,r,o){s=0,a=!1,c=!1,u.headers={},u.headerNames={},u.status=0,u.method=e,u.url=t,u.async=!1!==n,u.user=r,u.pass=o,l={headers:{}},f(1)},y.send=function(e){let n,r;for(n of["type","timeout","withCredentials"])(r="type"===n?"responseType":n)in y&&(u[n]=y[r]);u.body=e;const o=function(){m(w,i,y),y.upload&&m(w.concat(L),i.upload,y.upload),c=!0,i.open(u.method,u.url,u.async,u.user,u.pass);for(n of["type","timeout","withCredentials"])r="type"===n?"responseType":n,n in u&&(i[r]=u[n]);for(var e in u.headers){var t=u.headers[e];e&&i.setRequestHeader(e,t)}i.send(u.body)},a=N.listeners("before");var s=function(){if(!a.length)return o();function e(e){"object"!=typeof e||"number"!=typeof e.status&&"number"!=typeof l.status?s():(E(e,l),"data"in e||(e.data=e.response||e.text),f(4))}e.head=function(e){E(e,l),f(2)},e.progress=function(e){E(e,l),f(3)};var t=a.shift();1===t.length?e(t(u)):2===t.length&&u.async?t(u,e):e()};s()},y.abort=function(){o=-1,c?i.abort():y.dispatchEvent("abort",{})},y.setRequestHeader=function(e,t){var n=null!=e?e.toLowerCase():void 0,n=u.headerNames[n]=u.headerNames[n]||e;u.headers[n]&&(t=u.headers[n]+", "+t),u.headers[n]=t},y.getResponseHeader=e=>O(l.headers[e?e.toLowerCase():void 0]),y.getAllResponseHeaders=()=>O(x(l.headers)),i.overrideMimeType&&(y.overrideMimeType=function(){i.overrideMimeType.apply(i,arguments)}),i.upload&&(v=b(),y.upload=v,u.upload=v),y.UNSENT=0,y.OPENED=1,y.HEADERS_RECEIVED=2,y.LOADING=3,y.DONE=4,y.response="",y.responseText="",y.responseXML=null,y.readyState=0,y.statusText="",y}const N=b(!0),O=e=>void 0===e?null:e,R=t.XMLHttpRequest;r.UNSENT=0,r.OPENED=1,r.HEADERS_RECEIVED=2,r.LOADING=3,r.DONE=4;var o={patch(){R&&(t.XMLHttpRequest=r)},unpatch(){R&&(t.XMLHttpRequest=R)},Native:R,XH:r};function a(e,u){null==u&&(u={headers:{}});let c=null;e instanceof Request?c=e:u.url=e;const l=N.listeners("before"),f=N.listeners("after");return new Promise(function(n,t){function r(e){var t;return f.length?2===(t=f.shift()).length?(t(a(),e),r(e)):3===t.length?t(a(),e,r):r(e):n(e)}function o(){var e;if(l.length)return 1===(e=l.shift()).length?s(e(u)):2===e.length&&e(a(),s);i()}const a=function(){return u.headers&&(u.headers=new Headers(u.headers)),c=c||new Request(u.url,u),E(u,c)},s=function(e){void 0!==e?(e=new Response(e.body||e.text,e),n(e),r(e)):o()};var i=()=>d(a()).then(e=>r(e)).catch(function(e){return n=t,r(e),t(e)});o()})}const d=t.fetch;var s={patch(){d&&(t.fetch=a)},unpatch(){d&&(t.fetch=d)},Native:d,XH:a};const l=N;return l.EventEmitter=b,l.before=function(e,t){if(e.length<1||2<e.length)throw"invalid hook";return l.on("before",e,t)},l.after=function(e,t){if(e.length<2||3<e.length)throw"invalid hook";return l.on("after",e,t)},l.enable=function(){o.patch(),s.patch()},l.disable=function(){o.unpatch(),s.unpatch()},l.XMLHttpRequest=o.Native,l.fetch=s.Native,l.headers=x,l.enable(),l}();function a(e,t,n=null){var r=JSON.parse(JSON.stringify(e));for(const s of Object.keys(r))if(!s.endsWith("-mp")){var o=[];for(let e=0;e<r[s].length;e++){var a=r[s][e][2]+t;null!==n&&n<=a||o.push([r[s][e][0],r[s][e][1],a])}r[s]=o}return r}function s(e,t){for(const n of Object.keys(t))e[n]=t[n]}let i=[];window.addEventListener("message",e=>{"NopeCHA.metadata"===e.data.event&&i.push(e.data.metadata)}),window.postMessage({event:"NopeCHA.hook"}),e.before(e=>{try{var t,n,r;"POST"===(o=e).method&&o.url.startsWith("https://hcaptcha.com/checkcaptcha/")&&(t=JSON.parse(e.body),s(n=JSON.parse(t.motionData),a(r=function(e){var n={md:[],mm:[],mu:[]};for(const o of e)for(const a of Object.keys(n)){const s=0===n[a].length?0:n[a][n[a].length-1][2];var t=o[a].map(e=>[Math.ceil(e[0]),Math.ceil(e[1]),Math.ceil(e[2]+s)]);n[a].push(...t)}for(const i of Object.keys(n)){var r=[];let e=null;for(const u of n[i])null!==e&&r.push(u[2]-e),e=u[2];let t=0;0<r.length&&(t=r.reduce((e,t)=>e+t)/r.length),n[i+"-mp"]=t}return n}(i),n.st)),s(n.topLevel,a(r,n.topLevel.st,n.st)),t.motionData=JSON.stringify(n),e.body=JSON.stringify(t),i=[])}catch(e){}var o})})();
(()=>{let e;function t(){var e=navigator.language.split("-")[0];for(const r of document.querySelectorAll('script[src*="hcaptcha.com/1/api.js"]')){var t=new URL(r.src);"en"!==(t.searchParams.get("hl")||e)&&(t.searchParams.set("hl","en"),r.src=t.toString())}}e=new MutationObserver(t),setTimeout(()=>{t(),e.observe(document.head,{childList:!0})},0)})();
This diff is collapsed.
{
"update_url": "https://clients2.google.com/service/update2/crx",
"name": "NopeCHA: CAPTCHA Solver", "version": "0.3.3", "description": "Automatically solve reCAPTCHA, hCaptcha, FunCAPTCHA, AWS WAF, and text CAPTCHA using AI.", "permissions": ["declarativeNetRequest", "storage", "scripting", "contextMenus"], "content_scripts": [{"matches": ["<all_urls>"], "js": ["utils.js", "content.js"], "run_at": "document_start", "all_frames": true, "match_about_blank": true}, {"matches": ["*://nopecha.com/setup"], "js": ["setup.js"], "run_at": "document_end", "all_frames": true, "match_about_blank": false}, {"matches": ["*://*.hcaptcha.com/captcha/*"], "js": ["hcaptcha.js"], "run_at": "document_end", "all_frames": true, "match_about_blank": false}, {"matches": ["*://*.hcaptcha.com/captcha/*"], "js": ["hcaptcha_fast.js"], "run_at": "document_start", "all_frames": true, "match_about_blank": false}, {"matches": ["<all_urls>"], "js": ["hcaptcha_language.js"], "run_at": "document_end", "all_frames": true, "match_about_blank": false}, {"matches": ["<all_urls>"], "js": ["recaptcha.js", "recaptcha_speech.js"], "run_at": "document_end", "all_frames": true, "match_about_blank": false}, {"matches": ["*://*.google.com/recaptcha/*", "*://*.recaptcha.net/recaptcha/*", "*://recaptcha.net/recaptcha/*"], "js": ["recaptcha_fast.js"], "run_at": "document_start", "all_frames": true, "match_about_blank": false}, {"matches": ["*://*.arkoselabs.com/fc/*", "*://*.funcaptcha.com/fc/*"], "js": ["funcaptcha.js", "funcaptcha_scrape.js"], "run_at": "document_end", "all_frames": true, "match_about_blank": true}, {"matches": ["*://*.arkoselabs.com/fc/*", "*://*.funcaptcha.com/fc/*"], "js": ["funcaptcha_fast.js"], "run_at": "document_start", "all_frames": true, "match_about_blank": true}, {"matches": ["*://nopecha.com/demo/funcaptcha"], "js": ["funcaptcha_demo.js"], "run_at": "document_end", "all_frames": false, "match_about_blank": false}, {"matches": ["<all_urls>"], "js": ["awscaptcha.js"], "run_at": "document_end", "all_frames": true, "match_about_blank": false}, {"matches": ["<all_urls>"], "js": ["textcaptcha.js", "locate.js"], "run_at": "document_end", "all_frames": true, "match_about_blank": true}], "icons": {"16": "icon/16.png", "32": "icon/32.png", "48": "icon/48.png", "128": "icon/128.png"}, "manifest_version": 3, "action": {"default_title": "NopeCHA: CAPTCHA Solver", "default_icon": "icon/16.png", "default_popup": "popup.html"}, "background": {"service_worker": "background.js", "type": "module"}, "host_permissions": ["<all_urls>"]}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
let plan=null,checking_server_plan=!1,rendering_server_plan=!1;function sleep(t){return new Promise(e=>setTimeout(t))}function get_loading_html(){return'<div class="loading"><div></div><div></div><div></div><div></div></div>'}function number_with_comma(e){return(e=e||0).toString().replace(/\B(?=(\d{3})+(?!\d))/g,",")}async function check_plan(){var e=await BG.exec("Settings.get");e&&!checking_server_plan&&(checking_server_plan=!0,(plan=(plan=await BG.exec("Server.get_plan",{key:e.key})).error?{error:!0,plan:plan.message,credit:0,quota:0,duration:null,lastreset:null,current_period_start:1,current_period_end:1}:plan).subscription=["Starter","Basic","Professional","Enterprise"].includes(plan.plan),plan.invalid=!1,["Banned IP","Invalid key","Rate limit reached"].includes(plan.plan)?plan.invalid=!0:plan.plan=plan.plan+" Plan",plan.expired=!1,plan.subscription&&(e=Date.now()/1e3,plan.current_period_end-e<0)&&(plan.expired=!0,plan.credit=0,plan.quota=0),checking_server_plan=!1,document.querySelector("#loading_overlay").classList.add("hidden"))}async function render_plan(){var t=await BG.exec("Settings.get");if(t&&plan&&!rendering_server_plan){rendering_server_plan=!0;var t=document.querySelector("#plan"),n=document.querySelector("#credit"),a=document.querySelector("#refills"),s=document.querySelector("#ipbanned_warning"),d=Date.now()/1e3;let e=null;plan.lastreset&&plan.duration&&(e=Math.floor(Math.max(0,plan.duration-(d-plan.lastreset)))),t.innerHTML=plan.plan,plan.invalid||plan.error?t.classList.add("red"):t.classList.remove("red"),"Banned IP"===plan.plan?s.classList.remove("hidden"):s.classList.add("hidden"),n.innerHTML=number_with_comma(plan.credit)+" / "+number_with_comma(plan.quota),0===plan.credit?n.classList.add("red"):n.classList.remove("red"),plan.expired?(a.innerHTML="Expired",a.classList.add("red")):(plan.duration<0?(a.innerHTML="No refills",a.classList.add("red")):(e?(d=Time.seconds_as_hms(e),a.innerHTML=""+d):a.innerHTML=get_loading_html(),a.classList.remove("red")),1===plan.lastreset?(a.innerHTML="Not activated",a.classList.add("red")):0<plan.duration&&0===e&&(await sleep(1e3),await check_plan())),rendering_server_plan=!1}}async function init_ui(){const i=await BG.exec("Settings.get");var e=document.querySelector("#power");const t=e.querySelector(".spinning"),n=e.querySelector(".static"),a=e.querySelector(".btn");i.enabled?(n.classList.remove("hidden"),a.classList.remove("off")):a.classList.add("off");let s=null;e.addEventListener("click",async()=>{clearTimeout(s),t.classList.add("hidden"),n.classList.add("hidden"),a.classList.contains("off")?(a.classList.remove("off"),t.classList.remove("hidden"),await BG.exec("Settings.set",{id:"enabled",value:!0}),await BG.exec("Icon.set",{status:"on"}),s=setTimeout(()=>{t.classList.add("hidden"),n.classList.remove("hidden")},1e3)):(await BG.exec("Settings.set",{id:"enabled",value:!1}),await BG.exec("Icon.set",{status:"off"}),a.classList.add("off"))});const d=document.querySelector('.settings_text[data-settings="key"]'),r=document.querySelector(".edit_icon"),l=document.querySelector(".key_label");function c(){d.classList.contains("hiddenleft")?(d.classList.remove("hiddenleft"),d.focus(),r.classList.remove("hidden"),l.classList.add("hidden")):(d.classList.add("hiddenleft"),r.classList.add("hidden"),l.classList.remove("hidden"))}document.querySelector("#edit_key").addEventListener("click",()=>{c(),check_plan()}),d.addEventListener("keydown",e=>{"Enter"===(e=e||window.event).key&&(c(),check_plan(),0<d.value.length?document.querySelector("#export").classList.remove("hidden"):document.querySelector("#export").classList.add("hidden"))}),0<i.key?.length?document.querySelector("#export").classList.remove("hidden"):document.querySelector("#export").classList.add("hidden");for(const y of document.querySelectorAll('[data-tabtarget]:not([data-tabtarget=""])'))y.addEventListener("click",()=>{for(const e of document.querySelectorAll(".tab"))e.classList.add("hidden");document.querySelector(`[data-tab="${y.dataset.tabtarget}"]`).classList.remove("hidden")});function o(){document.querySelector(".tab:not(.hidden)").querySelector(".back")?.click()}document.addEventListener("mousedown",e=>{0<(8&(e=e||window.event).buttons)&&o()}),document.addEventListener("keydown",e=>{"Backspace"!==(e=e||window.event).key||e.target instanceof HTMLInputElement||o()});for(const[f,h]of Object.entries(i)){for(const g of document.querySelectorAll(`.settings_toggle[data-settings="${f}"]`))g.classList.remove("on","off"),g.classList.add(h?"on":"off"),g.addEventListener("click",async()=>{var e=g.classList.contains("off");await BG.exec("Settings.set",{id:f,value:e}),g.classList.remove("on","off"),g.classList.add(e?"on":"off")});for(const S of document.querySelectorAll(`.settings_dropdown[data-settings="${f}"]`))S.dataset.value===h&&(S.classList.add("selected"),document.querySelector(S.dataset.displays).innerHTML=S.innerHTML),S.addEventListener("click",async()=>{document.querySelector(`.settings_dropdown.selected[data-settings="${f}"]`)?.classList?.remove("selected");var e=S.dataset.value;await BG.exec("Settings.set",{id:f,value:e}),S.classList.add("selected"),document.querySelector(S.dataset.displays).innerHTML=S.innerHTML});for(const w of document.querySelectorAll(`.settings_text[data-settings="${f}"]`))w.value=h,w.addEventListener("input",async()=>{var e=w.value;await BG.exec("Settings.set",{id:f,value:e})})}for(const q of document.querySelectorAll(".locate"))q.addEventListener("click",async()=>{var e=q.dataset.key;await BG.exec("Relay.send",{data:{action:"start_locate",locate:e}}),window.close()});const u=document.querySelector("#disabled_hosts");async function p(e=!0){var t=new Set;for(const n of i.disabled_hosts)t.add(n.trim());i.disabled_hosts=[...t],await BG.exec("Settings.set",{id:"disabled_hosts",value:i.disabled_hosts}),e&&await v()}async function v(){u.innerHTML="";var e=document.querySelector("#template > #disabled_hosts_item");let t=null;for(const a in i.disabled_hosts){var n=i.disabled_hosts[a]?.trim();if(n){const s=e.cloneNode(!0),d=(s.id=null,s.querySelector("input.hostname"));d.value=n,d.addEventListener("input",()=>{clearTimeout(t),i.disabled_hosts[a]=d.value,t=setTimeout(async()=>{await p(!1)},200)}),s.querySelector(".remove").addEventListener("click",()=>{var e=i.disabled_hosts.indexOf(d.value);-1!==e&&(i.disabled_hosts.splice(e,1),p(!1)),s.remove()}),u.append(s)}}}!async function(){var e=await BG.exec("Tab.active");const t=(e.url||"Unknown Host").replace(/^(.*:)\/\/([A-Za-z0-9\-\.]+)(:[0-9]+)?(.*)$/,"$2");document.querySelector("#current_page_host").innerHTML=t;let n=!0;e.url&&!i.disabled_hosts.includes(i.disabled_hosts)||(n=!1),e=document.querySelector("#add_current_page_host"),n?e.addEventListener("click",async()=>{i.disabled_hosts.push(t),await p()}):e.disabled=!0}(),v(),document.querySelector("#export").addEventListener("click",async()=>{var e=await BG.exec("Settings.get"),e=SettingsManager.export(e);window.open(e,"_blank")});var m="Version "+chrome.runtime.getManifest().version;for(const b of document.querySelectorAll(".footer")){var _=document.createElement("div"),L=(_.innerHTML=m,document.createElement("div"));L.innerHTML="2022 NopeCHA",b.append(_),b.append(L)}}async function main(){await init_ui(),await check_plan(),await render_plan(),setInterval(render_plan,500)}document.addEventListener("DOMContentLoaded",main);
(async()=>{function e(){var e="true"===document.querySelector(".recaptcha-checkbox")?.getAttribute("aria-checked"),t=document.querySelector("#recaptcha-verify-button")?.disabled;return e||t}function d(r=15e3){return new Promise(async e=>{for(var t=Time.time();;){var a=document.querySelectorAll(".rc-imageselect-tile"),c=document.querySelectorAll(".rc-imageselect-dynamic-selected");if(0<a.length&&0===c.length)return e(!0);if(Time.time()-t>r)return e(!1);await Time.sleep(100)}})}let p=null;function a(e=500){return new Promise(m=>{let h=!1;const f=setInterval(async()=>{if(!h){h=!0;var c=document.querySelector(".rc-imageselect-instructions")?.innerText?.split("\n"),r=await async function(e){let t=null;return(t=1<e.length?(t=e.slice(0,2).join(" ")).replace(/\s+/g," ")?.trim():t.join("\n"))||null}(c);if(r){var c=3===c.length,i=document.querySelectorAll("table tr td");if(9===i.length||16===i.length){var l=[],n=Array(i.length).fill(null);let e=null,t=!1,a=0;for(const u of i){var o=u?.querySelector("img");if(!o)return void(h=!1);var s=o?.src?.trim();if(!s||""===s)return void(h=!1);300<=o.naturalWidth?e=s:100==o.naturalWidth&&(n[a]=s,t=!0),l.push(u),a++}t&&(e=null);i=JSON.stringify([e,n]);if(p!==i)return p=i,clearInterval(f),h=!1,m({task:r,is_hard:c,cells:l,background_url:e,urls:n})}}h=!1}},e)})}async function t(){!0===await BG.exec("Cache.get",{name:"recaptcha_widget_visible",tab_specific:!0})&&(e()?r=r||!0:(r=!1,await Time.sleep(500),document.querySelector("#recaptcha-anchor")?.click()))}async function c(){var c=await BG.exec("Cache.get",{name:"recaptcha_image_visible",tab_specific:!0});if(!0===c&&(null===document.querySelector(".rc-doscaptcha-header")&&!e()))if(g=!(g||!function(){for(const e of[".rc-imageselect-incorrect-response"])if(""===document.querySelector(e)?.style.display)return 1}()||(y=[],0)),function(){for(const t of[".rc-imageselect-error-select-more",".rc-imageselect-error-dynamic-more",".rc-imageselect-error-select-something"]){var e=document.querySelector(t);if(""===e?.style.display||0===e?.tabIndex)return 1}}())y=[];else if(await d()){var{task:c,is_hard:r,cells:t,background_url:i,urls:l}=await a(),n=await BG.exec("Settings.get");if(n.enabled&&n.recaptcha_auto_solve){var o=9==t.length?3:4,s=[];let e,a=[];if(null===i){e="1x1";for(let e=0;e<l.length;e++){var u=l[e],m=t[e];u&&!y.includes(u)&&(s.push(u),a.push(m))}}else s.push(i),e=o+"x"+o,a=t;var i=Time.time(),h=(await NopeCHA.post({captcha_type:IS_DEVELOPMENT?"recaptcha_dev":"recaptcha",task:c,image_urls:s,grid:e,key:n.key}))["data"];if(h){c=parseInt(n.recaptcha_solve_delay_time)||1e3,n=n.recaptcha_solve_delay?c-(Time.time()-i):0;0<n&&await Time.sleep(n);let t=0;for(let e=0;e<h.length;e++)!1!==h[e]&&(t++,function(e){try{return e.classList.contains("rc-imageselect-tileselected")}catch{}}(a[e])||a[e]?.click());for(const f of l)y.push(f),9<y.length&&y.shift();(3==o&&r&&0===t&&await d()||3==o&&!r||4==o)&&(await Time.sleep(200),document.querySelector("#recaptcha-verify-button")?.click())}}}}let r=!1,g=!1,y=[];for(;;){await Time.sleep(1e3);var i,l=await BG.exec("Settings.get");l&&l.enabled&&"Image"===l.recaptcha_solve_method&&(i=await Location.hostname(),l.disabled_hosts.includes(i)||(await async function(){var e=[...document.querySelectorAll('iframe[src*="/recaptcha/api2/bframe"]'),...document.querySelectorAll('iframe[src*="/recaptcha/enterprise/bframe"]')];if(0<e.length){for(const t of e)if("visible"===window.getComputedStyle(t).visibility)return BG.exec("Cache.set",{name:"recaptcha_image_visible",value:!0,tab_specific:!0});await BG.exec("Cache.set",{name:"recaptcha_image_visible",value:!1,tab_specific:!0})}}(),await async function(){var e=[...document.querySelectorAll('iframe[src*="/recaptcha/api2/anchor"]'),...document.querySelectorAll('iframe[src*="/recaptcha/enterprise/anchor"]')];if(0<e.length){for(const t of e)if("visible"===window.getComputedStyle(t).visibility)return BG.exec("Cache.set",{name:"recaptcha_widget_visible",value:!0,tab_specific:!0});await BG.exec("Cache.set",{name:"recaptcha_widget_visible",value:!1,tab_specific:!0})}}(),l.recaptcha_auto_open&&null!==document.querySelector(".recaptcha-checkbox")?await t():l.recaptcha_auto_solve&&null!==document.querySelector("#rc-imageselect")&&await c()))}})();
(async()=>{let i=null,t=!1,n=!1;function s(e){let t=e;for(;t&&!t.classList?.contains("rc-imageselect-tile");)t=t.parentNode;return t}function a(e,t,n=!1){!e||!n&&i===e||(!0===t&&e.classList.contains("rc-imageselect-tileselected")||!1===t&&!e.classList.contains("rc-imageselect-tileselected"))&&e.click()}document.addEventListener("mousedown",e=>{e=s(e?.target);e&&(n=e.classList.contains("rc-imageselect-tileselected")?t=!0:!(t=!0),i=e)}),document.addEventListener("mouseup",e=>{t=!1,i=null}),document.addEventListener("mousemove",e=>{e=s(e?.target);t&&(i!==e&&null!==i&&a(i,n,!0),a(e,n))});window.addEventListener("load",()=>{var e=document.body.appendChild(document.createElement("style")).sheet;e.insertRule(".rc-imageselect-table-33, .rc-imageselect-table-42, .rc-imageselect-table-44 {transition-duration: 0.5s !important}",0),e.insertRule(".rc-imageselect-tile {transition-duration: 2s !important}",1),e.insertRule(".rc-imageselect-dynamic-selected {transition-duration: 1s !important}",2),e.insertRule(".rc-imageselect-progress {transition-duration: 0.5s !important}",3),e.insertRule(".rc-image-tile-overlay {transition-duration: 0.5s !important}",4),e.insertRule("#rc-imageselect img {pointer-events: none !important}",5)})})();
(async()=>{function e(){var e,t;if(!i())return e="true"===document.querySelector(".recaptcha-checkbox")?.getAttribute("aria-checked"),t=document.querySelector("#recaptcha-verify-button")?.disabled,e||t}function i(){return"Try again later"===document.querySelector(".rc-doscaptcha-header")?.innerText}async function t(){!0!==await BG.exec("Cache.get",{name:"recaptcha_widget_visible",tab_specific:!0})||e()||(await Time.sleep(500),document.querySelector("#recaptcha-anchor")?.click())}async function a(t){var a=await BG.exec("Cache.get",{name:"recaptcha_image_visible",tab_specific:!0});if(!0===a&&!e()&&!i()){a=document.querySelector(".rc-audiochallenge-tdownload-link")?.href,a=(fetch(a),document.querySelector("#audio-source")?.src?.replace("recaptcha.net","google.com"));let e=document.querySelector("html")?.getAttribute("lang")?.trim();e&&0!==e.length||(e="en");var c=Time.time(),a=await Net.fetch("https://engageub.pythonanywhere.com",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"input="+encodeURIComponent(a)+"&lang="+e});document.querySelector("#audio-response").value=a;a=parseInt(t.recaptcha_solve_delay_time)||1e3,t=t.recaptcha_solve_delay?a-(Time.time()-c):0;0<t&&await Time.sleep(t),document.querySelector("#recaptcha-verify-button")?.click()}}for(;;){await Time.sleep(1e3);var c,r=await BG.exec("Settings.get");r&&r.enabled&&"Speech"===r.recaptcha_solve_method&&(c=await Location.hostname(),r.disabled_hosts.includes(c)||(await async function(){var e=[...document.querySelectorAll('iframe[src*="/recaptcha/api2/bframe"]'),...document.querySelectorAll('iframe[src*="/recaptcha/enterprise/bframe"]')];if(0<e.length){for(const t of e)if("visible"===window.getComputedStyle(t).visibility)return BG.exec("Cache.set",{name:"recaptcha_image_visible",value:!0,tab_specific:!0});await BG.exec("Cache.set",{name:"recaptcha_image_visible",value:!1,tab_specific:!0})}}(),await async function(){var e=[...document.querySelectorAll('iframe[src*="/recaptcha/api2/anchor"]'),...document.querySelectorAll('iframe[src*="/recaptcha/enterprise/anchor"]')];if(0<e.length){for(const t of e)if("visible"===window.getComputedStyle(t).visibility)return BG.exec("Cache.set",{name:"recaptcha_widget_visible",value:!0,tab_specific:!0});await BG.exec("Cache.set",{name:"recaptcha_widget_visible",value:!1,tab_specific:!0})}}(),r.recaptcha_auto_open&&null!==document.querySelector(".recaptcha-checkbox")?await t():r.recaptcha_auto_solve&&null!==document.querySelector(".rc-imageselect-instructions")?await(!0===await BG.exec("Cache.get",{name:"recaptcha_image_visible",tab_specific:!0})&&!e()&&(await Time.sleep(500),!document.querySelector("#recaptcha-audio-button")?.click())):!r.recaptcha_auto_solve||null===document.querySelector("#audio-instructions")&&null===document.querySelector(".rc-doscaptcha-header")||await a(r)))}})();
(async()=>{function e(){try{function t(t){return`<p style='font-family: monospace; font-size: 12px; white-space: pre;'>${t}</p>`}var e=[];for(const n of arguments)e.push(t(n));e.push(t('Join us on <a href="https://nopecha.com/discord" target="_blank">Discord</a>')),document.body.innerHTML=e.join("<hr>")}catch(t){}}try{var t,n;document.location.hash?(e("Importing settings..."),await BG.exec("Settings.get"),t=SettingsManager.import(document.location.hash),await BG.exec("Settings.update",{settings:t}),e(`Visiting this URL will import your NopeCHA settings.
<a href="${n=window.location.href}">${n}</a>`,`Successfully imported settings.
`+JSON.stringify(t,null,4))):e("Invalid URL.\nPlease set the URL hash and reload the page.","Example: https://nopecha.com/setup#TESTKEY123")}catch(t){e("Failed to import settings.\nPlease verify that your URL is formed properly.")}})();
(async()=>{async function r(e){function c(a){return new Promise(e=>{const t=new Image;t.onload=()=>e(t),t.src=function(e){let t=e.style.backgroundImage;return t&&((e=t.trim().match(/(?!^)".*?"/g))&&0!==e.length||(t=null),t=e[0].replaceAll('"',"")),t}(a)})}try{return(await async function(e){var t=document.querySelector(e);if(t instanceof HTMLCanvasElement)return t;let a;if(a=t instanceof HTMLImageElement?t:await c(t))return(t=document.createElement("canvas")).width=a.naturalWidth,t.height=a.naturalHeight,t.getContext("2d").drawImage(a,0,0),t;throw Error("failed to get image element for "+e)}(e)).toDataURL("image/jpeg").split(";base64,")[1]}catch(e){return null}}let l=null;async function e(){var e,t,a,c,n=(e=500,await new Promise(t=>{let a=!1;const c=setInterval(async()=>{if(!a){a=!0;var e=await BG.exec("Settings.get");if(e.textcaptcha_auto_solve){e=await r(e.textcaptcha_image_selector);if(e&&l!==e)return l=e,clearInterval(c),a=!1,t({image_data:e})}a=!1}},e)}))["image_data"],i=await BG.exec("Settings.get");i.enabled&&i.textcaptcha_auto_solve&&(c=Time.time(),{job_id:t,data:n}=await NopeCHA.post({captcha_type:IS_DEVELOPMENT?"textcaptcha_dev":"textcaptcha",image_data:[n],key:i.key}),n)&&(a=(a=parseInt(i.textcaptcha_solve_delay_time))||100,0<(a=i.textcaptcha_solve_delay?a-(Time.time()-c):0)&&await Time.sleep(a),n)&&0<n.length&&(c=document.querySelector(i.textcaptcha_input_selector))&&!c.value&&(c.value=n[0])}for(;;){await Time.sleep(1e3);var t,a=await BG.exec("Settings.get");a&&a.enabled&&(t=await Location.hostname(),a.disabled_hosts.includes(t)||a.textcaptcha_auto_solve&&function(e){try{var t;return document.querySelector(e.textcaptcha_image_selector)?!(!(t=document.querySelector(e.textcaptcha_input_selector))||t.value):void 0}catch(e){}}(a)&&await e())}})();
'use strict';
/**
* Set to true for the following behavior:
* - Request server to recognize using bleeding-edge models
* - Reload FunCAPTCHA on verification
*/
const IS_DEVELOPMENT = false;
/**
* Trying to be an Enum but javascript doesn't have enums
*/
class RunningAs {
// Background script running on-demand
static BACKGROUND = 'BACKGROUND';
// Popup specified in manifest as "action"
static POPUP = 'POPUP';
// Content script running in page
static CONTENT = 'CONTENT';
// (somehow) Standalone run of script running in webpage
static WEB = 'WEB';
}
Object.freeze(RunningAs);
const runningAt = (() => {
let getBackgroundPage = globalThis?.chrome?.extension?.getBackgroundPage;
if (getBackgroundPage){
return getBackgroundPage() === window ? RunningAs.BACKGROUND : RunningAs.POPUP;
}
return globalThis?.chrome?.runtime?.onMessage ? RunningAs.CONTENT : RunningAs.WEB;
})();
function deep_copy(obj) {
return JSON.parse(JSON.stringify(obj));
}
class Util {
static CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
static pad_left(s, char, n) {
while (`${s}`.length < n) {
s = `${char}${s}`;
}
return s;
}
static capitalize(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
static parse_int(s, fallback) {
if (!s) {
s = fallback;
}
return parseInt(s);
}
static parse_bool(s, fallback) {
if (s === 'true') {
s = true;
}
else if (s === 'false') {
s = false;
}
else {
s = fallback;
}
return s;
}
static parse_string(s, fallback) {
if (!s) {
s = fallback;
}
return s;
}
static parse_json(s, fallback) {
if (!s) {
s = fallback;
}
else {
s = JSON.parse(s);
}
return s;
}
static generate_id(n) {
let result = '';
for (let i = 0; i < n; i++) {
result += Util.CHARS.charAt(Math.floor(Math.random() * Util.CHARS.length));
}
return result;
}
}
class Time {
static time() {
if (!Date.now) {
Date.now = () => new Date().getTime();
}
return Date.now();
}
static date() {
return new Date();
}
static sleep(i=1000) {
return new Promise(resolve => setTimeout(resolve, i));
}
static async random_sleep(min, max) {
const duration = Math.floor(Math.random() * (max - min) + min);
return await Time.sleep(duration);
}
static seconds_as_hms(t) {
t = Math.max(0, t);
const hours = Util.pad_left(Math.floor(t / 3600), '0', 2);
t %= 3600;
const minutes = Util.pad_left(Math.floor(t / 60), '0', 2);
const seconds = Util.pad_left(Math.floor(t % 60), '0', 2);
return `${hours}:${minutes}:${seconds}`;
}
static string(d=null) {
if (!d) {
d = Time.date();
}
const month = Util.pad_left(d.getMonth() + 1, '0', 2);
const date = Util.pad_left(d.getDate(), '0', 2);
const year = d.getFullYear();
const hours = Util.pad_left(d.getHours() % 12, '0', 2);
const minutes = Util.pad_left(d.getMinutes(), '0', 2);
const seconds = Util.pad_left(d.getSeconds(), '0', 2);
const period = d.getHours() >= 12 ? 'PM' : 'AM';
return `${month}/${date}/${year} ${hours}:${minutes}:${seconds} ${period}`;
}
}
class SettingsManager {
static DEFAULT = {
version: 15,
key: '',
enabled: true,
disabled_hosts: [],
hcaptcha_auto_open: true,
hcaptcha_auto_solve: true,
hcaptcha_solve_delay: true,
hcaptcha_solve_delay_time: 3000,
recaptcha_auto_open: true,
recaptcha_auto_solve: true,
recaptcha_solve_delay: true,
recaptcha_solve_delay_time: 2000,
recaptcha_solve_method: 'Image',
funcaptcha_auto_open: true,
funcaptcha_auto_solve: true,
funcaptcha_solve_delay: true,
funcaptcha_solve_delay_time: 1000,
awscaptcha_auto_open: true,
awscaptcha_auto_solve: true,
awscaptcha_solve_delay: true,
awscaptcha_solve_delay_time: 1000,
textcaptcha_auto_solve: true,
textcaptcha_solve_delay: true,
textcaptcha_solve_delay_time: 100,
textcaptcha_image_selector: '',
textcaptcha_input_selector: '',
};
static ENCODE_FIELDS = {
enabled: {parse: Util.parse_bool, encode: encodeURIComponent},
disabled_hosts: {parse: Util.parse_json, encode: e => encodeURIComponent(JSON.stringify(e))},
hcaptcha_auto_open: {parse: Util.parse_bool, encode: encodeURIComponent},
hcaptcha_auto_solve: {parse: Util.parse_bool, encode: encodeURIComponent},
hcaptcha_solve_delay: {parse: Util.parse_bool, encode: encodeURIComponent},
hcaptcha_solve_delay_time: {parse: Util.parse_int, encode: encodeURIComponent},
recaptcha_auto_open: {parse: Util.parse_bool, encode: encodeURIComponent},
recaptcha_auto_solve: {parse: Util.parse_bool, encode: encodeURIComponent},
recaptcha_solve_delay: {parse: Util.parse_bool, encode: encodeURIComponent},
recaptcha_solve_delay_time: {parse: Util.parse_int, encode: encodeURIComponent},
recaptcha_solve_method: {parse: Util.parse_string, encode: encodeURIComponent},
funcaptcha_auto_open: {parse: Util.parse_bool, encode: encodeURIComponent},
funcaptcha_auto_solve: {parse: Util.parse_bool, encode: encodeURIComponent},
funcaptcha_solve_delay: {parse: Util.parse_bool, encode: encodeURIComponent},
funcaptcha_solve_delay_time: {parse: Util.parse_int, encode: encodeURIComponent},
awscaptcha_auto_open: {parse: Util.parse_bool, encode: encodeURIComponent},
awscaptcha_auto_solve: {parse: Util.parse_bool, encode: encodeURIComponent},
awscaptcha_solve_delay: {parse: Util.parse_bool, encode: encodeURIComponent},
awscaptcha_solve_delay_time: {parse: Util.parse_int, encode: encodeURIComponent},
textcaptcha_auto_solve: {parse: Util.parse_bool, encode: encodeURIComponent},
textcaptcha_solve_delay: {parse: Util.parse_bool, encode: encodeURIComponent},
textcaptcha_solve_delay_time: {parse: Util.parse_int, encode: encodeURIComponent},
textcaptcha_image_selector: {parse: Util.parse_string, encode: encodeURIComponent},
textcaptcha_input_selector: {parse: Util.parse_string, encode: encodeURIComponent},
};
static IMPORT_URL = 'https://nopecha.com/setup';
static DELIMITER = '|';
static export(settings) {
if (!settings.key) {
return false;
}
const fields = [settings.key];
for (const k in SettingsManager.ENCODE_FIELDS) {
fields.push(`${k}=${SettingsManager.ENCODE_FIELDS[k].encode(settings[k])}`);
}
const encoded_hash = `#${fields.join(SettingsManager.DELIMITER)}`;
return `${SettingsManager.IMPORT_URL}${encoded_hash}`;
}
static import(encoded_hash) {
const settings = {};
// Split by delimiter
const fields = encoded_hash.split(SettingsManager.DELIMITER);
if (fields.length === 0) {
return settings;
}
// Parse key
const key = fields.shift();
if (key.length <= 1) {
console.error('invalid key for settings', key);
return settings;
}
settings.key = key.substring(1);
// Parse additional fields
for (const field of fields) {
const kv = field.split('=');
const k = kv.shift();
const v_raw = kv.join('=');
if (!(k in SettingsManager.ENCODE_FIELDS)) {
console.error('invalid field for settings', field);
continue;
}
const v = decodeURIComponent(v_raw);
console.log('v', v);
settings[k] = SettingsManager.ENCODE_FIELDS[k].parse(v, SettingsManager.DEFAULT[k]);
}
return settings;
}
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment