@overpod/mcp-telegram 1.33.0 → 1.34.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.34.0] — 2026-04-24
9
+
10
+ ### Added
11
+
12
+ **Star Gifts (7 new tools, opt-in `MCP_TELEGRAM_ENABLE_STARS=1`):**
13
+ - **telegram-get-available-star-gifts** — List all available Star Gift catalog items: gift ID, cost in Stars, conversion value, limited-edition availability, and upgrade cost
14
+ - **telegram-get-saved-star-gifts** — List Star Gifts received by a user/chat. Supports pagination (`offset`/`nextOffset`), filter flags (`excludeUnsaved`, `excludeSaved`, `excludeUnlimited`, `excludeLimited`, `excludeUnique`), and `sortByValue`. Returns kind (regular/unique), from-peer, date, and upgrade eligibility
15
+ - **telegram-save-star-gift** — Show or hide a received gift on your profile. For personal gifts pass `msgId`; for chat/channel gifts pass `chatId` + `savedId`. Set `unsave=true` to hide, omit to show
16
+ - **telegram-convert-star-gift** — Convert a received gift into Stars (non-reversible, removes gift from profile). Same addressing as save: `msgId` for personal, `chatId`+`savedId` for chat gifts
17
+ - **telegram-get-stars-topup-options** — List available Stars top-up tiers with star count, currency, price amount (smallest currency units), and extended/standard tier flag
18
+ - **telegram-get-stars-subscriptions** — List active Stars subscriptions for a peer (`me` for self). Returns subscription ID, peer, until date, billing period (seconds), price in Stars, canceled status, and title. Pagination via `offset`
19
+ - **telegram-change-stars-subscription** — Cancel or restore a Stars subscription by ID. `canceled=true` to cancel before next renewal, `canceled=false` to restore
20
+
8
21
  ## [1.33.0] — 2026-04-24
9
22
 
10
23
  ### Added
@@ -901,6 +901,75 @@ export declare class TelegramService {
901
901
  offset?: string;
902
902
  limit?: number;
903
903
  }): Promise<StarsStatusSummary>;
904
+ getAvailableStarGifts(): Promise<Array<{
905
+ id: string;
906
+ stars: string;
907
+ convertStars: string;
908
+ limited: boolean;
909
+ availabilityRemains?: number;
910
+ availabilityTotal?: number;
911
+ upgradeStars?: string;
912
+ }>>;
913
+ getSavedStarGifts(chatId: string, opts?: {
914
+ limit?: number;
915
+ offset?: string;
916
+ excludeUnsaved?: boolean;
917
+ excludeSaved?: boolean;
918
+ excludeUnlimited?: boolean;
919
+ excludeLimited?: boolean;
920
+ excludeUnique?: boolean;
921
+ sortByValue?: boolean;
922
+ }): Promise<{
923
+ count: number;
924
+ nextOffset?: string;
925
+ gifts: Array<{
926
+ giftId: string;
927
+ giftKind: "regular" | "unique";
928
+ giftTitle?: string;
929
+ stars?: string;
930
+ convertStars?: string;
931
+ msgId?: number;
932
+ savedId?: string;
933
+ fromPeerId?: string;
934
+ date: number;
935
+ unsaved: boolean;
936
+ canUpgrade: boolean;
937
+ upgradeStars?: string;
938
+ }>;
939
+ }>;
940
+ saveStarGift(opts: {
941
+ msgId?: number;
942
+ chatId?: string;
943
+ savedId?: string;
944
+ unsave?: boolean;
945
+ }): Promise<void>;
946
+ convertStarGift(opts: {
947
+ msgId?: number;
948
+ chatId?: string;
949
+ savedId?: string;
950
+ }): Promise<void>;
951
+ getStarsTopupOptions(): Promise<Array<{
952
+ stars: string;
953
+ currency: string;
954
+ amount: string;
955
+ extended: boolean;
956
+ }>>;
957
+ getStarsSubscriptions(chatId: string, opts?: {
958
+ offset?: string;
959
+ missingBalance?: boolean;
960
+ }): Promise<{
961
+ subscriptions: Array<{
962
+ id: string;
963
+ peerId: string;
964
+ untilDate: number;
965
+ periodSeconds: number;
966
+ priceStars: string;
967
+ canceled: boolean;
968
+ title?: string;
969
+ }>;
970
+ nextOffset?: string;
971
+ }>;
972
+ changeStarsSubscription(chatId: string, subscriptionId: string, canceled: boolean): Promise<void>;
904
973
  getQuickReplies(hash?: string): Promise<QuickRepliesSummary>;
905
974
  getQuickReplyMessages(shortcutId: number, options?: {
906
975
  ids?: number[];
@@ -4196,6 +4196,152 @@ export class TelegramService {
4196
4196
  return summarizeStarsStatus(response);
4197
4197
  }, `getStarsTransactions ${chatId}`);
4198
4198
  }
4199
+ // ─── Star Gifts (v1.34.0) ──────────────────────────────────────────────────
4200
+ async getAvailableStarGifts() {
4201
+ if (!this.client || !this.connected)
4202
+ throw new Error(NOT_CONNECTED_ERROR);
4203
+ const result = await this.client.invoke(new Api.payments.GetStarGifts({ hash: 0 }));
4204
+ if (result.className === "payments.StarGiftsNotModified")
4205
+ return [];
4206
+ const gifts = result.className === "payments.StarGifts" ? result.gifts : [];
4207
+ return gifts
4208
+ .filter((g) => g instanceof Api.StarGift)
4209
+ .map((g) => ({
4210
+ id: g.id.toString(),
4211
+ stars: g.stars.toString(),
4212
+ convertStars: g.convertStars.toString(),
4213
+ limited: g.limited ?? false,
4214
+ availabilityRemains: g.availabilityRemains,
4215
+ availabilityTotal: g.availabilityTotal,
4216
+ upgradeStars: g.upgradeStars?.toString(),
4217
+ }));
4218
+ }
4219
+ async getSavedStarGifts(chatId, opts = {}) {
4220
+ if (!this.client || !this.connected)
4221
+ throw new Error(NOT_CONNECTED_ERROR);
4222
+ const client = this.client;
4223
+ const peer = await this.resolvePeer(chatId);
4224
+ const resolved = await client.getInputEntity(peer);
4225
+ const result = await client.invoke(new Api.payments.GetSavedStarGifts({
4226
+ peer: resolved,
4227
+ offset: opts.offset ?? "",
4228
+ limit: opts.limit ?? 20,
4229
+ excludeUnsaved: opts.excludeUnsaved,
4230
+ excludeSaved: opts.excludeSaved,
4231
+ excludeUnlimited: opts.excludeUnlimited,
4232
+ excludeLimited: opts.excludeLimited,
4233
+ excludeUnique: opts.excludeUnique,
4234
+ sortByValue: opts.sortByValue,
4235
+ }));
4236
+ const r = result;
4237
+ return {
4238
+ count: r.count,
4239
+ nextOffset: r.nextOffset,
4240
+ gifts: r.gifts.map((sg) => {
4241
+ const gift = sg.gift;
4242
+ if (gift instanceof Api.StarGift) {
4243
+ return {
4244
+ giftId: gift.id.toString(),
4245
+ giftKind: "regular",
4246
+ stars: gift.stars.toString(),
4247
+ convertStars: gift.convertStars.toString(),
4248
+ msgId: sg.msgId ?? undefined,
4249
+ savedId: sg.savedId?.toString(),
4250
+ fromPeerId: sg.fromId ? summarizePeer(sg.fromId).id : undefined,
4251
+ date: sg.date,
4252
+ unsaved: sg.unsaved ?? false,
4253
+ canUpgrade: sg.canUpgrade ?? false,
4254
+ upgradeStars: sg.upgradeStars?.toString(),
4255
+ };
4256
+ }
4257
+ const u = gift;
4258
+ return {
4259
+ giftId: u.id.toString(),
4260
+ giftKind: "unique",
4261
+ giftTitle: u.title,
4262
+ msgId: sg.msgId ?? undefined,
4263
+ savedId: sg.savedId?.toString(),
4264
+ fromPeerId: sg.fromId ? summarizePeer(sg.fromId).id : undefined,
4265
+ date: sg.date,
4266
+ unsaved: sg.unsaved ?? false,
4267
+ canUpgrade: false,
4268
+ };
4269
+ }),
4270
+ };
4271
+ }
4272
+ async saveStarGift(opts) {
4273
+ if (!this.client || !this.connected)
4274
+ throw new Error(NOT_CONNECTED_ERROR);
4275
+ const client = this.client;
4276
+ let stargift;
4277
+ if (opts.msgId !== undefined) {
4278
+ stargift = new Api.InputSavedStarGiftUser({ msgId: opts.msgId });
4279
+ }
4280
+ else if (opts.chatId && opts.savedId) {
4281
+ const peer = await this.resolvePeer(opts.chatId);
4282
+ const inputPeer = await client.getInputEntity(peer);
4283
+ stargift = new Api.InputSavedStarGiftChat({ peer: inputPeer, savedId: bigInt(opts.savedId) });
4284
+ }
4285
+ else {
4286
+ throw new Error("Provide msgId (user gift) or both chatId and savedId (chat gift)");
4287
+ }
4288
+ await client.invoke(new Api.payments.SaveStarGift({ stargift, unsave: opts.unsave }));
4289
+ }
4290
+ async convertStarGift(opts) {
4291
+ if (!this.client || !this.connected)
4292
+ throw new Error(NOT_CONNECTED_ERROR);
4293
+ const client = this.client;
4294
+ let stargift;
4295
+ if (opts.msgId !== undefined) {
4296
+ stargift = new Api.InputSavedStarGiftUser({ msgId: opts.msgId });
4297
+ }
4298
+ else if (opts.chatId && opts.savedId) {
4299
+ const peer = await this.resolvePeer(opts.chatId);
4300
+ const inputPeer = await client.getInputEntity(peer);
4301
+ stargift = new Api.InputSavedStarGiftChat({ peer: inputPeer, savedId: bigInt(opts.savedId) });
4302
+ }
4303
+ else {
4304
+ throw new Error("Provide msgId (user gift) or both chatId and savedId (chat gift)");
4305
+ }
4306
+ await client.invoke(new Api.payments.ConvertStarGift({ stargift }));
4307
+ }
4308
+ async getStarsTopupOptions() {
4309
+ if (!this.client || !this.connected)
4310
+ throw new Error(NOT_CONNECTED_ERROR);
4311
+ const result = await this.client.invoke(new Api.payments.GetStarsTopupOptions());
4312
+ return result.map((o) => ({
4313
+ stars: o.stars.toString(),
4314
+ currency: o.currency,
4315
+ amount: o.amount.toString(),
4316
+ extended: o.extended ?? false,
4317
+ }));
4318
+ }
4319
+ async getStarsSubscriptions(chatId, opts = {}) {
4320
+ if (!this.client || !this.connected)
4321
+ throw new Error(NOT_CONNECTED_ERROR);
4322
+ const peer = await this.resolvePeer(chatId);
4323
+ const result = await this.client.invoke(new Api.payments.GetStarsSubscriptions({ peer, offset: opts.offset ?? "", missingBalance: opts.missingBalance }));
4324
+ const r = result;
4325
+ const subs = r.subscriptions ?? [];
4326
+ return {
4327
+ subscriptions: subs.map((s) => ({
4328
+ id: s.id,
4329
+ peerId: summarizePeer(s.peer).id,
4330
+ untilDate: s.untilDate,
4331
+ periodSeconds: s.pricing.period,
4332
+ priceStars: s.pricing.amount.toString(),
4333
+ canceled: s.canceled ?? false,
4334
+ title: s.title,
4335
+ })),
4336
+ nextOffset: r.subscriptionsNextOffset,
4337
+ };
4338
+ }
4339
+ async changeStarsSubscription(chatId, subscriptionId, canceled) {
4340
+ if (!this.client || !this.connected)
4341
+ throw new Error(NOT_CONNECTED_ERROR);
4342
+ const peer = await this.resolvePeer(chatId);
4343
+ await this.client.invoke(new Api.payments.ChangeStarsSubscription({ peer, subscriptionId, canceled }));
4344
+ }
4199
4345
  // ─── Quick replies ─────────────────────────────────────────────────────────
4200
4346
  async getQuickReplies(hash) {
4201
4347
  if (!this.client || !this.connected)
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { fail, ok, READ_ONLY, requireConnection } from "./shared.js";
2
+ import { fail, ok, READ_ONLY, requireConnection, WRITE } from "./shared.js";
3
3
  export function isStarsEnabled() {
4
4
  return process.env.MCP_TELEGRAM_ENABLE_STARS === "1";
5
5
  }
@@ -68,4 +68,181 @@ export function registerStarsTools(server, telegram) {
68
68
  return fail(e);
69
69
  }
70
70
  });
71
+ server.registerTool("telegram-get-available-star-gifts", {
72
+ description: "List all available Telegram Star Gifts that can be sent to other users. Returns gift ID, cost in Stars, conversion value, availability (for limited gifts), and upgrade cost. Opt-in: register only when MCP_TELEGRAM_ENABLE_STARS=1. Read-only.",
73
+ inputSchema: {},
74
+ annotations: READ_ONLY,
75
+ }, async () => {
76
+ const err = await requireConnection(telegram);
77
+ if (err)
78
+ return fail(new Error(err));
79
+ try {
80
+ const gifts = await telegram.getAvailableStarGifts();
81
+ return ok(JSON.stringify(gifts));
82
+ }
83
+ catch (e) {
84
+ return fail(e);
85
+ }
86
+ });
87
+ server.registerTool("telegram-get-saved-star-gifts", {
88
+ description: "List Star Gifts received by a user or chat. Pass 'me' for your own gifts. Supports pagination via offset/nextOffset and filtering flags. Returns gift ID, kind (regular/unique), stars cost, from-peer, date, and upgrade eligibility. Opt-in: register only when MCP_TELEGRAM_ENABLE_STARS=1. Read-only.",
89
+ inputSchema: {
90
+ chatId: z.string().describe("User or chat to fetch received gifts for — 'me' for self"),
91
+ limit: z.number().int().min(1).max(100).default(20).describe("Max gifts per page"),
92
+ offset: z.string().optional().describe("Pagination cursor from a prior response's nextOffset"),
93
+ excludeUnsaved: z.boolean().optional().describe("Skip gifts the recipient chose to hide"),
94
+ excludeSaved: z.boolean().optional().describe("Skip gifts the recipient chose to show"),
95
+ excludeUnlimited: z.boolean().optional().describe("Skip unlimited-edition gifts"),
96
+ excludeLimited: z.boolean().optional().describe("Skip limited-edition gifts"),
97
+ excludeUnique: z.boolean().optional().describe("Skip unique gifts"),
98
+ sortByValue: z.boolean().optional().describe("Sort by Star value descending instead of date"),
99
+ },
100
+ annotations: READ_ONLY,
101
+ }, async ({ chatId, limit, offset, excludeUnsaved, excludeSaved, excludeUnlimited, excludeLimited, excludeUnique, sortByValue, }) => {
102
+ const err = await requireConnection(telegram);
103
+ if (err)
104
+ return fail(new Error(err));
105
+ try {
106
+ const result = await telegram.getSavedStarGifts(chatId, {
107
+ limit,
108
+ offset,
109
+ excludeUnsaved,
110
+ excludeSaved,
111
+ excludeUnlimited,
112
+ excludeLimited,
113
+ excludeUnique,
114
+ sortByValue,
115
+ });
116
+ return ok(JSON.stringify(result));
117
+ }
118
+ catch (e) {
119
+ return fail(e);
120
+ }
121
+ });
122
+ server.registerTool("telegram-save-star-gift", {
123
+ description: "Show or hide a received Star Gift on your profile. Pass msgId for a gift received as a personal message (DM), or chatId + savedId for a gift in a chat/channel. Set unsave=true to hide the gift (remove from profile display). Opt-in: register only when MCP_TELEGRAM_ENABLE_STARS=1.",
124
+ inputSchema: {
125
+ msgId: z
126
+ .number()
127
+ .int()
128
+ .positive()
129
+ .optional()
130
+ .describe("Message ID of the gift (from your DMs) — use this for personal gifts"),
131
+ chatId: z
132
+ .string()
133
+ .optional()
134
+ .describe("Chat/channel ID where the gift was received (required with savedId for chat gifts)"),
135
+ savedId: z
136
+ .string()
137
+ .regex(/^\d+$/, "must be a numeric saved gift ID")
138
+ .optional()
139
+ .describe("Saved gift ID (from get-saved-star-gifts) — required with chatId for chat gifts"),
140
+ unsave: z.boolean().optional().describe("true = hide the gift from profile; false/omit = show it"),
141
+ },
142
+ annotations: WRITE,
143
+ }, async ({ msgId, chatId, savedId, unsave }) => {
144
+ const err = await requireConnection(telegram);
145
+ if (err)
146
+ return fail(new Error(err));
147
+ if (msgId === undefined && !(chatId && savedId)) {
148
+ return fail(new Error("Provide msgId, or both chatId and savedId"));
149
+ }
150
+ try {
151
+ await telegram.saveStarGift({ msgId, chatId, savedId, unsave });
152
+ return ok(unsave ? "Gift hidden from profile" : "Gift shown on profile");
153
+ }
154
+ catch (e) {
155
+ return fail(e);
156
+ }
157
+ });
158
+ server.registerTool("telegram-convert-star-gift", {
159
+ description: "Convert a received Star Gift into Stars (non-reversible). The gift is removed from your profile and its conversion value is added to your Stars balance. Pass msgId for personal gifts or chatId + savedId for chat gifts. Opt-in: register only when MCP_TELEGRAM_ENABLE_STARS=1.",
160
+ inputSchema: {
161
+ msgId: z
162
+ .number()
163
+ .int()
164
+ .positive()
165
+ .optional()
166
+ .describe("Message ID of the gift (from your DMs) — for personal gifts"),
167
+ chatId: z.string().optional().describe("Chat/channel ID (for chat gifts, required with savedId)"),
168
+ savedId: z
169
+ .string()
170
+ .regex(/^\d+$/, "must be a numeric saved gift ID")
171
+ .optional()
172
+ .describe("Saved gift ID — for chat gifts, required with chatId"),
173
+ },
174
+ annotations: WRITE,
175
+ }, async ({ msgId, chatId, savedId }) => {
176
+ const err = await requireConnection(telegram);
177
+ if (err)
178
+ return fail(new Error(err));
179
+ if (msgId === undefined && !(chatId && savedId)) {
180
+ return fail(new Error("Provide msgId, or both chatId and savedId"));
181
+ }
182
+ try {
183
+ await telegram.convertStarGift({ msgId, chatId, savedId });
184
+ return ok("Gift converted to Stars");
185
+ }
186
+ catch (e) {
187
+ return fail(e);
188
+ }
189
+ });
190
+ server.registerTool("telegram-get-stars-topup-options", {
191
+ description: "List available Telegram Stars top-up tiers (from payments.GetStarsTopupOptions). Returns each option with star count, currency, price amount (in smallest currency units), and whether it is an extended/premium tier. Opt-in: register only when MCP_TELEGRAM_ENABLE_STARS=1. Read-only.",
192
+ inputSchema: {},
193
+ annotations: READ_ONLY,
194
+ }, async () => {
195
+ const err = await requireConnection(telegram);
196
+ if (err)
197
+ return fail(new Error(err));
198
+ try {
199
+ const options = await telegram.getStarsTopupOptions();
200
+ return ok(JSON.stringify(options));
201
+ }
202
+ catch (e) {
203
+ return fail(e);
204
+ }
205
+ });
206
+ server.registerTool("telegram-get-stars-subscriptions", {
207
+ description: "List active Telegram Stars subscriptions for a peer (payments.GetStarsSubscriptions). Pass 'me' for your own account or a bot/channel you own. Returns subscription ID, peer, renewal date, period, price in Stars, and canceled status. Paginate with offset. Opt-in: register only when MCP_TELEGRAM_ENABLE_STARS=1. Read-only.",
208
+ inputSchema: {
209
+ chatId: z.string().describe("Peer to query — 'me' for your own subscriptions, or a bot/channel you own"),
210
+ offset: z.string().optional().describe("Pagination cursor from a prior nextOffset"),
211
+ missingBalance: z.boolean().optional().describe("If true, return only subscriptions with missing balance"),
212
+ },
213
+ annotations: READ_ONLY,
214
+ }, async ({ chatId, offset, missingBalance }) => {
215
+ const err = await requireConnection(telegram);
216
+ if (err)
217
+ return fail(new Error(err));
218
+ try {
219
+ const result = await telegram.getStarsSubscriptions(chatId, { offset, missingBalance });
220
+ return ok(JSON.stringify(result));
221
+ }
222
+ catch (e) {
223
+ return fail(e);
224
+ }
225
+ });
226
+ server.registerTool("telegram-change-stars-subscription", {
227
+ description: "Cancel or restore a Telegram Stars subscription (payments.ChangeStarsSubscription). Pass canceled=true to cancel an active subscription before its next renewal, or false to restore a previously canceled one. Opt-in: register only when MCP_TELEGRAM_ENABLE_STARS=1.",
228
+ inputSchema: {
229
+ chatId: z
230
+ .string()
231
+ .describe("The peer the subscription belongs to — 'me' for your own subscriptions or a bot/channel you own"),
232
+ subscriptionId: z.string().min(1).describe("Subscription ID (from telegram-get-stars-subscriptions)"),
233
+ canceled: z.boolean().describe("true = cancel the subscription; false = restore a canceled subscription"),
234
+ },
235
+ annotations: WRITE,
236
+ }, async ({ chatId, subscriptionId, canceled }) => {
237
+ const err = await requireConnection(telegram);
238
+ if (err)
239
+ return fail(new Error(err));
240
+ try {
241
+ await telegram.changeStarsSubscription(chatId, subscriptionId, canceled);
242
+ return ok(canceled ? `Subscription ${subscriptionId} canceled` : `Subscription ${subscriptionId} restored`);
243
+ }
244
+ catch (e) {
245
+ return fail(e);
246
+ }
247
+ });
71
248
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overpod/mcp-telegram",
3
- "version": "1.33.0",
3
+ "version": "1.34.0",
4
4
  "description": "MCP server for Telegram userbot — messages, media, reactions, polls & more. Built on GramJS/MTProto.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",