Sponsored Message: Support BotApp, buttonText and user colors (#4080)
This commit is contained in:
parent
e079ba7a16
commit
d32afadbd1
@ -13,6 +13,7 @@ import type {
|
||||
ApiBotInlineSwitchWebview,
|
||||
ApiBotMenuButton,
|
||||
ApiInlineResultType,
|
||||
ApiMessagesBotApp,
|
||||
} from '../../types';
|
||||
|
||||
import { pick } from '../../../util/iteratees';
|
||||
@ -149,9 +150,9 @@ export function buildApiBotMenuButton(menuButton?: GramJs.TypeBotMenuButton): Ap
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiBotApp(botApp: GramJs.messages.BotApp): ApiBotApp | undefined {
|
||||
const { app, inactive, requestWriteAccess } = botApp;
|
||||
export function buildApiBotApp(app: GramJs.TypeBotApp): ApiBotApp | undefined {
|
||||
if (app instanceof GramJs.BotAppNotModified) return undefined;
|
||||
|
||||
const {
|
||||
id, accessHash, title, description, shortName, photo, document,
|
||||
} = app;
|
||||
@ -167,6 +168,16 @@ export function buildApiBotApp(botApp: GramJs.messages.BotApp): ApiBotApp | unde
|
||||
shortName,
|
||||
photo: apiPhoto,
|
||||
document: apiDocument,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiMessagesBotApp(botApp: GramJs.messages.BotApp): ApiMessagesBotApp | undefined {
|
||||
const { app, inactive, requestWriteAccess } = botApp;
|
||||
const baseApp = buildApiBotApp(app);
|
||||
if (!baseApp) return undefined;
|
||||
|
||||
return {
|
||||
...baseApp,
|
||||
isInactive: inactive,
|
||||
shouldRequestWriteAccess: requestWriteAccess,
|
||||
};
|
||||
|
||||
@ -50,6 +50,7 @@ import {
|
||||
resolveMessageApiChatId,
|
||||
serializeBytes,
|
||||
} from '../helpers';
|
||||
import { buildApiBotApp } from './bots';
|
||||
import { buildApiCallDiscardReason } from './calls';
|
||||
import {
|
||||
buildApiPhoto,
|
||||
@ -78,7 +79,7 @@ export function setMessageBuilderCurrentUserId(_currentUserId: string) {
|
||||
export function buildApiSponsoredMessage(mtpMessage: GramJs.SponsoredMessage): ApiSponsoredMessage | undefined {
|
||||
const {
|
||||
fromId, message, entities, startParam, channelPost, chatInvite, chatInviteHash, randomId, recommended, sponsorInfo,
|
||||
additionalInfo, showPeerPhoto, webpage,
|
||||
additionalInfo, showPeerPhoto, webpage, buttonText, app,
|
||||
} = mtpMessage;
|
||||
const chatId = fromId ? getApiChatIdFromMtpPeer(fromId) : undefined;
|
||||
const chatInviteTitle = chatInvite
|
||||
@ -102,6 +103,8 @@ export function buildApiSponsoredMessage(mtpMessage: GramJs.SponsoredMessage): A
|
||||
...(channelPost && { channelPostId: channelPost }),
|
||||
...(sponsorInfo && { sponsorInfo }),
|
||||
...(additionalInfo && { additionalInfo }),
|
||||
...(buttonText && { buttonText }),
|
||||
...(app && { botApp: buildApiBotApp(app) }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3,16 +3,21 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type {
|
||||
ApiBotApp,
|
||||
ApiChat, ApiInputMessageReplyInfo, ApiPeer, ApiThemeParameters, ApiUser, OnApiUpdate,
|
||||
ApiChat,
|
||||
ApiInputMessageReplyInfo,
|
||||
ApiPeer,
|
||||
ApiThemeParameters,
|
||||
ApiUser,
|
||||
OnApiUpdate,
|
||||
} from '../../types';
|
||||
|
||||
import { WEB_APP_PLATFORM } from '../../../config';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import {
|
||||
buildApiAttachBot,
|
||||
buildApiBotApp,
|
||||
buildApiBotInlineMediaResult,
|
||||
buildApiBotInlineResult,
|
||||
buildApiMessagesBotApp,
|
||||
buildBotSwitchPm,
|
||||
buildBotSwitchWebview,
|
||||
} from '../apiBuilders/bots';
|
||||
@ -255,7 +260,7 @@ export async function fetchBotApp({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return buildApiBotApp(result);
|
||||
return buildApiMessagesBotApp(result);
|
||||
}
|
||||
|
||||
export async function requestAppWebView({
|
||||
|
||||
@ -33,7 +33,7 @@ export {
|
||||
fetchPinnedMessages, fetchScheduledHistory, sendScheduledMessages, rescheduleMessage, deleteScheduledMessages,
|
||||
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs,
|
||||
saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, transcribeAudio,
|
||||
closePoll, fetchExtendedMedia, translateText, fetchMessageViews, fetchDiscussionMessage,
|
||||
closePoll, fetchExtendedMedia, translateText, fetchMessageViews, fetchDiscussionMessage, clickSponsoredMessage,
|
||||
} from './messages';
|
||||
|
||||
export {
|
||||
|
||||
@ -1554,6 +1554,13 @@ export async function viewSponsoredMessage({ chat, random }: { chat: ApiChat; ra
|
||||
}));
|
||||
}
|
||||
|
||||
export function clickSponsoredMessage({ chat, random }: { chat: ApiChat; random: string }) {
|
||||
return invokeRequest(new GramJs.channels.ClickSponsoredMessage({
|
||||
channel: buildInputPeer(chat.id, chat.accessHash),
|
||||
randomId: deserializeBytes(random),
|
||||
}));
|
||||
}
|
||||
|
||||
export function readAllMentions({
|
||||
chat,
|
||||
}: {
|
||||
|
||||
@ -602,6 +602,8 @@ export type ApiSponsoredMessage = {
|
||||
expiresAt: number;
|
||||
sponsorInfo?: string;
|
||||
additionalInfo?: string;
|
||||
buttonText?: string;
|
||||
botApp?: ApiBotApp;
|
||||
};
|
||||
|
||||
// KeyboardButtons
|
||||
@ -729,6 +731,9 @@ export type ApiBotApp = {
|
||||
description: string;
|
||||
photo?: ApiPhoto;
|
||||
document?: ApiDocument;
|
||||
};
|
||||
|
||||
export type ApiMessagesBotApp = ApiBotApp & {
|
||||
isInactive?: boolean;
|
||||
shouldRequestWriteAccess?: boolean;
|
||||
};
|
||||
|
||||
@ -103,6 +103,7 @@ type StateProps = {
|
||||
isRepliesChat?: boolean;
|
||||
isCreator?: boolean;
|
||||
isBot?: boolean;
|
||||
isSynced?: boolean;
|
||||
messageIds?: number[];
|
||||
messagesById?: Record<number, ApiMessage>;
|
||||
firstUnreadId?: number;
|
||||
@ -147,6 +148,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
isChannelChat,
|
||||
isGroupChat,
|
||||
canPost,
|
||||
isSynced,
|
||||
isReady,
|
||||
isChatWithSelf,
|
||||
isRepliesChat,
|
||||
@ -214,10 +216,10 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
}, [firstUnreadId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCurrentUserPremium && isChannelChat && isReady) {
|
||||
if (!isCurrentUserPremium && isChannelChat && isSynced && isReady) {
|
||||
loadSponsoredMessages({ chatId });
|
||||
}
|
||||
}, [isCurrentUserPremium, chatId, isReady, isChannelChat]);
|
||||
}, [isCurrentUserPremium, chatId, isSynced, isReady, isChannelChat]);
|
||||
|
||||
// Updated only once when messages are loaded (as we want the unread divider to keep its position)
|
||||
useSyncEffect(() => {
|
||||
@ -682,6 +684,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
||||
isRepliesChat: isChatWithRepliesBot(chatId),
|
||||
isBot: Boolean(chatBot),
|
||||
isSynced: global.isSynced,
|
||||
messageIds,
|
||||
messagesById,
|
||||
firstUnreadId: selectFirstUnreadId(global, chatId, threadId),
|
||||
|
||||
@ -27,6 +27,8 @@
|
||||
}
|
||||
|
||||
.message-type {
|
||||
padding-inline-end: 0.25rem;
|
||||
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import type {
|
||||
import { getChatTitle, getUserFullName } from '../../../global/helpers';
|
||||
import { selectChat, selectSponsoredMessage, selectUser } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
||||
import { IS_ANDROID, IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||
import { getPeerColorClass } from '../../common/helpers/peerColor';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
@ -57,10 +58,12 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
||||
viewSponsoredMessage,
|
||||
openChat,
|
||||
openChatByInvite,
|
||||
requestAppWebView,
|
||||
startBot,
|
||||
focusMessage,
|
||||
openUrl,
|
||||
openPremiumModal,
|
||||
clickSponsoredMessage,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
@ -84,6 +87,7 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
||||
const [isAboutAdsModalOpen, openAboutAdsModal, closeAboutAdsModal] = useFlag(false);
|
||||
const { isMobile } = useAppLayout();
|
||||
const withAvatar = Boolean(message?.isAvatarShown && peer);
|
||||
const isBotApp = Boolean(message?.botApp);
|
||||
|
||||
useEffect(() => {
|
||||
return shouldObserve ? observeIntersection(contentRef.current!, (target) => {
|
||||
@ -108,6 +112,8 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const handleLinkClick = useLastCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
|
||||
clickSponsoredMessage({ chatId });
|
||||
openUrl({ url: message!.webPage!.url, shouldSkipModal: true });
|
||||
|
||||
return false;
|
||||
@ -119,7 +125,20 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
if (!message) return;
|
||||
if (message.chatInviteHash) {
|
||||
|
||||
clickSponsoredMessage({ chatId });
|
||||
|
||||
if (isBotApp) {
|
||||
const { shortName } = message.botApp!;
|
||||
const theme = extractCurrentThemeParams();
|
||||
|
||||
requestAppWebView({
|
||||
botId: message.chatId!,
|
||||
appName: shortName,
|
||||
startApp: message.startParam,
|
||||
theme,
|
||||
});
|
||||
} else if (message.chatInviteHash) {
|
||||
openChatByInvite({ hash: message.chatInviteHash });
|
||||
} else if (message.channelPostId) {
|
||||
focusMessage({ chatId: message.chatId!, messageId: message.channelPostId });
|
||||
@ -149,6 +168,33 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
function renderPhoto() {
|
||||
if (message?.botApp) {
|
||||
if (!message.botApp.photo) return undefined;
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
size="large"
|
||||
peer={bot}
|
||||
photo={message.botApp.photo}
|
||||
className={buildClassName('channel-avatar', lang.isRtl && 'is-rtl')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (channel) {
|
||||
return (
|
||||
<Avatar
|
||||
size="large"
|
||||
peer={channel}
|
||||
className={buildClassName('channel-avatar', lang.isRtl && 'is-rtl')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function renderContent() {
|
||||
if (message?.webPage) {
|
||||
return (
|
||||
@ -179,12 +225,23 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
const buttonText = message?.buttonText ?? (
|
||||
isBotApp
|
||||
? lang('BotWebAppInstantViewOpen')
|
||||
: (message!.isBot
|
||||
? lang('Conversation.ViewBot')
|
||||
: lang(message!.channelPostId ? 'Conversation.ViewPost' : 'Conversation.ViewChannel')
|
||||
));
|
||||
const title = isBotApp
|
||||
? message!.botApp!.title
|
||||
: (bot
|
||||
? renderText(getUserFullName(bot) || '')
|
||||
: (channel ? renderText(message!.chatInviteTitle || getChatTitle(lang, channel) || '') : '')
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="message-title message-peer" dir="auto">
|
||||
{bot && renderText(getUserFullName(bot) || '')}
|
||||
{channel && renderText(message!.chatInviteTitle || getChatTitle(lang, channel) || '')}
|
||||
</div>
|
||||
<div className="message-title message-peer" dir="auto">{title}</div>
|
||||
<div className="text-content with-meta" dir="auto" ref={contentRef}>
|
||||
<span className="text-content-inner" dir="auto">
|
||||
{renderTextWithEntities({
|
||||
@ -201,9 +258,7 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
||||
isRectangular
|
||||
onClick={handleClick}
|
||||
>
|
||||
{lang(message!.isBot
|
||||
? 'Conversation.ViewBot'
|
||||
: (message!.channelPostId ? 'Conversation.ViewPost' : 'Conversation.ViewChannel'))}
|
||||
{buttonText}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
@ -211,7 +266,7 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const contentClassName = buildClassName(
|
||||
'message-content has-shadow has-solid-background has-appendix',
|
||||
getPeerColorClass(peer || channel, true, true),
|
||||
getPeerColorClass(bot || peer || channel),
|
||||
);
|
||||
|
||||
return (
|
||||
@ -228,13 +283,7 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
<div className="content-inner" dir="auto">
|
||||
{channel && (
|
||||
<Avatar
|
||||
size="large"
|
||||
peer={channel}
|
||||
className={buildClassName('channel-avatar', lang.isRtl && 'is-rtl')}
|
||||
/>
|
||||
)}
|
||||
{renderPhoto()}
|
||||
<span className="message-title message-type">
|
||||
{message!.isRecommended ? lang('Message.RecommendedLabel') : lang('SponsoredMessage')}
|
||||
</span>
|
||||
|
||||
@ -1458,6 +1458,17 @@ addActionHandler('viewSponsoredMessage', (global, actions, payload): ActionRetur
|
||||
void callApi('viewSponsoredMessage', { chat, random: message.randomId });
|
||||
});
|
||||
|
||||
addActionHandler('clickSponsoredMessage', (global, actions, payload): ActionReturnType => {
|
||||
const { chatId } = payload;
|
||||
const chat = selectChat(global, chatId);
|
||||
const message = selectSponsoredMessage(global, chatId);
|
||||
if (!chat || !message) {
|
||||
return;
|
||||
}
|
||||
|
||||
void callApi('clickSponsoredMessage', { chat, random: message.randomId });
|
||||
});
|
||||
|
||||
addActionHandler('fetchUnreadMentions', async (global, actions, payload): Promise<void> => {
|
||||
const { chatId, offsetId } = payload;
|
||||
const chat = selectChat(global, chatId);
|
||||
|
||||
@ -9,9 +9,7 @@ import type {
|
||||
} from '../../api/types';
|
||||
import type { LangFn } from '../../hooks/useLang';
|
||||
import type { NotifyException, NotifySettings } from '../../types';
|
||||
import {
|
||||
MAIN_THREAD_ID,
|
||||
} from '../../api/types';
|
||||
import { MAIN_THREAD_ID } from '../../api/types';
|
||||
|
||||
import {
|
||||
ARCHIVED_FOLDER_ID, CHANNEL_ID_LENGTH, GENERAL_TOPIC_ID, REPLIES_USER_ID, TME_LINK_PREFIX,
|
||||
@ -474,11 +472,11 @@ export function getPeerIdDividend(peerId: string) {
|
||||
export function getPeerColorKey(peer: ApiPeer | undefined) {
|
||||
if (peer?.color?.color) return peer.color.color;
|
||||
|
||||
const index = peer ? getPeerIdDividend(peer.id) % 7 : 0;
|
||||
return index;
|
||||
return peer ? getPeerIdDividend(peer.id) % 7 : 0;
|
||||
}
|
||||
|
||||
export function getPeerColorCount(peer: ApiPeer) {
|
||||
const key = getPeerColorKey(peer);
|
||||
// eslint-disable-next-line eslint-multitab-tt/no-immediate-global
|
||||
return getGlobal().peerColors?.general[key].colors?.length || 1;
|
||||
}
|
||||
|
||||
@ -1336,6 +1336,9 @@ export interface ActionPayloads {
|
||||
viewSponsoredMessage: {
|
||||
chatId: string;
|
||||
};
|
||||
clickSponsoredMessage: {
|
||||
chatId: string;
|
||||
};
|
||||
loadSendAs: {
|
||||
chatId: string;
|
||||
};
|
||||
|
||||
@ -1441,6 +1441,7 @@ channels.editForumTopic#f4dfa185 flags:# channel:InputChannel topic_id:int title
|
||||
channels.updatePinnedForumTopic#6c2d9026 channel:InputChannel topic_id:int pinned:Bool = Updates;
|
||||
channels.deleteTopicHistory#34435f2d channel:InputChannel top_msg_id:int = messages.AffectedHistory;
|
||||
channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = Updates;
|
||||
channels.clickSponsoredMessage#18afbc93 channel:InputChannel random_id:bytes = Bool;
|
||||
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
||||
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
||||
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
||||
|
||||
@ -288,6 +288,7 @@
|
||||
"channels.updatePinnedForumTopic",
|
||||
"channels.deleteTopicHistory",
|
||||
"channels.toggleParticipantsHidden",
|
||||
"channels.clickSponsoredMessage",
|
||||
"photos.uploadContactProfilePhoto",
|
||||
"messages.getMessagesViews",
|
||||
"chatlists.exportChatlistInvite",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user