@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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +690 -0
  3. package/dist/index.d.ts +23 -0
  4. package/dist/index.js +33 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/setup-entry.d.ts +3 -0
  7. package/dist/setup-entry.js +7 -0
  8. package/dist/setup-entry.js.map +1 -0
  9. package/dist/src/channel.d.ts +10 -0
  10. package/dist/src/channel.js +444 -0
  11. package/dist/src/channel.js.map +1 -0
  12. package/dist/src/config.d.ts +125 -0
  13. package/dist/src/config.js +146 -0
  14. package/dist/src/config.js.map +1 -0
  15. package/dist/src/message.d.ts +51 -0
  16. package/dist/src/message.js +145 -0
  17. package/dist/src/message.js.map +1 -0
  18. package/dist/src/mi/account.d.ts +5 -0
  19. package/dist/src/mi/account.js +162 -0
  20. package/dist/src/mi/account.js.map +1 -0
  21. package/dist/src/mi/common.d.ts +15 -0
  22. package/dist/src/mi/common.js +80 -0
  23. package/dist/src/mi/common.js.map +1 -0
  24. package/dist/src/mi/index.d.ts +4 -0
  25. package/dist/src/mi/index.js +10 -0
  26. package/dist/src/mi/index.js.map +1 -0
  27. package/dist/src/mi/mina.d.ts +66 -0
  28. package/dist/src/mi/mina.js +225 -0
  29. package/dist/src/mi/mina.js.map +1 -0
  30. package/dist/src/mi/miot.d.ts +35 -0
  31. package/dist/src/mi/miot.js +168 -0
  32. package/dist/src/mi/miot.js.map +1 -0
  33. package/dist/src/mi/typing.d.ts +90 -0
  34. package/dist/src/mi/typing.js +1 -0
  35. package/dist/src/mi/typing.js.map +1 -0
  36. package/dist/src/onboarding.d.ts +5 -0
  37. package/dist/src/onboarding.js +118 -0
  38. package/dist/src/onboarding.js.map +1 -0
  39. package/dist/src/openclaw-plugin-sdk.d.d.ts +185 -0
  40. package/dist/src/openclaw-plugin-sdk.d.js +1 -0
  41. package/dist/src/openclaw-plugin-sdk.d.js.map +1 -0
  42. package/dist/src/outbound.d.ts +5 -0
  43. package/dist/src/outbound.js +108 -0
  44. package/dist/src/outbound.js.map +1 -0
  45. package/dist/src/runtime.d.ts +6 -0
  46. package/dist/src/runtime.js +15 -0
  47. package/dist/src/runtime.js.map +1 -0
  48. package/dist/src/service.d.ts +70 -0
  49. package/dist/src/service.js +200 -0
  50. package/dist/src/service.js.map +1 -0
  51. package/dist/src/speaker.d.ts +62 -0
  52. package/dist/src/speaker.js +211 -0
  53. package/dist/src/speaker.js.map +1 -0
  54. package/dist/src/tts/mimo.d.ts +50 -0
  55. package/dist/src/tts/mimo.js +214 -0
  56. package/dist/src/tts/mimo.js.map +1 -0
  57. package/dist/src/types.d.ts +30 -0
  58. package/dist/src/types.js +1 -0
  59. package/dist/src/types.js.map +1 -0
  60. package/dist/src/utils/codec.d.ts +31 -0
  61. package/dist/src/utils/codec.js +144 -0
  62. package/dist/src/utils/codec.js.map +1 -0
  63. package/dist/src/utils/debug.d.ts +10 -0
  64. package/dist/src/utils/debug.js +15 -0
  65. package/dist/src/utils/debug.js.map +1 -0
  66. package/dist/src/utils/hash.d.ts +40 -0
  67. package/dist/src/utils/hash.js +75 -0
  68. package/dist/src/utils/hash.js.map +1 -0
  69. package/dist/src/utils/http.d.ts +24 -0
  70. package/dist/src/utils/http.js +151 -0
  71. package/dist/src/utils/http.js.map +1 -0
  72. package/dist/src/utils/index.d.ts +6 -0
  73. package/dist/src/utils/index.js +10 -0
  74. package/dist/src/utils/index.js.map +1 -0
  75. package/dist/src/utils/io.d.ts +26 -0
  76. package/dist/src/utils/io.js +53 -0
  77. package/dist/src/utils/io.js.map +1 -0
  78. package/dist/src/utils/parse.d.ts +26 -0
  79. package/dist/src/utils/parse.js +51 -0
  80. package/dist/src/utils/parse.js.map +1 -0
  81. package/index.ts +26 -0
  82. package/openclaw.plugin.json +344 -0
  83. package/package.json +106 -0
  84. package/setup-entry.ts +12 -0
  85. package/skills/migpt-volume/SKILL.md +182 -0
  86. package/skills/migpt-volume/index.ts +50 -0
  87. package/src/channel.ts +519 -0
  88. package/src/config.ts +299 -0
  89. package/src/message.ts +186 -0
  90. package/src/mi/account.ts +184 -0
  91. package/src/mi/common.ts +105 -0
  92. package/src/mi/index.ts +4 -0
  93. package/src/mi/mina.ts +261 -0
  94. package/src/mi/miot.ts +193 -0
  95. package/src/mi/typing.ts +93 -0
  96. package/src/onboarding.ts +136 -0
  97. package/src/openclaw-plugin-sdk.d.ts +185 -0
  98. package/src/outbound.ts +137 -0
  99. package/src/runtime.ts +14 -0
  100. package/src/service.ts +246 -0
  101. package/src/speaker.ts +264 -0
  102. package/src/tts/mimo.ts +300 -0
  103. package/src/types.ts +34 -0
  104. package/src/utils/codec.ts +206 -0
  105. package/src/utils/debug.ts +16 -0
  106. package/src/utils/hash.ts +104 -0
  107. package/src/utils/http.ts +193 -0
  108. package/src/utils/index.ts +5 -0
  109. package/src/utils/io.ts +68 -0
  110. package/src/utils/parse.ts +64 -0
  111. package/tsconfig.json +25 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/mi/account.ts"],"sourcesContent":["import { encodeQuery, parseAuthPass } from '../utils/codec.js';\nimport { md5, sha1 } from '../utils/hash.js';\nimport { Http } from '../utils/http.js';\nimport { MiNA } from './mina.js';\nimport { MIoT } from './miot.js';\nimport type { MiAccount } from './typing.js';\n\nconst kLoginAPI = 'https://account.xiaomi.com/pass';\n\nexport async function getAccount(_account: MiAccount): Promise<MiAccount | undefined> {\n let account = _account;\n \n // 打印使用的认证方式\n console.log('🔐 认证信息:', {\n userId: account.userId,\n hasPassToken: !!account.passToken,\n hasPassword: !!account.password,\n hasServiceToken: !!account.serviceToken,\n authMode: account.password ? 'password' : (account.passToken ? 'passToken' : 'unknown'),\n });\n \n // 如果已经提供了 passToken 和 serviceToken,尝试直接使用缓存的登录态\n if (account.passToken && account.serviceToken && account.pass?.ssecurity) {\n console.log('🔄 尝试使用缓存的登录态 (passToken + serviceToken + ssecurity)...');\n account.pass = {\n code: 0,\n passToken: account.passToken,\n ssecurity: account.pass.ssecurity,\n nonce: account.pass.nonce || '',\n };\n // 尝试直接获取设备列表,如果失败再重新登录\n // 根据 sid 选择调用对应的服务\n let devices: any;\n if (account.sid === 'micoapi') {\n devices = await MiNA.getDevice(account as any);\n } else if (account.sid === 'xiaomiio') {\n devices = await MIoT.getDevice(account as any);\n } else {\n devices = account;\n }\n if (devices.device) {\n console.log('✅ 使用缓存的登录态成功');\n return devices;\n }\n // 缓存失效,继续登录流程\n console.log('⚠️ 缓存的登录态已失效,使用密码重新登录...');\n }\n \n // 优先使用密码登录(如果有密码)\n if (account.password) {\n console.log('🔑 使用密码登录(passToken 作为 Cookie 辅助)...');\n } else if (account.passToken) {\n console.log('⚠️ 仅有 passToken 无法直接登录,passToken 需要配合密码使用');\n }\n \n let res = await Http.get(\n `${kLoginAPI}/serviceLogin`,\n { sid: account.sid, _json: true, _locale: 'zh_CN' },\n { cookies: _getLoginCookies(account) },\n );\n if (res.isError) {\n console.error('❌ 登录失败', res);\n return undefined;\n }\n let pass = parseAuthPass(res);\n console.log('📝 serviceLogin 响应:', { code: pass.code, description: pass.description,res: res });\n \n if (pass.code !== 0) {\n // 登录态失效,重新登录\n console.log('📝 登录态失效,尝试重新认证...');\n \n if (!account.password) {\n console.error('❌ 缺少密码,无法重新登录。请配置 password 字段。');\n return undefined;\n }\n \n const data = {\n _json: 'true',\n qs: pass.qs,\n sid: account.sid,\n _sign: pass._sign,\n callback: pass.callback,\n user: account.userId,\n hash: md5(account.password).toUpperCase(),\n };\n res = await Http.post(`${kLoginAPI}/serviceLoginAuth2`, encodeQuery(data), {\n cookies: _getLoginCookies(account),\n });\n if (res.isError) {\n console.error('❌ OAuth2 登录失败', res);\n return undefined;\n }\n console.log('返回结果:', res.data);\n pass = parseAuthPass(res);\n console.log('📝 serviceLoginAuth2 响应:', {\n code: pass.code,\n hasPassToken: !!pass.passToken,\n hasSsecurity: !!pass.ssecurity,\n description: pass.description,\n });\n }\n if (pass.location?.includes('identity/authStart')) {\n console.error('❌ 本次登录需要验证码,请检查 passToken 是否正确');\n console.log('💡 当前使用的 passToken:', account.passToken?.slice(0, 20) + '...');\n return undefined;\n }\n if (!pass.location || !pass.nonce || !pass.ssecurity) {\n console.error('❌ 登录失败,请检查你的账号密码是否正确');\n console.log('📋 返回数据:', {\n hasLocation: !!pass.location,\n hasNonce: !!pass.nonce,\n hasSsecurity: !!pass.ssecurity,\n hasPassToken: !!pass.passToken,\n code: pass.code,\n description: pass.description,\n });\n return undefined;\n }\n console.log('✅ 登录成功,获取 serviceToken...');\n // 刷新登录态\n const serviceToken = await _getServiceToken(pass);\n if (!serviceToken) {\n return undefined;\n }\n console.log('✅ 获取 serviceToken 成功',{account:account,serviceToken:serviceToken});\n account = { ...account, pass: pass as any, serviceToken };\n console.log('📱 正在获取设备信息... account.did =', account.did);\n console.log('📱 使用的 deviceId:', account.deviceId);\n \n // 根据 sid 选择调用对应的服务\n if (account.sid === 'micoapi') {\n account = await MiNA.getDevice(account as any);\n console.log('📱 MiNA.getDevice 结果:account.device =', account?.device);\n } else if (account.sid === 'xiaomiio') {\n account = await MIoT.getDevice(account as any);\n console.log('📱 MIoT.getDevice 结果:account.device =', account?.device);\n }\n \n if (account.did && !account.device) {\n console.error(`❌ 找不到设备:${account.did}`);\n console.log(\n '🐛 请检查你的 did 与米家中的设备名称是否一致。注意错别字、空格和大小写,比如:音响 👉 音箱',\n );\n console.log(\n '💡 建议打开 debug 选项,查看目标设备的真实 name、miotDID 或 mac 地址,更新 did 参数',\n );\n return undefined;\n }\n console.log('✅ 设备信息获取成功:', (account.device as any)?.name || (account.device as any)?.alias);\n return account;\n}\n\nfunction _getLoginCookies(account: MiAccount): Record<string, string | undefined> {\n return {\n userId: account.userId,\n deviceId: account.deviceId,\n passToken: account.pass?.passToken,\n sdkVersion: '3.9',\n };\n}\n\nasync function _getServiceToken(pass: any): Promise<string | undefined> {\n const { location, nonce, ssecurity } = pass ?? {};\n if (!location || !nonce || !ssecurity) {\n console.error('❌ 无法获取 serviceToken,缺少必要参数');\n return undefined;\n }\n const nsec = `nonce=${nonce}&${ssecurity}`;\n const clientSign = sha1(nsec);\n const url = location + '&clientSign=' + encodeURIComponent(clientSign);\n \n const res = await Http.get(url, {}, { rawResponse: true });\n \n // 从 set-cookie 中提取 serviceToken\n const cookies = res.headers?.['set-cookie'] ?? [];\n for (const cookie of cookies) {\n const match = cookie.match(/serviceToken=([^;]+)/);\n if (match) {\n return match[1];\n }\n }\n console.error('❌ 获取 Mi Service Token 失败');\n return undefined;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,15 @@
1
+ import { MiNA } from './mina.js';
2
+ import { MIoT } from './miot.js';
3
+ import { MiAccount } from './typing.js';
4
+
5
+ declare function updateMiAccount(account: MiAccount): (updated: MiAccount) => void;
6
+ declare function getMiService(config: {
7
+ service: 'miot' | 'mina';
8
+ userId?: string;
9
+ password?: string;
10
+ passToken?: string;
11
+ did?: string;
12
+ relogin?: boolean;
13
+ }): Promise<MiNA | MIoT | undefined>;
14
+
15
+ export { getMiService, updateMiAccount };
@@ -0,0 +1,80 @@
1
+ import { readJSON, writeJSON } from "../utils/io.js";
2
+ import { getAccount } from "./account.js";
3
+ import { MiNA } from "./mina.js";
4
+ import { MIoT } from "./miot.js";
5
+ import { Debugger } from "../utils/debug.js";
6
+ const kConfigFile = ".mi.json";
7
+ function updateMiAccount(account) {
8
+ return (updated) => {
9
+ if (updated.serviceToken) {
10
+ account.serviceToken = updated.serviceToken;
11
+ }
12
+ if (updated.deviceId) {
13
+ account.deviceId = updated.deviceId;
14
+ }
15
+ if (updated.pass?.ssecurity) {
16
+ if (!account.pass) account.pass = { code: 0 };
17
+ account.pass.ssecurity = updated.pass.ssecurity;
18
+ }
19
+ if (updated.pass?.nonce) {
20
+ if (!account.pass) account.pass = { code: 0 };
21
+ account.pass.nonce = updated.pass.nonce;
22
+ }
23
+ if (updated.pass?.passToken) {
24
+ if (!account.pass) account.pass = { code: 0 };
25
+ account.pass.passToken = updated.pass.passToken;
26
+ }
27
+ };
28
+ }
29
+ async function getMiService(config) {
30
+ const { service, relogin, ...rest } = config;
31
+ const overrides = relogin ? {} : rest;
32
+ if (overrides.passToken) {
33
+ overrides.pass = {
34
+ ...overrides.pass,
35
+ passToken: overrides.passToken
36
+ };
37
+ }
38
+ const store = await readJSON(kConfigFile) ?? {};
39
+ let deviceId = store[service]?.deviceId;
40
+ if (!deviceId) {
41
+ deviceId = Array(16).fill(0).map(
42
+ () => "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[Math.floor(Math.random() * 36)]
43
+ ).join("");
44
+ }
45
+ let account = {
46
+ deviceId,
47
+ ...store[service],
48
+ ...overrides,
49
+ sid: service === "miot" ? "xiaomiio" : "micoapi"
50
+ };
51
+ const cached = store[service];
52
+ if (cached?.pass) {
53
+ account.pass = {
54
+ ...account.pass,
55
+ ...cached.pass
56
+ };
57
+ }
58
+ const hasPassword = !!account.userId && !!account.password;
59
+ if (!hasPassword) {
60
+ console.error("\u274C \u7F3A\u5C11\u5FC5\u9700\u7684\u767B\u5F55\u51ED\u8BC1\uFF1A\u9700\u8981 userId \u548C password");
61
+ console.log("\u{1F4A1} passToken \u662F\u53EF\u9009\u7684\u8F85\u52A9\u51ED\u8BC1\uFF0C\u4E0D\u80FD\u5B8C\u5168\u66FF\u4EE3\u5BC6\u7801");
62
+ console.log('\u{1F4A1} \u914D\u7F6E\u793A\u4F8B\uFF1A{ userId: "123", password: "xxx", passToken: "yyy" }');
63
+ return;
64
+ }
65
+ const result = await getAccount(account);
66
+ if (Debugger.debug) {
67
+ console.log("\u{1F4A1} getAccount \u7ED3\u679C\uFF1A", { result });
68
+ }
69
+ if (!result?.serviceToken || !result.pass?.ssecurity) {
70
+ return void 0;
71
+ }
72
+ store[service] = result;
73
+ await writeJSON(kConfigFile, store);
74
+ return service === "miot" ? new MIoT(result) : new MiNA(result);
75
+ }
76
+ export {
77
+ getMiService,
78
+ updateMiAccount
79
+ };
80
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/mi/common.ts"],"sourcesContent":["import { readJSON, writeJSON } from '../utils/io.js';\nimport { getAccount } from './account.js';\nimport { MiNA } from './mina.js';\nimport { MIoT } from './miot.js';\nimport { Debugger } from '../utils/debug.js';\nimport type { MiAccount, MiPass } from './typing.js';\n\ninterface Store {\n miot?: MiAccount;\n mina?: MiAccount;\n}\n\nconst kConfigFile = '.mi.json';\n\nexport function updateMiAccount(account: MiAccount): (updated: MiAccount) => void {\n return (updated: MiAccount) => {\n if (updated.serviceToken) {\n account.serviceToken = updated.serviceToken;\n }\n if (updated.deviceId) {\n account.deviceId = updated.deviceId;\n }\n if (updated.pass?.ssecurity) {\n if (!account.pass) account.pass = { code: 0 } as MiPass;\n (account.pass as MiPass).ssecurity = updated.pass.ssecurity;\n }\n if (updated.pass?.nonce) {\n if (!account.pass) account.pass = { code: 0 } as MiPass;\n (account.pass as MiPass).nonce = updated.pass.nonce;\n }\n if (updated.pass?.passToken) {\n if (!account.pass) account.pass = { code: 0 } as MiPass;\n (account.pass as MiPass).passToken = updated.pass.passToken;\n }\n };\n}\n\nexport async function getMiService(config: {\n service: 'miot' | 'mina';\n userId?: string;\n password?: string;\n passToken?: string;\n did?: string;\n relogin?: boolean;\n}): Promise<MiNA | MIoT | undefined> {\n const { service, relogin, ...rest } = config;\n const overrides: any = relogin ? {} : rest;\n\n // 如果有 passToken,同时设置 pass 对象\n if (overrides.passToken) {\n overrides.pass = {\n ...overrides.pass,\n passToken: overrides.passToken,\n };\n }\n\n const store: Store = (await readJSON(kConfigFile)) ?? {};\n \n // 从缓存中获取 deviceId,如果没有则生成新的(16 位随机大写字母)\n let deviceId = store[service]?.deviceId;\n if (!deviceId) {\n deviceId = Array(16).fill(0).map(() => \n 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'[Math.floor(Math.random() * 36)]\n ).join('');\n }\n \n // 构建账户信息,优先使用传入的参数,其次使用缓存\n let account = {\n deviceId,\n ...store[service],\n ...overrides,\n sid: service === 'miot' ? 'xiaomiio' : 'micoapi',\n } as MiAccount;\n \n // 从缓存中补充 pass 信息(如果有)\n const cached = store[service];\n if (cached?.pass) {\n account.pass = {\n ...account.pass,\n ...cached.pass,\n };\n }\n \n // 检查是否有足够的凭证\n // passToken 是可选的辅助凭证,password 是必需的登录凭证\n const hasPassword = !!account.userId && !!account.password;\n \n if (!hasPassword) {\n console.error('❌ 缺少必需的登录凭证:需要 userId 和 password');\n console.log('💡 passToken 是可选的辅助凭证,不能完全替代密码');\n console.log('💡 配置示例:{ userId: \"123\", password: \"xxx\", passToken: \"yyy\" }');\n return;\n }\n\n const result = await getAccount(account);\n if (Debugger.debug) {\n console.log('💡 getAccount 结果:',{result:result});\n }\n if (!result?.serviceToken || !result.pass?.ssecurity) {\n return undefined;\n }\n store[service] = result;\n await writeJSON(kConfigFile, store);\n return service === 'miot' ? new MIoT(result as any) : new MiNA(result as any);\n}\n"],"mappings":"AAAA,SAAS,UAAU,iBAAiB;AACpC,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AACrB,SAAS,YAAY;AACrB,SAAS,gBAAgB;AAQzB,MAAM,cAAc;AAEb,SAAS,gBAAgB,SAAkD;AAChF,SAAO,CAAC,YAAuB;AAC7B,QAAI,QAAQ,cAAc;AACxB,cAAQ,eAAe,QAAQ;AAAA,IACjC;AACA,QAAI,QAAQ,UAAU;AACpB,cAAQ,WAAW,QAAQ;AAAA,IAC7B;AACA,QAAI,QAAQ,MAAM,WAAW;AAC3B,UAAI,CAAC,QAAQ,KAAM,SAAQ,OAAO,EAAE,MAAM,EAAE;AAC5C,MAAC,QAAQ,KAAgB,YAAY,QAAQ,KAAK;AAAA,IACpD;AACA,QAAI,QAAQ,MAAM,OAAO;AACvB,UAAI,CAAC,QAAQ,KAAM,SAAQ,OAAO,EAAE,MAAM,EAAE;AAC5C,MAAC,QAAQ,KAAgB,QAAQ,QAAQ,KAAK;AAAA,IAChD;AACA,QAAI,QAAQ,MAAM,WAAW;AAC3B,UAAI,CAAC,QAAQ,KAAM,SAAQ,OAAO,EAAE,MAAM,EAAE;AAC5C,MAAC,QAAQ,KAAgB,YAAY,QAAQ,KAAK;AAAA,IACpD;AAAA,EACF;AACF;AAEA,eAAsB,aAAa,QAOE;AACnC,QAAM,EAAE,SAAS,SAAS,GAAG,KAAK,IAAI;AACtC,QAAM,YAAiB,UAAU,CAAC,IAAI;AAGtC,MAAI,UAAU,WAAW;AACvB,cAAU,OAAO;AAAA,MACf,GAAG,UAAU;AAAA,MACb,WAAW,UAAU;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,QAAgB,MAAM,SAAS,WAAW,KAAM,CAAC;AAGvD,MAAI,WAAW,MAAM,OAAO,GAAG;AAC/B,MAAI,CAAC,UAAU;AACb,eAAW,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE;AAAA,MAAI,MAC/B,uCAAuC,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE,CAAC;AAAA,IACvE,EAAE,KAAK,EAAE;AAAA,EACX;AAGA,MAAI,UAAU;AAAA,IACZ;AAAA,IACA,GAAG,MAAM,OAAO;AAAA,IAChB,GAAG;AAAA,IACH,KAAK,YAAY,SAAS,aAAa;AAAA,EACzC;AAGA,QAAM,SAAS,MAAM,OAAO;AAC5B,MAAI,QAAQ,MAAM;AAChB,YAAQ,OAAO;AAAA,MACb,GAAG,QAAQ;AAAA,MACX,GAAG,OAAO;AAAA,IACZ;AAAA,EACF;AAIA,QAAM,cAAc,CAAC,CAAC,QAAQ,UAAU,CAAC,CAAC,QAAQ;AAElD,MAAI,CAAC,aAAa;AAChB,YAAQ,MAAM,wGAAkC;AAChD,YAAQ,IAAI,4HAAgC;AAC5C,YAAQ,IAAI,8FAA8D;AAC1E;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,WAAW,OAAO;AACvC,MAAI,SAAS,OAAO;AAClB,YAAQ,IAAI,2CAAoB,EAAC,OAAa,CAAC;AAAA,EACjD;AACA,MAAI,CAAC,QAAQ,gBAAgB,CAAC,OAAO,MAAM,WAAW;AACpD,WAAO;AAAA,EACT;AACA,QAAM,OAAO,IAAI;AACjB,QAAM,UAAU,aAAa,KAAK;AAClC,SAAO,YAAY,SAAS,IAAI,KAAK,MAAa,IAAI,IAAI,KAAK,MAAa;AAC9E;","names":[]}
@@ -0,0 +1,4 @@
1
+ export { MiNA } from './mina.js';
2
+ export { MIoT } from './miot.js';
3
+ export { getMiService } from './common.js';
4
+ export { MIoTDevice, MiAccount, MiConversation, MiConversations, MiNADevice, MiPass } from './typing.js';
@@ -0,0 +1,10 @@
1
+ import { MiNA } from "./mina.js";
2
+ import { MIoT } from "./miot.js";
3
+ import { getMiService } from "./common.js";
4
+ export * from "./typing.js";
5
+ export {
6
+ MIoT,
7
+ MiNA,
8
+ getMiService
9
+ };
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/mi/index.ts"],"sourcesContent":["export { MiNA } from './mina.js';\nexport { MIoT } from './miot.js';\nexport { getMiService } from './common.js';\nexport * from './typing.js';\n"],"mappings":"AAAA,SAAS,YAAY;AACrB,SAAS,YAAY;AACrB,SAAS,oBAAoB;AAC7B,cAAc;","names":[]}
@@ -0,0 +1,66 @@
1
+ import { MiAccount, MiNADevice, MiConversations } from './typing.js';
2
+
3
+ type MiNAAccount = MiAccount & {
4
+ device: MiNADevice;
5
+ };
6
+ declare class MiNA {
7
+ account: MiNAAccount;
8
+ constructor(account: MiNAAccount);
9
+ static getDevice(account: MiNAAccount): Promise<MiNAAccount>;
10
+ private static __callMiNA;
11
+ private _callMiNA;
12
+ /**
13
+ * 调用小爱音箱上的 ubus 服务
14
+ */
15
+ callUbus(scope: string, command: string, _message?: any): Promise<any>;
16
+ /**
17
+ * 获取设备列表
18
+ */
19
+ getDevices(): Promise<any>;
20
+ /**
21
+ * 获取设备播放状态
22
+ */
23
+ getStatus(): Promise<{
24
+ volume: number;
25
+ status: 'idle' | 'playing' | 'paused' | 'stopped' | 'unknown';
26
+ media_type?: number;
27
+ loop_type?: number;
28
+ } | undefined>;
29
+ /**
30
+ * 获取音量
31
+ */
32
+ getVolume(): Promise<number | undefined>;
33
+ /**
34
+ * 设置音量
35
+ */
36
+ setVolume(_volume: number): Promise<boolean>;
37
+ /**
38
+ * 播放
39
+ */
40
+ play({ text, url, save }?: {
41
+ text?: string;
42
+ url?: string;
43
+ save?: 0 | 1;
44
+ }): Promise<boolean>;
45
+ /**
46
+ * 暂停播放
47
+ */
48
+ pause(): Promise<boolean>;
49
+ /**
50
+ * 播放或暂停
51
+ */
52
+ playOrPause(): Promise<boolean>;
53
+ /**
54
+ * 停止播放
55
+ */
56
+ stop(): Promise<boolean>;
57
+ /**
58
+ * 获取对话消息列表
59
+ */
60
+ getConversations(options?: {
61
+ limit?: number;
62
+ timestamp?: number;
63
+ }): Promise<MiConversations | undefined>;
64
+ }
65
+
66
+ export { MiNA };
@@ -0,0 +1,225 @@
1
+ import { encodeQuery } from "../utils/codec.js";
2
+ import { Debugger } from "../utils/debug.js";
3
+ import { jsonEncode, jsonDecode } from "../utils/parse.js";
4
+ import { uuid } from "../utils/hash.js";
5
+ import { Http } from "../utils/http.js";
6
+ import { updateMiAccount } from "./common.js";
7
+ class MiNA {
8
+ account;
9
+ constructor(account) {
10
+ this.account = account;
11
+ }
12
+ static async getDevice(account) {
13
+ if (account.sid !== "micoapi") {
14
+ return account;
15
+ }
16
+ const devices = await MiNA.__callMiNA(account, "GET", "/admin/v2/device_list");
17
+ if (Debugger.debug) {
18
+ console.log("\u{1F41B} MiNA \u8BBE\u5907\u5217\u8868\uFF1A", jsonEncode(devices, { prettier: true }));
19
+ }
20
+ console.log("\u{1F50D} \u67E5\u627E\u8BBE\u5907 account.did:", account.did);
21
+ const device = (devices ?? []).find((e) => {
22
+ const matches = [e.deviceID, e.miotDID, e.name, e.alias, e.mac].includes(account.did);
23
+ console.log(`\u{1F50D} \u68C0\u67E5\u8BBE\u5907 ${e.name} (miotDID: ${e.miotDID}):`, {
24
+ deviceID: e.deviceID,
25
+ miotDID: e.miotDID,
26
+ name: e.name,
27
+ alias: e.alias,
28
+ mac: e.mac,
29
+ matches
30
+ });
31
+ return matches;
32
+ });
33
+ if (device) {
34
+ account.device = { ...device, deviceId: device.deviceID };
35
+ console.log("\u2705 \u627E\u5230\u8BBE\u5907:", device.name);
36
+ } else {
37
+ console.error("\u274C \u672A\u627E\u5230\u5339\u914D\u7684\u8BBE\u5907\uFF0Caccount.did:", account.did);
38
+ }
39
+ return account;
40
+ }
41
+ static async __callMiNA(account, method, path, _data) {
42
+ const data = {
43
+ ..._data,
44
+ requestId: uuid(),
45
+ timestamp: Math.floor(Date.now() / 1e3)
46
+ };
47
+ const url = `https://api2.mina.mi.com${path}`;
48
+ const config = {
49
+ account,
50
+ setAccount: updateMiAccount(account),
51
+ headers: { "User-Agent": "MICO/AndroidApp/@SHIP.TO.2A2FE0D7@/2.4.40" },
52
+ cookies: {
53
+ userId: account.userId,
54
+ serviceToken: account.serviceToken,
55
+ sn: account.device?.serialNumber,
56
+ hardware: account.device?.hardware,
57
+ deviceId: account.device?.deviceId,
58
+ deviceSNProfile: account.device?.deviceSNProfile
59
+ }
60
+ };
61
+ let res;
62
+ if (method === "GET") {
63
+ res = await Http.get(url, data, config);
64
+ } else {
65
+ res = await Http.post(url, encodeQuery(data), config);
66
+ }
67
+ if (res.code !== 0) {
68
+ if (Debugger.debug) {
69
+ console.error("\u274C _callMiNA failed", res);
70
+ }
71
+ return void 0;
72
+ }
73
+ return res.data;
74
+ }
75
+ async _callMiNA(method, path, data) {
76
+ return MiNA.__callMiNA(this.account, method, path, data);
77
+ }
78
+ /**
79
+ * 调用小爱音箱上的 ubus 服务
80
+ */
81
+ callUbus(scope, command, _message) {
82
+ const message = jsonEncode(_message ?? {});
83
+ return this._callMiNA("POST", "/remote/ubus", {
84
+ deviceId: this.account.device?.deviceId,
85
+ path: scope,
86
+ method: command,
87
+ message
88
+ });
89
+ }
90
+ /**
91
+ * 获取设备列表
92
+ */
93
+ getDevices() {
94
+ return this._callMiNA("GET", "/admin/v2/device_list");
95
+ }
96
+ /**
97
+ * 获取设备播放状态
98
+ */
99
+ async getStatus() {
100
+ const data = await this.callUbus("mediaplayer", "player_get_play_status");
101
+ const res = jsonDecode(data?.info);
102
+ if (!data || data.code !== 0 || !res) {
103
+ return;
104
+ }
105
+ const map = { 0: "idle", 1: "playing", 2: "paused", 3: "stopped" };
106
+ return {
107
+ ...res,
108
+ status: map[res.status] ?? "unknown",
109
+ volume: res.volume
110
+ };
111
+ }
112
+ /**
113
+ * 获取音量
114
+ */
115
+ async getVolume() {
116
+ const data = await this.getStatus();
117
+ return data?.volume;
118
+ }
119
+ /**
120
+ * 设置音量
121
+ */
122
+ async setVolume(_volume) {
123
+ const volume = Math.round(clamp(_volume, 6, 100));
124
+ const res = await this.callUbus("mediaplayer", "player_set_volume", {
125
+ volume
126
+ });
127
+ return res?.code === 0;
128
+ }
129
+ /**
130
+ * 播放
131
+ */
132
+ async play({ text, url, save = 0 } = {}) {
133
+ let res;
134
+ if (url) {
135
+ console.log(`\u{1F50A} MiNA.play URL: ${url}`);
136
+ res = await this.callUbus("mediaplayer", "player_play_url", {
137
+ url,
138
+ type: 1
139
+ });
140
+ console.log(`\u{1F50A} MiNA.play URL result:`, JSON.stringify(res));
141
+ } else if (text) {
142
+ console.log(`\u{1F50A} MiNA.play text: ${text.slice(0, 50)}...`);
143
+ res = await this.callUbus("mibrain", "text_to_speech", {
144
+ text,
145
+ save
146
+ });
147
+ console.log(`\u{1F50A} MiNA.play text result:`, JSON.stringify(res));
148
+ } else {
149
+ res = await this.callUbus("mediaplayer", "player_play_operation", {
150
+ action: "play"
151
+ });
152
+ }
153
+ return res?.code === 0;
154
+ }
155
+ /**
156
+ * 暂停播放
157
+ */
158
+ async pause() {
159
+ const res = await this.callUbus("mediaplayer", "player_play_operation", {
160
+ action: "pause"
161
+ });
162
+ return res?.code === 0;
163
+ }
164
+ /**
165
+ * 播放或暂停
166
+ */
167
+ async playOrPause() {
168
+ const res = await this.callUbus("mediaplayer", "player_play_operation", {
169
+ action: "toggle"
170
+ });
171
+ return res?.code === 0;
172
+ }
173
+ /**
174
+ * 停止播放
175
+ */
176
+ async stop() {
177
+ const res = await this.callUbus("mediaplayer", "player_play_operation", {
178
+ action: "stop"
179
+ });
180
+ return res?.code === 0;
181
+ }
182
+ /**
183
+ * 获取对话消息列表
184
+ */
185
+ async getConversations(options) {
186
+ const { limit = 10, timestamp } = options ?? {};
187
+ const res = await Http.get(
188
+ "https://userprofile.mina.mi.com/device_profile/v2/conversation",
189
+ {
190
+ limit,
191
+ timestamp,
192
+ requestId: uuid(),
193
+ source: "dialogu",
194
+ hardware: this.account.device?.hardware
195
+ },
196
+ {
197
+ account: this.account,
198
+ setAccount: updateMiAccount(this.account),
199
+ headers: {
200
+ "User-Agent": "Mozilla/5.0 (Linux; Android 10; 000; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/119.0.6045.193 Mobile Safari/537.36 /XiaoMi/HybridView/ micoSoundboxApp/i appVersion/A_2.4.40",
201
+ Referer: "https://userprofile.mina.mi.com/dialogue-note/index.html"
202
+ },
203
+ cookies: {
204
+ userId: this.account.userId,
205
+ serviceToken: this.account.serviceToken,
206
+ deviceId: this.account.device?.deviceId
207
+ }
208
+ }
209
+ );
210
+ if (res.code !== 0) {
211
+ if (Debugger.debug) {
212
+ console.error("\u274C getConversations failed", res);
213
+ }
214
+ return void 0;
215
+ }
216
+ return jsonDecode(res.data);
217
+ }
218
+ }
219
+ function clamp(value, min, max) {
220
+ return Math.min(Math.max(value, min), max);
221
+ }
222
+ export {
223
+ MiNA
224
+ };
225
+ //# sourceMappingURL=mina.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/mi/mina.ts"],"sourcesContent":["import { encodeQuery } from '../utils/codec.js';\nimport { Debugger } from '../utils/debug.js';\nimport { jsonEncode,jsonDecode } from '../utils/parse.js';\nimport { uuid } from '../utils/hash.js';\nimport { Http } from '../utils/http.js';\nimport { updateMiAccount } from './common.js';\nimport type { MiAccount, MiConversations, MiNADevice } from './typing.js';\n\ntype MiNAAccount = MiAccount & { device: MiNADevice };\n\nexport class MiNA {\n account: MiNAAccount;\n\n constructor(account: MiNAAccount) {\n this.account = account as any;\n }\n\n static async getDevice(account: MiNAAccount): Promise<MiNAAccount> {\n if (account.sid !== 'micoapi') {\n return account;\n }\n const devices = await MiNA.__callMiNA(account, 'GET', '/admin/v2/device_list');\n if (Debugger.debug) {\n console.log('🐛 MiNA 设备列表:', jsonEncode(devices, { prettier: true }));\n }\n\n console.log('🔍 查找设备 account.did:', account.did);\n \n const device = (devices ?? []).find((e: any) => {\n const matches = [e.deviceID, e.miotDID, e.name, e.alias, e.mac].includes(account.did);\n console.log(`🔍 检查设备 ${e.name} (miotDID: ${e.miotDID}):`, {\n deviceID: e.deviceID,\n miotDID: e.miotDID,\n name: e.name,\n alias: e.alias,\n mac: e.mac,\n matches,\n });\n return matches;\n });\n \n if (device) {\n account.device = { ...device, deviceId: device.deviceID };\n console.log('✅ 找到设备:', device.name);\n } else {\n console.error('❌ 未找到匹配的设备,account.did:', account.did);\n }\n return account;\n }\n\n private static async __callMiNA(\n account: MiNAAccount,\n method: 'GET' | 'POST',\n path: string,\n _data?: any,\n ): Promise<any> {\n const data = {\n ..._data,\n requestId: uuid(),\n timestamp: Math.floor(Date.now() / 1000),\n };\n const url = `https://api2.mina.mi.com${path}`;\n const config = {\n account,\n setAccount: updateMiAccount(account),\n headers: { 'User-Agent': 'MICO/AndroidApp/@SHIP.TO.2A2FE0D7@/2.4.40' },\n cookies: {\n userId: account.userId,\n serviceToken: account.serviceToken,\n sn: account.device?.serialNumber,\n hardware: account.device?.hardware,\n deviceId: account.device?.deviceId,\n deviceSNProfile: account.device?.deviceSNProfile,\n },\n };\n let res: any;\n if (method === 'GET') {\n res = await Http.get(url, data, config);\n } else {\n res = await Http.post(url, encodeQuery(data), config);\n }\n if (res.code !== 0) {\n if (Debugger.debug) {\n console.error('❌ _callMiNA failed', res);\n }\n return undefined;\n }\n return res.data;\n }\n\n private async _callMiNA(method: 'GET' | 'POST', path: string, data?: any): Promise<any> {\n return MiNA.__callMiNA(this.account, method, path, data);\n }\n\n /**\n * 调用小爱音箱上的 ubus 服务\n */\n callUbus(scope: string, command: string, _message?: any) {\n const message = jsonEncode(_message ?? {});\n return this._callMiNA('POST', '/remote/ubus', {\n deviceId: this.account.device?.deviceId,\n path: scope,\n method: command,\n message,\n });\n }\n\n /**\n * 获取设备列表\n */\n getDevices() {\n return this._callMiNA('GET', '/admin/v2/device_list');\n }\n\n /**\n * 获取设备播放状态\n */\n async getStatus(): Promise<\n | {\n volume: number;\n status: 'idle' | 'playing' | 'paused' | 'stopped' | 'unknown';\n media_type?: number;\n loop_type?: number;\n }\n | undefined\n > {\n const data = await this.callUbus('mediaplayer', 'player_get_play_status');\n const res = jsonDecode(data?.info);\n if (!data || data.code !== 0 || !res) {\n return;\n }\n const map = { 0: 'idle', 1: 'playing', 2: 'paused', 3: 'stopped' } as any;\n return {\n ...res,\n status: map[res.status] ?? 'unknown',\n volume: res.volume,\n };\n }\n\n /**\n * 获取音量\n */\n async getVolume() {\n const data = await this.getStatus();\n return data?.volume;\n }\n\n /**\n * 设置音量\n */\n async setVolume(_volume: number) {\n const volume = Math.round(clamp(_volume, 6, 100));\n const res = await this.callUbus('mediaplayer', 'player_set_volume', {\n volume,\n });\n return res?.code === 0;\n }\n\n /**\n * 播放\n */\n async play({ text, url, save = 0 }: { text?: string; url?: string; save?: 0 | 1 } = {}) {\n let res: any;\n if (url) {\n console.log(`🔊 MiNA.play URL: ${url}`);\n res = await this.callUbus('mediaplayer', 'player_play_url', {\n url,\n type: 1,\n });\n console.log(`🔊 MiNA.play URL result:`, JSON.stringify(res));\n } else if (text) {\n console.log(`🔊 MiNA.play text: ${text.slice(0, 50)}...`);\n res = await this.callUbus('mibrain', 'text_to_speech', {\n text,\n save,\n });\n console.log(`🔊 MiNA.play text result:`, JSON.stringify(res));\n } else {\n res = await this.callUbus('mediaplayer', 'player_play_operation', {\n action: 'play',\n });\n }\n return res?.code === 0;\n }\n\n /**\n * 暂停播放\n */\n async pause() {\n const res = await this.callUbus('mediaplayer', 'player_play_operation', {\n action: 'pause',\n });\n return res?.code === 0;\n }\n\n /**\n * 播放或暂停\n */\n async playOrPause() {\n const res = await this.callUbus('mediaplayer', 'player_play_operation', {\n action: 'toggle',\n });\n return res?.code === 0;\n }\n\n /**\n * 停止播放\n */\n async stop() {\n const res = await this.callUbus('mediaplayer', 'player_play_operation', {\n action: 'stop',\n });\n return res?.code === 0;\n }\n\n /**\n * 获取对话消息列表\n */\n async getConversations(options?: {\n limit?: number;\n timestamp?: number;\n }): Promise<MiConversations | undefined> {\n const { limit = 10, timestamp } = options ?? {};\n const res = await Http.get(\n 'https://userprofile.mina.mi.com/device_profile/v2/conversation',\n {\n limit,\n timestamp,\n requestId: uuid(),\n source: 'dialogu',\n hardware: this.account.device?.hardware,\n },\n {\n account: this.account,\n setAccount: updateMiAccount(this.account),\n headers: {\n 'User-Agent':\n 'Mozilla/5.0 (Linux; Android 10; 000; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/119.0.6045.193 Mobile Safari/537.36 /XiaoMi/HybridView/ micoSoundboxApp/i appVersion/A_2.4.40',\n Referer: 'https://userprofile.mina.mi.com/dialogue-note/index.html',\n },\n cookies: {\n userId: this.account.userId,\n serviceToken: this.account.serviceToken,\n deviceId: this.account.device?.deviceId,\n },\n },\n );\n if (res.code !== 0) {\n if (Debugger.debug) {\n console.error('❌ getConversations failed', res);\n }\n return undefined;\n }\n return jsonDecode(res.data);\n }\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n"],"mappings":"AAAA,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AACzB,SAAS,YAAW,kBAAkB;AACtC,SAAS,YAAY;AACrB,SAAS,YAAY;AACrB,SAAS,uBAAuB;AAKzB,MAAM,KAAK;AAAA,EAChB;AAAA,EAEA,YAAY,SAAsB;AAChC,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,aAAa,UAAU,SAA4C;AACjE,QAAI,QAAQ,QAAQ,WAAW;AAC7B,aAAO;AAAA,IACT;AACA,UAAM,UAAU,MAAM,KAAK,WAAW,SAAS,OAAO,uBAAuB;AAC7E,QAAI,SAAS,OAAO;AAClB,cAAQ,IAAI,iDAAiB,WAAW,SAAS,EAAE,UAAU,KAAK,CAAC,CAAC;AAAA,IACtE;AAEA,YAAQ,IAAI,mDAAwB,QAAQ,GAAG;AAE/C,UAAM,UAAU,WAAW,CAAC,GAAG,KAAK,CAAC,MAAW;AAC9C,YAAM,UAAU,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,QAAQ,GAAG;AACpF,cAAQ,IAAI,sCAAW,EAAE,IAAI,cAAc,EAAE,OAAO,MAAM;AAAA,QACxD,UAAU,EAAE;AAAA,QACZ,SAAS,EAAE;AAAA,QACX,MAAM,EAAE;AAAA,QACR,OAAO,EAAE;AAAA,QACT,KAAK,EAAE;AAAA,QACP;AAAA,MACF,CAAC;AACD,aAAO;AAAA,IACT,CAAC;AAED,QAAI,QAAQ;AACV,cAAQ,SAAS,EAAE,GAAG,QAAQ,UAAU,OAAO,SAAS;AACxD,cAAQ,IAAI,oCAAW,OAAO,IAAI;AAAA,IACpC,OAAO;AACL,cAAQ,MAAM,6EAA2B,QAAQ,GAAG;AAAA,IACtD;AACA,WAAO;AAAA,EACT;AAAA,EAEA,aAAqB,WACnB,SACA,QACA,MACA,OACc;AACd,UAAM,OAAO;AAAA,MACX,GAAG;AAAA,MACH,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACzC;AACA,UAAM,MAAM,2BAA2B,IAAI;AAC3C,UAAM,SAAS;AAAA,MACb;AAAA,MACA,YAAY,gBAAgB,OAAO;AAAA,MACnC,SAAS,EAAE,cAAc,4CAA4C;AAAA,MACrE,SAAS;AAAA,QACP,QAAQ,QAAQ;AAAA,QAChB,cAAc,QAAQ;AAAA,QACtB,IAAI,QAAQ,QAAQ;AAAA,QACpB,UAAU,QAAQ,QAAQ;AAAA,QAC1B,UAAU,QAAQ,QAAQ;AAAA,QAC1B,iBAAiB,QAAQ,QAAQ;AAAA,MACnC;AAAA,IACF;AACA,QAAI;AACJ,QAAI,WAAW,OAAO;AACpB,YAAM,MAAM,KAAK,IAAI,KAAK,MAAM,MAAM;AAAA,IACxC,OAAO;AACL,YAAM,MAAM,KAAK,KAAK,KAAK,YAAY,IAAI,GAAG,MAAM;AAAA,IACtD;AACA,QAAI,IAAI,SAAS,GAAG;AAClB,UAAI,SAAS,OAAO;AAClB,gBAAQ,MAAM,2BAAsB,GAAG;AAAA,MACzC;AACA,aAAO;AAAA,IACT;AACA,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAc,UAAU,QAAwB,MAAc,MAA0B;AACtF,WAAO,KAAK,WAAW,KAAK,SAAS,QAAQ,MAAM,IAAI;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,OAAe,SAAiB,UAAgB;AACvD,UAAM,UAAU,WAAW,YAAY,CAAC,CAAC;AACzC,WAAO,KAAK,UAAU,QAAQ,gBAAgB;AAAA,MAC5C,UAAU,KAAK,QAAQ,QAAQ;AAAA,MAC/B,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa;AACX,WAAO,KAAK,UAAU,OAAO,uBAAuB;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAQJ;AACA,UAAM,OAAO,MAAM,KAAK,SAAS,eAAe,wBAAwB;AACxE,UAAM,MAAM,WAAW,MAAM,IAAI;AACjC,QAAI,CAAC,QAAQ,KAAK,SAAS,KAAK,CAAC,KAAK;AACpC;AAAA,IACF;AACA,UAAM,MAAM,EAAE,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU;AACjE,WAAO;AAAA,MACL,GAAG;AAAA,MACH,QAAQ,IAAI,IAAI,MAAM,KAAK;AAAA,MAC3B,QAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY;AAChB,UAAM,OAAO,MAAM,KAAK,UAAU;AAClC,WAAO,MAAM;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,SAAiB;AAC/B,UAAM,SAAS,KAAK,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC;AAChD,UAAM,MAAM,MAAM,KAAK,SAAS,eAAe,qBAAqB;AAAA,MAClE;AAAA,IACF,CAAC;AACD,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,EAAE,MAAM,KAAK,OAAO,EAAE,IAAmD,CAAC,GAAG;AACtF,QAAI;AACJ,QAAI,KAAK;AACP,cAAQ,IAAI,4BAAqB,GAAG,EAAE;AACtC,YAAM,MAAM,KAAK,SAAS,eAAe,mBAAmB;AAAA,QAC1D;AAAA,QACA,MAAM;AAAA,MACR,CAAC;AACD,cAAQ,IAAI,mCAA4B,KAAK,UAAU,GAAG,CAAC;AAAA,IAC7D,WAAW,MAAM;AACf,cAAQ,IAAI,6BAAsB,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK;AACxD,YAAM,MAAM,KAAK,SAAS,WAAW,kBAAkB;AAAA,QACrD;AAAA,QACA;AAAA,MACF,CAAC;AACD,cAAQ,IAAI,oCAA6B,KAAK,UAAU,GAAG,CAAC;AAAA,IAC9D,OAAO;AACL,YAAM,MAAM,KAAK,SAAS,eAAe,yBAAyB;AAAA,QAChE,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ;AACZ,UAAM,MAAM,MAAM,KAAK,SAAS,eAAe,yBAAyB;AAAA,MACtE,QAAQ;AAAA,IACV,CAAC;AACD,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc;AAClB,UAAM,MAAM,MAAM,KAAK,SAAS,eAAe,yBAAyB;AAAA,MACtE,QAAQ;AAAA,IACV,CAAC;AACD,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO;AACX,UAAM,MAAM,MAAM,KAAK,SAAS,eAAe,yBAAyB;AAAA,MACtE,QAAQ;AAAA,IACV,CAAC;AACD,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,SAGkB;AACvC,UAAM,EAAE,QAAQ,IAAI,UAAU,IAAI,WAAW,CAAC;AAC9C,UAAM,MAAM,MAAM,KAAK;AAAA,MACrB;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA,WAAW,KAAK;AAAA,QAChB,QAAQ;AAAA,QACR,UAAU,KAAK,QAAQ,QAAQ;AAAA,MACjC;AAAA,MACA;AAAA,QACE,SAAS,KAAK;AAAA,QACd,YAAY,gBAAgB,KAAK,OAAO;AAAA,QACxC,SAAS;AAAA,UACP,cACE;AAAA,UACF,SAAS;AAAA,QACX;AAAA,QACA,SAAS;AAAA,UACP,QAAQ,KAAK,QAAQ;AAAA,UACrB,cAAc,KAAK,QAAQ;AAAA,UAC3B,UAAU,KAAK,QAAQ,QAAQ;AAAA,QACjC;AAAA,MACF;AAAA,IACF;AACA,QAAI,IAAI,SAAS,GAAG;AAClB,UAAI,SAAS,OAAO;AAClB,gBAAQ,MAAM,kCAA6B,GAAG;AAAA,MAChD;AACA,aAAO;AAAA,IACT;AACA,WAAO,WAAW,IAAI,IAAI;AAAA,EAC5B;AACF;AAEA,SAAS,MAAM,OAAe,KAAa,KAAqB;AAC9D,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,GAAG,GAAG,GAAG;AAC3C;","names":[]}
@@ -0,0 +1,35 @@
1
+ import { MiAccount, MIoTDevice } from './typing.js';
2
+
3
+ type MIoTAccount = MiAccount & {
4
+ device: MIoTDevice;
5
+ };
6
+ declare class MIoT {
7
+ account: MIoTAccount;
8
+ constructor(account: MIoTAccount);
9
+ static getDevice(account: MIoTAccount): Promise<MIoTAccount>;
10
+ private static __callMIoT;
11
+ private _callMIoT;
12
+ /**
13
+ * 获取 MIoT 设备列表
14
+ */
15
+ getDevices(getVirtualModel?: boolean, getHuamiDevices?: number): Promise<any>;
16
+ /**
17
+ * 获取 MIoT 设备属性值
18
+ */
19
+ getProperty(scope: number, property: number): Promise<any>;
20
+ /**
21
+ * 设置 MIoT 设备属性值
22
+ */
23
+ setProperty(scope: number, property: number, value: any): Promise<boolean>;
24
+ /**
25
+ * 调用 MIoT 设备能力指令
26
+ */
27
+ doAction(scope: number, action: number, args?: any): Promise<boolean>;
28
+ /**
29
+ * 调用 MIoT 设备 RPC 指令
30
+ */
31
+ rpc(method: string, params: any, id?: number): Promise<any>;
32
+ private _callMIoTSpec;
33
+ }
34
+
35
+ export { MIoT };
@@ -0,0 +1,168 @@
1
+ import { jsonEncode } from "../utils/parse.js";
2
+ import { decodeMIoT, encodeFormData, encodeMIoT } from "../utils/codec.js";
3
+ import { Http } from "../utils/http.js";
4
+ import { updateMiAccount } from "./common.js";
5
+ import { Debugger } from "../utils/debug.js";
6
+ class MIoT {
7
+ account;
8
+ constructor(account) {
9
+ this.account = account;
10
+ }
11
+ static async getDevice(account) {
12
+ if (account.sid !== "xiaomiio") {
13
+ return account;
14
+ }
15
+ const devices = await MIoT.__callMIoT(account, "POST", "/home/device_list", {
16
+ getVirtualModel: false,
17
+ getHuamiDevices: 0
18
+ });
19
+ if (Debugger.debug) {
20
+ console.log("\u{1F41B} MIoT \u8BBE\u5907\u5217\u8868\uFF1A", jsonEncode(devices, { prettier: true }));
21
+ }
22
+ const device = (devices?.list ?? []).find(
23
+ (e) => [e.did, e.name, e.mac].includes(account.did)
24
+ );
25
+ if (device) {
26
+ account.device = device;
27
+ }
28
+ return account;
29
+ }
30
+ static async __callMIoT(account, method, path, _data) {
31
+ const url = `https://api.io.mi.com/app${path}`;
32
+ const config = {
33
+ account,
34
+ setAccount: updateMiAccount(account),
35
+ rawResponse: true,
36
+ validateStatus: () => true,
37
+ headers: {
38
+ "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1",
39
+ "x-xiaomi-protocal-flag-cli": "PROTOCAL-HTTP2"
40
+ },
41
+ cookies: {
42
+ userId: account.userId,
43
+ serviceToken: account.serviceToken,
44
+ PassportDeviceId: account.deviceId
45
+ }
46
+ };
47
+ const data = encodeMIoT(path, _data, account.pass.ssecurity);
48
+ if (Debugger.debug) {
49
+ console.log("MIoT \u8BF7\u6C42:", {
50
+ url,
51
+ method,
52
+ cookies: config.cookies,
53
+ body: data
54
+ });
55
+ }
56
+ let res;
57
+ if (method === "GET") {
58
+ res = await Http.get(url, data, config);
59
+ } else {
60
+ const formData = encodeFormData(data);
61
+ if (Debugger.debug) {
62
+ console.log("POST body:", formData);
63
+ }
64
+ res = await Http.post(url, formData, {
65
+ ...config,
66
+ headers: {
67
+ ...config.headers,
68
+ "Content-Type": "application/x-www-form-urlencoded"
69
+ }
70
+ });
71
+ }
72
+ if (res.status === 401) {
73
+ console.error("\u274C 401 \u9519\u8BEF\u54CD\u5E94:", res.data);
74
+ return void 0;
75
+ }
76
+ if (res.data && typeof res.data === "object" && res.data.code === 0) {
77
+ return res.data.result;
78
+ }
79
+ if (typeof res.data === "string") {
80
+ try {
81
+ res = await decodeMIoT(
82
+ account.pass.ssecurity,
83
+ data._nonce,
84
+ res.data,
85
+ res.headers["miot-content-encoding"] === "GZIP"
86
+ );
87
+ return res?.result;
88
+ } catch (e) {
89
+ console.error("\u274C \u89E3\u5BC6\u5931\u8D25:", e);
90
+ return void 0;
91
+ }
92
+ }
93
+ console.error("\u274C \u672A\u77E5\u54CD\u5E94\u683C\u5F0F:", res);
94
+ return void 0;
95
+ }
96
+ async _callMIoT(method, path, data) {
97
+ return MIoT.__callMIoT(this.account, method, path, data);
98
+ }
99
+ /**
100
+ * 获取 MIoT 设备列表
101
+ */
102
+ async getDevices(getVirtualModel = false, getHuamiDevices = 0) {
103
+ const res = await this._callMIoT("POST", "/home/device_list", {
104
+ getVirtualModel,
105
+ getHuamiDevices
106
+ });
107
+ return res?.list;
108
+ }
109
+ /**
110
+ * 获取 MIoT 设备属性值
111
+ */
112
+ async getProperty(scope, property) {
113
+ const res = await this._callMIoTSpec("prop/get", [
114
+ {
115
+ did: this.account.device.did,
116
+ siid: scope,
117
+ piid: property
118
+ }
119
+ ]);
120
+ return (res ?? [])?.[0]?.value;
121
+ }
122
+ /**
123
+ * 设置 MIoT 设备属性值
124
+ */
125
+ async setProperty(scope, property, value) {
126
+ const res = await this._callMIoTSpec("prop/set", [
127
+ {
128
+ did: this.account.device.did,
129
+ siid: scope,
130
+ piid: property,
131
+ value
132
+ }
133
+ ]);
134
+ return (res ?? [])?.[0]?.code === 0;
135
+ }
136
+ /**
137
+ * 调用 MIoT 设备能力指令
138
+ */
139
+ async doAction(scope, action, args = []) {
140
+ const res = await this._callMIoTSpec("action", {
141
+ did: this.account.device.did,
142
+ siid: scope,
143
+ aiid: action,
144
+ in: Array.isArray(args) ? args : [args]
145
+ });
146
+ return res?.code === 0;
147
+ }
148
+ /**
149
+ * 调用 MIoT 设备 RPC 指令
150
+ */
151
+ rpc(method, params, id = 1) {
152
+ return this._callMIoT("POST", `/home/rpc/${this.account.device.did}`, {
153
+ id,
154
+ method,
155
+ params
156
+ });
157
+ }
158
+ _callMIoTSpec(command, params, datasource = 2) {
159
+ return this._callMIoT("POST", `/miotspec/${command}`, {
160
+ params,
161
+ datasource
162
+ });
163
+ }
164
+ }
165
+ export {
166
+ MIoT
167
+ };
168
+ //# sourceMappingURL=miot.js.map