(client: any, msg: Api.Message, cleanMembers: boolean = false)
| 257 | } |
| 258 | |
| 259 | // 群组已注销账号清理 |
| 260 | // 根因修复:必须传完整 User(含 accessHash),不能只传裸 userId; |
| 261 | // banUser 失败返回 false 时不能算「已清理」。 |
| 262 | private async cleanDeletedMember(client: any, msg: Api.Message, cleanMembers: boolean = false): Promise<void> { |
| 263 | const chat = await msg.getChat(); |
| 264 | if (!chat || !(chat instanceof Api.Chat || chat instanceof Api.Channel)) { |
| 265 | await this.sendError(msg, "此命令仅在群组中可用"); |
| 266 | return; |
| 267 | } |
| 268 | |
| 269 | await this.editMessage(msg, cleanMembers |
| 270 | ? "🔍 正在扫描并清理群组已注销账号..." |
| 271 | : "🔍 正在扫描群组已注销账号..."); |
| 272 | |
| 273 | // 用完整 chat 实体,避免 EditBanned 拿不到 channel access_hash |
| 274 | const chatEntity = await client.getInputEntity(chat); |
| 275 | if (cleanMembers && !await this.checkBanPermission(client, chat)) { |
| 276 | await this.sendError(msg, "没有封禁用户权限,无法执行清理"); |
| 277 | return; |
| 278 | } |
| 279 | |
| 280 | let foundCount = 0; |
| 281 | let removedCount = 0; |
| 282 | let failedCount = 0; |
| 283 | const deletedUsers: Array<{id: string, username?: string, ok?: boolean, err?: string}> = []; |
| 284 | |
| 285 | // 优先用完整 chat 实体迭代,保证 participant 上带 accessHash |
| 286 | const participants = client.iterParticipants(chat); |
| 287 | for await (const participant of participants) { |
| 288 | if (participant instanceof Api.User && participant.deleted) { |
| 289 | foundCount++; |
| 290 | const entry: {id: string, username?: string, ok?: boolean, err?: string} = { |
| 291 | id: participant.id.toString(), |
| 292 | username: participant.username || "已注销账号", |
| 293 | }; |
| 294 | |
| 295 | if (cleanMembers) { |
| 296 | try { |
| 297 | // 传完整 User,不要 participant.id(已注销账号 getInputEntity(id) 会失败) |
| 298 | const ok = await banUser(client, chatEntity, participant); |
| 299 | if (ok) { |
| 300 | removedCount++; |
| 301 | entry.ok = true; |
| 302 | // 踢出:封禁后再解封,避免永久 ban 列表堆积(与 kickUser 一致) |
| 303 | try { |
| 304 | await unbanUser(client, chatEntity, participant); |
| 305 | } catch { |
| 306 | /* 解封失败不影响已移出 */ |
| 307 | } |
| 308 | } else { |
| 309 | failedCount++; |
| 310 | entry.ok = false; |
| 311 | entry.err = "封禁接口返回失败"; |
| 312 | } |
| 313 | await sleep(150); |
| 314 | } catch (error: any) { |
| 315 | if (error.message?.includes("FLOOD_WAIT")) { |
| 316 | await this.handleFloodWait(msg, error); |
no test coverage detected