From 324f7c5e5c0c3270296565d521ed8cb921fe849e Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Thu, 16 Sep 2021 11:27:20 +0300 Subject: [PATCH] Support Replies bot (#1444) --- package-lock.json | 149 +++++++----------- package.json | 10 +- src/api/gramjs/apiBuilders/messages.ts | 3 +- src/api/gramjs/methods/client.ts | 4 +- src/api/gramjs/methods/messages.ts | 41 +++-- src/api/types/messages.ts | 1 + src/components/common/Avatar.scss | 4 + src/components/common/Avatar.tsx | 6 +- src/components/common/GifButton.tsx | 1 - src/components/common/ProfilePhoto.scss | 2 + src/components/common/ProfilePhoto.tsx | 8 +- src/components/main/Main.tsx | 3 +- src/components/middle/MessageList.tsx | 12 +- src/components/middle/MiddleHeader.tsx | 8 +- src/components/middle/message/Message.tsx | 26 ++- .../middle/message/hooks/useInnerHandlers.ts | 32 +++- src/config.ts | 2 +- src/global/types.ts | 2 +- src/modules/actions/api/chats.ts | 17 ++ src/modules/helpers/chats.ts | 8 +- src/styles/_common.scss | 1 + src/util/textFormat.ts | 6 +- 22 files changed, 201 insertions(+), 145 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6be22c63..77aaa4d37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4536,9 +4536,9 @@ "dev": true }, "@types/react": { - "version": "17.0.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.17.tgz", - "integrity": "sha512-nrfi7I13cAmrd0wje8czYpf5SFbryczCtPzFc6ijqvdjKcyA3tCvGxwchOUlxb2ucBPuJ9Y3oUqKrRqZvrz0lw==", + "version": "17.0.19", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.19.tgz", + "integrity": "sha512-sX1HisdB1/ZESixMTGnMxH9TDe8Sk709734fEQZzCV/4lSu9kJCPbo2PbTRoZM+53Pp0P10hYVyReUueGwUi4A==", "dev": true, "requires": { "@types/prop-types": "*", @@ -4604,13 +4604,13 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.1.tgz", - "integrity": "sha512-AHqIU+SqZZgBEiWOrtN94ldR3ZUABV5dUG94j8Nms9rQnHFc8fvDOue/58K4CFz6r8OtDDc35Pw9NQPWo0Ayrw==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.2.tgz", + "integrity": "sha512-x4EMgn4BTfVd9+Z+r+6rmWxoAzBaapt4QFqE+d8L8sUtYZYLDTK6VG/y/SMMWA5t1/BVU5Kf+20rX4PtWzUYZg==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.29.1", - "@typescript-eslint/scope-manager": "4.29.1", + "@typescript-eslint/experimental-utils": "4.29.2", + "@typescript-eslint/scope-manager": "4.29.2", "debug": "^4.3.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.1.0", @@ -4639,15 +4639,15 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.1.tgz", - "integrity": "sha512-kl6QG6qpzZthfd2bzPNSJB2YcZpNOrP6r9jueXupcZHnL74WiuSjaft7WSu17J9+ae9zTlk0KJMXPUj0daBxMw==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.2.tgz", + "integrity": "sha512-P6mn4pqObhftBBPAv4GQtEK7Yos1fz/MlpT7+YjH9fTxZcALbiiPKuSIfYP/j13CeOjfq8/fr9Thr2glM9ub7A==", "dev": true, "requires": { "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.29.1", - "@typescript-eslint/types": "4.29.1", - "@typescript-eslint/typescript-estree": "4.29.1", + "@typescript-eslint/scope-manager": "4.29.2", + "@typescript-eslint/types": "4.29.2", + "@typescript-eslint/typescript-estree": "4.29.2", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" }, @@ -4670,14 +4670,14 @@ } }, "@typescript-eslint/parser": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.29.1.tgz", - "integrity": "sha512-3fL5iN20hzX3Q4OkG7QEPFjZV2qsVGiDhEwwh+EkmE/w7oteiOvUNzmpu5eSwGJX/anCryONltJ3WDmAzAoCMg==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.29.2.tgz", + "integrity": "sha512-WQ6BPf+lNuwteUuyk1jD/aHKqMQ9jrdCn7Gxt9vvBnzbpj7aWEf+aZsJ1zvTjx5zFxGCt000lsbD9tQPEL8u6g==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.29.1", - "@typescript-eslint/types": "4.29.1", - "@typescript-eslint/typescript-estree": "4.29.1", + "@typescript-eslint/scope-manager": "4.29.2", + "@typescript-eslint/types": "4.29.2", + "@typescript-eslint/typescript-estree": "4.29.2", "debug": "^4.3.1" }, "dependencies": { @@ -4693,29 +4693,29 @@ } }, "@typescript-eslint/scope-manager": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.1.tgz", - "integrity": "sha512-Hzv/uZOa9zrD/W5mftZa54Jd5Fed3tL6b4HeaOpwVSabJK8CJ+2MkDasnX/XK4rqP5ZTWngK1ZDeCi6EnxPQ7A==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.2.tgz", + "integrity": "sha512-mfHmvlQxmfkU8D55CkZO2sQOueTxLqGvzV+mG6S/6fIunDiD2ouwsAoiYCZYDDK73QCibYjIZmGhpvKwAB5BOA==", "dev": true, "requires": { - "@typescript-eslint/types": "4.29.1", - "@typescript-eslint/visitor-keys": "4.29.1" + "@typescript-eslint/types": "4.29.2", + "@typescript-eslint/visitor-keys": "4.29.2" } }, "@typescript-eslint/types": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.1.tgz", - "integrity": "sha512-Jj2yu78IRfw4nlaLtKjVaGaxh/6FhofmQ/j8v3NXmAiKafbIqtAPnKYrf0sbGjKdj0hS316J8WhnGnErbJ4RCA==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.2.tgz", + "integrity": "sha512-K6ApnEXId+WTGxqnda8z4LhNMa/pZmbTFkDxEBLQAbhLZL50DjeY0VIDCml/0Y3FlcbqXZrABqrcKxq+n0LwzQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.1.tgz", - "integrity": "sha512-lIkkrR9E4lwZkzPiRDNq0xdC3f2iVCUjw/7WPJ4S2Sl6C3nRWkeE1YXCQ0+KsiaQRbpY16jNaokdWnm9aUIsfw==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.2.tgz", + "integrity": "sha512-TJ0/hEnYxapYn9SGn3dCnETO0r+MjaxtlWZ2xU+EvytF0g4CqTpZL48SqSNn2hXsPolnewF30pdzR9a5Lj3DNg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.29.1", - "@typescript-eslint/visitor-keys": "4.29.1", + "@typescript-eslint/types": "4.29.2", + "@typescript-eslint/visitor-keys": "4.29.2", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -4744,12 +4744,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.1.tgz", - "integrity": "sha512-zLqtjMoXvgdZY/PG6gqA73V8BjqPs4af1v2kiiETBObp+uC6gRYnJLmJHxC0QyUrrHDLJPIWNYxoBV3wbcRlag==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.2.tgz", + "integrity": "sha512-bDgJLQ86oWHJoZ1ai4TZdgXzJxsea3Ee9u9wsTAvjChdj2WLcVsgWYAPeY7RQMn16tKrlQaBnpKv7KBfs4EQag==", "dev": true, "requires": { - "@typescript-eslint/types": "4.29.1", + "@typescript-eslint/types": "4.29.2", "eslint-visitor-keys": "^2.0.0" }, "dependencies": { @@ -4923,9 +4923,9 @@ } }, "@webpack-cli/serve": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.5.1.tgz", - "integrity": "sha512-4vSVUiOPJLmr45S8rMGy7WDvpWxfFxfP/Qx/cxZFCfvoypTYpPPL1X8VIZMe0WTA+Jr7blUxwUSEZNkjoMTgSw==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.5.2.tgz", + "integrity": "sha512-vgJ5OLWadI8aKjDlOH3rb+dYyPd2GTZuQC/Tihjct6F9GpXGZINo3Y/IVuZVTM1eDQB+/AOsjPUWH/WySDaXvw==", "dev": true }, "@xtuc/ieee754": { @@ -8816,9 +8816,9 @@ "dev": true }, "fastq": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", - "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.12.0.tgz", + "integrity": "sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -17153,9 +17153,9 @@ "dev": true }, "sass": { - "version": "1.37.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.37.5.tgz", - "integrity": "sha512-Cx3ewxz9QB/ErnVIiWg2cH0kiYZ0FPvheDTVC6BsiEGBTZKKZJ1Gq5Kq6jy3PKtL6+EJ8NIoaBW/RSd2R6cZOA==", + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.38.0.tgz", + "integrity": "sha512-WBccZeMigAGKoI+NgD7Adh0ab1HUq+6BmyBUEaGxtErbUtWUevEbdgo5EZiJQofLUGcKtlNaO2IdN73AHEua5g==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0" @@ -18655,9 +18655,9 @@ } }, "ts-node": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.0.tgz", - "integrity": "sha512-FstYHtQz6isj8rBtYMN4bZdnXN1vq4HCbqn9vdNQcInRqtB86PePJQIxE6es0PhxKWhj2PHuwbG40H+bxkZPmg==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz", + "integrity": "sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==", "dev": true, "requires": { "@cspotcode/source-map-support": "0.6.1", @@ -19206,9 +19206,9 @@ "dev": true }, "webpack": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.50.0.tgz", - "integrity": "sha512-hqxI7t/KVygs0WRv/kTgUW8Kl3YC81uyWQSo/7WUs5LsuRw0htH/fCwbVBGCuiX/t4s7qzjXFcf41O8Reiypag==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.51.0.tgz", + "integrity": "sha512-oySQoKUuf5r0JaPIYi8q90c/GmU9fGdSbZ0cAjFq3OWx57wniRTWvta1T9t+e5WZ6H6mHrxksNatkqfIEuTWGg==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.0", @@ -19407,15 +19407,15 @@ } }, "webpack-cli": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.7.2.tgz", - "integrity": "sha512-mEoLmnmOIZQNiRl0ebnjzQ74Hk0iKS5SiEEnpq3dRezoyR3yPaeQZCMCe+db4524pj1Pd5ghZXjT41KLzIhSLw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.8.0.tgz", + "integrity": "sha512-+iBSWsX16uVna5aAYN6/wjhJy1q/GKk4KjKvfg90/6hykCTSgozbfz5iRgDTSJt/LgSbYxdBX3KBHeobIs+ZEw==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.0.4", "@webpack-cli/info": "^1.3.0", - "@webpack-cli/serve": "^1.5.1", + "@webpack-cli/serve": "^1.5.2", "colorette": "^1.2.1", "commander": "^7.0.0", "execa": "^5.0.0", @@ -19467,12 +19467,6 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, "import-local": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", @@ -19495,12 +19489,6 @@ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -19510,15 +19498,6 @@ "path-key": "^3.0.0" } }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -19561,22 +19540,6 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index c6a40bc80..7b37cbb9f 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@types/css-font-loading-module": "0.0.6", "@types/dom-mediacapture-record": "^1.0.10", "@types/jest": "^27.0.1", - "@types/react": "^17.0.17", + "@types/react": "^17.0.19", "@types/react-dom": "^17.0.9", "@types/resize-observer-browser": "^0.1.6", "@types/wicg-mediasession": "^1.1.2", @@ -87,17 +87,17 @@ "raw-loader": "^4.0.2", "react": "^17.0.2", "replace-in-file": "^6.2.0", - "sass": "^1.37.5", + "sass": "^1.38.0", "sass-loader": "^12.1.0", "style-loader": "^3.2.1", "terser": "^5.7.1", "terser-webpack-plugin": "^5.1.4", - "ts-node": "^10.2.0", + "ts-node": "^10.2.1", "typescript": "^4.3.5", "url-loader": "^4.1.1", - "webpack": "^5.50.0", + "webpack": "^5.51.0", "webpack-bundle-analyzer": "^4.4.2", - "webpack-cli": "^4.7.2", + "webpack-cli": "^4.8.0", "webpack-dev-server": "^3.11.2", "webpack-merge": "^5.8.0" }, diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index e2f10dc86..4bfa1eca5 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -137,7 +137,7 @@ export function buildApiMessageWithChatId(chatId: number, mtpMessage: UniversalM content.action = action; } - const { replyToMsgId, replyToTopId } = mtpMessage.replyTo || {}; + const { replyToMsgId, replyToTopId, replyToPeerId } = mtpMessage.replyTo || {}; const isEdited = mtpMessage.editDate && !mtpMessage.editHide; const { inlineButtons, keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse, @@ -158,6 +158,7 @@ export function buildApiMessageWithChatId(chatId: number, mtpMessage: UniversalM views: mtpMessage.views, isFromScheduled: mtpMessage.fromScheduled, ...(replyToMsgId && { replyToMessageId: replyToMsgId }), + ...(replyToPeerId && { replyToChatId: getApiChatIdFromMtpPeer(replyToPeerId) }), ...(replyToTopId && { replyToTopMessageId: replyToTopId }), ...(forwardInfo && { forwardInfo }), ...(isEdited && { isEdited }), diff --git a/src/api/gramjs/methods/client.ts b/src/api/gramjs/methods/client.ts index 498bb16f4..270033fec 100644 --- a/src/api/gramjs/methods/client.ts +++ b/src/api/gramjs/methods/client.ts @@ -214,12 +214,12 @@ export async function invokeRequest( console.error(err); } - dispatchErrorUpdate(err, request); - if (shouldThrow) { throw err; } + dispatchErrorUpdate(err, request); + return undefined; } } diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index 6a4c79be6..fb5bedfc4 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -116,16 +116,36 @@ export async function fetchMessages({ export async function fetchMessage({ chat, messageId }: { chat: ApiChat; messageId: number }) { const isChannel = getEntityTypeById(chat.id) === 'channel'; - const result = await invokeRequest( - isChannel - ? new GramJs.channels.GetMessages({ - channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, - id: [new GramJs.InputMessageID({ id: messageId })], - }) - : new GramJs.messages.GetMessages({ - id: [new GramJs.InputMessageID({ id: messageId })], - }), - ); + let result; + try { + result = await invokeRequest( + isChannel + ? new GramJs.channels.GetMessages({ + channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, + id: [new GramJs.InputMessageID({ id: messageId })], + }) + : new GramJs.messages.GetMessages({ + id: [new GramJs.InputMessageID({ id: messageId })], + }), + undefined, + true, + ); + } catch (err) { + const { message } = err; + + // When fetching messages for the bot @replies, there may be situations when the user was banned + // in the comment group or this group was deleted + if (message !== 'CHANNEL_PRIVATE') { + onUpdate({ + '@type': 'error', + error: { + message, + isSlowMode: false, + hasErrorKey: true, + }, + }); + } + } if (!result || result instanceof GramJs.messages.MessagesNotModified) { return undefined; @@ -731,6 +751,7 @@ export async function requestThreadInfoUpdate({ chatId: discussionChatId, threadId, threadInfo: { + threadId, topMessageId: topMessageResult.messages[topMessageResult.messages.length - 1].id, lastReadInboxMessageId: topMessageResult.readInboxMaxId, messagesCount: (repliesResult instanceof GramJs.messages.ChannelMessages) ? repliesResult.count : undefined, diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index d6a3da8a2..c34f5d6c0 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -236,6 +236,7 @@ export interface ApiMessage { date: number; isOutgoing: boolean; senderId?: number; + replyToChatId?: number; replyToMessageId?: number; replyToTopMessageId?: number; sendingState?: 'messageSendingStatePending' | 'messageSendingStateFailed'; diff --git a/src/components/common/Avatar.scss b/src/components/common/Avatar.scss index 50002eaf3..22299d823 100644 --- a/src/components/common/Avatar.scss +++ b/src/components/common/Avatar.scss @@ -23,6 +23,10 @@ i { font-size: 2.5rem; + + &.icon-reply-filled { + transform: scale(.7); + } } &.size-micro { diff --git a/src/components/common/Avatar.tsx b/src/components/common/Avatar.tsx index aa68be26c..a359b190f 100644 --- a/src/components/common/Avatar.tsx +++ b/src/components/common/Avatar.tsx @@ -6,7 +6,7 @@ import { ApiUser, ApiChat, ApiMediaFormat } from '../../api/types'; import { IS_TEST } from '../../config'; import { getChatAvatarHash, getChatTitle, isChatPrivate, - getUserFullName, isUserOnline, isDeletedUser, getUserColorKey, + getUserFullName, isUserOnline, isDeletedUser, getUserColorKey, isChatWithRepliesBot, } from '../../modules/helpers'; import { getFirstLetters } from '../../util/textFormat'; import buildClassName from '../../util/buildClassName'; @@ -41,6 +41,7 @@ const Avatar: FC = ({ onClick, }) => { const isDeleted = user && isDeletedUser(user); + const isReplies = user && isChatWithRepliesBot(user.id); let imageHash: string | undefined; if (!isSavedMessages && !isDeleted) { @@ -62,6 +63,8 @@ const Avatar: FC = ({ content = ; } else if (isDeleted) { content = ; + } else if (isReplies) { + content = ; } else if (shouldRenderFullMedia) { content = ; } else if (user) { @@ -81,6 +84,7 @@ const Avatar: FC = ({ `color-bg-${getUserColorKey(user || chat)}`, isSavedMessages && 'saved-messages', isDeleted && 'deleted-account', + isReplies && 'replies-bot-account', withOnlineStatus && isOnline && 'online', onClick && 'interactive', (!isSavedMessages && !shouldRenderFullMedia) && 'no-photo', diff --git a/src/components/common/GifButton.tsx b/src/components/common/GifButton.tsx index b51390867..a0105c633 100644 --- a/src/components/common/GifButton.tsx +++ b/src/components/common/GifButton.tsx @@ -7,7 +7,6 @@ import { ApiMediaFormat, ApiVideo } from '../../api/types'; import buildClassName from '../../util/buildClassName'; import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserver'; import useMedia from '../../hooks/useMedia'; -import useTransitionForMedia from '../../hooks/useTransitionForMedia'; import useVideoCleanup from '../../hooks/useVideoCleanup'; import useBuffering from '../../hooks/useBuffering'; import useCanvasBlur from '../../hooks/useCanvasBlur'; diff --git a/src/components/common/ProfilePhoto.scss b/src/components/common/ProfilePhoto.scss index 6296c1235..d66a97de7 100644 --- a/src/components/common/ProfilePhoto.scss +++ b/src/components/common/ProfilePhoto.scss @@ -23,6 +23,7 @@ .spinner-wrapper, &.deleted-account, + &.replies-bot-account, &.no-photo, &.saved-messages { display: flex; @@ -37,6 +38,7 @@ font-size: 14rem; } + &.replies-bot-account, &.deleted-account, &.saved-messages { font-size: 20rem; diff --git a/src/components/common/ProfilePhoto.tsx b/src/components/common/ProfilePhoto.tsx index 9740475f2..167ab5182 100644 --- a/src/components/common/ProfilePhoto.tsx +++ b/src/components/common/ProfilePhoto.tsx @@ -5,7 +5,7 @@ import { } from '../../api/types'; import { - getChatAvatarHash, isDeletedUser, getUserColorKey, getChatTitle, isChatPrivate, getUserFullName, + getChatAvatarHash, isDeletedUser, getUserColorKey, getChatTitle, isChatPrivate, getUserFullName, isChatWithRepliesBot, } from '../../modules/helpers'; import renderText from './helpers/renderText'; import buildClassName from '../../util/buildClassName'; @@ -40,6 +40,7 @@ const ProfilePhoto: FC = ({ }) => { const lang = useLang(); const isDeleted = user && isDeletedUser(user); + const isRepliesChat = chat && isChatWithRepliesBot(chat.id); function getMediaHash(size: 'normal' | 'big' = 'big', forceAvatar?: boolean) { if (photo && !forceAvatar) { @@ -47,7 +48,7 @@ const ProfilePhoto: FC = ({ } let hash: string | undefined; - if (!isSavedMessages && !isDeleted) { + if (!isSavedMessages && !isDeleted && !isRepliesChat) { if (user) { hash = getChatAvatarHash(user, size); } else if (chat) { @@ -81,6 +82,8 @@ const ProfilePhoto: FC = ({ content = ; } else if (isDeleted) { content = ; + } else if (isRepliesChat) { + content = ; } else if (imageSrc) { content = ; } else if (!imageSrc && user) { @@ -102,6 +105,7 @@ const ProfilePhoto: FC = ({ `color-bg-${getUserColorKey(user || chat)}`, isSavedMessages && 'saved-messages', isDeleted && 'deleted-account', + isRepliesChat && 'replies-bot-account', (!isSavedMessages && !(imageSrc)) && 'no-photo', ); diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 04e9cd6c0..a14b350d5 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -3,10 +3,9 @@ import React, { } from '../../lib/teact/teact'; import { getGlobal, withGlobal } from '../../lib/teact/teactn'; -import { AudioOrigin } from '../../types'; +import { AudioOrigin, LangCode } from '../../types'; import { GlobalActions } from '../../global/types'; import { ApiMessage } from '../../api/types'; -import { LangCode } from '../../types'; import '../../modules/actions/all'; import { diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index bf3713ca7..6e9b079a6 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -26,7 +26,12 @@ import { selectScheduledMessages, selectCurrentMessageIds, } from '../../modules/selectors'; -import { isChatChannel, isChatGroup, isChatPrivate } from '../../modules/helpers'; +import { + isChatChannel, + isChatPrivate, + isChatWithRepliesBot, + isChatGroup, +} from '../../modules/helpers'; import { orderBy, pick } from '../../util/iteratees'; import { fastRaf, debounce, onTickEnd } from '../../util/schedulers'; import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps'; @@ -67,6 +72,7 @@ type StateProps = { isChannelChat?: boolean; isGroupChat?: boolean; isChatWithSelf?: boolean; + isRepliesChat?: boolean; isCreator?: boolean; isBot?: boolean; messageIds?: number[]; @@ -112,6 +118,7 @@ const MessageList: FC = ({ isReady, isActive, isChatWithSelf, + isRepliesChat, isCreator, isBot, messageIds, @@ -441,7 +448,7 @@ const MessageList: FC = ({ const lang = useLang(); const isPrivate = Boolean(chatId && isChatPrivate(chatId)); - const withUsers = Boolean((!isPrivate && !isChannelChat) || isChatWithSelf); + const withUsers = Boolean((!isPrivate && !isChannelChat) || isChatWithSelf || isRepliesChat); const noAvatars = Boolean(!withUsers || isChannelChat); const shouldRenderGreeting = isChatPrivate(chatId) && !isChatWithSelf && !isBot && ( @@ -567,6 +574,7 @@ export default memo(withGlobal( isGroupChat: isChatGroup(chat), isCreator: chat.isCreator, isChatWithSelf: selectIsChatWithSelf(global, chatId), + isRepliesChat: isChatWithRepliesBot(chatId), isBot: Boolean(chatBot), messageIds, messagesById, diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index 1da714751..0d3a69e61 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -315,14 +315,14 @@ const MiddleHeader: FC = ({ <> {renderBackButton()}

- {lang('CommentsCount', messagesCount)} + {lang('CommentsCount', messagesCount, 'i')}

) : messageListType === 'pinned' ? ( <> {renderBackButton()}

- {lang('PinnedMessagesCount', messagesCount)} + {lang('PinnedMessagesCount', messagesCount, 'i')}

) : messageListType === 'scheduled' ? ( @@ -448,9 +448,7 @@ export default memo(withGlobal( messagesCount = scheduledIds?.length; } else if (messageListType === 'thread' && threadId !== MAIN_THREAD_ID) { const threadInfo = selectThreadInfo(global, chatId, threadId); - if (threadInfo) { - messagesCount = threadInfo.messagesCount; - } + messagesCount = threadInfo?.messagesCount || 0; } const state: StateProps = { diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index 3d894150e..a8e2f65fa 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -52,6 +52,7 @@ import { isAnonymousOwnMessage, isMessageLocal, isChatPrivate, + isChatWithRepliesBot, getMessageCustomShape, isChatChannel, getMessageSingleEmoji, @@ -142,6 +143,7 @@ type StateProps = { isResizingContainer?: boolean; isForwarding?: boolean; isChatWithSelf?: boolean; + isRepliesChat?: boolean; isChannel?: boolean; canReply?: boolean; lastSyncTime?: number; @@ -203,6 +205,7 @@ const Message: FC = ({ isResizingContainer, isForwarding, isChatWithSelf, + isRepliesChat, isChannel, canReply, lastSyncTime, @@ -262,7 +265,7 @@ const Message: FC = ({ const hasReply = isReplyMessage(message) && !shouldHideReply; const hasThread = Boolean(threadInfo) && messageListType === 'thread'; const { forwardInfo, viaBotId } = message; - const asForwarded = forwardInfo && !isChatWithSelf && !forwardInfo.isLinkedChannelPost; + const asForwarded = forwardInfo && !isChatWithSelf && !isRepliesChat && !forwardInfo.isLinkedChannelPost; const isInDocumentGroup = Boolean(message.groupedId) && !message.isInAlbum; const isAlbum = Boolean(album) && album!.messages.length > 1; const { @@ -283,8 +286,11 @@ const Message: FC = ({ ); const canForward = isChannel && !isScheduled; const canFocus = Boolean(isPinnedList - || (forwardInfo && (forwardInfo.isChannelPost || (isChatWithSelf && !isOwn)) && forwardInfo.fromMessageId)); - const avatarPeer = forwardInfo && (isChatWithSelf || !sender) ? originSender : sender; + || (forwardInfo + && (forwardInfo.isChannelPost || (isChatWithSelf && !isOwn) || isRepliesChat) + && forwardInfo.fromMessageId + )); + const avatarPeer = forwardInfo && (isChatWithSelf || isRepliesChat || !sender) ? originSender : sender; const senderPeer = forwardInfo ? originSender : sender; const selectMessage = useCallback((e?: React.MouseEvent, groupedId?: string) => { @@ -344,6 +350,7 @@ const Message: FC = ({ threadId, isInDocumentGroup, Boolean(isScheduled), + isRepliesChat, album, avatarPeer, senderPeer, @@ -389,7 +396,12 @@ const Message: FC = ({ && messageListType === 'thread' && !noComments; const withAppendix = contentClassName.includes('has-appendix'); - useEnsureMessage(chatId, hasReply ? message.replyToMessageId : undefined, replyMessage, message.id); + useEnsureMessage( + isRepliesChat && message.replyToChatId ? message.replyToChatId : chatId, + hasReply ? message.replyToMessageId : undefined, + replyMessage, + message.id, + ); useFocusMessage(ref, chatId, isFocused, focusDirection, noFocusHighlight, isResizingContainer); useLayoutEffect(() => { if (!appendixRef.current) { @@ -796,11 +808,12 @@ export default memo(withGlobal( message, album, withSenderName, withAvatar, threadId, messageListType, } = ownProps; const { - id, chatId, viaBotId, replyToMessageId, isOutgoing, + id, chatId, viaBotId, replyToChatId, replyToMessageId, isOutgoing, } = message; const chat = selectChat(global, chatId); const isChatWithSelf = selectIsChatWithSelf(global, chatId); + const isRepliesChat = isChatWithRepliesBot(chatId); const isChannel = chat && isChatChannel(chat); const chatUsername = chat?.username; @@ -815,7 +828,7 @@ export default memo(withGlobal( const shouldHideReply = replyToMessageId === threadTopMessageId; const replyMessage = replyToMessageId && !shouldHideReply - ? selectChatMessage(global, chatId, replyToMessageId) + ? selectChatMessage(global, isRepliesChat && replyToChatId ? replyToChatId : chatId, replyToMessageId) : undefined; const replyMessageSender = replyMessage && selectSender(global, replyMessage); @@ -859,6 +872,7 @@ export default memo(withGlobal( isFocused, isForwarding, isChatWithSelf, + isRepliesChat, isChannel, canReply, lastSyncTime, diff --git a/src/components/middle/message/hooks/useInnerHandlers.ts b/src/components/middle/message/hooks/useInnerHandlers.ts index 1b1d93dfa..8d4b0b43a 100644 --- a/src/components/middle/message/hooks/useInnerHandlers.ts +++ b/src/components/middle/message/hooks/useInnerHandlers.ts @@ -16,6 +16,7 @@ export default function useInnerHandlers( threadId: number, isInDocumentGroup: boolean, isScheduled?: boolean, + isChatWithRepliesBot?: boolean, album?: IAlbum, avatarPeer?: ApiUser | ApiChat, senderPeer?: ApiUser | ApiChat, @@ -23,11 +24,11 @@ export default function useInnerHandlers( ) { const { openUserInfo, openChat, showNotification, focusMessage, openMediaViewer, openAudioPlayer, - markMessagesRead, cancelSendingMessage, sendPollVote, openForwardMenu, + markMessagesRead, cancelSendingMessage, sendPollVote, openForwardMenu, focusMessageInComments, } = getDispatch(); const { - id: messageId, forwardInfo, replyToMessageId, groupedId, + id: messageId, forwardInfo, replyToMessageId, replyToChatId, replyToTopMessageId, groupedId, } = message; const handleAvatarClick = useCallback(() => { @@ -66,9 +67,12 @@ export default function useInnerHandlers( const handleReplyClick = useCallback((): void => { focusMessage({ - chatId, threadId, messageId: replyToMessageId, replyMessageId: messageId, + chatId: isChatWithRepliesBot && replyToChatId ? replyToChatId : chatId, + threadId, + messageId: replyToMessageId, + replyMessageId: isChatWithRepliesBot && replyToChatId ? undefined : messageId, }); - }, [focusMessage, chatId, threadId, replyToMessageId, messageId]); + }, [focusMessage, isChatWithRepliesBot, replyToChatId, chatId, threadId, replyToMessageId, messageId]); const handleMediaClick = useCallback((): void => { openMediaViewer({ @@ -127,10 +131,22 @@ export default function useInnerHandlers( }); return; } - focusMessage({ - chatId: forwardInfo!.fromChatId, messageId: forwardInfo!.fromMessageId, - }); - }, [isInDocumentGroup, focusMessage, forwardInfo, groupedId, chatId]); + + if (isChatWithRepliesBot && replyToChatId) { + focusMessageInComments({ + chatId: replyToChatId, + threadId: replyToTopMessageId, + messageId: forwardInfo!.fromMessageId, + }); + } else { + focusMessage({ + chatId: forwardInfo!.fromChatId, messageId: forwardInfo!.fromMessageId, + }); + } + }, [ + isInDocumentGroup, isChatWithRepliesBot, replyToChatId, focusMessage, forwardInfo, groupedId, chatId, + focusMessageInComments, replyToTopMessageId, + ]); const selectWithGroupedId = useCallback((e: React.MouseEvent) => { e.stopPropagation(); diff --git a/src/config.ts b/src/config.ts index 875fe7845..7b5e8feac 100644 --- a/src/config.ts +++ b/src/config.ts @@ -136,7 +136,7 @@ export const RE_TME_ADDSTICKERS_LINK = /^(?:https?:\/\/)?(?:t\.me\/addstickers\/ // MTProto constants export const SERVICE_NOTIFICATIONS_USER_ID = 777000; -export const REPLIES_USER_ID = 1271266957; +export const REPLIES_USER_ID = 1271266957; // TODO For Test connection ID must be equal to 708513 export const ALL_FOLDER_ID = 0; export const ARCHIVED_FOLDER_ID = 1; export const DELETED_COMMENTS_CHANNEL_ID = 777; diff --git a/src/global/types.ts b/src/global/types.ts index 674873b0a..dd9e03946 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -453,7 +453,7 @@ export type ActionTypes = ( 'setAuthRememberMe' | 'clearAuthError' | 'uploadProfilePhoto' | 'goToAuthQrCode' | 'clearCache' | // chats 'preloadTopChatMessages' | 'loadChats' | 'loadMoreChats' | 'openChat' | 'openChatWithInfo' | 'openLinkedChat' | - 'openSupportChat' | 'openTipsChat' | + 'openSupportChat' | 'openTipsChat' | 'focusMessageInComments' | 'loadFullChat' | 'loadTopChats' | 'requestChatUpdate' | 'updateChatMutedState' | 'joinChannel' | 'leaveChannel' | 'deleteChannel' | 'toggleChatPinned' | 'toggleChatArchived' | 'toggleChatUnread' | 'loadChatFolders' | 'loadRecommendedChatFolders' | 'editChatFolder' | 'addChatFolder' | 'deleteChatFolder' | diff --git a/src/modules/actions/api/chats.ts b/src/modules/actions/api/chats.ts index b670b8e75..db9adb79d 100644 --- a/src/modules/actions/api/chats.ts +++ b/src/modules/actions/api/chats.ts @@ -140,6 +140,23 @@ addReducer('openLinkedChat', (global, actions, payload) => { })(); }); +addReducer('focusMessageInComments', (global, actions, payload) => { + const { chatId, threadId, messageId } = payload!; + const chat = selectChat(global, chatId); + if (!chat) { + return; + } + + (async () => { + const result = await callApi('requestThreadInfoUpdate', { chat, threadId }); + if (!result) { + return; + } + + actions.focusMessage({ chatId, threadId, messageId }); + })(); +}); + addReducer('openSupportChat', (global, actions) => { const chat = selectSupportChat(global); diff --git a/src/modules/helpers/chats.ts b/src/modules/helpers/chats.ts index 054599978..543f28f9e 100644 --- a/src/modules/helpers/chats.ts +++ b/src/modules/helpers/chats.ts @@ -10,7 +10,7 @@ import { import { NotifyException, NotifySettings } from '../../types'; import { LangFn } from '../../hooks/useLang'; -import { ARCHIVED_FOLDER_ID } from '../../config'; +import { ARCHIVED_FOLDER_ID, REPLIES_USER_ID } from '../../config'; import { orderBy } from '../../util/iteratees'; import { getUserFirstOrLastName } from './users'; import { formatDateToString, formatTime } from '../../util/dateFormat'; @@ -44,6 +44,10 @@ export function isCommonBoxChat(chat: ApiChat) { return chat.type === 'chatTypePrivate' || chat.type === 'chatTypeBasicGroup'; } +export function isChatWithRepliesBot(chatId: number) { + return chatId === REPLIES_USER_ID; +} + export function getChatTypeString(chat: ApiChat) { switch (chat.type) { case 'chatTypePrivate': @@ -131,7 +135,7 @@ export function getCanPostInChat(chat: ApiChat, threadId: number) { return true; } - if (chat.isRestricted || chat.migratedTo || chat.isNotJoined) { + if (chat.isRestricted || chat.migratedTo || chat.isNotJoined || isChatWithRepliesBot(chat.id)) { return false; } diff --git a/src/styles/_common.scss b/src/styles/_common.scss index 61e896a82..5d135b725 100644 --- a/src/styles/_common.scss +++ b/src/styles/_common.scss @@ -126,6 +126,7 @@ div { --color-user: var(--color-user-8); } + &.replies-bot-account, &.saved-messages { --color-user: var(--color-primary); } diff --git a/src/util/textFormat.ts b/src/util/textFormat.ts index 2ccd2eb9a..3ee9b6b66 100644 --- a/src/util/textFormat.ts +++ b/src/util/textFormat.ts @@ -1,5 +1,5 @@ -import EMOJI_REGEX from "../lib/twemojiRegex"; -import { fixNonStandardEmoji } from "./emoji"; +import EMOJI_REGEX from '../lib/twemojiRegex'; +import { fixNonStandardEmoji } from './emoji'; export function formatInteger(value: number) { return String(value).replace(/\d(?=(\d{3})+$)/g, '$& '); @@ -39,7 +39,7 @@ export function getFirstLetters(phrase: string, count = 2) { if (emojis && word.startsWith(emojis[0])) { return emojis[0]; } - return word.match(/./u)![0].toUpperCase() + return word.match(/./u)![0].toUpperCase(); }) .join(''); }