common-tg-service 1.3.214 → 1.3.216

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.
Files changed (88) hide show
  1. package/dist/app.module.js +3 -0
  2. package/dist/app.module.js.map +1 -1
  3. package/dist/components/Telegram/Telegram.controller.d.ts +18 -5
  4. package/dist/components/Telegram/Telegram.controller.js +246 -56
  5. package/dist/components/Telegram/Telegram.controller.js.map +1 -1
  6. package/dist/components/Telegram/Telegram.service.d.ts +4 -0
  7. package/dist/components/Telegram/Telegram.service.js +39 -22
  8. package/dist/components/Telegram/Telegram.service.js.map +1 -1
  9. package/dist/components/Telegram/manager/TelegramManager.js +1 -1
  10. package/dist/components/Telegram/manager/TelegramManager.js.map +1 -1
  11. package/dist/components/Telegram/manager/helpers.d.ts +30 -1
  12. package/dist/components/Telegram/manager/helpers.js +101 -6
  13. package/dist/components/Telegram/manager/helpers.js.map +1 -1
  14. package/dist/components/Telegram/manager/media-operations.js +136 -62
  15. package/dist/components/Telegram/manager/media-operations.js.map +1 -1
  16. package/dist/components/Telegram/manager/types.d.ts +8 -0
  17. package/dist/components/Telegram/manager/types.js.map +1 -1
  18. package/dist/components/Telegram/utils/connection-manager.d.ts +1 -1
  19. package/dist/components/Telegram/utils/connection-manager.js +1 -1
  20. package/dist/components/Telegram/utils/connection-manager.js.map +1 -1
  21. package/dist/components/active-channels/active-channels.service.d.ts +8 -2
  22. package/dist/components/active-channels/active-channels.service.js +75 -18
  23. package/dist/components/active-channels/active-channels.service.js.map +1 -1
  24. package/dist/components/active-channels/dto/create-active-channel.dto.d.ts +4 -0
  25. package/dist/components/active-channels/dto/create-active-channel.dto.js +21 -1
  26. package/dist/components/active-channels/dto/create-active-channel.dto.js.map +1 -1
  27. package/dist/components/active-channels/schemas/active-channel.schema.d.ts +40 -0
  28. package/dist/components/active-channels/schemas/active-channel.schema.js +20 -0
  29. package/dist/components/active-channels/schemas/active-channel.schema.js.map +1 -1
  30. package/dist/components/buffer-clients/buffer-client.service.d.ts +1 -1
  31. package/dist/components/buffer-clients/buffer-client.service.js +1 -1
  32. package/dist/components/buffer-clients/buffer-client.service.js.map +1 -1
  33. package/dist/components/channels/channels.service.d.ts +7 -1
  34. package/dist/components/channels/channels.service.js +79 -15
  35. package/dist/components/channels/channels.service.js.map +1 -1
  36. package/dist/components/channels/dto/create-channel.dto.d.ts +2 -0
  37. package/dist/components/channels/dto/create-channel.dto.js +12 -0
  38. package/dist/components/channels/dto/create-channel.dto.js.map +1 -1
  39. package/dist/components/channels/schemas/channel.schema.d.ts +20 -0
  40. package/dist/components/channels/schemas/channel.schema.js +10 -0
  41. package/dist/components/channels/schemas/channel.schema.js.map +1 -1
  42. package/dist/components/clients/client.service.js +4 -0
  43. package/dist/components/clients/client.service.js.map +1 -1
  44. package/dist/components/collection-insights/collection-insights.controller.d.ts +75 -0
  45. package/dist/components/collection-insights/collection-insights.controller.js +106 -0
  46. package/dist/components/collection-insights/collection-insights.controller.js.map +1 -0
  47. package/dist/components/collection-insights/collection-insights.module.d.ts +2 -0
  48. package/dist/components/collection-insights/collection-insights.module.js +25 -0
  49. package/dist/components/collection-insights/collection-insights.module.js.map +1 -0
  50. package/dist/components/collection-insights/collection-insights.service.d.ts +79 -0
  51. package/dist/components/collection-insights/collection-insights.service.js +326 -0
  52. package/dist/components/collection-insights/collection-insights.service.js.map +1 -0
  53. package/dist/components/collection-insights/dto/collection-query.dto.d.ts +12 -0
  54. package/dist/components/collection-insights/dto/collection-query.dto.js +63 -0
  55. package/dist/components/collection-insights/dto/collection-query.dto.js.map +1 -0
  56. package/dist/components/collection-insights/dto/index.d.ts +1 -0
  57. package/dist/components/collection-insights/dto/index.js +18 -0
  58. package/dist/components/collection-insights/dto/index.js.map +1 -0
  59. package/dist/components/collection-insights/index.d.ts +4 -0
  60. package/dist/components/collection-insights/index.js +21 -0
  61. package/dist/components/collection-insights/index.js.map +1 -0
  62. package/dist/components/index.d.ts +1 -0
  63. package/dist/components/index.js +1 -0
  64. package/dist/components/index.js.map +1 -1
  65. package/dist/components/promote-stats/promote-stat.module.js +1 -1
  66. package/dist/components/promote-stats/promote-stat.module.js.map +1 -1
  67. package/dist/components/shared/base-client.service.d.ts +1 -0
  68. package/dist/components/shared/base-client.service.js +42 -3
  69. package/dist/components/shared/base-client.service.js.map +1 -1
  70. package/dist/components/user-data/schemas/user-data.schema.d.ts +30 -0
  71. package/dist/components/user-data/schemas/user-data.schema.js +12 -0
  72. package/dist/components/user-data/schemas/user-data.schema.js.map +1 -1
  73. package/dist/components/users/users.service.d.ts +1 -1
  74. package/dist/components/users/users.service.js +5 -4
  75. package/dist/components/users/users.service.js.map +1 -1
  76. package/dist/tsconfig.build.tsbuildinfo +1 -1
  77. package/dist/utils/parseError.d.ts +1 -1
  78. package/dist/utils/parseError.js +15 -8
  79. package/dist/utils/parseError.js.map +1 -1
  80. package/dist/utils/telegram-utils/channel-live-facts.d.ts +26 -0
  81. package/dist/utils/telegram-utils/channel-live-facts.js +74 -0
  82. package/dist/utils/telegram-utils/channel-live-facts.js.map +1 -0
  83. package/dist/utils/telegram-utils/channelinfo.js +6 -4
  84. package/dist/utils/telegram-utils/channelinfo.js.map +1 -1
  85. package/dist/utils/telegram-utils/common-chats.d.ts +12 -0
  86. package/dist/utils/telegram-utils/common-chats.js +46 -0
  87. package/dist/utils/telegram-utils/common-chats.js.map +1 -0
  88. package/package.json +1 -1
@@ -37,6 +37,7 @@ const guards_1 = require("./guards");
37
37
  const components_1 = require("./components");
38
38
  const interceptors_1 = require("./interceptors");
39
39
  const event_manager_module_1 = require("./components/event-manager/event-manager.module");
40
+ const collection_insights_module_1 = require("./components/collection-insights/collection-insights.module");
40
41
  let AppModule = class AppModule {
41
42
  configure(consumer) {
42
43
  consumer.apply(logger_middleware_1.LoggerMiddleware).forRoutes('*');
@@ -71,6 +72,7 @@ exports.AppModule = AppModule = __decorate([
71
72
  timestamp_module_1.TimestampModule,
72
73
  dynamic_data_module_1.DynamicDataModule,
73
74
  event_manager_module_1.EventManagerModule,
75
+ collection_insights_module_1.CollectionInsightsModule,
74
76
  ],
75
77
  providers: [
76
78
  {
@@ -96,6 +98,7 @@ exports.AppModule = AppModule = __decorate([
96
98
  transaction_module_1.TransactionModule,
97
99
  timestamp_module_1.TimestampModule,
98
100
  event_manager_module_1.EventManagerModule,
101
+ collection_insights_module_1.CollectionInsightsModule,
99
102
  ]
100
103
  })
101
104
  ], AppModule);
@@ -1 +1 @@
1
- {"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwE;AACxE,kEAA8D;AAC9D,8EAAyE;AACzE,sEAAkE;AAClE,2EAAuE;AACvE,2FAAsF;AACtF,gGAA2F;AAC3F,4EAAwE;AACxE,2EAAuE;AACvE,qDAAiD;AACjD,uEAAmE;AACnE,mEAA+D;AAC/D,wEAAkE;AAClE,uFAAiF;AACjF,gEAA4D;AAC5D,mEAA+D;AAC/D,wFAAmF;AACnF,8FAAyF;AACzF,6EAAwE;AACxE,qFAAiF;AACjF,+EAA2E;AAC3E,uFAAkF;AAClF,kEAA6D;AAC7D,0FAAqF;AACrF,6FAAwF;AACxF,uCAAqD;AACrD,qCAAqC;AACrC,6CAA0C;AAC1C,iDAAkD;AAClD,0FAAqF;AAwD9E,IAAM,SAAS,GAAf,MAAM,SAAS;IACpB,SAAS,CAAC,QAA4B;QACpC,QAAQ,CAAC,KAAK,CAAC,oCAAgB,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAClD,CAAC;CACF,CAAA;AAJY,8BAAS;oBAAT,SAAS;IAtDrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,wBAAU;YACV,gCAAc;YACd,uBAAU;YACV,6CAAoB;YACpB,4BAAY;YACZ,yCAAkB;YAClB,+BAAa;YACb,yCAAkB;YAClB,2CAAmB;YACnB,iCAAc;YACd,0BAAW;YACX,yCAAkB;YAClB,gCAAc;YACd,2CAAmB;YACnB,0BAAW;YACX,4BAAW;YACX,sCAAgB;YAChB,uCAAiB;YACjB,wBAAU;YACV,0BAAW;YACX,iCAAc;YACd,sCAAiB;YACjB,kCAAe;YACf,uCAAiB;YACjB,yCAAkB;SACnB;QACD,SAAS,EAAE;YACT;gBACE,OAAO,EAAE,gBAAS;gBAClB,QAAQ,EAAE,kBAAS;aACpB;YACD;gBACE,OAAO,EAAE,iBAAU;gBACnB,QAAQ,EAAE,+BAAgB;aAC3B;SACF;QACD,WAAW,EAAE,CAAC,8BAAa,CAAC;QAC5B,OAAO,EAAE;YACP,gCAAc;YACd,6CAAoB;YACpB,4BAAY;YACZ,iCAAc;YACd,0BAAW;YACX,yCAAkB;YAClB,gCAAc;YACd,2CAAmB;YACnB,iCAAc;YACd,sCAAiB;YACjB,kCAAe;YACf,yCAAkB;SACnB;KACF,CAAC;GACW,SAAS,CAIrB"}
1
+ {"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwE;AACxE,kEAA8D;AAC9D,8EAAyE;AACzE,sEAAkE;AAClE,2EAAuE;AACvE,2FAAsF;AACtF,gGAA2F;AAC3F,4EAAwE;AACxE,2EAAuE;AACvE,qDAAiD;AACjD,uEAAmE;AACnE,mEAA+D;AAC/D,wEAAkE;AAClE,uFAAiF;AACjF,gEAA4D;AAC5D,mEAA+D;AAC/D,wFAAmF;AACnF,8FAAyF;AACzF,6EAAwE;AACxE,qFAAiF;AACjF,+EAA2E;AAC3E,uFAAkF;AAClF,kEAA6D;AAC7D,0FAAqF;AACrF,6FAAwF;AACxF,uCAAqD;AACrD,qCAAqC;AACrC,6CAA0C;AAC1C,iDAAkD;AAClD,0FAAqF;AACrF,4GAAuG;AA0DhG,IAAM,SAAS,GAAf,MAAM,SAAS;IACpB,SAAS,CAAC,QAA4B;QACpC,QAAQ,CAAC,KAAK,CAAC,oCAAgB,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAClD,CAAC;CACF,CAAA;AAJY,8BAAS;oBAAT,SAAS;IAxDrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,wBAAU;YACV,gCAAc;YACd,uBAAU;YACV,6CAAoB;YACpB,4BAAY;YACZ,yCAAkB;YAClB,+BAAa;YACb,yCAAkB;YAClB,2CAAmB;YACnB,iCAAc;YACd,0BAAW;YACX,yCAAkB;YAClB,gCAAc;YACd,2CAAmB;YACnB,0BAAW;YACX,4BAAW;YACX,sCAAgB;YAChB,uCAAiB;YACjB,wBAAU;YACV,0BAAW;YACX,iCAAc;YACd,sCAAiB;YACjB,kCAAe;YACf,uCAAiB;YACjB,yCAAkB;YAClB,qDAAwB;SACzB;QACD,SAAS,EAAE;YACT;gBACE,OAAO,EAAE,gBAAS;gBAClB,QAAQ,EAAE,kBAAS;aACpB;YACD;gBACE,OAAO,EAAE,iBAAU;gBACnB,QAAQ,EAAE,+BAAgB;aAC3B;SACF;QACD,WAAW,EAAE,CAAC,8BAAa,CAAC;QAC5B,OAAO,EAAE;YACP,gCAAc;YACd,6CAAoB;YACpB,4BAAY;YACZ,iCAAc;YACd,0BAAW;YACX,yCAAkB;YAClB,gCAAc;YACd,2CAAmB;YACnB,iCAAc;YACd,sCAAiB;YACjB,kCAAe;YACf,yCAAkB;YAClB,qDAAwB;SACzB;KACF,CAAC;GACW,SAAS,CAIrB"}
@@ -1,4 +1,4 @@
1
- import { Response } from 'express';
1
+ import { Request, Response } from 'express';
2
2
  import { TelegramService } from './Telegram.service';
3
3
  import { SendMediaDto, GroupSettingsDto, GroupMemberOperationDto, AdminOperationDto, ChatCleanupDto, PrivacySettingsDto, ProfilePhotoDto, ScheduleMessageDto, BatchProcessDto, ForwardBatchDto, ContactExportImportDto, ContactBlockListDto, AddContactsDto, createGroupDto, ViewOnceMediaDto, CreateTgBotDto } from './dto';
4
4
  import { CreateChatFolderDto } from './dto/create-chat-folder.dto';
@@ -13,6 +13,19 @@ import { UpdateProfileDto } from './dto/update-profile.dto';
13
13
  export declare class TelegramController {
14
14
  private readonly telegramService;
15
15
  constructor(telegramService: TelegramService);
16
+ private parseIntegerQuery;
17
+ private parseBooleanQuery;
18
+ private parseThumbnailMode;
19
+ private sanitizeFilename;
20
+ private getRequestBaseUrl;
21
+ private getRequestApiKey;
22
+ private parseRangeHeader;
23
+ private sendRangeNotSatisfiable;
24
+ private waitForDrainOrClose;
25
+ private writeResponseChunk;
26
+ private isNotFoundMediaError;
27
+ private isUnsupportedMediaError;
28
+ private isUnavailableThumbnailError;
16
29
  connect(mobile: string, autoDisconnect?: boolean, handler?: boolean): Promise<{
17
30
  message: string;
18
31
  }>;
@@ -71,11 +84,11 @@ export declare class TelegramController {
71
84
  addContactsBulk(mobile: string, contactsDto: AddContactsDto): Promise<void>;
72
85
  getContacts(mobile: string): Promise<import("telegram").Api.contacts.TypeContacts>;
73
86
  sendMedia(mobile: string, sendMediaDto: SendMediaDto): Promise<void>;
74
- downloadMedia(mobile: string, chatId: string, messageId: number, res: Response): Promise<Response<any, Record<string, any>>>;
75
- getThumbnail(mobile: string, chatId: string, messageId: number, quality: string, res: Response): Promise<Response<any, Record<string, any>>>;
87
+ downloadMedia(mobile: string, chatId: string, messageId: unknown, res: Response): Promise<Response<any, Record<string, any>>>;
88
+ getThumbnail(mobile: string, chatId: string, messageId: unknown, quality: string, res: Response): Promise<Response<any, Record<string, any>>>;
76
89
  sendMediaAlbum(mobile: string, albumDto: MediaAlbumOptions): Promise<import("./dto").AlbumSendResult>;
77
- getMediaMetadata(mobile: string, chatId: string, types?: string | string[], startDate?: string, endDate?: string, limit?: number, maxId?: number, minId?: number): Promise<import("./dto").MediaListResponse>;
78
- getFilteredMedia(mobile: string, chatId: string, types?: string | string[], startDate?: string, endDate?: string, limit?: number, maxId?: number, minId?: number): Promise<import("./dto").FilteredMediaListResponse>;
90
+ getMediaMetadata(mobile: string, chatId: string, types?: string | string[], startDate?: string, endDate?: string, limit?: unknown, maxId?: unknown, minId?: unknown): Promise<import("./dto").MediaListResponse>;
91
+ getFilteredMedia(mobile: string, chatId: string, types?: string | string[], startDate?: string, endDate?: string, limit?: unknown, maxId?: unknown, minId?: unknown, thumbnailMode?: unknown, inlineThumbnailLimit?: unknown, includeThumbnails?: unknown, apiKey?: string, req?: Request): Promise<import("./dto").FilteredMediaListResponse>;
79
92
  getGroupMembers(mobile: string, groupId: string, offset?: number, limit?: number): Promise<import("./dto").PaginatedGroupMembers>;
80
93
  blockChat(mobile: string, chatId: string): Promise<void>;
81
94
  deleteChatHistory(mobile: string, deleteHistoryDto: DeleteHistoryDto): Promise<void>;
@@ -67,10 +67,147 @@ const update_username_dto_1 = require("./dto/update-username.dto");
67
67
  const send_message_dto_1 = require("./dto/send-message.dto");
68
68
  const update_profile_dto_1 = require("./dto/update-profile.dto");
69
69
  const big_integer_1 = __importDefault(require("big-integer"));
70
+ const MEDIA_HTTP_MAX_LIMIT = 200;
71
+ const INLINE_THUMBNAIL_HTTP_MAX_LIMIT = 50;
70
72
  let TelegramController = class TelegramController {
71
73
  constructor(telegramService) {
72
74
  this.telegramService = telegramService;
73
75
  }
76
+ parseIntegerQuery(value, name, options = {}) {
77
+ const { required = false, min = 1, max = Number.MAX_SAFE_INTEGER } = options;
78
+ const rawValue = Array.isArray(value) ? value[0] : value;
79
+ if (rawValue === undefined || rawValue === null || rawValue === '') {
80
+ if (required)
81
+ throw new common_1.BadRequestException(`${name} is required`);
82
+ return undefined;
83
+ }
84
+ const valueText = String(rawValue).trim();
85
+ if (!/^\d+$/.test(valueText)) {
86
+ throw new common_1.BadRequestException(`${name} must be an integer`);
87
+ }
88
+ const parsed = Number(valueText);
89
+ if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {
90
+ throw new common_1.BadRequestException(`${name} must be between ${min} and ${max}`);
91
+ }
92
+ return parsed;
93
+ }
94
+ parseBooleanQuery(value, name) {
95
+ const rawValue = Array.isArray(value) ? value[0] : value;
96
+ if (rawValue === undefined || rawValue === null || rawValue === '')
97
+ return undefined;
98
+ const valueText = String(rawValue).trim().toLowerCase();
99
+ if (['true', '1', 'yes'].includes(valueText))
100
+ return true;
101
+ if (['false', '0', 'no'].includes(valueText))
102
+ return false;
103
+ throw new common_1.BadRequestException(`${name} must be a boolean`);
104
+ }
105
+ parseThumbnailMode(value) {
106
+ const rawValue = Array.isArray(value) ? value[0] : value;
107
+ if (rawValue === undefined || rawValue === null || rawValue === '')
108
+ return 'url';
109
+ const mode = String(rawValue).trim().toLowerCase();
110
+ if (mode === 'url' || mode === 'base64' || mode === 'none')
111
+ return mode;
112
+ throw new common_1.BadRequestException('thumbnailMode must be one of: url, base64, none');
113
+ }
114
+ sanitizeFilename(filename) {
115
+ const sanitized = (filename || 'media.bin').replace(/[\r\n"]/g, '_').trim();
116
+ return (sanitized || 'media.bin').slice(0, 180);
117
+ }
118
+ getRequestBaseUrl(req) {
119
+ const forwardedProto = req.headers['x-forwarded-proto'];
120
+ const forwardedHost = req.headers['x-forwarded-host'];
121
+ const proto = Array.isArray(forwardedProto)
122
+ ? forwardedProto[0]
123
+ : forwardedProto || req.protocol || 'http';
124
+ const host = Array.isArray(forwardedHost)
125
+ ? forwardedHost[0]
126
+ : forwardedHost || req.get('host');
127
+ return host ? `${proto}://${host}` : '';
128
+ }
129
+ getRequestApiKey(req, queryApiKey) {
130
+ const headerApiKey = req.headers['x-api-key'];
131
+ return queryApiKey
132
+ || (Array.isArray(headerApiKey) ? headerApiKey[0] : headerApiKey)
133
+ || undefined;
134
+ }
135
+ parseRangeHeader(range, fileSize) {
136
+ const match = /^bytes=(\d*)-(\d*)$/.exec(range.trim());
137
+ if (!match || fileSize <= 0)
138
+ return null;
139
+ const [, startText, endText] = match;
140
+ if (!startText && !endText)
141
+ return null;
142
+ let start;
143
+ let end;
144
+ if (!startText) {
145
+ const suffixLength = Number(endText);
146
+ if (!Number.isSafeInteger(suffixLength) || suffixLength <= 0)
147
+ return null;
148
+ start = Math.max(fileSize - suffixLength, 0);
149
+ end = fileSize - 1;
150
+ }
151
+ else {
152
+ start = Number(startText);
153
+ end = endText ? Number(endText) : fileSize - 1;
154
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end))
155
+ return null;
156
+ }
157
+ if (start < 0 || end < 0 || start > end || start >= fileSize || end >= fileSize) {
158
+ return null;
159
+ }
160
+ return { start, end, length: end - start + 1 };
161
+ }
162
+ sendRangeNotSatisfiable(res, fileSize) {
163
+ res.status(416).setHeader('Content-Range', `bytes */${fileSize}`);
164
+ return res.end();
165
+ }
166
+ waitForDrainOrClose(res) {
167
+ return new Promise((resolve, reject) => {
168
+ const cleanup = () => {
169
+ res.off('drain', onDrain);
170
+ res.off('close', onClose);
171
+ res.off('error', onError);
172
+ };
173
+ const onDrain = () => {
174
+ cleanup();
175
+ resolve();
176
+ };
177
+ const onClose = () => {
178
+ cleanup();
179
+ resolve();
180
+ };
181
+ const onError = (error) => {
182
+ cleanup();
183
+ reject(error);
184
+ };
185
+ res.once('drain', onDrain);
186
+ res.once('close', onClose);
187
+ res.once('error', onError);
188
+ });
189
+ }
190
+ async writeResponseChunk(res, chunk) {
191
+ if (res.destroyed)
192
+ return false;
193
+ if (!chunk.length)
194
+ return true;
195
+ if (res.write(chunk))
196
+ return true;
197
+ await this.waitForDrainOrClose(res);
198
+ return !res.destroyed;
199
+ }
200
+ isNotFoundMediaError(error) {
201
+ const message = String(error?.message || error || '');
202
+ return message.includes('FILE_REFERENCE_EXPIRED') || message.toLowerCase().includes('not found');
203
+ }
204
+ isUnsupportedMediaError(error) {
205
+ return String(error?.message || error || '').toLowerCase().includes('unsupported media type');
206
+ }
207
+ isUnavailableThumbnailError(error) {
208
+ const message = String(error?.message || error || '').toLowerCase();
209
+ return this.isNotFoundMediaError(error) || message.includes('not available');
210
+ }
74
211
  async connect(mobile, autoDisconnect, handler) {
75
212
  const options = { autoDisconnect, handler };
76
213
  await this.telegramService.connect(mobile, options);
@@ -225,36 +362,32 @@ let TelegramController = class TelegramController {
225
362
  }
226
363
  }
227
364
  async downloadMedia(mobile, chatId, messageId, res) {
228
- if (!messageId || messageId <= 0 || !Number.isInteger(messageId)) {
229
- throw new common_1.BadRequestException('Message ID must be a positive integer');
230
- }
365
+ const parsedMessageId = this.parseIntegerQuery(messageId, 'messageId', { required: true });
231
366
  if (!chatId || chatId.trim().length === 0) {
232
367
  throw new common_1.BadRequestException('Chat ID is required and cannot be empty');
233
368
  }
234
369
  try {
235
- const fileInfo = await this.telegramService.getMediaFileDownloadInfo(mobile, messageId, chatId);
370
+ const fileInfo = await this.telegramService.getMediaFileDownloadInfo(mobile, parsedMessageId, chatId);
371
+ const safeFilename = this.sanitizeFilename(fileInfo.filename);
236
372
  if (res.req.headers['if-none-match'] === fileInfo.etag) {
237
373
  return res.status(304).end();
238
374
  }
239
375
  const range = res.req.headers.range;
240
376
  const ifRange = res.req.headers['if-range'];
241
377
  const chunkSize = 512 * 1024;
242
- const rangeValid = range && fileInfo.fileSize > 0 && (!ifRange || ifRange === fileInfo.etag);
243
- if (rangeValid) {
244
- const parts = range.replace(/bytes=/, "").split("-");
245
- const start = parseInt(parts[0], 10);
246
- const end = parts[1] ? parseInt(parts[1], 10) : fileInfo.fileSize - 1;
247
- const chunksize = (end - start) + 1;
248
- if (start >= fileInfo.fileSize || end >= fileInfo.fileSize || start > end) {
249
- res.status(416).setHeader('Content-Range', `bytes */${fileInfo.fileSize}`);
250
- return res.end();
251
- }
378
+ const rangeRequested = Boolean(range && fileInfo.fileSize > 0 && (!ifRange || ifRange === fileInfo.etag));
379
+ const parsedRange = rangeRequested ? this.parseRangeHeader(String(range), fileInfo.fileSize) : null;
380
+ if (rangeRequested && !parsedRange) {
381
+ return this.sendRangeNotSatisfiable(res, fileInfo.fileSize);
382
+ }
383
+ if (parsedRange) {
384
+ const { start, end, length } = parsedRange;
252
385
  res.status(206);
253
386
  res.setHeader('Content-Range', `bytes ${start}-${end}/${fileInfo.fileSize}`);
254
387
  res.setHeader('Accept-Ranges', 'bytes');
255
- res.setHeader('Content-Length', chunksize);
388
+ res.setHeader('Content-Length', length);
256
389
  res.setHeader('Content-Type', fileInfo.contentType);
257
- res.setHeader('Content-Disposition', `inline; filename="${fileInfo.filename}"`);
390
+ res.setHeader('Content-Disposition', `inline; filename="${safeFilename}"`);
258
391
  res.setHeader('Cache-Control', 'public, max-age=86400, immutable');
259
392
  res.setHeader('ETag', fileInfo.etag);
260
393
  res.setHeader('X-Content-Type-Options', 'nosniff');
@@ -262,8 +395,9 @@ let TelegramController = class TelegramController {
262
395
  res.setHeader('Access-Control-Expose-Headers', 'Content-Length, Content-Range, Accept-Ranges');
263
396
  const alignedStart = Math.floor(start / chunkSize) * chunkSize;
264
397
  const skipBytes = start - alignedStart;
265
- const fetchLimit = chunksize + skipBytes;
398
+ const fetchLimit = length + skipBytes;
266
399
  let skipped = 0;
400
+ let remaining = length;
267
401
  for await (const chunk of this.telegramService.streamMediaFile(mobile, fileInfo.fileLocation, (0, big_integer_1.default)(alignedStart), fetchLimit, chunkSize, fileInfo.fileSize || undefined, fileInfo.dcId)) {
268
402
  if (res.destroyed)
269
403
  break;
@@ -273,13 +407,22 @@ let TelegramController = class TelegramController {
273
407
  data = data.subarray(toSkip);
274
408
  skipped += toSkip;
275
409
  }
276
- if (data.length > 0)
277
- res.write(data);
410
+ if (data.length > remaining) {
411
+ data = data.subarray(0, remaining);
412
+ }
413
+ if (data.length > 0) {
414
+ const canContinue = await this.writeResponseChunk(res, data);
415
+ if (!canContinue)
416
+ return;
417
+ remaining -= data.length;
418
+ }
419
+ if (remaining <= 0)
420
+ break;
278
421
  }
279
422
  }
280
423
  else {
281
424
  res.setHeader('Content-Type', fileInfo.contentType);
282
- res.setHeader('Content-Disposition', `inline; filename="${fileInfo.filename}"`);
425
+ res.setHeader('Content-Disposition', `inline; filename="${safeFilename}"`);
283
426
  res.setHeader('Cache-Control', 'public, max-age=86400, immutable');
284
427
  res.setHeader('ETag', fileInfo.etag);
285
428
  res.setHeader('Accept-Ranges', 'bytes');
@@ -289,46 +432,56 @@ let TelegramController = class TelegramController {
289
432
  if (fileInfo.fileSize > 0) {
290
433
  res.setHeader('Content-Length', fileInfo.fileSize);
291
434
  }
292
- for await (const chunk of this.telegramService.streamMediaFile(mobile, fileInfo.fileLocation, (0, big_integer_1.default)(0), 5 * 1024 * 1024, chunkSize, fileInfo.fileSize || undefined, fileInfo.dcId)) {
435
+ for await (const chunk of this.telegramService.streamMediaFile(mobile, fileInfo.fileLocation, (0, big_integer_1.default)(0), fileInfo.fileSize > 0 ? fileInfo.fileSize : undefined, chunkSize, fileInfo.fileSize || undefined, fileInfo.dcId)) {
293
436
  if (res.destroyed)
294
437
  break;
295
- res.write(chunk);
438
+ const canContinue = await this.writeResponseChunk(res, chunk);
439
+ if (!canContinue)
440
+ return;
296
441
  }
297
442
  }
298
- res.end();
443
+ if (!res.destroyed)
444
+ res.end();
299
445
  }
300
446
  catch (error) {
301
- console.error(`[Download] Error messageId=${messageId} chatId=${chatId}:`, error.message || error, error.stack?.split('\n')[1]);
302
- if (error.message?.includes('FILE_REFERENCE_EXPIRED') || error.message?.includes('not found')) {
447
+ if (res.headersSent) {
448
+ console.error(`[Download] Stream failed messageId=${parsedMessageId} chatId=${chatId}:`, error.message || error, error.stack?.split('\n')[1]);
449
+ if (!res.destroyed)
450
+ res.destroy(error);
451
+ return;
452
+ }
453
+ if (this.isNotFoundMediaError(error)) {
454
+ console.warn(`[Download] Media unavailable messageId=${parsedMessageId} chatId=${chatId}:`, error.message || error);
303
455
  return res.status(404).send(error.message || 'File reference expired');
304
456
  }
305
- if (!res.headersSent) {
306
- res.status(500).send(`Error downloading media: ${error.message || 'Unknown error'}`);
457
+ if (this.isUnsupportedMediaError(error)) {
458
+ console.warn(`[Download] Unsupported media messageId=${parsedMessageId} chatId=${chatId}:`, error.message || error);
459
+ return res.status(415).send(error.message || 'Unsupported media type');
307
460
  }
461
+ console.error(`[Download] Error messageId=${parsedMessageId} chatId=${chatId}:`, error.message || error, error.stack?.split('\n')[1]);
462
+ res.status(500).send(`Error downloading media: ${error.message || 'Unknown error'}`);
308
463
  }
309
464
  }
310
465
  async getThumbnail(mobile, chatId, messageId, quality, res) {
311
- if (!messageId || messageId <= 0 || !Number.isInteger(messageId)) {
312
- throw new common_1.BadRequestException('Message ID must be a positive integer');
313
- }
466
+ const parsedMessageId = this.parseIntegerQuery(messageId, 'messageId', { required: true });
314
467
  if (!chatId || chatId.trim().length === 0) {
315
468
  throw new common_1.BadRequestException('Chat ID is required and cannot be empty');
316
469
  }
317
470
  const q = quality === 'high' ? 'high' : 'low';
318
471
  try {
319
- const thumbnail = await this.telegramService.getThumbnail(mobile, messageId, chatId, q);
472
+ const thumbnail = await this.telegramService.getThumbnail(mobile, parsedMessageId, chatId, q);
320
473
  if (res.req.headers['if-none-match'] === thumbnail.etag) {
321
474
  return res.status(304).end();
322
475
  }
323
476
  res.setHeader('Content-Type', thumbnail.contentType);
324
- res.setHeader('Content-Disposition', `inline; filename="${thumbnail.filename}"`);
477
+ res.setHeader('Content-Disposition', `inline; filename="${this.sanitizeFilename(thumbnail.filename)}"`);
325
478
  res.setHeader('Cache-Control', 'public, max-age=604800, immutable');
326
479
  res.setHeader('ETag', thumbnail.etag);
327
480
  res.setHeader('Content-Length', thumbnail.buffer.length);
328
481
  return res.send(thumbnail.buffer);
329
482
  }
330
483
  catch (error) {
331
- if (error.message?.includes('FILE_REFERENCE_EXPIRED') || error.message?.includes('not found') || error.message?.includes('not available')) {
484
+ if (this.isUnavailableThumbnailError(error)) {
332
485
  return res.status(404).send(error.message || 'Thumbnail not available');
333
486
  }
334
487
  if (!res.headersSent) {
@@ -349,16 +502,16 @@ let TelegramController = class TelegramController {
349
502
  if (!chatId || chatId.trim().length === 0) {
350
503
  throw new common_1.BadRequestException('Chat ID is required and cannot be empty');
351
504
  }
352
- if (limit !== undefined && (limit <= 0 || limit > 1000)) {
353
- throw new common_1.BadRequestException('Limit must be between 1 and 1000');
354
- }
505
+ const parsedLimit = this.parseIntegerQuery(limit, 'limit', { required: false, max: MEDIA_HTTP_MAX_LIMIT });
506
+ const parsedMaxId = this.parseIntegerQuery(maxId, 'maxId', { required: false });
507
+ const parsedMinId = this.parseIntegerQuery(minId, 'minId', { required: false });
355
508
  let parsedTypes;
356
509
  if (types) {
357
510
  const typesArray = Array.isArray(types) ? types : [types];
358
511
  const validTypes = ['photo', 'video', 'document', 'voice', 'audio', 'gif', 'roundVideo', 'sticker', 'all'];
359
512
  parsedTypes = typesArray
360
- .filter(t => validTypes.includes(t.toLowerCase()) || validTypes.includes(t))
361
- .map(t => t);
513
+ .map(t => validTypes.find(validType => validType.toLowerCase() === String(t).trim().toLowerCase()))
514
+ .filter((t) => Boolean(t));
362
515
  if (parsedTypes.length === 0) {
363
516
  throw new common_1.BadRequestException(`Invalid types. Must be one or more of: ${validTypes.join(', ')}`);
364
517
  }
@@ -385,25 +538,35 @@ let TelegramController = class TelegramController {
385
538
  types: parsedTypes,
386
539
  startDate: parsedStartDate,
387
540
  endDate: parsedEndDate,
388
- limit,
389
- maxId,
390
- minId
541
+ limit: parsedLimit,
542
+ maxId: parsedMaxId,
543
+ minId: parsedMinId
391
544
  });
392
545
  }
393
- async getFilteredMedia(mobile, chatId, types, startDate, endDate, limit, maxId, minId) {
546
+ async getFilteredMedia(mobile, chatId, types, startDate, endDate, limit, maxId, minId, thumbnailMode, inlineThumbnailLimit, includeThumbnails, apiKey, req) {
394
547
  if (!chatId || chatId.trim().length === 0) {
395
548
  throw new common_1.BadRequestException('Chat ID is required and cannot be empty');
396
549
  }
397
- if (limit !== undefined && (limit <= 0 || limit > 1000)) {
398
- throw new common_1.BadRequestException('Limit must be between 1 and 1000');
550
+ const parsedLimit = this.parseIntegerQuery(limit, 'limit', { required: false, max: MEDIA_HTTP_MAX_LIMIT });
551
+ const parsedMaxId = this.parseIntegerQuery(maxId, 'maxId', { required: false });
552
+ const parsedMinId = this.parseIntegerQuery(minId, 'minId', { required: false });
553
+ const parsedInlineThumbnailLimit = this.parseIntegerQuery(inlineThumbnailLimit, 'inlineThumbnailLimit', {
554
+ required: false,
555
+ min: 0,
556
+ max: INLINE_THUMBNAIL_HTTP_MAX_LIMIT,
557
+ });
558
+ const parsedIncludeThumbnails = this.parseBooleanQuery(includeThumbnails, 'includeThumbnails');
559
+ let parsedThumbnailMode = this.parseThumbnailMode(thumbnailMode);
560
+ if (parsedIncludeThumbnails === false) {
561
+ parsedThumbnailMode = 'none';
399
562
  }
400
563
  let parsedTypes;
401
564
  if (types) {
402
565
  const typesArray = Array.isArray(types) ? types : [types];
403
566
  const validTypes = ['photo', 'video', 'document', 'voice', 'audio', 'gif', 'roundVideo', 'sticker', 'all'];
404
567
  parsedTypes = typesArray
405
- .filter(t => validTypes.includes(t.toLowerCase()) || validTypes.includes(t))
406
- .map(t => t);
568
+ .map(t => validTypes.find(validType => validType.toLowerCase() === String(t).trim().toLowerCase()))
569
+ .filter((t) => Boolean(t));
407
570
  if (parsedTypes.length === 0) {
408
571
  throw new common_1.BadRequestException(`Invalid types. Must be one or more of: ${validTypes.join(', ')}`);
409
572
  }
@@ -430,9 +593,13 @@ let TelegramController = class TelegramController {
430
593
  types: parsedTypes,
431
594
  startDate: parsedStartDate,
432
595
  endDate: parsedEndDate,
433
- limit,
434
- maxId,
435
- minId
596
+ limit: parsedLimit,
597
+ maxId: parsedMaxId,
598
+ minId: parsedMinId,
599
+ thumbnailMode: parsedThumbnailMode,
600
+ inlineThumbnailLimit: parsedInlineThumbnailLimit,
601
+ thumbnailApiKey: req ? this.getRequestApiKey(req, typeof apiKey === 'string' ? apiKey : undefined) : undefined,
602
+ thumbnailBaseUrl: req ? this.getRequestBaseUrl(req) : undefined,
436
603
  });
437
604
  }
438
605
  async getGroupMembers(mobile, groupId, offset, limit) {
@@ -1135,7 +1302,7 @@ __decorate([
1135
1302
  __param(2, (0, common_1.Query)('messageId')),
1136
1303
  __param(3, (0, common_1.Res)()),
1137
1304
  __metadata("design:type", Function),
1138
- __metadata("design:paramtypes", [String, String, Number, Object]),
1305
+ __metadata("design:paramtypes", [String, String, Object, Object]),
1139
1306
  __metadata("design:returntype", Promise)
1140
1307
  ], TelegramController.prototype, "downloadMedia", null);
1141
1308
  __decorate([
@@ -1187,7 +1354,7 @@ __decorate([
1187
1354
  __param(3, (0, common_1.Query)('quality')),
1188
1355
  __param(4, (0, common_1.Res)()),
1189
1356
  __metadata("design:type", Function),
1190
- __metadata("design:paramtypes", [String, String, Number, String, Object]),
1357
+ __metadata("design:paramtypes", [String, String, Object, String, Object]),
1191
1358
  __metadata("design:returntype", Promise)
1192
1359
  ], TelegramController.prototype, "getThumbnail", null);
1193
1360
  __decorate([
@@ -1265,7 +1432,7 @@ __decorate([
1265
1432
  }),
1266
1433
  (0, swagger_1.ApiQuery)({
1267
1434
  name: 'limit',
1268
- description: 'Maximum number of messages to fetch (default: 50, max: 1000)',
1435
+ description: 'Maximum number of messages to fetch (default: 50, max: 200)',
1269
1436
  required: false,
1270
1437
  type: Number
1271
1438
  }),
@@ -1299,14 +1466,14 @@ __decorate([
1299
1466
  __param(6, (0, common_1.Query)('maxId')),
1300
1467
  __param(7, (0, common_1.Query)('minId')),
1301
1468
  __metadata("design:type", Function),
1302
- __metadata("design:paramtypes", [String, String, Object, String, String, Number, Number, Number]),
1469
+ __metadata("design:paramtypes", [String, String, Object, String, String, Object, Object, Object]),
1303
1470
  __metadata("design:returntype", Promise)
1304
1471
  ], TelegramController.prototype, "getMediaMetadata", null);
1305
1472
  __decorate([
1306
1473
  (0, common_1.Get)('media/filter/:mobile'),
1307
1474
  (0, swagger_1.ApiOperation)({
1308
1475
  summary: 'Get filtered media messages from a chat',
1309
- description: 'Get filtered list of media messages with detailed metadata including thumbnails. Returns standardized paginated response. Use maxId for pagination (get messages with ID less than maxId).'
1476
+ description: 'Get filtered list of media messages with detailed metadata. Returns thumbnail URLs by default; inline base64 thumbnails are opt-in and capped. Use maxId for pagination (get messages with ID less than maxId).'
1310
1477
  }),
1311
1478
  (0, swagger_1.ApiParam)({ name: 'mobile', description: 'Mobile number of the Telegram account', required: true }),
1312
1479
  (0, swagger_1.ApiQuery)({
@@ -1335,7 +1502,7 @@ __decorate([
1335
1502
  name: 'limit',
1336
1503
  required: false,
1337
1504
  type: Number,
1338
- description: 'Maximum number of media items to fetch (default: 50, max: 1000)'
1505
+ description: 'Maximum number of media items to fetch (default: 50, max: 200)'
1339
1506
  }),
1340
1507
  (0, swagger_1.ApiQuery)({
1341
1508
  name: 'maxId',
@@ -1349,6 +1516,24 @@ __decorate([
1349
1516
  type: Number,
1350
1517
  description: 'Minimum message ID to include'
1351
1518
  }),
1519
+ (0, swagger_1.ApiQuery)({
1520
+ name: 'thumbnailMode',
1521
+ required: false,
1522
+ enum: ['url', 'base64', 'none'],
1523
+ description: 'Thumbnail response mode. url is default and avoids inline media memory growth; base64 is capped by inlineThumbnailLimit.'
1524
+ }),
1525
+ (0, swagger_1.ApiQuery)({
1526
+ name: 'inlineThumbnailLimit',
1527
+ required: false,
1528
+ type: Number,
1529
+ description: 'Maximum number of inline base64 thumbnails when thumbnailMode=base64 (default: 25, max: 50)'
1530
+ }),
1531
+ (0, swagger_1.ApiQuery)({
1532
+ name: 'includeThumbnails',
1533
+ required: false,
1534
+ type: Boolean,
1535
+ description: 'Compatibility flag. false maps thumbnailMode to none; true maps unset thumbnailMode to url.'
1536
+ }),
1352
1537
  (0, swagger_1.ApiResponse)({
1353
1538
  status: 200,
1354
1539
  description: 'Paginated media response with standardized format',
@@ -1366,8 +1551,13 @@ __decorate([
1366
1551
  __param(5, (0, common_1.Query)('limit')),
1367
1552
  __param(6, (0, common_1.Query)('maxId')),
1368
1553
  __param(7, (0, common_1.Query)('minId')),
1554
+ __param(8, (0, common_1.Query)('thumbnailMode')),
1555
+ __param(9, (0, common_1.Query)('inlineThumbnailLimit')),
1556
+ __param(10, (0, common_1.Query)('includeThumbnails')),
1557
+ __param(11, (0, common_1.Query)('apiKey')),
1558
+ __param(12, (0, common_1.Req)()),
1369
1559
  __metadata("design:type", Function),
1370
- __metadata("design:paramtypes", [String, String, Object, String, String, Number, Number, Number]),
1560
+ __metadata("design:paramtypes", [String, String, Object, String, String, Object, Object, Object, Object, Object, Object, String, Object]),
1371
1561
  __metadata("design:returntype", Promise)
1372
1562
  ], TelegramController.prototype, "getFilteredMedia", null);
1373
1563
  __decorate([