123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- #!/usr/bin/env node
- import crypto from 'crypto';
- import fastify from 'fastify';
- import cors from '@fastify/cors';
- import { FastifySSEPlugin } from '@waylaidwanderer/fastify-sse-v2';
- import fs from 'fs';
- import { pathToFileURL } from 'url';
- import { KeyvFile } from 'keyv-file';
- import ChatGPTClient from '../src/ChatGPTClient.js';
- import ChatGPTBrowserClient from '../src/ChatGPTBrowserClient.js';
- import BingAIClient from '../src/BingAIClient.js';
- import dotenv from 'dotenv';
- dotenv.config();
- const arg = process.argv.find(_arg => _arg.startsWith('--settings'));
- const path = arg?.split('=')[1] ?? './settings.js';
- const iv = '2624750004598718';
- /**
- * 加密函数
- * @param str
- * @returns {string}
- */
- const encrypt = function (str) {
- // 创建一个加密对象,使用啥方式加密
- const crt = crypto.createCipheriv("aes-128-cbc", secret, iv);
- // 进行加密,使用输入使用啥编码,输出使用啥编码
- let res = crt.update(str, 'utf-8', 'hex');
- // 最后结果使用啥编码输出
- res += crt.final('hex');
- return res;
- }
- /**
- * 解密
- * @param str
- * @returns {string}
- */
- const decrypt = function (str) {
- // 创建一个解密对象,使用啥方式解密
- let dct = crypto.createDecipheriv("aes-128-cbc", secret, iv);
- let re = dct.update(str, 'hex', 'utf-8');
- re += dct.final('utf-8');
- return re;
- }
- let settings;
- if (fs.existsSync(path)) {
- // get the full path
- const fullPath = fs.realpathSync(path);
- settings = (await import(pathToFileURL(fullPath).toString())).default;
- } else {
- if (arg) {
- console.error('Error: the file specified by the --settings parameter does not exist.');
- } else {
- console.error('Error: the settings.js file does not exist.');
- }
- process.exit(1);
- }
- if (settings.storageFilePath && !settings.cacheOptions.store) {
- // make the directory and file if they don't exist
- const dir = settings.storageFilePath.split('/').slice(0, -1).join('/');
- if (!fs.existsSync(dir)) {
- fs.mkdirSync(dir, { recursive: true });
- }
- if (!fs.existsSync(settings.storageFilePath)) {
- fs.writeFileSync(settings.storageFilePath, '');
- }
- settings.cacheOptions.store = new KeyvFile({ filename: settings.storageFilePath });
- }
- const key = settings.apiOptions.encryptKey;
- const secret = Buffer.from(key);
- console.log(settings.apiOptions,'=======')
- console.log(process.env.ENCRYPT_KEY,'=======')
- console.log(process.env.OPENAI_API_KEY,'=======')
- const clientToUse = settings.apiOptions?.clientToUse || settings.clientToUse || 'chatgpt';
- const perMessageClientOptionsWhitelist = settings.apiOptions?.perMessageClientOptionsWhitelist || null;
- const server = fastify();
- await server.register(FastifySSEPlugin);
- await server.register(cors, {
- origin: '*',
- });
- server.get('/ping', () => Date.now().toString());
- server.post('/conversation', async (request, reply) => {
- // 解密
- let body = request.body || {};
- if(settings.apiOptions.openEncrypt){
- body = request.body && request.body.data && JSON.parse(decrypt(request.body.data)) || {};
- }
-
- const abortController = new AbortController();
- reply.raw.on('close', () => {
- if (abortController.signal.aborted === false) {
- abortController.abort();
- }
- });
- let onProgress;
- if (body.stream === true) {
- onProgress = (token) => {
- if (settings.apiOptions?.debug) {
- console.debug(token);
- }
- if (token !== '[DONE]') {
- reply.sse({ id: '', data: JSON.stringify(token) });
- }
- };
- } else {
- onProgress = null;
- }
- let result;
- let error;
- try {
- if (!body.message) {
- const invalidError = new Error();
- invalidError.data = {
- code: 400,
- message: 'The message parameter is required.',
- };
- // noinspection ExceptionCaughtLocallyJS
- throw invalidError;
- }
- let clientToUseForMessage = clientToUse;
- const clientOptions = filterClientOptions(body.clientOptions, clientToUseForMessage);
- if (clientOptions && clientOptions.clientToUse) {
- clientToUseForMessage = clientOptions.clientToUse;
- delete clientOptions.clientToUse;
- }
- let { shouldGenerateTitle } = body;
- if (typeof shouldGenerateTitle !== 'boolean') {
- shouldGenerateTitle = settings.apiOptions?.generateTitles || false;
- }
- const messageClient = getClient(clientToUseForMessage);
- result = await messageClient.sendMessage(body.message, {
- jailbreakConversationId: body.jailbreakConversationId,
- conversationId: body.conversationId ? body.conversationId.toString() : undefined,
- parentMessageId: body.parentMessageId ? body.parentMessageId.toString() : undefined,
- systemMessage: body.systemMessage,
- context: body.context,
- conversationSignature: body.conversationSignature,
- clientId: body.clientId,
- invocationId: body.invocationId,
- shouldGenerateTitle, // only used for ChatGPTClient
- toneStyle: body.toneStyle,
- clientOptions,
- onProgress,
- abortController,
- });
- } catch (e) {
- error = e;
- }
- if (result !== undefined) {
- if (settings.apiOptions?.debug) {
- console.debug(result);
- }
- if (body.stream === true) {
- reply.sse({ event: 'result', id: '', data: JSON.stringify(result) });
- reply.sse({ id: '', data: '[DONE]' });
- await nextTick();
- return reply.raw.end();
- }
- // 加密返回
- if(settings.apiOptions.openEncrypt){
- result = { data: encrypt(JSON.stringify(result)) }
- }
- return reply.send(result);
- }
- const code = error?.data?.code || (error.name === 'UnauthorizedRequest' ? 401 : 503);
- if (code === 503) {
- console.error(error);
- } else if (settings.apiOptions?.debug) {
- console.debug(error);
- }
- const message = error?.data?.message || error?.message || `There was an error communicating with ${clientToUse === 'bing' ? 'Bing' : 'ChatGPT'}.`;
- if (body.stream === true) {
- reply.sse({
- id: '',
- event: 'error',
- data: JSON.stringify({
- code,
- error: message,
- }),
- });
- await nextTick();
- return reply.raw.end();
- }
- return reply.code(code).send({ error: message });
- });
- server.listen({
- port: settings.apiOptions?.port || settings.port || 3000,
- host: settings.apiOptions?.host || 'localhost',
- }, (error) => {
- if (error) {
- console.error(error);
- process.exit(1);
- }
- });
- function nextTick() {
- return new Promise(resolve => setTimeout(resolve, 0));
- }
- function getClient(clientToUseForMessage) {
- switch (clientToUseForMessage) {
- case 'bing':
- return new BingAIClient({ ...settings.bingAiClient, cache: settings.cacheOptions });
- case 'chatgpt-browser':
- return new ChatGPTBrowserClient(
- settings.chatGptBrowserClient,
- settings.cacheOptions,
- );
- case 'chatgpt':
- return new ChatGPTClient(
- settings.openaiApiKey || settings.chatGptClient.openaiApiKey,
- settings.chatGptClient,
- settings.cacheOptions,
- );
- default:
- throw new Error(`Invalid clientToUse: ${clientToUseForMessage}`);
- }
- }
- /**
- * Filter objects to only include whitelisted properties set in
- * `settings.js` > `apiOptions.perMessageClientOptionsWhitelist`.
- * Returns original object if no whitelist is set.
- * @param {*} inputOptions
- * @param clientToUseForMessage
- */
- function filterClientOptions(inputOptions, clientToUseForMessage) {
- if (!inputOptions || !perMessageClientOptionsWhitelist) {
- return null;
- }
- // If inputOptions.clientToUse is set and is in the whitelist, use it instead of the default
- if (
- perMessageClientOptionsWhitelist.validClientsToUse
- && inputOptions.clientToUse
- && perMessageClientOptionsWhitelist.validClientsToUse.includes(inputOptions.clientToUse)
- ) {
- clientToUseForMessage = inputOptions.clientToUse;
- } else {
- inputOptions.clientToUse = clientToUseForMessage;
- }
- const whitelist = perMessageClientOptionsWhitelist[clientToUseForMessage];
- if (!whitelist) {
- // No whitelist, return all options
- return inputOptions;
- }
- const outputOptions = {
- clientToUse: clientToUseForMessage,
- };
- for (const property of Object.keys(inputOptions)) {
- const allowed = whitelist.includes(property);
- if (!allowed && typeof inputOptions[property] === 'object') {
- // Check for nested properties
- for (const nestedProp of Object.keys(inputOptions[property])) {
- const nestedAllowed = whitelist.includes(`${property}.${nestedProp}`);
- if (nestedAllowed) {
- outputOptions[property] = outputOptions[property] || {};
- outputOptions[property][nestedProp] = inputOptions[property][nestedProp];
- }
- }
- continue;
- }
- // Copy allowed properties to outputOptions
- if (allowed) {
- outputOptions[property] = inputOptions[property];
- }
- }
- return outputOptions;
- }
|