server.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. #!/usr/bin/env node
  2. import crypto from 'crypto';
  3. import fastify from 'fastify';
  4. import cors from '@fastify/cors';
  5. import { FastifySSEPlugin } from '@waylaidwanderer/fastify-sse-v2';
  6. import fs from 'fs';
  7. import { pathToFileURL } from 'url';
  8. import { KeyvFile } from 'keyv-file';
  9. import ChatGPTClient from '../src/ChatGPTClient.js';
  10. import ChatGPTBrowserClient from '../src/ChatGPTBrowserClient.js';
  11. import BingAIClient from '../src/BingAIClient.js';
  12. import dotenv from 'dotenv';
  13. dotenv.config();
  14. const arg = process.argv.find(_arg => _arg.startsWith('--settings'));
  15. const path = arg?.split('=')[1] ?? './settings.js';
  16. const iv = '2624750004598718';
  17. /**
  18. * 加密函数
  19. * @param str
  20. * @returns {string}
  21. */
  22. const encrypt = function (str) {
  23. // 创建一个加密对象,使用啥方式加密
  24. const crt = crypto.createCipheriv("aes-128-cbc", secret, iv);
  25. // 进行加密,使用输入使用啥编码,输出使用啥编码
  26. let res = crt.update(str, 'utf-8', 'hex');
  27. // 最后结果使用啥编码输出
  28. res += crt.final('hex');
  29. return res;
  30. }
  31. /**
  32. * 解密
  33. * @param str
  34. * @returns {string}
  35. */
  36. const decrypt = function (str) {
  37. // 创建一个解密对象,使用啥方式解密
  38. let dct = crypto.createDecipheriv("aes-128-cbc", secret, iv);
  39. let re = dct.update(str, 'hex', 'utf-8');
  40. re += dct.final('utf-8');
  41. return re;
  42. }
  43. let settings;
  44. if (fs.existsSync(path)) {
  45. // get the full path
  46. const fullPath = fs.realpathSync(path);
  47. settings = (await import(pathToFileURL(fullPath).toString())).default;
  48. } else {
  49. if (arg) {
  50. console.error('Error: the file specified by the --settings parameter does not exist.');
  51. } else {
  52. console.error('Error: the settings.js file does not exist.');
  53. }
  54. process.exit(1);
  55. }
  56. if (settings.storageFilePath && !settings.cacheOptions.store) {
  57. // make the directory and file if they don't exist
  58. const dir = settings.storageFilePath.split('/').slice(0, -1).join('/');
  59. if (!fs.existsSync(dir)) {
  60. fs.mkdirSync(dir, { recursive: true });
  61. }
  62. if (!fs.existsSync(settings.storageFilePath)) {
  63. fs.writeFileSync(settings.storageFilePath, '');
  64. }
  65. settings.cacheOptions.store = new KeyvFile({ filename: settings.storageFilePath });
  66. }
  67. const key = settings.apiOptions.encryptKey;
  68. const secret = Buffer.from(key);
  69. console.log(settings.apiOptions,'=======')
  70. console.log(process.env.ENCRYPT_KEY,'=======')
  71. console.log(process.env.OPENAI_API_KEY,'=======')
  72. const clientToUse = settings.apiOptions?.clientToUse || settings.clientToUse || 'chatgpt';
  73. const perMessageClientOptionsWhitelist = settings.apiOptions?.perMessageClientOptionsWhitelist || null;
  74. const server = fastify();
  75. await server.register(FastifySSEPlugin);
  76. await server.register(cors, {
  77. origin: '*',
  78. });
  79. server.get('/ping', () => Date.now().toString());
  80. server.post('/conversation', async (request, reply) => {
  81. // 解密
  82. let body = request.body || {};
  83. if(settings.apiOptions.openEncrypt){
  84. body = request.body && request.body.data && JSON.parse(decrypt(request.body.data)) || {};
  85. }
  86. const abortController = new AbortController();
  87. reply.raw.on('close', () => {
  88. if (abortController.signal.aborted === false) {
  89. abortController.abort();
  90. }
  91. });
  92. let onProgress;
  93. if (body.stream === true) {
  94. onProgress = (token) => {
  95. if (settings.apiOptions?.debug) {
  96. console.debug(token);
  97. }
  98. if (token !== '[DONE]') {
  99. reply.sse({ id: '', data: JSON.stringify(token) });
  100. }
  101. };
  102. } else {
  103. onProgress = null;
  104. }
  105. let result;
  106. let error;
  107. try {
  108. if (!body.message) {
  109. const invalidError = new Error();
  110. invalidError.data = {
  111. code: 400,
  112. message: 'The message parameter is required.',
  113. };
  114. // noinspection ExceptionCaughtLocallyJS
  115. throw invalidError;
  116. }
  117. let clientToUseForMessage = clientToUse;
  118. const clientOptions = filterClientOptions(body.clientOptions, clientToUseForMessage);
  119. if (clientOptions && clientOptions.clientToUse) {
  120. clientToUseForMessage = clientOptions.clientToUse;
  121. delete clientOptions.clientToUse;
  122. }
  123. let { shouldGenerateTitle } = body;
  124. if (typeof shouldGenerateTitle !== 'boolean') {
  125. shouldGenerateTitle = settings.apiOptions?.generateTitles || false;
  126. }
  127. const messageClient = getClient(clientToUseForMessage);
  128. result = await messageClient.sendMessage(body.message, {
  129. jailbreakConversationId: body.jailbreakConversationId,
  130. conversationId: body.conversationId ? body.conversationId.toString() : undefined,
  131. parentMessageId: body.parentMessageId ? body.parentMessageId.toString() : undefined,
  132. systemMessage: body.systemMessage,
  133. context: body.context,
  134. conversationSignature: body.conversationSignature,
  135. clientId: body.clientId,
  136. invocationId: body.invocationId,
  137. shouldGenerateTitle, // only used for ChatGPTClient
  138. toneStyle: body.toneStyle,
  139. clientOptions,
  140. onProgress,
  141. abortController,
  142. });
  143. } catch (e) {
  144. error = e;
  145. }
  146. if (result !== undefined) {
  147. if (settings.apiOptions?.debug) {
  148. console.debug(result);
  149. }
  150. if (body.stream === true) {
  151. reply.sse({ event: 'result', id: '', data: JSON.stringify(result) });
  152. reply.sse({ id: '', data: '[DONE]' });
  153. await nextTick();
  154. return reply.raw.end();
  155. }
  156. // 加密返回
  157. if(settings.apiOptions.openEncrypt){
  158. result = { data: encrypt(JSON.stringify(result)) }
  159. }
  160. return reply.send(result);
  161. }
  162. const code = error?.data?.code || (error.name === 'UnauthorizedRequest' ? 401 : 503);
  163. if (code === 503) {
  164. console.error(error);
  165. } else if (settings.apiOptions?.debug) {
  166. console.debug(error);
  167. }
  168. const message = error?.data?.message || error?.message || `There was an error communicating with ${clientToUse === 'bing' ? 'Bing' : 'ChatGPT'}.`;
  169. if (body.stream === true) {
  170. reply.sse({
  171. id: '',
  172. event: 'error',
  173. data: JSON.stringify({
  174. code,
  175. error: message,
  176. }),
  177. });
  178. await nextTick();
  179. return reply.raw.end();
  180. }
  181. return reply.code(code).send({ error: message });
  182. });
  183. server.listen({
  184. port: settings.apiOptions?.port || settings.port || 3000,
  185. host: settings.apiOptions?.host || 'localhost',
  186. }, (error) => {
  187. if (error) {
  188. console.error(error);
  189. process.exit(1);
  190. }
  191. });
  192. function nextTick() {
  193. return new Promise(resolve => setTimeout(resolve, 0));
  194. }
  195. function getClient(clientToUseForMessage) {
  196. switch (clientToUseForMessage) {
  197. case 'bing':
  198. return new BingAIClient({ ...settings.bingAiClient, cache: settings.cacheOptions });
  199. case 'chatgpt-browser':
  200. return new ChatGPTBrowserClient(
  201. settings.chatGptBrowserClient,
  202. settings.cacheOptions,
  203. );
  204. case 'chatgpt':
  205. return new ChatGPTClient(
  206. settings.openaiApiKey || settings.chatGptClient.openaiApiKey,
  207. settings.chatGptClient,
  208. settings.cacheOptions,
  209. );
  210. default:
  211. throw new Error(`Invalid clientToUse: ${clientToUseForMessage}`);
  212. }
  213. }
  214. /**
  215. * Filter objects to only include whitelisted properties set in
  216. * `settings.js` > `apiOptions.perMessageClientOptionsWhitelist`.
  217. * Returns original object if no whitelist is set.
  218. * @param {*} inputOptions
  219. * @param clientToUseForMessage
  220. */
  221. function filterClientOptions(inputOptions, clientToUseForMessage) {
  222. if (!inputOptions || !perMessageClientOptionsWhitelist) {
  223. return null;
  224. }
  225. // If inputOptions.clientToUse is set and is in the whitelist, use it instead of the default
  226. if (
  227. perMessageClientOptionsWhitelist.validClientsToUse
  228. && inputOptions.clientToUse
  229. && perMessageClientOptionsWhitelist.validClientsToUse.includes(inputOptions.clientToUse)
  230. ) {
  231. clientToUseForMessage = inputOptions.clientToUse;
  232. } else {
  233. inputOptions.clientToUse = clientToUseForMessage;
  234. }
  235. const whitelist = perMessageClientOptionsWhitelist[clientToUseForMessage];
  236. if (!whitelist) {
  237. // No whitelist, return all options
  238. return inputOptions;
  239. }
  240. const outputOptions = {
  241. clientToUse: clientToUseForMessage,
  242. };
  243. for (const property of Object.keys(inputOptions)) {
  244. const allowed = whitelist.includes(property);
  245. if (!allowed && typeof inputOptions[property] === 'object') {
  246. // Check for nested properties
  247. for (const nestedProp of Object.keys(inputOptions[property])) {
  248. const nestedAllowed = whitelist.includes(`${property}.${nestedProp}`);
  249. if (nestedAllowed) {
  250. outputOptions[property] = outputOptions[property] || {};
  251. outputOptions[property][nestedProp] = inputOptions[property][nestedProp];
  252. }
  253. }
  254. continue;
  255. }
  256. // Copy allowed properties to outputOptions
  257. if (allowed) {
  258. outputOptions[property] = inputOptions[property];
  259. }
  260. }
  261. return outputOptions;
  262. }