From 9bfd2d569c1ee71651c54e11640d0b2c83b7454a Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Fri, 4 Jul 2025 14:12:22 +0200 Subject: [PATCH] Layer 205: Introduce Checklists (#6010) Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com> --- src/api/gramjs/apiBuilders/appConfig.ts | 9 + src/api/gramjs/apiBuilders/messageActions.ts | 20 + src/api/gramjs/apiBuilders/messageContent.ts | 45 +- src/api/gramjs/apiBuilders/messages.ts | 12 + src/api/gramjs/gramjsBuilders/index.ts | 23 ++ src/api/gramjs/methods/messages.ts | 136 ++++++- src/api/gramjs/methods/users.ts | 4 +- src/api/types/messageActions.ts | 15 +- src/api/types/messages.ts | 29 ++ src/api/types/misc.ts | 3 + src/assets/localization/fallback.strings | 39 ++ src/bundles/extra.ts | 1 + src/components/auth/Auth.scss | 2 +- src/components/common/Composer.tsx | 54 ++- .../main/premium/PremiumFeatureModal.tsx | 21 +- .../main/premium/PremiumMainModal.tsx | 15 +- src/components/middle/composer/AttachMenu.tsx | 7 + src/components/middle/composer/PollModal.scss | 6 +- .../middle/composer/ToDoListModal.async.tsx | 16 + .../middle/composer/ToDoListModal.scss | 105 +++++ .../middle/composer/ToDoListModal.tsx | 383 ++++++++++++++++++ .../middle/message/ActionMessage.module.scss | 7 +- .../middle/message/ActionMessage.tsx | 2 + .../middle/message/ActionMessageText.tsx | 145 ++++++- .../middle/message/ContextMenuContainer.tsx | 50 ++- src/components/middle/message/Message.tsx | 6 +- .../middle/message/MessageContextMenu.tsx | 83 ++-- src/components/middle/message/TodoList.scss | 141 +++++++ src/components/middle/message/TodoList.tsx | 197 +++++++++ .../middle/message/helpers/messageActions.tsx | 17 +- .../stars/chatRefund/ChatRefundModal.tsx | 4 +- src/components/ui/Checkbox.scss | 30 +- src/components/ui/Checkbox.tsx | 16 + src/components/ui/CheckboxGroup.tsx | 4 + src/config.ts | 5 + src/global/actions/api/messages.ts | 85 ++++ src/global/actions/api/payments.ts | 3 +- src/global/actions/api/users.ts | 4 +- src/global/actions/apiUpdaters/messages.ts | 2 +- src/global/actions/ui/messages.ts | 17 + src/global/helpers/chats.ts | 3 + src/global/helpers/messageMedia.ts | 5 + src/global/helpers/messageSummary.ts | 10 + src/global/helpers/messages.ts | 9 +- src/global/types/actions.ts | 26 +- src/global/types/tabState.ts | 8 + src/lib/gramjs/tl/AllTLObjects.ts | 2 +- src/lib/gramjs/tl/api.d.ts | 169 +++++++- src/lib/gramjs/tl/apiTl.ts | 24 +- src/lib/gramjs/tl/static/api.json | 4 +- src/lib/gramjs/tl/static/api.tl | 28 +- src/types/index.ts | 2 + src/types/language.d.ts | 81 ++++ src/util/generateUniqueId.ts | 6 + 54 files changed, 2029 insertions(+), 111 deletions(-) create mode 100644 src/components/middle/composer/ToDoListModal.async.tsx create mode 100644 src/components/middle/composer/ToDoListModal.scss create mode 100644 src/components/middle/composer/ToDoListModal.tsx create mode 100644 src/components/middle/message/TodoList.scss create mode 100644 src/components/middle/message/TodoList.tsx diff --git a/src/api/gramjs/apiBuilders/appConfig.ts b/src/api/gramjs/apiBuilders/appConfig.ts index 1837800b9..9c049bd42 100644 --- a/src/api/gramjs/apiBuilders/appConfig.ts +++ b/src/api/gramjs/apiBuilders/appConfig.ts @@ -9,6 +9,9 @@ import { SERVICE_NOTIFICATIONS_USER_ID, STORY_EXPIRE_PERIOD, STORY_VIEWERS_EXPIRE_PERIOD, + TODO_ITEM_LENGTH_LIMIT, + TODO_ITEMS_LIMIT, + TODO_TITLE_LENGTH_LIMIT, } from '../../../config'; import localDb from '../localDb'; import { buildJson } from './misc'; @@ -99,6 +102,9 @@ export interface GramJsAppConfig extends LimitsConfig { stars_stargift_resale_amount_min?: number; stars_stargift_resale_commission_permille?: number; poll_answers_max?: number; + todo_items_max?: number; + todo_title_length_max?: number; + todo_item_length_max?: number; } function buildEmojiSounds(appConfig: GramJsAppConfig) { @@ -200,5 +206,8 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp starsStargiftResaleAmountMax: appConfig.stars_stargift_resale_amount_max, starsStargiftResaleCommissionPermille: appConfig.stars_stargift_resale_commission_permille, pollMaxAnswers: appConfig.poll_answers_max, + todoItemsMax: appConfig.todo_items_max ?? TODO_ITEMS_LIMIT, + todoTitleLengthMax: appConfig.todo_title_length_max ?? TODO_TITLE_LENGTH_LIMIT, + todoItemLengthMax: appConfig.todo_item_length_max ?? TODO_ITEM_LENGTH_LIMIT, }; } diff --git a/src/api/gramjs/apiBuilders/messageActions.ts b/src/api/gramjs/apiBuilders/messageActions.ts index d3b7a10f5..bea31f515 100644 --- a/src/api/gramjs/apiBuilders/messageActions.ts +++ b/src/api/gramjs/apiBuilders/messageActions.ts @@ -6,6 +6,7 @@ import type { ApiMessageAction } from '../../types/messageActions'; import { buildApiBotApp } from './bots'; import { buildApiFormattedText, buildApiPhoto } from './common'; import { buildApiStarGift } from './gifts'; +import { buildTodoItem } from './messageContent'; import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers'; const UNSUPPORTED_ACTION: ApiMessageAction = { @@ -446,6 +447,25 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess count, }; } + if (action instanceof GramJs.MessageActionTodoCompletions) { + const { + completed, incompleted, + } = action; + return { + mediaType: 'action', + type: 'todoCompletions', + completedIds: completed, + incompletedIds: incompleted, + }; + } + if (action instanceof GramJs.MessageActionTodoAppendTasks) { + const { list } = action; + return { + mediaType: 'action', + type: 'todoAppendTasks', + items: list.map(buildTodoItem), + }; + } return UNSUPPORTED_ACTION; } diff --git a/src/api/gramjs/apiBuilders/messageContent.ts b/src/api/gramjs/apiBuilders/messageContent.ts index 778a62148..23055b0ca 100644 --- a/src/api/gramjs/apiBuilders/messageContent.ts +++ b/src/api/gramjs/apiBuilders/messageContent.ts @@ -11,12 +11,14 @@ import type { ApiLocation, ApiMediaExtendedPreview, ApiMediaInvoice, + ApiMediaTodo, ApiMessageStoryData, ApiPaidMedia, ApiPhoto, ApiPoll, ApiStarGiftUnique, ApiSticker, + ApiTodoItem, ApiVideo, ApiVoice, ApiWebDocument, @@ -64,7 +66,7 @@ export function buildMessageContent( const hasUnsupportedMedia = mtpMessage.media instanceof GramJs.MessageMediaUnsupported; if (mtpMessage.message && !hasUnsupportedMedia - && !content.sticker && !content.pollId && !content.contact && !content.video?.isRound) { + && !content.sticker && !content.pollId && !content.todo && !content.contact && !content.video?.isRound) { const text = buildMessageTextContent(mtpMessage.message, mtpMessage.entities); const textWithTimestamps = addTimestampEntities(text); content = { @@ -153,6 +155,9 @@ export function buildMessageMediaContent( const pollId = buildPollIdFromMedia(media); if (pollId) return { pollId }; + const todo = buildTodoFromMedia(media); + if (todo) return { todo }; + const webPage = buildWebPage(media); if (webPage) return { webPage }; @@ -513,6 +518,14 @@ export function buildPollFromMedia(media: GramJs.TypeMessageMedia): ApiPoll | un return buildPoll(media.poll, media.results); } +function buildTodoFromMedia(media: GramJs.TypeMessageMedia): ApiMediaTodo | undefined { + if (!(media instanceof GramJs.MessageMediaToDo)) { + return undefined; + } + + return buildTodo(media.todo, media.completions); +} + function buildInvoiceFromMedia(media: GramJs.TypeMessageMedia): ApiMediaInvoice | undefined { if (!(media instanceof GramJs.MessageMediaInvoice)) { return undefined; @@ -711,6 +724,36 @@ export function buildPoll(poll: GramJs.Poll, pollResults: GramJs.PollResults): A }; } +export function buildTodoItem(item: GramJs.TodoItem): ApiTodoItem { + return { + id: item.id, + title: buildApiFormattedText(item.title), + }; +} + +export function buildTodo(todo: GramJs.TodoList, completions?: GramJs.TodoCompletion[]): ApiMediaTodo { + const { title, list: items } = todo; + + const todoItems = items.map(buildTodoItem); + + const todoCompletions = completions?.map((completion) => ({ + itemId: completion.id, + completedBy: completion.completedBy.toString(), + completedAt: completion.date, + })); + + return { + mediaType: 'todo', + todo: { + title: buildApiFormattedText(title), + items: todoItems, + othersCanAppend: todo.othersCanAppend, + othersCanComplete: todo.othersCanComplete, + }, + completions: todoCompletions, + }; +} + export function buildMediaInvoice(media: GramJs.MessageMediaInvoice): ApiMediaInvoice { const { description, title, photo, test, totalAmount, currency, receiptMsgId, extendedMedia, diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index 38362f6bb..6245da73d 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -8,10 +8,12 @@ import type { ApiFactCheck, ApiInputMessageReplyInfo, ApiInputReplyInfo, + ApiMediaTodo, ApiMessage, ApiMessageEntity, ApiMessageForwardInfo, ApiMessageReportResult, + ApiNewMediaTodo, ApiNewPoll, ApiPeer, ApiPhoto, @@ -382,6 +384,13 @@ function buildNewPoll(poll: ApiNewPoll, localId: number): ApiPoll { }; } +function buildNewTodo(todo: ApiNewMediaTodo): ApiMediaTodo { + return { + mediaType: 'todo', + todo: todo.todo, + }; +} + export function buildLocalMessage( chat: ApiChat, lastMessageId?: number, @@ -392,6 +401,7 @@ export function buildLocalMessage( sticker?: ApiSticker, gif?: ApiVideo, poll?: ApiNewPoll, + todo?: ApiNewMediaTodo, contact?: ApiContact, groupedId?: string, scheduledAt?: number, @@ -409,6 +419,7 @@ export function buildLocalMessage( const resultReplyInfo = replyInfo && buildReplyInfo(replyInfo, chat.isForum); const localPoll = poll && buildNewPoll(poll, localId); + const localTodo = todo && buildNewTodo(todo); const formattedText = text ? addTimestampEntities({ text, entities }) : undefined; @@ -423,6 +434,7 @@ export function buildLocalMessage( contact, storyData: story && { mediaType: 'storyData', ...story }, pollId: localPoll?.id, + todo: localTodo, }), date: scheduledAt || Math.round(Date.now() / 1000) + getServerTimeOffset(), isOutgoing: !isChannel, diff --git a/src/api/gramjs/gramjsBuilders/index.ts b/src/api/gramjs/gramjsBuilders/index.ts index fbe786a02..0f6603e3f 100644 --- a/src/api/gramjs/gramjsBuilders/index.ts +++ b/src/api/gramjs/gramjsBuilders/index.ts @@ -16,6 +16,7 @@ import type { ApiInputReplyInfo, ApiInputStorePaymentPurpose, ApiMessageEntity, + ApiNewMediaTodo, ApiNewPoll, ApiPhoneCall, ApiPhoto, @@ -263,6 +264,28 @@ export function buildInputPollFromExisting(poll: ApiPoll, shouldClose = false) { }); } +export function buildInputTodo(todo: ApiNewMediaTodo) { + const { title, items } = todo.todo; + + const todoItems = items.map((item) => { + return new GramJs.TodoItem({ + id: item.id, + title: buildInputTextWithEntities(item.title), + }); + }); + + const todoList = new GramJs.TodoList({ + title: buildInputTextWithEntities(title), + list: todoItems, + othersCanAppend: todo.todo.othersCanAppend || undefined, + othersCanComplete: todo.todo.othersCanComplete || undefined, + }); + + return new GramJs.InputMediaTodo({ + todo: todoList, + }); +} + export function buildFilterFromApiFolder(folder: ApiChatFolder): GramJs.DialogFilter | GramJs.DialogFilterChatlist { const { emoticon, diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index cfac13e76..ebf32a272 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -18,11 +18,13 @@ import type { ApiMessageEntity, ApiMessageSearchContext, ApiMessageSearchType, + ApiNewMediaTodo, ApiOnProgress, ApiPeer, ApiPoll, ApiReaction, ApiSendMessageAction, + ApiTodoItem, ApiUser, ApiUserStatus, MediaContent, @@ -47,7 +49,7 @@ import { import { fetchFile } from '../../../util/files'; import { compact, split } from '../../../util/iteratees'; import { getMessageKey } from '../../../util/keys/messageKey'; -import { getServerTime, getServerTimeOffset } from '../../../util/serverTime'; +import { getServerTime } from '../../../util/serverTime'; import { interpolateArray } from '../../../util/waveform'; import { buildApiChatFromPreview, @@ -82,6 +84,7 @@ import { buildInputReplyTo, buildInputStory, buildInputTextWithEntities, + buildInputTodo, buildInputUser, buildMessageFromUpdate, buildMtpMessageEntity, @@ -263,7 +266,7 @@ export function sendMessageLocal( params: SendMessageParams, ) { const { - chat, lastMessageId, text, entities, replyInfo, attachment, sticker, story, gif, poll, contact, + chat, lastMessageId, text, entities, replyInfo, attachment, sticker, story, gif, poll, todo, contact, scheduledAt, groupedId, sendAs, wasDrafted, isInvertedMedia, effectId, isPending, messagePriceInStars, } = params; @@ -282,6 +285,7 @@ export function sendMessageLocal( sticker, gif, poll, + todo, contact, groupedId, scheduledAt, @@ -311,7 +315,7 @@ export function sendApiMessage( onProgress?: ApiOnProgress, ) { const { - chat, text, entities, replyInfo, attachment, sticker, story, gif, poll, contact, + chat, text, entities, replyInfo, attachment, sticker, story, gif, poll, todo, contact, isSilent, scheduledAt, groupedId, noWebPage, sendAs, shouldUpdateStickerSetOrder, isInvertedMedia, effectId, webPageMediaSize, webPageUrl, messagePriceInStars, } = params; @@ -368,6 +372,8 @@ export function sendApiMessage( media = buildInputMediaDocument(gif); } else if (poll) { media = buildInputPoll(poll, randomId); + } else if (todo) { + media = buildInputTodo(todo); } else if (story) { media = buildInputStory(story); } else if (webPageUrl && webPageMediaSize) { @@ -629,7 +635,7 @@ export async function editMessage({ attachment?: ApiAttachment; noWebPage?: boolean; }, onProgress?: ApiOnProgress) { - const isScheduled = message.date * 1000 > Date.now() + getServerTimeOffset() * 1000; + const isScheduled = message.date * 1000 > getServerTime() * 1000; const media = attachment && buildUploadingMedia(attachment); @@ -702,6 +708,110 @@ export async function editMessage({ } } +export async function editTodo({ + chat, + message, + todo, +}: { + chat: ApiChat; + message: ApiMessage; + todo: ApiNewMediaTodo; +}) { + const media = buildInputTodo(todo); + const isScheduled = message.date * 1000 > getServerTime() * 1000; + + const newContent: MediaContent = { + ...message.content, + todo: { + mediaType: 'todo', + todo: todo.todo, + }, + }; + + const messageUpdate: Partial = { + ...message, + content: newContent, + }; + + sendApiUpdate({ + '@type': isScheduled ? 'updateScheduledMessage' : 'updateMessage', + id: message.id, + chatId: chat.id, + message: messageUpdate, + }); + + try { + await invokeRequest(new GramJs.messages.EditMessage({ + media, + peer: buildInputPeer(chat.id, chat.accessHash), + id: message.id, + }), { shouldThrow: true }); + } catch (err) { + if (DEBUG) { + // eslint-disable-next-line no-console + console.warn(err); + } + + const { message: messageErr } = err as Error; + + sendApiUpdate({ + '@type': 'error', + error: { + message: messageErr, + hasErrorKey: true, + }, + }); + + // Rollback changes + sendApiUpdate({ + '@type': 'updateMessage', + id: message.id, + chatId: chat.id, + message, + }); + } +} + +export async function appendTodoList({ + chat, + message, + items, +}: { + chat: ApiChat; + message: ApiMessage; + items: ApiTodoItem[]; +}) { + const todoItems = items.map((item) => { + return new GramJs.TodoItem({ + id: item.id, + title: buildInputTextWithEntities(item.title), + }); + }); + + try { + await invokeRequest(new GramJs.messages.AppendTodoList({ + peer: buildInputPeer(chat.id, chat.accessHash), + msgId: message.id, + list: todoItems, + }), { shouldThrow: true }); + } catch (err) { + if (DEBUG) { + // eslint-disable-next-line no-console + console.warn(err); + } + + const { message: messageErr } = err as Error; + + sendApiUpdate({ + '@type': 'error', + error: { + message: messageErr, + hasErrorKey: true, + }, + }); + } +} + export async function rescheduleMessage({ chat, message, @@ -1507,6 +1617,24 @@ export async function sendPollVote({ })); } +export async function toggleTodoCompleted({ + chat, messageId, completedIds, incompletedIds, +}: { + chat: ApiChat; + messageId: number; + completedIds: number[]; + incompletedIds: number[]; +}) { + const { id, accessHash } = chat; + + await invokeRequest(new GramJs.messages.ToggleTodoCompleted({ + peer: buildInputPeer(id, accessHash), + msgId: messageId, + completed: completedIds, + incompleted: incompletedIds, + })); +} + export async function closePoll({ chat, messageId, poll, }: { diff --git a/src/api/gramjs/methods/users.ts b/src/api/gramjs/methods/users.ts index fcd50504e..df0f0f32d 100644 --- a/src/api/gramjs/methods/users.ts +++ b/src/api/gramjs/methods/users.ts @@ -252,11 +252,11 @@ export async function deleteContact({ }); } -export async function addNoPaidMessagesException({ user, shouldRefundCharged }: { +export async function toggleNoPaidMessagesException({ user, shouldRefundCharged }: { user: ApiUser; shouldRefundCharged?: boolean; }) { - const result = await invokeRequest(new GramJs.account.AddNoPaidMessagesException({ + const result = await invokeRequest(new GramJs.account.ToggleNoPaidMessagesException ({ refundCharged: shouldRefundCharged ? true : undefined, userId: buildInputUser(user.id, user.accessHash), })); diff --git a/src/api/types/messageActions.ts b/src/api/types/messageActions.ts index 0a5bc4855..23d18bf58 100644 --- a/src/api/types/messageActions.ts +++ b/src/api/types/messageActions.ts @@ -1,5 +1,6 @@ import type { ApiGroupCall, ApiPhoneCallDiscardReason } from './calls'; import type { ApiBotApp, ApiFormattedText, ApiPhoto } from './messages'; +import type { ApiTodoItem } from './messages'; import type { ApiStarGiftRegular, ApiStarGiftUnique } from './stars'; interface ActionMediaType { @@ -281,6 +282,17 @@ export interface ApiMessageActionPaidMessagesPrice extends ActionMediaType { isAllowedInChannel?: boolean; } +export interface ApiMessageActionTodoCompletions extends ActionMediaType { + type: 'todoCompletions'; + completedIds: number[]; + incompletedIds: number[]; +} + +export interface ApiMessageActionTodoAppendTasks extends ActionMediaType { + type: 'todoAppendTasks'; + items: ApiTodoItem[]; +} + export interface ApiMessageActionUnsupported extends ActionMediaType { type: 'unsupported'; } @@ -298,4 +310,5 @@ export type ApiMessageAction = ApiMessageActionUnsupported | ApiMessageActionCha | ApiMessageActionChannelJoined | ApiMessageActionGiftCode | ApiMessageActionGiveawayLaunch | ApiMessageActionGiveawayResults | ApiMessageActionPaymentRefunded | ApiMessageActionGiftStars | ApiMessageActionPrizeStars | ApiMessageActionStarGift | ApiMessageActionStarGiftUnique - | ApiMessageActionPaidMessagesRefunded | ApiMessageActionPaidMessagesPrice; + | ApiMessageActionPaidMessagesRefunded | ApiMessageActionPaidMessagesPrice | ApiMessageActionTodoCompletions + | ApiMessageActionTodoAppendTasks; diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index bdebd715f..a606691c8 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -332,6 +332,34 @@ export type ApiNewPoll = { }; }; +export interface ApiTodoItem { + id: number; + title: ApiFormattedText; +} + +export interface ApiTodoList { + title: ApiFormattedText; + items: ApiTodoItem[]; + othersCanAppend?: boolean; + othersCanComplete?: boolean; +} + +export interface ApiTodoCompletion { + itemId: number; + completedBy: string; + completedAt: number; +} + +export interface ApiMediaTodo { + mediaType: 'todo'; + todo: ApiTodoList; + completions?: ApiTodoCompletion[]; +} + +export type ApiNewMediaTodo = { + todo: ApiTodoList; +}; + export interface ApiWebPage { mediaType: 'webpage'; id: number; @@ -516,6 +544,7 @@ export type MediaContent = { sticker?: ApiSticker; contact?: ApiContact; pollId?: string; + todo?: ApiMediaTodo; action?: ApiMessageAction; webPage?: ApiWebPage; audio?: ApiAudio; diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index 3ff76dd4f..7e83f37c8 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -253,6 +253,9 @@ export interface ApiAppConfig { starsStargiftResaleAmountMax?: number; starsStargiftResaleCommissionPermille?: number; pollMaxAnswers?: number; + todoItemsMax?: number; + todoTitleLengthMax?: number; + todoItemLengthMax?: number; } export interface ApiConfig { diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index dedab41c7..fc7c5304a 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -68,6 +68,7 @@ "PremiumPreviewVoiceToTextDescription" = "Ability to read the transcript of any incoming voice message."; "PremiumPreviewProfileBadgeDescription" = "An exclusive badge next to your name showing that you subscribe to Telegram Premium."; "PremiumPreviewDownloadSpeedDescription" = "No more limits on the speed with which media and documents are downloaded."; +"PremiumPreviewTodoDescription" = "Plan, assign, and complete tasks - seamlessly and efficiently."; "PremiumPreviewUploadsDescription" = "4 GB per each document, unlimited storage for your chats and media overall."; "PremiumPreviewAdvancedChatManagementDescription" = "Tools to set the default folder, auto-archive and hide new chats from non-contacts."; "PremiumPreviewAnimatedProfilesDescription" = "Video avatars animated in chat lists and chats to allow for additional self-expression."; @@ -2024,3 +2025,41 @@ "MonoforumComposerPlaceholder" = "Choose a message to reply"; "ChannelSendMessage" = "Direct Messages"; "AutomaticTranslation" = "Automatic Translation"; +"TitleNewToDoList" = "New Checklist"; +"TitleEditToDoList" = "Edit Checklist"; +"TitleAppendToDoList" = "Append Checklist"; +"InputTitle" = "Title"; +"TitleToDoList" = "Checklist"; +"TitleTask" = "Task"; +"TitleAddTask" = "Add a task"; +"AllowOthersAddTasks" = "Allow Others to Add Tasks"; +"AllowOthersMarkAsDone" = "Allow Others to Mark As Done"; +"AriaToDoCancel" = "Cancel checklist creation"; +"TitleGroupToDoList" = "Group Checklist"; +"TitleUserToDoList" = "{peer}'s Checklist"; +"TitleYourToDoList" = "Your Checklist"; +"DescriptionCompletedToDoTasks" = "{number} of {count} completed"; +"MessageActionTodoCompletionsAsDone" = "{peer} marked \"{task}\" as done"; +"MessageActionTodoCompletionsAsDoneYou" = "You marked \"{task}\" as done"; +"MessageActionTodoCompletionsAsDoneMultiple" = "{peer} marked {tasks} as done"; +"MessageActionTodoCompletionsAsDoneMultipleYou" = "You marked {tasks} as done"; +"MessageActionTodoCompletionsAsNotDone" = "{peer} marked \"{task}\" as not done"; +"MessageActionTodoCompletionsAsNotDoneYou" = "You marked \"{task}\" as not done"; +"MessageActionTodoCompletionsAsNotDoneMultiple" = "{peer} marked {tasks} as not done"; +"MessageActionTodoCompletionsAsNotDoneMultipleYou" = "You marked {tasks} as not done"; +"MessageActionTodoTaskCount_one" = "{count} task"; +"MessageActionTodoTaskCount_other" = "{count} tasks"; +"ToDoListNewTasks" = "New Tasks"; +"MenuButtonAppendTodoList" = "Add a Task"; +"MessageActionAppendTodo" = "{peer} added a new task \"{task}\" to {list}"; +"MessageActionAppendTodoYou" = "You added a new task \"{task}\" to {list}"; +"MessageActionAppendTodoMultiple" = "{peer} added {tasks} to {list}"; +"MessageActionAppendTodoMultipleYou" = "You added {tasks} to {list}"; +"PremiumMore" = "More"; +"SubscribeToTelegramPremiumForToggleTask" = "Subscribe to **Telegram Premium** to toggle tasks"; +"SubscribeToTelegramPremiumForCreateToDo" = "Subscribe to **Telegram Premium** to create Checklists"; +"SubscribeToTelegramPremiumForAppendToDo" = "Subscribe to **Telegram Premium** to append Checklists"; +"HintTodoListTasksCount" = "You can add {count} more tasks"; +"ToDoListErrorChooseTitle" = "Please enter a title."; +"ToDoListErrorChooseTasks" = "Please enter at least one task."; +"PremiumPreviewTodo" = "Checklists"; diff --git a/src/bundles/extra.ts b/src/bundles/extra.ts index 3d0f9b893..f6a6f4f44 100644 --- a/src/bundles/extra.ts +++ b/src/bundles/extra.ts @@ -68,6 +68,7 @@ export { default as ReactionPicker } from '../components/middle/message/reaction export { default as AttachmentModal } from '../components/middle/composer/AttachmentModal'; export { default as PollModal } from '../components/middle/composer/PollModal'; +export { default as ToDoListModal } from '../components/middle/composer/ToDoListModal'; export { default as SymbolMenu } from '../components/middle/composer/SymbolMenu'; export { default as ChatCommandTooltip } from '../components/middle/composer/ChatCommandTooltip'; export { default as BotCommandMenu } from '../components/middle/composer/BotCommandMenu'; diff --git a/src/components/auth/Auth.scss b/src/components/auth/Auth.scss index 50ae4d1e9..f7745a2dd 100644 --- a/src/components/auth/Auth.scss +++ b/src/components/auth/Auth.scss @@ -220,7 +220,7 @@ padding: 0; border-radius: 50%; - font-size: smaller; + font-size: 0.875rem; color: white; background: var(--color-primary); diff --git a/src/components/common/Composer.tsx b/src/components/common/Composer.tsx index bd58ebaba..9d141f0f5 100644 --- a/src/components/common/Composer.tsx +++ b/src/components/common/Composer.tsx @@ -20,6 +20,7 @@ import type { ApiFormattedText, ApiMessage, ApiMessageEntity, + ApiNewMediaTodo, ApiNewPoll, ApiPeer, ApiQuickReply, @@ -178,6 +179,7 @@ import PollModal from '../middle/composer/PollModal.async'; import SendAsMenu from '../middle/composer/SendAsMenu.async'; import StickerTooltip from '../middle/composer/StickerTooltip.async'; import SymbolMenuButton from '../middle/composer/SymbolMenuButton'; +import ToDoListModal from '../middle/composer/ToDoListModal.async'; import WebPagePreview from '../middle/composer/WebPagePreview'; import MessageEffect from '../middle/message/MessageEffect'; import ReactionSelector from '../middle/message/reactions/ReactionSelector'; @@ -235,6 +237,7 @@ type StateProps = isForwarding?: boolean; forwardedMessagesCount?: number; pollModal: TabState['pollModal']; + todoListModal: TabState['todoListModal']; botKeyboardMessageId?: number; botKeyboardPlaceholder?: string; withScheduledButton?: boolean; @@ -357,6 +360,7 @@ const Composer: FC = ({ isForwarding, forwardedMessagesCount, pollModal, + todoListModal, botKeyboardMessageId, botKeyboardPlaceholder, inputPlaceholder, @@ -433,6 +437,8 @@ const Composer: FC = ({ showDialog, openPollModal, closePollModal, + openTodoListModal, + closeTodoListModal, loadScheduledHistory, openThread, addRecentEmoji, @@ -540,7 +546,7 @@ const Composer: FC = ({ const [nextText, setNextText] = useState(undefined); const { - canSendStickers, canSendGifs, canAttachMedia, canAttachPolls, canAttachEmbedLinks, + canSendStickers, canSendGifs, canAttachMedia, canAttachPolls, canAttachEmbedLinks, canAttachToDoLists, canSendVoices, canSendPlainText, canSendAudios, canSendVideos, canSendPhotos, canSendDocuments, } = useMemo( () => getAllowedAttachmentOptions(chat, @@ -1216,6 +1222,22 @@ const Composer: FC = ({ handleActionWithPaymentConfirmation(handleSend, isSilent, scheduledAt); }); + const handleTodoListCreate = useLastCallback(() => { + if (!isCurrentUserPremium) { + showNotification({ + message: lang('SubscribeToTelegramPremiumForCreateToDo'), + action: { + action: 'openPremiumModal', + payload: { initialSection: 'todo' }, + }, + actionText: lang('PremiumMore'), + }); + return; + } + + openTodoListModal({ chatId }); + }); + const handleClickBotMenu = useLastCallback(() => { if (botMenuButton?.type !== 'webApp') { return; @@ -1455,6 +1477,28 @@ const Composer: FC = ({ } }); + const handleToDoListSend = useLastCallback((todo: ApiNewMediaTodo) => { + if (!currentMessageList) { + return; + } + + if (isInScheduledList) { + requestCalendar((scheduledAt) => { + handleActionWithPaymentConfirmation( + handleMessageSchedule, + { todo }, + scheduledAt, + currentMessageList, + ); + }); + } else { + handleActionWithPaymentConfirmation( + sendMessage, + { messageList: currentMessageList, todo, isSilent: isSilentPosting }, + ); + } + }); + const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => { if (isInScheduledList) { requestCalendar((scheduledAt) => { @@ -1878,6 +1922,11 @@ const Composer: FC = ({ onClear={closePollModal} onSend={handlePollSend} /> + = ({ isButtonVisible={!activeVoiceRecording} canAttachMedia={canAttachMedia} canAttachPolls={canAttachPolls} + canAttachToDoLists={canAttachToDoLists} canSendPhotos={canSendPhotos} canSendVideos={canSendVideos} canSendDocuments={canSendDocuments} canSendAudios={canSendAudios} onFileSelect={handleFileSelect} onPollCreate={openPollModal} + onTodoListCreate={handleTodoListCreate} isScheduled={isInScheduledList} attachBots={isInMessageList ? attachBots : undefined} peerType={attachMenuPeerType} @@ -2458,6 +2509,7 @@ export default memo(withGlobal( isForwarding, forwardedMessagesCount: isForwarding ? forwardMessageIds!.length : undefined, pollModal: tabState.pollModal, + todoListModal: tabState.todoListModal, stickersForEmoji: global.stickers.forEmoji.stickers, customEmojiForEmoji: global.customEmojis.forEmoji.stickers, chatFullInfo, diff --git a/src/components/main/premium/PremiumFeatureModal.tsx b/src/components/main/premium/PremiumFeatureModal.tsx index 48ae21e8c..1af9facb2 100644 --- a/src/components/main/premium/PremiumFeatureModal.tsx +++ b/src/components/main/premium/PremiumFeatureModal.tsx @@ -12,6 +12,7 @@ import type { ApiPremiumSubscriptionOption, } from '../../../api/types'; import type { GlobalState } from '../../../global/types'; +import type { LangPair } from '../../../types/language'; import { PREMIUM_BOTTOM_VIDEOS, PREMIUM_FEATURE_SECTIONS, PREMIUM_LIMITS_ORDER } from '../../../config'; import { requestMutation } from '../../../lib/fasterdom/fasterdom'; @@ -55,6 +56,7 @@ export const PREMIUM_FEATURE_TITLES: Record = { last_seen: 'PremiumPreviewLastSeen', message_privacy: 'PremiumPreviewMessagePrivacy', effects: 'Premium.MessageEffects', + todo: 'PremiumPreviewTodo', }; export const PREMIUM_FEATURE_DESCRIPTIONS: Record = { @@ -76,6 +78,7 @@ export const PREMIUM_FEATURE_DESCRIPTIONS: Record = { last_seen: 'PremiumPreviewLastSeenDescription', message_privacy: 'PremiumPreviewMessagePrivacyDescription', effects: 'Premium.MessageEffectsInfo', + todo: 'PremiumPreviewTodoDescription', }; const LIMITS_TITLES: Record = { @@ -290,6 +293,7 @@ const PremiumFeatureModal: FC = ({ const i = promo.videoSections.indexOf(section); if (i === -1) return undefined; + const shouldUseNewLang = promo.videoSections[i] === 'todo'; return (
@@ -303,10 +307,23 @@ const PremiumFeatureModal: FC = ({ />

- {oldLang(PREMIUM_FEATURE_TITLES[promo.videoSections[i]])} + {shouldUseNewLang + ? lang( + PREMIUM_FEATURE_TITLES[promo.videoSections[i]] as keyof LangPair, + undefined, + { withNodes: true, renderTextFilters: ['br'] }, + ) + : oldLang(PREMIUM_FEATURE_TITLES[promo.videoSections[i]])}

- {renderText(oldLang(PREMIUM_FEATURE_DESCRIPTIONS[promo.videoSections[i]]), ['br'])} + {renderText(shouldUseNewLang + ? lang( + PREMIUM_FEATURE_DESCRIPTIONS[promo.videoSections[i]] as keyof LangPair, + undefined, + { withNodes: true, renderTextFilters: ['br'] }, + ) + : oldLang(PREMIUM_FEATURE_DESCRIPTIONS[promo.videoSections[i]]), ['br'], + )}
); diff --git a/src/components/main/premium/PremiumMainModal.tsx b/src/components/main/premium/PremiumMainModal.tsx index 5021215bc..bd98ed202 100644 --- a/src/components/main/premium/PremiumMainModal.tsx +++ b/src/components/main/premium/PremiumMainModal.tsx @@ -9,6 +9,7 @@ import type { ApiPremiumPromo, ApiPremiumSection, ApiPremiumSubscriptionOption, ApiSticker, ApiStickerSet, ApiUser, } from '../../../api/types'; import type { GlobalState } from '../../../global/types'; +import type { LangPair } from '../../../types/language'; import { PREMIUM_FEATURE_SECTIONS, TME_LINK_PREFIX } from '../../../config'; import { getUserFullName } from '../../../global/helpers'; @@ -83,6 +84,7 @@ const PREMIUM_FEATURE_COLOR_ICONS: Record = { last_seen: PremiumLastSeen, message_privacy: PremiumMessagePrivacy, effects: PremiumEffects, + todo: PremiumBadge, }; export type OwnProps = { @@ -148,8 +150,10 @@ const PremiumMainModal: FC = ({ if (!isOpen) { setHeaderHidden(true); setCurrentSection(undefined); + } else if (initialSection) { + setCurrentSection(initialSection); } - }, [isOpen]); + }, [isOpen, initialSection]); const handleOpenSection = useLastCallback((section: ApiPremiumSection) => { setCurrentSection(section); @@ -401,14 +405,19 @@ const PremiumMainModal: FC = ({
{filteredSections.map((section, index) => { + const shouldUseNewLang = section === 'todo'; return ( void; onPollCreate: NoneToVoidFunction; + onTodoListCreate: NoneToVoidFunction; onMenuOpen: NoneToVoidFunction; onMenuClose: NoneToVoidFunction; canEditMedia?: boolean; @@ -72,6 +74,7 @@ const AttachMenu: FC = ({ isButtonVisible, canAttachMedia, canAttachPolls, + canAttachToDoLists, canSendPhotos, canSendVideos, canSendDocuments, @@ -85,6 +88,7 @@ const AttachMenu: FC = ({ onMenuOpen, onMenuClose, onPollCreate, + onTodoListCreate, canEditMedia, editingMessage, messageListType, @@ -263,6 +267,9 @@ const AttachMenu: FC = ({ {canAttachPolls && !editingMessage && ( {oldLang('Poll')} )} + {canAttachToDoLists && !editingMessage && ( + {lang('TitleToDoList')} + )} {!editingMessage && !canEditMedia && !isScheduled && bots?.map((bot) => ( = (props) => { + const { modal } = props; + const ToDoListModal = useModuleLoader(Bundles.Extra, 'ToDoListModal', !modal); + + return ToDoListModal ? : undefined; +}; + +export default ToDoListModalAsync; diff --git a/src/components/middle/composer/ToDoListModal.scss b/src/components/middle/composer/ToDoListModal.scss new file mode 100644 index 000000000..0d94b58b0 --- /dev/null +++ b/src/components/middle/composer/ToDoListModal.scss @@ -0,0 +1,105 @@ +@use '../../../styles/mixins'; + +.ToDoListModal { + .modal-dialog { + max-width: 26.25rem; + max-height: calc(100vh - 5rem); + } + + .modal-content { + min-height: 4.875rem; + } + + .modal-header-condensed { + margin-bottom: 1rem; + } + + .readonly-title { + margin-bottom: 1rem; + font-size: 1rem; + font-weight: var(--font-weight-medium); + } + + .items-header { + margin-top: 0.5rem; + margin-bottom: 0.75rem; + + font-size: 1rem; + font-weight: var(--font-weight-medium); + color: var(--color-text-secondary); + } + + .items-count-hint { + margin-bottom: 0.5rem; + margin-left: 1rem; + font-size: 0.875rem; + color: var(--color-text-secondary); + } + + .items-list { + overflow: auto; + overflow-y: scroll; + + max-height: 20rem; + margin: 1rem -0.75rem -0.5rem; + padding: 0 0.75rem; + border-top: 1px solid var(--color-chat-hover); + + @include mixins.adapt-padding-to-scrollbar(0.75rem); + + @media (max-width: 600px) { + overflow: hidden; + max-height: none; + } + } + + .item-wrapper { + position: relative; + + .form-control { + padding-right: 3rem; + } + + .item-remove-button { + position: absolute; + top: 0.125rem; + right: 0.3125rem; + } + } + + .options-footer { + margin-top: 1.5rem; + + .dialog-checkbox-group { + margin: 0 -1.125rem; + } + + .options-header { + margin-bottom: 0.5rem; + } + } + + .input-group:last-child { + margin-bottom: 0.5rem; + } + + .radio-group { + display: flex; + flex-direction: column; + gap: 0.8125rem; + margin-left: -1.125rem; + + .Radio, &:hover { + background-color: transparent; + } + } + + .Checkbox, + .Radio { + .Checkbox-main, + .Radio-main { + width: 100%; + max-height: 3rem; + } + } +} diff --git a/src/components/middle/composer/ToDoListModal.tsx b/src/components/middle/composer/ToDoListModal.tsx new file mode 100644 index 000000000..17d221d97 --- /dev/null +++ b/src/components/middle/composer/ToDoListModal.tsx @@ -0,0 +1,383 @@ +import type { ChangeEvent } from 'react'; +import type { ElementRef } from '../../../lib/teact/teact'; +import { + memo, useEffect, useRef, useState, +} from '../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../global'; + +import type { ApiNewMediaTodo } from '../../../api/types'; +import type { ApiMessage } from '../../../api/types'; +import type { TabState } from '../../../global/types/tabState'; + +import { + TODO_ITEM_LENGTH_LIMIT, + TODO_ITEMS_LIMIT, + TODO_TITLE_LENGTH_LIMIT, +} from '../../../config'; +import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom'; +import { selectChatMessage } from '../../../global/selectors'; +import captureEscKeyListener from '../../../util/captureEscKeyListener'; +import { generateUniqueNumberId } from '../../../util/generateUniqueId'; + +import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; + +import Icon from '../../common/icons/Icon'; +import Button from '../../ui/Button'; +import Checkbox from '../../ui/Checkbox'; +import InputText from '../../ui/InputText'; +import Modal from '../../ui/Modal'; + +import './ToDoListModal.scss'; + +export type OwnProps = { + modal: TabState['todoListModal']; + onSend: (todoList: ApiNewMediaTodo) => void; + onClear: () => void; +}; + +export type StateProps = { + editingMessage?: ApiMessage; + maxItemsCount?: number; + maxTitleLength?: number; + maxItemLength?: number; +}; + +type Item = { + id: number; + text: string; +}; + +const MAX_LIST_HEIGHT = 320; +const MAX_OPTION_LENGTH = 100; + +const ToDoListModal = ({ + modal, + maxItemsCount = TODO_ITEMS_LIMIT, + maxTitleLength = TODO_TITLE_LENGTH_LIMIT, + maxItemLength = TODO_ITEM_LENGTH_LIMIT, + editingMessage, + onSend, + onClear, +}: OwnProps & StateProps) => { + const { editTodo, closeTodoListModal, appendTodoList } = getActions(); + + const titleInputRef = useRef(); + const itemsListRef = useRef(); + + const [title, setTitle] = useState(''); + const [items, setItems] = useState([{ id: generateUniqueNumberId(), text: '' }]); + const [isOthersCanAppend, setIsOthersCanAppend] = useState(true); + const [isOthersCanComplete, setIsOthersCanComplete] = useState(true); + const [hasErrors, setHasErrors] = useState(false); + + const isOpen = Boolean(modal); + const renderingModal = useCurrentOrPrev(modal); + const isAddTaskMode = renderingModal?.isAddTaskMode; + + const lang = useLang(); + const editingTodo = editingMessage?.content.todo?.todo; + + const focusInput = useLastCallback((ref: ElementRef) => { + if (isOpen && ref.current) { + ref.current.focus(); + } + }); + + useEffect(() => { + if (editingTodo) { + setTitle(editingTodo.title.text); + setIsOthersCanAppend(editingTodo.othersCanAppend ?? false); + setIsOthersCanComplete(editingTodo.othersCanComplete ?? false); + if (!isAddTaskMode) { + const editingItems = editingTodo.items.map((item) => ({ + id: item.id, + text: item.title.text, + })); + if (editingItems.length < maxItemsCount) { + editingItems.push({ id: generateUniqueNumberId(), text: '' }); + } + setItems(editingItems); + } + } + }, [editingTodo, isAddTaskMode, maxItemsCount]); + + useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]); + useEffect(() => { + if (!isOpen) { + setTitle(''); + setItems([{ id: generateUniqueNumberId(), text: '' }]); + setIsOthersCanAppend(true); + setIsOthersCanComplete(true); + setHasErrors(false); + } + }, [isOpen]); + + useEffect(() => focusInput(titleInputRef), [focusInput, isOpen]); + + const addNewItem = useLastCallback((newItems: Item[]) => { + const id = generateUniqueNumberId(); + setItems([...newItems, { id, text: '' }]); + + requestNextMutation(() => { + const list = itemsListRef.current; + if (!list) { + return; + } + + requestMeasure(() => { + list.scrollTo({ top: list.scrollHeight, behavior: 'smooth' }); + }); + }); + }); + + const handleCreate = useLastCallback(() => { + setHasErrors(false); + if (!isOpen) { + return; + } + + const todoItems = items + .map((item) => { + const text = item.text.trim(); + + if (!text) return undefined; + + return { + id: item.id, + title: { + text: text.substring(0, maxItemLength), + }, + }; + }).filter(Boolean); + + const titleTrimmed = title.trim().substring(0, maxTitleLength); + if (!titleTrimmed || todoItems.length === 0) { + setTitle(titleTrimmed); + if (todoItems.length) { + const itemsTrimmed = items.map((o) => ( + { ...o, text: o.text.trim().substring(0, maxItemLength) })) + .filter((o) => o.text.length); + if (itemsTrimmed.length === 0) { + addNewItem([]); + } else { + setItems([...itemsTrimmed, { id: generateUniqueNumberId(), text: '' }]); + } + } else { + addNewItem([]); + } + setHasErrors(true); + return; + } + + if (isAddTaskMode && editingMessage) { + appendTodoList({ + chatId: editingMessage.chatId, + messageId: editingMessage.id, + items: todoItems, + }); + closeTodoListModal(); + return; + } + + const payload: ApiNewMediaTodo = { + todo: { + title: { + text: titleTrimmed, + }, + items: todoItems, + othersCanAppend: isOthersCanAppend, + othersCanComplete: isOthersCanComplete, + }, + }; + + if (editingMessage) { + editTodo({ + chatId: editingMessage.chatId, + todo: payload, + messageId: editingMessage.id, + }); + } else { + onSend(payload); + } + + closeTodoListModal(); + }); + + const updateItem = useLastCallback((index: number, text: string) => { + const newItems = [...items]; + newItems[index] = { ...newItems[index], text }; + if (newItems[newItems.length - 1].text.trim().length && newItems.length < maxItemsCount) { + addNewItem(newItems); + } else { + setItems(newItems); + } + }); + + const removeItem = useLastCallback((index: number) => { + const newItems = [...items]; + newItems.splice(index, 1); + setItems(newItems); + + requestNextMutation(() => { + if (!itemsListRef.current) { + return; + } + + itemsListRef.current.classList.toggle('overflown', itemsListRef.current.scrollHeight > MAX_LIST_HEIGHT); + }); + }); + + const handleIsOthersCanAppendChange = useLastCallback((e: ChangeEvent) => { + setIsOthersCanAppend(e.target.checked); + }); + const handleIsOthersCanCompleteChange = useLastCallback((e: ChangeEvent) => { + setIsOthersCanComplete(e.target.checked); + }); + + const handleKeyPress = useLastCallback((e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleCreate(); + } + }); + + const handleTitleChange = useLastCallback((e: ChangeEvent) => { + setTitle(e.target.value); + }); + + const getTitleError = useLastCallback(() => { + if (hasErrors && !title.trim().length) { + return lang('ToDoListErrorChooseTitle'); + } + + return undefined; + }); + + const getItemsError = useLastCallback((index: number) => { + const itemsTrimmed = items.map((o) => o.text.trim()).filter((o) => o.length); + if (hasErrors && itemsTrimmed.length < 1 && !items[index].text.trim().length) { + return lang('ToDoListErrorChooseTasks'); + } + return undefined; + }); + + function renderHeader() { + const title = isAddTaskMode ? 'TitleAppendToDoList' : editingMessage ? 'TitleEditToDoList' : 'TitleNewToDoList'; + return ( +
+ +
{lang(title)}
+ +
+ ); + } + + function renderItems() { + return items.map((item, index) => ( +
+ updateItem(index, e.currentTarget.value)} + onKeyPress={handleKeyPress} + /> + {index !== items.length - 1 && ( + + )} +
+ )); + } + + return ( + + {!isAddTaskMode && ( + + )} + {isAddTaskMode && ( +
+ {title} +
+ )} +
+ +
+

+ {lang(isAddTaskMode ? 'ToDoListNewTasks' : 'TitleToDoList')} +

+ + {renderItems()} + +
+ +
+ {lang('HintTodoListTasksCount', { + count: maxItemsCount - items.length - (isAddTaskMode && editingTodo ? editingTodo.items.length : 0), + })} +
+ +
+ + {!isAddTaskMode && ( +
+
+ + +
+
+ )} + + ); +}; + +export default memo(withGlobal( + (global, { modal }): StateProps => { + const { appConfig } = global; + const editingMessage = modal?.messageId ? selectChatMessage(global, modal.chatId, modal.messageId) : undefined; + return { + editingMessage, + maxItemsCount: appConfig?.todoItemsMax, + maxTitleLength: appConfig?.todoTitleLengthMax, + maxItemLength: appConfig?.todoItemLengthMax, + }; + }, +)(ToDoListModal)); diff --git a/src/components/middle/message/ActionMessage.module.scss b/src/components/middle/message/ActionMessage.module.scss index 226f0cf9f..9d4ced97b 100644 --- a/src/components/middle/message/ActionMessage.module.scss +++ b/src/components/middle/message/ActionMessage.module.scss @@ -64,7 +64,12 @@ .messageLink { overflow: hidden; - min-width: 0; + min-width: 1ch; +} + +.noEllipsis { + overflow: visible; + min-width: auto; } .singleLine, .messageLink { diff --git a/src/components/middle/message/ActionMessage.tsx b/src/components/middle/message/ActionMessage.tsx index 706006062..e68338f38 100644 --- a/src/components/middle/message/ActionMessage.tsx +++ b/src/components/middle/message/ActionMessage.tsx @@ -94,6 +94,8 @@ const SINGLE_LINE_ACTIONS = new Set([ 'pinMessage', 'chatEditPhoto', 'chatDeletePhoto', + 'todoCompletions', + 'todoAppendTasks', 'unsupported', ]); const HIDDEN_TEXT_ACTIONS = new Set(['giftCode', 'prizeStars', 'suggestProfilePhoto']); diff --git a/src/components/middle/message/ActionMessageText.tsx b/src/components/middle/message/ActionMessageText.tsx index a5dae2d2f..6fa7276d6 100644 --- a/src/components/middle/message/ActionMessageText.tsx +++ b/src/components/middle/message/ActionMessageText.tsx @@ -12,6 +12,7 @@ import { getMainUsername, getMessageInvoice, getMessageText, isChatChannel, } from '../../../global/helpers'; +import { getMessageContent } from '../../../global/helpers'; import { getPeerTitle } from '../../../global/helpers/peers'; import { getMessageReplyInfo } from '../../../global/helpers/replies'; import { @@ -534,14 +535,14 @@ const ActionMessageText = ({ if (isRecurringInit) { return lang( 'ActionPaymentInitRecurringFor', - { amount: cost, user: chatLink, invoice: renderMessageLink(replyMessage!, invoiceTitle, asPreview) }, + { amount: cost, user: chatLink, invoice: renderMessageLink(replyMessage, invoiceTitle, asPreview) }, { withNodes: true }, ); } return lang( 'ActionPaymentDoneFor', - { amount: cost, user: chatLink, invoice: renderMessageLink(replyMessage!, invoiceTitle, asPreview) }, + { amount: cost, user: chatLink, invoice: renderMessageLink(replyMessage, invoiceTitle, asPreview) }, { withNodes: true }, ); } @@ -749,6 +750,146 @@ const ActionMessageText = ({ }, { withNodes: true, withMarkdown: true }); } + case 'todoCompletions': { + const { completedIds, incompletedIds } = action; + + let completedItem; + let incompletedItem; + const { todo } = replyMessage ? getMessageContent(replyMessage) : {}; + if (todo) { + const todoItems = todo.todo.items; + completedItem = todoItems.find((item) => completedIds.includes(item.id)); + incompletedItem = todoItems.find((item) => incompletedIds.includes(item.id)); + } + + if (incompletedItem) { + const incompletedTaskTitle = incompletedItem.title; + + const incompletedTaskLink = renderMessageLink( + replyMessage, + renderTextWithEntities({ + text: incompletedTaskTitle.text, + entities: incompletedTaskTitle.entities, + asPreview: true, + }), + asPreview, + ); + + return translateWithYou(lang, 'MessageActionTodoCompletionsAsNotDone', isOutgoing, { + peer: senderLink, + task: incompletedTaskLink, + }); + } + + if (completedItem) { + const completedTaskTitle = completedItem.title; + const completedTaskLink = renderMessageLink( + replyMessage, + renderTextWithEntities({ + text: completedTaskTitle.text, + entities: completedTaskTitle.entities, + asPreview: true, + }), + asPreview, + ); + + return translateWithYou(lang, 'MessageActionTodoCompletionsAsDone', isOutgoing, { + peer: senderLink, + task: completedTaskLink, + }); + } + + if (completedIds) { + const completedText = lang('MessageActionTodoTaskCount', { + count: completedIds.length, + }, { pluralValue: completedIds.length }); + const completedLink = renderMessageLink( + replyMessage, + renderTextWithEntities({ + text: completedText, + asPreview: true, + }), + asPreview, + { noEllipsis: true }, + ); + return translateWithYou(lang, 'MessageActionTodoCompletionsAsDone', isOutgoing, { + peer: senderLink, + task: completedLink, + }); + } + + const incompletedText = lang('MessageActionTodoTaskCount', { + count: incompletedIds.length, + }, { pluralValue: incompletedIds.length }); + const incompletedLink = renderMessageLink( + replyMessage, + renderTextWithEntities({ + text: incompletedText, + asPreview: true, + }), + asPreview, + { noEllipsis: true }, + ); + + return translateWithYou(lang, 'MessageActionTodoCompletionsAsNotDone', isOutgoing, { + peer: senderLink, + task: incompletedLink, + }); + } + + case 'todoAppendTasks': { + const { items } = action; + const { todo } = replyMessage ? getMessageContent(replyMessage) : {}; + + const listTitle = todo?.todo.title.text || ''; + const listLink = renderMessageLink( + replyMessage, + renderTextWithEntities({ + text: listTitle, + asPreview: true, + }), + asPreview, + ); + + if (items.length === 1) { + const taskTitle = items[0].title; + const taskLink = renderMessageLink( + replyMessage, + renderTextWithEntities({ + text: taskTitle.text, + entities: taskTitle.entities, + asPreview: true, + }), + asPreview, + ); + + return translateWithYou(lang, 'MessageActionAppendTodo', isOutgoing, { + peer: senderLink, + task: taskLink, + list: listLink, + }); + } + + const tasksText = lang('MessageActionTodoTaskCount', { + count: items.length, + }, { pluralValue: items.length }); + const tasksLink = renderMessageLink( + replyMessage, + renderTextWithEntities({ + text: tasksText, + asPreview: true, + }), + asPreview, + { noEllipsis: true }, + ); + + return translateWithYou(lang, 'MessageActionAppendTodoMultiple', isOutgoing, { + peer: senderLink, + tasks: tasksLink, + list: listLink, + }); + } + case 'phoneCall': // Rendered as a regular message, but considered an action for the summary return lang(getCallMessageKey(action, isOutgoing)); default: diff --git a/src/components/middle/message/ContextMenuContainer.tsx b/src/components/middle/message/ContextMenuContainer.tsx index a50cfd1fd..902916fbf 100644 --- a/src/components/middle/message/ContextMenuContainer.tsx +++ b/src/components/middle/message/ContextMenuContainer.tsx @@ -25,6 +25,9 @@ import type { } from '../../../types'; import { MAIN_THREAD_ID } from '../../../api/types'; +import { + TODO_ITEMS_LIMIT, +} from '../../../config'; import { PREVIEW_AVATAR_COUNT, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config'; import { areReactionsEmpty, @@ -79,6 +82,7 @@ import { getSelectionAsFormattedText } from './helpers/getSelectionAsFormattedTe import { isSelectionRangeInsideMessage } from './helpers/isSelectionRangeInsideMessage'; import useFlag from '../../../hooks/useFlag'; +import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; import useOldLang from '../../../hooks/useOldLang'; import useSchedule from '../../../hooks/useSchedule'; @@ -125,6 +129,7 @@ type StateProps = { canDelete?: boolean; canReport?: boolean; canEdit?: boolean; + canAppendTodoList?: boolean; canForward?: boolean; canFaveSticker?: boolean; canUnfaveSticker?: boolean; @@ -192,6 +197,7 @@ const ContextMenuContainer: FC = ({ canReport, canShowReactionList, canEdit, + canAppendTodoList, enabledReactions, reactionsLimit, isPrivate, @@ -265,9 +271,12 @@ const ContextMenuContainer: FC = ({ addLocalPaidReaction, openPaidReactionModal, reportMessages, + openTodoListModal, + showNotification, } = getActions(); - const lang = useOldLang(); + const oldLang = useOldLang(); + const lang = useLang(); const { ref: containerRef } = useShowTransition({ isOpen, onCloseAnimationEnd, @@ -435,7 +444,34 @@ const ContextMenuContainer: FC = ({ }); const handleEdit = useLastCallback(() => { - setEditingId({ messageId: message.id }); + if (message.content.todo) { + openTodoListModal({ + chatId: message.chatId, + messageId: message.id, + }); + } else { + setEditingId({ messageId: message.id }); + } + closeMenu(); + }); + + const handleAppendTodoList = useLastCallback(() => { + if (!isCurrentUserPremium) { + showNotification({ + message: lang('SubscribeToTelegramPremiumForAppendToDo'), + action: { + action: 'openPremiumModal', + payload: { initialSection: 'todo' }, + }, + actionText: oldLang('PremiumMore'), + }); + } else { + openTodoListModal({ + chatId: message.chatId, + messageId: message.id, + isAddTaskMode: true, + }); + } closeMenu(); }); @@ -667,6 +703,7 @@ const ContextMenuContainer: FC = ({ repliesThreadInfo={repliesThreadInfo} canUnpin={canUnpin} canEdit={canEdit} + canAppendTodoList={canAppendTodoList} canForward={canForward} canFaveSticker={canFaveSticker} canUnfaveSticker={canUnfaveSticker} @@ -695,6 +732,7 @@ const ContextMenuContainer: FC = ({ onOpenThread={handleOpenThread} onReply={handleReply} onEdit={handleEdit} + onAppendTodoList={handleAppendTodoList} onPin={handlePin} onUnpin={handleUnpin} onForward={handleForward} @@ -734,8 +772,8 @@ const ContextMenuContainer: FC = ({ {canReschedule && calendar} @@ -855,6 +893,9 @@ export default memo(withGlobal( const canGift = selectCanGift(global, message.chatId); const savedDialogId = selectSavedDialogIdFromMessage(global, message); + const todoItemsMax = global.appConfig?.todoItemsMax || TODO_ITEMS_LIMIT; + const canAppendTodoList = message.content.todo?.todo.othersCanAppend + && message.content.todo?.todo.items?.length < todoItemsMax; return { threadId, @@ -871,6 +912,7 @@ export default memo(withGlobal( canUnpin: !isScheduled && canUnpin, canDelete, canEdit: !isPinned && canEdit, + canAppendTodoList, canForward: !isScheduled && canForward, canFaveSticker: !isScheduled && canFaveSticker, canUnfaveSticker: !isScheduled && canUnfaveSticker, diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index cf11d2d45..17b992788 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -189,6 +189,7 @@ import RoundVideo from './RoundVideo'; import Sticker from './Sticker'; import Story from './Story'; import StoryMention from './StoryMention'; +import TodoList from './TodoList'; import Video from './Video'; import WebPage from './WebPage'; @@ -520,7 +521,7 @@ const Message: FC = ({ voice, document, sticker, contact, webPage, invoice, location, action, game, storyData, giveaway, - giveawayResults, + giveawayResults, todo, } = getMessageContent(message); const messageReplyInfo = getMessageReplyInfo(message); @@ -1241,6 +1242,9 @@ const Message: FC = ({ {poll && ( )} + {todo && ( + + )} {(giveaway || giveawayResults) && ( )} diff --git a/src/components/middle/message/MessageContextMenu.tsx b/src/components/middle/message/MessageContextMenu.tsx index 1a66e1541..a0771cc81 100644 --- a/src/components/middle/message/MessageContextMenu.tsx +++ b/src/components/middle/message/MessageContextMenu.tsx @@ -32,6 +32,7 @@ import { getMessageCopyOptions } from './helpers/copyOptions'; import useAppLayout from '../../../hooks/useAppLayout'; import useFlag from '../../../hooks/useFlag'; +import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; import useOldLang from '../../../hooks/useOldLang'; @@ -73,6 +74,7 @@ type OwnProps = { canShowReactionList?: boolean; canBuyPremium?: boolean; canEdit?: boolean; + canAppendTodoList?: boolean; canForward?: boolean; canFaveSticker?: boolean; canUnfaveSticker?: boolean; @@ -101,6 +103,7 @@ type OwnProps = { onReply?: NoneToVoidFunction; onOpenThread?: VoidFunction; onEdit?: NoneToVoidFunction; + onAppendTodoList?: NoneToVoidFunction; onPin?: NoneToVoidFunction; onUnpin?: NoneToVoidFunction; onForward?: NoneToVoidFunction; @@ -159,6 +162,7 @@ const MessageContextMenu: FC = ({ canReply, canQuote, canEdit, + canAppendTodoList, noReplies, canPin, canUnpin, @@ -192,6 +196,7 @@ const MessageContextMenu: FC = ({ onReply, onOpenThread, onEdit, + onAppendTodoList, onPin, onUnpin, onForward, @@ -228,7 +233,8 @@ const MessageContextMenu: FC = ({ } = getActions(); const menuRef = useRef(); const scrollableRef = useRef(); - const lang = useOldLang(); + const oldLang = useOldLang(); + const lang = useLang(); const noReactions = !isPrivate && !enabledReactions; const areReactionsPossible = message.areReactionsPossible; const withReactions = (canShowReactionList && !noReactions) || areReactionsPossible; @@ -248,7 +254,7 @@ const MessageContextMenu: FC = ({ const handleAfterCopy = useLastCallback(() => { showNotification({ - message: lang('Share.Link.Copied'), + message: oldLang('Share.Link.Copied'), }); onClose(); }); @@ -395,40 +401,49 @@ const MessageContextMenu: FC = ({ 'MessageContextMenu_items scrollable-content custom-scroll', areItemsHidden && 'MessageContextMenu_items-hidden', )} - dir={lang.isRtl ? 'rtl' : undefined} + dir={oldLang.isRtl ? 'rtl' : undefined} > {shouldShowGiftButton && ( - {message?.isOutgoing ? lang('SendAnotherGift') - : lang('Conversation.ContextMenuSendGiftTo', userFullName)} + {message?.isOutgoing ? oldLang('SendAnotherGift') + : oldLang('Conversation.ContextMenuSendGiftTo', userFullName)} )} - {canSendNow && {lang('MessageScheduleSend')}} + {canSendNow && {oldLang('MessageScheduleSend')}} {canReschedule && ( - {lang('MessageScheduleEditTime')} + {oldLang('MessageScheduleEditTime')} )} {canReply && ( - {lang(canQuote ? 'lng_context_quote_and_reply' : 'Reply')} + {oldLang(canQuote ? 'lng_context_quote_and_reply' : 'Reply')} )} {!noReplies && Boolean(repliesThreadInfo?.messagesCount) && ( - {lang('Conversation.ContextViewReplies', repliesThreadInfo.messagesCount, 'i')} + {oldLang('Conversation.ContextViewReplies', repliesThreadInfo.messagesCount, 'i')} + + )} + {canEdit && {oldLang('Edit')}} + {canAppendTodoList && ( + + {lang('MenuButtonAppendTodoList')} )} - {canEdit && {lang('Edit')}} {canFaveSticker && ( - {lang('AddToFavorites')} + {oldLang('AddToFavorites')} )} {canUnfaveSticker && ( - {lang('Stickers.RemoveFromFavorites')} + {oldLang('Stickers.RemoveFromFavorites')} + )} + {canTranslate && {oldLang('TranslateMessage')}} + {canShowOriginal && ( + + {oldLang('ShowOriginalButton')} + )} - {canTranslate && {lang('TranslateMessage')}} - {canShowOriginal && {lang('ShowOriginalButton')}} {canSelectLanguage && ( - {lang('lng_settings_change_lang')} + {oldLang('lng_settings_change_lang')} )} {copyOptions.map((option) => ( = ({ onClick={option.handler} withPreventDefaultOnMouseDown > - {lang(option.label)} + {oldLang(option.label)} ))} - {canPin && {lang('DialogPin')}} - {canUnpin && {lang('DialogUnpin')}} - {canSaveGif && {lang('lng_context_save_gif')}} - {canRevote && {lang('lng_polls_retract')}} - {canClosePoll && {lang('lng_polls_stop')}} + {canPin && {oldLang('DialogPin')}} + {canUnpin && {oldLang('DialogUnpin')}} + {canSaveGif && {oldLang('lng_context_save_gif')}} + {canRevote && {oldLang('lng_polls_retract')}} + {canClosePoll && {oldLang('lng_polls_stop')}} {canDownload && ( - {isDownloading ? lang('lng_context_cancel_download') : lang('lng_media_download')} + {isDownloading ? oldLang('lng_context_cancel_download') : oldLang('lng_media_download')} )} - {canForward && {lang('Forward')}} - {canSelect && {lang('Common.Select')}} - {canReport && {lang('lng_context_report_msg')}} - {canDelete && {lang('Delete')}} + {canForward && {oldLang('Forward')}} + {canSelect && {oldLang('Common.Select')}} + {canReport && {oldLang('lng_context_report_msg')}} + {canDelete && {oldLang('Delete')}} {hasCustomEmoji && ( <> @@ -465,12 +480,14 @@ const MessageContextMenu: FC = ({ )} {customEmojiSets && customEmojiSets.length === 1 && ( - {renderText(lang('MessageContainsEmojiPack', customEmojiSets[0].title), ['simple_markdown', 'emoji'])} + {renderText( + oldLang('MessageContainsEmojiPack', customEmojiSets[0].title), ['simple_markdown', 'emoji'], + )} )} {customEmojiSets && customEmojiSets.length > 1 && ( - {renderText(lang('MessageContainsEmojiPacks', customEmojiSets.length), ['simple_markdown'])} + {renderText(oldLang('MessageContainsEmojiPacks', customEmojiSets.length), ['simple_markdown'])} )} @@ -484,14 +501,14 @@ const MessageContextMenu: FC = ({ disabled={!canShowReactionsCount && !seenByDatesCount} > - + {canShowReactionsCount && message.reactors?.count ? ( canShowSeenBy && seenByDatesCount - ? lang( + ? oldLang( 'Chat.OutgoingContextMixedReactionCount', [message.reactors.count, seenByDatesCount], ) - : lang('Chat.ContextReactionCount', message.reactors.count, 'i') + : oldLang('Chat.ContextReactionCount', message.reactors.count, 'i') ) : ( seenByDatesCount === 1 && seenByRecentPeers ? renderText( @@ -500,8 +517,8 @@ const MessageContextMenu: FC = ({ : (seenByRecentPeers[0] as ApiChat).title, ) : ( seenByDatesCount - ? lang('Conversation.ContextMenuSeen', seenByDatesCount, 'i') - : lang('Conversation.ContextMenuNoViews') + ? oldLang('Conversation.ContextMenuSeen', seenByDatesCount, 'i') + : oldLang('Conversation.ContextMenuNoViews') ) )} diff --git a/src/components/middle/message/TodoList.scss b/src/components/middle/message/TodoList.scss new file mode 100644 index 000000000..e27fdd276 --- /dev/null +++ b/src/components/middle/message/TodoList.scss @@ -0,0 +1,141 @@ +.todo-list { + min-width: 15rem; + text-align: initial; + + .todo-list-readonly-item { + display: flex; + align-items: center; + padding-bottom: 0.5rem; + } + + .todo-readonly-item-checkbox { + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: center; + + width: 1rem; + height: 1rem; + margin-inline-end: 0.5rem; + + font-weight: var(--font-weight-semibold); + color: var(--accent-color); + } + + .readonly-item-label { + overflow-wrap: anywhere; + } + + .todo-item-bullet-point { + width: 0.25rem; + height: 0.25rem; + border-radius: 50%; + background-color: var(--accent-color); + } + + .completed-label { + text-decoration: line-through; + } + + .Checkbox { + min-height: 2.5rem; + padding-bottom: 1.25rem; + padding-left: 2.5rem; + + &.withSubLabel { + padding-bottom: 0.25rem; + } + + &.disabled { + opacity: 1 !important; + + .Checkbox-main { + &::before, + &::after { + pointer-events: auto; + } + } + } + + &:hover { + background: none; + } + + .Checkbox-main { + padding: 0 !important; + + &::before { + --color-borders-input: var(--secondary-color); + + background-color: var(--background-color); + } + + &::after { + background-color: var(--accent-color); + outline: 1px solid var(--background-color); + + .theme-dark & { + background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEzLjkuOEw1LjggOC45IDIuMSA1LjJjLS40LS40LTEuMS0uNC0xLjYgMC0uNC40LS40IDEuMSAwIDEuNkw1IDExLjJjLjQuNCAxLjEuNCAxLjYgMGw4LjktOC45Yy40LS40LjQtMS4xIDAtMS42LS41LS40LTEuMi0uNC0xLjYuMXoiIGZpbGw9IiM3NjZhYzgiLz48L3N2Zz4=); + } + } + + .user-avatar, + &::before, + &::after { + top: 0.6875rem; + left: 0.125rem; + } + + .user-avatar { + left: 0.875rem; + } + + .label { + line-height: 1.3125rem; + } + } + + input:checked ~ .Checkbox-main { + &::before { + border-color: var(--accent-color); + } + } + + .Spinner { + top: 0.6875rem; + left: 0.125rem; + } + + &.loading { + .Spinner { + top: 0; + } + } + } + + .todo-list-title { + margin: 0.125rem 0; + font-weight: var(--font-weight-medium); + line-height: 1.25rem; + overflow-wrap: anywhere; + } + + .list-type, + .completed-tasks-count { + font-size: 0.875rem; + color: var(--secondary-color); + } + + .list-type { + margin-bottom: 0.5rem; + } + + .completed-tasks-count { + margin: 0 0 1.125rem; + text-align: center; + } + + @media (max-width: 600px) { + min-width: 50vw; + } +} diff --git a/src/components/middle/message/TodoList.tsx b/src/components/middle/message/TodoList.tsx new file mode 100644 index 000000000..1cbb52b62 --- /dev/null +++ b/src/components/middle/message/TodoList.tsx @@ -0,0 +1,197 @@ +import { + memo, useEffect, useMemo, useState, +} from '../../../lib/teact/teact'; +import { getActions, getGlobal, withGlobal } from '../../../global'; + +import type { + ApiMediaTodo, + ApiMessage, + ApiPeer, +} from '../../../api/types'; + +import { getPeerFullTitle, getPeerTitle } from '../../../global/helpers/peers'; +import { selectIsCurrentUserPremium, selectSender, selectUser } from '../../../global/selectors'; +import buildClassName from '../../../util/buildClassName'; +import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities'; + +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; + +import AnimatedCounter from '../../common/AnimatedCounter'; +import Icon from '../../common/icons/Icon'; +import CheckboxGroup from '../../ui/CheckboxGroup'; + +import './TodoList.scss'; + +type OwnProps = { + message: ApiMessage; + todoList: ApiMediaTodo; +}; + +type StateProps = { + sender?: ApiPeer; + isCurrentUserPremium: boolean; + isSynced?: boolean; +}; + +const TodoList = ({ + message, + todoList, + sender, + isCurrentUserPremium, + isSynced, +}: OwnProps & StateProps) => { + const { toggleTodoCompleted, showNotification } = getActions(); + const { todo, completions } = todoList; + const { title, items, othersCanComplete } = todo; + const [completedTasks, setCompletedTasks] = useState([]); + const completedTasksSet = useMemo(() => new Set(completedTasks), [completedTasks]); + + const canToggle = !message.isScheduled && isCurrentUserPremium && isSynced; + + useEffect(() => { + const completedIds = completions?.map((c) => c.itemId.toString()) || []; + setCompletedTasks(completedIds); + }, [completions]); + + const lang = useLang(); + + const handleTaskLabelClick = useLastCallback((e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + if (!isCurrentUserPremium) { + showNotification({ + message: lang('SubscribeToTelegramPremiumForToggleTask'), + action: { + action: 'openPremiumModal', + payload: { initialSection: 'todo' }, + }, + actionText: lang('PremiumMore'), + }); + return; + } + }); + + const handleTaskToggle = useLastCallback((newCompletedTasks: string[]) => { + const newCompletedId = newCompletedTasks.find((id) => !completedTasksSet.has(id)); + const newIncompletedId = Array.from(completedTasksSet).find((id) => !newCompletedTasks.includes(id)); + + toggleTodoCompleted({ + chatId: message.chatId, + messageId: message.id, + completedIds: newCompletedId ? [Number(newCompletedId)] : [], + incompletedIds: newIncompletedId ? [Number(newIncompletedId)] : [], + }); + }); + const isReadOnly = Boolean(message.forwardInfo) || (!othersCanComplete && !message.isOutgoing); + const isOutgoing = message.isOutgoing; + + const tasks = useMemo(() => items.map((task) => { + const user = !othersCanComplete ? undefined : selectUser(getGlobal(), + completions?.find((c) => c.itemId === task.id)?.completedBy || ''); + const subLabel = user ? getPeerFullTitle(lang, user) : undefined; + return { + label: renderTextWithEntities(task.title), + value: task.id.toString(), + user, + subLabel, + }; + }), [items, othersCanComplete, completions, lang]); + + const renderCheckBoxGroup = () => { + return ( + + ); + }; + + const renderReadOnlyTodoList = () => { + return ( +
+ {tasks.map((task) => ( +
+
+ {completedTasksSet.has(task.value) + ? + :
} +
+
+ {task.label} +
+
+ ))} +
+ ); + }; + + const renderTodoListType = () => { + if (message.forwardInfo) { + return lang('TitleToDoList'); + } + + if (othersCanComplete) { + return lang('TitleGroupToDoList'); + } + + if (isOutgoing) { + return lang('TitleYourToDoList'); + } + + if (sender) { + return lang('TitleUserToDoList', { peer: getPeerTitle(lang, sender) }, { withNodes: true }); + } + + return lang('TitleToDoList'); + }; + + return ( +
+
+
+ {renderTextWithEntities(title)} +
+
+ {renderTodoListType()} +
+
+ +
+ {isReadOnly ? renderReadOnlyTodoList() : renderCheckBoxGroup()} +
+
+ +
+
+ ); +}; + +export default memo(withGlobal((global, { message }): StateProps => { + const sender = selectSender(global, message); + return { + sender, + isCurrentUserPremium: selectIsCurrentUserPremium(global), + isSynced: global.isSynced, + }; +}, +)(TodoList)); diff --git a/src/components/middle/message/helpers/messageActions.tsx b/src/components/middle/message/helpers/messageActions.tsx index 03cacec92..0294b5b67 100644 --- a/src/components/middle/message/helpers/messageActions.tsx +++ b/src/components/middle/message/helpers/messageActions.tsx @@ -103,12 +103,21 @@ export function renderPeerLink(peerId: string | undefined, text: string, asPrevi ); } -export function renderMessageLink(targetMessage: ApiMessage, text: TeactNode, asPreview?: boolean) { - if (asPreview) return text; +export function renderMessageLink( + targetMessage: ApiMessage | undefined, + text: TeactNode, + asPreview: boolean | undefined, + params?: { + noEllipsis?: boolean; + }, +) { + const { noEllipsis } = params || {}; + + if (asPreview || !targetMessage) return text; + return ( { e.stopPropagation(); getActions().focusMessage({ chatId: targetMessage.chatId, messageId: targetMessage.id }); diff --git a/src/components/modals/stars/chatRefund/ChatRefundModal.tsx b/src/components/modals/stars/chatRefund/ChatRefundModal.tsx index 5b45a1758..17a7300bd 100644 --- a/src/components/modals/stars/chatRefund/ChatRefundModal.tsx +++ b/src/components/modals/stars/chatRefund/ChatRefundModal.tsx @@ -24,7 +24,7 @@ type StateProps = { }; const ChatRefundModal = ({ modal, user }: OwnProps & StateProps) => { - const { closeChatRefundModal, addNoPaidMessagesException } = getActions(); + const { closeChatRefundModal, toggleNoPaidMessagesException } = getActions(); const [shouldRefundStars, setShouldRefundStars] = useState(false); @@ -40,7 +40,7 @@ const ChatRefundModal = ({ modal, user }: OwnProps & StateProps) => { const handleConfirmRemoveFee = useLastCallback(() => { closeChatRefundModal(); if (!userId) return; - addNoPaidMessagesException({ userId, shouldRefundCharged: shouldRefundStars }); + toggleNoPaidMessagesException ({ userId, shouldRefundCharged: shouldRefundStars }); }); return ( diff --git a/src/components/ui/Checkbox.scss b/src/components/ui/Checkbox.scss index 46c4d5f92..aff90b212 100644 --- a/src/components/ui/Checkbox.scss +++ b/src/components/ui/Checkbox.scss @@ -27,6 +27,16 @@ } } + .user-avatar { + position: absolute; + top: 0; + left: 0; + + opacity: 0; + + transition: opacity 0.15s ease; + } + &.round { .Checkbox-main { &::before, &::after { @@ -101,11 +111,6 @@ pointer-events: none; content: ""; - position: absolute; - top: 50%; - left: 1.125rem; // 1 + ((1.5 - 1.25) / 2) - transform: translateY(-50%); - display: block; width: 1.25rem; @@ -127,6 +132,15 @@ transition: opacity 0.1s ease; } + .user-avatar, + &::before, + &::after { + position: absolute; + top: 50%; + left: 1.125rem; // 1 + ((1.5 - 1.25) / 2) + transform: translateY(-50%); + } + .label { unicode-bidi: plaintext; line-height: 1.25rem; @@ -202,6 +216,11 @@ &::after { opacity: 1; } + .user-avatar { + &.user-avatar-visible { + opacity: 1; + } + } } &[dir="rtl"] { @@ -218,6 +237,7 @@ } .Checkbox-main { + .user-avatar, &::before, &::after { right: 1rem; diff --git a/src/components/ui/Checkbox.tsx b/src/components/ui/Checkbox.tsx index cc66525d9..c94f4a46c 100644 --- a/src/components/ui/Checkbox.tsx +++ b/src/components/ui/Checkbox.tsx @@ -7,15 +7,19 @@ import { useState, } from '../../lib/teact/teact'; +import type { ApiUser } from '../../api/types'; import type { IconName } from '../../types/icons'; import type { IRadioOption } from './CheckboxGroup'; import buildClassName from '../../util/buildClassName'; +import { REM } from '../common/helpers/mediaDimensions'; import renderText from '../common/helpers/renderText'; +import useCurrentOrPrev from '../../hooks/useCurrentOrPrev'; import useLastCallback from '../../hooks/useLastCallback'; import useOldLang from '../../hooks/useOldLang'; +import Avatar from '../common/Avatar'; import Icon from '../common/icons/Icon'; import Button from './Button'; import Spinner from './Spinner'; @@ -26,6 +30,7 @@ type OwnProps = { id?: string; name?: string; value?: string; + user?: ApiUser; label?: TeactNode; labelText?: TeactNode; subLabel?: string; @@ -50,12 +55,14 @@ type OwnProps = { leftElement?: TeactNode; values?: string[]; }; +const AVATAR_SIZE = 1.25 * REM; const Checkbox: FC = ({ id, name, value, label, + user, labelText, subLabel, checked, @@ -81,6 +88,7 @@ const Checkbox: FC = ({ const lang = useOldLang(); const labelRef = useRef(); const [showNested, setShowNested] = useState(false); + const renderingUser = useCurrentOrPrev(user, true); const handleChange = useLastCallback((event: ChangeEvent) => { if (disabled) { @@ -151,6 +159,14 @@ const Checkbox: FC = ({ Boolean(leftElement) && 'Nested-avatar-list', )} > +
+ {renderingUser && ( + + )} +
{leftElement} {typeof label === 'string' ? renderText(label) : label} diff --git a/src/components/ui/CheckboxGroup.tsx b/src/components/ui/CheckboxGroup.tsx index bbe92d7e9..26c5caa6c 100644 --- a/src/components/ui/CheckboxGroup.tsx +++ b/src/components/ui/CheckboxGroup.tsx @@ -28,6 +28,7 @@ type OwnProps = { loadingOptions?: string[]; isRound?: boolean; onChange: (value: string[]) => void; + onClickLabel?: (e: React.MouseEvent, value?: string) => void; className?: string; }; @@ -40,6 +41,7 @@ const CheckboxGroup: FC = ({ loadingOptions, isRound, onChange, + onClickLabel, className, }) => { const handleChange = useLastCallback((event: ChangeEvent, nestedOptionList?: IRadioOption) => { @@ -87,10 +89,12 @@ const CheckboxGroup: FC = ({ label={option.label} subLabel={option.subLabel} value={option.value} + user={option.user} checked={selected?.indexOf(option.value) !== -1} disabled={option.disabled || disabled} isLoading={loadingOptions ? loadingOptions.indexOf(option.value) !== -1 : undefined} onChange={handleChange} + onClickLabel={onClickLabel} nestedCheckbox={nestedCheckbox} nestedCheckboxCount={getCheckedNestedCount(option.nestedOptions ?? [])} nestedOptionList={option} diff --git a/src/config.ts b/src/config.ts index 8bfba25c9..ef9812e38 100644 --- a/src/config.ts +++ b/src/config.ts @@ -106,6 +106,9 @@ export const STORY_LIST_LIMIT = 100; export const API_GENERAL_ID_LIMIT = 100; export const STATISTICS_PUBLIC_FORWARDS_LIMIT = 50; export const RESALE_GIFTS_LIMIT = 50; +export const TODO_ITEMS_LIMIT = 30; +export const TODO_TITLE_LENGTH_LIMIT = 32; +export const TODO_ITEM_LENGTH_LIMIT = 64; export const STORY_VIEWS_MIN_SEARCH = 15; export const STORY_MIN_REACTIONS_SORT = 10; @@ -429,6 +432,7 @@ export const PREMIUM_FEATURE_SECTIONS = [ 'last_seen', 'message_privacy', 'effects', + 'todo', ] as const; export const PREMIUM_BOTTOM_VIDEOS: ApiPremiumSection[] = [ @@ -444,6 +448,7 @@ export const PREMIUM_BOTTOM_VIDEOS: ApiPremiumSection[] = [ 'last_seen', 'message_privacy', 'effects', + 'todo', ]; export const PREMIUM_LIMITS_ORDER: ApiLimitTypeForPromo[] = [ diff --git a/src/global/actions/api/messages.ts b/src/global/actions/api/messages.ts index fcba6727d..bb066c3a6 100644 --- a/src/global/actions/api/messages.ts +++ b/src/global/actions/api/messages.ts @@ -53,6 +53,7 @@ import { getTranslationFn, type RegularLangFnParameters } from '../../../util/lo import { formatStarsAsText } from '../../../util/localization/format'; import { oldTranslate } from '../../../util/oldLangProvider'; import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers'; +import { getServerTime } from '../../../util/serverTime'; import { callApi, cancelApiProgress } from '../../../api/gramjs'; import { getIsSavedDialog, @@ -146,6 +147,7 @@ import { selectUserStatus, selectViewportIds, } from '../../selectors'; +import { updateWithLocalMedia } from '../apiUpdaters/messages'; import { deleteMessages } from '../apiUpdaters/messages'; const AUTOLOGIN_TOKEN_KEY = 'autologin_token'; @@ -557,6 +559,24 @@ addActionHandler('editMessage', (global, actions, payload): ActionReturnType => })(); }); +addActionHandler('editTodo', (global, actions, payload): ActionReturnType => { + const { + chatId, todo, messageId, + } = payload; + + const chat = selectChat(global, chatId); + const message = selectChatMessage(global, chatId, messageId); + if (!chat || !message) { + return; + } + + callApi('editTodo', { + chat, + message, + todo, + }); +}); + addActionHandler('cancelUploadMedia', (global, actions, payload): ActionReturnType => { const { chatId, messageId } = payload; @@ -1138,6 +1158,71 @@ addActionHandler('sendPollVote', (global, actions, payload): ActionReturnType => } }); +addActionHandler('toggleTodoCompleted', (global, actions, payload): ActionReturnType => { + const { chatId, messageId, completedIds, incompletedIds } = payload; + const chat = selectChat(global, chatId); + const message = selectChatMessage(global, chatId, messageId); + const currentUserId = global.currentUserId; + + const currentTodo = message?.content.todo; + if (!currentTodo || !currentUserId || !chat) { + return; + } + + const currentCompletions = currentTodo.completions || []; + const currentCompletionIds = currentCompletions.map((c) => c.itemId); + + const newCompletions = [...currentCompletions]; + const now = getServerTime(); + + completedIds.forEach((itemId) => { + if (!currentCompletionIds.includes(itemId)) { + newCompletions.push({ + itemId, + completedBy: currentUserId, + completedAt: now, + }); + } + }); + + const finalCompletions = newCompletions.filter((c) => !incompletedIds.includes(c.itemId)); + + const newContent = { + ...message.content, + todo: { + ...currentTodo, + completions: finalCompletions, + }, + }; + + const messageUpdate: Partial = { + ...message, + content: newContent, + }; + + global = updateWithLocalMedia(global, chatId, message.id, messageUpdate); + setGlobal(global); + + callApi('toggleTodoCompleted', { chat, messageId: message.id, completedIds, incompletedIds }); +}); +addActionHandler('appendTodoList', (global, actions, payload): ActionReturnType => { + const { + chatId, items, messageId, + } = payload; + + const chat = selectChat(global, chatId); + const message = selectChatMessage(global, chatId, messageId); + if (!chat || !message) { + return; + } + + callApi('appendTodoList', { + chat, + message, + items, + }); +}); + addActionHandler('cancelPollVote', (global, actions, payload): ActionReturnType => { const { chatId, messageId } = payload; const chat = selectChat(global, chatId); diff --git a/src/global/actions/api/payments.ts b/src/global/actions/api/payments.ts index f0745100c..b78102122 100644 --- a/src/global/actions/api/payments.ts +++ b/src/global/actions/api/payments.ts @@ -14,6 +14,7 @@ import { getCurrentTabId } from '../../../util/establishMultitabRole'; import * as langProvider from '../../../util/oldLangProvider'; import { getStripeError } from '../../../util/payments/stripe'; import { buildQueryString } from '../../../util/requestQuery'; +import { getServerTime } from '../../../util/serverTime'; import { extractCurrentThemeParams } from '../../../util/themeStyle'; import { callApi } from '../../../api/gramjs'; import { isChatChannel, isChatSuperGroup } from '../../helpers'; @@ -827,7 +828,7 @@ addActionHandler('applyBoost', async (global, actions, payload): Promise = const oldMyBoosts = tabState.boostModal?.myBoosts; if (oldMyBoosts) { - const unixNow = Math.floor(Date.now() / 1000); + const unixNow = getServerTime(); const newMyBoosts = oldMyBoosts.map((boost) => { if (slots.includes(boost.slot)) { return { diff --git a/src/global/actions/api/users.ts b/src/global/actions/api/users.ts index 91c1dbe1e..9c9c7bcde 100644 --- a/src/global/actions/api/users.ts +++ b/src/global/actions/api/users.ts @@ -191,14 +191,14 @@ addActionHandler('loadCommonChats', async (global, actions, payload): Promise => { +addActionHandler('toggleNoPaidMessagesException', async (global, actions, payload): Promise => { const { userId, shouldRefundCharged } = payload; const user = selectUser(global, userId); if (!user) { return; } - const result = await callApi('addNoPaidMessagesException', + const result = await callApi('toggleNoPaidMessagesException', { user, shouldRefundCharged }); if (!result) { return; diff --git a/src/global/actions/apiUpdaters/messages.ts b/src/global/actions/apiUpdaters/messages.ts index d1645fc79..12ff7a368 100644 --- a/src/global/actions/apiUpdaters/messages.ts +++ b/src/global/actions/apiUpdaters/messages.ts @@ -940,7 +940,7 @@ function updateReactions( return global; } -function updateWithLocalMedia( +export function updateWithLocalMedia( global: RequiredGlobalState, chatId: string, id: number, diff --git a/src/global/actions/ui/messages.ts b/src/global/actions/ui/messages.ts index 68eadb75e..cff3290bd 100644 --- a/src/global/actions/ui/messages.ts +++ b/src/global/actions/ui/messages.ts @@ -32,6 +32,7 @@ import { isChatChannel, } from '../../helpers'; import { getMessageSummaryText } from '../../helpers/messageSummary'; +import { addTabStateResetterAction } from '../../helpers/meta'; import { getPeerTitle } from '../../helpers/peers'; import { renderMessageSummaryHtml } from '../../helpers/renderMessageSummaryHtml'; import { addActionHandler, getGlobal, setGlobal } from '../../index'; @@ -762,6 +763,22 @@ addActionHandler('closePollModal', (global, actions, payload): ActionReturnType }, tabId); }); +addActionHandler('openTodoListModal', (global, actions, payload): ActionReturnType => { + const { + chatId, messageId, isAddTaskMode, tabId = getCurrentTabId(), + } = payload; + + return updateTabState(global, { + todoListModal: { + chatId, + messageId, + isAddTaskMode, + }, + }, tabId); +}); + +addTabStateResetterAction('closeTodoListModal', 'todoListModal'); + addActionHandler('checkVersionNotification', (global, actions): ActionReturnType => { if (RELEASE_DATETIME && Date.now() > Number(RELEASE_DATETIME) + VERSION_NOTIFICATION_DURATION) { return; diff --git a/src/global/helpers/chats.ts b/src/global/helpers/chats.ts index 71eeb743b..5cba3a24d 100644 --- a/src/global/helpers/chats.ts +++ b/src/global/helpers/chats.ts @@ -189,6 +189,7 @@ export interface IAllowedAttachmentOptions { canSendVoices: boolean; canSendPlainText: boolean; canSendDocuments: boolean; + canAttachToDoLists: boolean; } export function getAllowedAttachmentOptions( @@ -214,6 +215,7 @@ export function getAllowedAttachmentOptions( canSendVoices: false, canSendPlainText: false, canSendDocuments: false, + canAttachToDoLists: false, }; } @@ -224,6 +226,7 @@ export function getAllowedAttachmentOptions( canAttachPolls: !isStoryReply && !chat.isMonoforum && (isAdmin || !isUserRightBanned(chat, 'sendPolls', chatFullInfo)) && (!isUserId(chat.id) || isChatWithBot || isSavedMessages), + canAttachToDoLists: !isStoryReply && !chat.isMonoforum && !isChatChannel(chat), canSendStickers: isAdmin || isStoryReply || !isUserRightBanned(chat, 'sendStickers', chatFullInfo), canSendGifs: isAdmin || isStoryReply || !isUserRightBanned(chat, 'sendGifs', chatFullInfo), canAttachEmbedLinks: !isStoryReply && (isAdmin || !isUserRightBanned(chat, 'embedLinks', chatFullInfo)), diff --git a/src/global/helpers/messageMedia.ts b/src/global/helpers/messageMedia.ts index 943d1223b..3f5282433 100644 --- a/src/global/helpers/messageMedia.ts +++ b/src/global/helpers/messageMedia.ts @@ -51,6 +51,7 @@ export function hasMessageMedia(message: MediaContainer) { || getMessageSticker(message) || getMessageContact(message) || getMessagePollId(message) + || getMessageTodo(message) || getMessageAction(message) || getMessageAudio(message) || getMessageVoice(message) @@ -128,6 +129,10 @@ export function getMessagePollId(message: MediaContainer) { return message.content.pollId; } +export function getMessageTodo(message: MediaContainer) { + return message.content.todo; +} + export function getMessageInvoice(message: MediaContainer) { return message.content.invoice; } diff --git a/src/global/helpers/messageSummary.ts b/src/global/helpers/messageSummary.ts index 25a9b63d9..7c74a7097 100644 --- a/src/global/helpers/messageSummary.ts +++ b/src/global/helpers/messageSummary.ts @@ -72,6 +72,7 @@ export function getMessageSummaryEmoji(message: ApiMessage) { sticker, pollId, paidMedia, + todo, } = message.content; if (message.groupedId || photo || paidMedia) { @@ -102,6 +103,10 @@ export function getMessageSummaryEmoji(message: ApiMessage) { return '📊'; } + if (todo) { + return '📝'; + } + return undefined; } @@ -143,6 +148,7 @@ function getSummaryDescription( giveaway, giveawayResults, paidMedia, + todo, } = mediaContent; const { poll } = statefulContent || {}; @@ -239,6 +245,10 @@ function getSummaryDescription( summary = truncatedText || (message ? lang('ForwardedStory') : lang('Chat.ReplyStory')); } + if (todo) { + summary = lang('Chat.Todo.Message.Title'); + } + return summary || CONTENT_NOT_SUPPORTED; } diff --git a/src/global/helpers/messages.ts b/src/global/helpers/messages.ts index 6cca8a14a..b6ff19c7c 100644 --- a/src/global/helpers/messages.ts +++ b/src/global/helpers/messages.ts @@ -52,13 +52,14 @@ export function getMessageTranscription(message: ApiMessage) { export function hasMessageText(message: MediaContainer) { const { - action, text, sticker, photo, video, audio, voice, document, pollId, webPage, contact, invoice, location, - game, storyData, giveaway, giveawayResults, paidMedia, + action, text, sticker, photo, video, audio, voice, document, pollId, todo, + webPage, contact, invoice, location, game, storyData, giveaway, giveawayResults, paidMedia, } = message.content; return Boolean(text) || !( - sticker || photo || video || audio || voice || document || contact || pollId || webPage || invoice || location - || game || storyData || giveaway || giveawayResults || paidMedia || action?.type === 'phoneCall' + sticker || photo || video || audio || voice || document || contact || pollId || todo || webPage + || invoice || location || game || storyData || giveaway || giveawayResults + || paidMedia || action?.type === 'phoneCall' ); } diff --git a/src/global/types/actions.ts b/src/global/types/actions.ts index 1739ccb92..077d4d2b1 100644 --- a/src/global/types/actions.ts +++ b/src/global/types/actions.ts @@ -23,6 +23,7 @@ import type { ApiMessage, ApiMessageEntity, ApiMessageSearchContext, + ApiNewMediaTodo, ApiNotification, ApiNotifyPeerType, ApiPaymentStatus, @@ -45,6 +46,7 @@ import type { ApiStickerSet, ApiStickerSetInfo, ApiThemeParameters, + ApiTodoItem, ApiTypePrepaidGiveaway, ApiUpdate, ApiUser, @@ -502,6 +504,11 @@ export interface ActionPayloads { attachments?: ApiAttachment[]; entities?: ApiMessageEntity[]; } & WithTabId; + editTodo: { + chatId: string; + todo: ApiNewMediaTodo; + messageId: number; + } & WithTabId; deleteHistory: { chatId: string; shouldDeleteForAll?: boolean; @@ -1380,6 +1387,17 @@ export interface ActionPayloads { messageId: number; options: string[]; }; + toggleTodoCompleted: { + chatId: string; + messageId: number; + completedIds: number[]; + incompletedIds: number[]; + }; + appendTodoList: { + chatId: string; + items: ApiTodoItem[]; + messageId: number; + } & WithTabId; cancelPollVote: { chatId: string; messageId: number; @@ -1779,7 +1797,7 @@ export interface ActionPayloads { isMuted?: boolean; shouldSharePhoneNumber?: boolean; } & WithTabId; - addNoPaidMessagesException: { + toggleNoPaidMessagesException: { userId: string; shouldRefundCharged: boolean; }; @@ -2182,6 +2200,12 @@ export interface ActionPayloads { isQuiz?: boolean; } & WithTabId) | undefined; closePollModal: WithTabId | undefined; + openTodoListModal: { + chatId: string; + messageId?: number; + isAddTaskMode?: boolean; + } & WithTabId; + closeTodoListModal: WithTabId | undefined; requestConfetti: (ConfettiParams & WithTabId) | WithTabId; requestWave: { startX: number; diff --git a/src/global/types/tabState.ts b/src/global/types/tabState.ts index 79d3dd6bc..94f7b92e9 100644 --- a/src/global/types/tabState.ts +++ b/src/global/types/tabState.ts @@ -20,6 +20,7 @@ import type { ApiMessage, ApiMissingInvitedUser, ApiMyBoost, + ApiNewMediaTodo, ApiNewPoll, ApiNotification, ApiPaymentFormRegular, @@ -141,6 +142,7 @@ export type TabState = { gif?: ApiVideo; sticker?: ApiSticker; poll?: ApiNewPoll; + todo?: ApiNewMediaTodo; isSilent?: boolean; sendGrouped?: boolean; sendCompressed?: boolean; @@ -530,6 +532,12 @@ export type TabState = { isQuiz?: boolean; }; + todoListModal?: { + chatId: string; + messageId?: number; + isAddTaskMode?: boolean; + }; + preparedMessageModal?: { message: ApiPreparedInlineMessage; webAppKey: string; diff --git a/src/lib/gramjs/tl/AllTLObjects.ts b/src/lib/gramjs/tl/AllTLObjects.ts index 45c9e18be..5124c9fb2 100644 --- a/src/lib/gramjs/tl/AllTLObjects.ts +++ b/src/lib/gramjs/tl/AllTLObjects.ts @@ -12,5 +12,5 @@ for (const tl of Object.values(Api)) { } } -export const LAYER = 204; +export const LAYER = 205; export { tlobjects }; diff --git a/src/lib/gramjs/tl/api.d.ts b/src/lib/gramjs/tl/api.d.ts index 49df37610..4c6613801 100644 --- a/src/lib/gramjs/tl/api.d.ts +++ b/src/lib/gramjs/tl/api.d.ts @@ -53,7 +53,7 @@ namespace Api { export type TypeInputUser = InputUserEmpty | InputUserSelf | InputUser | InputUserFromMessage; export type TypeInputContact = InputPhoneContact; export type TypeInputFile = InputFile | InputFileBig | InputFileStoryDocument; - export type TypeInputMedia = InputMediaEmpty | InputMediaUploadedPhoto | InputMediaPhoto | InputMediaGeoPoint | InputMediaContact | InputMediaUploadedDocument | InputMediaDocument | InputMediaVenue | InputMediaPhotoExternal | InputMediaDocumentExternal | InputMediaGame | InputMediaInvoice | InputMediaGeoLive | InputMediaPoll | InputMediaDice | InputMediaStory | InputMediaWebPage | InputMediaPaidMedia; + export type TypeInputMedia = InputMediaEmpty | InputMediaUploadedPhoto | InputMediaPhoto | InputMediaGeoPoint | InputMediaContact | InputMediaUploadedDocument | InputMediaDocument | InputMediaVenue | InputMediaPhotoExternal | InputMediaDocumentExternal | InputMediaGame | InputMediaInvoice | InputMediaGeoLive | InputMediaPoll | InputMediaDice | InputMediaStory | InputMediaWebPage | InputMediaPaidMedia | InputMediaTodo; export type TypeInputChatPhoto = InputChatPhotoEmpty | InputChatUploadedPhoto | InputChatPhoto; export type TypeInputGeoPoint = InputGeoPointEmpty | InputGeoPoint; export type TypeInputPhoto = InputPhotoEmpty | InputPhoto; @@ -68,8 +68,8 @@ namespace Api { export type TypeChatParticipants = ChatParticipantsForbidden | ChatParticipants; export type TypeChatPhoto = ChatPhotoEmpty | ChatPhoto; export type TypeMessage = MessageEmpty | Message | MessageService; - export type TypeMessageMedia = MessageMediaEmpty | MessageMediaPhoto | MessageMediaGeo | MessageMediaContact | MessageMediaUnsupported | MessageMediaDocument | MessageMediaWebPage | MessageMediaVenue | MessageMediaGame | MessageMediaInvoice | MessageMediaGeoLive | MessageMediaPoll | MessageMediaDice | MessageMediaStory | MessageMediaGiveaway | MessageMediaGiveawayResults | MessageMediaPaidMedia; - export type TypeMessageAction = MessageActionEmpty | MessageActionChatCreate | MessageActionChatEditTitle | MessageActionChatEditPhoto | MessageActionChatDeletePhoto | MessageActionChatAddUser | MessageActionChatDeleteUser | MessageActionChatJoinedByLink | MessageActionChannelCreate | MessageActionChatMigrateTo | MessageActionChannelMigrateFrom | MessageActionPinMessage | MessageActionHistoryClear | MessageActionGameScore | MessageActionPaymentSentMe | MessageActionPaymentSent | MessageActionPhoneCall | MessageActionScreenshotTaken | MessageActionCustomAction | MessageActionBotAllowed | MessageActionSecureValuesSentMe | MessageActionSecureValuesSent | MessageActionContactSignUp | MessageActionGeoProximityReached | MessageActionGroupCall | MessageActionInviteToGroupCall | MessageActionSetMessagesTTL | MessageActionGroupCallScheduled | MessageActionSetChatTheme | MessageActionChatJoinedByRequest | MessageActionWebViewDataSentMe | MessageActionWebViewDataSent | MessageActionGiftPremium | MessageActionTopicCreate | MessageActionTopicEdit | MessageActionSuggestProfilePhoto | MessageActionRequestedPeer | MessageActionSetChatWallPaper | MessageActionGiftCode | MessageActionGiveawayLaunch | MessageActionGiveawayResults | MessageActionBoostApply | MessageActionRequestedPeerSentMe | MessageActionPaymentRefunded | MessageActionGiftStars | MessageActionPrizeStars | MessageActionStarGift | MessageActionStarGiftUnique | MessageActionPaidMessagesRefunded | MessageActionPaidMessagesPrice | MessageActionConferenceCall; + export type TypeMessageMedia = MessageMediaEmpty | MessageMediaPhoto | MessageMediaGeo | MessageMediaContact | MessageMediaUnsupported | MessageMediaDocument | MessageMediaWebPage | MessageMediaVenue | MessageMediaGame | MessageMediaInvoice | MessageMediaGeoLive | MessageMediaPoll | MessageMediaDice | MessageMediaStory | MessageMediaGiveaway | MessageMediaGiveawayResults | MessageMediaPaidMedia | MessageMediaToDo; + export type TypeMessageAction = MessageActionEmpty | MessageActionChatCreate | MessageActionChatEditTitle | MessageActionChatEditPhoto | MessageActionChatDeletePhoto | MessageActionChatAddUser | MessageActionChatDeleteUser | MessageActionChatJoinedByLink | MessageActionChannelCreate | MessageActionChatMigrateTo | MessageActionChannelMigrateFrom | MessageActionPinMessage | MessageActionHistoryClear | MessageActionGameScore | MessageActionPaymentSentMe | MessageActionPaymentSent | MessageActionPhoneCall | MessageActionScreenshotTaken | MessageActionCustomAction | MessageActionBotAllowed | MessageActionSecureValuesSentMe | MessageActionSecureValuesSent | MessageActionContactSignUp | MessageActionGeoProximityReached | MessageActionGroupCall | MessageActionInviteToGroupCall | MessageActionSetMessagesTTL | MessageActionGroupCallScheduled | MessageActionSetChatTheme | MessageActionChatJoinedByRequest | MessageActionWebViewDataSentMe | MessageActionWebViewDataSent | MessageActionGiftPremium | MessageActionTopicCreate | MessageActionTopicEdit | MessageActionSuggestProfilePhoto | MessageActionRequestedPeer | MessageActionSetChatWallPaper | MessageActionGiftCode | MessageActionGiveawayLaunch | MessageActionGiveawayResults | MessageActionBoostApply | MessageActionRequestedPeerSentMe | MessageActionPaymentRefunded | MessageActionGiftStars | MessageActionPrizeStars | MessageActionStarGift | MessageActionStarGiftUnique | MessageActionPaidMessagesRefunded | MessageActionPaidMessagesPrice | MessageActionConferenceCall | MessageActionTodoCompletions | MessageActionTodoAppendTasks; export type TypeDialog = Dialog | DialogFolder; export type TypePhoto = PhotoEmpty | Photo; export type TypePhotoSize = PhotoSizeEmpty | PhotoSize | PhotoCachedSize | PhotoStrippedSize | PhotoSizeProgressive | PhotoPathSize; @@ -395,6 +395,9 @@ namespace Api { export type TypeStarGiftAttributeId = StarGiftAttributeIdModel | StarGiftAttributeIdPattern | StarGiftAttributeIdBackdrop; export type TypeStarGiftAttributeCounter = StarGiftAttributeCounter; export type TypePendingSuggestion = PendingSuggestion; + export type TypeTodoItem = TodoItem; + export type TypeTodoList = TodoList; + export type TypeTodoCompletion = TodoCompletion; export type TypeResPQ = ResPQ; export type TypeP_Q_inner_data = PQInnerData | PQInnerDataDc | PQInnerDataTemp | PQInnerDataTempDc; export type TypeServer_DH_Params = ServerDHParamsFail | ServerDHParamsOk; @@ -1162,6 +1165,16 @@ namespace Api { static fromReader(reader: Reader): InputMediaPaidMedia; } + export class InputMediaTodo extends VirtualClass<{ + todo: Api.TypeTodoList; + }> { + todo: Api.TypeTodoList; + CONSTRUCTOR_ID: 2680512478; + SUBCLASS_OF_ID: 4210575092; + className: 'InputMediaTodo'; + + static fromReader(reader: Reader): InputMediaTodo; + } export class InputChatPhotoEmpty extends VirtualClass { CONSTRUCTOR_ID: 480546647; SUBCLASS_OF_ID: 3572182388; @@ -1931,6 +1944,7 @@ namespace Api { emojiset?: Api.TypeStickerSet; botVerification?: Api.TypeBotVerification; stargiftsCount?: int; + sendPaidMessagesStars?: long; }> { // flags: Api.Type; canViewParticipants?: true; @@ -1998,7 +2012,8 @@ namespace Api { emojiset?: Api.TypeStickerSet; botVerification?: Api.TypeBotVerification; stargiftsCount?: int; - CONSTRUCTOR_ID: 1389789291; + sendPaidMessagesStars?: long; + CONSTRUCTOR_ID: 3765709278; SUBCLASS_OF_ID: 3566872215; className: 'ChannelFull'; @@ -2215,6 +2230,7 @@ namespace Api { id: int; fromId?: Api.TypePeer; peerId: Api.TypePeer; + savedPeerId?: Api.TypePeer; replyTo?: Api.TypeMessageReplyHeader; date: int; action: Api.TypeMessageAction; @@ -2232,12 +2248,13 @@ namespace Api { id: int; fromId?: Api.TypePeer; peerId: Api.TypePeer; + savedPeerId?: Api.TypePeer; replyTo?: Api.TypeMessageReplyHeader; date: int; action: Api.TypeMessageAction; reactions?: Api.TypeMessageReactions; ttlPeriod?: int; - CONSTRUCTOR_ID: 3553789248; + CONSTRUCTOR_ID: 2055212554; SUBCLASS_OF_ID: 2030045667; className: 'MessageService'; @@ -2545,6 +2562,20 @@ namespace Api { static fromReader(reader: Reader): MessageMediaPaidMedia; } + export class MessageMediaToDo extends VirtualClass<{ + // flags: Api.Type; + todo: Api.TypeTodoList; + completions?: Api.TypeTodoCompletion[]; + }> { + // flags: Api.Type; + todo: Api.TypeTodoList; + completions?: Api.TypeTodoCompletion[]; + CONSTRUCTOR_ID: 2320740372; + SUBCLASS_OF_ID: 1198308914; + className: 'MessageMediaToDo'; + + static fromReader(reader: Reader): MessageMediaToDo; + } export class MessageActionEmpty extends VirtualClass { CONSTRUCTOR_ID: 3064919984; SUBCLASS_OF_ID: 2256589094; @@ -3274,6 +3305,28 @@ namespace Api { static fromReader(reader: Reader): MessageActionConferenceCall; } + export class MessageActionTodoCompletions extends VirtualClass<{ + completed: int[]; + incompleted: int[]; + }> { + completed: int[]; + incompleted: int[]; + CONSTRUCTOR_ID: 3430702217; + SUBCLASS_OF_ID: 2256589094; + className: 'MessageActionTodoCompletions'; + + static fromReader(reader: Reader): MessageActionTodoCompletions; + } + export class MessageActionTodoAppendTasks extends VirtualClass<{ + list: Api.TypeTodoItem[]; + }> { + list: Api.TypeTodoItem[]; + CONSTRUCTOR_ID: 3354246275; + SUBCLASS_OF_ID: 2256589094; + className: 'MessageActionTodoAppendTasks'; + + static fromReader(reader: Reader): MessageActionTodoAppendTasks; + } export class Dialog extends VirtualClass<{ // flags: Api.Type; pinned?: true; @@ -13611,6 +13664,8 @@ namespace Api { buttonText: string; sponsorInfo?: string; additionalInfo?: string; + minDisplayDuration?: int; + maxDisplayDuration?: int; }> { // flags: Api.Type; recommended?: true; @@ -13626,7 +13681,9 @@ namespace Api { buttonText: string; sponsorInfo?: string; additionalInfo?: string; - CONSTRUCTOR_ID: 1301522832; + minDisplayDuration?: int; + maxDisplayDuration?: int; + CONSTRUCTOR_ID: 2109703795; SUBCLASS_OF_ID: 3780630582; className: 'SponsoredMessage'; @@ -15611,6 +15668,7 @@ namespace Api { export class MonoForumDialog extends VirtualClass<{ // flags: Api.Type; unreadMark?: true; + nopaidMessagesException?: true; peer: Api.TypePeer; topMessage: int; readInboxMaxId: int; @@ -15621,6 +15679,7 @@ namespace Api { }> { // flags: Api.Type; unreadMark?: true; + nopaidMessagesException?: true; peer: Api.TypePeer; topMessage: int; readInboxMaxId: int; @@ -17230,6 +17289,50 @@ namespace Api { static fromReader(reader: Reader): PendingSuggestion; } + export class TodoItem extends VirtualClass<{ + id: int; + title: Api.TypeTextWithEntities; + }> { + id: int; + title: Api.TypeTextWithEntities; + CONSTRUCTOR_ID: 3416892719; + SUBCLASS_OF_ID: 3755665077; + className: 'TodoItem'; + + static fromReader(reader: Reader): TodoItem; + } + export class TodoList extends VirtualClass<{ + // flags: Api.Type; + othersCanAppend?: true; + othersCanComplete?: true; + title: Api.TypeTextWithEntities; + list: Api.TypeTodoItem[]; + }> { + // flags: Api.Type; + othersCanAppend?: true; + othersCanComplete?: true; + title: Api.TypeTextWithEntities; + list: Api.TypeTodoItem[]; + CONSTRUCTOR_ID: 1236871718; + SUBCLASS_OF_ID: 2215197619; + className: 'TodoList'; + + static fromReader(reader: Reader): TodoList; + } + export class TodoCompletion extends VirtualClass<{ + id: int; + completedBy: long; + date: int; + }> { + id: int; + completedBy: long; + date: int; + CONSTRUCTOR_ID: 1287725239; + SUBCLASS_OF_ID: 3135658875; + className: 'TodoCompletion'; + + static fromReader(reader: Reader): TodoCompletion; + } export class ResPQ extends VirtualClass<{ nonce: int128; serverNonce: int128; @@ -19100,16 +19203,20 @@ namespace Api { export class SponsoredMessages extends VirtualClass<{ // flags: Api.Type; postsBetween?: int; + startDelay?: int; + betweenDelay?: int; messages: Api.TypeSponsoredMessage[]; chats: Api.TypeChat[]; users: Api.TypeUser[]; }> { // flags: Api.Type; postsBetween?: int; + startDelay?: int; + betweenDelay?: int; messages: Api.TypeSponsoredMessage[]; chats: Api.TypeChat[]; users: Api.TypeUser[]; - CONSTRUCTOR_ID: 3387825543; + CONSTRUCTOR_ID: 4292502893; SUBCLASS_OF_ID: 2134993376; className: 'SponsoredMessages'; @@ -23021,18 +23128,26 @@ namespace Api { }, account.TypeEmojiStatuses> { hash: long; } - export class AddNoPaidMessagesException extends Request<{ + export class GetPaidMessagesRevenue extends Request<{ + // flags: Api.Type; + parentPeer?: Api.TypeInputPeer; + userId: Api.TypeInputUser; + }, account.TypePaidMessagesRevenue> { + // flags: Api.Type; + parentPeer?: Api.TypeInputPeer; + userId: Api.TypeInputUser; + } + export class ToggleNoPaidMessagesException extends Request<{ // flags: Api.Type; refundCharged?: true; + requirePayment?: true; + parentPeer?: Api.TypeInputPeer; userId: Api.TypeInputUser; }, Bool> { // flags: Api.Type; refundCharged?: true; - userId: Api.TypeInputUser; - } - export class GetPaidMessagesRevenue extends Request<{ - userId: Api.TypeInputUser; - }, account.TypePaidMessagesRevenue> { + requirePayment?: true; + parentPeer?: Api.TypeInputPeer; userId: Api.TypeInputUser; } } @@ -25470,9 +25585,13 @@ namespace Api { option: bytes; } export class GetSponsoredMessages extends Request<{ + // flags: Api.Type; peer: Api.TypeInputPeer; + msgId?: int; }, messages.TypeSponsoredMessages> { + // flags: Api.Type; peer: Api.TypeInputPeer; + msgId?: int; } export class SavePreparedInlineMessage extends Request<{ // flags: Api.Type; @@ -25540,6 +25659,26 @@ namespace Api { peer: Api.TypeInputPeer; maxId: int; } + export class ToggleTodoCompleted extends Request<{ + peer: Api.TypeInputPeer; + msgId: int; + completed: int[]; + incompleted: int[]; + }, Api.TypeUpdates> { + peer: Api.TypeInputPeer; + msgId: int; + completed: int[]; + incompleted: int[]; + } + export class AppendTodoList extends Request<{ + peer: Api.TypeInputPeer; + msgId: int; + list: Api.TypeTodoItem[]; + }, Api.TypeUpdates> { + peer: Api.TypeInputPeer; + msgId: int; + list: Api.TypeTodoItem[]; + } } export namespace updates { @@ -28018,10 +28157,10 @@ namespace Api { export type AnyRequest = InvokeAfterMsg | InvokeAfterMsgs | InitConnection | InvokeWithLayer | InvokeWithoutUpdates | InvokeWithMessagesRange | InvokeWithTakeout | InvokeWithBusinessConnection | InvokeWithGooglePlayIntegrity | InvokeWithApnsSecret | InvokeWithReCaptcha | ReqPq | ReqPqMulti | ReqPqMultiNew | ReqDHParams | SetClientDHParams | DestroyAuthKey | RpcDropAnswer | GetFutureSalts | Ping | PingDelayDisconnect | DestroySession | auth.SendCode | auth.SignUp | auth.SignIn | auth.LogOut | auth.ResetAuthorizations | auth.ExportAuthorization | auth.ImportAuthorization | auth.BindTempAuthKey | auth.ImportBotAuthorization | auth.CheckPassword | auth.RequestPasswordRecovery | auth.RecoverPassword | auth.ResendCode | auth.CancelCode | auth.DropTempAuthKeys | auth.ExportLoginToken | auth.ImportLoginToken | auth.AcceptLoginToken | auth.CheckRecoveryPassword | auth.ImportWebTokenAuthorization | auth.RequestFirebaseSms | auth.ResetLoginEmail | auth.ReportMissingCode - | account.RegisterDevice | account.UnregisterDevice | account.UpdateNotifySettings | account.GetNotifySettings | account.ResetNotifySettings | account.UpdateProfile | account.UpdateStatus | account.GetWallPapers | account.ReportPeer | account.CheckUsername | account.UpdateUsername | account.GetPrivacy | account.SetPrivacy | account.DeleteAccount | account.GetAccountTTL | account.SetAccountTTL | account.SendChangePhoneCode | account.ChangePhone | account.UpdateDeviceLocked | account.GetAuthorizations | account.ResetAuthorization | account.GetPassword | account.GetPasswordSettings | account.UpdatePasswordSettings | account.SendConfirmPhoneCode | account.ConfirmPhone | account.GetTmpPassword | account.GetWebAuthorizations | account.ResetWebAuthorization | account.ResetWebAuthorizations | account.GetAllSecureValues | account.GetSecureValue | account.SaveSecureValue | account.DeleteSecureValue | account.GetAuthorizationForm | account.AcceptAuthorization | account.SendVerifyPhoneCode | account.VerifyPhone | account.SendVerifyEmailCode | account.VerifyEmail | account.InitTakeoutSession | account.FinishTakeoutSession | account.ConfirmPasswordEmail | account.ResendPasswordEmail | account.CancelPasswordEmail | account.GetContactSignUpNotification | account.SetContactSignUpNotification | account.GetNotifyExceptions | account.GetWallPaper | account.UploadWallPaper | account.SaveWallPaper | account.InstallWallPaper | account.ResetWallPapers | account.GetAutoDownloadSettings | account.SaveAutoDownloadSettings | account.UploadTheme | account.CreateTheme | account.UpdateTheme | account.SaveTheme | account.InstallTheme | account.GetTheme | account.GetThemes | account.SetContentSettings | account.GetContentSettings | account.GetMultiWallPapers | account.GetGlobalPrivacySettings | account.SetGlobalPrivacySettings | account.ReportProfilePhoto | account.ResetPassword | account.DeclinePasswordReset | account.GetChatThemes | account.SetAuthorizationTTL | account.ChangeAuthorizationSettings | account.GetSavedRingtones | account.SaveRingtone | account.UploadRingtone | account.UpdateEmojiStatus | account.GetDefaultEmojiStatuses | account.GetRecentEmojiStatuses | account.ClearRecentEmojiStatuses | account.ReorderUsernames | account.ToggleUsername | account.GetDefaultProfilePhotoEmojis | account.GetDefaultGroupPhotoEmojis | account.GetAutoSaveSettings | account.SaveAutoSaveSettings | account.DeleteAutoSaveExceptions | account.InvalidateSignInCodes | account.UpdateColor | account.GetDefaultBackgroundEmojis | account.GetChannelDefaultEmojiStatuses | account.GetChannelRestrictedStatusEmojis | account.UpdateBusinessWorkHours | account.UpdateBusinessLocation | account.UpdateBusinessGreetingMessage | account.UpdateBusinessAwayMessage | account.UpdateConnectedBot | account.GetConnectedBots | account.GetBotBusinessConnection | account.UpdateBusinessIntro | account.ToggleConnectedBotPaused | account.DisablePeerConnectedBot | account.UpdateBirthday | account.CreateBusinessChatLink | account.EditBusinessChatLink | account.DeleteBusinessChatLink | account.GetBusinessChatLinks | account.ResolveBusinessChatLink | account.UpdatePersonalChannel | account.ToggleSponsoredMessages | account.GetReactionsNotifySettings | account.SetReactionsNotifySettings | account.GetCollectibleEmojiStatuses | account.AddNoPaidMessagesException | account.GetPaidMessagesRevenue + | account.RegisterDevice | account.UnregisterDevice | account.UpdateNotifySettings | account.GetNotifySettings | account.ResetNotifySettings | account.UpdateProfile | account.UpdateStatus | account.GetWallPapers | account.ReportPeer | account.CheckUsername | account.UpdateUsername | account.GetPrivacy | account.SetPrivacy | account.DeleteAccount | account.GetAccountTTL | account.SetAccountTTL | account.SendChangePhoneCode | account.ChangePhone | account.UpdateDeviceLocked | account.GetAuthorizations | account.ResetAuthorization | account.GetPassword | account.GetPasswordSettings | account.UpdatePasswordSettings | account.SendConfirmPhoneCode | account.ConfirmPhone | account.GetTmpPassword | account.GetWebAuthorizations | account.ResetWebAuthorization | account.ResetWebAuthorizations | account.GetAllSecureValues | account.GetSecureValue | account.SaveSecureValue | account.DeleteSecureValue | account.GetAuthorizationForm | account.AcceptAuthorization | account.SendVerifyPhoneCode | account.VerifyPhone | account.SendVerifyEmailCode | account.VerifyEmail | account.InitTakeoutSession | account.FinishTakeoutSession | account.ConfirmPasswordEmail | account.ResendPasswordEmail | account.CancelPasswordEmail | account.GetContactSignUpNotification | account.SetContactSignUpNotification | account.GetNotifyExceptions | account.GetWallPaper | account.UploadWallPaper | account.SaveWallPaper | account.InstallWallPaper | account.ResetWallPapers | account.GetAutoDownloadSettings | account.SaveAutoDownloadSettings | account.UploadTheme | account.CreateTheme | account.UpdateTheme | account.SaveTheme | account.InstallTheme | account.GetTheme | account.GetThemes | account.SetContentSettings | account.GetContentSettings | account.GetMultiWallPapers | account.GetGlobalPrivacySettings | account.SetGlobalPrivacySettings | account.ReportProfilePhoto | account.ResetPassword | account.DeclinePasswordReset | account.GetChatThemes | account.SetAuthorizationTTL | account.ChangeAuthorizationSettings | account.GetSavedRingtones | account.SaveRingtone | account.UploadRingtone | account.UpdateEmojiStatus | account.GetDefaultEmojiStatuses | account.GetRecentEmojiStatuses | account.ClearRecentEmojiStatuses | account.ReorderUsernames | account.ToggleUsername | account.GetDefaultProfilePhotoEmojis | account.GetDefaultGroupPhotoEmojis | account.GetAutoSaveSettings | account.SaveAutoSaveSettings | account.DeleteAutoSaveExceptions | account.InvalidateSignInCodes | account.UpdateColor | account.GetDefaultBackgroundEmojis | account.GetChannelDefaultEmojiStatuses | account.GetChannelRestrictedStatusEmojis | account.UpdateBusinessWorkHours | account.UpdateBusinessLocation | account.UpdateBusinessGreetingMessage | account.UpdateBusinessAwayMessage | account.UpdateConnectedBot | account.GetConnectedBots | account.GetBotBusinessConnection | account.UpdateBusinessIntro | account.ToggleConnectedBotPaused | account.DisablePeerConnectedBot | account.UpdateBirthday | account.CreateBusinessChatLink | account.EditBusinessChatLink | account.DeleteBusinessChatLink | account.GetBusinessChatLinks | account.ResolveBusinessChatLink | account.UpdatePersonalChannel | account.ToggleSponsoredMessages | account.GetReactionsNotifySettings | account.SetReactionsNotifySettings | account.GetCollectibleEmojiStatuses | account.GetPaidMessagesRevenue | account.ToggleNoPaidMessagesException | users.GetUsers | users.GetFullUser | users.SetSecureValueErrors | users.GetRequirementsToContact | contacts.GetContactIDs | contacts.GetStatuses | contacts.GetContacts | contacts.ImportContacts | contacts.DeleteContacts | contacts.DeleteByPhones | contacts.Block | contacts.Unblock | contacts.GetBlocked | contacts.Search | contacts.ResolveUsername | contacts.GetTopPeers | contacts.ResetTopPeerRating | contacts.ResetSaved | contacts.GetSaved | contacts.ToggleTopPeers | contacts.AddContact | contacts.AcceptContact | contacts.GetLocated | contacts.BlockFromReplies | contacts.ResolvePhone | contacts.ExportContactToken | contacts.ImportContactToken | contacts.EditCloseFriends | contacts.SetBlocked | contacts.GetBirthdays | contacts.GetSponsoredPeers - | messages.GetMessages | messages.GetDialogs | messages.GetHistory | messages.Search | messages.ReadHistory | messages.DeleteHistory | messages.DeleteMessages | messages.ReceivedMessages | messages.SetTyping | messages.SendMessage | messages.SendMedia | messages.ForwardMessages | messages.ReportSpam | messages.GetPeerSettings | messages.Report | messages.GetChats | messages.GetFullChat | messages.EditChatTitle | messages.EditChatPhoto | messages.AddChatUser | messages.DeleteChatUser | messages.CreateChat | messages.GetDhConfig | messages.RequestEncryption | messages.AcceptEncryption | messages.DiscardEncryption | messages.SetEncryptedTyping | messages.ReadEncryptedHistory | messages.SendEncrypted | messages.SendEncryptedFile | messages.SendEncryptedService | messages.ReceivedQueue | messages.ReportEncryptedSpam | messages.ReadMessageContents | messages.GetStickers | messages.GetAllStickers | messages.GetWebPagePreview | messages.ExportChatInvite | messages.CheckChatInvite | messages.ImportChatInvite | messages.GetStickerSet | messages.InstallStickerSet | messages.UninstallStickerSet | messages.StartBot | messages.GetMessagesViews | messages.EditChatAdmin | messages.MigrateChat | messages.SearchGlobal | messages.ReorderStickerSets | messages.GetDocumentByHash | messages.GetSavedGifs | messages.SaveGif | messages.GetInlineBotResults | messages.SetInlineBotResults | messages.SendInlineBotResult | messages.GetMessageEditData | messages.EditMessage | messages.EditInlineBotMessage | messages.GetBotCallbackAnswer | messages.SetBotCallbackAnswer | messages.GetPeerDialogs | messages.SaveDraft | messages.GetAllDrafts | messages.GetFeaturedStickers | messages.ReadFeaturedStickers | messages.GetRecentStickers | messages.SaveRecentSticker | messages.ClearRecentStickers | messages.GetArchivedStickers | messages.GetMaskStickers | messages.GetAttachedStickers | messages.SetGameScore | messages.SetInlineGameScore | messages.GetGameHighScores | messages.GetInlineGameHighScores | messages.GetCommonChats | messages.GetWebPage | messages.ToggleDialogPin | messages.ReorderPinnedDialogs | messages.GetPinnedDialogs | messages.SetBotShippingResults | messages.SetBotPrecheckoutResults | messages.UploadMedia | messages.SendScreenshotNotification | messages.GetFavedStickers | messages.FaveSticker | messages.GetUnreadMentions | messages.ReadMentions | messages.GetRecentLocations | messages.SendMultiMedia | messages.UploadEncryptedFile | messages.SearchStickerSets | messages.GetSplitRanges | messages.MarkDialogUnread | messages.GetDialogUnreadMarks | messages.ClearAllDrafts | messages.UpdatePinnedMessage | messages.SendVote | messages.GetPollResults | messages.GetOnlines | messages.EditChatAbout | messages.EditChatDefaultBannedRights | messages.GetEmojiKeywords | messages.GetEmojiKeywordsDifference | messages.GetEmojiKeywordsLanguages | messages.GetEmojiURL | messages.GetSearchCounters | messages.RequestUrlAuth | messages.AcceptUrlAuth | messages.HidePeerSettingsBar | messages.GetScheduledHistory | messages.GetScheduledMessages | messages.SendScheduledMessages | messages.DeleteScheduledMessages | messages.GetPollVotes | messages.ToggleStickerSets | messages.GetDialogFilters | messages.GetSuggestedDialogFilters | messages.UpdateDialogFilter | messages.UpdateDialogFiltersOrder | messages.GetOldFeaturedStickers | messages.GetReplies | messages.GetDiscussionMessage | messages.ReadDiscussion | messages.UnpinAllMessages | messages.DeleteChat | messages.DeletePhoneCallHistory | messages.CheckHistoryImport | messages.InitHistoryImport | messages.UploadImportedMedia | messages.StartHistoryImport | messages.GetExportedChatInvites | messages.GetExportedChatInvite | messages.EditExportedChatInvite | messages.DeleteRevokedExportedChatInvites | messages.DeleteExportedChatInvite | messages.GetAdminsWithInvites | messages.GetChatInviteImporters | messages.SetHistoryTTL | messages.CheckHistoryImportPeer | messages.SetChatTheme | messages.GetMessageReadParticipants | messages.GetSearchResultsCalendar | messages.GetSearchResultsPositions | messages.HideChatJoinRequest | messages.HideAllChatJoinRequests | messages.ToggleNoForwards | messages.SaveDefaultSendAs | messages.SendReaction | messages.GetMessagesReactions | messages.GetMessageReactionsList | messages.SetChatAvailableReactions | messages.GetAvailableReactions | messages.SetDefaultReaction | messages.TranslateText | messages.GetUnreadReactions | messages.ReadReactions | messages.SearchSentMedia | messages.GetAttachMenuBots | messages.GetAttachMenuBot | messages.ToggleBotInAttachMenu | messages.RequestWebView | messages.ProlongWebView | messages.RequestSimpleWebView | messages.SendWebViewResultMessage | messages.SendWebViewData | messages.TranscribeAudio | messages.RateTranscribedAudio | messages.GetCustomEmojiDocuments | messages.GetEmojiStickers | messages.GetFeaturedEmojiStickers | messages.ReportReaction | messages.GetTopReactions | messages.GetRecentReactions | messages.ClearRecentReactions | messages.GetExtendedMedia | messages.SetDefaultHistoryTTL | messages.GetDefaultHistoryTTL | messages.SendBotRequestedPeer | messages.GetEmojiGroups | messages.GetEmojiStatusGroups | messages.GetEmojiProfilePhotoGroups | messages.SearchCustomEmoji | messages.TogglePeerTranslations | messages.GetBotApp | messages.RequestAppWebView | messages.SetChatWallPaper | messages.SearchEmojiStickerSets | messages.GetSavedDialogs | messages.GetSavedHistory | messages.DeleteSavedHistory | messages.GetPinnedSavedDialogs | messages.ToggleSavedDialogPin | messages.ReorderPinnedSavedDialogs | messages.GetSavedReactionTags | messages.UpdateSavedReactionTag | messages.GetDefaultTagReactions | messages.GetOutboxReadDate | messages.GetQuickReplies | messages.ReorderQuickReplies | messages.CheckQuickReplyShortcut | messages.EditQuickReplyShortcut | messages.DeleteQuickReplyShortcut | messages.GetQuickReplyMessages | messages.SendQuickReplyMessages | messages.DeleteQuickReplyMessages | messages.ToggleDialogFilterTags | messages.GetMyStickers | messages.GetEmojiStickerGroups | messages.GetAvailableEffects | messages.EditFactCheck | messages.DeleteFactCheck | messages.GetFactCheck | messages.RequestMainWebView | messages.SendPaidReaction | messages.TogglePaidReactionPrivacy | messages.GetPaidReactionPrivacy | messages.ViewSponsoredMessage | messages.ClickSponsoredMessage | messages.ReportSponsoredMessage | messages.GetSponsoredMessages | messages.SavePreparedInlineMessage | messages.GetPreparedInlineMessage | messages.SearchStickers | messages.ReportMessagesDelivery | messages.GetSavedDialogsByID | messages.ReadSavedHistory + | messages.GetMessages | messages.GetDialogs | messages.GetHistory | messages.Search | messages.ReadHistory | messages.DeleteHistory | messages.DeleteMessages | messages.ReceivedMessages | messages.SetTyping | messages.SendMessage | messages.SendMedia | messages.ForwardMessages | messages.ReportSpam | messages.GetPeerSettings | messages.Report | messages.GetChats | messages.GetFullChat | messages.EditChatTitle | messages.EditChatPhoto | messages.AddChatUser | messages.DeleteChatUser | messages.CreateChat | messages.GetDhConfig | messages.RequestEncryption | messages.AcceptEncryption | messages.DiscardEncryption | messages.SetEncryptedTyping | messages.ReadEncryptedHistory | messages.SendEncrypted | messages.SendEncryptedFile | messages.SendEncryptedService | messages.ReceivedQueue | messages.ReportEncryptedSpam | messages.ReadMessageContents | messages.GetStickers | messages.GetAllStickers | messages.GetWebPagePreview | messages.ExportChatInvite | messages.CheckChatInvite | messages.ImportChatInvite | messages.GetStickerSet | messages.InstallStickerSet | messages.UninstallStickerSet | messages.StartBot | messages.GetMessagesViews | messages.EditChatAdmin | messages.MigrateChat | messages.SearchGlobal | messages.ReorderStickerSets | messages.GetDocumentByHash | messages.GetSavedGifs | messages.SaveGif | messages.GetInlineBotResults | messages.SetInlineBotResults | messages.SendInlineBotResult | messages.GetMessageEditData | messages.EditMessage | messages.EditInlineBotMessage | messages.GetBotCallbackAnswer | messages.SetBotCallbackAnswer | messages.GetPeerDialogs | messages.SaveDraft | messages.GetAllDrafts | messages.GetFeaturedStickers | messages.ReadFeaturedStickers | messages.GetRecentStickers | messages.SaveRecentSticker | messages.ClearRecentStickers | messages.GetArchivedStickers | messages.GetMaskStickers | messages.GetAttachedStickers | messages.SetGameScore | messages.SetInlineGameScore | messages.GetGameHighScores | messages.GetInlineGameHighScores | messages.GetCommonChats | messages.GetWebPage | messages.ToggleDialogPin | messages.ReorderPinnedDialogs | messages.GetPinnedDialogs | messages.SetBotShippingResults | messages.SetBotPrecheckoutResults | messages.UploadMedia | messages.SendScreenshotNotification | messages.GetFavedStickers | messages.FaveSticker | messages.GetUnreadMentions | messages.ReadMentions | messages.GetRecentLocations | messages.SendMultiMedia | messages.UploadEncryptedFile | messages.SearchStickerSets | messages.GetSplitRanges | messages.MarkDialogUnread | messages.GetDialogUnreadMarks | messages.ClearAllDrafts | messages.UpdatePinnedMessage | messages.SendVote | messages.GetPollResults | messages.GetOnlines | messages.EditChatAbout | messages.EditChatDefaultBannedRights | messages.GetEmojiKeywords | messages.GetEmojiKeywordsDifference | messages.GetEmojiKeywordsLanguages | messages.GetEmojiURL | messages.GetSearchCounters | messages.RequestUrlAuth | messages.AcceptUrlAuth | messages.HidePeerSettingsBar | messages.GetScheduledHistory | messages.GetScheduledMessages | messages.SendScheduledMessages | messages.DeleteScheduledMessages | messages.GetPollVotes | messages.ToggleStickerSets | messages.GetDialogFilters | messages.GetSuggestedDialogFilters | messages.UpdateDialogFilter | messages.UpdateDialogFiltersOrder | messages.GetOldFeaturedStickers | messages.GetReplies | messages.GetDiscussionMessage | messages.ReadDiscussion | messages.UnpinAllMessages | messages.DeleteChat | messages.DeletePhoneCallHistory | messages.CheckHistoryImport | messages.InitHistoryImport | messages.UploadImportedMedia | messages.StartHistoryImport | messages.GetExportedChatInvites | messages.GetExportedChatInvite | messages.EditExportedChatInvite | messages.DeleteRevokedExportedChatInvites | messages.DeleteExportedChatInvite | messages.GetAdminsWithInvites | messages.GetChatInviteImporters | messages.SetHistoryTTL | messages.CheckHistoryImportPeer | messages.SetChatTheme | messages.GetMessageReadParticipants | messages.GetSearchResultsCalendar | messages.GetSearchResultsPositions | messages.HideChatJoinRequest | messages.HideAllChatJoinRequests | messages.ToggleNoForwards | messages.SaveDefaultSendAs | messages.SendReaction | messages.GetMessagesReactions | messages.GetMessageReactionsList | messages.SetChatAvailableReactions | messages.GetAvailableReactions | messages.SetDefaultReaction | messages.TranslateText | messages.GetUnreadReactions | messages.ReadReactions | messages.SearchSentMedia | messages.GetAttachMenuBots | messages.GetAttachMenuBot | messages.ToggleBotInAttachMenu | messages.RequestWebView | messages.ProlongWebView | messages.RequestSimpleWebView | messages.SendWebViewResultMessage | messages.SendWebViewData | messages.TranscribeAudio | messages.RateTranscribedAudio | messages.GetCustomEmojiDocuments | messages.GetEmojiStickers | messages.GetFeaturedEmojiStickers | messages.ReportReaction | messages.GetTopReactions | messages.GetRecentReactions | messages.ClearRecentReactions | messages.GetExtendedMedia | messages.SetDefaultHistoryTTL | messages.GetDefaultHistoryTTL | messages.SendBotRequestedPeer | messages.GetEmojiGroups | messages.GetEmojiStatusGroups | messages.GetEmojiProfilePhotoGroups | messages.SearchCustomEmoji | messages.TogglePeerTranslations | messages.GetBotApp | messages.RequestAppWebView | messages.SetChatWallPaper | messages.SearchEmojiStickerSets | messages.GetSavedDialogs | messages.GetSavedHistory | messages.DeleteSavedHistory | messages.GetPinnedSavedDialogs | messages.ToggleSavedDialogPin | messages.ReorderPinnedSavedDialogs | messages.GetSavedReactionTags | messages.UpdateSavedReactionTag | messages.GetDefaultTagReactions | messages.GetOutboxReadDate | messages.GetQuickReplies | messages.ReorderQuickReplies | messages.CheckQuickReplyShortcut | messages.EditQuickReplyShortcut | messages.DeleteQuickReplyShortcut | messages.GetQuickReplyMessages | messages.SendQuickReplyMessages | messages.DeleteQuickReplyMessages | messages.ToggleDialogFilterTags | messages.GetMyStickers | messages.GetEmojiStickerGroups | messages.GetAvailableEffects | messages.EditFactCheck | messages.DeleteFactCheck | messages.GetFactCheck | messages.RequestMainWebView | messages.SendPaidReaction | messages.TogglePaidReactionPrivacy | messages.GetPaidReactionPrivacy | messages.ViewSponsoredMessage | messages.ClickSponsoredMessage | messages.ReportSponsoredMessage | messages.GetSponsoredMessages | messages.SavePreparedInlineMessage | messages.GetPreparedInlineMessage | messages.SearchStickers | messages.ReportMessagesDelivery | messages.GetSavedDialogsByID | messages.ReadSavedHistory | messages.ToggleTodoCompleted | messages.AppendTodoList | updates.GetState | updates.GetDifference | updates.GetChannelDifference | photos.UpdateProfilePhoto | photos.UploadProfilePhoto | photos.DeletePhotos | photos.GetUserPhotos | photos.UploadContactProfilePhoto | upload.SaveFilePart | upload.GetFile | upload.SaveBigFilePart | upload.GetWebFile | upload.GetCdnFile | upload.ReuploadCdnFile | upload.GetCdnFileHashes | upload.GetFileHashes diff --git a/src/lib/gramjs/tl/apiTl.ts b/src/lib/gramjs/tl/apiTl.ts index 171743900..93cfcf53d 100644 --- a/src/lib/gramjs/tl/apiTl.ts +++ b/src/lib/gramjs/tl/apiTl.ts @@ -37,6 +37,7 @@ inputMediaDice#e66fbf7b emoticon:string = InputMedia; inputMediaStory#89fdd778 peer:InputPeer id:int = InputMedia; inputMediaWebPage#c21b8849 flags:# force_large_media:flags.0?true force_small_media:flags.1?true optional:flags.2?true url:string = InputMedia; inputMediaPaidMedia#c4103386 flags:# stars_amount:long extended_media:Vector payload:flags.0?string = InputMedia; +inputMediaTodo#9fc55fde todo:TodoList = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto; inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto; @@ -83,7 +84,7 @@ chatForbidden#6592a1a7 id:long title:string = Chat; channel#fe685355 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true signature_profiles:flags2.12?true autotranslation:flags2.15?true broadcast_messages_allowed:flags2.16?true monoforum:flags2.17?true forum_tabs:flags2.19?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int subscription_until_date:flags2.11?int bot_verification_icon:flags2.13?long send_paid_messages_stars:flags2.14?long linked_monoforum_id:flags2.18?long = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull; -channelFull#52d6806b flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int = ChatFull; +channelFull#e07429de flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int send_paid_messages_stars:flags2.21?long = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; chatParticipantAdmin#a0933f5b user_id:long inviter_id:long date:int = ChatParticipant; @@ -93,7 +94,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; message#eabcdd4d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true video_processing_pending:flags2.4?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck report_delivery_until_date:flags2.5?int paid_message_stars:flags2.6?long = Message; -messageService#d3d28540 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true reactions_are_possible:flags.9?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction reactions:flags.20?MessageReactions ttl_period:flags.25?int = Message; +messageService#7a800e0a flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true reactions_are_possible:flags.9?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer saved_peer_id:flags.28?Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction reactions:flags.20?MessageReactions ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; messageMediaPhoto#695150d7 flags:# spoiler:flags.3?true photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; @@ -111,6 +112,7 @@ messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int sto messageMediaGiveaway#aa073beb flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.2?true channels:Vector countries_iso2:flags.1?Vector prize_description:flags.3?string quantity:int months:flags.4?int stars:flags.5?long until_date:int = MessageMedia; messageMediaGiveawayResults#ceaa3ea1 flags:# only_new_subscribers:flags.0?true refunded:flags.2?true channel_id:long additional_peers_count:flags.3?int launch_msg_id:int winners_count:int unclaimed_count:int winners:Vector months:flags.4?int stars:flags.5?long prize_description:flags.1?string until_date:int = MessageMedia; messageMediaPaidMedia#a8852491 stars_amount:long extended_media:Vector = MessageMedia; +messageMediaToDo#8a53b014 flags:# todo:TodoList completions:flags.0?Vector = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#bd47cbad title:string users:Vector = MessageAction; messageActionChatEditTitle#b5a1ce5a title:string = MessageAction; @@ -162,6 +164,8 @@ messageActionStarGiftUnique#2e3ae60e flags:# upgrade:flags.0?true transferred:fl messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction; messageActionPaidMessagesPrice#84b88578 flags:# broadcast_messages_allowed:flags.0?true stars:long = MessageAction; messageActionConferenceCall#2ffe2f7a flags:# missed:flags.0?true active:flags.1?true video:flags.4?true call_id:long duration:flags.2?int other_participants:flags.3?Vector = MessageAction; +messageActionTodoCompletions#cc7c5c89 completed:Vector incompleted:Vector = MessageAction; +messageActionTodoAppendTasks#c7edbc83 list:Vector = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; photoEmpty#2331b22d id:long = Photo; @@ -1087,8 +1091,8 @@ botCommandScopePeerUser#a1321f3 peer:InputPeer user_id:InputUser = BotCommandSco account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordResult; account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -sponsoredMessage#4d93a990 flags:# recommended:flags.5?true can_report:flags.12?true random_id:bytes url:string title:string message:string entities:flags.1?Vector photo:flags.6?Photo media:flags.14?MessageMedia color:flags.13?PeerColor button_text:string sponsor_info:flags.7?string additional_info:flags.8?string = SponsoredMessage; -messages.sponsoredMessages#c9ee1d87 flags:# posts_between:flags.0?int messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; +sponsoredMessage#7dbf8673 flags:# recommended:flags.5?true can_report:flags.12?true random_id:bytes url:string title:string message:string entities:flags.1?Vector photo:flags.6?Photo media:flags.14?MessageMedia color:flags.13?PeerColor button_text:string sponsor_info:flags.7?string additional_info:flags.8?string min_display_duration:flags.15?int max_display_duration:flags.15?int = SponsoredMessage; +messages.sponsoredMessages#ffda656d flags:# posts_between:flags.0?int start_delay:flags.1?int between_delay:flags.2?int messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; messages.sponsoredMessagesEmpty#1839490f = messages.SponsoredMessages; searchResultsCalendarPeriod#c9b0539f date:int min_msg_id:int max_msg_id:int count:int = SearchResultsCalendarPeriod; messages.searchResultsCalendar#147ee23c flags:# inexact:flags.0?true count:int min_date:int min_msg_id:int offset_id_offset:flags.1?int periods:Vector messages:Vector chats:Vector users:Vector = messages.SearchResultsCalendar; @@ -1283,7 +1287,7 @@ storyReactionPublicForward#bbab2643 message:Message = StoryReaction; storyReactionPublicRepost#cfcd0f13 peer_id:Peer story:StoryItem = StoryReaction; stories.storyReactionsList#aa5f789c flags:# count:int reactions:Vector chats:Vector users:Vector next_offset:flags.0?string = stories.StoryReactionsList; savedDialog#bd87cb6c flags:# pinned:flags.2?true peer:Peer top_message:int = SavedDialog; -monoForumDialog#64407ea7 flags:# unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_reactions_count:int draft:flags.1?DraftMessage = SavedDialog; +monoForumDialog#64407ea7 flags:# unread_mark:flags.3?true nopaid_messages_exception:flags.4?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_reactions_count:int draft:flags.1?DraftMessage = SavedDialog; messages.savedDialogs#f83ae221 dialogs:Vector messages:Vector chats:Vector users:Vector = messages.SavedDialogs; messages.savedDialogsSlice#44ba9dd9 count:int dialogs:Vector messages:Vector chats:Vector users:Vector = messages.SavedDialogs; messages.savedDialogsNotModified#c01f6fe8 count:int = messages.SavedDialogs; @@ -1438,6 +1442,9 @@ starGiftAttributeCounter#2eb1b658 attribute:StarGiftAttributeId count:int = Star payments.resaleStarGifts#947a12df flags:# count:int gifts:Vector next_offset:flags.0?string attributes:flags.1?Vector attributes_hash:flags.1?long chats:Vector counters:flags.2?Vector users:Vector = payments.ResaleStarGifts; stories.canSendStoryCount#c387c04e count_remains:int = stories.CanSendStoryCount; pendingSuggestion#e7e82e12 suggestion:string title:TextWithEntities description:TextWithEntities url:string = PendingSuggestion; +todoItem#cba9a52f id:int title:TextWithEntities = TodoItem; +todoList#49b92a26 flags:# others_can_append:flags.0?true others_can_complete:flags.1?true title:TextWithEntities list:Vector = TodoList; +todoCompletion#4cc120b7 id:int completed_by:long date:int = TodoCompletion; ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X; @@ -1503,8 +1510,7 @@ account.toggleUsername#58d6b376 username:string active:Bool = Bool; account.resolveBusinessChatLink#5492e5ee slug:string = account.ResolvedBusinessChatLinks; account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool; account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses; -account.addNoPaidMessagesException#6f688aa7 flags:# refund_charged:flags.0?true user_id:InputUser = Bool; -account.getPaidMessagesRevenue#f1266f38 user_id:InputUser = account.PaidMessagesRevenue; +account.getPaidMessagesRevenue#19ba4a67 flags:# parent_peer:flags.0?InputPeer user_id:InputUser = account.PaidMessagesRevenue; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; contacts.getContacts#5dd69e12 hash:long = contacts.Contacts; @@ -1663,9 +1669,11 @@ messages.getPaidReactionPrivacy#472455aa = Updates; messages.viewSponsoredMessage#269e3643 random_id:bytes = Bool; messages.clickSponsoredMessage#8235057e flags:# media:flags.0?true fullscreen:flags.1?true random_id:bytes = Bool; messages.reportSponsoredMessage#12cbf0c4 random_id:bytes option:bytes = channels.SponsoredMessageReportResult; -messages.getSponsoredMessages#9bd2f439 peer:InputPeer = messages.SponsoredMessages; +messages.getSponsoredMessages#3d6ce850 flags:# peer:InputPeer msg_id:flags.0?int = messages.SponsoredMessages; messages.getPreparedInlineMessage#857ebdb8 bot:InputUser id:string = messages.PreparedInlineMessage; messages.reportMessagesDelivery#5a6d7395 flags:# push:flags.0?true peer:InputPeer id:Vector = Bool; +messages.toggleTodoCompleted#d3e03124 peer:InputPeer msg_id:int completed:Vector incompleted:Vector = Updates; +messages.appendTodoList#21a61057 peer:InputPeer msg_id:int list:Vector = Updates; updates.getState#edd4882a = updates.State; updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; diff --git a/src/lib/gramjs/tl/static/api.json b/src/lib/gramjs/tl/static/api.json index 7a4005102..8cd431ff4 100644 --- a/src/lib/gramjs/tl/static/api.json +++ b/src/lib/gramjs/tl/static/api.json @@ -61,7 +61,7 @@ "account.resolveBusinessChatLink", "account.toggleSponsoredMessages", "account.getCollectibleEmojiStatuses", - "account.addNoPaidMessagesException", + "account.toggleNoPaidMessagesException ", "account.getPaidMessagesRevenue", "account.getAccountTTL", "account.setAccountTTL", @@ -225,6 +225,8 @@ "messages.getSponsoredMessages", "messages.reportMessagesDelivery", "messages.getPreparedInlineMessage", + "messages.toggleTodoCompleted", + "messages.appendTodoList", "updates.getState", "updates.getDifference", "updates.getChannelDifference", diff --git a/src/lib/gramjs/tl/static/api.tl b/src/lib/gramjs/tl/static/api.tl index f3e89ea93..17b1608ba 100644 --- a/src/lib/gramjs/tl/static/api.tl +++ b/src/lib/gramjs/tl/static/api.tl @@ -46,6 +46,7 @@ inputMediaDice#e66fbf7b emoticon:string = InputMedia; inputMediaStory#89fdd778 peer:InputPeer id:int = InputMedia; inputMediaWebPage#c21b8849 flags:# force_large_media:flags.0?true force_small_media:flags.1?true optional:flags.2?true url:string = InputMedia; inputMediaPaidMedia#c4103386 flags:# stars_amount:long extended_media:Vector payload:flags.0?string = InputMedia; +inputMediaTodo#9fc55fde todo:TodoList = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto; @@ -103,7 +104,7 @@ channel#fe685355 flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull; -channelFull#52d6806b flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int = ChatFull; +channelFull#e07429de flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int send_paid_messages_stars:flags2.21?long = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -117,7 +118,7 @@ chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:f messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; message#eabcdd4d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true video_processing_pending:flags2.4?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck report_delivery_until_date:flags2.5?int paid_message_stars:flags2.6?long = Message; -messageService#d3d28540 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true reactions_are_possible:flags.9?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction reactions:flags.20?MessageReactions ttl_period:flags.25?int = Message; +messageService#7a800e0a flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true reactions_are_possible:flags.9?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer saved_peer_id:flags.28?Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction reactions:flags.20?MessageReactions ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; messageMediaPhoto#695150d7 flags:# spoiler:flags.3?true photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; @@ -136,6 +137,7 @@ messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int sto messageMediaGiveaway#aa073beb flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.2?true channels:Vector countries_iso2:flags.1?Vector prize_description:flags.3?string quantity:int months:flags.4?int stars:flags.5?long until_date:int = MessageMedia; messageMediaGiveawayResults#ceaa3ea1 flags:# only_new_subscribers:flags.0?true refunded:flags.2?true channel_id:long additional_peers_count:flags.3?int launch_msg_id:int winners_count:int unclaimed_count:int winners:Vector months:flags.4?int stars:flags.5?long prize_description:flags.1?string until_date:int = MessageMedia; messageMediaPaidMedia#a8852491 stars_amount:long extended_media:Vector = MessageMedia; +messageMediaToDo#8a53b014 flags:# todo:TodoList completions:flags.0?Vector = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#bd47cbad title:string users:Vector = MessageAction; @@ -188,6 +190,8 @@ messageActionStarGiftUnique#2e3ae60e flags:# upgrade:flags.0?true transferred:fl messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction; messageActionPaidMessagesPrice#84b88578 flags:# broadcast_messages_allowed:flags.0?true stars:long = MessageAction; messageActionConferenceCall#2ffe2f7a flags:# missed:flags.0?true active:flags.1?true video:flags.4?true call_id:long duration:flags.2?int other_participants:flags.3?Vector = MessageAction; +messageActionTodoCompletions#cc7c5c89 completed:Vector incompleted:Vector = MessageAction; +messageActionTodoAppendTasks#c7edbc83 list:Vector = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -1407,9 +1411,9 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -sponsoredMessage#4d93a990 flags:# recommended:flags.5?true can_report:flags.12?true random_id:bytes url:string title:string message:string entities:flags.1?Vector photo:flags.6?Photo media:flags.14?MessageMedia color:flags.13?PeerColor button_text:string sponsor_info:flags.7?string additional_info:flags.8?string = SponsoredMessage; +sponsoredMessage#7dbf8673 flags:# recommended:flags.5?true can_report:flags.12?true random_id:bytes url:string title:string message:string entities:flags.1?Vector photo:flags.6?Photo media:flags.14?MessageMedia color:flags.13?PeerColor button_text:string sponsor_info:flags.7?string additional_info:flags.8?string min_display_duration:flags.15?int max_display_duration:flags.15?int = SponsoredMessage; -messages.sponsoredMessages#c9ee1d87 flags:# posts_between:flags.0?int messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; +messages.sponsoredMessages#ffda656d flags:# posts_between:flags.0?int start_delay:flags.1?int between_delay:flags.2?int messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; messages.sponsoredMessagesEmpty#1839490f = messages.SponsoredMessages; searchResultsCalendarPeriod#c9b0539f date:int min_msg_id:int max_msg_id:int count:int = SearchResultsCalendarPeriod; @@ -1715,7 +1719,7 @@ storyReactionPublicRepost#cfcd0f13 peer_id:Peer story:StoryItem = StoryReaction; stories.storyReactionsList#aa5f789c flags:# count:int reactions:Vector chats:Vector users:Vector next_offset:flags.0?string = stories.StoryReactionsList; savedDialog#bd87cb6c flags:# pinned:flags.2?true peer:Peer top_message:int = SavedDialog; -monoForumDialog#64407ea7 flags:# unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_reactions_count:int draft:flags.1?DraftMessage = SavedDialog; +monoForumDialog#64407ea7 flags:# unread_mark:flags.3?true nopaid_messages_exception:flags.4?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_reactions_count:int draft:flags.1?DraftMessage = SavedDialog; messages.savedDialogs#f83ae221 dialogs:Vector messages:Vector chats:Vector users:Vector = messages.SavedDialogs; messages.savedDialogsSlice#44ba9dd9 count:int dialogs:Vector messages:Vector chats:Vector users:Vector = messages.SavedDialogs; @@ -1983,6 +1987,12 @@ stories.canSendStoryCount#c387c04e count_remains:int = stories.CanSendStoryCount pendingSuggestion#e7e82e12 suggestion:string title:TextWithEntities description:TextWithEntities url:string = PendingSuggestion; +todoItem#cba9a52f id:int title:TextWithEntities = TodoItem; + +todoList#49b92a26 flags:# others_can_append:flags.0?true others_can_complete:flags.1?true title:TextWithEntities list:Vector = TodoList; + +todoCompletion#4cc120b7 id:int completed_by:long date:int = TodoCompletion; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2134,8 +2144,8 @@ account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool; account.getReactionsNotifySettings#6dd654c = ReactionsNotifySettings; account.setReactionsNotifySettings#316ce548 settings:ReactionsNotifySettings = ReactionsNotifySettings; account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses; -account.addNoPaidMessagesException#6f688aa7 flags:# refund_charged:flags.0?true user_id:InputUser = Bool; -account.getPaidMessagesRevenue#f1266f38 user_id:InputUser = account.PaidMessagesRevenue; +account.getPaidMessagesRevenue#19ba4a67 flags:# parent_peer:flags.0?InputPeer user_id:InputUser = account.PaidMessagesRevenue; +account.toggleNoPaidMessagesException#fe2eda76 flags:# refund_charged:flags.0?true require_payment:flags.2?true parent_peer:flags.1?InputPeer user_id:InputUser = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; @@ -2390,13 +2400,15 @@ messages.getPaidReactionPrivacy#472455aa = Updates; messages.viewSponsoredMessage#269e3643 random_id:bytes = Bool; messages.clickSponsoredMessage#8235057e flags:# media:flags.0?true fullscreen:flags.1?true random_id:bytes = Bool; messages.reportSponsoredMessage#12cbf0c4 random_id:bytes option:bytes = channels.SponsoredMessageReportResult; -messages.getSponsoredMessages#9bd2f439 peer:InputPeer = messages.SponsoredMessages; +messages.getSponsoredMessages#3d6ce850 flags:# peer:InputPeer msg_id:flags.0?int = messages.SponsoredMessages; messages.savePreparedInlineMessage#f21f7f2f flags:# result:InputBotInlineResult user_id:InputUser peer_types:flags.0?Vector = messages.BotPreparedInlineMessage; messages.getPreparedInlineMessage#857ebdb8 bot:InputUser id:string = messages.PreparedInlineMessage; messages.searchStickers#29b1c66a flags:# emojis:flags.0?true q:string emoticon:string lang_code:Vector offset:int limit:int hash:long = messages.FoundStickers; messages.reportMessagesDelivery#5a6d7395 flags:# push:flags.0?true peer:InputPeer id:Vector = Bool; messages.getSavedDialogsByID#6f6f9c96 flags:# parent_peer:flags.1?InputPeer ids:Vector = messages.SavedDialogs; messages.readSavedHistory#ba4a3b5b parent_peer:InputPeer peer:InputPeer max_id:int = Bool; +messages.toggleTodoCompleted#d3e03124 peer:InputPeer msg_id:int completed:Vector incompleted:Vector = Updates; +messages.appendTodoList#21a61057 peer:InputPeer msg_id:int list:Vector = Updates; updates.getState#edd4882a = updates.State; updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference; diff --git a/src/types/index.ts b/src/types/index.ts index ef38d342e..f25379b41 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -21,6 +21,7 @@ import type { ApiMediaFormat, ApiMessage, ApiMessageEntity, + ApiNewMediaTodo, ApiNewPoll, ApiPeer, ApiPhoto, @@ -730,6 +731,7 @@ export type SendMessageParams = { story?: ApiStory | ApiStorySkipped; gif?: ApiVideo; poll?: ApiNewPoll; + todo?: ApiNewMediaTodo; contact?: ApiContact; isSilent?: boolean; scheduledAt?: number; diff --git a/src/types/language.d.ts b/src/types/language.d.ts index 3227472dd..8beadf494 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -50,6 +50,7 @@ export interface LangPair { 'PremiumPreviewVoiceToTextDescription': undefined; 'PremiumPreviewProfileBadgeDescription': undefined; 'PremiumPreviewDownloadSpeedDescription': undefined; + 'PremiumPreviewTodoDescription': undefined; 'PremiumPreviewUploadsDescription': undefined; 'PremiumPreviewAdvancedChatManagementDescription': undefined; 'PremiumPreviewAnimatedProfilesDescription': undefined; @@ -1537,6 +1538,27 @@ export interface LangPair { 'MonoforumComposerPlaceholder': undefined; 'ChannelSendMessage': undefined; 'AutomaticTranslation': undefined; + 'TitleNewToDoList': undefined; + 'TitleEditToDoList': undefined; + 'TitleAppendToDoList': undefined; + 'InputTitle': undefined; + 'TitleToDoList': undefined; + 'TitleTask': undefined; + 'TitleAddTask': undefined; + 'AllowOthersAddTasks': undefined; + 'AllowOthersMarkAsDone': undefined; + 'AriaToDoCancel': undefined; + 'TitleGroupToDoList': undefined; + 'TitleYourToDoList': undefined; + 'ToDoListNewTasks': undefined; + 'MenuButtonAppendTodoList': undefined; + 'PremiumMore': undefined; + 'SubscribeToTelegramPremiumForToggleTask': undefined; + 'SubscribeToTelegramPremiumForCreateToDo': undefined; + 'SubscribeToTelegramPremiumForAppendToDo': undefined; + 'ToDoListErrorChooseTitle': undefined; + 'ToDoListErrorChooseTasks': undefined; + 'PremiumPreviewTodo': undefined; } export interface LangPairWithVariables { @@ -2518,6 +2540,62 @@ export interface LangPairWithVariables { 'ComposerTitleForwardFrom': { 'users': V; }; + 'TitleUserToDoList': { + 'peer': V; + }; + 'DescriptionCompletedToDoTasks': { + 'number': V; + 'count': V; + }; + 'MessageActionTodoCompletionsAsDone': { + 'peer': V; + 'task': V; + }; + 'MessageActionTodoCompletionsAsDoneYou': { + 'task': V; + }; + 'MessageActionTodoCompletionsAsDoneMultiple': { + 'peer': V; + 'tasks': V; + }; + 'MessageActionTodoCompletionsAsDoneMultipleYou': { + 'tasks': V; + }; + 'MessageActionTodoCompletionsAsNotDone': { + 'peer': V; + 'task': V; + }; + 'MessageActionTodoCompletionsAsNotDoneYou': { + 'task': V; + }; + 'MessageActionTodoCompletionsAsNotDoneMultiple': { + 'peer': V; + 'tasks': V; + }; + 'MessageActionTodoCompletionsAsNotDoneMultipleYou': { + 'tasks': V; + }; + 'MessageActionAppendTodo': { + 'peer': V; + 'task': V; + 'list': V; + }; + 'MessageActionAppendTodoYou': { + 'task': V; + 'list': V; + }; + 'MessageActionAppendTodoMultiple': { + 'peer': V; + 'tasks': V; + 'list': V; + }; + 'MessageActionAppendTodoMultipleYou': { + 'tasks': V; + 'list': V; + }; + 'HintTodoListTasksCount': { + 'count': V; + }; } export interface LangPairPlural { @@ -2830,6 +2908,9 @@ export interface LangPairPluralWithVariables { 'GiftAttributeSymbolPlural': { 'count': V; }; + 'MessageActionTodoTaskCount': { + 'count': V; + }; } export type RegularLangKey = keyof LangPair; export type RegularLangKeyWithVariables = keyof LangPairWithVariables; diff --git a/src/util/generateUniqueId.ts b/src/util/generateUniqueId.ts index 79d81fc55..f36cfb1e9 100644 --- a/src/util/generateUniqueId.ts +++ b/src/util/generateUniqueId.ts @@ -1,3 +1,9 @@ export default function generateUniqueId() { return Date.now().toString(36) + Math.random().toString(36).slice(2); } + +export function generateUniqueNumberId() { + const timestamp = Date.now() % 100000000; + const random = Math.floor(Math.random() * 1000); + return timestamp + random; +}