This commit is contained in:
linyuchen
2024-04-15 00:09:08 +08:00
parent 0ef3e38d70
commit 356aba762c
218 changed files with 8465 additions and 5 deletions

View File

@@ -0,0 +1,60 @@
import path from 'node:path';
import fs from 'node:fs';
import os from 'node:os';
import { systemPlatform } from '@/common/utils/system';
export const exePath = process.execPath;
export const pkgInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
let configVersionInfoPath;
if (os.platform() !== 'linux') {
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
} else {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './.config/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
}
if (typeof configVersionInfoPath !== 'string') {
throw new Error('Something went wrong when load QQ info path');
}
export { configVersionInfoPath };
type QQPkgInfo = {
version: string;
buildVersion: string;
platform: string;
eleArch: string;
}
type QQVersionConfigInfo = {
baseVersion: string;
curVersion: string;
prevVersion: string;
onErrorVersions: Array<any>;
buildId: string;
}
let _qqVersionConfigInfo: QQVersionConfigInfo = {
'baseVersion': '9.9.9-22578',
'curVersion': '9.9.9-22578',
'prevVersion': '',
'onErrorVersions': [],
'buildId': '22578'
};
if (fs.existsSync(configVersionInfoPath)) {
_qqVersionConfigInfo = JSON.parse(fs.readFileSync(configVersionInfoPath).toString());
}
export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo;
export const qqPkgInfo: QQPkgInfo = require(pkgInfoPath);
let _appid: string = '537213335'; // 默认为 Windows 平台的 appid
if (systemPlatform === 'linux') {
_appid = '537213710';
}
// todo: mac 平台的 appid
export const appid = _appid;

135
src/common/utils/audio.ts Normal file
View File

@@ -0,0 +1,135 @@
import fs from 'fs';
import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm';
import fsPromise from 'fs/promises';
import { log } from './log';
import path from 'node:path';
import { v4 as uuidv4 } from 'uuid';
import { spawn } from 'node:child_process';
import { getTempDir } from '@/common/utils/file';
let TEMP_DIR = './';
setTimeout(() => {
TEMP_DIR = getTempDir();
}, 100);
export async function encodeSilk(filePath: string) {
function getFileHeader(filePath: string) {
// 定义要读取的字节数
const bytesToRead = 7;
try {
const buffer = fs.readFileSync(filePath, {
encoding: null,
flag: 'r',
});
const fileHeader = buffer.toString('hex', 0, bytesToRead);
return fileHeader;
} catch (err) {
console.error('读取文件错误:', err);
return;
}
}
async function isWavFile(filePath: string) {
return isWav(fs.readFileSync(filePath));
}
async function guessDuration(pttPath: string) {
const pttFileInfo = await fsPromise.stat(pttPath);
let duration = pttFileInfo.size / 1024 / 3; // 3kb/s
duration = Math.floor(duration);
duration = Math.max(1, duration);
log('通过文件大小估算语音的时长:', duration);
return duration;
}
// function verifyDuration(oriDuration: number, guessDuration: number) {
// // 单位都是秒
// if (oriDuration - guessDuration > 10) {
// return guessDuration
// }
// oriDuration = Math.max(1, oriDuration)
// return oriDuration
// }
// async function getAudioSampleRate(filePath: string) {
// try {
// const mm = await import('music-metadata');
// const metadata = await mm.parseFile(filePath);
// log(`${filePath}采样率`, metadata.format.sampleRate);
// return metadata.format.sampleRate;
// } catch (error) {
// log(`${filePath}采样率获取失败`, error.stack);
// // console.error(error);
// }
// }
try {
const pttPath = path.join(TEMP_DIR, uuidv4());
if (getFileHeader(filePath) !== '02232153494c4b') {
log(`语音文件${filePath}需要转换成silk`);
const _isWav = await isWavFile(filePath);
const pcmPath = pttPath + '.pcm';
let sampleRate = 0;
const convert = () => {
return new Promise<Buffer>((resolve, reject) => {
// todo: 通过配置文件获取ffmpeg路径
const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg';
const cp = spawn(ffmpegPath, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]);
cp.on('error', err => {
log('FFmpeg处理转换出错: ', err.message);
return reject(err);
});
cp.on('exit', (code, signal) => {
const EXIT_CODES = [0, 255];
if (code == null || EXIT_CODES.includes(code)) {
sampleRate = 24000;
const data = fs.readFileSync(pcmPath);
fs.unlink(pcmPath, (err) => {
});
return resolve(data);
}
log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
reject(Error('FFmpeg处理转换失败'));
});
});
};
let input: Buffer;
if (!_isWav) {
input = await convert();
} else {
input = fs.readFileSync(filePath);
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
const { fmt } = getWavFileInfo(input);
// log(`wav文件信息`, fmt)
if (!allowSampleRate.includes(fmt.sampleRate)) {
input = await convert();
}
}
const silk = await encode(input, sampleRate);
fs.writeFileSync(pttPath, silk.data);
log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
return {
converted: true,
path: pttPath,
duration: silk.duration / 1000
};
} else {
const silk = fs.readFileSync(filePath);
let duration = 0;
try {
duration = getDuration(silk) / 1000;
} catch (e: any) {
log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
duration = await guessDuration(filePath);
}
return {
converted: false,
path: filePath,
duration,
};
}
} catch (error: any) {
log('convert silk failed', error.stack);
return {};
}
}

323
src/common/utils/db.ts Normal file
View File

@@ -0,0 +1,323 @@
import { ElementType, FileElement, PicElement, PttElement, RawMessage, VideoElement } from '@/core/qqnt/entities';
import sqlite3 from 'sqlite3';
import { log } from '@/common/utils/log';
type DBMsg = {
id: number,
longId: string,
seq: number,
peerUid: string,
msg: string
}
type DBFile = {
name: string; // 文件名
path: string;
url: string;
size: number;
uuid: string;
msgId: string;
elementId: string;
element: PicElement | VideoElement | FileElement | PttElement;
elementType: ElementType.PIC | ElementType.VIDEO | ElementType.FILE | ElementType.PTT;
}
class DBUtilBase {
protected db: sqlite3.Database | undefined;
createConnection(dbPath: string) {
if (this.db) {
return;
}
this.db = new sqlite3.Database(dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
if (err) {
log('Could not connect to database', err);
return;
}
this.createTable();
});
}
protected createTable() {
throw new Error('Method not implemented.');
}
close() {
this.db?.close();
}
}
class DBUtil extends DBUtilBase {
private msgCache: Map<string, RawMessage> = new Map<string, RawMessage>();
constructor() {
super();
const interval = 1000 * 60 * 10; // 10分钟清理一次缓存
setInterval(() => {
log('清理消息缓存');
this.msgCache.forEach((msg, key) => {
if ((Date.now() - parseInt(msg.msgTime) * 1000) > interval) {
this.msgCache.delete(key);
}
});
}, interval);
}
protected createTable() {
// 消息记录
const createTableSQL = `
CREATE TABLE IF NOT EXISTS msgs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
long_id TEXT NOT NULL UNIQUE,
seq INTEGER NOT NULL,
peer_uid TEXT NOT NULL,
msg TEXT NOT NULL
)`;
this.db!.run(createTableSQL, function (err) {
if (err) {
log('Could not create table', err);
}
});
// 文件缓存
const createFileTableSQL = `
CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
path TEXT NOT NULL,
url TEXT,
size INTEGER NOT NULL,
uuid TEXT,
elementType INTEGER,
element TEXT NOT NULL,
elementId TEXT NOT NULL,
msgId TEXT NOT NULL
)`;
this.db!.run(createFileTableSQL, function (err) {
if (err) {
log('Could not create table files', err);
}
});
// 接收到的临时会话消息uid
const createTempUinTableSQL = `
CREATE TABLE IF NOT EXISTS temp_uins (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid TEXT,
uin TEXT
)`;
this.db!.run(createTempUinTableSQL, function (err) {
if (err) {
log('Could not create table temp_uins', err);
}
});
}
private async getMsg(query: string, params: any[]) {
const stmt = this.db!.prepare(query);
return new Promise<RawMessage | null>((resolve, reject) => {
stmt.get(...params, (err: any, row: DBMsg) => {
// log("getMsg", row, err);
if (err) {
log('Could not get msg by short id', err);
resolve(null);
}
try {
const msg = JSON.parse(row.msg);
msg.id = row.id;
return resolve(msg);
} catch (e) {
return resolve(null);
}
});
});
}
async getMsgByShortId(shortId: number): Promise<RawMessage | null> {
const getStmt = 'SELECT * FROM msgs WHERE id = ?';
return this.getMsg(getStmt, [shortId]);
}
async getMsgByLongId(longId: string): Promise<RawMessage | null> {
if (this.msgCache.has(longId)) {
return this.msgCache.get(longId)!;
}
return this.getMsg('SELECT * FROM msgs WHERE long_id = ?', [longId]);
}
async getMsgBySeq(peerUid: string, seq: string): Promise<RawMessage | null> {
const stmt = 'SELECT * FROM msgs WHERE peer_uid = ? AND seq = ?';
return this.getMsg(stmt, [peerUid, seq]);
}
async addMsg(msg: RawMessage, update = true): Promise<number> {
log('正在记录消息到数据库', msg.msgId);
const existMsg = await this.getMsgByLongId(msg.msgId);
if (existMsg) {
// log('消息已存在,更新数据库', msg.msgId);
if (update) this.updateMsg(msg).then();
return existMsg.id!;
}
const stmt = this.db!.prepare('INSERT INTO msgs (long_id, seq, peer_uid, msg) VALUES (?, ?, ?, ?)');
// const runAsync = promisify(stmt.run.bind(stmt));
return new Promise((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const dbInstance = this;
stmt.run(msg.msgId, msg.msgSeq, msg.peerUid, JSON.stringify(msg), function (err: any) {
if (err) {
if (err.errno === 19) {
// log('消息已存在,更新数据库', msg.msgId);
dbInstance.getMsgByLongId(msg.msgId).then((msg: RawMessage | null) => {
if (msg) {
dbInstance.msgCache.set(msg.msgId, msg);
// log('获取消息短id成功', msg.id);
resolve(msg.id!);
} else {
log('db could not get msg by long id', err);
resolve(-1);
}
});
} else {
log('db could not add msg', err);
resolve(-1);
}
} else {
// log("addMsg", this);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
msg.id = this.lastID;
dbInstance.msgCache.set(msg.msgId, msg);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// log('获取消息短id成功', this.lastID);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
resolve(this.lastID);
}
});
});
}
async updateMsg(msg: RawMessage) {
const existMsg = this.msgCache.get(msg.msgId);
if (existMsg) {
Object.assign(existMsg, msg);
}
const stmt = this.db!.prepare('UPDATE msgs SET msg = ?, seq = ? WHERE long_id = ?');
try {
stmt.run(JSON.stringify(msg), msg.msgSeq, msg.msgId);
} catch (e) {
log('updateMsg db error', e);
}
}
async addFileCache(file: DBFile) {
const stmt = this.db!.prepare('INSERT INTO files (name, path, url, size, uuid, elementType ,element, elementId, msgId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
return new Promise((resolve, reject) => {
stmt.run(file.name, file.path, file.url, file.size, file.uuid,
file.elementType,
JSON.stringify(file.element),
file.elementId,
file.msgId,
function (err: any) {
if (err) {
log('db could not add file', err);
reject(err);
}
resolve(null);
});
});
}
private async getFileCache(query: string, params: any[]) {
const stmt = this.db!.prepare(query);
return new Promise<DBFile | null>((resolve, reject) => {
stmt.get(...params, (err: any, row: DBFile & { element: string }) => {
if (err) {
log('db could not get file cache', err);
reject(err);
}
if (row) {
row.element = JSON.parse(row.element);
}
resolve(row);
});
});
}
async getFileCacheByName(name: string): Promise<DBFile | null> {
return this.getFileCache('SELECT * FROM files WHERE name = ?', [name]);
}
async getFileCacheByUuid(uuid: string): Promise<DBFile | null> {
return this.getFileCache('SELECT * FROM files WHERE uuid = ?', [uuid]);
}
// todo: 是否所有的文件都有uuid语音消息有没有uuid
async updateFileCache(file: DBFile) {
const stmt = this.db!.prepare('UPDATE files SET path = ?, url = ? WHERE uuid = ?');
return new Promise((resolve, reject) => {
stmt.run(file.path, file.url, function (err: any) {
if (err) {
log('db could not update file cache', err);
reject(err);
}
resolve(null);
});
});
}
// 被动收到的临时会话消息uin->uid
async getReceivedTempUinMap() {
const stmt = 'SELECT * FROM temp_uins';
return new Promise<Record<string, string>>((resolve, reject) => {
this.db!.all(stmt, (err, rows: { uin: string, uid: string }[]) => {
if (err) {
log('db could not get temp uin map', err);
reject(err);
}
const map: Record<string, string> = {};
rows.forEach(row => {
map[row.uin] = row.uid;
});
resolve(map);
});
});
}
// 通过uin获取临时会话消息uid
async getUidByTempUin(uid: string) {
const stmt = 'SELECT * FROM temp_uins WHERE uin = ?';
return new Promise<string>((resolve, reject) => {
this.db!.get(stmt, [uid], (err, row: { uin: string, uid: string }) => {
if (err) {
log('db could not get temp uin map', err);
reject(err);
}
resolve(row?.uid);
});
});
}
async addTempUin(uin: string, uid: string) {
const existUid = await this.getUidByTempUin(uin);
if (!existUid) {
const stmt = this.db!.prepare('INSERT INTO temp_uins (uin, uid) VALUES (?, ?)');
return new Promise((resolve, reject) => {
stmt.run(uin, uid, function (err: any) {
if (err) {
log('db could not add temp uin', err);
reject(err);
}
resolve(null);
});
});
}
}
}
export const dbUtil = new DBUtil();

273
src/common/utils/file.ts Normal file
View File

@@ -0,0 +1,273 @@
import fs from 'fs';
import fsPromise from 'fs/promises';
import crypto from 'crypto';
import util from 'util';
import path from 'node:path';
import { log } from './log';
import { dbUtil } from './db';
import * as fileType from 'file-type';
import { v4 as uuidv4 } from 'uuid';
import { napCatCore } from '@/core';
import os from 'node:os';
export const getNapCatDir = () => {
const p = path.join(napCatCore.wrapper.dataPath, 'NapCat');
fs.mkdirSync(p, { recursive: true });
return p;
};
export const getTempDir = () => {
const p = path.join(getNapCatDir(), 'temp');
// 创建临时目录
if (!fs.existsSync(p)) {
fs.mkdirSync(p, { recursive: true });
}
return p;
};
export function isGIF(path: string) {
const buffer = Buffer.alloc(4);
const fd = fs.openSync(path, 'r');
fs.readSync(fd, buffer, 0, 4, 0);
fs.closeSync(fd);
return buffer.toString() === 'GIF8';
}
// 定义一个异步函数来检查文件是否存在
export function checkFileReceived(path: string, timeout: number = 3000): Promise<void> {
return new Promise((resolve, reject) => {
const startTime = Date.now();
function check() {
if (fs.existsSync(path)) {
resolve();
} else if (Date.now() - startTime > timeout) {
reject(new Error(`文件不存在: ${path}`));
} else {
setTimeout(check, 100);
}
}
check();
});
}
export async function file2base64(path: string) {
const readFile = util.promisify(fs.readFile);
const result = {
err: '',
data: ''
};
try {
// 读取文件内容
// if (!fs.existsSync(path)){
// path = path.replace("\\Ori\\", "\\Thumb\\");
// }
try {
await checkFileReceived(path, 5000);
} catch (e: any) {
result.err = e.toString();
return result;
}
const data = await readFile(path);
// 转换为Base64编码
result.data = data.toString('base64');
} catch (err: any) {
result.err = err.toString();
}
return result;
}
export function calculateFileMD5(filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
// 创建一个流式读取器
const stream = fs.createReadStream(filePath);
const hash = crypto.createHash('md5');
stream.on('data', (data: Buffer) => {
// 当读取到数据时,更新哈希对象的状态
hash.update(data);
});
stream.on('end', () => {
// 文件读取完成,计算哈希
const md5 = hash.digest('hex');
resolve(md5);
});
stream.on('error', (err: Error) => {
// 处理可能的读取错误
reject(err);
});
});
}
export interface HttpDownloadOptions {
url: string;
headers?: Record<string, string> | string;
}
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
const chunks: Buffer[] = [];
let url: string;
let headers: Record<string, string> = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36'
};
if (typeof options === 'string') {
url = options;
} else {
url = options.url;
if (options.headers) {
if (typeof options.headers === 'string') {
headers = JSON.parse(options.headers);
} else {
headers = options.headers;
}
}
}
const fetchRes = await fetch(url, headers);
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`);
const blob = await fetchRes.blob();
const buffer = await blob.arrayBuffer();
return Buffer.from(buffer);
}
type Uri2LocalRes = {
success: boolean,
errMsg: string,
fileName: string,
ext: string,
path: string,
isLocal: boolean
}
export async function uri2local(uri: string, fileName: string | null = null): Promise<Uri2LocalRes> {
const res = {
success: false,
errMsg: '',
fileName: '',
ext: '',
path: '',
isLocal: false
};
if (!fileName) {
fileName = uuidv4();
}
let filePath = path.join(getTempDir(), fileName);
let url = null;
try {
url = new URL(uri);
} catch (e: any) {
res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在`;
return res;
}
// log("uri protocol", url.protocol, uri);
if (url.protocol == 'base64:') {
// base64转成文件
const base64Data = uri.split('base64://')[1];
try {
const buffer = Buffer.from(base64Data, 'base64');
fs.writeFileSync(filePath, buffer);
} catch (e: any) {
res.errMsg = 'base64文件下载失败,' + e.toString();
return res;
}
} else if (url.protocol == 'http:' || url.protocol == 'https:') {
// 下载文件
let buffer: Buffer | null = null;
try {
buffer = await httpDownload(uri);
} catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString();
return res;
}
try {
const pathInfo = path.parse(decodeURIComponent(url.pathname));
if (pathInfo.name) {
fileName = pathInfo.name;
if (pathInfo.ext) {
fileName += pathInfo.ext;
// res.ext = pathInfo.ext
}
}
res.fileName = fileName;
filePath = path.join(getTempDir(), uuidv4() + fileName);
fs.writeFileSync(filePath, buffer);
} catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString();
return res;
}
} else {
let pathname: string;
if (url.protocol === 'file:') {
// await fs.copyFile(url.pathname, filePath);
pathname = decodeURIComponent(url.pathname);
if (process.platform === 'win32') {
filePath = pathname.slice(1);
} else {
filePath = pathname;
}
} else {
const cache = await dbUtil.getFileCacheByName(uri);
if (cache) {
filePath = cache.path;
} else {
filePath = uri;
}
}
res.isLocal = true;
}
// else{
// res.errMsg = `不支持的file协议,` + url.protocol
// return res
// }
// if (isGIF(filePath) && !res.isLocal) {
// await fs.rename(filePath, filePath + ".gif");
// filePath += ".gif";
// }
if (!res.isLocal && !res.ext) {
try {
const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext;
if (ext) {
log('获取文件类型', ext, filePath);
fs.renameSync(filePath, filePath + `.${ext}`);
filePath += `.${ext}`;
res.fileName += `.${ext}`;
res.ext = ext;
}
} catch (e) {
// log("获取文件类型失败", filePath,e.stack)
}
}
res.success = true;
res.path = filePath;
return res;
}
export async function copyFolder(sourcePath: string, destPath: string) {
try {
const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true });
await fsPromise.mkdir(destPath, { recursive: true });
for (const entry of entries) {
const srcPath = path.join(sourcePath, entry.name);
const dstPath = path.join(destPath, entry.name);
if (entry.isDirectory()) {
await copyFolder(srcPath, dstPath);
} else {
try {
await fsPromise.copyFile(srcPath, dstPath);
} catch (error) {
console.error(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`);
// 这里可以决定是否要继续复制其他文件
}
}
}
} catch (error) {
console.error('复制文件夹时出错:', error);
}
}

View File

@@ -0,0 +1,21 @@
import crypto from 'node:crypto';
import { resolve } from 'dns';
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
export function getMd5(s: string) {
const h = crypto.createHash('md5');
h.update(s);
return h.digest('hex');
}
export function isNull(value: any) {
return value === undefined || value === null;
}
export function isNumeric(str: string) {
return /^\d+$/.test(str);
}

4
src/common/utils/log.ts Normal file
View File

@@ -0,0 +1,4 @@
export function log(...args: any[]) {
console.log(...args);
}

View File

@@ -0,0 +1,7 @@
// QQ等级换算
import { QQLevel } from '../../ntqqapi/types';
export function calcQQLevel(level: QQLevel) {
const { crownNum, sunNum, moonNum, starNum } = level;
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
}

View File

@@ -0,0 +1,9 @@
import os from 'node:os';
import path from 'node:path';
export const systemPlatform = os.platform();
export const systemVersion = os.release();
export const hostname = os.hostname();
const homeDir = os.homedir();
export const downloadsPath = path.join(homeDir, 'Downloads');
export const systemName = os.type();

38
src/common/utils/umami.ts Normal file
View File

@@ -0,0 +1,38 @@
import { request } from "https";
export function noifyLoginStatus() {
let req = request(
{
hostname: 'napcat.wumiao.wang',
path: '/api/send',
port: 443,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0`
}
},
(res) => {
//let data = '';
res.on('data', (chunk) => {
//data += chunk;
});
res.on('end', () => {
//console.log('Response:', data);
});
}
);
let StatesData = {
type: "event",
payload: {
"website": "952bf82f-8f49-4456-aec5-e17db5f27f7e",
"hostname": "napcat.demo.cn",
"screen": "1920x1080",
"language": "zh-CN",
"title": "OneBot.Login",
"url": "/login/onebot11",
"referrer": "https://napcat.demo.cn/login?type=onebot11"
}
};
req.write(JSON.stringify(StatesData));
req.end();
}

View File

@@ -0,0 +1,44 @@
import { get as httpsGet } from "node:https";
function requestMirror(url: string): Promise<string | undefined> {
return new Promise((resolve, reject) => {
httpsGet(url, (response) => {
let data = '';
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
try {
const parsedData = JSON.parse(data);
const version = parsedData.version;
resolve(version);
} catch (error) {
// 解析失败或无法访问域名,跳过
resolve(undefined);
}
});
}).on('error', (error) => {
// 请求失败,跳过
resolve(undefined);
});
});
}
export async function checkVersion(): Promise<string> {
return new Promise(async (resolve, reject) => {
const MirrorList =
[
"https://fastly.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json",
"https://gcore.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json",
"https://cdn.jsdelivr.us/gh/NapNeko/NapCatQQ@main/package.json",
"https://jsd.cdn.zzko.cn/gh/NapNeko/NapCatQQ@main/package.json"
];
for (const url of MirrorList) {
const version = await requestMirror(url);
if (version) {
resolve(version);
}
}
reject("get verison error!");
});
}

88
src/common/utils/video.ts Normal file

File diff suppressed because one or more lines are too long