SnowCat 雪猫底层全源码:NapCat 4.18.28 底座 + SnowLuma 能力移植 + V8 通道适配 9.9.37
26 包 monorepo(TS 全源码): - snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包 - snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线 - snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI) - snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports) - snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时 - 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏) - 工具件: 解包器/启动器/便携制作器/doctor GitHub 关联已全量指向 DandanLLab/SnowCatQQ
This commit is contained in:
commit
b9655a5ab1
1048 files changed
+130851
No files matched your search
@@ -0,0 +1,47 @@
|
||||
// love from https://github.com/LagrangeDev/lagrangejs & https://github.com/takayama-lily/oicq
|
||||
import * as crypto from 'crypto';
|
||||
import * as stream from 'stream';
|
||||
import * as fs from 'fs';
|
||||
import { CalculateStreamBytesTransform } from '@/snowcat-core/packet/utils/crypto/sha1StreamBytesTransform';
|
||||
|
||||
function sha1Stream (readable: stream.Readable) {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
readable.on('error', reject);
|
||||
readable.pipe(crypto.createHash('sha1').on('error', reject).on('data', resolve));
|
||||
});
|
||||
}
|
||||
|
||||
function md5Stream (readable: stream.Readable) {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
readable.on('error', reject);
|
||||
readable.pipe(crypto.createHash('md5').on('error', reject).on('data', resolve));
|
||||
});
|
||||
}
|
||||
|
||||
export function calculateSha1 (filePath: string): Promise<Buffer> {
|
||||
const readable = fs.createReadStream(filePath);
|
||||
return sha1Stream(readable);
|
||||
}
|
||||
|
||||
export function computeMd5AndLengthWithLimit (filePath: string, limit?: number): Promise<Buffer> {
|
||||
const readStream = fs.createReadStream(filePath, limit ? { start: 0, end: limit - 1 } : {});
|
||||
return md5Stream(readStream);
|
||||
}
|
||||
|
||||
export function calculateSha1StreamBytes (filePath: string): Promise<Buffer[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const readable = fs.createReadStream(filePath);
|
||||
const calculateStreamBytes = new CalculateStreamBytesTransform();
|
||||
const byteArrayList: Buffer[] = [];
|
||||
calculateStreamBytes.on('data', (chunk: Buffer) => {
|
||||
byteArrayList.push(chunk);
|
||||
});
|
||||
calculateStreamBytes.on('end', () => {
|
||||
resolve(byteArrayList);
|
||||
});
|
||||
calculateStreamBytes.on('error', (err: Error) => {
|
||||
reject(err);
|
||||
});
|
||||
readable.pipe(calculateStreamBytes);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import crypto from 'crypto';
|
||||
import assert from 'assert';
|
||||
import { Sha1Stream } from './sha1Stream';
|
||||
|
||||
function testSha1Stream () {
|
||||
for (let i = 0; i < 100000; i++) {
|
||||
const randomLength = Math.floor(Math.random() * 1024);
|
||||
const randomData = crypto.randomBytes(randomLength);
|
||||
const sha1Stream = new Sha1Stream();
|
||||
sha1Stream.update(randomData);
|
||||
const hash = sha1Stream.final();
|
||||
const expectedDigest = crypto.createHash('sha1').update(randomData).digest();
|
||||
assert.strictEqual(hash.toString('hex'), expectedDigest.toString('hex'));
|
||||
console.log(`Test ${i + 1}: Passed`);
|
||||
}
|
||||
console.log('All tests passed successfully.');
|
||||
}
|
||||
|
||||
testSha1Stream();
|
||||
@@ -0,0 +1,125 @@
|
||||
// @ts-nocheck
|
||||
export class Sha1Stream {
|
||||
readonly Sha1BlockSize = 64;
|
||||
readonly Sha1DigestSize = 20;
|
||||
private readonly _padding = Buffer.concat([Buffer.from([0x80]), Buffer.alloc(63)]);
|
||||
private readonly _state = new Uint32Array(5);
|
||||
private readonly _count = new Uint32Array(2);
|
||||
private readonly _buffer = Buffer.allocUnsafe(this.Sha1BlockSize);
|
||||
private readonly _w = new Uint32Array(80);
|
||||
|
||||
constructor () {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
private reset (): void {
|
||||
this._state[0] = 0x67452301;
|
||||
this._state[1] = 0xEFCDAB89;
|
||||
this._state[2] = 0x98BADCFE;
|
||||
this._state[3] = 0x10325476;
|
||||
this._state[4] = 0xC3D2E1F0;
|
||||
this._count[0] = 0;
|
||||
this._count[1] = 0;
|
||||
this._buffer.fill(0);
|
||||
}
|
||||
|
||||
private rotateLeft (v: number, o: number): number {
|
||||
return ((v << o) | (v >>> (32 - o))) >>> 0;
|
||||
}
|
||||
|
||||
private transform (chunk: Buffer, offset: number): void {
|
||||
const w = this._w;
|
||||
const view = new DataView(chunk.buffer, chunk.byteOffset + offset, 64);
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
w[i] = view.getUint32(i * 4, false);
|
||||
}
|
||||
|
||||
for (let i = 16; i < 80; i++) {
|
||||
w[i] = this.rotateLeft(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1) >>> 0;
|
||||
}
|
||||
|
||||
let a = this._state[0];
|
||||
let b = this._state[1];
|
||||
let c = this._state[2];
|
||||
let d = this._state[3];
|
||||
let e = this._state[4];
|
||||
|
||||
for (let i = 0; i < 80; i++) {
|
||||
let temp;
|
||||
if (i < 20) {
|
||||
temp = ((b & c) | (~b & d)) + 0x5A827999;
|
||||
} else if (i < 40) {
|
||||
temp = (b ^ c ^ d) + 0x6ED9EBA1;
|
||||
} else if (i < 60) {
|
||||
temp = ((b & c) | (b & d) | (c & d)) + 0x8F1BBCDC;
|
||||
} else {
|
||||
temp = (b ^ c ^ d) + 0xCA62C1D6;
|
||||
}
|
||||
temp += ((this.rotateLeft(a, 5) + e + w[i]) >>> 0);
|
||||
e = d;
|
||||
d = c;
|
||||
c = this.rotateLeft(b, 30) >>> 0;
|
||||
b = a;
|
||||
a = temp;
|
||||
}
|
||||
|
||||
this._state[0] = (this._state[0] + a) >>> 0;
|
||||
this._state[1] = (this._state[1] + b) >>> 0;
|
||||
this._state[2] = (this._state[2] + c) >>> 0;
|
||||
this._state[3] = (this._state[3] + d) >>> 0;
|
||||
this._state[4] = (this._state[4] + e) >>> 0;
|
||||
}
|
||||
|
||||
public update (data: Buffer, len?: number): void {
|
||||
let index = ((this._count[0] >>> 3) & 0x3F) >>> 0;
|
||||
const dataLen = len ?? data.length;
|
||||
this._count[0] = (this._count[0] + (dataLen << 3)) >>> 0;
|
||||
|
||||
if (this._count[0] < (dataLen << 3)) this._count[1] = (this._count[1] + 1) >>> 0;
|
||||
|
||||
this._count[1] = (this._count[1] + (dataLen >>> 29)) >>> 0;
|
||||
|
||||
const partLen = (this.Sha1BlockSize - index) >>> 0;
|
||||
let i = 0;
|
||||
|
||||
if (dataLen >= partLen) {
|
||||
data.copy(this._buffer, index, 0, partLen);
|
||||
this.transform(this._buffer, 0);
|
||||
for (i = partLen; (i + this.Sha1BlockSize) <= dataLen; i = (i + this.Sha1BlockSize) >>> 0) {
|
||||
this.transform(data, i);
|
||||
}
|
||||
index = 0;
|
||||
}
|
||||
|
||||
data.copy(this._buffer, index, i, dataLen);
|
||||
}
|
||||
|
||||
public hash (bigEndian: boolean = true): Buffer {
|
||||
const digest = Buffer.allocUnsafe(this.Sha1DigestSize);
|
||||
if (bigEndian) {
|
||||
for (let i = 0; i < 5; i++) digest.writeUInt32BE(this._state[i], i * 4);
|
||||
} else {
|
||||
for (let i = 0; i < 5; i++) digest.writeUInt32LE(this._state[i], i * 4);
|
||||
}
|
||||
return digest;
|
||||
}
|
||||
|
||||
public final (): Buffer {
|
||||
const digest = Buffer.allocUnsafe(this.Sha1DigestSize);
|
||||
const bits = Buffer.allocUnsafe(8);
|
||||
bits.writeUInt32BE(this._count[1], 0);
|
||||
bits.writeUInt32BE(this._count[0], 4);
|
||||
|
||||
const index = ((this._count[0] >>> 3) & 0x3F) >>> 0;
|
||||
const padLen = ((index < 56) ? (56 - index) : (120 - index)) >>> 0;
|
||||
this.update(this._padding, padLen);
|
||||
this.update(bits);
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
digest.writeUInt32BE(this._state[i], i * 4);
|
||||
}
|
||||
|
||||
return digest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import * as stream from 'node:stream';
|
||||
import { Sha1Stream } from '@/snowcat-core/packet/utils/crypto/sha1Stream';
|
||||
|
||||
export class CalculateStreamBytesTransform extends stream.Transform {
|
||||
private readonly blockSize = 1024 * 1024;
|
||||
private readonly sha1: Sha1Stream;
|
||||
private buffer: Buffer;
|
||||
private bytesRead: number;
|
||||
private readonly byteArrayList: Buffer[];
|
||||
|
||||
constructor () {
|
||||
super();
|
||||
this.sha1 = new Sha1Stream();
|
||||
this.buffer = Buffer.alloc(0);
|
||||
this.bytesRead = 0;
|
||||
this.byteArrayList = [];
|
||||
}
|
||||
|
||||
override _transform (chunk: Buffer, _: BufferEncoding, callback: stream.TransformCallback): void {
|
||||
try {
|
||||
this.buffer = Buffer.concat([this.buffer, chunk]);
|
||||
let offset = 0;
|
||||
while (this.buffer.length - offset >= this.sha1.Sha1BlockSize) {
|
||||
const block = this.buffer.subarray(offset, offset + this.sha1.Sha1BlockSize);
|
||||
this.sha1.update(block);
|
||||
offset += this.sha1.Sha1BlockSize;
|
||||
this.bytesRead += this.sha1.Sha1BlockSize;
|
||||
if (this.bytesRead % this.blockSize === 0) {
|
||||
const digest = this.sha1.hash(false);
|
||||
this.byteArrayList.push(Buffer.from(digest));
|
||||
}
|
||||
}
|
||||
this.buffer = this.buffer.subarray(offset);
|
||||
callback(null);
|
||||
} catch (err) {
|
||||
callback(err as Error);
|
||||
}
|
||||
}
|
||||
|
||||
override _flush (callback: stream.TransformCallback): void {
|
||||
try {
|
||||
if (this.buffer.length > 0) this.sha1.update(this.buffer);
|
||||
const finalDigest = this.sha1.final();
|
||||
this.byteArrayList.push(Buffer.from(finalDigest));
|
||||
for (const digest of this.byteArrayList) {
|
||||
this.push(digest);
|
||||
}
|
||||
callback(null);
|
||||
} catch (err) {
|
||||
callback(err as Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// love from https://github.com/LagrangeDev/lagrangejs/blob/main/src/core/tea.ts & https://github.com/takayama-lily/oicq/blob/main/lib/core/tea.ts
|
||||
|
||||
// @ts-nocheck
|
||||
const BUF7 = Buffer.alloc(7);
|
||||
const deltas = [
|
||||
0x9e3779b9, 0x3c6ef372, 0xdaa66d2b, 0x78dde6e4, 0x1715609d, 0xb54cda56, 0x5384540f, 0xf1bbcdc8, 0x8ff34781,
|
||||
0x2e2ac13a, 0xcc623af3, 0x6a99b4ac, 0x08d12e65, 0xa708a81e, 0x454021d7, 0xe3779b90,
|
||||
];
|
||||
|
||||
function _toUInt32 (num: number) {
|
||||
return num >>> 0;
|
||||
}
|
||||
|
||||
function _encrypt (x: number, y: number, k0: number, k1: number, k2: number, k3: number): [number, number] {
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
let aa = ((_toUInt32(((y << 4) >>> 0) + k0) ^ _toUInt32(y + deltas[i])) >>> 0) ^ _toUInt32(~~(y / 32) + k1);
|
||||
aa >>>= 0;
|
||||
x = _toUInt32(x + aa);
|
||||
let bb = ((_toUInt32(((x << 4) >>> 0) + k2) ^ _toUInt32(x + deltas[i])) >>> 0) ^ _toUInt32(~~(x / 32) + k3);
|
||||
bb >>>= 0;
|
||||
y = _toUInt32(y + bb);
|
||||
}
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
export function encrypt (data: Buffer, key: Buffer) {
|
||||
let n = (6 - data.length) >>> 0;
|
||||
n = (n % 8) + 2;
|
||||
const v = Buffer.concat([Buffer.from([(n - 2) | 0xf8]), Buffer.allocUnsafe(n), data, BUF7]);
|
||||
const k0 = key.readUInt32BE(0);
|
||||
const k1 = key.readUInt32BE(4);
|
||||
const k2 = key.readUInt32BE(8);
|
||||
const k3 = key.readUInt32BE(12);
|
||||
let r1 = 0; let r2 = 0; let t1 = 0; let t2 = 0;
|
||||
for (let i = 0; i < v.length; i += 8) {
|
||||
const a1 = v.readUInt32BE(i);
|
||||
const a2 = v.readUInt32BE(i + 4);
|
||||
const b1 = a1 ^ r1;
|
||||
const b2 = a2 ^ r2;
|
||||
const [x, y] = _encrypt(b1 >>> 0, b2 >>> 0, k0, k1, k2, k3);
|
||||
r1 = x ^ t1;
|
||||
r2 = y ^ t2;
|
||||
t1 = b1;
|
||||
t2 = b2;
|
||||
v.writeInt32BE(r1, i);
|
||||
v.writeInt32BE(r2, i + 4);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
function _decrypt (x: number, y: number, k0: number, k1: number, k2: number, k3: number) {
|
||||
for (let i = 15; i >= 0; --i) {
|
||||
const aa = ((_toUInt32(((x << 4) >>> 0) + k2) ^ _toUInt32(x + deltas[i])) >>> 0) ^ _toUInt32(~~(x / 32) + k3);
|
||||
y = (y - aa) >>> 0;
|
||||
const bb = ((_toUInt32(((y << 4) >>> 0) + k0) ^ _toUInt32(y + deltas[i])) >>> 0) ^ _toUInt32(~~(y / 32) + k1);
|
||||
x = (x - bb) >>> 0;
|
||||
}
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
export function decrypt (encrypted: Buffer, key: Buffer) {
|
||||
if (encrypted.length % 8) throw ERROR_ENCRYPTED_LENGTH;
|
||||
const k0 = key.readUInt32BE(0);
|
||||
const k1 = key.readUInt32BE(4);
|
||||
const k2 = key.readUInt32BE(8);
|
||||
const k3 = key.readUInt32BE(12);
|
||||
let r1 = 0; let r2 = 0; let t1 = 0; let t2 = 0; let x = 0; let y = 0;
|
||||
for (let i = 0; i < encrypted.length; i += 8) {
|
||||
const a1 = encrypted.readUInt32BE(i);
|
||||
const a2 = encrypted.readUInt32BE(i + 4);
|
||||
const b1 = a1 ^ x;
|
||||
const b2 = a2 ^ y;
|
||||
[x, y] = _decrypt(b1 >>> 0, b2 >>> 0, k0, k1, k2, k3);
|
||||
r1 = x ^ t1;
|
||||
r2 = y ^ t2;
|
||||
t1 = a1;
|
||||
t2 = a2;
|
||||
encrypted.writeInt32BE(r1, i);
|
||||
encrypted.writeInt32BE(r2, i + 4);
|
||||
}
|
||||
if (Buffer.compare(encrypted.subarray(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL;
|
||||
// if (Buffer.compare(encrypted.slice(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL;
|
||||
return encrypted.subarray((encrypted[0] & 0x07) + 3, encrypted.length - 7);
|
||||
// return encrypted.slice((encrypted[0] & 0x07) + 3, encrypted.length - 7);
|
||||
}
|
||||
|
||||
const ERROR_ENCRYPTED_LENGTH = new Error('length of encrypted data must be a multiple of 8');
|
||||
const ERROR_ENCRYPTED_ILLEGAL = new Error('encrypted data is illegal');
|
||||
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
MiniAppData,
|
||||
MiniAppReqParams,
|
||||
MiniAppRawData,
|
||||
MiniAppReqCustomParams,
|
||||
MiniAppReqTemplateParams,
|
||||
} from '@/snowcat-core/packet/entities/miniApp';
|
||||
|
||||
type MiniAppTemplateNameList = 'bili' | 'weibo';
|
||||
|
||||
export abstract class MiniAppInfo {
|
||||
static readonly sdkId: string = 'V1_PC_MINISDK_99.99.99_1_APP_A';
|
||||
template: MiniAppReqTemplateParams;
|
||||
|
||||
private static readonly appMap = new Map<MiniAppTemplateNameList, MiniAppInfo>();
|
||||
|
||||
protected constructor (template: MiniAppReqTemplateParams) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
static get (name: MiniAppTemplateNameList): MiniAppInfo | undefined {
|
||||
return this.appMap.get(name);
|
||||
}
|
||||
|
||||
static readonly Bili = new class extends MiniAppInfo {
|
||||
constructor () {
|
||||
super({
|
||||
sdkId: MiniAppInfo.sdkId,
|
||||
appId: '1109937557',
|
||||
scene: 1,
|
||||
templateType: 1,
|
||||
businessType: 0,
|
||||
verType: 3,
|
||||
shareType: 0,
|
||||
versionId: 'cfc5f7b05b44b5956502edaecf9d2240',
|
||||
withShareTicket: 0,
|
||||
iconUrl: 'https://miniapp.gtimg.cn/public/appicon/51f90239b78a2e4994c11215f4c4ba15_200.jpg',
|
||||
});
|
||||
MiniAppInfo.appMap.set('bili', this);
|
||||
}
|
||||
}();
|
||||
|
||||
static readonly WeiBo = new class extends MiniAppInfo {
|
||||
constructor () {
|
||||
super({
|
||||
sdkId: MiniAppInfo.sdkId,
|
||||
appId: '1109224783',
|
||||
scene: 1,
|
||||
templateType: 1,
|
||||
businessType: 0,
|
||||
verType: 3,
|
||||
shareType: 0,
|
||||
versionId: 'e482a3cc4e574d9b772e96ba6eec9ba2',
|
||||
withShareTicket: 0,
|
||||
iconUrl: 'https://miniapp.gtimg.cn/public/appicon/35bbb44dc68e65194cfacfb206b8f1f7_200.jpg',
|
||||
});
|
||||
MiniAppInfo.appMap.set('weibo', this);
|
||||
}
|
||||
}();
|
||||
}
|
||||
|
||||
export class MiniAppInfoHelper {
|
||||
static generateReq (custom: MiniAppReqCustomParams, template: MiniAppReqTemplateParams): MiniAppReqParams {
|
||||
return {
|
||||
...custom,
|
||||
...template,
|
||||
};
|
||||
}
|
||||
|
||||
static RawToSend (rawData: MiniAppRawData): MiniAppData {
|
||||
return {
|
||||
ver: rawData.ver,
|
||||
prompt: rawData.prompt,
|
||||
config: rawData.config,
|
||||
app: rawData.appName,
|
||||
view: rawData.appView,
|
||||
meta: rawData.metaData,
|
||||
miniappShareOrigin: 3,
|
||||
miniappOpenRefer: '10002',
|
||||
};
|
||||
}
|
||||
|
||||
static SendToRaw (data: MiniAppData): MiniAppRawData {
|
||||
return {
|
||||
appName: data.app,
|
||||
appView: data.view,
|
||||
ver: data.ver,
|
||||
desc: data.meta.detail_1.desc,
|
||||
prompt: data.prompt,
|
||||
metaData: data.meta,
|
||||
config: data.config,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user