feat: add ja3 support to enable claude2

This commit is contained in:
ikechan8370 2023-09-08 22:04:48 +08:00
parent 56d6b50aef
commit b743259e78
2 changed files with 97 additions and 58 deletions

View file

@ -41,7 +41,8 @@
"puppeteer-extra": "^3.3.6", "puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-recaptcha": "^3.6.8", "puppeteer-extra-plugin-recaptcha": "^3.6.8",
"puppeteer-extra-plugin-stealth": "^2.11.2", "puppeteer-extra-plugin-stealth": "^2.11.2",
"sharp": "^0.32.3" "sharp": "^0.32.3",
"cycletls": "^1.0.21"
}, },
"devDependencies": { "devDependencies": {
"ts-node": "^10.9.1", "ts-node": "^10.9.1",

View file

@ -1,34 +1,52 @@
import fetch, { File, FormData, Headers } from 'node-fetch' import { File, FormData, Headers } from 'node-fetch'
import fs from 'fs' import fs from 'fs'
import crypto from 'crypto' import crypto from 'crypto'
import HttpsProxyAgent from 'https-proxy-agent' // import initCycleTLS from 'cycletls'
let initCycleTLS
try {
initCycleTLS = await import('cycletls')
} catch (err) {
console.warn('未安装cycletls无法使用claude2功能。')
}
export class ClaudeAIClient { export class ClaudeAIClient {
constructor (opts) { constructor (opts) {
if (!initCycleTLS) {
throw new Error('CycleTLS is not installed')
}
const { organizationId, sessionKey, proxy, debug = false } = opts const { organizationId, sessionKey, proxy, debug = false } = opts
this.organizationId = organizationId this.organizationId = organizationId
this.sessionKey = sessionKey this.sessionKey = sessionKey
this.debug = debug this.debug = debug
let headers = new Headers() let headers = new Headers()
headers.append('Cookie', `sessionKey=${sessionKey}`) headers.append('Cookie', `sessionKey=${sessionKey}`)
headers.append('referrer', 'https://claude.ai/chat/360f8c2c-56e8-4193-99c6-8d52fad3ecc8') headers.append('referrer', 'https://claude.ai/chat')
headers.append('origin', 'https://claude.ai') headers.append('origin', 'https://claude.ai')
headers.append('Content-Type', 'application/json') headers.append('Content-Type', 'application/json')
this.headers = headers headers.append('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36')
this.proxy = proxy // headers.append('sec-ch-ua', '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"')
this.fetch = (url, options = {}) => { // headers.append('Sec-Ch-Ua-Mobile', '?0')
const defaultOptions = proxy // headers.append('Sec-Ch-Ua-Platform', '"Windows"')
? { headers.append('Sec-Fetch-Dest', 'empty')
agent: HttpsProxyAgent(proxy) headers.append('Sec-Fetch-Mode', 'cors')
} headers.append('Sec-Fetch-Site', 'same-origin')
: {} headers.append('Connection', 'keep-alive')
const mergedOptions = { headers.append('TE', 'trailers')
...defaultOptions, headers.append('Accept-Encoding', 'gzip, deflate, br')
...options headers.append('Accept-Language', 'en-US,en;q=0.5')
} headers.append('Dnt', '1')
headers.append('Accept', '*/*')
// headers.append('sentry-trace', 'd1c13c8e760c4e9e969a5e1aed6a38cf-a854f94e3d1a4bc7-0')
// headers.append('anthropic-client-sha', 'cab849b55d41c73804c1b2b87a7a7fdb84263dc9')
// headers.append('anthropic-client-version', '1')
// headers.append('baggage', 'sentry-environment=production,sentry-release=cab849b55d41c73804c1b2b87a7a7fdb84263dc9,sentry-public_key=58e9b9d0fc244061a1b54fe288b0e483,sentry-trace_id=d1c13c8e760c4e9e969a5e1aed6a38cf')
this.JA3 = '772,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,5-27-45-35-65281-16-18-10-17513-43-13-23-51-0-11,29-23-24,0'
return fetch(url, mergedOptions) this.headers = headers
} this.rawHeaders = {}
Array.from(this.headers.keys()).forEach(key => {
this.rawHeaders[key] = this.headers.get(key)
})
this.proxy = proxy
} }
/** /**
@ -42,26 +60,27 @@ export class ClaudeAIClient {
formData.append('orgUuid', this.organizationId) formData.append('orgUuid', this.organizationId)
let buffer = fs.readFileSync(filePath) let buffer = fs.readFileSync(filePath)
formData.append('file', new File([buffer], filename)) formData.append('file', new File([buffer], filename))
let result = await this.fetch('https://claude.ai/api/convert_document', { // let result = await this.fetch('https://claude.ai/api/convert_document', {
body: formData, // body: formData,
headers: this.headers, // headers: this.headers,
method: 'POST', // method: 'POST',
redirect: 'manual' // redirect: 'manual',
}) // referrer: 'https://claude.ai/chat/bba5a67d-ee59-4196-a371-ece8a35db1f2'
if (result.statusCode === 307) { // })
throw new Error('claude.ai目前不支持你所在的地区') // if (result.statusCode === 307) {
} // throw new Error('claude.ai目前不支持你所在的地区')
if (result.statusCode !== 200) { // }
console.warn('failed to parse document convert result: ' + result.statusCode + ' ' + result.statusText) // if (result.statusCode !== 200) {
return null // console.warn('failed to parse document convert result: ' + result.statusCode + ' ' + result.statusText)
} // return null
let raw = await result.text() // }
try { // let raw = await result.text()
return JSON.parse(raw) // try {
} catch (e) { // return JSON.parse(raw)
console.warn('failed to parse document convert result: ' + raw) // } catch (e) {
return null // console.warn('failed to parse document convert result: ' + raw)
} // return null
// }
} }
/** /**
@ -76,21 +95,32 @@ export class ClaudeAIClient {
uuid uuid
} }
body = JSON.stringify(body) body = JSON.stringify(body)
let result = await this.fetch(`https://claude.ai/api/organizations/${this.organizationId}/chat_conversations`, { // let result = await this.fetch(`https://claude.ai/api/organizations/${this.organizationId}/chat_conversations`, {
// body,
// headers: this.headers,
// method: 'POST',
// redirect: 'manual'
// // referrer: 'https://claude.ai/chat/bba5a67d-ee59-4196-a371-ece8a35db1f2'
// })
const cycleTLS = await initCycleTLS()
let result = await cycleTLS(`https://claude.ai/api/organizations/${this.organizationId}/chat_conversations`, {
ja3: this.JA3,
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36',
proxy: this.proxy,
body, body,
headers: this.headers, headers: this.rawHeaders,
method: 'POST', disableRedirect: true
redirect: 'manual' }, 'post')
}) if (result.status === 307) {
if (result.statusCode === 307) {
throw new Error('claude.ai目前不支持你所在的地区') throw new Error('claude.ai目前不支持你所在的地区')
} }
let jsonRes = await result.json() let jsonRes = result.body
if (this.debug) { if (this.debug) {
console.log(jsonRes) console.log(jsonRes)
} }
if (!jsonRes?.uuid) { if (!jsonRes?.uuid) {
console.error(jsonRes) console.error(jsonRes)
// console.log(result.headers)
throw new Error('conversation create error') throw new Error('conversation create error')
} }
return jsonRes return jsonRes
@ -110,37 +140,44 @@ export class ClaudeAIClient {
} }
} }
let url = 'https://claude.ai/api/append_message' let url = 'https://claude.ai/api/append_message'
let streamDataRes = await this.fetch(url, { const cycleTLS = await initCycleTLS()
method: 'POST', let streamDataRes = await cycleTLS(url, {
ja3: this.JA3,
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36',
proxy: this.proxy,
body: JSON.stringify(body), body: JSON.stringify(body),
headers: this.headers, headers: this.rawHeaders,
redirect: 'manual' disableRedirect: true
}) }, 'post')
if (streamDataRes.statusCode === 307) { if (streamDataRes.status === 307) {
throw new Error('claude.ai目前不支持你所在的地区') throw new Error('claude.ai目前不支持你所在的地区')
} }
let streamData = await streamDataRes.text() let streamData = streamDataRes.body
// console.log(streamData)
let responseText = '' let responseText = ''
let streams = streamData.split('\n\n') let streams = streamData.split('\n\n')
streams.forEach(s => { for (let s of streams) {
let jsonStr = s.replace('data: ', '').trim() let jsonStr = s.replace('data: ', '').trim()
try { try {
let jsonObj = JSON.parse(jsonStr) let jsonObj = JSON.parse(jsonStr)
if (jsonObj && jsonObj.completion) { if (jsonObj && jsonObj.completion) {
responseText += jsonObj.completion responseText += jsonObj.completion
} }
if (this.debug) {
console.log(jsonObj)
}
// console.log(responseText)
} catch (err) { } catch (err) {
// ignore error // ignore error
if (this.debug) { if (this.debug) {
console.log(jsonStr) console.log(jsonStr)
} }
} }
}) }
let response = { return {
text: responseText.trim(), text: responseText.trim(),
conversationId conversationId
} }
return response
} }
} }
@ -153,7 +190,8 @@ async function testClaudeAI () {
}) })
let conv = await client.createConversation() let conv = await client.createConversation()
let result = await client.sendMessage('hello, who are you', conv.uuid) let result = await client.sendMessage('hello, who are you', conv.uuid)
console.log(result.response) console.log(result.text)
return result
} }
// testClaudeAI() // testClaudeAI()