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.
@@ -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
- filteredResults.totalCalls++;
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
- filteredResults.outgoing++;
1172
+ stats.outgoing++;
1168
1173
  }
1169
1174
  else {
1170
- filteredResults.incoming++;
1175
+ stats.incoming++;
1171
1176
  }
1172
1177
  if (logAction.video) {
1173
- filteredResults.video++;
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: 5,
3502
+ videoCall: 2,
3500
3503
  incomingCall: 4,
3501
- outgoingCall: 3,
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
- const getTimeDecayMultiplier = (daysSinceActivity) => {
3514
- if (daysSinceActivity <= 1)
3515
- return 1.5;
3516
- if (daysSinceActivity <= 7)
3517
- return 1.2;
3518
- if (daysSinceActivity <= 30)
3519
- return 1.0;
3520
- if (daysSinceActivity <= 90)
3521
- return 0.7;
3522
- return 0.3;
3523
- };
3524
- this.logger.info(this.phoneNumber, 'Fetching dialogs with metadata...');
3525
- const dialogs = [];
3526
- for await (const dialog of this.client.iterDialogs({
3527
- limit: 200
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
- if (userId === "777000" || userId === "42777")
3541
- return false;
3542
- return true;
3537
+ return userId !== "777000" && userId !== "42777";
3543
3538
  })
3544
- .sort((a, b) => {
3545
- const dateA = a.message?.date || 0;
3546
- const dateB = b.message?.date || 0;
3547
- return dateB - dateA;
3548
- });
3549
- this.logger.info(this.phoneNumber, `Found ${privateChats.length} valid private chats after smart filtering`);
3550
- const callLogs = await this.getCallLogsInternal();
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.logger.info(this.phoneNumber, `Processing self chat (me) with chatId: ${selfChatId}`);
3556
- let messageCount = 0;
3557
- let ownMessageCount = 0;
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 (selfChatError) {
3652
- this.logger.warn(this.phoneNumber, `Error processing self chat, will continue without it:`, selfChatError);
3559
+ catch (e) {
3560
+ this.logger.warn(this.phoneNumber, 'Error processing self chat:', e);
3653
3561
  }
3654
- const batchSize = 10;
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 CHAT_TIMEOUT_MS = 30000;
3657
- const BATCH_DELAY_MS = 100;
3658
- const targetCandidates = clampedLimit * 2;
3659
- for (let i = 0; i < privateChats.length; i += batchSize) {
3660
- if (chatStats.length >= targetCandidates) {
3661
- this.logger.info(this.phoneNumber, `Early termination: Found ${chatStats.length} candidates (target: ${targetCandidates})`);
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
- const chatProcessingPromise = (async () => {
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
- const processingTime = Date.now() - processingStart;
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} top chats (requested: ${clampedLimit})`);
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');