common-tg-service 1.3.214 → 1.3.217
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/dist/app.module.js +3 -0
- package/dist/app.module.js.map +1 -1
- package/dist/components/Telegram/Telegram.controller.d.ts +18 -5
- package/dist/components/Telegram/Telegram.controller.js +246 -56
- package/dist/components/Telegram/Telegram.controller.js.map +1 -1
- package/dist/components/Telegram/Telegram.service.d.ts +4 -0
- package/dist/components/Telegram/Telegram.service.js +39 -22
- package/dist/components/Telegram/Telegram.service.js.map +1 -1
- package/dist/components/Telegram/manager/TelegramManager.js +1 -1
- package/dist/components/Telegram/manager/TelegramManager.js.map +1 -1
- package/dist/components/Telegram/manager/helpers.d.ts +30 -1
- package/dist/components/Telegram/manager/helpers.js +101 -6
- package/dist/components/Telegram/manager/helpers.js.map +1 -1
- package/dist/components/Telegram/manager/media-operations.js +196 -74
- package/dist/components/Telegram/manager/media-operations.js.map +1 -1
- package/dist/components/Telegram/manager/types.d.ts +8 -0
- package/dist/components/Telegram/manager/types.js.map +1 -1
- package/dist/components/Telegram/utils/connection-manager.d.ts +1 -1
- package/dist/components/Telegram/utils/connection-manager.js +1 -1
- package/dist/components/Telegram/utils/connection-manager.js.map +1 -1
- package/dist/components/active-channels/active-channels.service.d.ts +8 -2
- package/dist/components/active-channels/active-channels.service.js +75 -18
- package/dist/components/active-channels/active-channels.service.js.map +1 -1
- package/dist/components/active-channels/dto/create-active-channel.dto.d.ts +4 -0
- package/dist/components/active-channels/dto/create-active-channel.dto.js +21 -1
- package/dist/components/active-channels/dto/create-active-channel.dto.js.map +1 -1
- package/dist/components/active-channels/schemas/active-channel.schema.d.ts +40 -0
- package/dist/components/active-channels/schemas/active-channel.schema.js +20 -0
- package/dist/components/active-channels/schemas/active-channel.schema.js.map +1 -1
- package/dist/components/buffer-clients/buffer-client.service.d.ts +1 -1
- package/dist/components/buffer-clients/buffer-client.service.js +1 -1
- package/dist/components/buffer-clients/buffer-client.service.js.map +1 -1
- package/dist/components/channels/channels.service.d.ts +7 -1
- package/dist/components/channels/channels.service.js +79 -15
- package/dist/components/channels/channels.service.js.map +1 -1
- package/dist/components/channels/dto/create-channel.dto.d.ts +2 -0
- package/dist/components/channels/dto/create-channel.dto.js +12 -0
- package/dist/components/channels/dto/create-channel.dto.js.map +1 -1
- package/dist/components/channels/schemas/channel.schema.d.ts +20 -0
- package/dist/components/channels/schemas/channel.schema.js +10 -0
- package/dist/components/channels/schemas/channel.schema.js.map +1 -1
- package/dist/components/clients/client.service.js +4 -0
- package/dist/components/clients/client.service.js.map +1 -1
- package/dist/components/collection-insights/collection-insights.controller.d.ts +75 -0
- package/dist/components/collection-insights/collection-insights.controller.js +106 -0
- package/dist/components/collection-insights/collection-insights.controller.js.map +1 -0
- package/dist/components/collection-insights/collection-insights.module.d.ts +2 -0
- package/dist/components/collection-insights/collection-insights.module.js +25 -0
- package/dist/components/collection-insights/collection-insights.module.js.map +1 -0
- package/dist/components/collection-insights/collection-insights.service.d.ts +79 -0
- package/dist/components/collection-insights/collection-insights.service.js +326 -0
- package/dist/components/collection-insights/collection-insights.service.js.map +1 -0
- package/dist/components/collection-insights/dto/collection-query.dto.d.ts +12 -0
- package/dist/components/collection-insights/dto/collection-query.dto.js +63 -0
- package/dist/components/collection-insights/dto/collection-query.dto.js.map +1 -0
- package/dist/components/collection-insights/dto/index.d.ts +1 -0
- package/dist/components/collection-insights/dto/index.js +18 -0
- package/dist/components/collection-insights/dto/index.js.map +1 -0
- package/dist/components/collection-insights/index.d.ts +4 -0
- package/dist/components/collection-insights/index.js +21 -0
- package/dist/components/collection-insights/index.js.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/promote-stats/promote-stat.module.js +1 -1
- package/dist/components/promote-stats/promote-stat.module.js.map +1 -1
- package/dist/components/shared/base-client.service.d.ts +1 -0
- package/dist/components/shared/base-client.service.js +42 -3
- package/dist/components/shared/base-client.service.js.map +1 -1
- package/dist/components/user-data/schemas/user-data.schema.d.ts +30 -0
- package/dist/components/user-data/schemas/user-data.schema.js +12 -0
- package/dist/components/user-data/schemas/user-data.schema.js.map +1 -1
- package/dist/components/users/users.controller.d.ts +7 -0
- package/dist/components/users/users.controller.js +38 -0
- package/dist/components/users/users.controller.js.map +1 -1
- package/dist/components/users/users.service.d.ts +4 -2
- package/dist/components/users/users.service.js +165 -36
- package/dist/components/users/users.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils/parseError.d.ts +1 -1
- package/dist/utils/parseError.js +15 -8
- package/dist/utils/parseError.js.map +1 -1
- package/dist/utils/telegram-utils/channel-live-facts.d.ts +26 -0
- package/dist/utils/telegram-utils/channel-live-facts.js +74 -0
- package/dist/utils/telegram-utils/channel-live-facts.js.map +1 -0
- package/dist/utils/telegram-utils/channelinfo.js +6 -4
- package/dist/utils/telegram-utils/channelinfo.js.map +1 -1
- package/dist/utils/telegram-utils/common-chats.d.ts +12 -0
- package/dist/utils/telegram-utils/common-chats.js +46 -0
- package/dist/utils/telegram-utils/common-chats.js.map +1 -0
- package/package.json +1 -1
package/dist/app.module.js
CHANGED
|
@@ -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);
|
package/dist/app.module.js.map
CHANGED
|
@@ -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;
|
|
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:
|
|
75
|
-
getThumbnail(mobile: string, chatId: string, messageId:
|
|
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?:
|
|
78
|
-
getFilteredMedia(mobile: string, chatId: string, types?: string | string[], startDate?: string, endDate?: string, limit?:
|
|
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
|
-
|
|
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,
|
|
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
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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',
|
|
388
|
+
res.setHeader('Content-Length', length);
|
|
256
389
|
res.setHeader('Content-Type', fileInfo.contentType);
|
|
257
|
-
res.setHeader('Content-Disposition', `inline; 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 =
|
|
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 >
|
|
277
|
-
|
|
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="${
|
|
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),
|
|
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
|
-
|
|
438
|
+
const canContinue = await this.writeResponseChunk(res, chunk);
|
|
439
|
+
if (!canContinue)
|
|
440
|
+
return;
|
|
296
441
|
}
|
|
297
442
|
}
|
|
298
|
-
res.
|
|
443
|
+
if (!res.destroyed)
|
|
444
|
+
res.end();
|
|
299
445
|
}
|
|
300
446
|
catch (error) {
|
|
301
|
-
|
|
302
|
-
|
|
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 (
|
|
306
|
-
|
|
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
|
-
|
|
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,
|
|
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 (
|
|
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
|
-
|
|
353
|
-
|
|
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
|
-
.
|
|
361
|
-
.
|
|
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
|
-
|
|
398
|
-
|
|
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
|
-
.
|
|
406
|
-
.
|
|
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,
|
|
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,
|
|
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:
|
|
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,
|
|
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
|
|
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:
|
|
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,
|
|
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([
|