mytglib 2.0.0 → 2.0.3
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/README.md +171 -59
- package/dist/index.js +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -55,6 +55,20 @@ npm run example:login
|
|
|
55
55
|
|
|
56
56
|
示例按服务端响应处理邮箱设置、邮箱验证码、手机验证码和新账号注册。reCAPTCHA 出现时会显示 `action` 与 `siteKey`,并等待输入外部移动端求解器返回的 token。
|
|
57
57
|
|
|
58
|
+
### RegHelp 发码验收
|
|
59
|
+
|
|
60
|
+
`examples/reghelp-send-code.js` 默认读取 `examples/.env`,使用 `ios.API_CREDENTIALS`
|
|
61
|
+
第一项(当前 API ID `8`)和手机号 `+19156326494` 发起一次真实的 `auth.sendCode`。
|
|
62
|
+
可通过 `TELEGRAM_PHONE` 覆盖手机号,或同时通过 `TG_API_ID` 与 `TG_API_HASH` 选择数组中的其他凭据:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm run example:reghelp-send-code
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
该示例会完整输出 device token API、RegHelp 建任务与每次轮询、`invokeWithReCaptcha` 和最终
|
|
69
|
+
`auth.sentCode` 响应,并输出中间件从 Telegram challenge 暴露的全部字段。其中包含 device token、
|
|
70
|
+
rcmToken 和 `phoneCodeHash`。只应在可信的本地终端运行,且每次执行都会真实请求发送登录验证码。
|
|
71
|
+
|
|
58
72
|
## 基本 API
|
|
59
73
|
|
|
60
74
|
下面是常见的短信验证码登录路径;邮箱设置、新账号注册等完整分支见 `examples/login.js`。
|
|
@@ -102,8 +116,33 @@ try {
|
|
|
102
116
|
}
|
|
103
117
|
```
|
|
104
118
|
|
|
105
|
-
iOS
|
|
106
|
-
`
|
|
119
|
+
Android 和 iOS 分别通过公开且深冻结的 `android.API_CREDENTIALS` 与
|
|
120
|
+
`ios.API_CREDENTIALS` 列出可用的官方 API 凭据。Android 支持 ID 4、6,iOS 支持 ID 8、1;
|
|
121
|
+
数组第一项是省略自定义参数时的默认值,仍分别为 Android API ID 4 和 iOS API ID 8。需要使用
|
|
122
|
+
同平台的其他官方凭据时,保持 `clientType` 为 `"android"` 或 `"ios"`,并同时传入数组中同一项的
|
|
123
|
+
`apiId` 与 `apiHash`:
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
const iosCore = new CoreClient({
|
|
127
|
+
clientType: "ios",
|
|
128
|
+
apiId: 1,
|
|
129
|
+
apiHash: "b6b154c3707471f5339bd661645ed3d6",
|
|
130
|
+
phone: "+12025550123",
|
|
131
|
+
proxy: { type: "http", host: "127.0.0.1", port: 8080 },
|
|
132
|
+
sessionDirPath: resolve("telegram-sessions"),
|
|
133
|
+
deviceTokenResolver: async () => ({ token: "AQIDBA==" }),
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`apiId` 与 `apiHash` 必须同时省略或同时提供;显式提供时必须精确匹配所选平台公开数组中的同一对,
|
|
138
|
+
不能交叉组合,也不会新增 `clientType`。选中的凭据会用于连接初始化和登录请求;首次授权新建 session
|
|
139
|
+
Metadata 时也会保存该凭据,之后 `syncSessionJson()` 从这份注册快照导出。重新打开已有 session 时,
|
|
140
|
+
按既有契约保留首次写入的 API 凭据及其他初始化配置,不会用本次构造参数改写注册快照。
|
|
141
|
+
|
|
142
|
+
iOS 默认使用 `ios.CURRENT_VERSION` 的最后一项。覆盖版本时仍需同时传入 `appVersion` 与
|
|
143
|
+
`overrideLayer`,以兼容现有调用方;`appVersion` 会原样用于 Telegram `initConnection.appVersion`,
|
|
144
|
+
`overrideLayer` 不会传给底层客户端。Node.js 的协议 layer 始终由 `@mtcute/node` 当前 codec 的
|
|
145
|
+
`tl.LAYER` 决定,当前为 layer 229。
|
|
107
146
|
|
|
108
147
|
```js
|
|
109
148
|
const iosCore = new CoreClient({
|
|
@@ -112,14 +151,15 @@ const iosCore = new CoreClient({
|
|
|
112
151
|
proxy: { type: "http", host: "127.0.0.1", port: 8080 },
|
|
113
152
|
sessionDirPath: resolve("telegram-sessions"),
|
|
114
153
|
langCode: "en",
|
|
115
|
-
appVersion: "
|
|
116
|
-
overrideLayer:
|
|
154
|
+
appVersion: "12.8.1 (33181) ",
|
|
155
|
+
overrideLayer: 227,
|
|
117
156
|
deviceTokenResolver: async () => ({ token: "AQIDBA==" }),
|
|
118
157
|
});
|
|
119
158
|
```
|
|
120
159
|
|
|
121
|
-
覆盖值仅适用于 iOS;`appVersion` 必须是非空字符串,`overrideLayer` 必须是正数 int32。
|
|
122
|
-
|
|
160
|
+
覆盖值仅适用于 iOS;`appVersion` 必须是非空字符串,`overrideLayer` 必须是正数 int32。Rust fork
|
|
161
|
+
同样只把 `app_version` 用于 `initConnection`,协议 layer 始终使用编译期 `tl::LAYER`。Node.js 和 Rust
|
|
162
|
+
新写入的 `override_layer` Metadata 均记录当前真实 codec layer(当前为 229),不记录历史选择值。
|
|
123
163
|
`langCode` 同样只支持 iOS,并且必须来自公开的 `iosLangPackLanguages`;省略时继续根据
|
|
124
164
|
手机号自动选择。手工覆盖只改变 Telegram `langCode`,`systemLangCode` 仍按手机号地区推断。
|
|
125
165
|
|
|
@@ -137,7 +177,7 @@ const response = await core.sendCode({
|
|
|
137
177
|
});
|
|
138
178
|
```
|
|
139
179
|
|
|
140
|
-
传入的五个能力字段必须是 boolean;Android 的 `allowAppHash` 以及 iOS 的 APNs `token`、`appSandbox` 仍由平台配置自动生成,不属于可覆盖字段。根据 [mtcute 0.
|
|
180
|
+
传入的五个能力字段必须是 boolean;Android 的 `allowAppHash` 以及 iOS 的 APNs `token`、`appSandbox` 仍由平台配置自动生成,不属于可覆盖字段。根据 [mtcute 0.32.0 `sendCode` API](https://ref.mtcute.dev/funcs/_mtcute_core.highlevel_methods.sendCode.html),future auth tokens 的参数类型是 `Uint8Array[]`;本库直接构造 raw `codeSettings`,因此对应的 `logoutTokens` 使用原生 JavaScript 数组,并在请求对象中固定保留空数组 `[]`,而不是 TL JSON 包装对象或字符串数组。mtcute 0.32.0 只会序列化非空的 `logoutTokens`,因此空数组不会设置可选 TL vector flag。
|
|
141
181
|
|
|
142
182
|
`deviceTokenResolver` 和 `recaptchaMobileTokenResolver` 必须返回包含非空字符串 `token` 字段的对象,对象可保留求解服务返回的 `errorMessage`、`message` 等附加字段。无有效 token 时会抛出 `TypeError`,其 `cause` 是 resolver 返回的完整结果;reCAPTCHA token 最长为 16,384 个字符。
|
|
143
183
|
|
|
@@ -163,7 +203,7 @@ const updates = await core.submitCredentials({
|
|
|
163
203
|
|
|
164
204
|
`timeout`、`maxRetryCount` 和 `abortSignal` 是所有 mytglib 登录 RPC 的统一调用参数。`timeout` 默认为 `30000`,必须是正安全整数毫秒;`maxRetryCount` 默认为 `0`,必须是非负安全整数;`abortSignal` 默认为 `undefined`,传入时必须是 `AbortSignal`。客户端创建后会通过原始 `TelegramClient.withParams()` 生成 `core.client`,登录流程和通过 `core.client.call()` 发出的请求都会使用这些参数。密码更新的最终 `account.updatePasswordSettings` 会把 `maxRetryCount` 强制为 `0`,避免 internal/flood retry middleware 重放;前置的只读 `account.getPassword` 仍使用客户端配置。它们只作用于 RPC,不处理 `connect()` 超时,也不会取消正在进行的 `connect()`。
|
|
165
205
|
|
|
166
|
-
`sessionDirPath` 是必传的会话主目录。路径已存在时必须是目录;路径不存在时会在初始化过程中递归创建。`init()` 会按照 `<主目录>/<+手机号>/<+手机号>.session` 创建 SQLite 会话文件,然后依次解析设备 token 并连接客户端。连接成功后可通过 `core.sessionFilePath` 直接取得当前客户端使用的 SQLite 文件路径;连接前该属性为 `null`。登录相关公共异步方法均返回原始 Telegram TL 响应。`core.rawClient` 保存原始 `TelegramClient`,只负责 `connect()`、`notifyLoggedIn()` 和 `destroy()`;`core.client` 保存 `withParams()` 返回的 RPC Proxy。`core.destroy()` 始终销毁 `rawClient`,不会在 `@mtcute/node` 0.
|
|
206
|
+
`sessionDirPath` 是必传的会话主目录。路径已存在时必须是目录;路径不存在时会在初始化过程中递归创建。`init()` 会按照 `<主目录>/<+手机号>/<+手机号>.session` 创建 SQLite 会话文件,然后依次解析设备 token 并连接客户端。连接成功后可通过 `core.sessionFilePath` 直接取得当前客户端使用的 SQLite 文件路径;连接前该属性为 `null`。登录相关公共异步方法均返回原始 Telegram TL 响应。`core.rawClient` 保存原始 `TelegramClient`,只负责 `connect()`、`notifyLoggedIn()` 和 `destroy()`;`core.client` 保存 `withParams()` 返回的 RPC Proxy。`core.destroy()` 始终销毁 `rawClient`,不会在 `@mtcute/node` 0.32.0 的包装对象上调用 `destroy()`,从而避免私有字段错误。无论成功或失败,都应在 `finally` 中调用 `core.destroy()`,永久关闭连接、定时器和存储资源。销毁成功后两个客户端引用都会恢复为 `null`,SQLite 文件路径仍保留在 `core.sessionFilePath` 上。
|
|
167
207
|
|
|
168
208
|
初始化 `.session` 时,库会在同一个 SQLite 数据库内创建单行 `session_metadata` 表,并在授权成功后写入账号 Metadata;不会自动创建 JSON 文件或第二个数据库连接。每次初始化都会让 `session_file` 与当前会话路径一致;初始化配置与首次授权时间作为注册快照保留,`reg_time` 取最终产生授权的注册或登录方法入口调用时间。重新授权只刷新用户名、姓名、Premium 状态及非空运行态字段。`first_name` 和 `last_name` 在注册或登录授权成功后自动从 Telegram 用户信息同步,缺失时写入 `NULL`。`completion_type` 只有 `sign_up`(实际执行 `auth.signUp` 创建账号)和 `login`(已有账号完成授权)两种;缺失字段写入 `NULL`。最新 `phone_code_hash`、已验证登录邮箱、成功使用的 reCAPTCHA token 和两步验证密码会随运行生命周期同步更新。数据库写入失败时会按 `50ms`、`150ms`、`300ms` 额外重试三次;Telegram RPC 已成功但 Metadata 最终仍失败时,可调用 `await core.retrySaveSessionMetadata()` 重试当前全部待写操作。
|
|
169
209
|
|
|
@@ -182,8 +222,9 @@ const jsonFilePath = await core.syncSessionJson();
|
|
|
182
222
|
测试或删除账号,不需要手工启用、停用或扩缩容。
|
|
183
223
|
|
|
184
224
|
Pool 使用消费者模式:外部请求只消费已经连接的 `READY` 槽位,不会在请求路径中临时连接
|
|
185
|
-
Telegram。创建 Pool
|
|
186
|
-
|
|
225
|
+
Telegram。创建 Pool 和后续补槽均为异步操作;检测可以在内部 deadline 内等待正在连接、正在被其他
|
|
226
|
+
请求使用或正在异步补入的槽位,但不会同步建立临时账号连接。生产环境应在接入流量前完成基础暖槽,
|
|
227
|
+
并为重试配置独立的 `RETRY` 暖槽。
|
|
187
228
|
|
|
188
229
|
### 初始化与导入
|
|
189
230
|
|
|
@@ -194,6 +235,7 @@ import { createPhoneStatusPool } from "mytglib";
|
|
|
194
235
|
const pool = await createPhoneStatusPool({
|
|
195
236
|
databasePath: resolve(".data/phone-status-pool/pool.sqlite"),
|
|
196
237
|
minWarmSlots: 10,
|
|
238
|
+
retryWarmSlots: 200,
|
|
197
239
|
});
|
|
198
240
|
|
|
199
241
|
const account = await pool.importAccount({
|
|
@@ -210,9 +252,9 @@ console.log(account.accountId, account.runtimeStatus);
|
|
|
210
252
|
自动识别。两套 schema 同时存在时:两侧都有效且授权状态完全一致才接受;两侧都有效但内容冲突
|
|
211
253
|
则拒绝;只有一侧有效时使用有效侧;两侧都无有效授权时拒绝。会话文件与 JSON 必须属于同一个
|
|
212
254
|
有效账号,导入时会严格核对 auth key、DC、账号身份和手机号。Pool 只接受 Telegram 官方移动端
|
|
213
|
-
凭据:Android
|
|
214
|
-
`
|
|
215
|
-
|
|
255
|
+
凭据:Android 必须精确匹配 `android.API_CREDENTIALS` 中的 ID 4 或 6 凭据对,iOS 必须精确匹配
|
|
256
|
+
`ios.API_CREDENTIALS` 中的 ID 8 或 1 凭据对;TDesktop、自定义 API ID、交叉组合或平台与
|
|
257
|
+
API 凭据不匹配的 JSON 会在建立 Telegram 连接前直接拒绝。当前不支持代理。
|
|
216
258
|
|
|
217
259
|
为兼容常见会话导出格式,JSON 的 `session_file` 只能写真实文件名
|
|
218
260
|
`account.session` 或同名 stem `account`,不得携带目录组件;导入后会统一规整为真实文件名。
|
|
@@ -222,6 +264,13 @@ StringSession 优先读取 `session_str`,也兼容 `session_string`;两者
|
|
|
222
264
|
SQLite 与 StringSession 中的 endpoint 只用于交叉校验;实际创建 Pool 客户端时会按 JSON 对应的
|
|
223
265
|
官方 iOS 或 Android 平台,使用相同 DC ID 的内置官方 seed,不会连接上传文件指定的地址。
|
|
224
266
|
`device_token` 可以省略、设为 `null` 或留空;空值按未提供处理,不会写入连接参数。
|
|
267
|
+
导入会在写入账号池前主动检查账号状态,并且只接受 `UNFROZEN`:`FROZEN` 返回
|
|
268
|
+
`ACCOUNT_INVALID`,无法确认的 `UNKNOWN` 返回 `POOL_CONNECTION_FAILED`,两者都不会留下账号记录或
|
|
269
|
+
可调度槽位。只有 Telegram RPC 本身真实返回 `FROZEN_METHOD_INVALID` 或
|
|
270
|
+
`FROZEN_PARTICIPANT_MISSING` 时,才会原样保留其 `code = 420` 和错误文本;内部按认证失效一类
|
|
271
|
+
致命账号错误处置,不会把真实错误改写成 401。AppConfig RPC 的普通失败或超时才归为 `UNKNOWN`;
|
|
272
|
+
若该 RPC 真实返回现有致命账号错误或 `FLOOD_WAIT`,导入会保留原错误并拒绝落库,池内自检则分别
|
|
273
|
+
执行终态隔离或服务端时长冷却。
|
|
225
274
|
|
|
226
275
|
`minWarmSlots` 可省略,默认值为 `10`,可设为正的 JavaScript 安全整数,不再固定限制为 50;
|
|
227
276
|
`minWarmSlots * 5` 也必须是 JavaScript 安全整数。
|
|
@@ -229,12 +278,21 @@ SQLite 与 StringSession 中的 endpoint 只用于交叉校验;实际创建 Po
|
|
|
229
278
|
例如导入 500 个账号且设置 `minWarmSlots: 100` 时,Pool 只会先预热目标槽位,
|
|
230
279
|
剩余账号作为持久化库存待命,不会把 500 个账号同时上线。
|
|
231
280
|
|
|
232
|
-
|
|
281
|
+
`retryWarmSlots` 是专门服务账号级失败重试的独立暖槽数,默认值为 `0`,生产环境可按流量单独配置
|
|
282
|
+
(首轮建议 `200`,不把比例写死在代码中)。首次检测只使用 `PRIMARY` 槽;账号错误、
|
|
283
|
+
`FLOOD_WAIT` 或 RPC 超时等需要换账号时,只使用 `RETRY` 槽,普通 `PRIMARY` 槽不会被重试借用。
|
|
284
|
+
`maxAccountsPerPhone` 限制一次手机号检测最多使用的不同账号数,默认值为 `8`,可配置上限为 `16`,
|
|
285
|
+
用于避免单个手机号占满重试区。
|
|
286
|
+
|
|
287
|
+
Pool 的 PRIMARY 策略硬上限固定为 `minWarmSlots * 5`,加上 `retryWarmSlots` 后形成总策略上限,实际连接上限还会受进程 CPU、内存、文件描述符和
|
|
233
288
|
事件循环压力评估限制。忙碌槽位超过当前连接数一半时,Pool 会在资源允许的情况下异步提高目标槽位;
|
|
234
289
|
高峰过后,弹性槽位连续空闲 10 分钟才进入回收,并且每 30 秒最多回收一个,避免连接数瞬间震荡。
|
|
290
|
+
进入 `PRESSURED` 时,Pool 会先回收 PRIMARY 弹性槽,并保留当前资源上限能够承载的基础 PRIMARY
|
|
291
|
+
和独立 RETRY 槽;若上限不足,则优先保证 PRIMARY,按剩余容量降低 RETRY 数。只有进入
|
|
292
|
+
`CRITICAL` 时才暂停建连并回收全部 RETRY 和 PRIMARY 弹性槽。
|
|
235
293
|
当总连接上限高于 `minWarmSlots` 时,Pool 会在这个上限内部为导入和待机账号自检保留 1 个临时
|
|
236
|
-
管理连接位;该预留不会突破 `
|
|
237
|
-
|
|
294
|
+
管理连接位;该预留不会突破 PRIMARY 策略上限与 `retryWarmSlots` 之和。若资源上限已经低到不高于
|
|
295
|
+
最小暖槽数,管理建连会返回 `POOL_BUSY`,优先保护正在提供服务的暖槽。
|
|
238
296
|
|
|
239
297
|
### 检测与返回值
|
|
240
298
|
|
|
@@ -267,12 +325,19 @@ console.log(response.status, response.waitTime, response.error);
|
|
|
267
325
|
未映射到上述业务状态的检测异常会被捕获并以 `OTHER_ERROR` 正常返回,`error` 仅保留可用的 `message`、`name`、
|
|
268
326
|
`code`、`text` 和 `seconds`。
|
|
269
327
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
328
|
+
外部 HTTP 接口的端到端目标是 15 秒;Pool 内部为输入、排队、重试和清理预留固定的 14 秒共享
|
|
329
|
+
检测 deadline。每次 Telegram 检测 RPC 单独使用最多约 3 秒的局部 timeout,且关闭 mtcute 自带的该请求
|
|
330
|
+
重试。第一次使用 `PRIMARY` 槽;账号级错误、`FLOOD_WAIT` 或 RPC 超时后,后续尝试只从已经
|
|
331
|
+
`READY` 或正在异步补入的 `RETRY` 槽获取。后续尝试次数由剩余 `RETRY` 资源、单手机号账号上限和
|
|
332
|
+
14 秒 deadline 共同限制;资源允许时,仍可继续使用剩余重试暖槽兜底。
|
|
333
|
+
|
|
334
|
+
请求会在 deadline 内等待正在使用、正在连接或正在补槽的对应角色槽位,但不会在请求路径同步重启账号。
|
|
335
|
+
没有任何未使用的合格账号、重试暖槽耗尽或 deadline 到期时,返回最后一次已经实际发生的暖槽错误;
|
|
336
|
+
如果还没有发生暖槽错误,则返回 `OTHER_ERROR`,并通过 `error.code` 区分 `POOL_RETRY_TIMEOUT`、
|
|
337
|
+
`POOL_QUEUE_FULL` 或 `POOL_RESOURCE_CRITICAL` 等 Pool 原因。触发 `FLOOD_WAIT` 的账号会按 Telegram
|
|
338
|
+
返回的等待秒数退出暖槽并进入冷却,Pool 随即从库存异步补入其他账号。
|
|
339
|
+
若暖槽返回冻结账号错误,Pool 会将该账号永久隔离并换用其他 `READY` 槽位;当前检测返回的错误仍保留
|
|
340
|
+
Telegram 原始 `code = 420` 和错误文本。
|
|
276
341
|
`PHONE_NUMBER_FLOOD` 是目标手机号级错误,不换槽重试,只保护目标手机号 5 分钟且不处罚账号。
|
|
277
342
|
明确检测结果缓存 5 分钟,最多保存 10,000 条;同一手机号的并发请求会合并为一次检测,
|
|
278
343
|
基础设施错误不缓存,也不提供强制刷新。
|
|
@@ -291,9 +356,12 @@ single-flight RPC。即使当前只有一个等待者,后台检测仍可完成
|
|
|
291
356
|
const accounts = await pool.listAccounts();
|
|
292
357
|
const detail = await pool.getAccount(account.accountId);
|
|
293
358
|
const testResult = await pool.testAccount(account.accountId);
|
|
359
|
+
const testReport = await pool.testAccounts({
|
|
360
|
+
abortSignal: new AbortController().signal,
|
|
361
|
+
});
|
|
294
362
|
const status = await pool.getStatus();
|
|
295
363
|
|
|
296
|
-
console.log(accounts, detail, testResult, status.runtime);
|
|
364
|
+
console.log(accounts, detail, testResult, testReport, status.runtime);
|
|
297
365
|
console.log(accounts.map(({ phone, cooldownUntil }) => ({ phone, cooldownUntil })));
|
|
298
366
|
|
|
299
367
|
// 删除会先停止派单,等待该账号当前任务结束,再断开连接并删除持久化记录。
|
|
@@ -302,14 +370,42 @@ const removed = await pool.removeAccount(account.accountId);
|
|
|
302
370
|
|
|
303
371
|
账号运行态由 Pool 管理:可用账号处于 `STANDBY`、`RESERVED`、`CONNECTING`、`READY` 或
|
|
304
372
|
`BUSY`;触发 flood 的账号进入 `COOLDOWN`,认证失效等致命错误会进入 `QUARANTINED`。
|
|
305
|
-
`testAccount()`
|
|
306
|
-
|
|
373
|
+
`testAccount()` 是由管理员主动触发的账号健康自检,不会拿这个账号去检测某个外部手机号,也不会由
|
|
374
|
+
协调器定时自动执行。`QUARANTINED` 是终态;自检不会把已隔离账号恢复为 `ACTIVE` 或重新加入调度。
|
|
375
|
+
单账号自检失败时会直接拒绝 Promise:Telegram RPC 错误保留具体的 `message`、数值 `code`、`text` 和
|
|
376
|
+
`seconds` 字段;Pool 自身错误仍使用 `PhoneStatusPoolError` 的字符串 `code`。`testAccounts()` 继续将
|
|
377
|
+
单账号错误收敛到批量报告的 `{ code, message }`,不改变批量接口契约。
|
|
307
378
|
管理接口返回完整 `phone` 和 ISO 格式或 `null` 的 `cooldownUntil`,但不会包含 auth key、原始
|
|
308
|
-
Metadata、session
|
|
379
|
+
Metadata、session、Telegram 冻结时间或申诉地址等内部探测字段。
|
|
380
|
+
|
|
381
|
+
`testAccounts()` 由使用者主动触发,会固定批次开始时的账号快照并严格串行调用单账号自检。同一个 Pool
|
|
382
|
+
同时只运行一个批次;已经 `BUSY` 的账号不会被等待或抢断,而是记为 `FAILED` 后继续。已隔离账号不联网,
|
|
383
|
+
直接记为 `QUARANTINED`;批次中的账号被删除或进入 `REMOVING` 时记为 `SKIPPED`。返回结构固定为:
|
|
384
|
+
|
|
385
|
+
```js
|
|
386
|
+
{
|
|
387
|
+
total: 1,
|
|
388
|
+
healthy: 1,
|
|
389
|
+
quarantined: 0,
|
|
390
|
+
failed: 0,
|
|
391
|
+
skipped: 0,
|
|
392
|
+
results: [{
|
|
393
|
+
accountId: "account-id",
|
|
394
|
+
outcome: "HEALTHY",
|
|
395
|
+
account: { /* PoolAccount */ },
|
|
396
|
+
error: null,
|
|
397
|
+
}],
|
|
398
|
+
}
|
|
399
|
+
```
|
|
309
400
|
|
|
310
|
-
`
|
|
311
|
-
|
|
312
|
-
|
|
401
|
+
`outcome` 只会是 `HEALTHY`、`QUARANTINED`、`FAILED` 或 `SKIPPED`;`account` 为当前
|
|
402
|
+
`PoolAccount` 或 `null`,`error` 为 `null` 或 `{ code: string | null, message: string }`。
|
|
403
|
+
取消信号会停止批次并直接拒绝当前调用,不会把取消记成账号失败或改变账号健康状态。
|
|
404
|
+
|
|
405
|
+
`getStatus()` 可用于健康检查和后台展示,包含生命周期、账号库存、各运行态槽位数、PRIMARY/RETRY
|
|
406
|
+
角色槽位、对应等待队列、当前目标、策略上限、资源上限、资源压力和建连并发等信息。若业务需要等待
|
|
407
|
+
初始暖槽,可以在 API 对外接流量前轮询 `status.runtime.ready`;检测方法只在共享 deadline 内等待已安排的
|
|
408
|
+
槽位,不会同步创建连接。
|
|
313
409
|
|
|
314
410
|
### 部署与关闭
|
|
315
411
|
|
|
@@ -365,26 +461,39 @@ Telegram 客户端、定时器、缓存、SQLite 连接和 owner lock。关闭
|
|
|
365
461
|
### Rust API
|
|
366
462
|
|
|
367
463
|
Rust 实现从 `grammers_core` 根模块导出 `create_phone_status_pool()`、`PhoneStatusPool` 及相关
|
|
368
|
-
输入、响应和状态类型。调度、持久化格式和暖槽策略与 Node.js
|
|
369
|
-
`
|
|
370
|
-
`
|
|
371
|
-
|
|
372
|
-
`
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
464
|
+
输入、响应和状态类型。调度、持久化格式和暖槽策略与 Node.js 实现一致。手机号检测统一返回
|
|
465
|
+
扁平的 `PhoneStatusCheckResponse`,字段为 `wait_time`、`phone`、`started_at`、`ended_at`、
|
|
466
|
+
`duration_ms`、`status` 和 `error`;serde 序列化后与 Node.js 的 camelCase 对齐。没有可用资源、
|
|
467
|
+
排队超时或资源压力不会返回旧的 `BUSY` 变体,而是返回
|
|
468
|
+
`status = PhoneStatusPoolResult::OtherError`,序列化值为 `OTHER_ERROR`,并在 `error.code` 中区分
|
|
469
|
+
`POOL_RETRY_TIMEOUT`、`POOL_QUEUE_FULL` 或 `POOL_RESOURCE_CRITICAL`。创建和管理操作的
|
|
470
|
+
无效输入、调用取消、Pool 生命周期错误或内部协调错误仍通过 `PhoneStatusPoolError` 返回。
|
|
471
|
+
`test_account()` 的单账号失败会把 Telegram RPC 的具体文本写入 `PhoneStatusPoolError` 的 `message()`;
|
|
472
|
+
需要与 Node.js 对齐的结构化字段时调用 `phone_status_error()`,可取得 `message`、RPC 数值 `code`、
|
|
473
|
+
`text` 和 `seconds`。`PhoneStatusPoolError::code()` 仍保留 Pool 层分类,批量 `test_accounts()` 的错误
|
|
474
|
+
报告契约不变。
|
|
475
|
+
Rust Pool 使用 14 秒共享检测 deadline,每次 Telegram 检测 RPC 最多约 3 秒;首次只用 `PRIMARY`
|
|
476
|
+
暖槽,账号级失败后的重试只用隔离的 `RETRY` 暖槽。后续尝试由剩余 `RETRY` 资源、
|
|
477
|
+
`max_accounts_per_phone` 和 deadline 共同限制;`PHONE_NUMBER_FLOOD` 按手机号保护,
|
|
478
|
+
不换账号。
|
|
479
|
+
Rust 与 Node.js 采用相同的冻结账号规则:导入时探测到 `FROZEN` 或 `UNKNOWN` 不落库,运行中发现冻结
|
|
480
|
+
则进入终态 `QUARANTINED`。AppConfig 主动识别冻结返回 `ACCOUNT_INVALID`;Telegram RPC 真实返回的
|
|
481
|
+
`FROZEN_*` 错误仍在错误 source 链中保留 `code = 420`,不会改写成 401。公共账号结构不暴露冻结
|
|
482
|
+
探测字段。AppConfig RPC 的致命账号错误与 `FLOOD_WAIT` 同样保留原始分类,不降级为 `UNKNOWN`。
|
|
376
483
|
Rust 字段名使用 snake_case,通过 serde 序列化时使用 camelCase。资源上限使用 CPU、内存和文件
|
|
377
484
|
描述符评估;Tokio 没有稳定的 event-loop 利用率指标,因此 Rust 状态不会伪造该项数据。
|
|
378
485
|
|
|
379
486
|
```rust,no_run
|
|
380
487
|
use grammers_core::{
|
|
381
|
-
CheckPhoneStatusInput, CloseOptions, ImportAccountInput,
|
|
382
|
-
|
|
488
|
+
CheckPhoneStatusInput, CloseOptions, ImportAccountInput, PhoneStatusPoolError,
|
|
489
|
+
PhoneStatusPoolOptions, TestAccountsInput, create_phone_status_pool,
|
|
383
490
|
};
|
|
384
491
|
|
|
385
492
|
# async fn example() -> Result<(), PhoneStatusPoolError> {
|
|
386
493
|
let mut options = PhoneStatusPoolOptions::new("/secure/phone-status-pool/pool.sqlite");
|
|
387
494
|
options.min_warm_slots = 10;
|
|
495
|
+
options.retry_warm_slots = 200;
|
|
496
|
+
options.max_accounts_per_phone = 8;
|
|
388
497
|
let pool = create_phone_status_pool(options).await?;
|
|
389
498
|
|
|
390
499
|
// 即使任一管理或检测步骤失败,下面仍会执行安全关闭。
|
|
@@ -396,31 +505,26 @@ use grammers_core::{
|
|
|
396
505
|
))
|
|
397
506
|
.await?;
|
|
398
507
|
|
|
399
|
-
|
|
508
|
+
let response = pool
|
|
400
509
|
.check_phone_status(CheckPhoneStatusInput::new("+12025550123"))
|
|
401
|
-
.await
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
cached,
|
|
407
|
-
..
|
|
408
|
-
} => println!("{result:?} {checked_at} cached={cached}"),
|
|
409
|
-
PhoneStatusCheckResponse::Busy {
|
|
410
|
-
retry_after_seconds,
|
|
411
|
-
} => println!("BUSY retryAfterSeconds={retry_after_seconds}"),
|
|
412
|
-
}
|
|
510
|
+
.await?;
|
|
511
|
+
println!(
|
|
512
|
+
"status={:?} waitTime={} durationMs={} error={:?}",
|
|
513
|
+
response.status, response.wait_time, response.duration_ms, response.error,
|
|
514
|
+
);
|
|
413
515
|
|
|
414
516
|
let accounts = pool.list_accounts().await?;
|
|
415
517
|
let detail = pool.get_account(&account.account_id).await?;
|
|
416
518
|
let tested = pool.test_account(&account.account_id).await?;
|
|
519
|
+
let test_report = pool.test_accounts(TestAccountsInput::default()).await?;
|
|
417
520
|
let status = pool.get_status().await?;
|
|
418
521
|
println!(
|
|
419
|
-
"phone={} accounts={} detail={} tested={} ready={}",
|
|
522
|
+
"phone={} accounts={} detail={} tested={} healthy={} ready={}",
|
|
420
523
|
account.phone,
|
|
421
524
|
accounts.len(),
|
|
422
525
|
detail.is_some(),
|
|
423
526
|
tested.is_some(),
|
|
527
|
+
test_report.healthy,
|
|
424
528
|
status.runtime.ready,
|
|
425
529
|
);
|
|
426
530
|
|
|
@@ -436,7 +540,8 @@ use grammers_core::{
|
|
|
436
540
|
# }
|
|
437
541
|
```
|
|
438
542
|
|
|
439
|
-
`PhoneStatusPoolOptions::new()` 要求绝对数据库路径,`min_warm_slots` 省略时为 `10
|
|
543
|
+
`PhoneStatusPoolOptions::new()` 要求绝对数据库路径,`min_warm_slots` 省略时为 `10`,
|
|
544
|
+
`retry_warm_slots` 默认为 `0`,`max_accounts_per_phone` 默认为 `8` 且最大为 `16`。创建方法只
|
|
440
545
|
打开本地状态并启动异步预热,不等待 Telegram 暖槽连接完成;可在接入业务流量前轮询
|
|
441
546
|
`pool.get_status().await?.runtime.ready`。同一个数据库仍只能由一个 Pool 实例持有,进程退出前应
|
|
442
547
|
始终调用 `pool.close(CloseOptions::default()).await`。如果强制关闭宽限期结束时仍有后台清理,
|
|
@@ -445,7 +550,7 @@ use grammers_core::{
|
|
|
445
550
|
使用相同的 Telethon/mtcute schema 自动识别、`session_file` stem 兼容和混合 schema 选择规则;
|
|
446
551
|
mtcute 缺失 `dc_main` 时按其原语义使用默认生产 DC2。SQLite 与 StringSession 中的
|
|
447
552
|
endpoint 只用于校验两个文件描述同一个会话;实际连接始终使用 grammers 内置的 Telegram DC
|
|
448
|
-
地址,上传文件不能指定 API 进程的 TCP 目标。`ImportAccountInput` 和
|
|
553
|
+
地址,上传文件不能指定 API 进程的 TCP 目标。`ImportAccountInput`、`TestAccountsInput` 和
|
|
449
554
|
`CheckPhoneStatusInput` 的 `abort_signal` 是仅运行时字段,使用 serde 反序列化输入时需要由 Rust
|
|
450
555
|
调用方另行赋值。
|
|
451
556
|
|
|
@@ -472,7 +577,10 @@ console.log(result);
|
|
|
472
577
|
```
|
|
473
578
|
|
|
474
579
|
JSON 必须包含与 `syncSessionJson()` 输出格式兼容的 `session_str`、`session_file`、`app_id`、`app_hash`
|
|
475
|
-
|
|
580
|
+
和客户端配置。导入已有授权 session 时,保存的 `app_version` 继续用于 `initConnection`;源文件保持
|
|
581
|
+
只读,账号池新生成的待持久化 Metadata 会把 iOS `override_layer` 规范为当前 `tl.LAYER`。该字段不会传给
|
|
582
|
+
底层客户端。重连不会重新授权,也不会自动重试
|
|
583
|
+
`auth.signUp` 或任何其他授权写操作。`session_file` 可以写完整文件名
|
|
476
584
|
`account.session` 或同名 stem `account`,但不得包含目录组件。`device_token` 可以省略、设为 `null`
|
|
477
585
|
或留空,空值按未提供处理。
|
|
478
586
|
方法会以只读方式检查 SQLite schema,自动识别 Telethon 的 `sessions` 表或 mtcute 的
|
|
@@ -503,7 +611,8 @@ Grammers、Telethon `sessions` 或 mtcute `key_value`/`auth_keys` 会话,并
|
|
|
503
611
|
旧导出 JSON 中同 stem 的 `.session` 名称,所有形式均不得包含目录组件。`app_id` 只需为正整数且
|
|
504
612
|
`app_hash` 非空,因此不破坏已有自定义凭据;`device_token` 缺失、为 `null` 或空白时按未提供处理。
|
|
505
613
|
Grammers 分支若提供非空 `device_token`,trim 后仍必须与内部 `session_metadata` 一致;省略或空值不会
|
|
506
|
-
从内部 Metadata
|
|
614
|
+
从内部 Metadata 自动注入连接参数。临时客户端沿用保存的 iOS `app_version`,协议 layer 使用当前
|
|
615
|
+
编译期 `tl::LAYER`(当前为 229),`override_layer` 不参与连接。
|
|
507
616
|
输入字段使用 snake_case,结果序列化时使用与 npm 一致的 camelCase:
|
|
508
617
|
|
|
509
618
|
```rust,no_run
|
|
@@ -618,8 +727,11 @@ console.log(result);
|
|
|
618
727
|
|
|
619
728
|
`vendor/grammers` 是为后续 Tauri/Rust 桌面端准备的内置 fork,其中项目自有的
|
|
620
729
|
`grammers-core` 承载 `src` 对应的 Rust 实现。它目前尚未接入本 npm 包的 JavaScript 运行路径,
|
|
621
|
-
也不会进入 npm 发布包。
|
|
622
|
-
|
|
730
|
+
也不会进入 npm 发布包。crate 根模块公开 `ApiCredentials`、`android::API_CREDENTIALS` 和
|
|
731
|
+
`ios::API_CREDENTIALS`;`CoreClientConfig.api_id` 与 `api_hash` 同时省略时使用对应数组第一项,同时
|
|
732
|
+
提供时必须精确匹配同平台凭据对。Rust `CoreClient` 在 backend 连接初始化尚未完成、没有业务
|
|
733
|
+
transport frame 写出时,若初始化明确超时会自动重新创建一次 backend;该限定重试独立于 RPC 的
|
|
734
|
+
`max_retry_count`。
|
|
623
735
|
上游基线、本地覆盖层、
|
|
624
736
|
可重复的同步流程和验证范围见
|
|
625
737
|
[grammers fork 维护文档](docs/grammers-fork.md)。
|