Add local sponsored message hiding and message filter engine
- MessageList: gate loadSponsoredMessages and canShowAds on hideSponsoredMessages - messageFilters util: keyword/regex/media-type/sender/chat rules with compiled regex cache - shouldHideMessageByRules inserted in listedMessages useMemo, skips action messages - reversed and caseInsensitive fields supported per AyuGram filter model
This commit is contained in:
parent
32153e86de
commit
2879188020
@ -52,6 +52,9 @@ import {
|
|||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { selectIsChatRestricted } from '../../global/selectors/chats';
|
import { selectIsChatRestricted } from '../../global/selectors/chats';
|
||||||
import { selectActiveRestrictionReasons, selectCurrentMessageList } from '../../global/selectors/messages';
|
import { selectActiveRestrictionReasons, selectCurrentMessageList } from '../../global/selectors/messages';
|
||||||
|
import type { MessageFilterRule } from '../../global/types/sharedState';
|
||||||
|
import { selectSharedSettings } from '../../global/selectors/sharedState';
|
||||||
|
import { shouldHideMessageByRules } from '../../util/ayuLike/messageFilters';
|
||||||
import {
|
import {
|
||||||
selectLastScrollOffset,
|
selectLastScrollOffset,
|
||||||
selectScrollOffset,
|
selectScrollOffset,
|
||||||
@ -143,6 +146,8 @@ type StateProps = {
|
|||||||
currentUserId: string;
|
currentUserId: string;
|
||||||
isAccountFrozen?: boolean;
|
isAccountFrozen?: boolean;
|
||||||
areAdsEnabled?: boolean;
|
areAdsEnabled?: boolean;
|
||||||
|
hideSponsoredMessages?: boolean;
|
||||||
|
messageFilters?: MessageFilterRule[];
|
||||||
channelJoinInfo?: ApiChatFullInfo['joinInfo'];
|
channelJoinInfo?: ApiChatFullInfo['joinInfo'];
|
||||||
isChatProtected?: boolean;
|
isChatProtected?: boolean;
|
||||||
hasCustomGreeting?: boolean;
|
hasCustomGreeting?: boolean;
|
||||||
@ -243,6 +248,8 @@ const MessageList = ({
|
|||||||
isContactRequirePremium,
|
isContactRequirePremium,
|
||||||
paidMessagesStars,
|
paidMessagesStars,
|
||||||
areAdsEnabled,
|
areAdsEnabled,
|
||||||
|
hideSponsoredMessages,
|
||||||
|
messageFilters,
|
||||||
channelJoinInfo,
|
channelJoinInfo,
|
||||||
isChatProtected,
|
isChatProtected,
|
||||||
isAccountFrozen,
|
isAccountFrozen,
|
||||||
@ -387,10 +394,10 @@ const MessageList = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canHaveAds = isChannelChat || isBot;
|
const canHaveAds = isChannelChat || isBot;
|
||||||
if (areAdsEnabled && canHaveAds && isSynced && isReady && isAppConfigLoaded) {
|
if (!hideSponsoredMessages && areAdsEnabled && canHaveAds && isSynced && isReady && isAppConfigLoaded) {
|
||||||
loadSponsoredMessages({ peerId: chatId });
|
loadSponsoredMessages({ peerId: chatId });
|
||||||
}
|
}
|
||||||
}, [chatId, isSynced, isReady, isChannelChat, isBot, areAdsEnabled, isAppConfigLoaded]);
|
}, [chatId, isSynced, isReady, isChannelChat, isBot, areAdsEnabled, isAppConfigLoaded, hideSponsoredMessages]);
|
||||||
|
|
||||||
// Updated only once when messages are loaded (as we want the unread divider to keep its position)
|
// Updated only once when messages are loaded (as we want the unread divider to keep its position)
|
||||||
useSyncEffect(() => {
|
useSyncEffect(() => {
|
||||||
@ -425,6 +432,10 @@ const MessageList = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldHideMessageByRules(message, messageFilters)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { shouldAppendJoinMessage, shouldAppendJoinMessageAfterCurrent } = (() => {
|
const { shouldAppendJoinMessage, shouldAppendJoinMessageAfterCurrent } = (() => {
|
||||||
if (!channelJoinInfo || type !== 'thread') return undefined;
|
if (!channelJoinInfo || type !== 'thread') return undefined;
|
||||||
if (prevMessage
|
if (prevMessage
|
||||||
@ -487,7 +498,8 @@ const MessageList = ({
|
|||||||
}, [withUsers,
|
}, [withUsers,
|
||||||
messageIds, messagesById, type,
|
messageIds, messagesById, type,
|
||||||
isServiceNotificationsChat, isForum,
|
isServiceNotificationsChat, isForum,
|
||||||
threadId, isChatWithSelf, channelJoinInfo, effectiveLiveTailStartOriginalId]);
|
threadId, isChatWithSelf, channelJoinInfo, effectiveLiveTailStartOriginalId,
|
||||||
|
messageFilters]);
|
||||||
|
|
||||||
const currentLastMessageId = messageIds?.[messageIds.length - 1];
|
const currentLastMessageId = messageIds?.[messageIds.length - 1];
|
||||||
const currentLastMessage = currentLastMessageId !== undefined ? messagesById?.[currentLastMessageId] : undefined;
|
const currentLastMessage = currentLastMessageId !== undefined ? messagesById?.[currentLastMessageId] : undefined;
|
||||||
@ -1146,7 +1158,7 @@ const MessageList = ({
|
|||||||
/>
|
/>
|
||||||
) : activeKey === Content.MessageList ? (
|
) : activeKey === Content.MessageList ? (
|
||||||
<MessageListContent
|
<MessageListContent
|
||||||
canShowAds={areAdsEnabled && isChannelChat}
|
canShowAds={!hideSponsoredMessages && areAdsEnabled && isChannelChat}
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
isComments={isComments}
|
isComments={isComments}
|
||||||
isChannelChat={isChannelChat}
|
isChannelChat={isChannelChat}
|
||||||
@ -1253,6 +1265,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||||
const areAdsEnabled = !isCurrentUserPremium || selectUserFullInfo(global, currentUserId)?.areAdsEnabled;
|
const areAdsEnabled = !isCurrentUserPremium || selectUserFullInfo(global, currentUserId)?.areAdsEnabled;
|
||||||
|
const { ayuLike } = selectSharedSettings(global);
|
||||||
const isAccountFrozen = selectIsCurrentUserFrozen(global);
|
const isAccountFrozen = selectIsCurrentUserFrozen(global);
|
||||||
|
|
||||||
const hasCustomGreeting = Boolean(userFullInfo?.businessIntro);
|
const hasCustomGreeting = Boolean(userFullInfo?.businessIntro);
|
||||||
@ -1277,6 +1290,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return {
|
return {
|
||||||
isActive,
|
isActive,
|
||||||
areAdsEnabled,
|
areAdsEnabled,
|
||||||
|
hideSponsoredMessages: ayuLike?.hideSponsoredMessages,
|
||||||
|
messageFilters: ayuLike?.messageFilters,
|
||||||
isChatLoaded: true,
|
isChatLoaded: true,
|
||||||
isRestricted,
|
isRestricted,
|
||||||
restrictionReasons,
|
restrictionReasons,
|
||||||
|
|||||||
68
src/util/ayuLike/messageFilters.ts
Normal file
68
src/util/ayuLike/messageFilters.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import type { ApiMessage } from '../../api/types';
|
||||||
|
import type { MessageFilterRule } from '../../global/types/sharedState';
|
||||||
|
|
||||||
|
const compiledRegexCache = new Map<string, RegExp | null>();
|
||||||
|
|
||||||
|
function getCompiledRegex(pattern: string, caseInsensitive?: boolean): RegExp | null {
|
||||||
|
const key = `${caseInsensitive ? 'i' : ''}:${pattern}`;
|
||||||
|
if (!compiledRegexCache.has(key)) {
|
||||||
|
try {
|
||||||
|
compiledRegexCache.set(key, new RegExp(pattern, caseInsensitive ? 'i' : ''));
|
||||||
|
} catch {
|
||||||
|
compiledRegexCache.set(key, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return compiledRegexCache.get(key) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMessageMediaTypes(message: ApiMessage): string[] {
|
||||||
|
const { content } = message;
|
||||||
|
return [
|
||||||
|
content.text && 'text',
|
||||||
|
content.photo && 'photo',
|
||||||
|
content.video && 'video',
|
||||||
|
content.document && 'document',
|
||||||
|
content.sticker && 'sticker',
|
||||||
|
content.voice && 'voice',
|
||||||
|
content.audio && 'audio',
|
||||||
|
content.webPage && 'webPage',
|
||||||
|
content.pollId && 'poll',
|
||||||
|
content.storyData && 'story',
|
||||||
|
].filter(Boolean) as string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldHideMessageByRules(
|
||||||
|
message: ApiMessage,
|
||||||
|
rules: MessageFilterRule[] = [],
|
||||||
|
): boolean {
|
||||||
|
if (!rules.length) return false;
|
||||||
|
|
||||||
|
// Never filter action/service messages
|
||||||
|
if (message.content.action) return false;
|
||||||
|
|
||||||
|
const text = message.content.text?.text ?? '';
|
||||||
|
const mediaTypes = getMessageMediaTypes(message);
|
||||||
|
|
||||||
|
return rules.some((rule) => {
|
||||||
|
if (!rule.enabled) return false;
|
||||||
|
|
||||||
|
if (rule.chatIds?.length && !rule.chatIds.includes(message.chatId)) return false;
|
||||||
|
if (rule.senderIds?.length && (!message.senderId || !rule.senderIds.includes(message.senderId))) return false;
|
||||||
|
if (rule.mediaTypes?.length && !rule.mediaTypes.some((t) => mediaTypes.includes(t))) return false;
|
||||||
|
|
||||||
|
let matched = false;
|
||||||
|
|
||||||
|
if (rule.keyword) {
|
||||||
|
matched = rule.caseInsensitive
|
||||||
|
? text.toLowerCase().includes(rule.keyword.toLowerCase())
|
||||||
|
: text.includes(rule.keyword);
|
||||||
|
} else if (rule.regex) {
|
||||||
|
const re = getCompiledRegex(rule.regex, rule.caseInsensitive);
|
||||||
|
matched = re ? re.test(text) : false;
|
||||||
|
} else if (rule.mediaTypes?.length) {
|
||||||
|
matched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rule.reversed ? !matched : matched;
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user