@imessaging/telegram-mtproto 0.8.5 → 0.8.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imessaging/telegram-mtproto",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "Telegram MTProto user-account transport for imessaging",
5
5
  "keywords": [
6
6
  "messaging",
@@ -31,7 +31,7 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@imessaging/core": "^0.8.5",
34
+ "@imessaging/core": "^0.8.7",
35
35
  "big-integer": "^1.6.52"
36
36
  },
37
37
  "peerDependencies": {
@@ -1,4 +1,7 @@
1
1
  import type {
2
+ ContactCard,
3
+ ContactTransport,
4
+ PhoneLookupOptions,
2
5
  PhoneLookupTransport,
3
6
  PhoneMatch,
4
7
  DraftTransport,
@@ -109,6 +112,7 @@ function inputPeerChatId(peer: Api.TypeInputPeer): string {
109
112
 
110
113
  export class TelegramMtprotoTransport
111
114
  implements
115
+ ContactTransport,
112
116
  DraftTransport,
113
117
  EditableMessageTransport,
114
118
  EditedMessageTransport,
@@ -228,10 +232,10 @@ export class TelegramMtprotoTransport
228
232
 
229
233
  /**
230
234
  * Учётка по номеру телефона: сначала contacts.ResolvePhone (номер, открытый настройками приватности),
231
- * затем contacts.ImportContacts (номер виден только контакту). Контакт, заведённый ради поиска, сразу
232
- * удаляется — адресная книга не растёт; бывший контактом до поиска остаётся на месте.
235
+ * затем contacts.ImportContacts (номер виден только контакту) — он и заводит контакт. С `keepContact: false`
236
+ * заведённый контакт удаляется сразу; бывший контактом до поиска не удаляется никогда.
233
237
  */
234
- async findByPhone(phone: string): Promise<PhoneMatch | null> {
238
+ async findByPhone(phone: string, options: PhoneLookupOptions = {}): Promise<PhoneMatch | null> {
235
239
  this.assertConnected();
236
240
  const digits = phone.replace(/[\s()-]/g, "").replace(/^\+/, "");
237
241
  if (!/^\d{7,15}$/.test(digits))
@@ -263,8 +267,8 @@ export class TelegramMtprotoTransport
263
267
  new Api.InputPhoneContact({
264
268
  clientId: bigInt(Date.now()),
265
269
  phone: `+${digits}`,
266
- firstName: `+${digits}`,
267
- lastName: "",
270
+ firstName: options.firstName?.trim() || `+${digits}`,
271
+ lastName: options.lastName?.trim() ?? "",
268
272
  }),
269
273
  ],
270
274
  }),
@@ -274,7 +278,7 @@ export class TelegramMtprotoTransport
274
278
  try {
275
279
  return await this.rememberPhoneMatch(user);
276
280
  } finally {
277
- if (!known.has(user.id.toString()) && user.accessHash) {
281
+ if (options.keepContact === false && !known.has(user.id.toString()) && user.accessHash) {
278
282
  await this.client.invoke(
279
283
  new Api.contacts.DeleteContacts({
280
284
  id: [new Api.InputUser({ userId: user.id, accessHash: user.accessHash })],
@@ -284,6 +288,43 @@ export class TelegramMtprotoTransport
284
288
  }
285
289
  }
286
290
 
291
+ /**
292
+ * Контакт в адресной книге: имя и заметка (contacts.addContact.note — поле слоя Telegram, не фамилия).
293
+ * Свой номер человеку не открываем: addPhonePrivacyException не ставится. Пир — только из хранилища:
294
+ * без него запрос ушёл бы искать человека по сети, а это дело findByPhone и переписки.
295
+ */
296
+ async saveContact(contact: ContactCard): Promise<void> {
297
+ this.assertConnected();
298
+ if (!/^\d+$/.test(contact.userId))
299
+ throw new Error(`Telegram user id must be numeric: ${contact.userId}`);
300
+ if (!contact.firstName.trim()) throw new Error("Contact firstName must not be empty");
301
+ const stored = await this.peerStore.get(this.options.accountId, {
302
+ type: "user",
303
+ id: contact.userId,
304
+ });
305
+ if (!stored?.accessHash) {
306
+ throw new Error(
307
+ `Telegram user ${contact.userId} is unknown to this account: find it with findByPhone or receive a message first`,
308
+ );
309
+ }
310
+ const phone = contact.phone?.replace(/[\s()-]/g, "") ?? "";
311
+ await this.client.invoke(
312
+ new Api.contacts.AddContact({
313
+ id: new Api.InputUser({
314
+ userId: bigInt(contact.userId),
315
+ accessHash: bigInt(stored.accessHash),
316
+ }),
317
+ firstName: contact.firstName.trim(),
318
+ lastName: contact.lastName?.trim() ?? "",
319
+ phone,
320
+ ...(contact.note
321
+ ? { note: new Api.TextWithEntities({ text: contact.note, entities: [] }) }
322
+ : {}),
323
+ }),
324
+ );
325
+ this.lastActivityAt = new Date();
326
+ }
327
+
287
328
  private async rememberPhoneMatch(user: Api.User): Promise<PhoneMatch> {
288
329
  const userId = user.id.toString();
289
330
  if (user.accessHash) {