@z-qinghui/migpt-claw 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +690 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/setup-entry.d.ts +3 -0
- package/dist/setup-entry.js +7 -0
- package/dist/setup-entry.js.map +1 -0
- package/dist/src/channel.d.ts +10 -0
- package/dist/src/channel.js +444 -0
- package/dist/src/channel.js.map +1 -0
- package/dist/src/config.d.ts +125 -0
- package/dist/src/config.js +146 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/message.d.ts +51 -0
- package/dist/src/message.js +145 -0
- package/dist/src/message.js.map +1 -0
- package/dist/src/mi/account.d.ts +5 -0
- package/dist/src/mi/account.js +162 -0
- package/dist/src/mi/account.js.map +1 -0
- package/dist/src/mi/common.d.ts +15 -0
- package/dist/src/mi/common.js +80 -0
- package/dist/src/mi/common.js.map +1 -0
- package/dist/src/mi/index.d.ts +4 -0
- package/dist/src/mi/index.js +10 -0
- package/dist/src/mi/index.js.map +1 -0
- package/dist/src/mi/mina.d.ts +66 -0
- package/dist/src/mi/mina.js +225 -0
- package/dist/src/mi/mina.js.map +1 -0
- package/dist/src/mi/miot.d.ts +35 -0
- package/dist/src/mi/miot.js +168 -0
- package/dist/src/mi/miot.js.map +1 -0
- package/dist/src/mi/typing.d.ts +90 -0
- package/dist/src/mi/typing.js +1 -0
- package/dist/src/mi/typing.js.map +1 -0
- package/dist/src/onboarding.d.ts +5 -0
- package/dist/src/onboarding.js +118 -0
- package/dist/src/onboarding.js.map +1 -0
- package/dist/src/openclaw-plugin-sdk.d.d.ts +185 -0
- package/dist/src/openclaw-plugin-sdk.d.js +1 -0
- package/dist/src/openclaw-plugin-sdk.d.js.map +1 -0
- package/dist/src/outbound.d.ts +5 -0
- package/dist/src/outbound.js +108 -0
- package/dist/src/outbound.js.map +1 -0
- package/dist/src/runtime.d.ts +6 -0
- package/dist/src/runtime.js +15 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/service.d.ts +70 -0
- package/dist/src/service.js +200 -0
- package/dist/src/service.js.map +1 -0
- package/dist/src/speaker.d.ts +62 -0
- package/dist/src/speaker.js +211 -0
- package/dist/src/speaker.js.map +1 -0
- package/dist/src/tts/mimo.d.ts +50 -0
- package/dist/src/tts/mimo.js +214 -0
- package/dist/src/tts/mimo.js.map +1 -0
- package/dist/src/types.d.ts +30 -0
- package/dist/src/types.js +1 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/codec.d.ts +31 -0
- package/dist/src/utils/codec.js +144 -0
- package/dist/src/utils/codec.js.map +1 -0
- package/dist/src/utils/debug.d.ts +10 -0
- package/dist/src/utils/debug.js +15 -0
- package/dist/src/utils/debug.js.map +1 -0
- package/dist/src/utils/hash.d.ts +40 -0
- package/dist/src/utils/hash.js +75 -0
- package/dist/src/utils/hash.js.map +1 -0
- package/dist/src/utils/http.d.ts +24 -0
- package/dist/src/utils/http.js +151 -0
- package/dist/src/utils/http.js.map +1 -0
- package/dist/src/utils/index.d.ts +6 -0
- package/dist/src/utils/index.js +10 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/io.d.ts +26 -0
- package/dist/src/utils/io.js +53 -0
- package/dist/src/utils/io.js.map +1 -0
- package/dist/src/utils/parse.d.ts +26 -0
- package/dist/src/utils/parse.js +51 -0
- package/dist/src/utils/parse.js.map +1 -0
- package/index.ts +26 -0
- package/openclaw.plugin.json +344 -0
- package/package.json +106 -0
- package/setup-entry.ts +12 -0
- package/skills/migpt-volume/SKILL.md +182 -0
- package/skills/migpt-volume/index.ts +50 -0
- package/src/channel.ts +519 -0
- package/src/config.ts +299 -0
- package/src/message.ts +186 -0
- package/src/mi/account.ts +184 -0
- package/src/mi/common.ts +105 -0
- package/src/mi/index.ts +4 -0
- package/src/mi/mina.ts +261 -0
- package/src/mi/miot.ts +193 -0
- package/src/mi/typing.ts +93 -0
- package/src/onboarding.ts +136 -0
- package/src/openclaw-plugin-sdk.d.ts +185 -0
- package/src/outbound.ts +137 -0
- package/src/runtime.ts +14 -0
- package/src/service.ts +246 -0
- package/src/speaker.ts +264 -0
- package/src/tts/mimo.ts +300 -0
- package/src/types.ts +34 -0
- package/src/utils/codec.ts +206 -0
- package/src/utils/debug.ts +16 -0
- package/src/utils/hash.ts +104 -0
- package/src/utils/http.ts +193 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/io.ts +68 -0
- package/src/utils/parse.ts +64 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { writeFile, mkdir, rm } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
class MiMoTTS {
|
|
7
|
+
_server = null;
|
|
8
|
+
_serverUrl = "";
|
|
9
|
+
_audioDir = "";
|
|
10
|
+
_config;
|
|
11
|
+
_ready = false;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this._config = {
|
|
14
|
+
apiKey: config.apiKey,
|
|
15
|
+
baseUrl: config.baseUrl ?? "https://api.xiaomimimo.com/v1",
|
|
16
|
+
model: config.model ?? "mimo-v2.5-tts",
|
|
17
|
+
voice: config.voice ?? "mimo_default",
|
|
18
|
+
style: config.style ?? "",
|
|
19
|
+
stream: config.stream ?? true,
|
|
20
|
+
port: config.port ?? 0,
|
|
21
|
+
host: config.host ?? "0.0.0.0"
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 初始化:创建临时目录 + 启动 HTTP 服务器
|
|
26
|
+
*/
|
|
27
|
+
async init() {
|
|
28
|
+
if (this._ready) return true;
|
|
29
|
+
try {
|
|
30
|
+
this._audioDir = join(tmpdir(), "migpt-claw-tts", randomUUID());
|
|
31
|
+
await mkdir(this._audioDir, { recursive: true });
|
|
32
|
+
this._server = createServer((req, res) => {
|
|
33
|
+
const url = decodeURIComponent(req.url ?? "");
|
|
34
|
+
if (!url.startsWith("/audio/")) {
|
|
35
|
+
res.writeHead(404);
|
|
36
|
+
res.end("Not Found");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const filePath = join(this._audioDir, url.replace("/audio/", ""));
|
|
40
|
+
serveWavFile(filePath, res).catch(() => {
|
|
41
|
+
res.writeHead(404);
|
|
42
|
+
res.end("Not Found");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
await new Promise((resolve, reject) => {
|
|
46
|
+
this._server.listen(this._config.port, this._config.host, () => {
|
|
47
|
+
const addr = this._server.address();
|
|
48
|
+
if (addr && typeof addr === "object") {
|
|
49
|
+
this._serverUrl = `http://${addr.address === "::" ? "localhost" : addr.address}:${addr.port}`;
|
|
50
|
+
this._ready = true;
|
|
51
|
+
resolve();
|
|
52
|
+
} else {
|
|
53
|
+
reject(new Error("Failed to get server address"));
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
this._server.on("error", reject);
|
|
57
|
+
});
|
|
58
|
+
console.log(`\u2705 MiMo TTS \u670D\u52A1\u5668\u5DF2\u542F\u52A8: ${this._serverUrl}`);
|
|
59
|
+
return true;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error("\u274C MiMo TTS \u521D\u59CB\u5316\u5931\u8D25:", err.message);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 生成语音并返回可播放的 URL
|
|
67
|
+
*/
|
|
68
|
+
async synthesize(text, options) {
|
|
69
|
+
if (!this._ready) {
|
|
70
|
+
const ok = await this.init();
|
|
71
|
+
if (!ok) return { url: "", success: false, error: "TTS server not initialized" };
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const messages = [];
|
|
75
|
+
const style = options?.style ?? this._config.style;
|
|
76
|
+
if (style) {
|
|
77
|
+
messages.push({ role: "user", content: style });
|
|
78
|
+
}
|
|
79
|
+
messages.push({ role: "assistant", content: text });
|
|
80
|
+
const voice = options?.voice ?? this._config.voice;
|
|
81
|
+
const useStream = this._config.stream;
|
|
82
|
+
const body = {
|
|
83
|
+
model: this._config.model,
|
|
84
|
+
messages,
|
|
85
|
+
audio: { format: useStream ? "pcm16" : "wav", voice },
|
|
86
|
+
stream: useStream
|
|
87
|
+
};
|
|
88
|
+
const response = await fetch(`${this._config.baseUrl}/chat/completions`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: {
|
|
91
|
+
"api-key": this._config.apiKey,
|
|
92
|
+
"Content-Type": "application/json"
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify(body)
|
|
95
|
+
});
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
const errText = await response.text().catch(() => "unknown");
|
|
98
|
+
return { url: "", success: false, error: `MiMo API error ${response.status}: ${errText}` };
|
|
99
|
+
}
|
|
100
|
+
let audioBuffer;
|
|
101
|
+
if (useStream) {
|
|
102
|
+
const pcmChunks = [];
|
|
103
|
+
const reader = response.body?.getReader();
|
|
104
|
+
if (!reader) {
|
|
105
|
+
return { url: "", success: false, error: "Failed to read streaming response" };
|
|
106
|
+
}
|
|
107
|
+
const decoder = new TextDecoder();
|
|
108
|
+
let buffer = "";
|
|
109
|
+
while (true) {
|
|
110
|
+
const { done, value } = await reader.read();
|
|
111
|
+
if (done) break;
|
|
112
|
+
buffer += decoder.decode(value, { stream: true });
|
|
113
|
+
const lines = buffer.split("\n");
|
|
114
|
+
buffer = lines.pop() ?? "";
|
|
115
|
+
for (const line of lines) {
|
|
116
|
+
const trimmed = line.trim();
|
|
117
|
+
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
118
|
+
const jsonStr = trimmed.slice(6);
|
|
119
|
+
if (jsonStr === "[DONE]") continue;
|
|
120
|
+
try {
|
|
121
|
+
const chunk = JSON.parse(jsonStr);
|
|
122
|
+
const audioData = chunk?.choices?.[0]?.delta?.audio?.data;
|
|
123
|
+
if (audioData) {
|
|
124
|
+
pcmChunks.push(Buffer.from(audioData, "base64"));
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (pcmChunks.length === 0) {
|
|
131
|
+
return { url: "", success: false, error: "No audio data in streaming response" };
|
|
132
|
+
}
|
|
133
|
+
const pcmData = Buffer.concat(pcmChunks);
|
|
134
|
+
audioBuffer = pcmToWav(pcmData, 24e3, 1, 16);
|
|
135
|
+
} else {
|
|
136
|
+
const data = await response.json();
|
|
137
|
+
const audioBase64 = data?.choices?.[0]?.message?.audio?.data;
|
|
138
|
+
if (!audioBase64) {
|
|
139
|
+
return { url: "", success: false, error: "No audio data in MiMo response" };
|
|
140
|
+
}
|
|
141
|
+
audioBuffer = Buffer.from(audioBase64, "base64");
|
|
142
|
+
}
|
|
143
|
+
const fileName = `${randomUUID()}.wav`;
|
|
144
|
+
const filePath = join(this._audioDir, fileName);
|
|
145
|
+
await writeFile(filePath, audioBuffer);
|
|
146
|
+
const url = `${this._serverUrl}/audio/${fileName}`;
|
|
147
|
+
const headerSize = 44;
|
|
148
|
+
const sampleRate = 24e3;
|
|
149
|
+
const bytesPerSample = 2;
|
|
150
|
+
const duration = (audioBuffer.length - headerSize) / (sampleRate * bytesPerSample);
|
|
151
|
+
return { url, success: true, duration };
|
|
152
|
+
} catch (err) {
|
|
153
|
+
return { url: "", success: false, error: err.message };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 清理临时文件和关闭服务器
|
|
158
|
+
*/
|
|
159
|
+
async destroy() {
|
|
160
|
+
if (this._server) {
|
|
161
|
+
await new Promise((resolve) => {
|
|
162
|
+
this._server.close(() => resolve());
|
|
163
|
+
});
|
|
164
|
+
this._server = null;
|
|
165
|
+
}
|
|
166
|
+
if (this._audioDir) {
|
|
167
|
+
await rm(this._audioDir, { recursive: true, force: true }).catch(() => {
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
this._ready = false;
|
|
171
|
+
}
|
|
172
|
+
get ready() {
|
|
173
|
+
return this._ready;
|
|
174
|
+
}
|
|
175
|
+
get serverUrl() {
|
|
176
|
+
return this._serverUrl;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function serveWavFile(filePath, res) {
|
|
180
|
+
const { readFile } = await import("node:fs/promises");
|
|
181
|
+
const data = await readFile(filePath);
|
|
182
|
+
res.writeHead(200, {
|
|
183
|
+
"Content-Type": "audio/wav",
|
|
184
|
+
"Content-Length": data.length,
|
|
185
|
+
"Cache-Control": "no-cache"
|
|
186
|
+
});
|
|
187
|
+
res.end(data);
|
|
188
|
+
}
|
|
189
|
+
function pcmToWav(pcmData, sampleRate, channels, bitsPerSample) {
|
|
190
|
+
const byteRate = sampleRate * channels * (bitsPerSample / 8);
|
|
191
|
+
const blockAlign = channels * (bitsPerSample / 8);
|
|
192
|
+
const dataSize = pcmData.length;
|
|
193
|
+
const headerSize = 44;
|
|
194
|
+
const buffer = Buffer.alloc(headerSize + dataSize);
|
|
195
|
+
buffer.write("RIFF", 0);
|
|
196
|
+
buffer.writeUInt32LE(36 + dataSize, 4);
|
|
197
|
+
buffer.write("WAVE", 8);
|
|
198
|
+
buffer.write("fmt ", 12);
|
|
199
|
+
buffer.writeUInt32LE(16, 16);
|
|
200
|
+
buffer.writeUInt16LE(1, 20);
|
|
201
|
+
buffer.writeUInt16LE(channels, 22);
|
|
202
|
+
buffer.writeUInt32LE(sampleRate, 24);
|
|
203
|
+
buffer.writeUInt32LE(byteRate, 28);
|
|
204
|
+
buffer.writeUInt16LE(blockAlign, 32);
|
|
205
|
+
buffer.writeUInt16LE(bitsPerSample, 34);
|
|
206
|
+
buffer.write("data", 36);
|
|
207
|
+
buffer.writeUInt32LE(dataSize, 40);
|
|
208
|
+
pcmData.copy(buffer, headerSize);
|
|
209
|
+
return buffer;
|
|
210
|
+
}
|
|
211
|
+
export {
|
|
212
|
+
MiMoTTS
|
|
213
|
+
};
|
|
214
|
+
//# sourceMappingURL=mimo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/tts/mimo.ts"],"sourcesContent":["/**\n * MiMo-V2.5-TT TTS Provider\n *\n * 使用小米 MiMo TTS API 生成语音,通过本地 HTTP 服务器提供给音箱播放。\n * API 文档: https://mimo.mi.com/docs/zh-CN/quick-start/usage-guide/multimodal-understanding/speech-synthesis-v2.5\n */\nimport { createServer, type Server } from 'node:http';\nimport { writeFile, mkdir, rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\n\nexport interface MiMoTTSConfig {\n /** MiMo API Key */\n apiKey: string;\n /** MiMo API Base URL,默认 https://api.xiaomimimo.com/v1 */\n baseUrl?: string;\n /** TTS 模型,默认 mimo-v2.5-tts */\n model?: string;\n /** 预设音色 ID,默认 mimo_default */\n voice?: string;\n /** 风格指令(放在 user role 中) */\n style?: string;\n /** 是否启用流式传输(减少首字延迟),默认 true */\n stream?: boolean;\n /** 本地服务器监听端口,0 = 自动分配 */\n port?: number;\n /** 本地服务器监听地址,默认 0.0.0.0 */\n host?: string;\n}\n\nexport class MiMoTTS {\n private _server: Server | null = null;\n private _serverUrl = '';\n private _audioDir = '';\n private _config: Required<MiMoTTSConfig>;\n private _ready = false;\n\n constructor(config: MiMoTTSConfig) {\n this._config = {\n apiKey: config.apiKey,\n baseUrl: config.baseUrl ?? 'https://api.xiaomimimo.com/v1',\n model: config.model ?? 'mimo-v2.5-tts',\n voice: config.voice ?? 'mimo_default',\n style: config.style ?? '',\n stream: config.stream ?? true,\n port: config.port ?? 0,\n host: config.host ?? '0.0.0.0',\n };\n }\n\n /**\n * 初始化:创建临时目录 + 启动 HTTP 服务器\n */\n async init(): Promise<boolean> {\n if (this._ready) return true;\n\n try {\n this._audioDir = join(tmpdir(), 'migpt-claw-tts', randomUUID());\n await mkdir(this._audioDir, { recursive: true });\n\n this._server = createServer((req, res) => {\n const url = decodeURIComponent(req.url ?? '');\n if (!url.startsWith('/audio/')) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n\n const filePath = join(this._audioDir, url.replace('/audio/', ''));\n serveWavFile(filePath, res).catch(() => {\n res.writeHead(404);\n res.end('Not Found');\n });\n });\n\n await new Promise<void>((resolve, reject) => {\n this._server!.listen(this._config.port, this._config.host, () => {\n const addr = this._server!.address();\n if (addr && typeof addr === 'object') {\n this._serverUrl = `http://${addr.address === '::' ? 'localhost' : addr.address}:${addr.port}`;\n this._ready = true;\n resolve();\n } else {\n reject(new Error('Failed to get server address'));\n }\n });\n this._server!.on('error', reject);\n });\n\n console.log(`✅ MiMo TTS 服务器已启动: ${this._serverUrl}`);\n return true;\n } catch (err: any) {\n console.error('❌ MiMo TTS 初始化失败:', err.message);\n return false;\n }\n }\n\n /**\n * 生成语音并返回可播放的 URL\n */\n async synthesize(text: string, options?: {\n voice?: string;\n style?: string;\n }): Promise<{ url: string; success: boolean; error?: string; duration?: number }> {\n if (!this._ready) {\n const ok = await this.init();\n if (!ok) return { url: '', success: false, error: 'TTS server not initialized' };\n }\n\n try {\n // 构建请求\n const messages: Array<{ role: string; content: string }> = [];\n\n const style = options?.style ?? this._config.style;\n if (style) {\n messages.push({ role: 'user', content: style });\n }\n\n messages.push({ role: 'assistant', content: text });\n\n const voice = options?.voice ?? this._config.voice;\n\n const useStream = this._config.stream;\n\n const body = {\n model: this._config.model,\n messages,\n audio: { format: useStream ? 'pcm16' : 'wav', voice },\n stream: useStream,\n };\n\n // 调用 MiMo API\n const response = await fetch(`${this._config.baseUrl}/chat/completions`, {\n method: 'POST',\n headers: {\n 'api-key': this._config.apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errText = await response.text().catch(() => 'unknown');\n return { url: '', success: false, error: `MiMo API error ${response.status}: ${errText}` };\n }\n\n let audioBuffer: Buffer;\n\n if (useStream) {\n // 流式模式:收集所有 PCM16 chunk 并拼接为 WAV\n const pcmChunks: Buffer[] = [];\n const reader = response.body?.getReader();\n if (!reader) {\n return { url: '', success: false, error: 'Failed to read streaming response' };\n }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || !trimmed.startsWith('data: ')) continue;\n const jsonStr = trimmed.slice(6);\n if (jsonStr === '[DONE]') continue;\n\n try {\n const chunk = JSON.parse(jsonStr);\n const audioData = chunk?.choices?.[0]?.delta?.audio?.data;\n if (audioData) {\n pcmChunks.push(Buffer.from(audioData, 'base64'));\n }\n } catch {\n // 忽略解析错误\n }\n }\n }\n\n if (pcmChunks.length === 0) {\n return { url: '', success: false, error: 'No audio data in streaming response' };\n }\n\n // PCM16 → WAV(24kHz, mono, 16-bit)\n const pcmData = Buffer.concat(pcmChunks);\n audioBuffer = pcmToWav(pcmData, 24000, 1, 16);\n } else {\n // 非流式模式:直接从响应中获取 WAV\n const data = await response.json() as any;\n const audioBase64 = data?.choices?.[0]?.message?.audio?.data;\n\n if (!audioBase64) {\n return { url: '', success: false, error: 'No audio data in MiMo response' };\n }\n\n audioBuffer = Buffer.from(audioBase64, 'base64');\n }\n\n // 保存音频文件\n const fileName = `${randomUUID()}.wav`;\n const filePath = join(this._audioDir, fileName);\n await writeFile(filePath, audioBuffer);\n\n const url = `${this._serverUrl}/audio/${fileName}`;\n \n // 计算音频时长(秒)\n // WAV: PCM 16-bit mono 24000 Hz\n const headerSize = 44;\n const sampleRate = 24000;\n const bytesPerSample = 2;\n const duration = (audioBuffer.length - headerSize) / (sampleRate * bytesPerSample);\n \n return { url, success: true, duration };\n } catch (err: any) {\n return { url: '', success: false, error: err.message };\n }\n }\n\n /**\n * 清理临时文件和关闭服务器\n */\n async destroy() {\n if (this._server) {\n await new Promise<void>((resolve) => {\n this._server!.close(() => resolve());\n });\n this._server = null;\n }\n if (this._audioDir) {\n await rm(this._audioDir, { recursive: true, force: true }).catch(() => {});\n }\n this._ready = false;\n }\n\n get ready() {\n return this._ready;\n }\n\n get serverUrl() {\n return this._serverUrl;\n }\n}\n\n/**\n * 将 WAV 文件作为 HTTP 响应返回\n */\nasync function serveWavFile(filePath: string, res: import('node:http').ServerResponse) {\n const { readFile } = await import('node:fs/promises');\n const data = await readFile(filePath);\n res.writeHead(200, {\n 'Content-Type': 'audio/wav',\n 'Content-Length': data.length,\n 'Cache-Control': 'no-cache',\n });\n res.end(data);\n}\n\n/**\n * PCM16 原始数据 → WAV 文件 Buffer\n * @param pcmData PCM16 原始音频数据\n * @param sampleRate 采样率(MiMo TTS 流式输出为 24000)\n * @param channels 声道数(1 = mono)\n * @param bitsPerSample 每样本位数(16)\n */\nfunction pcmToWav(pcmData: Buffer, sampleRate: number, channels: number, bitsPerSample: number): Buffer {\n const byteRate = sampleRate * channels * (bitsPerSample / 8);\n const blockAlign = channels * (bitsPerSample / 8);\n const dataSize = pcmData.length;\n const headerSize = 44;\n const buffer = Buffer.alloc(headerSize + dataSize);\n\n // RIFF header\n buffer.write('RIFF', 0);\n buffer.writeUInt32LE(36 + dataSize, 4);\n buffer.write('WAVE', 8);\n\n // fmt sub-chunk\n buffer.write('fmt ', 12);\n buffer.writeUInt32LE(16, 16); // sub-chunk size\n buffer.writeUInt16LE(1, 20); // PCM format\n buffer.writeUInt16LE(channels, 22);\n buffer.writeUInt32LE(sampleRate, 24);\n buffer.writeUInt32LE(byteRate, 28);\n buffer.writeUInt16LE(blockAlign, 32);\n buffer.writeUInt16LE(bitsPerSample, 34);\n\n // data sub-chunk\n buffer.write('data', 36);\n buffer.writeUInt32LE(dataSize, 40);\n pcmData.copy(buffer, headerSize);\n\n return buffer;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export { ExtendedOpenClawConfig, MiGPTAccountConfig, MiGPTConfig, ResolvedMiAccount } from './config.js';
|
|
2
|
+
import 'openclaw/plugin-sdk';
|
|
3
|
+
import './service.js';
|
|
4
|
+
import './mi/mina.js';
|
|
5
|
+
import './mi/typing.js';
|
|
6
|
+
import './mi/miot.js';
|
|
7
|
+
|
|
8
|
+
interface IMessage {
|
|
9
|
+
id: string;
|
|
10
|
+
sender: 'user';
|
|
11
|
+
text: string;
|
|
12
|
+
timestamp: number;
|
|
13
|
+
deviceId: string;
|
|
14
|
+
}
|
|
15
|
+
interface MiDevice {
|
|
16
|
+
did: string;
|
|
17
|
+
name: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
mac?: string;
|
|
20
|
+
}
|
|
21
|
+
interface MiMessageEvent {
|
|
22
|
+
channel: 'migpt';
|
|
23
|
+
accountId: string;
|
|
24
|
+
from: string;
|
|
25
|
+
fromName: string;
|
|
26
|
+
text: string;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type { IMessage, MiDevice, MiMessageEvent };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { MiPass } from '../mi/typing.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 解析登录响应中的认证参数
|
|
5
|
+
* 参考 migpt-next 实现
|
|
6
|
+
*/
|
|
7
|
+
declare function parseAuthPass(res: string): Partial<{
|
|
8
|
+
code: number;
|
|
9
|
+
description: string;
|
|
10
|
+
captchaUrl: string;
|
|
11
|
+
notificationUrl: string;
|
|
12
|
+
} & MiPass>;
|
|
13
|
+
declare function encodeQuery(data: Record<string, string | number | boolean | undefined>): string;
|
|
14
|
+
declare function decodeQuery(str: string): any;
|
|
15
|
+
/**
|
|
16
|
+
* URL 编码对象为 form 格式
|
|
17
|
+
*/
|
|
18
|
+
declare function encodeFormData(data: Record<string, any>): string;
|
|
19
|
+
interface MIoTRequest {
|
|
20
|
+
data: string;
|
|
21
|
+
signature: string;
|
|
22
|
+
_nonce: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* MIoT 请求编码 - 参考 MiService sign_data
|
|
26
|
+
* 只签名,不加密数据
|
|
27
|
+
*/
|
|
28
|
+
declare function encodeMIoT(uri: string, data: any, ssecurity: string): MIoTRequest;
|
|
29
|
+
declare function decodeMIoT(ssecurity: string, nonce: string, data: string, gzip?: boolean): Promise<any | undefined>;
|
|
30
|
+
|
|
31
|
+
export { decodeMIoT, decodeQuery, encodeFormData, encodeMIoT, encodeQuery, parseAuthPass };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { jsonDecode } from "./parse.js";
|
|
2
|
+
import * as pako from "pako";
|
|
3
|
+
import { randomNoise, signNonce } from "./hash.js";
|
|
4
|
+
import { createHmac } from "crypto";
|
|
5
|
+
import { Debugger } from "./debug.js";
|
|
6
|
+
function parseAuthPass(res) {
|
|
7
|
+
try {
|
|
8
|
+
return jsonDecode(
|
|
9
|
+
res.replace("&&&START&&&", "").replace(/:(\d{9,})/g, ':"$1"')
|
|
10
|
+
// 把 userId 和 nonce 转成 string
|
|
11
|
+
) ?? {};
|
|
12
|
+
} catch {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function encodeQuery(data) {
|
|
17
|
+
return Object.entries(data).map(
|
|
18
|
+
([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value == null ? "" : value.toString())}`
|
|
19
|
+
).join("&");
|
|
20
|
+
}
|
|
21
|
+
function decodeQuery(str) {
|
|
22
|
+
const data = {};
|
|
23
|
+
if (!str) {
|
|
24
|
+
return data;
|
|
25
|
+
}
|
|
26
|
+
const ss = str.split("&");
|
|
27
|
+
for (let i = 0; i < ss.length; i++) {
|
|
28
|
+
const s = ss[i].split("=");
|
|
29
|
+
if (s.length != 2) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const k = decodeURIComponent(s[0]);
|
|
33
|
+
let v = decodeURIComponent(s[1]);
|
|
34
|
+
if (/^\[{/.test(v)) {
|
|
35
|
+
try {
|
|
36
|
+
v = jsonDecode(v);
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
data[k] = v;
|
|
41
|
+
}
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
44
|
+
function encodeFormData(data) {
|
|
45
|
+
return Object.entries(data).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
|
|
46
|
+
}
|
|
47
|
+
class RC4 {
|
|
48
|
+
i = 0;
|
|
49
|
+
j = 0;
|
|
50
|
+
S;
|
|
51
|
+
constructor(key) {
|
|
52
|
+
this.S = Buffer.alloc(256);
|
|
53
|
+
for (let idx = 0; idx < 256; idx++) {
|
|
54
|
+
this.S[idx] = idx;
|
|
55
|
+
}
|
|
56
|
+
let j = 0;
|
|
57
|
+
for (let idx = 0; idx < 256; idx++) {
|
|
58
|
+
j = j + this.S[idx] + key[idx % key.length] & 255;
|
|
59
|
+
[this.S[idx], this.S[j]] = [this.S[j], this.S[idx]];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
update(data) {
|
|
63
|
+
const result = Buffer.alloc(data.length);
|
|
64
|
+
for (let n = 0; n < data.length; n++) {
|
|
65
|
+
this.i = this.i + 1 & 255;
|
|
66
|
+
this.j = this.j + this.S[this.i] & 255;
|
|
67
|
+
[this.S[this.i], this.S[this.j]] = [this.S[this.j], this.S[this.i]];
|
|
68
|
+
const K = this.S[this.S[this.i] + this.S[this.j] & 255];
|
|
69
|
+
result[n] = data[n] ^ K;
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function signMIoT(uri, snonce, nonce, data) {
|
|
75
|
+
const msg = `${uri}&${snonce}&${nonce}&data=${data}`;
|
|
76
|
+
const key = Buffer.from(snonce, "base64");
|
|
77
|
+
return createHmac("sha256", key).update(msg).digest("base64");
|
|
78
|
+
}
|
|
79
|
+
function encodeMIoT(uri, data, ssecurity) {
|
|
80
|
+
const nonce = randomNoise();
|
|
81
|
+
const snonce = signNonce(ssecurity, nonce);
|
|
82
|
+
const json = JSON.stringify(data).replace(/:/g, ": ").replace(/,/g, ", ");
|
|
83
|
+
const signature = signMIoT(uri, snonce, nonce, json);
|
|
84
|
+
if (Debugger.debug) {
|
|
85
|
+
console.log("encodeMIoT \u7B7E\u540D\u8BE6\u60C5:", {
|
|
86
|
+
uri,
|
|
87
|
+
ssecurity: ssecurity.slice(0, 20) + "...",
|
|
88
|
+
nonce,
|
|
89
|
+
snonce,
|
|
90
|
+
json,
|
|
91
|
+
signature,
|
|
92
|
+
signMsg: `${uri}&${snonce}&${nonce}&data=${json}`
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
_nonce: nonce,
|
|
97
|
+
data: json,
|
|
98
|
+
signature
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function decodeMIoT(ssecurity, nonce, data, gzip) {
|
|
102
|
+
try {
|
|
103
|
+
const res2 = jsonDecode(data);
|
|
104
|
+
if (res2) {
|
|
105
|
+
return Promise.resolve(res2);
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
let decrypted;
|
|
110
|
+
try {
|
|
111
|
+
const key = Buffer.from(signNonce(ssecurity, nonce), "base64");
|
|
112
|
+
const rc4 = new RC4(key);
|
|
113
|
+
rc4.update(Buffer.alloc(1024));
|
|
114
|
+
decrypted = Buffer.from(rc4.update(Buffer.from(data, "base64")));
|
|
115
|
+
if (gzip) {
|
|
116
|
+
try {
|
|
117
|
+
decrypted = Buffer.from(pako.ungzip(decrypted));
|
|
118
|
+
} catch (err) {
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
decrypted = Buffer.from(data, "base64");
|
|
123
|
+
if (gzip) {
|
|
124
|
+
try {
|
|
125
|
+
decrypted = Buffer.from(pako.ungzip(decrypted));
|
|
126
|
+
} catch (err) {
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const res = jsonDecode(decrypted.toString());
|
|
131
|
+
if (!res) {
|
|
132
|
+
console.error("\u274C decodeMIoT failed");
|
|
133
|
+
}
|
|
134
|
+
return Promise.resolve(res);
|
|
135
|
+
}
|
|
136
|
+
export {
|
|
137
|
+
decodeMIoT,
|
|
138
|
+
decodeQuery,
|
|
139
|
+
encodeFormData,
|
|
140
|
+
encodeMIoT,
|
|
141
|
+
encodeQuery,
|
|
142
|
+
parseAuthPass
|
|
143
|
+
};
|
|
144
|
+
//# sourceMappingURL=codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/codec.ts"],"sourcesContent":["import { jsonDecode } from './parse.js';\nimport * as pako from 'pako';\nimport { randomNoise, signNonce } from './hash.js';\nimport { createHmac } from 'crypto';\nimport { Debugger } from './debug.js';\nimport type { MiPass } from '../mi/typing.js';\n\n/**\n * 解析登录响应中的认证参数\n * 参考 migpt-next 实现\n */\nexport function parseAuthPass(res: string): Partial<{\n code: number;\n description: string;\n captchaUrl: string;\n notificationUrl: string;\n} & MiPass> {\n try {\n // 如果传入的是 HttpResponse 对象,提取 data 字段\n return (\n jsonDecode(\n res\n .replace('&&&START&&&', '') // 去除前缀\n .replace(/:(\\d{9,})/g, ':\"$1\"'), // 把 userId 和 nonce 转成 string\n ) ?? {}\n );\n } catch {\n return {};\n }\n}\n\nexport function encodeQuery(data: Record<string, string | number | boolean | undefined>): string {\n return Object.entries(data)\n .map(\n ([key, value]) =>\n `${encodeURIComponent(key)}=${encodeURIComponent(value == null ? '' : value.toString())}`,\n )\n .join('&');\n}\n\nexport function decodeQuery(str: string) {\n const data: any = {};\n if (!str) {\n return data;\n }\n const ss: any = str.split('&');\n for (let i = 0; i < ss.length; i++) {\n const s = ss[i].split('=');\n if (s.length != 2) {\n continue;\n }\n const k = decodeURIComponent(s[0]);\n let v: any = decodeURIComponent(s[1]);\n if (/^\\[{/.test(v)) {\n try {\n v = jsonDecode(v);\n } catch {\n // ignore\n }\n }\n data[k] = v;\n }\n return data;\n}\n\n/**\n * URL 编码对象为 form 格式\n */\nexport function encodeFormData(data: Record<string, any>): string {\n return Object.entries(data)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n .join('&');\n}\n\ninterface MIoTRequest {\n data: string;\n signature: string;\n _nonce: string;\n}\n\n// RC4 实现(用于兼容旧版本和响应解密)\nclass RC4 {\n private i: number = 0;\n private j: number = 0;\n private S: Buffer;\n\n constructor(key: Buffer) {\n this.S = Buffer.alloc(256);\n for (let idx = 0; idx < 256; idx++) {\n this.S[idx] = idx;\n }\n let j = 0;\n for (let idx = 0; idx < 256; idx++) {\n j = (j + this.S[idx] + key[idx % key.length]) & 0xff;\n [this.S[idx], this.S[j]] = [this.S[j], this.S[idx]];\n }\n }\n\n update(data: Buffer): Buffer {\n const result = Buffer.alloc(data.length);\n for (let n = 0; n < data.length; n++) {\n this.i = (this.i + 1) & 0xff;\n this.j = (this.j + this.S[this.i]) & 0xff;\n [this.S[this.i], this.S[this.j]] = [this.S[this.j], this.S[this.i]];\n const K = this.S[(this.S[this.i] + this.S[this.j]) & 0xff];\n result[n] = data[n] ^ K;\n }\n return result;\n }\n}\n\n/**\n * MIoT 签名算法 - 参考 MiService Python 实现\n * 签名格式:uri&snonce&nonce&data=xxx\n * 使用 HMAC-SHA256\n * 注意:只签名,不加密数据!\n */\nfunction signMIoT(uri: string, snonce: string, nonce: string, data: string): string {\n const msg = `${uri}&${snonce}&${nonce}&data=${data}`;\n const key = Buffer.from(snonce, 'base64');\n return createHmac('sha256', key).update(msg).digest('base64');\n}\n\n/**\n * MIoT 请求编码 - 参考 MiService sign_data\n * 只签名,不加密数据\n */\nexport function encodeMIoT(uri: string, data: any, ssecurity: string): MIoTRequest {\n const nonce = randomNoise();\n const snonce = signNonce(ssecurity, nonce) as string;\n // Python 的 json.dumps 默认格式:\": \" 和 \", \"\n // JavaScript 的 JSON.stringify 默认紧凑格式,需要手动替换\n const json = JSON.stringify(data).replace(/:/g, ': ').replace(/,/g, ', ');\n\n // HMAC-SHA256 签名\n const signature = signMIoT(uri, snonce, nonce, json);\n\n if (Debugger.debug) {\n console.log('encodeMIoT 签名详情:', {\n uri,\n ssecurity: ssecurity.slice(0, 20) + '...',\n nonce,\n snonce,\n json,\n signature,\n signMsg: `${uri}&${snonce}&${nonce}&data=${json}`,\n });\n }\n\n return {\n _nonce: nonce,\n data: json,\n signature: signature,\n };\n}\n\nexport function decodeMIoT(\n ssecurity: string,\n nonce: string,\n data: string,\n gzip?: boolean,\n): Promise<any | undefined> {\n // 先尝试直接解析 JSON(MiService 响应是明文)\n try {\n const res = jsonDecode(data);\n if (res) {\n return Promise.resolve(res);\n }\n } catch {\n // ignore\n }\n \n // 如果直接解析失败,尝试 RC4 解密(兼容旧版本)\n let decrypted: Buffer;\n try {\n const key = Buffer.from(signNonce(ssecurity, nonce), 'base64');\n const rc4 = new RC4(key);\n rc4.update(Buffer.alloc(1024));\n decrypted = Buffer.from(rc4.update(Buffer.from(data, 'base64')));\n // 如果 RC4 解密成功,尝试 gzip 解压\n if (gzip) {\n try {\n decrypted = Buffer.from(pako.ungzip(decrypted));\n } catch (err) {\n // ignore gzip error\n }\n }\n } catch {\n // 如果不是 RC4 加密的数据,直接使用原始数据\n decrypted = Buffer.from(data, 'base64');\n // 如果需要 gzip 解压\n if (gzip) {\n try {\n decrypted = Buffer.from(pako.ungzip(decrypted));\n } catch (err) {\n // ignore gzip error\n }\n }\n }\n \n const res = jsonDecode(decrypted.toString());\n if (!res) {\n console.error('❌ decodeMIoT failed');\n }\n return Promise.resolve(res);\n}\n"],"mappings":"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","names":["res"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class Debugger {
|
|
2
|
+
static debug = false;
|
|
3
|
+
static log(...args) {
|
|
4
|
+
if (this.debug) {
|
|
5
|
+
console.log("[MiGPT]", ...args);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
static error(...args) {
|
|
9
|
+
console.error("[MiGPT]", ...args);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
Debugger
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=debug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/debug.ts"],"sourcesContent":["/**\n * 调试工具\n */\nexport class Debugger {\n static debug = false;\n\n static log(...args: any[]) {\n if (this.debug) {\n console.log('[MiGPT]', ...args);\n }\n }\n\n static error(...args: any[]) {\n console.error('[MiGPT]', ...args);\n }\n}\n"],"mappings":"AAGO,MAAM,SAAS;AAAA,EACpB,OAAO,QAAQ;AAAA,EAEf,OAAO,OAAO,MAAa;AACzB,QAAI,KAAK,OAAO;AACd,cAAQ,IAAI,WAAW,GAAG,IAAI;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,OAAO,SAAS,MAAa;AAC3B,YAAQ,MAAM,WAAW,GAAG,IAAI;AAAA,EAClC;AACF;","names":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MD5 哈希
|
|
3
|
+
*/
|
|
4
|
+
declare function md5(data: string | Buffer): string;
|
|
5
|
+
/**
|
|
6
|
+
* SHA1 哈希
|
|
7
|
+
*/
|
|
8
|
+
declare function sha1(data: string | Buffer): string;
|
|
9
|
+
/**
|
|
10
|
+
* SHA256 HMAC
|
|
11
|
+
*/
|
|
12
|
+
declare function sha256(snonce: string, msg: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* 签名 nonce(用于 MIoT)
|
|
15
|
+
* 参考 migpt-next 实现
|
|
16
|
+
*/
|
|
17
|
+
declare function signNonce(ssecurity: string, nonce: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* 生成 UUID
|
|
20
|
+
*/
|
|
21
|
+
declare function uuid(): string;
|
|
22
|
+
/**
|
|
23
|
+
* 生成随机字符串
|
|
24
|
+
*/
|
|
25
|
+
declare function randomString(len: number): string;
|
|
26
|
+
/**
|
|
27
|
+
* 生成随机噪声(用于 MIoT nonce)
|
|
28
|
+
* 参考 MiService Python 实现:urandom(8) + int(time.time() / 60).to_bytes(4, "big")
|
|
29
|
+
*/
|
|
30
|
+
declare function randomNoise(): string;
|
|
31
|
+
/**
|
|
32
|
+
* RC4 加密
|
|
33
|
+
*/
|
|
34
|
+
declare function rc4Encrypt(key: Buffer, data: Buffer): Buffer;
|
|
35
|
+
/**
|
|
36
|
+
* RC4 解密(与加密相同)
|
|
37
|
+
*/
|
|
38
|
+
declare function rc4Decrypt(key: Buffer, data: Buffer): Buffer;
|
|
39
|
+
|
|
40
|
+
export { md5, randomNoise, randomString, rc4Decrypt, rc4Encrypt, sha1, sha256, signNonce, uuid };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
function md5(data) {
|
|
3
|
+
return crypto.createHash("md5").update(data).digest("hex");
|
|
4
|
+
}
|
|
5
|
+
function sha1(data) {
|
|
6
|
+
return crypto.createHash("sha1").update(data).digest("base64");
|
|
7
|
+
}
|
|
8
|
+
function sha256(snonce, msg) {
|
|
9
|
+
return crypto.createHmac("sha256", Buffer.from(snonce, "base64")).update(msg).digest("base64");
|
|
10
|
+
}
|
|
11
|
+
function signNonce(ssecurity, nonce) {
|
|
12
|
+
const m = crypto.createHash("sha256");
|
|
13
|
+
m.update(ssecurity, "base64");
|
|
14
|
+
m.update(nonce, "base64");
|
|
15
|
+
return m.digest().toString("base64");
|
|
16
|
+
}
|
|
17
|
+
function uuid() {
|
|
18
|
+
return crypto.randomUUID();
|
|
19
|
+
}
|
|
20
|
+
function randomString(len) {
|
|
21
|
+
if (len < 1) return "";
|
|
22
|
+
const s = Math.random().toString(36).slice(2);
|
|
23
|
+
return s + randomString(len - s.length);
|
|
24
|
+
}
|
|
25
|
+
function randomNoise() {
|
|
26
|
+
const randomBytes = crypto.randomBytes(8);
|
|
27
|
+
const timeInMinutes = Math.floor(Date.now() / 1e3 / 60);
|
|
28
|
+
const timeBuffer = Buffer.alloc(4);
|
|
29
|
+
timeBuffer.writeUInt32BE(timeInMinutes >>> 0, 0);
|
|
30
|
+
return Buffer.concat([randomBytes, timeBuffer]).toString("base64");
|
|
31
|
+
}
|
|
32
|
+
function rc4Encrypt(key, data) {
|
|
33
|
+
const ksa = (key2) => {
|
|
34
|
+
const S2 = new Array(256);
|
|
35
|
+
for (let i = 0; i < 256; i++) {
|
|
36
|
+
S2[i] = i;
|
|
37
|
+
}
|
|
38
|
+
let j = 0;
|
|
39
|
+
for (let i = 0; i < 256; i++) {
|
|
40
|
+
j = j + S2[i] + key2[i % key2.length] & 255;
|
|
41
|
+
[S2[i], S2[j]] = [S2[j], S2[i]];
|
|
42
|
+
}
|
|
43
|
+
return S2;
|
|
44
|
+
};
|
|
45
|
+
const prga = (S2, data2) => {
|
|
46
|
+
const result = Buffer.alloc(data2.length);
|
|
47
|
+
let i = 0;
|
|
48
|
+
let j = 0;
|
|
49
|
+
for (let n = 0; n < data2.length; n++) {
|
|
50
|
+
i = i + 1 & 255;
|
|
51
|
+
j = j + S2[i] & 255;
|
|
52
|
+
[S2[i], S2[j]] = [S2[j], S2[i]];
|
|
53
|
+
const K = S2[S2[i] + S2[j] & 255];
|
|
54
|
+
result[n] = data2[n] ^ K;
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
|
+
const S = ksa(key);
|
|
59
|
+
return prga(S, data);
|
|
60
|
+
}
|
|
61
|
+
function rc4Decrypt(key, data) {
|
|
62
|
+
return rc4Encrypt(key, data);
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
md5,
|
|
66
|
+
randomNoise,
|
|
67
|
+
randomString,
|
|
68
|
+
rc4Decrypt,
|
|
69
|
+
rc4Encrypt,
|
|
70
|
+
sha1,
|
|
71
|
+
sha256,
|
|
72
|
+
signNonce,
|
|
73
|
+
uuid
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/hash.ts"],"sourcesContent":["import * as crypto from 'node:crypto';\n\n/**\n * MD5 哈希\n */\nexport function md5(data: string | Buffer): string {\n return crypto.createHash('md5').update(data).digest('hex');\n}\n\n/**\n * SHA1 哈希\n */\nexport function sha1(data: string | Buffer): string {\n return crypto.createHash('sha1').update(data).digest('base64');\n}\n\n/**\n * SHA256 HMAC\n */\nexport function sha256(snonce: string, msg: string): string {\n return crypto.createHmac('sha256', Buffer.from(snonce, 'base64')).update(msg).digest('base64');\n}\n\n/**\n * 签名 nonce(用于 MIoT)\n * 参考 migpt-next 实现\n */\nexport function signNonce(ssecurity: string, nonce: string): string {\n const m = crypto.createHash('sha256');\n m.update(ssecurity, 'base64');\n m.update(nonce, 'base64');\n return m.digest().toString('base64');\n}\n\n/**\n * 生成 UUID\n */\nexport function uuid(): string {\n return crypto.randomUUID();\n}\n\n/**\n * 生成随机字符串\n */\nexport function randomString(len: number): string {\n if (len < 1) return '';\n const s = Math.random().toString(36).slice(2);\n return s + randomString(len - s.length);\n}\n\n/**\n * 生成随机噪声(用于 MIoT nonce)\n * 参考 MiService Python 实现:urandom(8) + int(time.time() / 60).to_bytes(4, \"big\")\n */\nexport function randomNoise(): string {\n const randomBytes = crypto.randomBytes(8);\n const timeInMinutes = Math.floor(Date.now() / 1000 / 60);\n // 使用 Uint32BE 写入 4 字节时间戳(大端序)\n const timeBuffer = Buffer.alloc(4);\n timeBuffer.writeUInt32BE(timeInMinutes >>> 0, 0);\n return Buffer.concat([randomBytes, timeBuffer]).toString('base64');\n}\n\n/**\n * RC4 加密\n */\nexport function rc4Encrypt(key: Buffer, data: Buffer): Buffer {\n const ksa = (key: Buffer): number[] => {\n const S = new Array(256);\n for (let i = 0; i < 256; i++) {\n S[i] = i;\n }\n let j = 0;\n for (let i = 0; i < 256; i++) {\n j = (j + S[i] + key[i % key.length]) & 0xff;\n [S[i], S[j]] = [S[j], S[i]];\n }\n return S;\n };\n\n const prga = (S: number[], data: Buffer): Buffer => {\n const result = Buffer.alloc(data.length);\n let i = 0;\n let j = 0;\n for (let n = 0; n < data.length; n++) {\n i = (i + 1) & 0xff;\n j = (j + S[i]) & 0xff;\n [S[i], S[j]] = [S[j], S[i]];\n const K = S[(S[i] + S[j]) & 0xff];\n result[n] = data[n] ^ K;\n }\n return result;\n };\n\n const S = ksa(key);\n return prga(S, data);\n}\n\n/**\n * RC4 解密(与加密相同)\n */\nexport function rc4Decrypt(key: Buffer, data: Buffer): Buffer {\n return rc4Encrypt(key, data);\n}\n"],"mappings":"AAAA,YAAY,YAAY;AAKjB,SAAS,IAAI,MAA+B;AACjD,SAAO,OAAO,WAAW,KAAK,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAC3D;AAKO,SAAS,KAAK,MAA+B;AAClD,SAAO,OAAO,WAAW,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,QAAQ;AAC/D;AAKO,SAAS,OAAO,QAAgB,KAAqB;AAC1D,SAAO,OAAO,WAAW,UAAU,OAAO,KAAK,QAAQ,QAAQ,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,QAAQ;AAC/F;AAMO,SAAS,UAAU,WAAmB,OAAuB;AAClE,QAAM,IAAI,OAAO,WAAW,QAAQ;AACpC,IAAE,OAAO,WAAW,QAAQ;AAC5B,IAAE,OAAO,OAAO,QAAQ;AACxB,SAAO,EAAE,OAAO,EAAE,SAAS,QAAQ;AACrC;AAKO,SAAS,OAAe;AAC7B,SAAO,OAAO,WAAW;AAC3B;AAKO,SAAS,aAAa,KAAqB;AAChD,MAAI,MAAM,EAAG,QAAO;AACpB,QAAM,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC;AAC5C,SAAO,IAAI,aAAa,MAAM,EAAE,MAAM;AACxC;AAMO,SAAS,cAAsB;AACpC,QAAM,cAAc,OAAO,YAAY,CAAC;AACxC,QAAM,gBAAgB,KAAK,MAAM,KAAK,IAAI,IAAI,MAAO,EAAE;AAEvD,QAAM,aAAa,OAAO,MAAM,CAAC;AACjC,aAAW,cAAc,kBAAkB,GAAG,CAAC;AAC/C,SAAO,OAAO,OAAO,CAAC,aAAa,UAAU,CAAC,EAAE,SAAS,QAAQ;AACnE;AAKO,SAAS,WAAW,KAAa,MAAsB;AAC5D,QAAM,MAAM,CAACA,SAA0B;AACrC,UAAMC,KAAI,IAAI,MAAM,GAAG;AACvB,aAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,MAAAA,GAAE,CAAC,IAAI;AAAA,IACT;AACA,QAAI,IAAI;AACR,aAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAK,IAAIA,GAAE,CAAC,IAAID,KAAI,IAAIA,KAAI,MAAM,IAAK;AACvC,OAACC,GAAE,CAAC,GAAGA,GAAE,CAAC,CAAC,IAAI,CAACA,GAAE,CAAC,GAAGA,GAAE,CAAC,CAAC;AAAA,IAC5B;AACA,WAAOA;AAAA,EACT;AAEA,QAAM,OAAO,CAACA,IAAaC,UAAyB;AAClD,UAAM,SAAS,OAAO,MAAMA,MAAK,MAAM;AACvC,QAAI,IAAI;AACR,QAAI,IAAI;AACR,aAAS,IAAI,GAAG,IAAIA,MAAK,QAAQ,KAAK;AACpC,UAAK,IAAI,IAAK;AACd,UAAK,IAAID,GAAE,CAAC,IAAK;AACjB,OAACA,GAAE,CAAC,GAAGA,GAAE,CAAC,CAAC,IAAI,CAACA,GAAE,CAAC,GAAGA,GAAE,CAAC,CAAC;AAC1B,YAAM,IAAIA,GAAGA,GAAE,CAAC,IAAIA,GAAE,CAAC,IAAK,GAAI;AAChC,aAAO,CAAC,IAAIC,MAAK,CAAC,IAAI;AAAA,IACxB;AACA,WAAO;AAAA,EACT;AAEA,QAAM,IAAI,IAAI,GAAG;AACjB,SAAO,KAAK,GAAG,IAAI;AACrB;AAKO,SAAS,WAAW,KAAa,MAAsB;AAC5D,SAAO,WAAW,KAAK,IAAI;AAC7B;","names":["key","S","data"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MiAccount } from '../mi/typing.js';
|
|
2
|
+
|
|
3
|
+
interface HttpError {
|
|
4
|
+
isError: true;
|
|
5
|
+
error: any;
|
|
6
|
+
code: string;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
type RequestConfig = any & {
|
|
10
|
+
account?: MiAccount;
|
|
11
|
+
setAccount?: (newAccount: any) => void;
|
|
12
|
+
rawResponse?: boolean;
|
|
13
|
+
cookies?: Record<string, string | number | boolean | undefined>;
|
|
14
|
+
};
|
|
15
|
+
declare class HTTPClient {
|
|
16
|
+
timeout: number;
|
|
17
|
+
get<T = any>(url: string, _query?: Record<string, string | number | boolean | undefined> | RequestConfig, _config?: RequestConfig): Promise<T | HttpError>;
|
|
18
|
+
post<T = any>(url: string, data?: any, config?: RequestConfig): Promise<T | HttpError>;
|
|
19
|
+
static buildURL: (url: string, query?: Record<string, any>) => string;
|
|
20
|
+
static buildConfig: (config?: RequestConfig) => any;
|
|
21
|
+
}
|
|
22
|
+
declare const Http: HTTPClient;
|
|
23
|
+
|
|
24
|
+
export { Http };
|