common-tg-service 1.3.127 → 1.3.129
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/components/Telegram/TelegramManager.d.ts +7 -1
- package/dist/components/Telegram/TelegramManager.js +139 -315
- package/dist/components/Telegram/TelegramManager.js.map +1 -1
- package/dist/components/users/users.service.js +2 -2
- package/dist/components/users/users.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -123,7 +123,12 @@ declare class TelegramManager {
|
|
|
123
123
|
totalCalls: number;
|
|
124
124
|
analyzedCalls: number;
|
|
125
125
|
}>;
|
|
126
|
-
getCallLogsInternal(): Promise<{
|
|
126
|
+
getCallLogsInternal(): Promise<Record<string, {
|
|
127
|
+
outgoing: number;
|
|
128
|
+
incoming: number;
|
|
129
|
+
video: number;
|
|
130
|
+
totalCalls: number;
|
|
131
|
+
}>>;
|
|
127
132
|
handleEvents(event: NewMessageEvent): Promise<void>;
|
|
128
133
|
updatePrivacyforDeletedAccount(): Promise<void>;
|
|
129
134
|
updateProfile(firstName: string, about: string): Promise<void>;
|
|
@@ -914,6 +919,7 @@ declare class TelegramManager {
|
|
|
914
919
|
textMessages: number;
|
|
915
920
|
};
|
|
916
921
|
}>>;
|
|
922
|
+
private analyzeChatEngagement;
|
|
917
923
|
createGroupOrChannel(options: GroupOptions): Promise<Api.TypeUpdates>;
|
|
918
924
|
createBot(options: {
|
|
919
925
|
name: string;
|
|
@@ -1153,27 +1153,30 @@ class TelegramManager {
|
|
|
1153
1153
|
hash: (0, big_integer_1.default)(0),
|
|
1154
1154
|
}));
|
|
1155
1155
|
const callLogs = result.messages.filter((message) => message.action instanceof telegram_1.Api.MessageActionPhoneCall);
|
|
1156
|
-
const filteredResults = {
|
|
1157
|
-
outgoing: 0,
|
|
1158
|
-
incoming: 0,
|
|
1159
|
-
video: 0,
|
|
1160
|
-
chatCallCounts: {},
|
|
1161
|
-
totalCalls: 0
|
|
1162
|
-
};
|
|
1163
1156
|
for (const log of callLogs) {
|
|
1164
|
-
|
|
1157
|
+
if (!log.peerId || !(log.peerId instanceof telegram_1.Api.PeerUser))
|
|
1158
|
+
continue;
|
|
1159
|
+
const chatId = log.peerId.userId.toString();
|
|
1160
|
+
if (!finalResult[chatId]) {
|
|
1161
|
+
finalResult[chatId] = {
|
|
1162
|
+
outgoing: 0,
|
|
1163
|
+
incoming: 0,
|
|
1164
|
+
video: 0,
|
|
1165
|
+
totalCalls: 0
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
const stats = finalResult[chatId];
|
|
1169
|
+
stats.totalCalls++;
|
|
1165
1170
|
const logAction = log.action;
|
|
1166
1171
|
if (log.out) {
|
|
1167
|
-
|
|
1172
|
+
stats.outgoing++;
|
|
1168
1173
|
}
|
|
1169
1174
|
else {
|
|
1170
|
-
|
|
1175
|
+
stats.incoming++;
|
|
1171
1176
|
}
|
|
1172
1177
|
if (logAction.video) {
|
|
1173
|
-
|
|
1178
|
+
stats.video++;
|
|
1174
1179
|
}
|
|
1175
|
-
const chatId = log.peerId.userId.toString();
|
|
1176
|
-
finalResult[chatId] = filteredResults;
|
|
1177
1180
|
}
|
|
1178
1181
|
return finalResult;
|
|
1179
1182
|
}
|
|
@@ -3491,45 +3494,39 @@ class TelegramManager {
|
|
|
3491
3494
|
if (!this.client)
|
|
3492
3495
|
throw new Error('Client not initialized');
|
|
3493
3496
|
const clampedLimit = Math.max(1, Math.min(50, limit || 10));
|
|
3494
|
-
this.logger.info(this.phoneNumber, `Starting getTopPrivateChats analysis with limit=${clampedLimit}...`);
|
|
3497
|
+
this.logger.info(this.phoneNumber, `Starting optimized getTopPrivateChats analysis with limit=${clampedLimit}...`);
|
|
3495
3498
|
const startTime = Date.now();
|
|
3496
3499
|
const now = Date.now();
|
|
3497
3500
|
const nowSeconds = Math.floor(now / 1000);
|
|
3498
3501
|
const weights = {
|
|
3499
|
-
videoCall:
|
|
3502
|
+
videoCall: 2,
|
|
3500
3503
|
incomingCall: 4,
|
|
3501
|
-
outgoingCall:
|
|
3504
|
+
outgoingCall: 1,
|
|
3502
3505
|
sharedVideo: 12,
|
|
3503
3506
|
sharedPhoto: 10,
|
|
3504
3507
|
textMessage: 1,
|
|
3505
3508
|
unreadMessages: 1,
|
|
3506
|
-
recentActivity: 1,
|
|
3507
3509
|
};
|
|
3508
3510
|
const ACTIVITY_WINDOWS = {
|
|
3509
3511
|
recent: 7,
|
|
3510
3512
|
active: 30,
|
|
3511
3513
|
dormant: 90
|
|
3512
3514
|
};
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
})) {
|
|
3529
|
-
dialogs.push(dialog);
|
|
3530
|
-
}
|
|
3531
|
-
this.logger.info(this.phoneNumber, `Found ${dialogs.length} total dialogs`);
|
|
3532
|
-
const privateChats = dialogs
|
|
3515
|
+
this.logger.info(this.phoneNumber, 'Fetching initial metadata in parallel...');
|
|
3516
|
+
const [me, callLogs, dialogs] = await Promise.all([
|
|
3517
|
+
this.getMe().catch(() => null),
|
|
3518
|
+
this.getCallLogsInternal().catch(() => ({})),
|
|
3519
|
+
(async () => {
|
|
3520
|
+
const results = [];
|
|
3521
|
+
for await (const dialog of this.client.iterDialogs({ limit: 150 })) {
|
|
3522
|
+
results.push(dialog);
|
|
3523
|
+
}
|
|
3524
|
+
return results;
|
|
3525
|
+
})()
|
|
3526
|
+
]);
|
|
3527
|
+
if (!me)
|
|
3528
|
+
throw new Error('Failed to fetch self userInfo');
|
|
3529
|
+
const candidateChats = dialogs
|
|
3533
3530
|
.filter(dialog => {
|
|
3534
3531
|
if (!dialog.isUser || !(dialog.entity instanceof telegram_1.Api.User))
|
|
3535
3532
|
return false;
|
|
@@ -3537,308 +3534,135 @@ class TelegramManager {
|
|
|
3537
3534
|
if (user.bot || user.fake)
|
|
3538
3535
|
return false;
|
|
3539
3536
|
const userId = user.id.toString();
|
|
3540
|
-
|
|
3541
|
-
return false;
|
|
3542
|
-
return true;
|
|
3537
|
+
return userId !== "777000" && userId !== "42777";
|
|
3543
3538
|
})
|
|
3544
|
-
.
|
|
3545
|
-
const
|
|
3546
|
-
const
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3539
|
+
.map(dialog => {
|
|
3540
|
+
const lastDate = dialog.message?.date || 0;
|
|
3541
|
+
const daysSinceLast = (nowSeconds - lastDate) / 86400;
|
|
3542
|
+
const recencyScore = Math.max(0, 100 - daysSinceLast * 2);
|
|
3543
|
+
const unreadScore = (dialog.unreadCount || 0) * 10;
|
|
3544
|
+
const pinnedScore = dialog.pinned ? 50 : 0;
|
|
3545
|
+
return {
|
|
3546
|
+
dialog,
|
|
3547
|
+
preliminaryScore: recencyScore + unreadScore + pinnedScore,
|
|
3548
|
+
daysSinceLast
|
|
3549
|
+
};
|
|
3550
|
+
})
|
|
3551
|
+
.sort((a, b) => b.preliminaryScore - a.preliminaryScore);
|
|
3551
3552
|
let selfChatData = null;
|
|
3552
3553
|
try {
|
|
3553
|
-
const me = await this.getMe();
|
|
3554
3554
|
const selfChatId = me.id.toString();
|
|
3555
|
-
this.
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
let replyChainCount = 0;
|
|
3559
|
-
const messageDates = [];
|
|
3560
|
-
const mediaStats = { photos: 0, videos: 0 };
|
|
3561
|
-
for await (const message of this.client.iterMessages('me', {
|
|
3562
|
-
limit: 500,
|
|
3563
|
-
reverse: false
|
|
3564
|
-
})) {
|
|
3565
|
-
messageCount++;
|
|
3566
|
-
if (message.date) {
|
|
3567
|
-
messageDates.push(message.date * 1000);
|
|
3568
|
-
}
|
|
3569
|
-
if (message.out) {
|
|
3570
|
-
ownMessageCount++;
|
|
3571
|
-
}
|
|
3572
|
-
if (message.replyTo) {
|
|
3573
|
-
replyChainCount++;
|
|
3574
|
-
}
|
|
3575
|
-
if (message.media && !(message.media instanceof telegram_1.Api.MessageMediaEmpty)) {
|
|
3576
|
-
if (message.media instanceof telegram_1.Api.MessageMediaPhoto) {
|
|
3577
|
-
mediaStats.photos++;
|
|
3578
|
-
}
|
|
3579
|
-
else if (message.media instanceof telegram_1.Api.MessageMediaDocument) {
|
|
3580
|
-
const document = message.media.document;
|
|
3581
|
-
if (document instanceof telegram_1.Api.Document) {
|
|
3582
|
-
const isVideo = document.attributes.some(attr => attr instanceof telegram_1.Api.DocumentAttributeVideo);
|
|
3583
|
-
if (isVideo) {
|
|
3584
|
-
mediaStats.videos++;
|
|
3585
|
-
}
|
|
3586
|
-
}
|
|
3587
|
-
}
|
|
3588
|
-
}
|
|
3589
|
-
}
|
|
3590
|
-
let totalMessages = messageCount;
|
|
3591
|
-
try {
|
|
3592
|
-
const firstBatch = await this.client.getMessages('me', { limit: 1 });
|
|
3593
|
-
if (firstBatch.total) {
|
|
3594
|
-
totalMessages = firstBatch.total;
|
|
3595
|
-
}
|
|
3596
|
-
}
|
|
3597
|
-
catch (totalError) {
|
|
3598
|
-
}
|
|
3599
|
-
const lastMessageDate = messageDates.length > 0 ? Math.max(...messageDates) : now;
|
|
3600
|
-
const daysSinceLastActivity = (now - lastMessageDate) / (1000 * 60 * 60 * 24);
|
|
3601
|
-
const timeDecay = getTimeDecayMultiplier(daysSinceLastActivity);
|
|
3602
|
-
const mutualEngagementScore = messageCount > 0 ? Math.min(1.5, (ownMessageCount / messageCount) * 2) : 1.0;
|
|
3603
|
-
const conversationDepthScore = messageCount > 0 ? Math.min(1.3, 1 + (replyChainCount / messageCount) * 0.3) : 1.0;
|
|
3604
|
-
const callStats = {
|
|
3605
|
-
total: 0,
|
|
3606
|
-
incoming: { total: 0, audio: 0, video: 0 },
|
|
3607
|
-
outgoing: { total: 0, audio: 0, video: 0 }
|
|
3608
|
-
};
|
|
3609
|
-
const baseScore = (mediaStats.videos * weights.sharedVideo +
|
|
3610
|
-
mediaStats.photos * weights.sharedPhoto +
|
|
3611
|
-
totalMessages * weights.textMessage);
|
|
3612
|
-
const interactionScore = baseScore * timeDecay * mutualEngagementScore * conversationDepthScore;
|
|
3613
|
-
let engagementLevel;
|
|
3614
|
-
if (daysSinceLastActivity <= ACTIVITY_WINDOWS.recent) {
|
|
3615
|
-
engagementLevel = 'recent';
|
|
3616
|
-
}
|
|
3617
|
-
else if (daysSinceLastActivity <= ACTIVITY_WINDOWS.active) {
|
|
3618
|
-
engagementLevel = 'active';
|
|
3619
|
-
}
|
|
3620
|
-
else {
|
|
3621
|
-
engagementLevel = 'dormant';
|
|
3622
|
-
}
|
|
3623
|
-
const totalActivity = (mediaStats.videos * weights.sharedVideo + mediaStats.photos * weights.sharedPhoto) +
|
|
3624
|
-
totalMessages * weights.textMessage;
|
|
3625
|
-
const activityBreakdown = totalActivity > 0 ? {
|
|
3626
|
-
videoCalls: 0,
|
|
3627
|
-
audioCalls: 0,
|
|
3628
|
-
mediaSharing: Math.round(((mediaStats.videos * weights.sharedVideo + mediaStats.photos * weights.sharedPhoto) / totalActivity) * 100),
|
|
3629
|
-
textMessages: Math.round((totalMessages * weights.textMessage / totalActivity) * 100)
|
|
3630
|
-
} : {
|
|
3631
|
-
videoCalls: 0,
|
|
3632
|
-
audioCalls: 0,
|
|
3633
|
-
mediaSharing: 0,
|
|
3634
|
-
textMessages: 100
|
|
3635
|
-
};
|
|
3636
|
-
selfChatData = {
|
|
3637
|
-
chatId: 'me',
|
|
3638
|
-
username: me.username,
|
|
3639
|
-
firstName: me.firstName || 'Saved Messages',
|
|
3640
|
-
lastName: me.lastName,
|
|
3641
|
-
totalMessages,
|
|
3642
|
-
interactionScore: Math.round(interactionScore * 100) / 100,
|
|
3643
|
-
engagementLevel,
|
|
3644
|
-
lastActivityDays: Math.round(daysSinceLastActivity * 10) / 10,
|
|
3645
|
-
calls: callStats,
|
|
3646
|
-
media: mediaStats,
|
|
3647
|
-
activityBreakdown
|
|
3648
|
-
};
|
|
3649
|
-
this.logger.info(this.phoneNumber, `Self chat processed - Score: ${selfChatData.interactionScore}, Total Messages: ${totalMessages}`);
|
|
3555
|
+
const results = await this.analyzeChatEngagement('me', me, 300, callLogs[selfChatId], weights, now, ACTIVITY_WINDOWS);
|
|
3556
|
+
selfChatData = results;
|
|
3557
|
+
this.logger.info(this.phoneNumber, `Self chat processed - Score: ${selfChatData.interactionScore}`);
|
|
3650
3558
|
}
|
|
3651
|
-
catch (
|
|
3652
|
-
this.logger.warn(this.phoneNumber,
|
|
3559
|
+
catch (e) {
|
|
3560
|
+
this.logger.warn(this.phoneNumber, 'Error processing self chat:', e);
|
|
3653
3561
|
}
|
|
3654
|
-
const
|
|
3562
|
+
const topCandidates = candidateChats.slice(0, clampedLimit * 2);
|
|
3563
|
+
this.logger.info(this.phoneNumber, `Analyzing top ${topCandidates.length} candidates in depth...`);
|
|
3655
3564
|
const chatStats = [];
|
|
3656
|
-
const
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
break;
|
|
3663
|
-
}
|
|
3664
|
-
this.logger.info(this.phoneNumber, `Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(privateChats.length / batchSize)} (${chatStats.length} candidates so far)`);
|
|
3665
|
-
const batch = privateChats.slice(i, i + batchSize);
|
|
3666
|
-
const batchResults = await Promise.all(batch.map(async (dialog) => {
|
|
3667
|
-
const processingStart = Date.now();
|
|
3668
|
-
const chatId = dialog.entity.id.toString();
|
|
3669
|
-
const user = dialog.entity;
|
|
3670
|
-
const lastMessageDate = dialog.message?.date ? dialog.message.date * 1000 : now;
|
|
3671
|
-
const daysSinceLastActivity = (now - lastMessageDate) / (1000 * 60 * 60 * 24);
|
|
3672
|
-
const unreadCount = dialog.unreadCount || 0;
|
|
3673
|
-
const isPinned = dialog.pinned || false;
|
|
3674
|
-
this.logger.info(this.phoneNumber, `Processing chat ${chatId} (${user.firstName || 'Unknown'}) - Last activity: ${daysSinceLastActivity.toFixed(1)} days ago, Unread: ${unreadCount}`);
|
|
3565
|
+
const batchSize = 5;
|
|
3566
|
+
for (let i = 0; i < topCandidates.length; i += batchSize) {
|
|
3567
|
+
const batch = topCandidates.slice(i, i + batchSize);
|
|
3568
|
+
const batchResults = await Promise.all(batch.map(async (candidate) => {
|
|
3569
|
+
const user = candidate.dialog.entity;
|
|
3570
|
+
const chatId = user.id.toString();
|
|
3675
3571
|
try {
|
|
3676
|
-
|
|
3677
|
-
let messageCount = 0;
|
|
3678
|
-
let ownMessageCount = 0;
|
|
3679
|
-
let replyChainCount = 0;
|
|
3680
|
-
let recentMediaCount = 0;
|
|
3681
|
-
const messageDates = [];
|
|
3682
|
-
const mediaStats = { photos: 0, videos: 0 };
|
|
3683
|
-
for await (const message of this.client.iterMessages(chatId, {
|
|
3684
|
-
limit: 400,
|
|
3685
|
-
reverse: false
|
|
3686
|
-
})) {
|
|
3687
|
-
messageCount++;
|
|
3688
|
-
if (message.date) {
|
|
3689
|
-
messageDates.push(message.date * 1000);
|
|
3690
|
-
}
|
|
3691
|
-
if (message.out) {
|
|
3692
|
-
ownMessageCount++;
|
|
3693
|
-
}
|
|
3694
|
-
if (message.replyTo) {
|
|
3695
|
-
replyChainCount++;
|
|
3696
|
-
}
|
|
3697
|
-
if (message.media && !(message.media instanceof telegram_1.Api.MessageMediaEmpty)) {
|
|
3698
|
-
recentMediaCount++;
|
|
3699
|
-
if (message.media instanceof telegram_1.Api.MessageMediaPhoto) {
|
|
3700
|
-
mediaStats.photos++;
|
|
3701
|
-
}
|
|
3702
|
-
else if (message.media instanceof telegram_1.Api.MessageMediaDocument) {
|
|
3703
|
-
const document = message.media.document;
|
|
3704
|
-
if (document instanceof telegram_1.Api.Document) {
|
|
3705
|
-
const isVideo = document.attributes.some(attr => attr instanceof telegram_1.Api.DocumentAttributeVideo);
|
|
3706
|
-
if (isVideo) {
|
|
3707
|
-
mediaStats.videos++;
|
|
3708
|
-
}
|
|
3709
|
-
}
|
|
3710
|
-
}
|
|
3711
|
-
}
|
|
3712
|
-
}
|
|
3713
|
-
let totalMessages = messageCount;
|
|
3714
|
-
try {
|
|
3715
|
-
const firstBatch = await this.client.getMessages(chatId, { limit: 1 });
|
|
3716
|
-
if (firstBatch.total) {
|
|
3717
|
-
totalMessages = firstBatch.total;
|
|
3718
|
-
}
|
|
3719
|
-
}
|
|
3720
|
-
catch (totalError) {
|
|
3721
|
-
}
|
|
3722
|
-
return { messageCount, ownMessageCount, replyChainCount, messageDates, mediaStats, totalMessages };
|
|
3723
|
-
})();
|
|
3724
|
-
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), CHAT_TIMEOUT_MS));
|
|
3725
|
-
const result = await Promise.race([chatProcessingPromise, timeoutPromise]);
|
|
3726
|
-
if (result === null) {
|
|
3727
|
-
this.logger.warn(this.phoneNumber, `Chat ${chatId} processing timed out after ${CHAT_TIMEOUT_MS}ms - skipping`);
|
|
3728
|
-
return null;
|
|
3729
|
-
}
|
|
3730
|
-
const { messageCount, ownMessageCount, replyChainCount, messageDates, mediaStats, totalMessages } = result;
|
|
3731
|
-
const callStats = {
|
|
3732
|
-
total: 0,
|
|
3733
|
-
incoming: { total: 0, audio: 0, video: 0 },
|
|
3734
|
-
outgoing: { total: 0, audio: 0, video: 0 }
|
|
3735
|
-
};
|
|
3736
|
-
const userCalls = callLogs[chatId];
|
|
3737
|
-
if (userCalls) {
|
|
3738
|
-
callStats.total = userCalls.totalCalls || 0;
|
|
3739
|
-
callStats.incoming.total = userCalls.incoming || 0;
|
|
3740
|
-
callStats.outgoing.total = userCalls.outgoing || 0;
|
|
3741
|
-
callStats.incoming.video = userCalls.video || 0;
|
|
3742
|
-
callStats.incoming.audio = callStats.incoming.total - callStats.incoming.video;
|
|
3743
|
-
callStats.outgoing.audio = callStats.outgoing.total;
|
|
3744
|
-
}
|
|
3745
|
-
const baseScore = (callStats.incoming.total * weights.incomingCall +
|
|
3746
|
-
callStats.outgoing.total * weights.outgoingCall +
|
|
3747
|
-
(callStats.incoming.video + (callStats.outgoing.total > 0 ? 1 : 0)) * weights.videoCall +
|
|
3748
|
-
mediaStats.videos * weights.sharedVideo +
|
|
3749
|
-
mediaStats.photos * weights.sharedPhoto +
|
|
3750
|
-
totalMessages * weights.textMessage +
|
|
3751
|
-
unreadCount * weights.unreadMessages);
|
|
3752
|
-
const interactionScore = baseScore;
|
|
3753
|
-
const finalScore = isPinned ? interactionScore * 1.2 : interactionScore;
|
|
3754
|
-
let engagementLevel;
|
|
3755
|
-
if (daysSinceLastActivity <= ACTIVITY_WINDOWS.recent) {
|
|
3756
|
-
engagementLevel = 'recent';
|
|
3757
|
-
}
|
|
3758
|
-
else if (daysSinceLastActivity <= ACTIVITY_WINDOWS.active) {
|
|
3759
|
-
engagementLevel = 'active';
|
|
3760
|
-
}
|
|
3761
|
-
else {
|
|
3762
|
-
engagementLevel = 'dormant';
|
|
3763
|
-
}
|
|
3764
|
-
const totalActivity = callStats.incoming.video * weights.videoCall +
|
|
3765
|
-
callStats.incoming.total * weights.incomingCall +
|
|
3766
|
-
callStats.outgoing.total * weights.outgoingCall +
|
|
3767
|
-
(mediaStats.videos * weights.sharedVideo + mediaStats.photos * weights.sharedPhoto) +
|
|
3768
|
-
totalMessages * weights.textMessage;
|
|
3769
|
-
const activityBreakdown = totalActivity > 0 ? {
|
|
3770
|
-
videoCalls: Math.round((callStats.incoming.video * weights.videoCall / totalActivity) * 100),
|
|
3771
|
-
audioCalls: Math.round(((callStats.incoming.total + callStats.outgoing.total) * weights.incomingCall / totalActivity) * 100),
|
|
3772
|
-
mediaSharing: Math.round(((mediaStats.videos * weights.sharedVideo + mediaStats.photos * weights.sharedPhoto) / totalActivity) * 100),
|
|
3773
|
-
textMessages: Math.round((totalMessages * weights.textMessage / totalActivity) * 100)
|
|
3774
|
-
} : {
|
|
3775
|
-
videoCalls: 0,
|
|
3776
|
-
audioCalls: 0,
|
|
3777
|
-
mediaSharing: 0,
|
|
3778
|
-
textMessages: 100
|
|
3779
|
-
};
|
|
3780
|
-
const processingTime = Date.now() - processingStart;
|
|
3781
|
-
this.logger.info(this.phoneNumber, `Finished processing chat ${chatId} in ${processingTime}ms - Score: ${finalScore.toFixed(2)}, Level: ${engagementLevel}, Days: ${daysSinceLastActivity.toFixed(1)}`);
|
|
3782
|
-
return {
|
|
3783
|
-
chatId,
|
|
3784
|
-
username: user.username,
|
|
3785
|
-
firstName: user.firstName,
|
|
3786
|
-
lastName: user.lastName,
|
|
3787
|
-
totalMessages,
|
|
3788
|
-
interactionScore: Math.round(finalScore * 100) / 100,
|
|
3789
|
-
engagementLevel,
|
|
3790
|
-
lastActivityDays: Math.round(daysSinceLastActivity * 10) / 10,
|
|
3791
|
-
calls: {
|
|
3792
|
-
total: callStats.total,
|
|
3793
|
-
incoming: callStats.incoming,
|
|
3794
|
-
outgoing: callStats.outgoing
|
|
3795
|
-
},
|
|
3796
|
-
media: mediaStats,
|
|
3797
|
-
activityBreakdown
|
|
3798
|
-
};
|
|
3572
|
+
return await this.analyzeChatEngagement(chatId, user, 150, callLogs[chatId], weights, now, ACTIVITY_WINDOWS, candidate.dialog);
|
|
3799
3573
|
}
|
|
3800
3574
|
catch (error) {
|
|
3801
|
-
|
|
3802
|
-
if (error.message === 'Chat processing timeout') {
|
|
3803
|
-
this.logger.warn(this.phoneNumber, `Chat ${chatId} timed out after ${processingTime}ms, skipping...`);
|
|
3804
|
-
}
|
|
3805
|
-
else {
|
|
3806
|
-
this.logger.error(this.phoneNumber, `Error processing chat ${chatId} after ${processingTime}ms:`, error);
|
|
3807
|
-
}
|
|
3575
|
+
this.logger.warn(this.phoneNumber, `Error analyzing chat ${chatId}:`, error.message);
|
|
3808
3576
|
return null;
|
|
3809
3577
|
}
|
|
3810
3578
|
}));
|
|
3811
3579
|
chatStats.push(...batchResults.filter(Boolean));
|
|
3812
|
-
if (i + batchSize < privateChats.length && chatStats.length < targetCandidates) {
|
|
3813
|
-
await (0, Helpers_1.sleep)(BATCH_DELAY_MS);
|
|
3814
|
-
}
|
|
3815
3580
|
}
|
|
3816
3581
|
let topChats = chatStats
|
|
3817
|
-
.sort((a, b) =>
|
|
3818
|
-
if (Math.abs(b.interactionScore - a.interactionScore) > 0.1) {
|
|
3819
|
-
return b.interactionScore - a.interactionScore;
|
|
3820
|
-
}
|
|
3821
|
-
const levelOrder = { recent: 3, active: 2, dormant: 1 };
|
|
3822
|
-
if (levelOrder[b.engagementLevel] !== levelOrder[a.engagementLevel]) {
|
|
3823
|
-
return levelOrder[b.engagementLevel] - levelOrder[a.engagementLevel];
|
|
3824
|
-
}
|
|
3825
|
-
return a.lastActivityDays - b.lastActivityDays;
|
|
3826
|
-
})
|
|
3582
|
+
.sort((a, b) => b.interactionScore - a.interactionScore)
|
|
3827
3583
|
.slice(0, clampedLimit);
|
|
3828
3584
|
if (selfChatData) {
|
|
3829
|
-
topChats = topChats.filter(chat => chat.chatId !== 'me');
|
|
3585
|
+
topChats = topChats.filter(chat => chat.chatId !== 'me' && chat.chatId !== me.id.toString());
|
|
3830
3586
|
topChats.unshift(selfChatData);
|
|
3831
|
-
if (topChats.length > clampedLimit)
|
|
3587
|
+
if (topChats.length > clampedLimit)
|
|
3832
3588
|
topChats = topChats.slice(0, clampedLimit);
|
|
3833
|
-
}
|
|
3834
3589
|
}
|
|
3835
3590
|
const totalTime = Date.now() - startTime;
|
|
3836
|
-
this.logger.info(this.phoneNumber, `getTopPrivateChats completed in ${totalTime}ms. Found ${topChats.length}
|
|
3837
|
-
topChats.forEach((chat, index) => {
|
|
3838
|
-
this.logger.info(this.phoneNumber, `Top ${index + 1}: ${chat.firstName} (${chat.username || 'no username'}) - Score: ${chat.interactionScore}, Level: ${chat.engagementLevel}, Days: ${chat.lastActivityDays}`);
|
|
3839
|
-
});
|
|
3591
|
+
this.logger.info(this.phoneNumber, `getTopPrivateChats optimized completed in ${totalTime}ms. Found ${topChats.length} results.`);
|
|
3840
3592
|
return topChats;
|
|
3841
3593
|
}
|
|
3594
|
+
async analyzeChatEngagement(chatId, user, messageLimit, callStats, weights, now, windows, dialog) {
|
|
3595
|
+
let messageCount = 0;
|
|
3596
|
+
let ownMessageCount = 0;
|
|
3597
|
+
let replyChainCount = 0;
|
|
3598
|
+
const mediaStats = { photos: 0, videos: 0 };
|
|
3599
|
+
const messageDates = [];
|
|
3600
|
+
for await (const message of this.client.iterMessages(chatId, {
|
|
3601
|
+
limit: messageLimit,
|
|
3602
|
+
reverse: false
|
|
3603
|
+
})) {
|
|
3604
|
+
messageCount++;
|
|
3605
|
+
if (message.date)
|
|
3606
|
+
messageDates.push(message.date * 1000);
|
|
3607
|
+
if (message.out)
|
|
3608
|
+
ownMessageCount++;
|
|
3609
|
+
if (message.replyTo)
|
|
3610
|
+
replyChainCount++;
|
|
3611
|
+
if (message.media && !(message.media instanceof telegram_1.Api.MessageMediaEmpty)) {
|
|
3612
|
+
if (message.media instanceof telegram_1.Api.MessageMediaPhoto) {
|
|
3613
|
+
mediaStats.photos++;
|
|
3614
|
+
}
|
|
3615
|
+
else if (message.media instanceof telegram_1.Api.MessageMediaDocument) {
|
|
3616
|
+
const doc = message.media.document;
|
|
3617
|
+
if (doc instanceof telegram_1.Api.Document && doc.attributes.some(a => a instanceof telegram_1.Api.DocumentAttributeVideo)) {
|
|
3618
|
+
mediaStats.videos++;
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
const lastMessageDate = messageDates.length > 0 ? Math.max(...messageDates) : (dialog?.message?.date ? dialog.message.date * 1000 : now);
|
|
3624
|
+
const daysSinceLastActivity = (now - lastMessageDate) / (1000 * 60 * 60 * 24);
|
|
3625
|
+
const cCalls = callStats || { total: 0, incoming: 0, outgoing: 0, video: 0 };
|
|
3626
|
+
const baseScore = (cCalls.incoming * weights.incomingCall +
|
|
3627
|
+
cCalls.outgoing * weights.outgoingCall +
|
|
3628
|
+
cCalls.video * weights.videoCall +
|
|
3629
|
+
mediaStats.videos * weights.sharedVideo +
|
|
3630
|
+
mediaStats.photos * weights.sharedPhoto +
|
|
3631
|
+
messageCount * weights.textMessage);
|
|
3632
|
+
const mutualEngagementMultiplier = messageCount > 0 ? Math.min(1.5, (ownMessageCount / messageCount) * 2) : 1.0;
|
|
3633
|
+
const interactionScore = baseScore * mutualEngagementMultiplier * (dialog?.pinned ? 1.2 : 1.0);
|
|
3634
|
+
let engagementLevel;
|
|
3635
|
+
if (daysSinceLastActivity <= windows.recent)
|
|
3636
|
+
engagementLevel = 'recent';
|
|
3637
|
+
else if (daysSinceLastActivity <= windows.active)
|
|
3638
|
+
engagementLevel = 'active';
|
|
3639
|
+
else
|
|
3640
|
+
engagementLevel = 'dormant';
|
|
3641
|
+
const totalActivity = Math.max(1, baseScore);
|
|
3642
|
+
const activityBreakdown = {
|
|
3643
|
+
videoCalls: Math.round((cCalls.video * weights.videoCall / totalActivity) * 100),
|
|
3644
|
+
audioCalls: Math.round(((cCalls.total - cCalls.video) * (weights.incomingCall || weights.outgoingCall) / totalActivity) * 100),
|
|
3645
|
+
mediaSharing: Math.round(((mediaStats.videos * weights.sharedVideo + mediaStats.photos * weights.sharedPhoto) / totalActivity) * 100),
|
|
3646
|
+
textMessages: Math.round((messageCount * weights.textMessage / totalActivity) * 100)
|
|
3647
|
+
};
|
|
3648
|
+
return {
|
|
3649
|
+
chatId: chatId === 'me' ? 'me' : user.id.toString(),
|
|
3650
|
+
username: user.username,
|
|
3651
|
+
firstName: user.firstName || (chatId === 'me' ? 'Saved Messages' : ''),
|
|
3652
|
+
lastName: user.lastName,
|
|
3653
|
+
totalMessages: messageCount,
|
|
3654
|
+
interactionScore: Math.round(interactionScore * 100) / 100,
|
|
3655
|
+
engagementLevel,
|
|
3656
|
+
lastActivityDays: Math.round(daysSinceLastActivity * 10) / 10,
|
|
3657
|
+
calls: {
|
|
3658
|
+
total: cCalls.total || 0,
|
|
3659
|
+
incoming: { total: cCalls.incoming || 0, audio: Math.max(0, cCalls.incoming - cCalls.video) || 0, video: cCalls.video || 0 },
|
|
3660
|
+
outgoing: { total: cCalls.outgoing || 0, audio: cCalls.outgoing || 0, video: 0 }
|
|
3661
|
+
},
|
|
3662
|
+
media: mediaStats,
|
|
3663
|
+
activityBreakdown
|
|
3664
|
+
};
|
|
3665
|
+
}
|
|
3842
3666
|
async createGroupOrChannel(options) {
|
|
3843
3667
|
if (!this.client)
|
|
3844
3668
|
throw new Error('Client not initialized');
|