From 04b6c71a39d0436b3eb3bd42cecab87ca84b8cba Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Sat, 12 Jun 2021 20:04:16 +0300 Subject: [PATCH] Group call service messages --- src/helpers/date.ts | 10 ++ src/helpers/formatCallDuration.ts | 25 ++++ src/helpers/formatDuration.ts | 38 ++++++ src/lang.ts | 36 ++++++ src/lib/appManagers/appMessagesManager.ts | 146 ++++++++++++++++------ src/lib/langPack.ts | 13 +- 6 files changed, 229 insertions(+), 39 deletions(-) create mode 100644 src/helpers/formatCallDuration.ts create mode 100644 src/helpers/formatDuration.ts diff --git a/src/helpers/date.ts b/src/helpers/date.ts index d4ac285b..cbcef992 100644 --- a/src/helpers/date.ts +++ b/src/helpers/date.ts @@ -64,6 +64,16 @@ export function formatDateAccordingToTodayNew(time: Date) { }).element; } +export function formatTime(date: Date) { + return new I18n.IntlDateElement({ + date, + options: { + hour: '2-digit', + minute: '2-digit' + } + }).element; +} + MOUNT_CLASS_TO && (MOUNT_CLASS_TO.formatDateAccordingToTodayNew = formatDateAccordingToTodayNew); export const getFullDate = (date: Date, options: Partial<{ diff --git a/src/helpers/formatCallDuration.ts b/src/helpers/formatCallDuration.ts new file mode 100644 index 00000000..d510ab6e --- /dev/null +++ b/src/helpers/formatCallDuration.ts @@ -0,0 +1,25 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import { i18n, join, LangPackKey } from "../lib/langPack"; +import formatDuration, { DurationType } from "./formatDuration"; + +const CALL_DURATION_LANG_KEYS: {[type in DurationType]: LangPackKey} = { + s: 'Seconds', + m: 'Minutes', + h: 'Hours', + d: 'Days', + w: 'Weeks' +}; +export default function formatCallDuration(duration: number) { + const a = formatDuration(duration, 2); + const elements = a.map(d => i18n(CALL_DURATION_LANG_KEYS[d.type], [d.duration])); + + const fragment = document.createElement('span'); + fragment.append(...join(elements, false)); + + return fragment; +} diff --git a/src/helpers/formatDuration.ts b/src/helpers/formatDuration.ts new file mode 100644 index 00000000..79554a4e --- /dev/null +++ b/src/helpers/formatDuration.ts @@ -0,0 +1,38 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +export type DurationType = 's' | 'm' | 'h' | 'd' | 'w'; +export default function formatDuration(duration: number, showLast = 2) { + if(!duration) { + duration = 1; + } + + let d: {duration: number, type: DurationType}[] = []; + const p = [ + {m: 1, t: 's'}, + {m: 60, t: 'm'}, + {m: 60, t: 'h'}, + {m: 24, t: 'd'}, + {m: 7, t: 'w'} + ] as Array<{m?: number, t: DurationType}> + const s = 1; + let t = s; + p.forEach((o, idx) => { + t *= o.m; + + if(duration < t) { + return; + } + + const modulus = p[idx === (p.length - 1) ? idx : idx + 1].m; + d.push({ + duration: (duration / t % modulus | 0), + type: o.t + }); + }); + + return d.slice(-showLast).reverse(); +} diff --git a/src/lang.ts b/src/lang.ts index ca8ac449..2ed68935 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -441,6 +441,34 @@ const lang = { "Emoji": "Emoji", "AddContactTitle": "Add Contact", "HiddenName": "Deleted Account", + "ActionGroupCallStarted": "un1 started a voice chat", + "ActionGroupCallStartedByYou": "You started a voice chat", + "ActionGroupCallJustStarted": "Voice chat started", + "ActionGroupCallInvited": "un1 invited un2 to the voice chat", + "ActionGroupCallYouInvited": "You invited un2 to the voice chat", + "ActionGroupCallInvitedYou": "un1 invited you to the voice chat", + "Seconds": { + "one_value": "%1$d second", + "other_value": "%1$d seconds" + }, + "Minutes": { + "one_value": "%1$d minute", + "other_value": "%1$d minutes" + }, + "Hours": { + "one_value": "%1$d hour", + "other_value": "%1$d hours" + }, + "Days": { + "one_value": "%1$d day", + "other_value": "%1$d days" + }, + "Weeks": { + "one_value": "%1$d week", + "other_value": "%1$d weeks" + }, + "TodayAtFormattedWithToday": "today at %1$s", + "formatDateAtTime": "%1$s at %2$s", // * macos "AccountSettings.Filters": "Chat Folders", @@ -465,6 +493,10 @@ const lang = { "Chat.Service.Channel.RemovedPhoto": "Channel photo removed", "Chat.Service.Channel.UpdatedVideo": "Channel video updated", "Chat.Service.BotPermissionAllowed": "You allowed this bot to message you when you logged in on %@", + "Chat.Service.VoiceChatFinished": "%1$@ ended the voice chat (%2$@)", + "Chat.Service.VoiceChatFinishedYou": "You ended the voice chat (%@)", + //"Chat.Service.VoiceChatScheduled": "%1$@ scheduled a [voice chat](open) for %2$@", + //"Chat.Service.VoiceChatScheduledYou": "You scheduled a [voice chat](open) for %1$@", "Chat.Poll.Unvote": "Retract Vote", "Chat.Poll.Stop": "Stop Poll", "Chat.Poll.ViewResults": "View Results", @@ -516,6 +548,8 @@ const lang = { "ChatList.Service.Call.outgoing": "Outgoing Call (%@)", "ChatList.Service.Call.Cancelled": "Cancelled Call", "ChatList.Service.Call.Missed": "Missed Call", + "ChatList.Service.VoiceChatScheduled": "%1$@ scheduled a voice chat for %2$@", + "ChatList.Service.VoiceChatScheduledYou": "You scheduled a voice chat for %2$@", "ChatList.Filter.Header": "Create folders for different groups of chats and quickly switch between them.", "ChatList.Filter.NewTitle": "Create Folder", "ChatList.Filter.List.Title": "Chat Folders", @@ -677,11 +711,13 @@ const lang = { "GeneralSettings.BigEmoji": "Large Emoji", "GeneralSettings.EmojiPrediction": "Suggest Emoji", "GroupPermission.Delete": "Delete Exception", + "ScheduleController.at": "at", "Schedule.SendToday": "Send today at %@", "Schedule.SendDate": "Send on %@ at %@", //"Schedule.SendWhenOnline": "Send When Online", "Stickers.Recent": "Recent", //"Stickers.Favorite": "Favorite", + "Time.TomorrowAt": "tomorrow at %@", "TwoStepAuth.SetPasswordHelp": "You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS.", "TwoStepAuth.GenericHelp": "You have enabled Two-Step verification.\nYou'll need the password you set up here to log in to your Telegram account.", "TwoStepAuth.ChangePassword": "Change Password", diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 98c24c35..531eadcd 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -12,7 +12,7 @@ import { LazyLoadQueueBase } from "../../components/lazyLoadQueue"; import ProgressivePreloader from "../../components/preloader"; import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; -import { tsNow } from "../../helpers/date"; +import { formatTime, tsNow } from "../../helpers/date"; import { createPosterForVideo } from "../../helpers/files"; import { copy, getObjectKeysAndSort } from "../../helpers/object"; import { randomLong } from "../../helpers/random"; @@ -52,6 +52,7 @@ import { forEachReverse } from "../../helpers/array"; import htmlToDocumentFragment from "../../helpers/dom/htmlToDocumentFragment"; import htmlToSpan from "../../helpers/dom/htmlToSpan"; import { REPLIES_PEER_ID } from "../mtproto/mtproto_config"; +import formatCallDuration from "../../helpers/formatCallDuration"; //console.trace('include'); // TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет @@ -2311,69 +2312,88 @@ export class AppMessagesManager { } if(message.action) { + const action = message.action; let migrateFrom: number; let migrateTo: number; const suffix = message.fromId === appUsersManager.getSelf().id ? 'You' : ''; - switch(message.action._) { + switch(action._) { //case 'messageActionChannelEditPhoto': case 'messageActionChatEditPhoto': - message.action.photo = appPhotosManager.savePhoto(message.action.photo, mediaContext); - if(message.action.photo.video_sizes) { - message.action._ = isBroadcast ? 'messageActionChannelEditVideo' : 'messageActionChatEditVideo'; + action.photo = appPhotosManager.savePhoto(action.photo, mediaContext); + if(action.photo.video_sizes) { + action._ = isBroadcast ? 'messageActionChannelEditVideo' : 'messageActionChatEditVideo'; } else { if(isBroadcast) { // ! messageActionChannelEditPhoto не существует в принципе, это используется для перевода. - message.action._ = 'messageActionChannelEditPhoto'; + action._ = 'messageActionChannelEditPhoto'; } } break; + + case 'messageActionGroupCall': { + //assumeType(action); + + let type: string; + if(action.duration === undefined) { + type = 'started'; + if(message.peerId !== message.fromId) { + type += '_by' + suffix; + } + } else { + type = 'ended_by' + suffix; + } + + action.type = type; + + break; + } case 'messageActionChatEditTitle': /* if(options.isNew) { const chat = appChatsManager.getChat(-peerId); - chat.title = message.action.title; + chat.title = action.title; appChatsManager.saveApiChat(chat, true); } */ if(isBroadcast) { - message.action._ = 'messageActionChannelEditTitle'; + action._ = 'messageActionChannelEditTitle'; } break; case 'messageActionChatDeletePhoto': if(isBroadcast) { - message.action._ = 'messageActionChannelDeletePhoto'; + action._ = 'messageActionChannelDeletePhoto'; } break; case 'messageActionChatAddUser': - if(message.action.users.length === 1) { - message.action.user_id = message.action.users[0]; - if(message.fromId === message.action.user_id) { + if(action.users.length === 1) { + action.user_id = action.users[0]; + if(message.fromId === action.user_id) { if(isChannel) { - message.action._ = 'messageActionChatJoined' + suffix; + action._ = 'messageActionChatJoined' + suffix; } else { - message.action._ = 'messageActionChatReturn' + suffix; + action._ = 'messageActionChatReturn' + suffix; } } - } else if(message.action.users.length > 1) { - message.action._ = 'messageActionChatAddUsers'; + } else if(action.users.length > 1) { + action._ = 'messageActionChatAddUsers'; } break; case 'messageActionChatDeleteUser': - if(message.fromId === message.action.user_id) { - message.action._ = 'messageActionChatLeave' + suffix; + if(message.fromId === action.user_id) { + action._ = 'messageActionChatLeave' + suffix; } break; case 'messageActionChannelMigrateFrom': - migrateFrom = -message.action.chat_id; + migrateFrom = -action.chat_id; migrateTo = -channelId; break case 'messageActionChatMigrateTo': migrateFrom = -channelId; - migrateTo = -message.action.channel_id; + migrateTo = -action.channel_id; break; case 'messageActionHistoryClear': @@ -2384,11 +2404,11 @@ export class AppMessagesManager { break; case 'messageActionPhoneCall': - message.action.type = + action.type = (message.pFlags.out ? 'out_' : 'in_') + ( - message.action.reason._ === 'phoneCallDiscardReasonMissed' || - message.action.reason._ === 'phoneCallDiscardReasonBusy' + action.reason._ === 'phoneCallDiscardReasonMissed' || + action.reason._ === 'phoneCallDiscardReasonBusy' ? 'missed' : 'ok' ); @@ -2641,18 +2661,73 @@ export class AppMessagesManager { }; switch(action._) { - case "messageActionPhoneCall": { + case 'messageActionPhoneCall': { _ += '.' + (action as any).type; - const duration = action.duration || 1; - const d: string[] = []; - - d.push(duration % 60 + ' s'); - if(duration >= 60) d.push((duration / 60 | 0) + ' min'); - //if(duration >= 3600) d.push((duration / 3600 | 0) + ' h'); + args = [formatCallDuration(action.duration)]; + break; + } + + case 'messageActionGroupCall': { + _ += '.' + (action as any).type; + + args = []; + if(!_.endsWith('You')) { + args.push(getNameDivHTML(message.fromId, plain)); + } + + args.push(formatCallDuration(action.duration)); + break; + } + + case 'messageActionInviteToGroupCall': { + const peerIds = [message.fromId, action.users[0]]; + let a = 'ActionGroupCall'; + const myId = appUsersManager.getSelf().id; + if(peerIds[0] === myId) a += 'You'; + a += 'Invited'; + if(peerIds[1] === myId) a += 'You'; + peerIds.findAndSplice(peerId => peerId === myId); + + langPackKey = a as LangPackKey; + args = peerIds.map(peerId => getNameDivHTML(peerId, plain)); + break; + } + + case 'messageActionGroupCallScheduled': { + const today = new Date(); + const date = new Date(action.schedule_date * 1000); + const daysToStart = (date.getTime() - today.getTime()) / 86400e3; + const tomorrowDate = new Date(today); + tomorrowDate.setDate(tomorrowDate.getDate() + 1); + + langPackKey = 'ChatList.Service.VoiceChatScheduled'; + const myId = appUsersManager.getSelf().id; + if(message.fromId === myId) { + langPackKey += 'You'; + } + + let k: LangPackKey, _args: any[] = []; + if(daysToStart < 1 && date.getDate() === today.getDate()) { + k = 'TodayAtFormattedWithToday'; + } else if(daysToStart < 2 && date.getDate() === tomorrowDate.getDate()) { + k = 'Time.TomorrowAt'; + } else { + k = 'formatDateAtTime'; + _args.push(new I18n.IntlDateElement({ + date, + options: { + day: '2-digit', + month: '2-digit', + year: '2-digit' + } + }).element); + } + + _args.push(formatTime(date)); + const t = i18n(k, _args); + args = [t]; - langPackKey = langPack[_]; - args = [d.reverse().join(' ')]; break; } @@ -2668,15 +2743,12 @@ export class AppMessagesManager { case 'messageActionChatJoinedByLink': case 'messageActionChannelEditVideo': case 'messageActionChannelDeletePhoto': { - langPackKey = langPack[_]; args = [getNameDivHTML(message.fromId, plain)]; break; } case 'messageActionChannelEditTitle': case 'messageActionChatEditTitle': { - langPackKey = langPack[_]; - args = []; if(action._ === 'messageActionChatEditTitle') { args.push(getNameDivHTML(message.fromId, plain)); @@ -2692,7 +2764,6 @@ export class AppMessagesManager { const users: number[] = (action as MessageAction.messageActionChatAddUser).users || [(action as MessageAction.messageActionChatDeleteUser).user_id]; - langPackKey = langPack[_]; args = [getNameDivHTML(message.fromId, plain)]; if(users.length > 1) { @@ -2725,8 +2796,7 @@ export class AppMessagesManager { }); const node = htmlToSpan(anchorHTML); - - langPackKey = langPack[_]; + args = [node]; break; } diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts index 7cd4d304..fd9e305e 100644 --- a/src/lib/langPack.ts +++ b/src/lib/langPack.ts @@ -47,6 +47,12 @@ export const langPack: {[actionType: string]: LangPackKey} = { "messageActionPhoneCall.in_missed": "ChatList.Service.Call.Missed", "messageActionPhoneCall.out_missed": "ChatList.Service.Call.Cancelled", + "messageActionGroupCall.started": "ActionGroupCallJustStarted", + "messageActionGroupCall.started_by": "ActionGroupCallStarted", + "messageActionGroupCall.started_byYou": "ActionGroupCallStartedByYou", + "messageActionGroupCall.ended_by": "Chat.Service.VoiceChatFinished", + "messageActionGroupCall.ended_byYou": "Chat.Service.VoiceChatFinishedYou", + "messageActionBotAllowed": "Chat.Service.BotPermissionAllowed" }; @@ -198,7 +204,12 @@ namespace I18n { return; } - pluralRules = new Intl.PluralRules(langPack.lang_code); + try { + pluralRules = new Intl.PluralRules(langPack.lang_code); + } catch(err) { + console.error('pluralRules error', err); + pluralRules = new Intl.PluralRules(langPack.lang_code.split('-', 1)[0]); + } strings.clear();