@overpod/mcp-telegram 1.25.0 → 1.26.1
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/CHANGELOG.md +35 -0
- package/README.md +35 -5
- package/dist/index.js +4 -1
- package/dist/rate-limiter.d.ts +9 -3
- package/dist/rate-limiter.js +23 -16
- package/dist/telegram-client.d.ts +134 -18
- package/dist/telegram-client.js +601 -136
- package/dist/telegram-helpers.d.ts +470 -0
- package/dist/telegram-helpers.js +870 -0
- package/dist/tools/account.js +22 -6
- package/dist/tools/boosts.d.ts +3 -0
- package/dist/tools/boosts.js +65 -0
- package/dist/tools/chats.js +155 -5
- package/dist/tools/contacts.js +3 -3
- package/dist/tools/extras.js +3 -3
- package/dist/tools/group-calls.d.ts +4 -0
- package/dist/tools/group-calls.js +77 -0
- package/dist/tools/index.js +10 -0
- package/dist/tools/messages.js +203 -11
- package/dist/tools/quick-replies.d.ts +4 -0
- package/dist/tools/quick-replies.js +58 -0
- package/dist/tools/reactions.js +45 -2
- package/dist/tools/shared.d.ts +3 -3
- package/dist/tools/shared.js +8 -7
- package/dist/tools/stars.d.ts +4 -0
- package/dist/tools/stars.js +71 -0
- package/dist/tools/stickers.js +5 -5
- package/dist/tools/stories.d.ts +3 -0
- package/dist/tools/stories.js +107 -0
- package/package.json +1 -1
- package/dist/__tests__/admin-log.test.d.ts +0 -1
- package/dist/__tests__/admin-log.test.js +0 -41
- package/dist/__tests__/rate-limiter.test.d.ts +0 -1
- package/dist/__tests__/rate-limiter.test.js +0 -81
- package/dist/__tests__/reactions.test.d.ts +0 -1
- package/dist/__tests__/reactions.test.js +0 -23
- package/dist/__tests__/set-chat-permissions-merge.test.d.ts +0 -1
- package/dist/__tests__/set-chat-permissions-merge.test.js +0 -107
- package/dist/__tests__/tools/shared.test.d.ts +0 -1
- package/dist/__tests__/tools/shared.test.js +0 -110
package/dist/telegram-client.js
CHANGED
|
@@ -10,6 +10,8 @@ import { CustomFile } from "telegram/client/uploads.js";
|
|
|
10
10
|
import { StringSession } from "telegram/sessions/index.js";
|
|
11
11
|
import { Api } from "telegram/tl/index.js";
|
|
12
12
|
import { RateLimiter } from "./rate-limiter.js";
|
|
13
|
+
import { describeAdminLogAction, describeAdminLogDetails, describeKeyboardButton, mergeBannedRights, reactionToEmoji, summarizeAllStories, summarizeBoostsList, summarizeBoostsStatus, summarizeBroadcastStats, summarizeBusinessChatLinks, summarizeChannelDifference, summarizeGroupCall, summarizeGroupCallParticipants, summarizeMegagroupStats, summarizeMyBoosts, summarizePeerStories, summarizeQuickReplies, summarizeQuickReplyMessages, summarizeStarsStatus, summarizeStoriesById, summarizeStoryViewsList, summarizeUpdatesDifference, } from "./telegram-helpers.js";
|
|
14
|
+
export { describeAdminLogAction, describeAdminLogDetails, describeKeyboardButton, mergeBannedRights, peerToCompact, reactionToEmoji, summarizeAllStories, summarizeBoost, summarizeBoostsList, summarizeBoostsStatus, summarizeBroadcastStats, summarizeBusinessChatLink, summarizeBusinessChatLinks, summarizeChannelDifference, summarizeGroupCall, summarizeGroupCallInfo, summarizeGroupCallParticipant, summarizeGroupCallParticipants, summarizeMegagroupStats, summarizeMyBoost, summarizeMyBoosts, summarizePeerStories, summarizePrepaidGiveaway, summarizeQuickReplies, summarizeQuickReply, summarizeQuickReplyMessage, summarizeQuickReplyMessages, summarizeStarsAmount, summarizeStarsStatus, summarizeStarsSubscription, summarizeStarsTransaction, summarizeStarsTransactionPeer, summarizeStoriesById, summarizeStoryItem, summarizeStoryView, summarizeStoryViewsList, summarizeUpdatesDifference, } from "./telegram-helpers.js";
|
|
13
15
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
16
|
const LEGACY_SESSION_FILE = join(__dirname, "..", ".telegram-session");
|
|
15
17
|
const DEFAULT_SESSION_DIR = join(homedir(), ".mcp-telegram");
|
|
@@ -44,137 +46,6 @@ function ensureSessionDir(filePath) {
|
|
|
44
46
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
|
-
export function describeAdminLogAction(action) {
|
|
48
|
-
const prefix = "ChannelAdminLogEventAction";
|
|
49
|
-
const raw = action.className.startsWith(prefix) ? action.className.slice(prefix.length) : action.className;
|
|
50
|
-
return raw
|
|
51
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
|
|
52
|
-
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
53
|
-
.toLowerCase();
|
|
54
|
-
}
|
|
55
|
-
export function describeAdminLogDetails(action, describeUser) {
|
|
56
|
-
if (action instanceof Api.ChannelAdminLogEventActionChangeTitle) {
|
|
57
|
-
return `"${action.prevValue}" → "${action.newValue}"`;
|
|
58
|
-
}
|
|
59
|
-
if (action instanceof Api.ChannelAdminLogEventActionChangeAbout) {
|
|
60
|
-
return `description changed`;
|
|
61
|
-
}
|
|
62
|
-
if (action instanceof Api.ChannelAdminLogEventActionChangeUsername) {
|
|
63
|
-
return `@${action.prevValue || "-"} → @${action.newValue || "-"}`;
|
|
64
|
-
}
|
|
65
|
-
if (action instanceof Api.ChannelAdminLogEventActionUpdatePinned) {
|
|
66
|
-
return `message #${action.message instanceof Api.Message ? action.message.id : "?"}`;
|
|
67
|
-
}
|
|
68
|
-
if (action instanceof Api.ChannelAdminLogEventActionEditMessage) {
|
|
69
|
-
return `message #${action.newMessage instanceof Api.Message ? action.newMessage.id : "?"}`;
|
|
70
|
-
}
|
|
71
|
-
if (action instanceof Api.ChannelAdminLogEventActionDeleteMessage) {
|
|
72
|
-
return `message #${action.message instanceof Api.Message ? action.message.id : "?"}`;
|
|
73
|
-
}
|
|
74
|
-
if (action instanceof Api.ChannelAdminLogEventActionParticipantInvite) {
|
|
75
|
-
const p = action.participant;
|
|
76
|
-
return `invited user ${"userId" in p ? describeUser(p.userId) : "?"}`;
|
|
77
|
-
}
|
|
78
|
-
if (action instanceof Api.ChannelAdminLogEventActionParticipantToggleBan) {
|
|
79
|
-
const p = action.newParticipant;
|
|
80
|
-
if (p instanceof Api.ChannelParticipantBanned) {
|
|
81
|
-
const uid = p.peer instanceof Api.PeerUser ? p.peer.userId : undefined;
|
|
82
|
-
return `banned user ${uid ? describeUser(uid) : "?"}`;
|
|
83
|
-
}
|
|
84
|
-
return `unbanned user ${"userId" in p ? describeUser(p.userId) : "?"}`;
|
|
85
|
-
}
|
|
86
|
-
if (action instanceof Api.ChannelAdminLogEventActionParticipantToggleAdmin) {
|
|
87
|
-
const p = action.newParticipant;
|
|
88
|
-
return `admin rights changed for ${"userId" in p ? describeUser(p.userId) : "?"}`;
|
|
89
|
-
}
|
|
90
|
-
if (action instanceof Api.ChannelAdminLogEventActionToggleSlowMode) {
|
|
91
|
-
return `${action.prevValue}s → ${action.newValue}s`;
|
|
92
|
-
}
|
|
93
|
-
if (action instanceof Api.ChannelAdminLogEventActionToggleInvites) {
|
|
94
|
-
return `invites ${action.newValue ? "enabled" : "disabled"}`;
|
|
95
|
-
}
|
|
96
|
-
if (action instanceof Api.ChannelAdminLogEventActionToggleSignatures) {
|
|
97
|
-
return `signatures ${action.newValue ? "enabled" : "disabled"}`;
|
|
98
|
-
}
|
|
99
|
-
if (action instanceof Api.ChannelAdminLogEventActionTogglePreHistoryHidden) {
|
|
100
|
-
return `pre-history hidden: ${action.newValue}`;
|
|
101
|
-
}
|
|
102
|
-
if (action instanceof Api.ChannelAdminLogEventActionChangeHistoryTTL) {
|
|
103
|
-
return `${action.prevValue}s → ${action.newValue}s`;
|
|
104
|
-
}
|
|
105
|
-
if (action instanceof Api.ChannelAdminLogEventActionChangeStickerSet) {
|
|
106
|
-
return `sticker set changed`;
|
|
107
|
-
}
|
|
108
|
-
if (action instanceof Api.ChannelAdminLogEventActionChangeLinkedChat) {
|
|
109
|
-
return `${action.prevValue.toString()} → ${action.newValue.toString()}`;
|
|
110
|
-
}
|
|
111
|
-
if (action instanceof Api.ChannelAdminLogEventActionStopPoll) {
|
|
112
|
-
return `poll in message #${action.message instanceof Api.Message ? action.message.id : "?"}`;
|
|
113
|
-
}
|
|
114
|
-
if (action instanceof Api.ChannelAdminLogEventActionSendMessage) {
|
|
115
|
-
return `message #${action.message instanceof Api.Message ? action.message.id : "?"}`;
|
|
116
|
-
}
|
|
117
|
-
if (action instanceof Api.ChannelAdminLogEventActionCreateTopic) {
|
|
118
|
-
return `topic "${action.topic instanceof Api.ForumTopic ? action.topic.title : "?"}"`;
|
|
119
|
-
}
|
|
120
|
-
if (action instanceof Api.ChannelAdminLogEventActionDeleteTopic) {
|
|
121
|
-
return `topic "${action.topic instanceof Api.ForumTopic ? action.topic.title : "?"}"`;
|
|
122
|
-
}
|
|
123
|
-
if (action instanceof Api.ChannelAdminLogEventActionEditTopic) {
|
|
124
|
-
return `topic "${action.newTopic instanceof Api.ForumTopic ? action.newTopic.title : "?"}"`;
|
|
125
|
-
}
|
|
126
|
-
return "";
|
|
127
|
-
}
|
|
128
|
-
export function reactionToEmoji(reaction) {
|
|
129
|
-
if (reaction instanceof Api.ReactionEmoji)
|
|
130
|
-
return reaction.emoticon;
|
|
131
|
-
if (reaction instanceof Api.ReactionCustomEmoji)
|
|
132
|
-
return `custom:${reaction.documentId.toString()}`;
|
|
133
|
-
if (reaction instanceof Api.ReactionPaid)
|
|
134
|
-
return "⭐";
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
const BANNED_RIGHT_FLAGS = [
|
|
138
|
-
"sendMessages",
|
|
139
|
-
"sendMedia",
|
|
140
|
-
"sendStickers",
|
|
141
|
-
"sendGifs",
|
|
142
|
-
"sendPolls",
|
|
143
|
-
"sendInline",
|
|
144
|
-
"embedLinks",
|
|
145
|
-
"changeInfo",
|
|
146
|
-
"inviteUsers",
|
|
147
|
-
"pinMessages",
|
|
148
|
-
];
|
|
149
|
-
// Newer granular flags not exposed in ChatPermissions input but must be preserved from currentRights
|
|
150
|
-
const EXTRA_BANNED_RIGHT_FLAGS = [
|
|
151
|
-
"sendGames",
|
|
152
|
-
"manageTopics",
|
|
153
|
-
"sendPhotos",
|
|
154
|
-
"sendVideos",
|
|
155
|
-
"sendRoundvideos",
|
|
156
|
-
"sendAudios",
|
|
157
|
-
"sendVoices",
|
|
158
|
-
"sendDocs",
|
|
159
|
-
"sendPlain",
|
|
160
|
-
];
|
|
161
|
-
export function mergeBannedRights(current, permissions) {
|
|
162
|
-
const result = {};
|
|
163
|
-
for (const flag of BANNED_RIGHT_FLAGS) {
|
|
164
|
-
const userValue = permissions[flag];
|
|
165
|
-
if (userValue !== undefined) {
|
|
166
|
-
result[flag] = !userValue;
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
result[flag] = Boolean(current?.[flag]);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// Preserve newer granular flags from existing rights so they are not silently cleared
|
|
173
|
-
for (const flag of EXTRA_BANNED_RIGHT_FLAGS) {
|
|
174
|
-
result[flag] = Boolean(current?.[flag]);
|
|
175
|
-
}
|
|
176
|
-
return result;
|
|
177
|
-
}
|
|
178
49
|
export class TelegramService {
|
|
179
50
|
client = null;
|
|
180
51
|
apiId;
|
|
@@ -184,6 +55,7 @@ export class TelegramService {
|
|
|
184
55
|
sessionPath;
|
|
185
56
|
rateLimiter = new RateLimiter();
|
|
186
57
|
lastTypingAt = new Map();
|
|
58
|
+
entityCache = new Map();
|
|
187
59
|
lastError = "";
|
|
188
60
|
get sessionDir() {
|
|
189
61
|
return dirname(this.sessionPath);
|
|
@@ -300,6 +172,7 @@ export class TelegramService {
|
|
|
300
172
|
this.connected = false;
|
|
301
173
|
this.sessionString = "";
|
|
302
174
|
this.client = null;
|
|
175
|
+
this.entityCache.clear();
|
|
303
176
|
if (existsSync(this.sessionPath)) {
|
|
304
177
|
await unlink(this.sessionPath);
|
|
305
178
|
}
|
|
@@ -317,6 +190,7 @@ export class TelegramService {
|
|
|
317
190
|
await this.client.destroy();
|
|
318
191
|
this.connected = false;
|
|
319
192
|
this.client = null;
|
|
193
|
+
this.entityCache.clear();
|
|
320
194
|
}
|
|
321
195
|
}
|
|
322
196
|
/**
|
|
@@ -916,13 +790,17 @@ export class TelegramService {
|
|
|
916
790
|
* Resolve a chat by ID, username, or display name.
|
|
917
791
|
* Falls back to searching user's dialogs if getEntity() fails.
|
|
918
792
|
*/
|
|
919
|
-
// biome-ignore lint: GramJS has no proper entity union type
|
|
920
793
|
async resolveChat(chatId) {
|
|
921
794
|
if (!this.client)
|
|
922
795
|
throw new Error(NOT_CONNECTED_ERROR);
|
|
796
|
+
const cached = this.entityCache.get(chatId);
|
|
797
|
+
if (cached)
|
|
798
|
+
return cached;
|
|
923
799
|
// First try direct resolve (numeric ID, username, phone)
|
|
924
800
|
try {
|
|
925
|
-
|
|
801
|
+
const entity = await this.client.getEntity(chatId);
|
|
802
|
+
this.entityCache.set(chatId, entity);
|
|
803
|
+
return entity;
|
|
926
804
|
}
|
|
927
805
|
catch {
|
|
928
806
|
// Fall through to dialog search
|
|
@@ -932,12 +810,16 @@ export class TelegramService {
|
|
|
932
810
|
const query = chatId.toLowerCase();
|
|
933
811
|
// Exact match first
|
|
934
812
|
const exact = dialogs.find((d) => d.title?.toLowerCase() === query);
|
|
935
|
-
if (exact?.entity)
|
|
813
|
+
if (exact?.entity) {
|
|
814
|
+
this.entityCache.set(chatId, exact.entity);
|
|
936
815
|
return exact.entity;
|
|
816
|
+
}
|
|
937
817
|
// Partial match
|
|
938
818
|
const partial = dialogs.filter((d) => d.title?.toLowerCase().includes(query));
|
|
939
|
-
if (partial.length === 1 && partial[0].entity)
|
|
819
|
+
if (partial.length === 1 && partial[0].entity) {
|
|
820
|
+
this.entityCache.set(chatId, partial[0].entity);
|
|
940
821
|
return partial[0].entity;
|
|
822
|
+
}
|
|
941
823
|
if (partial.length > 1) {
|
|
942
824
|
const matches = partial.map((d) => ` ${d.title} (${d.entity?.id?.toString() ?? "?"})`).join("\n");
|
|
943
825
|
throw new Error(`Multiple chats match "${chatId}". Use the numeric ID instead:\n${matches}`);
|
|
@@ -948,8 +830,10 @@ export class TelegramService {
|
|
|
948
830
|
* Resolve chatId to a peer string that GramJS methods accept.
|
|
949
831
|
* Handles display names by searching dialogs.
|
|
950
832
|
*/
|
|
951
|
-
// biome-ignore lint: GramJS has no proper entity union type
|
|
952
833
|
async resolvePeer(chatId) {
|
|
834
|
+
// Normalize '@me' — GramJS only intercepts the plain 'me' string as InputPeerSelf
|
|
835
|
+
if (chatId === "@me")
|
|
836
|
+
return "me";
|
|
953
837
|
// Numeric IDs and @usernames work directly
|
|
954
838
|
if (/^-?\d+$/.test(chatId) || chatId.startsWith("@"))
|
|
955
839
|
return chatId;
|
|
@@ -2151,6 +2035,375 @@ export class TelegramService {
|
|
|
2151
2035
|
await this.client?.invoke(new Api.channels.ToggleSlowMode({ channel: entity, seconds }));
|
|
2152
2036
|
}, `setSlowMode ${chatId}`);
|
|
2153
2037
|
}
|
|
2038
|
+
async toggleChannelSignatures(chatId, enabled) {
|
|
2039
|
+
if (!this.client || !this.connected)
|
|
2040
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2041
|
+
return this.rateLimiter.execute(async () => {
|
|
2042
|
+
const entity = await this.resolveChat(chatId);
|
|
2043
|
+
if (!(entity instanceof Api.Channel)) {
|
|
2044
|
+
throw new Error("Channel signatures are only available for broadcast channels (not groups or supergroups)");
|
|
2045
|
+
}
|
|
2046
|
+
if (entity.megagroup) {
|
|
2047
|
+
throw new Error("Channel signatures are only available for broadcast channels, not supergroups");
|
|
2048
|
+
}
|
|
2049
|
+
await this.client?.invoke(new Api.channels.ToggleSignatures({ channel: entity, signaturesEnabled: enabled }));
|
|
2050
|
+
}, `toggleChannelSignatures ${chatId}`);
|
|
2051
|
+
}
|
|
2052
|
+
async toggleAntiSpam(chatId, enabled) {
|
|
2053
|
+
if (!this.client || !this.connected)
|
|
2054
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2055
|
+
return this.rateLimiter.execute(async () => {
|
|
2056
|
+
const entity = await this.resolveChat(chatId);
|
|
2057
|
+
if (!(entity instanceof Api.Channel)) {
|
|
2058
|
+
throw new Error("Aggressive anti-spam is only available for supergroups");
|
|
2059
|
+
}
|
|
2060
|
+
if (!entity.megagroup) {
|
|
2061
|
+
throw new Error("Aggressive anti-spam is only available for supergroups, not broadcast channels");
|
|
2062
|
+
}
|
|
2063
|
+
await this.client?.invoke(new Api.channels.ToggleAntiSpam({ channel: entity, enabled }));
|
|
2064
|
+
}, `toggleAntiSpam ${chatId}`);
|
|
2065
|
+
}
|
|
2066
|
+
async toggleForumMode(chatId, enabled) {
|
|
2067
|
+
if (!this.client || !this.connected)
|
|
2068
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2069
|
+
return this.rateLimiter.execute(async () => {
|
|
2070
|
+
const entity = await this.resolveChat(chatId);
|
|
2071
|
+
if (!(entity instanceof Api.Channel)) {
|
|
2072
|
+
throw new Error("Forum mode is only available for supergroups");
|
|
2073
|
+
}
|
|
2074
|
+
if (!entity.megagroup) {
|
|
2075
|
+
throw new Error("Forum mode is only available for supergroups, not broadcast channels");
|
|
2076
|
+
}
|
|
2077
|
+
await this.client?.invoke(new Api.channels.ToggleForum({ channel: entity, enabled }));
|
|
2078
|
+
}, `toggleForumMode ${chatId}`);
|
|
2079
|
+
}
|
|
2080
|
+
async togglePrehistoryHidden(chatId, hidden) {
|
|
2081
|
+
if (!this.client || !this.connected)
|
|
2082
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2083
|
+
return this.rateLimiter.execute(async () => {
|
|
2084
|
+
const entity = await this.resolveChat(chatId);
|
|
2085
|
+
if (!(entity instanceof Api.Channel)) {
|
|
2086
|
+
throw new Error("Prehistory visibility is only available for supergroups");
|
|
2087
|
+
}
|
|
2088
|
+
if (!entity.megagroup) {
|
|
2089
|
+
throw new Error("Prehistory visibility is only available for supergroups, not broadcast channels");
|
|
2090
|
+
}
|
|
2091
|
+
await this.client?.invoke(new Api.channels.TogglePreHistoryHidden({ channel: entity, enabled: hidden }));
|
|
2092
|
+
}, `togglePrehistoryHidden ${chatId}`);
|
|
2093
|
+
}
|
|
2094
|
+
async setChatAvailableReactions(chatId, reactions) {
|
|
2095
|
+
if (!this.client || !this.connected)
|
|
2096
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2097
|
+
return this.rateLimiter.execute(async () => {
|
|
2098
|
+
const entity = await this.resolveChat(chatId);
|
|
2099
|
+
if (!(entity instanceof Api.Channel) && !(entity instanceof Api.Chat)) {
|
|
2100
|
+
throw new Error("Chat reactions can only be configured for groups, supergroups, and channels");
|
|
2101
|
+
}
|
|
2102
|
+
let availableReactions;
|
|
2103
|
+
if (reactions.type === "all") {
|
|
2104
|
+
availableReactions = new Api.ChatReactionsAll({ allowCustom: reactions.allowCustom });
|
|
2105
|
+
}
|
|
2106
|
+
else if (reactions.type === "none") {
|
|
2107
|
+
availableReactions = new Api.ChatReactionsNone();
|
|
2108
|
+
}
|
|
2109
|
+
else {
|
|
2110
|
+
if (reactions.emoji.length === 0) {
|
|
2111
|
+
throw new Error('reactions.emoji must be non-empty when type is "some" (use type:"none" to disable)');
|
|
2112
|
+
}
|
|
2113
|
+
availableReactions = new Api.ChatReactionsSome({
|
|
2114
|
+
reactions: reactions.emoji.map((emoticon) => new Api.ReactionEmoji({ emoticon })),
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
await this.client?.invoke(new Api.messages.SetChatAvailableReactions({ peer: entity, availableReactions }));
|
|
2118
|
+
}, `setChatAvailableReactions ${chatId}`);
|
|
2119
|
+
}
|
|
2120
|
+
async approveChatJoinRequest(chatId, userId, approved) {
|
|
2121
|
+
if (!this.client || !this.connected)
|
|
2122
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2123
|
+
return this.rateLimiter.execute(async () => {
|
|
2124
|
+
const entity = await this.resolveChat(chatId);
|
|
2125
|
+
if (!(entity instanceof Api.Channel)) {
|
|
2126
|
+
throw new Error("Join request approval is only supported for supergroups and channels, not basic groups");
|
|
2127
|
+
}
|
|
2128
|
+
const user = await this.client?.getEntity(userId);
|
|
2129
|
+
if (!(user instanceof Api.User)) {
|
|
2130
|
+
throw new Error("Target is not a user");
|
|
2131
|
+
}
|
|
2132
|
+
const inputUser = new Api.InputUser({ userId: user.id, accessHash: user.accessHash ?? bigInt.zero });
|
|
2133
|
+
await this.client?.invoke(new Api.messages.HideChatJoinRequest({ peer: entity, userId: inputUser, approved }));
|
|
2134
|
+
}, `approveChatJoinRequest ${chatId}/${userId}`);
|
|
2135
|
+
}
|
|
2136
|
+
async getInlineBotResults(bot, chatId, query, offset) {
|
|
2137
|
+
if (!this.client || !this.connected)
|
|
2138
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2139
|
+
return this.rateLimiter.execute(async () => {
|
|
2140
|
+
const peer = await this.resolveChat(chatId);
|
|
2141
|
+
const botEntity = await this.client?.getEntity(bot);
|
|
2142
|
+
if (!(botEntity instanceof Api.User)) {
|
|
2143
|
+
throw new Error(`'${bot}' is not a user/bot`);
|
|
2144
|
+
}
|
|
2145
|
+
if (!botEntity.bot) {
|
|
2146
|
+
throw new Error(`'${bot}' is not a bot (inline queries require a bot account)`);
|
|
2147
|
+
}
|
|
2148
|
+
const inputBot = new Api.InputUser({
|
|
2149
|
+
userId: botEntity.id,
|
|
2150
|
+
accessHash: botEntity.accessHash ?? bigInt.zero,
|
|
2151
|
+
});
|
|
2152
|
+
const result = await this.client?.invoke(new Api.messages.GetInlineBotResults({
|
|
2153
|
+
bot: inputBot,
|
|
2154
|
+
peer,
|
|
2155
|
+
query,
|
|
2156
|
+
offset: offset ?? "",
|
|
2157
|
+
}));
|
|
2158
|
+
if (!result)
|
|
2159
|
+
throw new Error("No inline bot results returned");
|
|
2160
|
+
return {
|
|
2161
|
+
queryId: result.queryId.toString(),
|
|
2162
|
+
nextOffset: result.nextOffset,
|
|
2163
|
+
cacheTime: result.cacheTime,
|
|
2164
|
+
gallery: result.gallery === true,
|
|
2165
|
+
results: result.results.map((r) => {
|
|
2166
|
+
if (r instanceof Api.BotInlineResult) {
|
|
2167
|
+
return { id: r.id, type: r.type, title: r.title, description: r.description, url: r.url };
|
|
2168
|
+
}
|
|
2169
|
+
const mr = r;
|
|
2170
|
+
return { id: mr.id, type: mr.type, title: mr.title, description: mr.description };
|
|
2171
|
+
}),
|
|
2172
|
+
};
|
|
2173
|
+
}, `getInlineBotResults via ${bot}`);
|
|
2174
|
+
}
|
|
2175
|
+
async sendInlineBotResult(chatId, queryId, resultId, options) {
|
|
2176
|
+
if (!this.client || !this.connected)
|
|
2177
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2178
|
+
return this.rateLimiter.execute(async () => {
|
|
2179
|
+
const peer = await this.resolveChat(chatId);
|
|
2180
|
+
const randomId = bigInt(Math.floor(Math.random() * 1e15));
|
|
2181
|
+
const replyTo = options?.replyTo ? new Api.InputReplyToMessage({ replyToMsgId: options.replyTo }) : undefined;
|
|
2182
|
+
const result = await this.client?.invoke(new Api.messages.SendInlineBotResult({
|
|
2183
|
+
peer,
|
|
2184
|
+
queryId: bigInt(queryId),
|
|
2185
|
+
id: resultId,
|
|
2186
|
+
randomId,
|
|
2187
|
+
...(replyTo ? { replyTo } : {}),
|
|
2188
|
+
...(options?.silent ? { silent: true } : {}),
|
|
2189
|
+
...(options?.hideVia ? { hideVia: true } : {}),
|
|
2190
|
+
...(options?.clearDraft ? { clearDraft: true } : {}),
|
|
2191
|
+
}));
|
|
2192
|
+
if (!result)
|
|
2193
|
+
throw new Error("No response from SendInlineBotResult");
|
|
2194
|
+
if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
|
|
2195
|
+
for (const update of result.updates) {
|
|
2196
|
+
if (update instanceof Api.UpdateMessageID && update.randomId?.equals(randomId)) {
|
|
2197
|
+
return { messageId: update.id };
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
if (result instanceof Api.UpdateShortSentMessage) {
|
|
2202
|
+
return { messageId: result.id };
|
|
2203
|
+
}
|
|
2204
|
+
return { messageId: 0 };
|
|
2205
|
+
}, `sendInlineBotResult ${resultId} to ${chatId}`);
|
|
2206
|
+
}
|
|
2207
|
+
async pressButton(chatId, messageId, options) {
|
|
2208
|
+
if (!this.client || !this.connected)
|
|
2209
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2210
|
+
return this.rateLimiter.execute(async () => {
|
|
2211
|
+
const entity = await this.resolveChat(chatId);
|
|
2212
|
+
let data;
|
|
2213
|
+
if (options.buttonIndex) {
|
|
2214
|
+
const { row, column } = options.buttonIndex;
|
|
2215
|
+
const messages = await this.client?.getMessages(entity, { ids: [messageId] });
|
|
2216
|
+
const msg = messages?.[0];
|
|
2217
|
+
if (!msg)
|
|
2218
|
+
throw new Error(`Message ${messageId} not found in ${chatId}`);
|
|
2219
|
+
const markup = msg.replyMarkup;
|
|
2220
|
+
if (!markup)
|
|
2221
|
+
throw new Error(`Message ${messageId} has no reply markup`);
|
|
2222
|
+
if (!(markup instanceof Api.ReplyInlineMarkup)) {
|
|
2223
|
+
throw new Error(`Message ${messageId} reply markup is ${markup.className} (only ReplyInlineMarkup has callable buttons)`);
|
|
2224
|
+
}
|
|
2225
|
+
const rowEntry = markup.rows[row];
|
|
2226
|
+
if (!rowEntry)
|
|
2227
|
+
throw new Error(`Row ${row} out of bounds (message has ${markup.rows.length} rows)`);
|
|
2228
|
+
const button = rowEntry.buttons[column];
|
|
2229
|
+
if (!button) {
|
|
2230
|
+
throw new Error(`Column ${column} out of bounds in row ${row} (row has ${rowEntry.buttons.length} buttons)`);
|
|
2231
|
+
}
|
|
2232
|
+
if (!(button instanceof Api.KeyboardButtonCallback)) {
|
|
2233
|
+
throw new Error(`Button at (${row},${column}) is ${button.className}, not callable — use the appropriate tool for URL/switch-inline/game buttons`);
|
|
2234
|
+
}
|
|
2235
|
+
if (button.requiresPassword) {
|
|
2236
|
+
throw new Error(`Button at (${row},${column}) requires 2FA password confirmation — not supported by telegram-press-button`);
|
|
2237
|
+
}
|
|
2238
|
+
data = Buffer.from(button.data);
|
|
2239
|
+
}
|
|
2240
|
+
else if (options.data !== undefined) {
|
|
2241
|
+
data = Buffer.from(options.data, "base64");
|
|
2242
|
+
}
|
|
2243
|
+
else {
|
|
2244
|
+
throw new Error("Either buttonIndex or data must be provided");
|
|
2245
|
+
}
|
|
2246
|
+
const answer = await this.client?.invoke(new Api.messages.GetBotCallbackAnswer({
|
|
2247
|
+
peer: entity,
|
|
2248
|
+
msgId: messageId,
|
|
2249
|
+
data,
|
|
2250
|
+
}));
|
|
2251
|
+
if (!answer)
|
|
2252
|
+
throw new Error("No callback answer returned");
|
|
2253
|
+
return {
|
|
2254
|
+
alert: answer.alert,
|
|
2255
|
+
hasUrl: answer.hasUrl,
|
|
2256
|
+
nativeUi: answer.nativeUi,
|
|
2257
|
+
message: answer.message,
|
|
2258
|
+
url: answer.url,
|
|
2259
|
+
cacheTime: answer.cacheTime,
|
|
2260
|
+
};
|
|
2261
|
+
}, `pressButton ${chatId}/${messageId}`);
|
|
2262
|
+
}
|
|
2263
|
+
async getMessageButtons(chatId, messageId) {
|
|
2264
|
+
if (!this.client || !this.connected)
|
|
2265
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2266
|
+
return this.rateLimiter.execute(async () => {
|
|
2267
|
+
const entity = await this.resolveChat(chatId);
|
|
2268
|
+
const messages = await this.client?.getMessages(entity, { ids: [messageId] });
|
|
2269
|
+
const msg = messages?.[0];
|
|
2270
|
+
if (!msg)
|
|
2271
|
+
throw new Error(`Message ${messageId} not found in ${chatId}`);
|
|
2272
|
+
const markup = msg.replyMarkup;
|
|
2273
|
+
if (!markup) {
|
|
2274
|
+
return { markupType: "none", buttons: [] };
|
|
2275
|
+
}
|
|
2276
|
+
if (!(markup instanceof Api.ReplyInlineMarkup) && !(markup instanceof Api.ReplyKeyboardMarkup)) {
|
|
2277
|
+
return { markupType: markup.className, buttons: [] };
|
|
2278
|
+
}
|
|
2279
|
+
const buttons = [];
|
|
2280
|
+
markup.rows.forEach((rowEntry, row) => {
|
|
2281
|
+
rowEntry.buttons.forEach((button, col) => {
|
|
2282
|
+
buttons.push(describeKeyboardButton(button, row, col));
|
|
2283
|
+
});
|
|
2284
|
+
});
|
|
2285
|
+
return { markupType: markup.className, buttons };
|
|
2286
|
+
}, `getMessageButtons ${chatId}/${messageId}`);
|
|
2287
|
+
}
|
|
2288
|
+
async getBroadcastStats(chatId, options) {
|
|
2289
|
+
if (!this.client || !this.connected)
|
|
2290
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2291
|
+
return this.rateLimiter.execute(async () => {
|
|
2292
|
+
const entity = await this.resolveChat(chatId);
|
|
2293
|
+
if (!(entity instanceof Api.Channel)) {
|
|
2294
|
+
throw new Error("Broadcast stats are only available for channels");
|
|
2295
|
+
}
|
|
2296
|
+
if (entity.megagroup) {
|
|
2297
|
+
throw new Error("Broadcast stats are only available for broadcast channels, not supergroups (use telegram-get-megagroup-stats)");
|
|
2298
|
+
}
|
|
2299
|
+
let result;
|
|
2300
|
+
try {
|
|
2301
|
+
const response = await this.client?.invoke(new Api.stats.GetBroadcastStats({ channel: entity, dark: options?.dark }));
|
|
2302
|
+
if (!response) {
|
|
2303
|
+
throw new Error("channel has no stats (may require Telegram Premium admin)");
|
|
2304
|
+
}
|
|
2305
|
+
result = response;
|
|
2306
|
+
}
|
|
2307
|
+
catch (e) {
|
|
2308
|
+
const msg = e.message ?? String(e);
|
|
2309
|
+
if (/CHAT_ADMIN_REQUIRED|ADMIN_RANK_INVALID/i.test(msg)) {
|
|
2310
|
+
throw new Error("Access denied: channel stats require admin rights (and may require Telegram Premium)");
|
|
2311
|
+
}
|
|
2312
|
+
if (/STATS_UNAVAILABLE|BROADCAST_REQUIRED|PARTICIPANTS_TOO_FEW/i.test(msg)) {
|
|
2313
|
+
throw new Error("channel has no stats (may require Telegram Premium admin)");
|
|
2314
|
+
}
|
|
2315
|
+
throw e;
|
|
2316
|
+
}
|
|
2317
|
+
return summarizeBroadcastStats(result, options?.includeGraphs === true);
|
|
2318
|
+
}, `getBroadcastStats ${chatId}`);
|
|
2319
|
+
}
|
|
2320
|
+
async getMegagroupStats(chatId, options) {
|
|
2321
|
+
if (!this.client || !this.connected)
|
|
2322
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2323
|
+
return this.rateLimiter.execute(async () => {
|
|
2324
|
+
const entity = await this.resolveChat(chatId);
|
|
2325
|
+
if (!(entity instanceof Api.Channel)) {
|
|
2326
|
+
throw new Error("Megagroup stats are only available for supergroups");
|
|
2327
|
+
}
|
|
2328
|
+
if (!entity.megagroup) {
|
|
2329
|
+
throw new Error("Megagroup stats are only available for supergroups, not broadcast channels (use telegram-get-broadcast-stats)");
|
|
2330
|
+
}
|
|
2331
|
+
let result;
|
|
2332
|
+
try {
|
|
2333
|
+
const response = await this.client?.invoke(new Api.stats.GetMegagroupStats({ channel: entity, dark: options?.dark }));
|
|
2334
|
+
if (!response) {
|
|
2335
|
+
throw new Error("supergroup has no stats yet (needs more activity/members)");
|
|
2336
|
+
}
|
|
2337
|
+
result = response;
|
|
2338
|
+
}
|
|
2339
|
+
catch (e) {
|
|
2340
|
+
const msg = e.message ?? String(e);
|
|
2341
|
+
if (/CHAT_ADMIN_REQUIRED|ADMIN_RANK_INVALID/i.test(msg)) {
|
|
2342
|
+
throw new Error("Access denied: supergroup stats require admin rights");
|
|
2343
|
+
}
|
|
2344
|
+
if (/STATS_UNAVAILABLE|PARTICIPANTS_TOO_FEW|MEGAGROUP_REQUIRED/i.test(msg)) {
|
|
2345
|
+
throw new Error("supergroup has no stats yet (needs more activity/members)");
|
|
2346
|
+
}
|
|
2347
|
+
throw e;
|
|
2348
|
+
}
|
|
2349
|
+
return summarizeMegagroupStats(result, options?.includeGraphs === true);
|
|
2350
|
+
}, `getMegagroupStats ${chatId}`, { throwOnFloodWait: true });
|
|
2351
|
+
}
|
|
2352
|
+
async getUpdatesState() {
|
|
2353
|
+
if (!this.client || !this.connected)
|
|
2354
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2355
|
+
return this.rateLimiter.execute(async () => {
|
|
2356
|
+
const state = await this.client?.invoke(new Api.updates.GetState());
|
|
2357
|
+
if (!state)
|
|
2358
|
+
throw new Error("updates.GetState returned no state");
|
|
2359
|
+
return {
|
|
2360
|
+
pts: state.pts,
|
|
2361
|
+
qts: state.qts,
|
|
2362
|
+
date: state.date,
|
|
2363
|
+
seq: state.seq,
|
|
2364
|
+
unreadCount: state.unreadCount,
|
|
2365
|
+
};
|
|
2366
|
+
}, "getUpdatesState");
|
|
2367
|
+
}
|
|
2368
|
+
async getUpdates(cursor) {
|
|
2369
|
+
if (!this.client || !this.connected)
|
|
2370
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2371
|
+
const ptsLimit = Math.min(cursor.ptsLimit ?? 100, 1000);
|
|
2372
|
+
const ptsTotalLimit = Math.min(cursor.ptsTotalLimit ?? 1000, 1000);
|
|
2373
|
+
return this.rateLimiter.execute(async () => {
|
|
2374
|
+
const diff = await this.client?.invoke(new Api.updates.GetDifference({
|
|
2375
|
+
pts: cursor.pts,
|
|
2376
|
+
date: cursor.date,
|
|
2377
|
+
qts: cursor.qts,
|
|
2378
|
+
ptsLimit,
|
|
2379
|
+
ptsTotalLimit,
|
|
2380
|
+
}));
|
|
2381
|
+
if (!diff)
|
|
2382
|
+
throw new Error("updates.GetDifference returned nothing");
|
|
2383
|
+
return summarizeUpdatesDifference(diff, cursor);
|
|
2384
|
+
}, "getUpdates");
|
|
2385
|
+
}
|
|
2386
|
+
async getChannelUpdates(chatId, cursor) {
|
|
2387
|
+
if (!this.client || !this.connected)
|
|
2388
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2389
|
+
const limit = Math.min(cursor.limit ?? 100, 1_000);
|
|
2390
|
+
return this.rateLimiter.execute(async () => {
|
|
2391
|
+
const entity = await this.resolveChat(chatId);
|
|
2392
|
+
if (!(entity instanceof Api.Channel)) {
|
|
2393
|
+
throw new Error("Channel updates are only available for channels/supergroups");
|
|
2394
|
+
}
|
|
2395
|
+
const diff = await this.client?.invoke(new Api.updates.GetChannelDifference({
|
|
2396
|
+
channel: entity,
|
|
2397
|
+
filter: new Api.ChannelMessagesFilterEmpty(),
|
|
2398
|
+
pts: cursor.pts,
|
|
2399
|
+
limit,
|
|
2400
|
+
force: cursor.force,
|
|
2401
|
+
}));
|
|
2402
|
+
if (!diff)
|
|
2403
|
+
throw new Error("updates.GetChannelDifference returned nothing");
|
|
2404
|
+
return summarizeChannelDifference(diff, entity.id.toString(), cursor.pts);
|
|
2405
|
+
}, `getChannelUpdates ${chatId}`);
|
|
2406
|
+
}
|
|
2154
2407
|
async createForumTopic(chatId, title, iconColor, iconEmojiId) {
|
|
2155
2408
|
if (!this.client || !this.connected)
|
|
2156
2409
|
throw new Error(NOT_CONNECTED_ERROR);
|
|
@@ -2690,4 +2943,216 @@ export class TelegramService {
|
|
|
2690
2943
|
emoji: emojiMap.get(doc.id.toString()) || "",
|
|
2691
2944
|
}));
|
|
2692
2945
|
}
|
|
2946
|
+
async getAllStories(options) {
|
|
2947
|
+
if (!this.client || !this.connected)
|
|
2948
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2949
|
+
return this.rateLimiter.execute(async () => {
|
|
2950
|
+
const response = await this.client?.invoke(new Api.stories.GetAllStories({
|
|
2951
|
+
next: options?.next,
|
|
2952
|
+
hidden: options?.hidden,
|
|
2953
|
+
state: options?.state,
|
|
2954
|
+
}));
|
|
2955
|
+
if (!response)
|
|
2956
|
+
throw new Error("stories.GetAllStories returned nothing");
|
|
2957
|
+
return summarizeAllStories(response);
|
|
2958
|
+
}, "getAllStories");
|
|
2959
|
+
}
|
|
2960
|
+
async getPeerStories(chatId) {
|
|
2961
|
+
if (!this.client || !this.connected)
|
|
2962
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2963
|
+
const peer = await this.resolvePeer(chatId);
|
|
2964
|
+
return this.rateLimiter.execute(async () => {
|
|
2965
|
+
const response = await this.client?.invoke(new Api.stories.GetPeerStories({ peer }));
|
|
2966
|
+
if (!response)
|
|
2967
|
+
throw new Error("stories.GetPeerStories returned nothing");
|
|
2968
|
+
return summarizePeerStories(response.stories);
|
|
2969
|
+
}, `getPeerStories ${chatId}`);
|
|
2970
|
+
}
|
|
2971
|
+
async getStoriesById(chatId, ids) {
|
|
2972
|
+
if (!this.client || !this.connected)
|
|
2973
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2974
|
+
const peer = await this.resolvePeer(chatId);
|
|
2975
|
+
return this.rateLimiter.execute(async () => {
|
|
2976
|
+
const response = await this.client?.invoke(new Api.stories.GetStoriesByID({ peer, id: ids }));
|
|
2977
|
+
if (!response)
|
|
2978
|
+
throw new Error("stories.GetStoriesByID returned nothing");
|
|
2979
|
+
return summarizeStoriesById(response);
|
|
2980
|
+
}, `getStoriesById ${chatId}`);
|
|
2981
|
+
}
|
|
2982
|
+
async getStoryViewsList(chatId, options) {
|
|
2983
|
+
if (!this.client || !this.connected)
|
|
2984
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
2985
|
+
const peer = await this.resolvePeer(chatId);
|
|
2986
|
+
return this.rateLimiter.execute(async () => {
|
|
2987
|
+
const response = await this.client?.invoke(new Api.stories.GetStoryViewsList({
|
|
2988
|
+
peer,
|
|
2989
|
+
id: options.id,
|
|
2990
|
+
q: options.q,
|
|
2991
|
+
justContacts: options.justContacts,
|
|
2992
|
+
reactionsFirst: options.reactionsFirst,
|
|
2993
|
+
forwardsFirst: options.forwardsFirst,
|
|
2994
|
+
offset: options.offset ?? "",
|
|
2995
|
+
limit: options.limit ?? 50,
|
|
2996
|
+
}));
|
|
2997
|
+
if (!response)
|
|
2998
|
+
throw new Error("stories.GetStoryViewsList returned nothing");
|
|
2999
|
+
return summarizeStoryViewsList(response);
|
|
3000
|
+
}, `getStoryViewsList ${chatId}/${options.id}`);
|
|
3001
|
+
}
|
|
3002
|
+
async getMyBoosts() {
|
|
3003
|
+
if (!this.client || !this.connected)
|
|
3004
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
3005
|
+
return this.rateLimiter.execute(async () => {
|
|
3006
|
+
const response = await this.client?.invoke(new Api.premium.GetMyBoosts());
|
|
3007
|
+
if (!response)
|
|
3008
|
+
throw new Error("premium.GetMyBoosts returned nothing");
|
|
3009
|
+
return summarizeMyBoosts(response);
|
|
3010
|
+
}, "getMyBoosts");
|
|
3011
|
+
}
|
|
3012
|
+
async getBoostsStatus(chatId) {
|
|
3013
|
+
if (!this.client || !this.connected)
|
|
3014
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
3015
|
+
const peer = await this.resolvePeer(chatId);
|
|
3016
|
+
return this.rateLimiter.execute(async () => {
|
|
3017
|
+
const response = await this.client?.invoke(new Api.premium.GetBoostsStatus({ peer }));
|
|
3018
|
+
if (!response)
|
|
3019
|
+
throw new Error("premium.GetBoostsStatus returned nothing");
|
|
3020
|
+
return summarizeBoostsStatus(response);
|
|
3021
|
+
}, `getBoostsStatus ${chatId}`);
|
|
3022
|
+
}
|
|
3023
|
+
async getBoostsList(chatId, options = {}) {
|
|
3024
|
+
if (!this.client || !this.connected)
|
|
3025
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
3026
|
+
const peer = await this.resolvePeer(chatId);
|
|
3027
|
+
return this.rateLimiter.execute(async () => {
|
|
3028
|
+
const response = await this.client?.invoke(new Api.premium.GetBoostsList({
|
|
3029
|
+
peer,
|
|
3030
|
+
gifts: options.gifts,
|
|
3031
|
+
offset: options.offset ?? "",
|
|
3032
|
+
limit: options.limit ?? 50,
|
|
3033
|
+
}));
|
|
3034
|
+
if (!response)
|
|
3035
|
+
throw new Error("premium.GetBoostsList returned nothing");
|
|
3036
|
+
return summarizeBoostsList(response);
|
|
3037
|
+
}, `getBoostsList ${chatId}`);
|
|
3038
|
+
}
|
|
3039
|
+
async getBusinessChatLinks() {
|
|
3040
|
+
if (!this.client || !this.connected)
|
|
3041
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
3042
|
+
return this.rateLimiter.execute(async () => {
|
|
3043
|
+
const response = await this.client?.invoke(new Api.account.GetBusinessChatLinks());
|
|
3044
|
+
if (!response)
|
|
3045
|
+
throw new Error("account.GetBusinessChatLinks returned nothing");
|
|
3046
|
+
return summarizeBusinessChatLinks(response);
|
|
3047
|
+
}, "getBusinessChatLinks");
|
|
3048
|
+
}
|
|
3049
|
+
async getGroupCall(chatId, options = {}) {
|
|
3050
|
+
if (!this.client || !this.connected)
|
|
3051
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
3052
|
+
return this.rateLimiter.execute(async () => {
|
|
3053
|
+
const call = await this.resolveInputGroupCall(chatId);
|
|
3054
|
+
const response = await this.client?.invoke(new Api.phone.GetGroupCall({ call, limit: options.limit ?? 0 }));
|
|
3055
|
+
if (!response)
|
|
3056
|
+
throw new Error("phone.GetGroupCall returned nothing");
|
|
3057
|
+
return summarizeGroupCall(response);
|
|
3058
|
+
}, `getGroupCall ${chatId}`);
|
|
3059
|
+
}
|
|
3060
|
+
async getGroupCallParticipants(chatId, options = {}) {
|
|
3061
|
+
if (!this.client || !this.connected)
|
|
3062
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
3063
|
+
return this.rateLimiter.execute(async () => {
|
|
3064
|
+
const call = await this.resolveInputGroupCall(chatId);
|
|
3065
|
+
const ids = [];
|
|
3066
|
+
for (const id of options.ids ?? []) {
|
|
3067
|
+
ids.push(await this.resolvePeer(id));
|
|
3068
|
+
}
|
|
3069
|
+
const response = await this.client?.invoke(new Api.phone.GetGroupParticipants({
|
|
3070
|
+
call,
|
|
3071
|
+
ids,
|
|
3072
|
+
sources: options.sources ?? [],
|
|
3073
|
+
offset: options.offset ?? "",
|
|
3074
|
+
limit: options.limit ?? 100,
|
|
3075
|
+
}));
|
|
3076
|
+
if (!response)
|
|
3077
|
+
throw new Error("phone.GetGroupParticipants returned nothing");
|
|
3078
|
+
return summarizeGroupCallParticipants(response);
|
|
3079
|
+
}, `getGroupCallParticipants ${chatId}`);
|
|
3080
|
+
}
|
|
3081
|
+
async getStarsStatus(chatId) {
|
|
3082
|
+
if (!this.client || !this.connected)
|
|
3083
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
3084
|
+
const peer = await this.resolvePeer(chatId);
|
|
3085
|
+
return this.rateLimiter.execute(async () => {
|
|
3086
|
+
const response = await this.client?.invoke(new Api.payments.GetStarsStatus({ peer }));
|
|
3087
|
+
if (!response)
|
|
3088
|
+
throw new Error("payments.GetStarsStatus returned nothing");
|
|
3089
|
+
return summarizeStarsStatus(response);
|
|
3090
|
+
}, `getStarsStatus ${chatId}`);
|
|
3091
|
+
}
|
|
3092
|
+
async getStarsTransactions(chatId, options = {}) {
|
|
3093
|
+
if (!this.client || !this.connected)
|
|
3094
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
3095
|
+
const peer = await this.resolvePeer(chatId);
|
|
3096
|
+
return this.rateLimiter.execute(async () => {
|
|
3097
|
+
const response = await this.client?.invoke(new Api.payments.GetStarsTransactions({
|
|
3098
|
+
peer,
|
|
3099
|
+
inbound: options.inbound,
|
|
3100
|
+
outbound: options.outbound,
|
|
3101
|
+
ascending: options.ascending,
|
|
3102
|
+
subscriptionId: options.subscriptionId,
|
|
3103
|
+
offset: options.offset ?? "",
|
|
3104
|
+
limit: options.limit ?? 50,
|
|
3105
|
+
}));
|
|
3106
|
+
if (!response)
|
|
3107
|
+
throw new Error("payments.GetStarsTransactions returned nothing");
|
|
3108
|
+
return summarizeStarsStatus(response);
|
|
3109
|
+
}, `getStarsTransactions ${chatId}`);
|
|
3110
|
+
}
|
|
3111
|
+
async getQuickReplies(hash) {
|
|
3112
|
+
if (!this.client || !this.connected)
|
|
3113
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
3114
|
+
return this.rateLimiter.execute(async () => {
|
|
3115
|
+
const response = await this.client?.invoke(new Api.messages.GetQuickReplies({ hash: hash ? bigInt(hash) : bigInt(0) }));
|
|
3116
|
+
if (!response)
|
|
3117
|
+
throw new Error("messages.GetQuickReplies returned nothing");
|
|
3118
|
+
return summarizeQuickReplies(response);
|
|
3119
|
+
}, "getQuickReplies");
|
|
3120
|
+
}
|
|
3121
|
+
async getQuickReplyMessages(shortcutId, options = {}) {
|
|
3122
|
+
if (!this.client || !this.connected)
|
|
3123
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
3124
|
+
return this.rateLimiter.execute(async () => {
|
|
3125
|
+
const response = await this.client?.invoke(new Api.messages.GetQuickReplyMessages({
|
|
3126
|
+
shortcutId,
|
|
3127
|
+
id: options.ids,
|
|
3128
|
+
hash: options.hash ? bigInt(options.hash) : bigInt(0),
|
|
3129
|
+
}));
|
|
3130
|
+
if (!response)
|
|
3131
|
+
throw new Error("messages.GetQuickReplyMessages returned nothing");
|
|
3132
|
+
return summarizeQuickReplyMessages(response);
|
|
3133
|
+
}, `getQuickReplyMessages ${shortcutId}`);
|
|
3134
|
+
}
|
|
3135
|
+
async resolveInputGroupCall(chatId) {
|
|
3136
|
+
const entity = await this.resolveChat(chatId);
|
|
3137
|
+
let call;
|
|
3138
|
+
if (entity instanceof Api.Channel) {
|
|
3139
|
+
const full = await this.client?.invoke(new Api.channels.GetFullChannel({ channel: entity }));
|
|
3140
|
+
if (full?.fullChat instanceof Api.ChannelFull) {
|
|
3141
|
+
call = full.fullChat.call;
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
else if (entity instanceof Api.Chat) {
|
|
3145
|
+
const full = await this.client?.invoke(new Api.messages.GetFullChat({ chatId: entity.id }));
|
|
3146
|
+
if (full?.fullChat instanceof Api.ChatFull) {
|
|
3147
|
+
call = full.fullChat.call;
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
else {
|
|
3151
|
+
throw new Error("Group calls are only available for groups/supergroups/channels");
|
|
3152
|
+
}
|
|
3153
|
+
if (!call) {
|
|
3154
|
+
throw new Error(`No active group call in chat ${chatId}`);
|
|
3155
|
+
}
|
|
3156
|
+
return call;
|
|
3157
|
+
}
|
|
2693
3158
|
}
|