@chatman-media/channel-telegram 1.1.0 → 1.3.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.
@@ -115,6 +115,7 @@ export declare class TelegramClient {
115
115
  callbackQueryId: string;
116
116
  text?: string;
117
117
  showAlert?: boolean;
118
+ url?: string;
118
119
  }): Promise<true>;
119
120
  sendChatAction(input: {
120
121
  chatId: number | string;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/bot-api/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAErF,MAAM,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC;AAErC,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IAEhC,MAAM,EAAE,MAAM;IACd,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,MAAM,GAAG,SAAS;IAC7B,WAAW,EAAE,MAAM;gBAHnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,EAAE,MAAM;CAK7B;AASD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;gBAE1B,IAAI,EAAE,qBAAqB;YAOzB,IAAI;IA6BlB,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAIxB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIxC;;;;;;;;;;;;;OAaG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IASrD,WAAW,CAAC,KAAK,EAAE;QACjB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,CAAC,EAAE,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;QAC/C,WAAW,CAAC,EAAE,aAAa,CAAC;QAC5B,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAYhC;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,EAAE;QACf,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAShC,SAAS,CAAC,KAAK,EAAE;QACf,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAShC;;;;;;OAMG;IACH,cAAc,CAAC,MAAM,EAAE;QACrB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;QACxB,aAAa,EAAE,MAAM,CAAC;QACtB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAIhC,YAAY,CAAC,KAAK,EAAE;QAClB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAShC;;;;;;OAMG;IACH,eAAe,CAAC,KAAK,EAAE;QACrB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,CAAC,EAAE,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;QAC/C,WAAW,CAAC,EAAE,aAAa,CAAC;KAC7B,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAWvC;;;;OAIG;IACH,aAAa,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAOnF;;;;OAIG;IACH,mBAAmB,CAAC,KAAK,EAAE;QACzB,eAAe,EAAE,MAAM,CAAC;QACxB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IASjB,cAAc,CAAC,KAAK,EAAE;QACpB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;QACxB,MAAM,EAAE,QAAQ,GAAG,cAAc,GAAG,cAAc,GAAG,cAAc,GAAG,iBAAiB,CAAC;KACzF,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjB,UAAU,CAAC,KAAK,EAAE;QAChB,GAAG,EAAE,MAAM,CAAC;QACZ,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;KAC9B,GAAG,OAAO,CAAC,IAAI,CAAC;IASjB,aAAa,CAAC,WAAW,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjD,cAAc,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,oBAAoB,EAAE,MAAM,CAAA;KAAE,CAAC;CAGzE"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/bot-api/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.test.d.ts","sourceRoot":"","sources":["../../src/bot-api/client.test.ts"],"names":[],"mappings":""}
@@ -90,6 +90,7 @@ export interface TgMessage {
90
90
  * cards (matched by the parent's `message_id` against
91
91
  * `leads.ops_message_id`). */
92
92
  reply_to_message?: TgMessage;
93
+ reply_markup?: TgReplyMarkup;
93
94
  }
94
95
  export interface TgCallbackQuery {
95
96
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bot-api/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bot-api/types.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -32668,6 +32668,8 @@ class TelegramClient {
32668
32668
  params.text = input.text;
32669
32669
  if (input.showAlert)
32670
32670
  params.show_alert = input.showAlert;
32671
+ if (input.url)
32672
+ params.url = input.url;
32671
32673
  return this.call("answerCallbackQuery", params);
32672
32674
  }
32673
32675
  sendChatAction(input) {
@@ -32972,6 +32974,16 @@ var import_telegram = __toESM(require_telegram(), 1);
32972
32974
  var import_events = __toESM(require_events(), 1);
32973
32975
  var import_Logger = __toESM(require_Logger(), 1);
32974
32976
  var import_sessions = __toESM(require_sessions(), 1);
32977
+ function defaultGramjsClientFactory(o) {
32978
+ const session = new import_sessions.StringSession(o.sessionString);
32979
+ const client = new import_telegram.TelegramClient(session, o.apiId, o.apiHash, {
32980
+ connectionRetries: o.connectionRetries,
32981
+ retryDelay: o.retryDelayMs,
32982
+ timeout: 30
32983
+ });
32984
+ client.setLogLevel(import_Logger.LogLevel.NONE);
32985
+ return client;
32986
+ }
32975
32987
  var TG_USERBOT_CAPABILITIES = {
32976
32988
  text: true,
32977
32989
  photo: true,
@@ -33004,13 +33016,13 @@ class TelegramUserbotAdapter {
33004
33016
  async connect() {
33005
33017
  if (this.client && this.client.connected)
33006
33018
  return;
33007
- const session = new import_sessions.StringSession(this.opts.sessionString);
33008
- const client = new import_telegram.TelegramClient(session, this.opts.apiId, this.opts.apiHash, {
33019
+ const client = (this.opts.clientFactory ?? defaultGramjsClientFactory)({
33020
+ sessionString: this.opts.sessionString,
33021
+ apiId: this.opts.apiId,
33022
+ apiHash: this.opts.apiHash,
33009
33023
  connectionRetries: this.opts.connectionRetries ?? 5,
33010
- retryDelay: this.opts.retryDelayMs ?? 3000,
33011
- timeout: 30
33024
+ retryDelayMs: this.opts.retryDelayMs ?? 3000
33012
33025
  });
33013
- client.setLogLevel(import_Logger.LogLevel.NONE);
33014
33026
  const maxAttempts = this.opts.connectionRetries ?? 5;
33015
33027
  let lastErr = null;
33016
33028
  for (let attempt = 1;attempt <= maxAttempts; attempt++) {
@@ -33344,10 +33356,11 @@ function mapRpcError(err) {
33344
33356
  }
33345
33357
  return new UserbotLoginError("unknown", msg);
33346
33358
  }
33359
+ function defaultLoginClientFactory(apiId, apiHash) {
33360
+ return new import_telegram2.TelegramClient(new import_sessions2.StringSession(""), apiId, apiHash, { connectionRetries: 3 });
33361
+ }
33347
33362
  async function startUserbotLogin(opts) {
33348
- const client = new import_telegram2.TelegramClient(new import_sessions2.StringSession(""), opts.apiId, opts.apiHash, {
33349
- connectionRetries: 3
33350
- });
33363
+ const client = (opts.clientFactory ?? defaultLoginClientFactory)(opts.apiId, opts.apiHash);
33351
33364
  await client.connect();
33352
33365
  try {
33353
33366
  const { phoneCodeHash } = await client.sendCode({ apiId: opts.apiId, apiHash: opts.apiHash }, opts.phone);
@@ -1,4 +1,5 @@
1
1
  import type { ChannelAdapter, ChannelCapabilities, DeleteOpts, EditOpts, Inbound, MediaRef, OutboundEnvelope, Sent } from "@chatman-media/channel-core";
2
+ import { TelegramClient as GramjsClient } from "telegram";
2
3
  /**
3
4
  * Healthcheck-статусы MTProto userbot'а. apps/worker отслеживает их через
4
5
  * healthEvents() async iterable и:
@@ -52,6 +53,19 @@ export interface TelegramUserbotAdapterOptions {
52
53
  connectionRetries?: number;
53
54
  /** ms между connect-попытками, default 5000. */
54
55
  retryDelayMs?: number;
56
+ /**
57
+ * Фабрика gramjs-клиента. По умолчанию — реальный конструктор
58
+ * (StringSession + GramjsClient + setLogLevel). Инъектируется в тестах,
59
+ * чтобы покрыть connect() без живого MTProto-соединения.
60
+ */
61
+ clientFactory?: (opts: GramjsClientFactoryOpts) => GramjsClient;
62
+ }
63
+ export interface GramjsClientFactoryOpts {
64
+ sessionString: string;
65
+ apiId: number;
66
+ apiHash: string;
67
+ connectionRetries: number;
68
+ retryDelayMs: number;
55
69
  }
56
70
  export declare class TelegramUserbotAdapter implements ChannelAdapter {
57
71
  readonly kind: "telegram_userbot";
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/userbot/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,mBAAmB,EACnB,UAAU,EACV,QAAQ,EACR,OAAO,EAEP,QAAQ,EACR,gBAAgB,EAChB,IAAI,EACL,MAAM,6BAA6B,CAAC;AAMrC;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,qBAAqB,GAAG,mBAAmB,CAAC;AAE5F,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,6BAA6B;IAC5C,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACnE,8CAA8C;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAcD,qBAAa,sBAAuB,YAAW,cAAc;IAC3D,QAAQ,CAAC,IAAI,EAAG,kBAAkB,CAAU;IAC5C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,YAAY,sBAA2B;IAEhD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgC;IACrD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,OAAO,CAAmD;IAClE,OAAO,CAAC,MAAM,CAAS;IACvB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAO;IAClD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA8B;IAC7D,sDAAsD;IACtD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4B;IACxD,OAAO,CAAC,aAAa,CAA8D;gBAEvE,IAAI,EAAE,6BAA6B;IAK/C;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAwD9B,OAAO,CAAC,UAAU;IAUlB;;;;;OAKG;IACH,YAAY,IAAI,aAAa,CAAC,kBAAkB,CAAC;IAqBjD,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,cAAc;IA+BtB,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,WAAW;IAgDnB,OAAO,CAAC,OAAO;IAST,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB5B,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC;IAoC/C,IAAI,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsC/C,IAAI,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpC,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7C;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;IA2BxF,YAAY,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBzD,6DAA6D;YAC/C,WAAW;CAQ1B"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/userbot/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=adapter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.test.d.ts","sourceRoot":"","sources":["../../src/userbot/adapter.test.ts"],"names":[],"mappings":""}
@@ -31,11 +31,15 @@ export interface FinishedUserbotLogin {
31
31
  username: string | null;
32
32
  phone: string | null;
33
33
  }
34
+ /** Фабрика клиента для startUserbotLogin (по умолчанию — реальный конструктор). */
35
+ export type LoginClientFactory = (apiId: number, apiHash: string) => TelegramClient;
34
36
  /** Шаг 1: подключиться и отправить код подтверждения на номер. */
35
37
  export declare function startUserbotLogin(opts: {
36
38
  apiId: number;
37
39
  apiHash: string;
38
40
  phone: string;
41
+ /** Инъекция клиента для тестов; по умолчанию — defaultLoginClientFactory. */
42
+ clientFactory?: LoginClientFactory;
39
43
  }): Promise<StartedUserbotLogin>;
40
44
  /**
41
45
  * Шаг 2: отправить код. Если у аккаунта включён 2FA — Telegram вернёт
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/userbot/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,cAAc,EAAE,MAAM,UAAU,CAAC;AAI/C;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,qBAAqB,GAC7B,eAAe,GACf,cAAc,GACd,cAAc,GACd,kBAAkB,GAClB,YAAY,GACZ,SAAS,CAAC;AAEd,qBAAa,iBAAkB,SAAQ,KAAK;IAExC,QAAQ,CAAC,IAAI,EAAE,qBAAqB;IAEpC,6CAA6C;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM;gBAHtB,IAAI,EAAE,qBAAqB,EACpC,OAAO,EAAE,MAAM;IACf,6CAA6C;IACpC,aAAa,CAAC,EAAE,MAAM,YAAA;CAKlC;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAgCD,kEAAkE;AAClE,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAe/B;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAAG,CAAC;IAAE,QAAQ,EAAE,KAAK,CAAA;CAAE,GAAG,oBAAoB,CAAC,CAAC,CAoB7E;AAED,2DAA2D;AAC3D,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAShC;AAED,mEAAmE;AACnE,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAS9F"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/userbot/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,cAAc,EAAE,MAAM,UAAU,CAAC;AAI/C;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,qBAAqB,GAC7B,eAAe,GACf,cAAc,GACd,cAAc,GACd,kBAAkB,GAClB,YAAY,GACZ,SAAS,CAAC;AAEd,qBAAa,iBAAkB,SAAQ,KAAK;IAExC,QAAQ,CAAC,IAAI,EAAE,qBAAqB;IAEpC,6CAA6C;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM;gBAHtB,IAAI,EAAE,qBAAqB,EACpC,OAAO,EAAE,MAAM;IACf,6CAA6C;IACpC,aAAa,CAAC,EAAE,MAAM,YAAA;CAKlC;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAgCD,mFAAmF;AACnF,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,cAAc,CAAC;AAMpF,kEAAkE;AAClE,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACpC,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAa/B;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAAG,CAAC;IAAE,QAAQ,EAAE,KAAK,CAAA;CAAE,GAAG,oBAAoB,CAAC,CAAC,CAoB7E;AAED,2DAA2D;AAC3D,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAShC;AAED,mEAAmE;AACnE,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAS9F"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=login.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.test.d.ts","sourceRoot":"","sources":["../../src/userbot/login.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatman-media/channel-telegram",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Telegram-каналы (BotAPI + MTProto userbot) как реализация ChannelAdapter из @chatman-media/channel-core.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -37,7 +37,7 @@
37
37
  "author": "Alexander Kireev",
38
38
  "license": "MIT",
39
39
  "dependencies": {
40
- "@chatman-media/channel-core": "1.0.0"
40
+ "@chatman-media/channel-core": "1.2.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "telegram": ">=2.26.0"
@@ -51,7 +51,7 @@
51
51
  "telegram": "^2.26.22"
52
52
  },
53
53
  "devDependencies": {
54
- "@biomejs/biome": "^2.4.14",
54
+ "@biomejs/biome": "^2.4.16",
55
55
  "@types/bun": "1.3.14",
56
56
  "typescript": "^6.0.3"
57
57
  },
@@ -0,0 +1,129 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { TelegramApiError, TelegramClient } from "./client.ts";
3
+ import type { FetchLike } from "./client.ts";
4
+
5
+ function envelope(result: unknown): Response {
6
+ return new Response(JSON.stringify({ ok: true, result }), { status: 200 });
7
+ }
8
+ function mock(handler: (url: string, init?: RequestInit) => Response | Promise<Response>) {
9
+ const calls: Array<{ url: string; init?: RequestInit }> = [];
10
+ const fn = (async (url: string, init?: RequestInit) => {
11
+ calls.push({ url: String(url), init });
12
+ return handler(String(url), init);
13
+ }) as unknown as FetchLike;
14
+ return { fn, calls };
15
+ }
16
+ const client = (fetch: FetchLike) => new TelegramClient({ token: "T", fetch });
17
+ const bodyOf = (init?: RequestInit) => JSON.parse(init?.body as string);
18
+
19
+ describe("TelegramClient constructor", () => {
20
+ it("требует token", () => {
21
+ expect(() => new TelegramClient({ token: "" })).toThrow("token is required");
22
+ });
23
+ });
24
+
25
+ describe("TelegramClient методы", () => {
26
+ it("getMe", async () => {
27
+ const { fn, calls } = mock(() => envelope({ id: 1, is_bot: true }));
28
+ expect((await client(fn).getMe()).id).toBe(1);
29
+ expect(calls[0]!.url).toBe("https://api.telegram.org/botT/getMe");
30
+ });
31
+
32
+ it("sendMessage — параметры и результат", async () => {
33
+ const { fn, calls } = mock(() => envelope({ message_id: 9 }));
34
+ const r = await client(fn).sendMessage({
35
+ chatId: "100",
36
+ text: "hi",
37
+ parseMode: "HTML",
38
+ replyMarkup: { inline_keyboard: [[{ text: "x", callback_data: "y" }]] },
39
+ disableWebPagePreview: true,
40
+ replyToMessageId: 5,
41
+ });
42
+ expect(r.message_id).toBe(9);
43
+ const b = bodyOf(calls[0]!.init);
44
+ expect(b).toMatchObject({ chat_id: "100", text: "hi", parse_mode: "HTML", disable_web_page_preview: true, reply_to_message_id: 5 });
45
+ expect(b.reply_markup.inline_keyboard).toBeDefined();
46
+ });
47
+
48
+ it("sendPhoto / sendVideo / sendDocument", async () => {
49
+ const { fn, calls } = mock(() => envelope({ message_id: 1 }));
50
+ const c = client(fn);
51
+ await c.sendPhoto({ chatId: 1, photoFileId: "p", caption: "c" });
52
+ await c.sendVideo({ chatId: 1, videoFileId: "v" });
53
+ await c.sendDocument({ chatId: 1, documentFileId: "d", caption: "cap" });
54
+ expect(calls[0]!.url).toContain("/sendPhoto");
55
+ expect(bodyOf(calls[0]!.init).photo).toBe("p");
56
+ expect(calls[1]!.url).toContain("/sendVideo");
57
+ expect(bodyOf(calls[2]!.init).document).toBe("d");
58
+ });
59
+
60
+ it("sendLocalVideo — не поддержано (throws)", () => {
61
+ expect(() => client(mock(() => envelope({})).fn).sendLocalVideo({ chatId: 1, localFilePath: "/x" })).toThrow();
62
+ });
63
+
64
+ it("editMessageText / deleteMessage", async () => {
65
+ const { fn, calls } = mock((url) => (url.includes("delete") ? envelope(true) : envelope({ message_id: 2 })));
66
+ const c = client(fn);
67
+ await c.editMessageText({ chatId: 1, messageId: 2, text: "new", parseMode: "HTML", replyMarkup: {} });
68
+ expect(await c.deleteMessage({ chatId: 1, messageId: 2 })).toBe(true);
69
+ expect(bodyOf(calls[0]!.init)).toMatchObject({ message_id: 2, text: "new" });
70
+ });
71
+
72
+ it("answerCallbackQuery — text + show_alert", async () => {
73
+ const { fn, calls } = mock(() => envelope(true));
74
+ await client(fn).answerCallbackQuery({ callbackQueryId: "cb", text: "ok", showAlert: true });
75
+ expect(bodyOf(calls[0]!.init)).toMatchObject({ callback_query_id: "cb", text: "ok", show_alert: true });
76
+ });
77
+
78
+ it("answerCallbackQuery — url", async () => {
79
+ const { fn, calls } = mock(() => envelope(true));
80
+ await client(fn).answerCallbackQuery({ callbackQueryId: "cb", url: "https://app.test/conversations/7" });
81
+ expect(bodyOf(calls[0]!.init)).toMatchObject({
82
+ callback_query_id: "cb",
83
+ url: "https://app.test/conversations/7",
84
+ });
85
+ });
86
+
87
+ it("sendChatAction", async () => {
88
+ const { fn, calls } = mock(() => envelope(true));
89
+ await client(fn).sendChatAction({ chatId: 1, action: "typing" });
90
+ expect(bodyOf(calls[0]!.init).action).toBe("typing");
91
+ });
92
+
93
+ it("setWebhook / deleteWebhook / getWebhookInfo", async () => {
94
+ const { fn, calls } = mock((url) =>
95
+ url.includes("getWebhookInfo") ? envelope({ url: "u", pending_update_count: 0 }) : envelope(true),
96
+ );
97
+ const c = client(fn);
98
+ await c.setWebhook({ url: "https://x", secretToken: "s", allowedUpdates: ["message"], dropPendingUpdates: true });
99
+ await c.deleteWebhook(true);
100
+ expect((await c.getWebhookInfo()).url).toBe("u");
101
+ expect(bodyOf(calls[0]!.init)).toMatchObject({ url: "https://x", secret_token: "s", drop_pending_updates: true });
102
+ });
103
+
104
+ it("getFile + downloadFile (two-step)", async () => {
105
+ const { fn } = mock((url) => (url.includes("/getFile") ? envelope({ file_path: "photos/a.jpg" }) : new Response("BYTES")));
106
+ const res = await client(fn).downloadFile("fid");
107
+ expect(await res.text()).toBe("BYTES");
108
+ });
109
+
110
+ it("downloadFile без file_path → throws", async () => {
111
+ const { fn } = mock(() => envelope({ file_id: "f" }));
112
+ await expect(client(fn).downloadFile("fid")).rejects.toThrow("no file_path");
113
+ });
114
+ });
115
+
116
+ describe("TelegramClient.call error-ветки", () => {
117
+ it("HTTP !ok → TelegramApiError", async () => {
118
+ const { fn } = mock(() => new Response(JSON.stringify({ ok: false, error_code: 401, description: "bad" }), { status: 401 }));
119
+ await expect(client(fn).getMe()).rejects.toBeInstanceOf(TelegramApiError);
120
+ });
121
+ it("body.ok=false → TelegramApiError с описанием", async () => {
122
+ const { fn } = mock(() => new Response(JSON.stringify({ ok: false, description: "nope" }), { status: 200 }));
123
+ await expect(client(fn).getMe()).rejects.toThrow("nope");
124
+ });
125
+ it("non-JSON → TelegramApiError", async () => {
126
+ const { fn } = mock(() => new Response("<html>", { status: 200 }));
127
+ await expect(client(fn).getMe()).rejects.toBeInstanceOf(TelegramApiError);
128
+ });
129
+ });
@@ -230,12 +230,14 @@ export class TelegramClient {
230
230
  callbackQueryId: string;
231
231
  text?: string;
232
232
  showAlert?: boolean;
233
+ url?: string;
233
234
  }): Promise<true> {
234
235
  const params: Record<string, unknown> = {
235
236
  callback_query_id: input.callbackQueryId,
236
237
  };
237
238
  if (input.text) params.text = input.text;
238
239
  if (input.showAlert) params.show_alert = input.showAlert;
240
+ if (input.url) params.url = input.url;
239
241
  return this.call<true>("answerCallbackQuery", params);
240
242
  }
241
243
 
@@ -100,6 +100,7 @@ export interface TgMessage {
100
100
  * cards (matched by the parent's `message_id` against
101
101
  * `leads.ops_message_id`). */
102
102
  reply_to_message?: TgMessage;
103
+ reply_markup?: TgReplyMarkup;
103
104
  }
104
105
 
105
106
  export interface TgCallbackQuery {
@@ -0,0 +1,253 @@
1
+ import type { Inbound } from "@chatman-media/channel-core";
2
+ import { describe, expect, it } from "bun:test";
3
+ import { TelegramUserbotAdapter } from "./adapter.ts";
4
+
5
+ function make() {
6
+ return new TelegramUserbotAdapter({ id: "ub1", apiId: 1, apiHash: "h", sessionString: "" });
7
+ }
8
+ // Доступ к приватам/инъекция фейкового gramjs-клиента.
9
+ // biome-ignore lint/suspicious/noExplicitAny: тестовый доступ к приватам
10
+ const priv = (a: TelegramUserbotAdapter) => a as any;
11
+
12
+ function event(over: Record<string, unknown> = {}) {
13
+ return {
14
+ message: {
15
+ senderId: { toString: () => "123" },
16
+ peerId: { className: "PeerUser" },
17
+ message: "hi",
18
+ id: 5,
19
+ ...over,
20
+ },
21
+ };
22
+ }
23
+
24
+ describe("TelegramUserbotAdapter базовое", () => {
25
+ it("kind/id/capabilities", () => {
26
+ const a = make();
27
+ expect(a.kind).toBe("telegram_userbot");
28
+ expect(a.id).toBe("ub1");
29
+ expect(a.capabilities.text).toBe(true);
30
+ expect(a.capabilities.callbackQuery).toBe(false);
31
+ });
32
+ });
33
+
34
+ describe("eventToInbound", () => {
35
+ it("текст → text-part", () => {
36
+ const inb = priv(make()).eventToInbound(event());
37
+ expect(inb.externalUserId).toBe("123");
38
+ expect(inb.externalMessageId).toBe("5");
39
+ expect(inb.parts).toEqual([{ kind: "text", text: "hi" }]);
40
+ });
41
+ it("фото → photo-part с caption", () => {
42
+ const inb = priv(make()).eventToInbound(event({ message: "подпись", photo: {}, id: 7 }));
43
+ expect(inb.parts[0]).toMatchObject({ kind: "photo", caption: "подпись" });
44
+ });
45
+ it("video / voice / document", () => {
46
+ const a = priv(make());
47
+ expect(a.eventToInbound(event({ message: "", video: { duration: 3 }, photo: undefined })).parts[0].kind).toBe("video");
48
+ expect(a.eventToInbound(event({ message: "", voice: { duration: 2 } })).parts[0]).toMatchObject({ kind: "voice", durationSec: 2 });
49
+ expect(
50
+ a.eventToInbound(event({ message: "", document: { mimeType: "application/pdf", attributes: [{ fileName: "x.pdf" }] } })).parts[0],
51
+ ).toMatchObject({ kind: "document", mimeType: "application/pdf", fileName: "x.pdf" });
52
+ });
53
+ it("не-private peer → null", () => {
54
+ expect(priv(make()).eventToInbound(event({ peerId: { className: "PeerChannel" } }))).toBeNull();
55
+ });
56
+ it("нет senderId → null", () => {
57
+ expect(priv(make()).eventToInbound(event({ senderId: undefined }))).toBeNull();
58
+ });
59
+ it("пустой текст без медиа → null", () => {
60
+ expect(priv(make()).eventToInbound(event({ message: "" }))).toBeNull();
61
+ });
62
+ });
63
+
64
+ describe("cacheMessage LRU", () => {
65
+ it("вытесняет старейшие при превышении лимита", () => {
66
+ const a = priv(make());
67
+ for (let i = 0; i < 300; i++) a.cacheMessage("u", String(i), { i });
68
+ expect(a.recentMessages.size).toBe(256);
69
+ expect(a.recentMessages.has("u:0")).toBe(false); // старейшие вытеснены
70
+ expect(a.recentMessages.has("u:299")).toBe(true);
71
+ });
72
+ });
73
+
74
+ describe("receive() очередь", () => {
75
+ it("отдаёт заэнкью'енные inbound, затем done после close", async () => {
76
+ const a = make();
77
+ priv(a).enqueue({ channelId: "ub1", externalMessageId: "1", externalUserId: "123", parts: [], receivedAt: 0 } as unknown as Inbound);
78
+ const it = a.receive()[Symbol.asyncIterator]();
79
+ expect((await it.next()).value.externalMessageId).toBe("1");
80
+ await a.close();
81
+ expect((await it.next()).done).toBe(true);
82
+ });
83
+ });
84
+
85
+ describe("healthEvents()", () => {
86
+ it("эмит → читается из итератора", async () => {
87
+ const a = make();
88
+ priv(a).emitHealth({ status: "connected" });
89
+ const it = a.healthEvents()[Symbol.asyncIterator]();
90
+ expect((await it.next()).value.status).toBe("connected");
91
+ });
92
+ });
93
+
94
+ describe("send / delete / downloadMedia / signalTyping", () => {
95
+ const fakeClient = () => ({
96
+ sendMessage: async () => ({ id: 42 }),
97
+ deleteMessages: async () => {},
98
+ downloadMedia: async () => Buffer.from("MEDIA"),
99
+ getInputEntity: async (id: number) => ({ id }),
100
+ invoke: async () => ({}),
101
+ disconnect: async () => {},
102
+ connected: true,
103
+ });
104
+
105
+ it("send до connect → throws", async () => {
106
+ await expect(make().send({ externalUserId: "1", parts: [{ kind: "text", text: "x" }] } as never)).rejects.toThrow("before connect");
107
+ });
108
+ it("send пустые parts → throws", async () => {
109
+ const a = make();
110
+ priv(a).client = fakeClient();
111
+ await expect(a.send({ externalUserId: "1", parts: [] } as never)).rejects.toThrow("non-empty");
112
+ });
113
+ it("send текст → Sent с id", async () => {
114
+ const a = make();
115
+ priv(a).client = fakeClient();
116
+ const sent = await a.send({ externalUserId: "999", parts: [{ kind: "text", text: "hi" }] } as never);
117
+ expect(sent.externalMessageId).toBe("42");
118
+ expect(sent.channelId).toBe("ub1");
119
+ });
120
+ it("send неподдержанный part.kind → throws", async () => {
121
+ const a = make();
122
+ priv(a).client = fakeClient();
123
+ await expect(a.send({ externalUserId: "1", parts: [{ kind: "photo", mediaRef: { channelId: "ub1", externalRef: "r" } }] } as never)).rejects.toThrow("unsupported");
124
+ });
125
+
126
+ it("delete до connect → throws; NaN id → throws; ok с клиентом", async () => {
127
+ await expect(make().delete({ externalUserId: "1", externalMessageId: "2" } as never)).rejects.toThrow("before connect");
128
+ const a = make();
129
+ priv(a).client = fakeClient();
130
+ await expect(a.delete({ externalUserId: "1", externalMessageId: "nope" } as never)).rejects.toThrow("numeric");
131
+ await a.delete({ externalUserId: "1", externalMessageId: "2" } as never); // не бросает
132
+ });
133
+
134
+ it("downloadMedia: guards + успех из кэша", async () => {
135
+ await expect(make().downloadMedia({ channelId: "ub1", externalRef: "7" })).rejects.toThrow("before connect");
136
+ const a = make();
137
+ priv(a).client = fakeClient();
138
+ await expect(a.downloadMedia({ channelId: "ub1", externalRef: "7" })).rejects.toThrow("externalUserId");
139
+ await expect(a.downloadMedia({ channelId: "ub1", externalRef: "7" }, { externalUserId: "123" })).rejects.toThrow("evicted");
140
+ priv(a).recentMessages.set("123:7", { msg: true });
141
+ const res = await a.downloadMedia({ channelId: "ub1", externalRef: "7" }, { externalUserId: "123" });
142
+ expect(await res.text()).toBe("MEDIA");
143
+ });
144
+
145
+ it("signalTyping: без клиента no-op; с клиентом invoke", async () => {
146
+ await make().signalTyping("1"); // no-op, не бросает
147
+ const a = make();
148
+ let invoked = false;
149
+ priv(a).client = { ...fakeClient(), invoke: async () => { invoked = true; } };
150
+ await a.signalTyping("123");
151
+ expect(invoked).toBe(true);
152
+ });
153
+
154
+ it("resolvePeer NaN → throws", async () => {
155
+ const a = make();
156
+ priv(a).client = fakeClient();
157
+ await expect(priv(a).resolvePeer("not-a-number")).rejects.toThrow("invalid externalUserId");
158
+ });
159
+
160
+ it("edit всегда throws (capability disabled)", async () => {
161
+ await expect(make().edit({} as never)).rejects.toThrow("disabled");
162
+ });
163
+ });
164
+
165
+ describe("registerHandler + receive abort", () => {
166
+ it("registerHandler ставит обработчик, эмитит connected, входящее → inbox+cache", async () => {
167
+ const a = make();
168
+ let handler: ((e: unknown) => Promise<void>) | null = null;
169
+ priv(a).registerHandler({ addEventHandler: (h: (e: unknown) => Promise<void>) => { handler = h; } });
170
+ // эмит "connected"
171
+ const hIt = a.healthEvents()[Symbol.asyncIterator]();
172
+ expect((await hIt.next()).value.status).toBe("connected");
173
+ // прогоняем входящее сообщение через захваченный обработчик
174
+ expect(handler).not.toBeNull();
175
+ await handler!(event());
176
+ expect(priv(a).recentMessages.has("123:5")).toBe(true);
177
+ const rIt = a.receive()[Symbol.asyncIterator]();
178
+ expect((await rIt.next()).value.externalUserId).toBe("123");
179
+ });
180
+
181
+ it("registerHandler пропускает свои исходящие (out)", async () => {
182
+ const a = make();
183
+ let handler: ((e: unknown) => Promise<void>) | null = null;
184
+ priv(a).registerHandler({ addEventHandler: (h: (e: unknown) => Promise<void>) => { handler = h; } });
185
+ await handler!({ message: { out: true, senderId: { toString: () => "1" }, peerId: { className: "PeerUser" }, message: "x", id: 1 } });
186
+ expect(priv(a).inbox.length).toBe(0);
187
+ });
188
+
189
+ it("receive(): abort прерывает ожидание (done)", async () => {
190
+ const a = make();
191
+ const ctrl = new AbortController();
192
+ const it = a.receive(ctrl.signal)[Symbol.asyncIterator]();
193
+ const p = it.next(); // нет элементов → ждёт
194
+ ctrl.abort();
195
+ expect((await p).done).toBe(true);
196
+ });
197
+ });
198
+
199
+ describe("connect() (через clientFactory)", () => {
200
+ const fakeGramjs = (over: Record<string, unknown> = {}) => ({
201
+ connect: async () => true,
202
+ session: { save: () => "NEWSESS" },
203
+ addEventHandler: () => {},
204
+ disconnect: async () => {},
205
+ connected: false,
206
+ ...over,
207
+ });
208
+ const withFactory = (fc: unknown, over: Record<string, unknown> = {}) =>
209
+ new TelegramUserbotAdapter({
210
+ id: "ub1",
211
+ apiId: 1,
212
+ apiHash: "h",
213
+ sessionString: "",
214
+ connectionRetries: 1,
215
+ retryDelayMs: 0,
216
+ clientFactory: (() => fc) as never,
217
+ ...over,
218
+ });
219
+
220
+ it("успех: client установлен, emitted connected, onSessionUpdated при смене сессии", async () => {
221
+ const updated: string[] = [];
222
+ const a = withFactory(fakeGramjs(), { onSessionUpdated: (s: string) => updated.push(s) });
223
+ await a.connect();
224
+ expect((await a.healthEvents()[Symbol.asyncIterator]().next()).value.status).toBe("connected");
225
+ expect(updated).toEqual(["NEWSESS"]);
226
+ });
227
+
228
+ it("AUTH_KEY_DUPLICATED → health auth_key_duplicated + throw", async () => {
229
+ const a = withFactory(fakeGramjs({ connect: async () => { throw new Error("AUTH_KEY_DUPLICATED"); } }));
230
+ await expect(a.connect()).rejects.toThrow("auth key revoked");
231
+ expect((await a.healthEvents()[Symbol.asyncIterator]().next()).value.status).toBe("auth_key_duplicated");
232
+ });
233
+
234
+ it("connect бросает (non-auth) на всех попытках → connection_failed + throw", async () => {
235
+ const a = withFactory(fakeGramjs({ connect: async () => { throw new Error("network blip"); } }));
236
+ await expect(a.connect()).rejects.toThrow("connect failed");
237
+ expect((await a.healthEvents()[Symbol.asyncIterator]().next()).value.status).toBe("connection_failed");
238
+ });
239
+
240
+ it("connect возвращает false → connection_failed", async () => {
241
+ const a = withFactory(fakeGramjs({ connect: async () => false }));
242
+ await expect(a.connect()).rejects.toThrow("connect failed");
243
+ });
244
+
245
+ it("идемпотентность: уже connected → фабрика не зовётся", async () => {
246
+ const a = new TelegramUserbotAdapter({
247
+ id: "ub1", apiId: 1, apiHash: "h", sessionString: "",
248
+ clientFactory: (() => { throw new Error("должен быть no-op"); }) as never,
249
+ });
250
+ priv(a).client = { connected: true };
251
+ await a.connect(); // не бросает → фабрика не вызвана
252
+ });
253
+ });
@@ -69,6 +69,36 @@ export interface TelegramUserbotAdapterOptions {
69
69
  connectionRetries?: number;
70
70
  /** ms между connect-попытками, default 5000. */
71
71
  retryDelayMs?: number;
72
+ /**
73
+ * Фабрика gramjs-клиента. По умолчанию — реальный конструктор
74
+ * (StringSession + GramjsClient + setLogLevel). Инъектируется в тестах,
75
+ * чтобы покрыть connect() без живого MTProto-соединения.
76
+ */
77
+ clientFactory?: (opts: GramjsClientFactoryOpts) => GramjsClient;
78
+ }
79
+
80
+ export interface GramjsClientFactoryOpts {
81
+ sessionString: string;
82
+ apiId: number;
83
+ apiHash: string;
84
+ connectionRetries: number;
85
+ retryDelayMs: number;
86
+ }
87
+
88
+ /** Реальная фабрика gramjs-клиента (production-путь connect()). */
89
+ function defaultGramjsClientFactory(o: GramjsClientFactoryOpts): GramjsClient {
90
+ const session = new StringSession(o.sessionString);
91
+ const client = new GramjsClient(session, o.apiId, o.apiHash, {
92
+ // count, not flag — 5 = разумный лимит чтобы supervisor мог перерестартить.
93
+ connectionRetries: o.connectionRetries,
94
+ retryDelay: o.retryDelayMs,
95
+ timeout: 30,
96
+ });
97
+ // GramJS на уровне ERROR console.error'ит рутинные ping-timeout'ы своего
98
+ // update-loop'а (он сам их ловит и reconnect'ит). Для long-running SaaS это
99
+ // шум; значимые статусы отдаём через healthEvents().
100
+ client.setLogLevel(LogLevel.NONE);
101
+ return client;
72
102
  }
73
103
 
74
104
  const TG_USERBOT_CAPABILITIES: ChannelCapabilities = {
@@ -117,19 +147,13 @@ export class TelegramUserbotAdapter implements ChannelAdapter {
117
147
  */
118
148
  async connect(): Promise<void> {
119
149
  if (this.client && this.client.connected) return;
120
- const session = new StringSession(this.opts.sessionString);
121
- const client = new GramjsClient(session, this.opts.apiId, this.opts.apiHash, {
122
- // count, not flag — Infinity = бесконечно, 5 = разумный лимит чтобы
123
- // supervisor мог перерестартить вместо infinite-spin'а.
150
+ const client = (this.opts.clientFactory ?? defaultGramjsClientFactory)({
151
+ sessionString: this.opts.sessionString,
152
+ apiId: this.opts.apiId,
153
+ apiHash: this.opts.apiHash,
124
154
  connectionRetries: this.opts.connectionRetries ?? 5,
125
- retryDelay: this.opts.retryDelayMs ?? 3000,
126
- timeout: 30,
155
+ retryDelayMs: this.opts.retryDelayMs ?? 3000,
127
156
  });
128
- // GramJS на уровне ERROR console.error'ит рутинные ping-timeout'ы своего
129
- // update-loop'а (он сам их ловит и делает reconnect — см. updates.js).
130
- // Для long-running SaaS это шум; значимые статусы (connected /
131
- // connection_failed / auth_key_duplicated) мы отдаём через healthEvents().
132
- client.setLogLevel(LogLevel.NONE);
133
157
 
134
158
  const maxAttempts = this.opts.connectionRetries ?? 5;
135
159
  let lastErr: string | null = null;
@@ -0,0 +1,118 @@
1
+ import type { TelegramClient } from "telegram";
2
+ import { describe, expect, it } from "bun:test";
3
+ import {
4
+ finishUserbotLogin,
5
+ startUserbotLogin,
6
+ submitUserbot2fa,
7
+ submitUserbotCode,
8
+ UserbotLoginError,
9
+ type UserbotLoginErrorCode,
10
+ } from "./login.ts";
11
+
12
+ function fc(over: Record<string, unknown> = {}): TelegramClient {
13
+ return {
14
+ invoke: async () => ({}),
15
+ getMe: async () => ({ id: 777, username: "u", phone: "+1" }),
16
+ session: { save: () => "SESSION" },
17
+ ...over,
18
+ } as unknown as TelegramClient;
19
+ }
20
+ const throwing = (msg: string) =>
21
+ fc({
22
+ invoke: async () => {
23
+ throw new Error(msg);
24
+ },
25
+ });
26
+ const code = (client: TelegramClient) =>
27
+ submitUserbotCode({ client, phone: "+100", phoneCodeHash: "h", code: "12345" });
28
+
29
+ describe("UserbotLoginError", () => {
30
+ it("несёт code/retryAfterSec/name", () => {
31
+ const e = new UserbotLoginError("flood_wait", "msg", 30);
32
+ expect(e.code).toBe("flood_wait");
33
+ expect(e.retryAfterSec).toBe(30);
34
+ expect(e.name).toBe("UserbotLoginError");
35
+ });
36
+ });
37
+
38
+ describe("finishUserbotLogin", () => {
39
+ it("собирает session + идентификаторы", async () => {
40
+ expect(await finishUserbotLogin(fc())).toEqual({
41
+ sessionString: "SESSION",
42
+ userId: "777",
43
+ username: "u",
44
+ phone: "+1",
45
+ });
46
+ });
47
+ it("null username/phone когда их нет", async () => {
48
+ const r = await finishUserbotLogin(fc({ getMe: async () => ({ id: 1 }) }));
49
+ expect(r.username).toBeNull();
50
+ expect(r.phone).toBeNull();
51
+ });
52
+ });
53
+
54
+ describe("submitUserbotCode", () => {
55
+ it("успех → needs2fa:false + session", async () => {
56
+ const r = await code(fc());
57
+ expect(r).toMatchObject({ needs2fa: false, sessionString: "SESSION", userId: "777" });
58
+ });
59
+ it("SESSION_PASSWORD_NEEDED → needs2fa:true", async () => {
60
+ expect(await code(throwing("SESSION_PASSWORD_NEEDED"))).toEqual({ needs2fa: true });
61
+ });
62
+ });
63
+
64
+ describe("mapRpcError (через submitUserbotCode)", () => {
65
+ const cases: Array<[string, UserbotLoginErrorCode, number | undefined]> = [
66
+ ["FLOOD_WAIT_30", "flood_wait", 30],
67
+ ["PHONE_NUMBER_INVALID", "phone_invalid", undefined],
68
+ ["PHONE_CODE_EXPIRED", "code_expired", undefined],
69
+ ["PHONE_CODE_INVALID", "code_invalid", undefined],
70
+ ["что-то совсем другое", "unknown", undefined],
71
+ ];
72
+ for (const [raw, expectedCode, retry] of cases) {
73
+ it(`${raw} → ${expectedCode}`, async () => {
74
+ try {
75
+ await code(throwing(raw));
76
+ throw new Error("ожидалась ошибка");
77
+ } catch (err) {
78
+ expect(err).toBeInstanceOf(UserbotLoginError);
79
+ expect((err as UserbotLoginError).code).toBe(expectedCode);
80
+ if (retry !== undefined) expect((err as UserbotLoginError).retryAfterSec).toBe(retry);
81
+ }
82
+ });
83
+ }
84
+ });
85
+
86
+ describe("submitUserbot2fa", () => {
87
+ it("ошибка invoke → password_invalid", async () => {
88
+ await expect(
89
+ submitUserbot2fa({ client: throwing("PASSWORD_HASH_INVALID"), password: "p" }),
90
+ ).rejects.toMatchObject({ code: "password_invalid" });
91
+ });
92
+ });
93
+
94
+ describe("startUserbotLogin (через clientFactory)", () => {
95
+ it("успех → { client, phoneCodeHash }", async () => {
96
+ const fc = { connect: async () => {}, sendCode: async () => ({ phoneCodeHash: "hash" }), disconnect: async () => {} };
97
+ const r = await startUserbotLogin({ apiId: 1, apiHash: "h", phone: "+100", clientFactory: (() => fc) as never });
98
+ expect(r.phoneCodeHash).toBe("hash");
99
+ expect(r.client).toBe(fc as never);
100
+ });
101
+
102
+ it("ошибка sendCode → disconnect + mapRpcError", async () => {
103
+ let disconnected = false;
104
+ const fc = {
105
+ connect: async () => {},
106
+ sendCode: async () => {
107
+ throw new Error("PHONE_NUMBER_INVALID");
108
+ },
109
+ disconnect: async () => {
110
+ disconnected = true;
111
+ },
112
+ };
113
+ await expect(
114
+ startUserbotLogin({ apiId: 1, apiHash: "h", phone: "+100", clientFactory: (() => fc) as never }),
115
+ ).rejects.toMatchObject({ code: "phone_invalid" });
116
+ expect(disconnected).toBe(true);
117
+ });
118
+ });
@@ -78,15 +78,22 @@ function mapRpcError(err: unknown): UserbotLoginError {
78
78
  return new UserbotLoginError("unknown", msg);
79
79
  }
80
80
 
81
+ /** Фабрика клиента для startUserbotLogin (по умолчанию — реальный конструктор). */
82
+ export type LoginClientFactory = (apiId: number, apiHash: string) => TelegramClient;
83
+
84
+ function defaultLoginClientFactory(apiId: number, apiHash: string): TelegramClient {
85
+ return new TelegramClient(new StringSession(""), apiId, apiHash, { connectionRetries: 3 });
86
+ }
87
+
81
88
  /** Шаг 1: подключиться и отправить код подтверждения на номер. */
82
89
  export async function startUserbotLogin(opts: {
83
90
  apiId: number;
84
91
  apiHash: string;
85
92
  phone: string;
93
+ /** Инъекция клиента для тестов; по умолчанию — defaultLoginClientFactory. */
94
+ clientFactory?: LoginClientFactory;
86
95
  }): Promise<StartedUserbotLogin> {
87
- const client = new TelegramClient(new StringSession(""), opts.apiId, opts.apiHash, {
88
- connectionRetries: 3,
89
- });
96
+ const client = (opts.clientFactory ?? defaultLoginClientFactory)(opts.apiId, opts.apiHash);
90
97
  await client.connect();
91
98
  try {
92
99
  const { phoneCodeHash } = await client.sendCode(