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';
|
||||
import { selectIsChatRestricted } from '../../global/selectors/chats';
|
||||
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 {
|
||||
selectLastScrollOffset,
|
||||
selectScrollOffset,
|
||||
@ -143,6 +146,8 @@ type StateProps = {
|
||||
currentUserId: string;
|
||||
isAccountFrozen?: boolean;
|
||||
areAdsEnabled?: boolean;
|
||||
hideSponsoredMessages?: boolean;
|
||||
messageFilters?: MessageFilterRule[];
|
||||
channelJoinInfo?: ApiChatFullInfo['joinInfo'];
|
||||
isChatProtected?: boolean;
|
||||
hasCustomGreeting?: boolean;
|
||||
@ -243,6 +248,8 @@ const MessageList = ({
|
||||
isContactRequirePremium,
|
||||
paidMessagesStars,
|
||||
areAdsEnabled,
|
||||
hideSponsoredMessages,
|
||||
messageFilters,
|
||||
channelJoinInfo,
|
||||
isChatProtected,
|
||||
isAccountFrozen,
|
||||
@ -387,10 +394,10 @@ const MessageList = ({
|
||||
|
||||
useEffect(() => {
|
||||
const canHaveAds = isChannelChat || isBot;
|
||||
if (areAdsEnabled && canHaveAds && isSynced && isReady && isAppConfigLoaded) {
|
||||
if (!hideSponsoredMessages && areAdsEnabled && canHaveAds && isSynced && isReady && isAppConfigLoaded) {
|
||||
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)
|
||||
useSyncEffect(() => {
|
||||
@ -425,6 +432,10 @@ const MessageList = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldHideMessageByRules(message, messageFilters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { shouldAppendJoinMessage, shouldAppendJoinMessageAfterCurrent } = (() => {
|
||||
if (!channelJoinInfo || type !== 'thread') return undefined;
|
||||
if (prevMessage
|
||||
@ -487,7 +498,8 @@ const MessageList = ({
|
||||
}, [withUsers,
|
||||
messageIds, messagesById, type,
|
||||
isServiceNotificationsChat, isForum,
|
||||
threadId, isChatWithSelf, channelJoinInfo, effectiveLiveTailStartOriginalId]);
|
||||
threadId, isChatWithSelf, channelJoinInfo, effectiveLiveTailStartOriginalId,
|
||||
messageFilters]);
|
||||
|
||||
const currentLastMessageId = messageIds?.[messageIds.length - 1];
|
||||
const currentLastMessage = currentLastMessageId !== undefined ? messagesById?.[currentLastMessageId] : undefined;
|
||||
@ -1146,7 +1158,7 @@ const MessageList = ({
|
||||
/>
|
||||
) : activeKey === Content.MessageList ? (
|
||||
<MessageListContent
|
||||
canShowAds={areAdsEnabled && isChannelChat}
|
||||
canShowAds={!hideSponsoredMessages && areAdsEnabled && isChannelChat}
|
||||
chatId={chatId}
|
||||
isComments={isComments}
|
||||
isChannelChat={isChannelChat}
|
||||
@ -1253,6 +1265,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||
const areAdsEnabled = !isCurrentUserPremium || selectUserFullInfo(global, currentUserId)?.areAdsEnabled;
|
||||
const { ayuLike } = selectSharedSettings(global);
|
||||
const isAccountFrozen = selectIsCurrentUserFrozen(global);
|
||||
|
||||
const hasCustomGreeting = Boolean(userFullInfo?.businessIntro);
|
||||
@ -1277,6 +1290,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
return {
|
||||
isActive,
|
||||
areAdsEnabled,
|
||||
hideSponsoredMessages: ayuLike?.hideSponsoredMessages,
|
||||
messageFilters: ayuLike?.messageFilters,
|
||||
isChatLoaded: true,
|
||||
isRestricted,
|
||||
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