|
|
|
|
|
|
|
|
|
'use strict'; |
|
|
|
const {createServer: Server, IncomingMessage, ServerResponse} = require('node:http'), {createHash: Hash, randomUUID, randomInt, randomBytes} = require('node:crypto'), {TransformStream, ReadableStream} = require('node:stream/web'), {Readable, Writable} = require('node:stream'), {Blob} = require('node:buffer'), {existsSync: exists, writeFileSync: write, createWriteStream} = require('node:fs'), {join: joinP} = require('node:path'), {ClewdSuperfetch: Superfetch, SuperfetchAvailable} = require('./lib/clewd-superfetch'), {AI, fileName, genericFixes, bytesToSize, setTitle, checkResErr, Replacements, Main} = require('./lib/clewd-utils'), ClewdStream = require('./lib/clewd-stream'); |
|
|
|
|
|
let currentIndex, Firstlogin = true, changeflag = 0, changetime = 0, totaltime, uuidOrgArray = [], model, tokens, apiKey; |
|
|
|
const events = require('events'), CookieChanger = new events.EventEmitter(); |
|
require('events').EventEmitter.defaultMaxListeners = 0; |
|
|
|
CookieChanger.on('ChangeCookie', () => { |
|
changeflag = 0; |
|
Proxy && Proxy.close(); |
|
console.log(`Changing Cookie...\n`); |
|
Proxy.listen(Config.Port, Config.Ip, onListen); |
|
Proxy.on('error', (err => { |
|
console.error('Proxy error\n%o', err); |
|
})); |
|
}); |
|
|
|
const convertToType = value => { |
|
if (value === "true") return true; |
|
if (value === "false") return false; |
|
if (/^\d+$/.test(value)) return parseInt(value); |
|
return value; |
|
}, CookieCleaner = () => { |
|
Config.CookieArray = Config.CookieArray.filter(item => item !== Config.Cookie); |
|
!process.env.Cookie && !process.env.CookieArray && writeSettings(Config); |
|
currentIndex = (currentIndex - 1 + Config.CookieArray.length) % Config.CookieArray.length; |
|
}, padtxt = content => { |
|
const {countTokens} = require('@anthropic-ai/tokenizer'); |
|
const placeholder = Config.padtxt_placeholder || randomBytes(randomInt(5, 15)).toString('hex'); |
|
tokens = countTokens(content); |
|
!apiKey && (content = placeholder.repeat(Math.floor(Math.max(1000, Config.Settings.padtxt - tokens) / countTokens(placeholder.trim()))) + '\n\n\n' + content.trim()); |
|
return content; |
|
}, xmlPlot = content => { |
|
|
|
const card = content.includes('<card>'); |
|
|
|
if (card) { |
|
let segcontentHuman = content.split('\n\nHuman:'); |
|
const seglength = segcontentHuman.length; |
|
if (/Assistant: *.$/.test(content) && seglength > 1 && !segcontentHuman[seglength - 2].includes('\n\nAssistant:')) { |
|
segcontentHuman[seglength - 2] = segcontentHuman.splice(seglength - 1, 1, segcontentHuman[seglength - 2])[0]; |
|
} |
|
content = segcontentHuman.join('\n\nHuman:'); |
|
} |
|
|
|
const MergeDisable = content.includes('<\!-- Merge Disable -->'); |
|
const MergeHumanDisable = content.includes('<\!-- Merge Human Disable -->'); |
|
const MergeAssistantDisable = content.includes('<\!-- Merge Assistant Disable -->'); |
|
if (!MergeDisable) { |
|
if (content.includes('<\!-- Merge System Disable -->')) { |
|
content = content.replace(/(\n\n|^\s*)xmlPlot:\s*/gm, '$1'); |
|
} |
|
if (!MergeHumanDisable) { |
|
const Human = /^\s*Human:/.test(content); |
|
content = content.replace(/(\n\n|^\s*)xmlPlot:/g, '$1Human:'); |
|
content = content.replace(/(?:\n\n|^\s*)Human:(.*?(?:\n\nAssistant:|$))/gs, function(match, p1) {return '\n\nHuman:' + p1.replace(/\n\nHuman:\s*/g, '\n\n')}); |
|
!apiKey && !Human && (content = content.replace(/^\s*Human:\s*/, '')); |
|
} |
|
if (!MergeAssistantDisable) { |
|
content = content.replace(/\n\nAssistant:(.*?(?:\n\nHuman:|$))/gs, function(match, p1) {return '\n\nAssistant:' + p1.replace(/\n\nAssistant:\s*/g, '\n\n')}); |
|
} |
|
} |
|
content = content.replace(/(\n\n|^\s*)xmlPlot:\s*/gm, '$1'); |
|
content = content.replace(/<\!-- Merge.*?Disable -->/gm, ''); |
|
|
|
content = content.replace(/(<\/?)PrevAssistant>/gm, '$1@1>'); |
|
content = content.replace(/(<\/?)PrevHuman>/gm, '$1@2>'); |
|
let splitContent = content.split(/\n\n(?=Assistant:|Human:)/g); |
|
let match; |
|
while ((match = /<@(\d+)>(.*?)<\/@\1>/gs.exec(content)) !== null) { |
|
let index = splitContent.length - parseInt(match[1]) - 1; |
|
if (index >= 0) { |
|
splitContent[index] += '\n\n' + match[2]; |
|
} |
|
content = content.replace(match[0], ''); |
|
} |
|
content = splitContent.join('\n\n'); |
|
content = content.replace(/<@(\d+)>.*?<\/@\1>/gs, ''); |
|
|
|
while ((match = /<regex>"(\/?)(.*)\1(.*)" *: *"(.*?)"<\/regex>/gm.exec(content)) !== null) { |
|
try { |
|
content = content.replace(new RegExp(match[2], match[3]), match[4]); |
|
} catch (error) {} |
|
content = content.replace(match[0], ''); |
|
} |
|
content = content.replace(/(\r\n|\r|\\n)/gm, '\n'); |
|
|
|
if (!MergeDisable) { |
|
if (!MergeHumanDisable) { |
|
content = content.replace(/(?:\n\n|^\s*)Human:(.*?(?:\n\nAssistant:|$))/gs, function(match, p1) {return '\n\nHuman:' + p1.replace(/\n\nHuman:\s*/g, '\n\n')}); |
|
} |
|
if (!MergeAssistantDisable) { |
|
content = content.replace(/\n\nAssistant:(.*?(?:\n\nHuman:|$))/gs, function(match, p1) {return '\n\nAssistant:' + p1.replace(/\n\nAssistant:\s*/g, '\n\n')}); |
|
} |
|
} |
|
|
|
let segcontentHuman = content.split('\n\nHuman:'); |
|
let segcontentlastIndex = segcontentHuman.length - 1; |
|
if (segcontentlastIndex >= 2 && segcontentHuman[segcontentlastIndex].includes('<!-- Plain Prompt Enable -->') && !content.includes('\n\nPlainPrompt:')) { |
|
content = segcontentHuman.slice(0, segcontentlastIndex).join('\n\nHuman:') + '\n\nPlainPrompt:' + segcontentHuman.slice(segcontentlastIndex).join('\n\nHuman:'); |
|
} |
|
content = content.replace(/<\!-- Plain Prompt Enable -->/gm, ''); |
|
content = content.replace(/\n\nHuman: *PlainPrompt:/, '\n\nPlainPrompt:'); |
|
|
|
if (!card) { |
|
content = content.replace(/(<reply>\n|\n<\/reply>)/g, ''); |
|
return content.replace(/<customname>(.*?)<\/customname>/gm, '$1'); |
|
} else { |
|
content = content.replace(/(<reply>\n|\n<\/reply>)\1*/g, '$1'); |
|
content = content.replace(/<customname>(.*?)<\/customname>:/gm, '$1:\n'); |
|
} |
|
|
|
const cardtag = content.match(/(?=\n\n<\/card>)/) || '</card>'; |
|
const exampletag = content.match(/(?=\n\n<\/example>)/) || '</example>'; |
|
const plot = content.includes('</plot>') ? '<plot>' : ''; |
|
const firstChatStart = content.indexOf('\n\n[Start a new'); |
|
const lastChatStart = content.lastIndexOf('\n\n[Start a new'); |
|
firstChatStart != -1 && firstChatStart === lastChatStart && (content = content.slice(0, firstChatStart) + `\n\n${cardtag}` + content.slice(firstChatStart)); |
|
firstChatStart != lastChatStart && (content = content.slice(0, firstChatStart) + `\n\n${cardtag}\n<example>` + content.slice(firstChatStart, lastChatStart) + `\n\n${exampletag}\n\n${plot}` + content.slice(lastChatStart)); |
|
|
|
content = content.replace(/\s*<\|curtail\|>\s*/g, '\n'); |
|
content = content.replace(/\n<\/(card|hidden|META)>\s+?<\1>\n/g, '\n'); |
|
content = content.replace(/\n<(\/?card|example|hidden|plot|META)>\s+?<\1>/g, '\n<$1>'); |
|
content = content.replace(/(?:<!--.*?-->)?\n<(card|example|hidden|plot|META)>\s+?<\/\1>/g, ''); |
|
content = content.replace(/(?<=(: |\n)<(card|hidden|example|plot|META|EOT)>\n)\s*/g, ''); |
|
content = content.replace(/\s*(?=\n<\/(card|hidden|example|plot|META|EOT)>(\n|$))/g, ''); |
|
content = content.replace(/(?<=\n)\n(?=\n)/g, ''); |
|
return content.trim().replace(/^\s*Human:/, '\n\nHuman:'); |
|
}; |
|
|
|
|
|
let ChangedSettings, UnknownSettings, Logger; |
|
|
|
const ConfigPath = joinP(__dirname, './config.js'), LogPath = joinP(__dirname, './log.txt'), Conversation = { |
|
char: null, |
|
uuid: null, |
|
depth: 0 |
|
}, cookies = {}; |
|
|
|
let uuidOrg, curPrompt = {}, prevPrompt = {}, prevMessages = [], prevImpersonated = false, Config = { |
|
Cookie: '', |
|
CookieArray: [], |
|
Cookiecounter: 3, |
|
CookieIndex: 0, |
|
ProxyPassword: '', |
|
Ip: (process.env.Cookie || process.env.CookieArray) ? '0.0.0.0' : '127.0.0.1', |
|
Port: process.env.PORT || 8444, |
|
localtunnel: false, |
|
BufferSize: 1, |
|
SystemInterval: 3, |
|
rProxy: AI.end(), |
|
api_rProxy: '', |
|
padtxt_placeholder: '', |
|
PromptExperimentFirst: '', |
|
PromptExperimentNext: '', |
|
PersonalityFormat: '{{char}}\'s personality: {{personality}}', |
|
ScenarioFormat: 'Dialogue scenario: {{scenario}}', |
|
Settings: { |
|
RenewAlways: true, |
|
RetryRegenerate: false, |
|
PromptExperiments: true, |
|
SystemExperiments: true, |
|
PreventImperson: false, |
|
AllSamples: false, |
|
NoSamples: false, |
|
StripAssistant: false, |
|
StripHuman: false, |
|
PassParams: false, |
|
ClearFlags: true, |
|
PreserveChats: false, |
|
LogMessages: true, |
|
FullColon: true, |
|
padtxt: 15000, |
|
xmlPlot: true, |
|
Superfetch: true |
|
} |
|
}; |
|
|
|
ServerResponse.prototype.json = async function(body, statusCode = 200, headers) { |
|
body = body instanceof Promise ? await body : body; |
|
this.headersSent || this.writeHead(statusCode, { |
|
'Content-Type': 'application/json', |
|
...headers && headers |
|
}); |
|
this.end('object' == typeof body ? JSON.stringify(body) : body); |
|
return this; |
|
}; |
|
|
|
Array.prototype.sample = function() { |
|
return this[Math.floor(Math.random() * this.length)]; |
|
}; |
|
|
|
const updateParams = res => { |
|
updateCookies(res); |
|
}, updateCookies = res => { |
|
let cookieNew = ''; |
|
res instanceof Response ? cookieNew = res.headers?.get('set-cookie') : res?.superfetch ? cookieNew = res.headers?.['set-cookie'] : 'string' == typeof res && (cookieNew = res.split('\n').join('')); |
|
if (!cookieNew) { |
|
return; |
|
} |
|
let cookieArr = cookieNew.split(/;\s?/gi).filter((prop => false === /^(path|expires|domain|HttpOnly|Secure|SameSite)[=;]*/i.test(prop))); |
|
for (const cookie of cookieArr) { |
|
const divide = cookie.split(/^(.*?)=\s*(.*)/), cookieName = divide[1], cookieVal = divide[2]; |
|
cookies[cookieName] = cookieVal; |
|
} |
|
}, getCookies = () => { |
|
const cookieNames = Object.keys(cookies); |
|
return cookieNames.map(((name, idx) => `${name}=${cookies[name]}${idx === cookieNames.length - 1 ? '' : ';'}`)).join(' ').replace(/(\s+)$/gi, ''); |
|
}, deleteChat = async uuid => { |
|
if (!uuid) { |
|
return; |
|
} |
|
if (uuid === Conversation.uuid) { |
|
Conversation.uuid = null; |
|
Conversation.depth = 0; |
|
} |
|
if (Config.Settings.PreserveChats) { |
|
return; |
|
} |
|
try { |
|
const res = await fetch(`${Config.rProxy}/api/organizations/${uuidOrg}/chat_conversations/${uuid}`, { |
|
headers: { |
|
...AI.hdr(), |
|
Cookie: getCookies() |
|
}, |
|
method: 'DELETE' |
|
}); |
|
updateParams(res); |
|
} catch (err) { |
|
console.log(`[33mdeleteChat failed[0m`); |
|
} |
|
}, onListen = async () => { |
|
|
|
if (Firstlogin) { |
|
Firstlogin = false; |
|
console.log(`[2m${Main}[0m\n[33mhttp://${Config.Ip}:${Config.Port}/v1[0m\n\n${Object.keys(Config.Settings).map((setting => UnknownSettings.includes(setting) ? `??? [31m${setting}: ${Config.Settings[setting]}[0m` : `[1m${setting}:[0m ${ChangedSettings.includes(setting) ? '[33m' : '[36m'}${Config.Settings[setting]}[0m`)).sort().join('\n')}\n`); |
|
Config.Settings.Superfetch && SuperfetchAvailable(true); |
|
if (Config.localtunnel) { |
|
const localtunnel = require('localtunnel'); |
|
localtunnel({ port: Config.Port }) |
|
.then((tunnel) => { |
|
console.log(`\nTunnel URL for outer websites: ${tunnel.url}/v1\n`); |
|
}) |
|
} |
|
totaltime = Config.CookieArray.length; |
|
} |
|
if (Config.CookieArray?.length > 0) { |
|
Config.Cookie = Config.CookieArray[currentIndex]; |
|
currentIndex = (currentIndex + 1) % Config.CookieArray.length; |
|
changetime += 1; |
|
} |
|
let percentage = ((changetime + Math.max(Config.CookieIndex - 1, 0)) / totaltime) * 100 |
|
if (Config.Cookiecounter < 0 && percentage > 100) { |
|
console.log(`\n※※※Cookie cleanup completed※※※\n\n`); |
|
return process.exit(); |
|
} |
|
try { |
|
|
|
if ('SET YOUR COOKIE HERE' === Config.Cookie || Config.Cookie?.length < 1) { |
|
throw Error('Set your cookie inside config.js'); |
|
} |
|
!/^sessionKey=/.test(Config.Cookie) && (Config.Cookie += 'sessionKey='); |
|
updateCookies(Config.Cookie); |
|
|
|
|
|
const accRes = await fetch(Config.rProxy + '/api/organizations', { |
|
method: 'GET', |
|
headers: { |
|
...AI.hdr(), |
|
Cookie: getCookies() |
|
} |
|
}); |
|
|
|
if (accRes.statusText === 'Forbidden' && Config.CookieArray?.length > 0) { |
|
CookieCleaner(); |
|
console.log(`[31mExpired![0m`); |
|
Config.Cookiecounter < 0 && console.log(`[progress]: [32m${percentage.toFixed(2)}%[0m\n[length]: [33m${Config.CookieArray.length}[0m\n`); |
|
return CookieChanger.emit('ChangeCookie'); |
|
} |
|
|
|
await checkResErr(accRes); |
|
const accInfo = (await accRes.json())?.[0]; |
|
if (!accInfo || accInfo.error) { |
|
throw Error(`Couldn't get account info: "${accInfo?.error?.message || accRes.statusText}"`); |
|
} |
|
if (!accInfo?.uuid) { |
|
throw Error('Invalid account id'); |
|
} |
|
setTitle('ok'); |
|
updateParams(accRes); |
|
|
|
const accountRes = await fetch(Config.rProxy + '/api/auth/current_account', { |
|
method: 'GET', |
|
headers: { |
|
...AI.hdr(), |
|
Cookie: getCookies() |
|
} |
|
}); |
|
await checkResErr(accountRes); |
|
const accountInfo = await accountRes.json(); |
|
|
|
console.log(Config.CookieArray?.length > 0 ? `(index: [36m${currentIndex || Config.CookieArray.length}[0m) Logged in %o` : 'Logged in %o', { |
|
name: accInfo.name?.split('@')?.[0], |
|
mail: accountInfo.account.email_address, |
|
capabilities: accInfo.capabilities, |
|
}); |
|
uuidOrg = accInfo?.uuid; |
|
|
|
model = accountInfo.account.statsig.values.dynamic_configs["6zA9wvTedwkzjLxWy9PVe7yydI00XDQ6L5Fejjq/2o8="]?.value?.model; |
|
model != AI.mdl() && console.log(`[33m${model}[0m`); |
|
if (model != AI.mdl() && Config.Cookiecounter === -2) { |
|
CookieCleaner(); |
|
return CookieChanger.emit('ChangeCookie'); |
|
} |
|
const Overlap = uuidOrgArray.includes(uuidOrg) && percentage <= 100 && Config.CookieArray?.length > 0; |
|
!Overlap && uuidOrgArray.push(uuidOrg); |
|
const Unverified = !accountInfo.account.completed_verification_at; |
|
const abuseTag = accountInfo.account.statsig.values.feature_gates["4fDxNAVXgvks8yzKUoU+T+w3Qr3oYVqoJJVNYh04Mik="]?.secondary_exposures[0]; |
|
const Banned = abuseTag.gateValue === 'true' && abuseTag.gate === 'segment:abuse'; |
|
const Remain = accountInfo.messageLimit?.remaining; |
|
const Exceededlimit = (accountInfo.messageLimit?.type === 'approaching_limit' && Remain === 0) || accountInfo.messageLimit?.type === 'exceeded_limit'; |
|
if (Remain) { |
|
changeflag = Math.max(Config.Cookiecounter - Remain, changeflag); |
|
console.log(`[33mApproachingLimit!: Remain ${Remain}[0m`); |
|
} |
|
if ((Overlap || Unverified || Banned) && Config.CookieArray?.length > 0) { |
|
Overlap ? console.log(`[31mOverlap![0m`) : Unverified ? console.log(`[31mUnverified![0m`) : Banned && console.log(`[31mBanned![0m`); |
|
CookieCleaner(); |
|
Config.Cookiecounter < 0 && console.log(`[progress]: [32m${percentage.toFixed(2)}%[0m\n[length]: [33m${Config.CookieArray.length}[0m\n`); |
|
return CookieChanger.emit('ChangeCookie'); |
|
} |
|
|
|
if (accInfo?.active_flags.length > 0) { |
|
let flagtype; |
|
const now = new Date, formattedFlags = accInfo.active_flags.map((flag => { |
|
const days = ((new Date(flag.expires_at).getTime() - now.getTime()) / 864e5).toFixed(2); |
|
flagtype = flag.type; |
|
return { |
|
type: flag.type, |
|
remaining_days: days |
|
}; |
|
})); |
|
console.warn(`${'consumer_banned' === flagtype ? '[31m' : '[35m'}Your account has warnings[0m %o`, formattedFlags); |
|
await Promise.all(accInfo.active_flags.map((flag => (async type => { |
|
if (!Config.Settings.ClearFlags) { |
|
return; |
|
} |
|
if ('consumer_restricted_mode' === type || 'consumer_banned' === type) { |
|
return; |
|
} |
|
const req = await (Config.Settings.Superfetch ? Superfetch : fetch)(`${Config.rProxy}/api/organizations/${uuidOrg}/flags/${type}/dismiss`, { |
|
headers: { |
|
...AI.hdr(), |
|
Cookie: getCookies() |
|
}, |
|
method: 'POST' |
|
}); |
|
updateParams(req); |
|
const json = await req.json(); |
|
console.log(`${type}: ${json.error ? json.error.message || json.error.type || json.detail : 'OK'}`); |
|
})(flag.type)))); |
|
|
|
if (Config.CookieArray?.length > 0) { |
|
console.log(`${'consumer_banned' === flagtype ? '[31mBanned' : '[35mRestricted'}![0m`); |
|
'consumer_banned' === flagtype && CookieCleaner(); |
|
Config.Cookiecounter < 0 && console.log(`[progress]: [32m${percentage.toFixed(2)}%[0m\n[length]: [33m${Config.CookieArray.length}[0m\n`); |
|
return CookieChanger.emit('ChangeCookie'); |
|
} |
|
} |
|
if (Config.Cookiecounter < 0 || Exceededlimit) { |
|
console.log(Config.Cookiecounter < 0 ? `[progress]: [32m${percentage.toFixed(2)}%[0m\n[length]: [33m${Config.CookieArray.length}[0m\n` : '[35mExceeded limit![0m\n'); |
|
return CookieChanger.emit('ChangeCookie'); |
|
|
|
} |
|
const convRes = await fetch(`${Config.rProxy}/api/organizations/${uuidOrg}/chat_conversations`, { |
|
method: 'GET', |
|
headers: { |
|
...AI.hdr(), |
|
Cookie: getCookies() |
|
} |
|
}), conversations = await convRes.json(); |
|
updateParams(convRes); |
|
conversations.length > 0 && await Promise.all(conversations.map((conv => deleteChat(conv.uuid)))); |
|
|
|
} catch (err) { |
|
console.error('[33mClewd:[0m\n%o', err); |
|
Config.CookieArray?.length > 0 && CookieChanger.emit('ChangeCookie'); |
|
} |
|
|
|
}, writeSettings = async (config, firstRun = false) => { |
|
write(ConfigPath, `/*\n* https://rentry.org/teralomaniac_clewd\n* https://github.com/teralomaniac/clewd\n*/\n\n// SET YOUR COOKIE BELOW\n\nmodule.exports = ${JSON.stringify(config, null, 4)}\n\n/*\n BufferSize\n * How many characters will be buffered before the AI types once\n * lower = less chance of \`PreventImperson\` working properly\n\n ---\n\n SystemInterval\n * How many messages until \`SystemExperiments alternates\`\n\n ---\n\n Other settings\n * https://gitgud.io/ahsk/clewd/#defaults\n * and\n * https://gitgud.io/ahsk/clewd/-/blob/master/CHANGELOG.md\n */`.trim().replace(/((?<!\r)\n|\r(?!\n))/g, '\r\n')); |
|
if (firstRun) { |
|
console.warn('[33mconfig file created!\nedit[0m [1mconfig.js[0m [33mto set your settings and restart the program[0m'); |
|
process.exit(0); |
|
} |
|
}, Proxy = Server((async (req, res) => { |
|
if ('OPTIONS' === req.method) { |
|
return ((req, res) => { |
|
res.writeHead(200, { |
|
'Access-Control-Allow-Origin': '*', |
|
'Access-Control-Allow-Headers': 'Authorization, Content-Type', |
|
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS' |
|
}).end(); |
|
})(0, res); |
|
} |
|
switch (req.url) { |
|
case '/v1/models': |
|
res.json({ |
|
data: [ { |
|
|
|
id: 'claude-2.1' },{ |
|
id: 'claude-2.0' },{ |
|
id: 'claude-v1.3' },{ |
|
id: 'claude-v1.3-100k' },{ |
|
id: 'claude-v1.2' },{ |
|
id: 'claude-v1.0' },{ |
|
id: 'claude-instant-1.2' },{ |
|
id: 'claude-instant-v1.1' },{ |
|
id: 'claude-instant-v1.1-100k' },{ |
|
id: 'claude-instant-v1.0' |
|
|
|
} ] |
|
}); |
|
break; |
|
|
|
case '/v1/chat/completions': |
|
((req, res) => { |
|
setTitle('recv...'); |
|
let fetchAPI; |
|
const abortControl = new AbortController, {signal} = abortControl; |
|
res.socket.on('close', (async () => { |
|
abortControl.signal.aborted || abortControl.abort(); |
|
})); |
|
const buffer = []; |
|
req.on('data', (chunk => { |
|
buffer.push(chunk); |
|
})); |
|
req.on('end', (async () => { |
|
let clewdStream, titleTimer, samePrompt = false, shouldRenew = true, retryRegen = false; |
|
try { |
|
const body = JSON.parse(Buffer.concat(buffer).toString()), temperature = Math.max(.1, Math.min(1, body.temperature)); |
|
let {messages} = body; |
|
|
|
apiKey = /(?<=^Bearer \s*)sk-ant-api[\w-]*(?=\s*)$/.exec(req.headers.authorization); |
|
let api_max_tokens, api_model; |
|
if (apiKey) { |
|
apiKey = apiKey[0]; |
|
api_max_tokens = body.max_tokens; |
|
api_model = body.model; |
|
} else if (Config.ProxyPassword != '' && req.headers.authorization != 'Bearer ' + Config.ProxyPassword) { |
|
throw Error('ProxyPassword Wrong'); |
|
} |
|
|
|
if (messages?.length < 1) { |
|
throw Error('Select OpenAI as completion source'); |
|
} |
|
if (!body.stream && 1 === messages.length && JSON.stringify(messages.sort() || []) === JSON.stringify([ { |
|
role: 'user', |
|
content: 'Hi' |
|
} ].sort())) { |
|
return res.json({ |
|
choices: [ { |
|
message: { |
|
content: Main |
|
} |
|
} ] |
|
}); |
|
} |
|
res.setHeader('Access-Control-Allow-Origin', '*'); |
|
body.stream && res.setHeader('Content-Type', 'text/event-stream'); |
|
if (!body.stream && messages?.[0]?.content?.startsWith('From the list below, choose a word that best represents a character\'s outfit description, action, or emotion in their dialogue')) { |
|
return res.json({ |
|
choices: [ { |
|
message: { |
|
content: 'neutral' |
|
} |
|
} ] |
|
}); |
|
} |
|
if (Config.Settings.AllSamples && Config.Settings.NoSamples) { |
|
console.log('[33mhaving[0m [1mAllSamples[0m and [1mNoSamples[0m both set to true is not supported'); |
|
throw Error('Only one can be used at the same time: AllSamples/NoSamples'); |
|
} |
|
|
|
curPrompt = { |
|
firstUser: messages.find((message => 'user' === message.role)), |
|
firstSystem: messages.find((message => 'system' === message.role)), |
|
firstAssistant: messages.find((message => 'assistant' === message.role)), |
|
lastUser: messages.findLast((message => 'user' === message.role)), |
|
lastSystem: messages.findLast((message => 'system' === message.role && '[Start a new chat]' !== message.content)), |
|
lastAssistant: messages.findLast((message => 'assistant' === message.role)) |
|
}; |
|
prevPrompt = { |
|
...prevMessages.length > 0 && { |
|
firstUser: prevMessages.find((message => 'user' === message.role)), |
|
firstSystem: prevMessages.find((message => 'system' === message.role)), |
|
firstAssistant: prevMessages.find((message => 'assistant' === message.role)), |
|
lastUser: prevMessages.findLast((message => 'user' === message.role)), |
|
lastSystem: prevMessages.find((message => 'system' === message.role && '[Start a new chat]' !== message.content)), |
|
lastAssistant: prevMessages.findLast((message => 'assistant' === message.role)) |
|
} |
|
}; |
|
samePrompt = JSON.stringify(messages.filter((message => 'system' !== message.role)).sort()) === JSON.stringify(prevMessages.filter((message => 'system' !== message.role)).sort()); |
|
const sameCharDiffChat = !samePrompt && curPrompt.firstSystem?.content === prevPrompt.firstSystem?.content && curPrompt.firstUser?.content !== prevPrompt.firstUser?.content; |
|
shouldRenew = Config.Settings.RenewAlways || !Conversation.uuid || prevImpersonated || !Config.Settings.RenewAlways && samePrompt || sameCharDiffChat; |
|
retryRegen = Config.Settings.RetryRegenerate && samePrompt && null != Conversation.uuid; |
|
samePrompt || (prevMessages = JSON.parse(JSON.stringify(messages))); |
|
let type = ''; |
|
if (apiKey) { type = 'api'; } else if (retryRegen) { |
|
type = 'R'; |
|
fetchAPI = await (async (signal, model) => { |
|
let res; |
|
const body = { |
|
completion: { |
|
prompt: '', |
|
timezone: AI.zone(), |
|
model: model || AI.mdl() |
|
}, |
|
organization_uuid: uuidOrg, |
|
conversation_uuid: Conversation.uuid, |
|
text: '' |
|
}; |
|
let headers = { |
|
...AI.hdr(Conversation.uuid || ''), |
|
Accept: 'text/event-stream', |
|
Cookie: getCookies() |
|
}; |
|
if (Config.Settings.Superfetch) { |
|
const names = Object.keys(headers), values = Object.values(headers); |
|
headers = names.map(((header, idx) => `${header}: ${values[idx]}`)); |
|
} |
|
res = await (Config.Settings.Superfetch ? Superfetch : fetch)(Config.rProxy + '/api/retry_message', { |
|
stream: true, |
|
signal, |
|
method: 'POST', |
|
body: JSON.stringify(body), |
|
headers |
|
}); |
|
updateParams(res); |
|
await checkResErr(res); |
|
return res; |
|
})(signal, model); |
|
} else if (shouldRenew) { |
|
Conversation.uuid && await deleteChat(Conversation.uuid); |
|
fetchAPI = await (async signal => { |
|
Conversation.uuid = randomUUID().toString(); |
|
Conversation.depth = 0; |
|
const res = await (Config.Settings.Superfetch ? Superfetch : fetch)(`${Config.rProxy}/api/organizations/${uuidOrg}/chat_conversations`, { |
|
signal, |
|
headers: { |
|
...AI.hdr(), |
|
Cookie: getCookies() |
|
}, |
|
method: 'POST', |
|
body: JSON.stringify({ |
|
uuid: Conversation.uuid, |
|
name: '' |
|
}) |
|
}); |
|
updateParams(res); |
|
await checkResErr(res); |
|
return res; |
|
})(signal); |
|
type = 'r'; |
|
} else if (samePrompt) {} else { |
|
const systemExperiment = !Config.Settings.RenewAlways && Config.Settings.SystemExperiments; |
|
if (!systemExperiment || systemExperiment && Conversation.depth >= Config.SystemInterval) { |
|
type = 'c-r'; |
|
Conversation.depth = 0; |
|
} else { |
|
type = 'c-c'; |
|
Conversation.depth++; |
|
} |
|
} |
|
let {prompt, systems} = ((messages, type) => { |
|
const rgxScenario = /^\[Circumstances and context of the dialogue: ([\s\S]+?)\.?\]$/i, rgxPerson = /^\[([\s\S]+?)'s personality: ([\s\S]+?)\]$/i, messagesClone = JSON.parse(JSON.stringify(messages)), realLogs = messagesClone.filter((message => [ 'user', 'assistant' ].includes(message.role))), sampleLogs = messagesClone.filter((message => message.name)), mergedLogs = [ ...sampleLogs, ...realLogs ]; |
|
mergedLogs.forEach(((message, idx) => { |
|
const next = mergedLogs[idx + 1]; |
|
message.customname = (message => [ 'assistant', 'user' ].includes(message.role) && null != message.name && !(message.name in Replacements))(message); |
|
if (next && !Config.Settings.xmlPlot) { |
|
if ('name' in message && 'name' in next) { |
|
if (message.name === next.name) { |
|
message.content += '\n' + next.content; |
|
next.merged = true; |
|
} |
|
} else if ('system' !== next.role) { |
|
if (next.role === message.role) { |
|
message.content += '\n' + next.content; |
|
next.merged = true; |
|
} |
|
} else { |
|
message.content += '\n' + next.content; |
|
next.merged = true; |
|
} |
|
} |
|
})); |
|
const lastAssistant = realLogs.findLast((message => !message.merged && 'assistant' === message.role)); |
|
lastAssistant && Config.Settings.StripAssistant && (lastAssistant.strip = true); |
|
const lastUser = realLogs.findLast((message => !message.merged && 'user' === message.role)); |
|
lastUser && Config.Settings.StripHuman && (lastUser.strip = true); |
|
const systemMessages = messagesClone.filter((message => 'system' === message.role && !('name' in message))); |
|
systemMessages.forEach(((message, idx) => { |
|
const scenario = message.content.match(rgxScenario)?.[1], personality = message.content.match(rgxPerson); |
|
if (scenario) { |
|
message.content = Config.ScenarioFormat.replace(/{{scenario}}/gim, scenario); |
|
message.scenario = true; |
|
} |
|
if (3 === personality?.length) { |
|
message.content = Config.PersonalityFormat.replace(/{{char}}/gim, personality[1]).replace(/{{personality}}/gim, personality[2]); |
|
message.personality = true; |
|
} |
|
message.main = 0 === idx; |
|
message.jailbreak = idx === systemMessages.length - 1; |
|
' ' === message.content && (message.discard = true); |
|
})); |
|
Config.Settings.AllSamples && !Config.Settings.NoSamples && realLogs.forEach((message => { |
|
if (![ lastUser, lastAssistant ].includes(message)) { |
|
if ('user' === message.role) { |
|
message.name = message.customname ? message.name : 'example_user'; |
|
message.role = 'system'; |
|
} else if ('assistant' === message.role) { |
|
message.name = message.customname ? message.name : 'example_assistant'; |
|
message.role = 'system'; |
|
} else if (!message.customname) { |
|
throw Error('Invalid role ' + message.name); |
|
} |
|
} |
|
})); |
|
Config.Settings.NoSamples && !Config.Settings.AllSamples && sampleLogs.forEach((message => { |
|
if ('example_user' === message.name) { |
|
message.role = 'user'; |
|
} else if ('example_assistant' === message.name) { |
|
message.role = 'assistant'; |
|
} else if (!message.customname) { |
|
throw Error('Invalid role ' + message.name); |
|
} |
|
message.customname || delete message.name; |
|
})); |
|
let systems = []; |
|
if (![ 'r', 'R', 'api' ].includes(type)) { |
|
lastUser.strip = true; |
|
systemMessages.forEach((message => message.discard = message.discard || 'c-c' === type ? !message.jailbreak : !message.jailbreak && !message.main)); |
|
systems = systemMessages.filter((message => !message.discard)).map((message => `"${message.content.substring(0, 25).replace(/\n/g, '\\n').trim()}..."`)); |
|
messagesClone.forEach((message => message.discard = message.discard || mergedLogs.includes(message) && ![ lastUser ].includes(message))); |
|
} |
|
const prompt = messagesClone.map(((message, idx) => { |
|
if (message.merged || message.discard) { |
|
return ''; |
|
} |
|
if (message.content.length < 1) { |
|
return message.content; |
|
} |
|
let spacing = ''; |
|
|
|
if (Config.Settings.xmlPlot) { |
|
idx > 0 && (spacing = '\n\n'); |
|
const prefix = message.customname ? message.role + ': <customname>' + message.name + '</customname>: ' : 'system' !== message.role || message.name ? Replacements[message.name || message.role] + ': ' : 'xmlPlot: ' + Replacements[message.role]; |
|
return `${spacing}${prefix}${message.customname ? '<reply>\n' + message.content.trim() + '\n</reply>' : message.content}`; |
|
} else { |
|
|
|
idx > 0 && (spacing = systemMessages.includes(message) ? '\n' : '\n\n'); |
|
const prefix = message.customname ? message.name + ': ' : 'system' !== message.role || message.name ? Replacements[message.name || message.role] + ': ' : '' + Replacements[message.role]; |
|
return `${spacing}${message.strip ? '' : prefix}${'system' === message.role ? message.content : message.content.trim()}`; |
|
} |
|
})); |
|
return { |
|
prompt: prompt.join('').trim(), |
|
systems |
|
}; |
|
})(messages, type); |
|
console.log(`${apiKey ? api_model : model} [[2m${type}[0m]${!retryRegen && systems.length > 0 ? ' ' + systems.join(' [33m/[0m ') : ''}`); |
|
'R' !== type || prompt || (prompt = '...regen...'); |
|
|
|
prompt = Config.Settings.xmlPlot ? xmlPlot(prompt) : genericFixes(prompt); |
|
!apiKey && Config.Settings.FullColon && (prompt = prompt.replace(/(?<=\n\n(H(?:uman)?|A(?:ssistant)?)):[ ]?/g, ': ')); |
|
Config.Settings.padtxt && (prompt = padtxt(prompt)); |
|
|
|
Logger?.write(`\n\n-------\n[${(new Date).toLocaleString()}]\n####### PROMPT (${type}):\n${prompt}\n--\n####### [Tokens: ${tokens}] REPLY:\n`); |
|
retryRegen || (fetchAPI = await (async (signal, model, prompt, temperature, type) => { |
|
|
|
if (apiKey) { |
|
const res = await fetch(`${Config.api_rProxy ? Config.api_rProxy : 'https://api.anthropic.com'}/v1/complete`, { |
|
method: 'POST', |
|
signal, |
|
headers: { |
|
'Content-Type': 'application/json', |
|
'x-api-key': apiKey, |
|
'anthropic-version': '2023-06-01' |
|
}, |
|
body: JSON.stringify({ |
|
model: api_model, |
|
max_tokens_to_sample: api_max_tokens, |
|
stream: true, |
|
prompt, |
|
temperature |
|
}), |
|
}); |
|
await checkResErr(res); |
|
return res; |
|
} |
|
|
|
const attachments = []; |
|
if (Config.Settings.PromptExperiments) { |
|
let splitedprompt = prompt.split('\n\nPlainPrompt:'); |
|
prompt = splitedprompt[0]; |
|
attachments.push({ |
|
extracted_content: (prompt), |
|
file_name: 'paste.txt', |
|
file_size: Buffer.from(prompt).byteLength, |
|
file_type: 'txt' |
|
}); |
|
prompt = 'r' === type ? Config.PromptExperimentFirst : Config.PromptExperimentNext; |
|
splitedprompt.length > 1 && (prompt += splitedprompt[1]); |
|
} |
|
let res; |
|
const body = { |
|
completion: { |
|
...Config.Settings.PassParams && { |
|
temperature |
|
}, |
|
prompt: prompt || '', |
|
timezone: AI.zone(), |
|
model: model || AI.mdl() |
|
}, |
|
organization_uuid: uuidOrg, |
|
conversation_uuid: Conversation.uuid, |
|
text: prompt, |
|
attachments |
|
}; |
|
let headers = { |
|
...AI.hdr(Conversation.uuid || ''), |
|
Accept: 'text/event-stream', |
|
Cookie: getCookies() |
|
}; |
|
res = await (Config.Settings.Superfetch ? Superfetch : fetch)(Config.rProxy + '/api/append_message', { |
|
stream: true, |
|
signal, |
|
method: 'POST', |
|
body: JSON.stringify(body), |
|
headers |
|
}); |
|
updateParams(res); |
|
await checkResErr(res, CookieChanger); |
|
return res; |
|
})(signal, model, prompt, temperature, type)); |
|
const response = Writable.toWeb(res); |
|
clewdStream = new ClewdStream({ |
|
config: { |
|
...Config, |
|
Settings: { |
|
...Config.Settings, |
|
Superfetch: apiKey ? false : Config.Settings.Superfetch |
|
} |
|
}, |
|
version: Main, |
|
minSize: Config.BufferSize, |
|
model, |
|
streaming: body.stream, |
|
abortControl, |
|
source: fetchAPI |
|
}, Logger); |
|
titleTimer = setInterval((() => setTitle('recv ' + bytesToSize(clewdStream.size))), 300); |
|
(!apiKey && Config.Settings.Superfetch) ? await Readable.toWeb(fetchAPI.body).pipeThrough(clewdStream).pipeTo(response) : await fetchAPI.body.pipeThrough(clewdStream).pipeTo(response); |
|
} catch (err) { |
|
if ('AbortError' === err.name) { |
|
res.end(); |
|
} else { |
|
err.planned || console.error('[33mClewd:[0m\n%o', err); |
|
res.json({ |
|
error: { |
|
message: 'clewd: ' + (err.message || err.name || err.type), |
|
type: err.type || err.name || err.code, |
|
param: null, |
|
code: err.code || 500 |
|
} |
|
}); |
|
} |
|
} |
|
clearInterval(titleTimer); |
|
if (clewdStream) { |
|
clewdStream.censored && console.warn('[33mlikely your account is hard-censored[0m'); |
|
prevImpersonated = clewdStream.impersonated; |
|
setTitle('ok ' + bytesToSize(clewdStream.size)); |
|
429 == fetchAPI.status ? console.log(`[35mExceeded limit![0m\n`) : console.log(`${200 == fetchAPI.status ? '[32m' : '[33m'}${fetchAPI.status}![0m\n`); |
|
clewdStream.empty(); |
|
} |
|
if (!apiKey) { |
|
await deleteChat(Conversation.uuid); |
|
|
|
changeflag += 1; |
|
if (Config.CookieArray?.length > 0 && (429 == fetchAPI.status || Config.Cookiecounter && changeflag >= Config.Cookiecounter)) { |
|
changeflag = 0; |
|
CookieChanger.emit('ChangeCookie'); |
|
} |
|
|
|
} |
|
})); |
|
})(req, res); |
|
break; |
|
|
|
case '/v1/complete': |
|
res.json({ |
|
error: { |
|
message: 'clewd: Set "Chat Completion" to OpenAI instead of Claude. Enable "External" models aswell' |
|
} |
|
}); |
|
break; |
|
|
|
default: |
|
!['/', '/v1', '/favicon.ico'].includes(req.url) && (console.log('unknown request: ' + req.url)); |
|
res.writeHead(200, {'Content-Type': 'text/html'}); |
|
res.write(`<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8">\n<script>\nfunction copyToClipboard(text) {\n var textarea = document.createElement("textarea");\n textarea.textContent = text;\n textarea.style.position = "fixed";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand("copy");\n } catch (ex) {\n console.warn("Copy to clipboard failed.", ex);\n return false;\n } finally {\n document.body.removeChild(textarea);\n }\n}\nfunction copyLink(event) {\n event.preventDefault();\n const url = new URL(window.location.href);\n const link = url.protocol + '//' + url.host + '/v1';\n copyToClipboard(link);\n alert('链接已复制: ' + link);\n}\n</script>\n</head>\n<body>\n${Main}<br/><br/>完全开源、免费且禁止商用<br/><br/>反向代理: <a href="v1" onclick="copyLink(event)">点击复制链接</a><br/>填入OpenAI API反向代理并选择OpenAI分类中的claude-2模型(酒馆需打开Show "External" models)<br/><br/>教程与FAQ: <a href="https://rentry.org/teralomaniac_clewd" target="FAQ">https://rentry.org/teralomaniac_clewd</a>\n</body>\n</html>`); |
|
res.end(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
})); |
|
|
|
!async function() { |
|
await (async () => { |
|
if (exists(ConfigPath)) { |
|
const userConfig = require(ConfigPath), validConfigs = Object.keys(Config), parsedConfigs = Object.keys(userConfig), parsedSettings = Object.keys(userConfig.Settings), invalidConfigs = parsedConfigs.filter((config => !validConfigs.includes(config))), validSettings = Object.keys(Config.Settings); |
|
UnknownSettings = parsedSettings.filter((setting => !validSettings.includes(setting))); |
|
invalidConfigs.forEach((config => { |
|
console.warn(`unknown config in config.js: [33m${config}[0m`); |
|
})); |
|
UnknownSettings.forEach((setting => { |
|
console.warn(`unknown setting in config.js: [33mSettings.${setting}[0m`); |
|
})); |
|
const missingConfigs = validConfigs.filter((config => !parsedConfigs.includes(config))), missingSettings = validSettings.filter((config => !parsedSettings.includes(config))); |
|
missingConfigs.forEach((config => { |
|
console.warn(`adding missing config in config.js: [33m${config}[0m`); |
|
userConfig[config] = Config[config]; |
|
})); |
|
missingSettings.forEach((setting => { |
|
console.warn(`adding missing setting in config.js: [33mSettings.${setting}[0m`); |
|
userConfig.Settings[setting] = Config.Settings[setting]; |
|
})); |
|
ChangedSettings = parsedSettings.filter((setting => Config.Settings[setting] !== userConfig.Settings[setting])); |
|
(missingConfigs.length > 0 || missingSettings.length > 0) && await writeSettings(userConfig); |
|
userConfig.Settings.LogMessages && (Logger = createWriteStream(LogPath)); |
|
Config = { |
|
...Config, |
|
...userConfig |
|
}; |
|
} else { |
|
Config.Cookie = 'SET YOUR COOKIE HERE'; |
|
writeSettings(Config, true); |
|
} |
|
})(); |
|
|
|
for (let key in Config) { |
|
if (key === 'Settings') { |
|
for (let setting in Config.Settings) { |
|
Config.Settings[setting] = convertToType(process.env[setting]) ?? Config.Settings[setting]; |
|
} |
|
} else { |
|
Config[key] = key === 'CookieArray' ? (process.env[key]?.split(',')?.map(x => x.replace(/[\[\]"\s]/g, '')) ?? Config[key]) : (convertToType(process.env[key]) ?? Config[key]); |
|
} |
|
} |
|
Config.rProxy = Config.rProxy.endsWith('/') ? Config.rProxy.slice(0, -1) : Config.rProxy || AI.end(); |
|
Config.CookieArray = [...new Set(Config.CookieArray)]; |
|
!process.env.Cookie && !process.env.CookieArray && writeSettings(Config); |
|
currentIndex = Config.CookieIndex > 0 ? Config.CookieIndex - 1 : Config.Cookiecounter >= 0 ? Math.floor(Math.random() * Config.CookieArray.length) : 0; |
|
|
|
Proxy.listen(Config.Port, Config.Ip, onListen); |
|
Proxy.on('error', (err => { |
|
console.error('Proxy error\n%o', err); |
|
})); |
|
}(); |
|
|
|
const cleanup = async () => { |
|
console.log('cleaning...'); |
|
try { |
|
await deleteChat(Conversation.uuid); |
|
Logger?.close(); |
|
} catch (err) {} |
|
process.exit(); |
|
}; |
|
|
|
process.on('SIGHUP', cleanup); |
|
|
|
process.on('SIGTERM', cleanup); |
|
|
|
process.on('SIGINT', cleanup); |
|
|
|
process.on('exit', (async () => { |
|
console.log('exiting...'); |
|
})); |
|
|