refactor: 用 zod parse 环境变量

This commit is contained in:
Clansty 2024-01-15 17:58:48 +08:00
parent df9fd35a32
commit 6b80afca58
No known key found for this signature in database
GPG Key ID: 3A6BE8BAF2EDE134
14 changed files with 142 additions and 76 deletions

View File

@ -48,7 +48,8 @@
"telegram": "^2.19.10",
"tmp-promise": "^3.0.3",
"undici": "^6.3.0",
"zincsearch-node": "^2.1.0"
"zincsearch-node": "^2.1.0",
"zod": "^3.22.4"
},
"engines": {
"node": "^14.13.1 || >=16.0.0"

View File

@ -80,6 +80,9 @@ dependencies:
zincsearch-node:
specifier: ^2.1.0
version: 2.1.1(undici@6.3.0)
zod:
specifier: ^3.22.4
version: 3.22.4
devDependencies:
'@types/cli-progress':
@ -3721,6 +3724,10 @@ packages:
undici: 6.3.0
dev: false
/zod@3.22.4:
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
dev: false
'@github.com/Clansty/quote-api/archive/37a0e48a434b94bb04c04c7d86d9f0d2295df869.tar.gz':
resolution: {tarball: https://github.com/Clansty/quote-api/archive/37a0e48a434b94bb04c04c7d86d9f0d2295df869.tar.gz}
name: quote-api

View File

@ -20,8 +20,9 @@ import { Converter, Image, rand2uuid } from 'icqq/lib/message';
import { randomBytes } from 'crypto';
import { escapeXml, gzip, timestamp } from 'icqq/lib/common';
import { pb } from 'icqq/lib/core';
import env from '../models/env';
const LOG_LEVEL: LogLevel = process.env.LOG_LEVEL as LogLevel || 'warn';
const LOG_LEVEL: LogLevel = env.OICQ_LOG_LEVEL;
type MessageHandler = (event: PrivateMessageEvent | GroupMessageEvent) => Promise<boolean | void>
@ -123,10 +124,10 @@ export default class OicqClient extends Client {
platform: params.platform,
data_dir: dataPath(params.uin.toString()),
log_level: LOG_LEVEL,
ffmpeg_path: process.env.FFMPEG_PATH,
ffprobe_path: process.env.FFPROBE_PATH,
sign_api_addr: params.signApi || process.env.SIGN_API,
ver: params.signVer || process.env.SIGN_VER,
ffmpeg_path: env.FFMPEG_PATH,
ffprobe_path: env.FFPROBE_PATH,
sign_api_addr: params.signApi || env.SIGN_API,
ver: params.signVer || env.SIGN_VER,
}, params.signDockerId);
client.on('system.login.device', loginDeviceHandler);
client.on('system.login.slider', loginSliderHandler);

View File

@ -15,6 +15,7 @@ import { BigInteger } from 'big-integer';
import { EditMessageParams, IterMessagesParams } from 'telegram/client/messages';
import { PromisedNetSockets, PromisedWebSockets } from 'telegram/extensions';
import { ConnectionTCPFull, ConnectionTCPObfuscated } from 'telegram/network';
import env from '../models/env';
type MessageHandler = (message: Api.Message) => Promise<boolean | void>;
type ServiceMessageHandler = (message: Api.MessageService) => Promise<boolean | void>;
@ -41,24 +42,24 @@ export default class Telegram {
private constructor(appName: string, sessionId?: number) {
this.client = new TelegramClient(
new TelegramSession(sessionId),
parseInt(process.env.TG_API_ID),
process.env.TG_API_HASH,
env.TG_API_ID,
env.TG_API_HASH,
{
connectionRetries: 20,
langCode: 'zh',
deviceModel: `${appName} On ${os.hostname()}`,
appVersion: 'rainbowcat',
useIPV6: !!process.env.IPV6,
proxy: process.env.PROXY_IP ? {
useIPV6: !!env.IPV6,
proxy: env.PROXY_IP ? {
socksType: 5,
ip: process.env.PROXY_IP,
port: parseInt(process.env.PROXY_PORT),
...(process.env.PROXY_USERNAME && { username: process.env.PROXY_USERNAME }),
...(process.env.PROXY_PASSWORD && { password: process.env.PROXY_PASSWORD }),
ip: env.PROXY_IP,
port: env.PROXY_PORT,
username: env.PROXY_USERNAME,
password: env.PROXY_PASSWORD,
} : undefined,
autoReconnect: true,
networkSocket: process.env.TG_CONNECTION === 'websocket' ? PromisedWebSockets : PromisedNetSockets,
connection: process.env.TG_CONNECTION === 'websocket' ? ConnectionTCPObfuscated : ConnectionTCPFull,
networkSocket: env.TG_CONNECTION === 'websocket' ? PromisedWebSockets : PromisedNetSockets,
connection: env.TG_CONNECTION === 'websocket' ? ConnectionTCPObfuscated : ConnectionTCPFull,
},
);
// this.client.logger.setLevel(LogLevel.WARN);

View File

@ -12,6 +12,7 @@ import BigInteger from 'big-integer';
import { getAvatarUrl } from '../utils/urls';
import convert from '../helpers/convert';
import { Pair } from '../models/Pair';
import env from '../models/env';
export default class {
private readonly log: Logger;
@ -280,7 +281,7 @@ export default class {
throw new Error('不支持的消息类型');
}
const res = await quotly({
botToken: process.env.TG_BOT_TOKEN,
botToken: env.TG_BOT_TOKEN,
type,
format,
backgroundColor,

View File

@ -9,6 +9,7 @@ import { WorkMode } from '../types/definitions';
import OicqClient from '../client/OicqClient';
import { md5Hex } from '../utils/hashing';
import Instance from '../models/Instance';
import env from '../models/env';
export default class SetupController {
private readonly setupService: SetupService;
@ -106,7 +107,7 @@ export default class SetupController {
let signApi: string;
if (!process.env.SIGN_API) {
if (!env.SIGN_API) {
signApi = await this.setupService.waitForOwnerInput('请输入签名服务器地址', [
[Button.text('不需要签名服务器', true, true)],
]);
@ -115,7 +116,7 @@ export default class SetupController {
let signVer: string;
if (signApi && !process.env.SIGN_VER) {
if (signApi && !env.SIGN_VER) {
signVer = await this.setupService.waitForOwnerInput('请输入签名服务器版本', [
[Button.text('8.9.63', true, true),
Button.text('8.9.68', true, true)],

View File

@ -1,8 +1,9 @@
import { spawn } from 'child_process';
import env from '../models/env';
export default function tgsToGif(tgsPath: string) {
return new Promise(resolve => {
spawn(process.env.TGS_TO_GIF || 'tgs_to_gif', [tgsPath]).on('exit', () => {
spawn(env.TGS_TO_GIF, [tgsPath]).on('exit', () => {
resolve(tgsPath + '.gif');
});
});

View File

@ -19,6 +19,7 @@ import axios from 'axios';
import { CustomFile } from 'telegram/client/uploads';
import fsP from 'fs/promises';
import { file } from 'tmp-promise';
import env from '../models/env';
export default class {
private readonly log: Logger;
@ -197,21 +198,26 @@ export default class {
}
break;
case 'forward':
try {
const messages = await this.pair.qq.getForwardMsg(result.resId);
const hash = md5Hex(result.resId);
text += `转发的消息记录 ${process.env.CRV_API}/?hash=${hash}`;
// 传到 Cloudflare
axios.post(`${process.env.CRV_API}/add`, {
auth: process.env.CRV_KEY,
key: hash,
data: messages,
})
.then(data => this.log.trace('上传消息记录到 Cloudflare', data.data))
.catch(e => this.log.error('上传消息记录到 Cloudflare 失败', e));
if (env.CRV_API) {
try {
const messages = await this.pair.qq.getForwardMsg(result.resId);
const hash = md5Hex(result.resId);
text += `转发的消息记录 ${env.CRV_API}/?hash=${hash}`;
// 传到 Cloudflare
axios.post(`${env.CRV_API}/add`, {
auth: env.CRV_KEY,
key: hash,
data: messages,
})
.then(data => this.log.trace('上传消息记录到 Cloudflare', data.data))
.catch(e => this.log.error('上传消息记录到 Cloudflare 失败', e));
}
catch (e) {
text += '[转发多条消息(无法获取)]';
}
}
catch (e) {
text += '[转发多条消息(无法获取)]';
else {
text += '[转发多条消息]';
}
break;
}
@ -255,11 +261,11 @@ export default class {
ext: 'tgs',
mime: 'application/x-tgsticker',
} : await fileTypeFromFile(filePath);
if(!type){
if (!type) {
type = {
ext: 'bin',
mime: 'application/octet-stream',
}
};
}
let media: Api.TypeInputMedia;
if (['.webp', '.tgs'].includes(path.extname(filePath))) {

View File

@ -1,7 +1,6 @@
import path from 'path';
const DATA_DIR = process.env.DATA_DIR || path.resolve('./data');
import env from '../models/env';
// Wrap of path.join, add base DATA_DIR
export default (...paths: string[]) =>
path.join(DATA_DIR, ...paths);
path.join(env.DATA_DIR, ...paths);

View File

@ -24,6 +24,7 @@ import StatusReportController from '../controllers/StatusReportController';
import HugController from '../controllers/HugController';
import QuotLyController from '../controllers/QuotLyController';
import MiraiSkipFilterController from '../controllers/MiraiSkipFilterController';
import env from './env';
export default class Instance {
private _owner = 0;
@ -96,7 +97,7 @@ export default class Instance {
this.tgBot = await Telegram.connect(this._botSessionId);
}
else {
const token = this.id === 0 ? process.env.TG_BOT_TOKEN : botToken;
const token = this.id === 0 ? env.TG_BOT_TOKEN : botToken;
if (!token) {
throw new Error('botToken 未指定');
}

View File

@ -2,6 +2,7 @@ import { MemorySession } from 'telegram/sessions';
import db from './db';
import { AuthKey } from 'telegram/crypto/AuthKey';
import { getLogger, Logger } from 'log4js';
import env from './env';
const PASS = () => 0;
@ -19,19 +20,19 @@ export default class TelegramSession extends MemorySession {
async load() {
this.log.trace('load');
if (process.env.TG_INITIAL_DCID) {
this._dcId = Number(process.env.TG_INITIAL_DCID);
if (env.TG_INITIAL_DCID) {
this._dcId = env.TG_INITIAL_DCID;
}
if (process.env.TG_INITIAL_SERVER) {
this._serverAddress = process.env.TG_INITIAL_SERVER;
if (env.TG_INITIAL_SERVER) {
this._serverAddress = env.TG_INITIAL_SERVER;
}
if (!this._dbId) {
this.log.debug('Session 不存在,创建');
// 创建并返回
const newDbEntry = await db.session.create({
data: {
dcId: process.env.TG_INITIAL_DCID ? Number(process.env.TG_INITIAL_DCID) : null,
serverAddress: process.env.TG_INITIAL_SERVER,
dcId: env.TG_INITIAL_DCID,
serverAddress: env.TG_INITIAL_SERVER,
},
});
this._dbId = newDbEntry.id;

39
src/models/env.ts Normal file
View File

@ -0,0 +1,39 @@
import z from 'zod';
import path from 'path';
const configParsed = z.object({
DATA_DIR: z.string().default(path.resolve('./data')),
OICQ_LOG_LEVEL: z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'mark', 'off']).default('warn'),
FFMPEG_PATH: z.string().optional(),
FFPROBE_PATH: z.string().optional(),
SIGN_API: z.string().url().optional(),
SIGN_VER: z.string().optional(),
TG_API_ID: z.string().regex(/^\d+$/).transform(Number),
TG_API_HASH: z.string(),
TG_BOT_TOKEN: z.string(),
TG_CONNECTION: z.enum(['websocket', 'tcp']).default('tcp'),
TG_INITIAL_DCID: z.string().regex(/^\d+$/).transform(Number).optional(),
TG_INITIAL_SERVER: z.string().ip().optional(),
IPV6: z.string().transform((v) => ['true', '1', 'yes'].includes(v.toLowerCase())).default('false'),
PROXY_IP: z.string().ip().optional(),
PROXY_PORT: z.string().regex(/^\d+$/).transform(Number).optional(),
PROXY_USERNAME: z.string().optional(),
PROXY_PASSWORD: z.string().optional(),
TGS_TO_GIF: z.string().default('tgs_to_gif'),
CRV_API: z.string().url().optional(),
CRV_KEY: z.string().optional(),
ZINC_URL: z.string().url().optional(),
ZINC_USERNAME: z.string().optional(),
ZINC_PASSWORD: z.string().optional(),
BAIDU_APP_ID: z.string().optional(),
BAIDU_API_KEY: z.string().optional(),
BAIDU_SECRET_KEY: z.string().optional(),
DISABLE_FILE_UPLOAD_TIP: z.string().transform((v) => ['true', '1', 'yes'].includes(v.toLowerCase())).default('false'),
}).safeParse(process.env);
if (!configParsed.success) {
console.error('环境变量解析错误:', (configParsed as any).error);
process.exit(1);
}
export default configParsed.data;

View File

@ -41,6 +41,7 @@ import random from '../utils/random';
import { escapeXml } from 'icqq/lib/common';
import Docker from 'dockerode';
import ReplyKeyboardHide = Api.ReplyKeyboardHide;
import env from '../models/env';
const NOT_CHAINABLE_ELEMENTS = ['flash', 'record', 'video', 'location', 'share', 'json', 'xml', 'poke'];
@ -55,18 +56,18 @@ export default class ForwardService {
private readonly tgBot: Telegram,
private readonly oicq: OicqClient) {
this.log = getLogger(`ForwardService - ${instance.id}`);
if (process.env.ZINC_URL) {
if (env.ZINC_URL) {
this.zincSearch = new ZincSearch({
url: process.env.ZINC_URL,
user: process.env.ZINC_USERNAME,
password: process.env.ZINC_PASSWORD,
url: env.ZINC_URL,
user: env.ZINC_USERNAME,
password: env.ZINC_PASSWORD,
});
}
if (process.env.BAIDU_APP_ID) {
if (env.BAIDU_APP_ID) {
this.speechClient = new AipSpeechClient(
process.env.BAIDU_APP_ID,
process.env.BAIDU_API_KEY,
process.env.BAIDU_SECRET_KEY,
env.BAIDU_APP_ID,
env.BAIDU_API_KEY,
env.BAIDU_SECRET_KEY,
);
}
if (oicq.signDockerId) {
@ -110,22 +111,27 @@ export default class ForwardService {
}
};
const useForward = async (resId: string) => {
try {
const messages = await pair.qq.getForwardMsg(resId);
message = helper.generateForwardBrief(messages);
const hash = md5Hex(resId);
buttons.push(Button.url('📃查看', `${process.env.CRV_API}/?hash=${hash}`));
// 传到 Cloudflare
axios.post(`${process.env.CRV_API}/add`, {
auth: process.env.CRV_KEY,
key: hash,
data: messages,
})
.then(data => this.log.trace('上传消息记录到 Cloudflare', data.data))
.catch(e => this.log.error('上传消息记录到 Cloudflare 失败', e));
if(env.CRV_API) {
try {
const messages = await pair.qq.getForwardMsg(resId);
message = helper.generateForwardBrief(messages);
const hash = md5Hex(resId);
buttons.push(Button.url('📃查看', `${env.CRV_API}/?hash=${hash}`));
// 传到 Cloudflare
axios.post(`${env.CRV_API}/add`, {
auth: env.CRV_KEY,
key: hash,
data: messages,
})
.then(data => this.log.trace('上传消息记录到 Cloudflare', data.data))
.catch(e => this.log.error('上传消息记录到 Cloudflare 失败', e));
}
catch (e) {
message = '[<i>转发多条消息(无法获取)</i>]';
}
}
catch (e) {
message = '[<i>转发多条消息(无法获取)</i>]';
else {
message = '[<i>转发多条消息(未配置</i>]';
}
};
for (const elem of event.message) {
@ -541,7 +547,7 @@ export default class ForwardService {
}
}
brief += '[文件]';
if (process.env.DISABLE_FILE_UPLOAD_TIP) {
if (env.DISABLE_FILE_UPLOAD_TIP) {
chain = [];
}
}
@ -713,10 +719,10 @@ export default class ForwardService {
nick: string,
}) {
if (!this.zincSearch) return;
const existsReq = await fetch(process.env.ZINC_URL + `/api/index/q2tg-${pairId}`, {
const existsReq = await fetch(env.ZINC_URL + `/api/index/q2tg-${pairId}`, {
method: 'HEAD',
headers: {
Authorization: 'Basic ' + Buffer.from(process.env.ZINC_USERNAME + ':' + process.env.ZINC_PASSWORD).toString('base64'),
Authorization: 'Basic ' + Buffer.from(env.ZINC_USERNAME + ':' + env.ZINC_PASSWORD).toString('base64'),
},
});
if (existsReq.status === 404) {

View File

@ -11,6 +11,7 @@ import db from '../models/db';
import { Friend, Group } from 'icqq';
import { format } from 'date-and-time';
import ZincSearch from 'zincsearch-node';
import env from '../models/env';
export default class InChatCommandsService {
private readonly log: Logger;
@ -20,11 +21,11 @@ export default class InChatCommandsService {
private readonly tgBot: Telegram,
private readonly oicq: OicqClient) {
this.log = getLogger(`InChatCommandsService - ${instance.id}`);
if (process.env.ZINC_URL) {
if (env.ZINC_URL) {
this.zincSearch = new ZincSearch({
url: process.env.ZINC_URL,
user: process.env.ZINC_USERNAME,
password: process.env.ZINC_PASSWORD,
url: env.ZINC_URL,
user: env.ZINC_USERNAME,
password: env.ZINC_PASSWORD,
});
}
}