diff --git a/src/api/gramjs/apiBuilders/statistics.ts b/src/api/gramjs/apiBuilders/statistics.ts index 0d1ca96c6..5bec4c917 100644 --- a/src/api/gramjs/apiBuilders/statistics.ts +++ b/src/api/gramjs/apiBuilders/statistics.ts @@ -3,11 +3,14 @@ import type { ApiChannelStatistics, ApiGroupStatistics, ApiMessageStatistics, + ApiMessagePublicForward, StatisticsGraph, StatisticsOverviewItem, StatisticsOverviewPercentage, StatisticsOverviewPeriod, } from '../../types'; +import { buildAvatarHash } from './chats'; +import { buildApiPeerId } from './peers'; export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiChannelStatistics { return { @@ -61,6 +64,31 @@ export function buildMessageStatistics(stats: GramJs.stats.MessageStats): ApiMes }; } +export function buildMessagePublicForwards( + result: GramJs.messages.TypeMessages, +): ApiMessagePublicForward[] | undefined { + if (!result || !('messages' in result)) { + return undefined; + } + + return result.messages.map((message) => { + const peerId = buildApiPeerId((message.peerId as GramJs.PeerChannel).channelId, 'channel'); + const channel = result.chats.find((p) => buildApiPeerId(p.id, 'channel') === peerId); + + return { + messageId: message.id, + views: (message as GramJs.Message).views, + title: (channel as GramJs.Channel).title, + chat: { + id: peerId, + type: 'chatTypeChannel', + username: (channel as GramJs.Channel).username, + avatarHash: buildAvatarHash((channel as GramJs.Channel).photo), + }, + }; + }); +} + export function buildGraph( result: GramJs.TypeStatsGraph, isPercentage?: boolean, ): StatisticsGraph | undefined { diff --git a/src/api/gramjs/methods/index.ts b/src/api/gramjs/methods/index.ts index 9d67a06af..398623d99 100644 --- a/src/api/gramjs/methods/index.ts +++ b/src/api/gramjs/methods/index.ts @@ -85,7 +85,8 @@ export { } from './reactions'; export { - fetchChannelStatistics, fetchGroupStatistics, fetchMessageStatistics, fetchStatisticsAsyncGraph, + fetchChannelStatistics, fetchGroupStatistics, fetchMessageStatistics, + fetchMessagePublicForwards, fetchStatisticsAsyncGraph, } from './statistics'; export { diff --git a/src/api/gramjs/methods/statistics.ts b/src/api/gramjs/methods/statistics.ts index 1501f6ba0..9b6e2a1e1 100644 --- a/src/api/gramjs/methods/statistics.ts +++ b/src/api/gramjs/methods/statistics.ts @@ -2,13 +2,14 @@ import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { - ApiChat, ApiChannelStatistics, ApiGroupStatistics, ApiMessageStatistics, StatisticsGraph, + ApiChat, ApiChannelStatistics, ApiGroupStatistics, ApiMessageStatistics, ApiMessagePublicForward, StatisticsGraph, } from '../../types'; import { invokeRequest } from './client'; +import { addEntitiesWithPhotosToLocalDb } from '../helpers'; import { buildInputEntity } from '../gramjsBuilders'; import { - buildChannelStatistics, buildGroupStatistics, buildMessageStatistics, buildGraph, + buildChannelStatistics, buildGroupStatistics, buildMessageStatistics, buildMessagePublicForwards, buildGraph, } from '../apiBuilders/statistics'; export async function fetchChannelStatistics({ @@ -58,6 +59,32 @@ export async function fetchMessageStatistics({ return buildMessageStatistics(result); } +export async function fetchMessagePublicForwards({ + chat, + messageId, + dcId, +}: { + chat: ApiChat; + messageId: number; + dcId?: number; +}): Promise { + const result = await invokeRequest(new GramJs.stats.GetMessagePublicForwards({ + channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, + msgId: messageId, + offsetPeer: new GramJs.InputPeerEmpty(), + }), undefined, undefined, undefined, dcId); + + if (!result) { + return undefined; + } + + if ('chats' in result) { + addEntitiesWithPhotosToLocalDb(result.chats); + } + + return buildMessagePublicForwards(result); +} + export async function fetchStatisticsAsyncGraph({ token, x, diff --git a/src/api/types/statistics.ts b/src/api/types/statistics.ts index 34c6a5a4b..e989e966a 100644 --- a/src/api/types/statistics.ts +++ b/src/api/types/statistics.ts @@ -1,4 +1,5 @@ -import type { ApiMessage } from './messages'; +import type { ApiChat } from './chats'; +import type { ApiMessage, ApiPhoto } from './messages'; export interface ApiChannelStatistics { growthGraph?: StatisticsGraph | string; @@ -34,6 +35,15 @@ export interface ApiMessageStatistics { viewsGraph?: StatisticsGraph | string; forwards?: number; views?: number; + publicForwards?: number; + publicForwardsData?: ApiMessagePublicForward[]; +} + +export interface ApiMessagePublicForward { + messageId: number; + views?: number; + title?: string; + chat: ApiChat; } export interface StatisticsGraph { diff --git a/src/components/right/RightHeader.tsx b/src/components/right/RightHeader.tsx index 31fc73225..9115b7fbe 100644 --- a/src/components/right/RightHeader.tsx +++ b/src/components/right/RightHeader.tsx @@ -439,6 +439,7 @@ const RightHeader: FC = ({ || contentKey === HeaderContent.SharedMedia || contentKey === HeaderContent.MemberList || contentKey === HeaderContent.AddingMembers + || contentKey === HeaderContent.MessageStatistics || isManagement ); diff --git a/src/components/right/statistics/MessageStatistics.tsx b/src/components/right/statistics/MessageStatistics.tsx index 01650e516..43ae06bd7 100644 --- a/src/components/right/statistics/MessageStatistics.tsx +++ b/src/components/right/statistics/MessageStatistics.tsx @@ -5,7 +5,7 @@ import React, { import { getActions, withGlobal } from '../../../global'; import { callApi } from '../../../api/gramjs'; -import type { ApiMessageStatistics, StatisticsGraph } from '../../../api/types'; +import type { ApiMessageStatistics, ApiMessagePublicForward, StatisticsGraph } from '../../../api/types'; import { selectChat } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; @@ -14,6 +14,7 @@ import useForceUpdate from '../../../hooks/useForceUpdate'; import Loading from '../../ui/Loading'; import StatisticsOverview from './StatisticsOverview'; +import StatisticsPublicForward from './StatisticsPublicForward'; import './Statistics.scss'; @@ -156,6 +157,16 @@ const Statistics: FC = ({
))}
+ + {Boolean(statistics.publicForwards) && ( +
+

{lang('Stats.Message.PublicShares')}

+ + {statistics.publicForwardsData!.map((item: ApiMessagePublicForward) => ( + + ))} +
+ )} ); }; diff --git a/src/components/right/statistics/Statistics.scss b/src/components/right/statistics/Statistics.scss index 680afd4a5..13fc76ca0 100644 --- a/src/components/right/statistics/Statistics.scss +++ b/src/components/right/statistics/Statistics.scss @@ -3,8 +3,8 @@ overflow-x: hidden; overflow-y: hidden; - &__messages { - padding: 1rem 0.25rem; + &__messages, &__public-forwards { + padding: 1rem 0; border-top: 1px solid var(--color-borders); &-title { @@ -38,6 +38,7 @@ &.hidden { opacity: 0; + margin: 0; } } @@ -47,7 +48,7 @@ } .lovely-chart--header { - margin: 0 1rem; + margin: 0 0.75rem; } .lovely-chart--header, diff --git a/src/components/right/statistics/Statistics.tsx b/src/components/right/statistics/Statistics.tsx index 7839bf9bc..bb70a8019 100644 --- a/src/components/right/statistics/Statistics.tsx +++ b/src/components/right/statistics/Statistics.tsx @@ -160,6 +160,8 @@ const Statistics: FC = ({ ); loadedCharts.current.push(name); + + containerRef.current!.children[index].classList.remove('hidden'); }); forceUpdate(); @@ -180,7 +182,7 @@ const Statistics: FC = ({
{graphs.map((graph) => ( -
+
))}
diff --git a/src/components/right/statistics/StatisticsOverview.scss b/src/components/right/statistics/StatisticsOverview.scss index 95e2845c8..4ae7efbab 100644 --- a/src/components/right/statistics/StatisticsOverview.scss +++ b/src/components/right/statistics/StatisticsOverview.scss @@ -13,7 +13,6 @@ &__title { margin-right: 2em; - padding-left: 0.25rem; font-size: 16px; color: var(--text-color); line-height: 30px; diff --git a/src/components/right/statistics/StatisticsOverview.tsx b/src/components/right/statistics/StatisticsOverview.tsx index fced9261d..140d3cd00 100644 --- a/src/components/right/statistics/StatisticsOverview.tsx +++ b/src/components/right/statistics/StatisticsOverview.tsx @@ -5,7 +5,7 @@ import type { ApiChannelStatistics, ApiGroupStatistics, ApiMessageStatistics, StatisticsOverviewItem, } from '../../../api/types'; -import { formatIntegerCompact } from '../../../util/textFormat'; +import { formatInteger, formatIntegerCompact } from '../../../util/textFormat'; import { formatFullDate } from '../../../util/dateFormat'; import buildClassName from '../../../util/buildClassName'; import useLang from '../../../hooks/useLang'; @@ -44,11 +44,14 @@ const GROUP_OVERVIEW: OverviewCell[][] = [ const MESSAGE_OVERVIEW: OverviewCell[][] = [ [ - { name: 'views', title: 'StatisticViews', isPlain: true }, + { name: 'views', title: 'Stats.Message.Views', isPlain: true }, { - name: 'forwards', title: 'PrivateShares', isPlain: true, isApproximate: true, + name: 'forwards', title: 'Stats.Message.PrivateShares', isPlain: true, isApproximate: true, }, ], + [ + { name: 'publicForwards', title: 'Stats.Message.PublicShares', isPlain: true }, + ], ]; export type OwnProps = { @@ -103,7 +106,9 @@ const StatisticsOverview: FC = ({ isGroup, isMessage, statistics }) => if (cell.isPlain) { return ( - {cell.isApproximate ? `≈${field}` : field} + + {cell.isApproximate ? `≈${formatInteger(field)}` : formatInteger(field)} +

{lang(cell.title)}

); diff --git a/src/components/right/statistics/StatisticsPublicForward.scss b/src/components/right/statistics/StatisticsPublicForward.scss new file mode 100644 index 000000000..d41cb1c28 --- /dev/null +++ b/src/components/right/statistics/StatisticsPublicForward.scss @@ -0,0 +1,25 @@ +.StatisticsPublicForward { + position: relative; + cursor: pointer; + padding: 0.5rem 0.75rem; + display: flex; + align-items: center; + + &:hover, &:active { + background-color: var(--color-chat-hover); + } + + .Avatar { + flex-shrink: 0; + margin-right: 0.5rem; + } + + &__title { + line-height: 1.25rem; + } + + &__views { + color: var(--color-text-meta); + font-size: 0.8125rem; + } +} diff --git a/src/components/right/statistics/StatisticsPublicForward.tsx b/src/components/right/statistics/StatisticsPublicForward.tsx new file mode 100644 index 000000000..6e15705e5 --- /dev/null +++ b/src/components/right/statistics/StatisticsPublicForward.tsx @@ -0,0 +1,41 @@ +import type { FC } from '../../../lib/teact/teact'; +import React, { memo, useCallback } from '../../../lib/teact/teact'; + +import useLang from '../../../hooks/useLang'; +import { getActions } from '../../../global'; +import type { ApiMessagePublicForward } from '../../../api/types'; + +import Avatar from '../../common/Avatar'; + +import './StatisticsPublicForward.scss'; + +export type OwnProps = { + data: ApiMessagePublicForward; +}; + +const StatisticsPublicForward: FC = ({ data }) => { + const lang = useLang(); + const { openChatByUsername } = getActions(); + + const handleClick = useCallback(() => { + openChatByUsername({ username: data.chat.username, messageId: data.messageId }); + }, [data, openChatByUsername]); + + return ( +
+ + +
+
+ {data.title} +
+ +
+ {lang('ChannelStats.ViewsCount', data.views, 'i')} +
+
+
+ ); +}; + +export default memo(StatisticsPublicForward); diff --git a/src/components/right/statistics/StatisticsRecentMessage.scss b/src/components/right/statistics/StatisticsRecentMessage.scss index 74acd6500..da666f24d 100644 --- a/src/components/right/statistics/StatisticsRecentMessage.scss +++ b/src/components/right/statistics/StatisticsRecentMessage.scss @@ -1,8 +1,7 @@ .StatisticsRecentMessage { position: relative; cursor: pointer; - padding: 0.5rem; - border-radius: var(--border-radius-default-small); + padding: 0.5rem 0.75rem; &:hover, &:active { background-color: var(--color-chat-hover); diff --git a/src/components/right/statistics/StatisticsRecentMessage.tsx b/src/components/right/statistics/StatisticsRecentMessage.tsx index ad9cac747..1cac6a4b5 100644 --- a/src/components/right/statistics/StatisticsRecentMessage.tsx +++ b/src/components/right/statistics/StatisticsRecentMessage.tsx @@ -49,7 +49,7 @@ const StatisticsRecentMessage: FC = ({ message }) => { {renderSummary(lang, message, mediaBlobUrl || mediaThumbnail, isRoundVideo)}
- {lang('ChannelStats.ViewsCount', message.views)} + {lang('ChannelStats.ViewsCount', message.views, 'i')}
diff --git a/src/global/actions/api/statistics.ts b/src/global/actions/api/statistics.ts index fb9208115..aa38c2cce 100644 --- a/src/global/actions/api/statistics.ts +++ b/src/global/actions/api/statistics.ts @@ -44,10 +44,16 @@ addActionHandler('loadMessageStatistics', async (global, actions, payload) => { global = getGlobal(); const { views, forwards } = selectChatMessages(global, chatId)[messageId]; - result.views = views; result.forwards = forwards; + const dcId = chat.fullInfo!.statisticsDcId; + const publicForwards = await callApi('fetchMessagePublicForwards', { chat, messageId, dcId }); + result.publicForwards = publicForwards?.length; + result.publicForwardsData = publicForwards; + + global = getGlobal(); + setGlobal(updateMessageStatistics(global, result)); }); diff --git a/src/lib/gramjs/tl/apiTl.js b/src/lib/gramjs/tl/apiTl.js index 29155b6b8..17fb64bb8 100644 --- a/src/lib/gramjs/tl/apiTl.js +++ b/src/lib/gramjs/tl/apiTl.js @@ -1226,4 +1226,5 @@ folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats; +stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;`; \ No newline at end of file diff --git a/src/lib/gramjs/tl/static/api.json b/src/lib/gramjs/tl/static/api.json index fb54b7730..a10acc8b0 100644 --- a/src/lib/gramjs/tl/static/api.json +++ b/src/lib/gramjs/tl/static/api.json @@ -227,6 +227,7 @@ "stats.getBroadcastStats", "stats.getMegagroupStats", "stats.getMessageStats", + "stats.getMessagePublicForwards", "stats.loadAsyncGraph", "messages.getAttachMenuBots", "messages.getAttachMenuBot",