Add AyuLike settings screen with filter import/export/clear
- New SettingsScreens.AyuLikeSettings screen wired into Settings.tsx - SettingsAyuLike: toggle hideSponsoredMessages, import JSON from AyuGram Desktop, export back to same format, clear all rules with confirmation - messageFilters util: importFromAyuGram / exportToAyuGram supporting AyuGram v2 export format (text=regex, dialogId, caseInsensitive, reversed) - Entry in SettingsMain under existing menu section
This commit is contained in:
parent
c3fa8684cc
commit
1913174e1c
@ -23,6 +23,7 @@ import SettingsCustomEmoji from './SettingsCustomEmoji';
|
||||
import SettingsDataStorage from './SettingsDataStorage';
|
||||
import SettingsDoNotTranslate from './SettingsDoNotTranslate';
|
||||
import SettingsEditProfile from './SettingsEditProfile';
|
||||
import SettingsAyuLike from './SettingsAyuLike';
|
||||
import SettingsExperimental from './SettingsExperimental';
|
||||
import SettingsGeneral from './SettingsGeneral';
|
||||
import SettingsGeneralBackground from './SettingsGeneralBackground';
|
||||
@ -307,6 +308,10 @@ const Settings: FC<OwnProps> = ({
|
||||
return (
|
||||
<SettingsStickers isActive={isScreenActive} onReset={handleReset} />
|
||||
);
|
||||
case SettingsScreens.AyuLikeSettings:
|
||||
return (
|
||||
<SettingsAyuLike isActive={isScreenActive} onReset={handleReset} />
|
||||
);
|
||||
case SettingsScreens.Experimental:
|
||||
return (
|
||||
<SettingsExperimental isActive={isScreenActive} onReset={handleReset} />
|
||||
|
||||
154
src/components/left/settings/SettingsAyuLike.tsx
Normal file
154
src/components/left/settings/SettingsAyuLike.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
import { memo, useRef } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { MessageFilterRule } from '../../../global/types/sharedState';
|
||||
import { selectSharedSettings } from '../../../global/selectors/sharedState';
|
||||
import download from '../../../util/download';
|
||||
import { exportToAyuGram, importFromAyuGram } from '../../../util/ayuLike/messageFilters';
|
||||
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Island from '../../gili/layout/Island';
|
||||
import Checkbox from '../../ui/Checkbox';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
hideSponsoredMessages: boolean;
|
||||
messageFilters: MessageFilterRule[];
|
||||
};
|
||||
|
||||
const EXPORT_FILENAME = 'ayu-filters.json';
|
||||
|
||||
const SettingsAyuLike = ({
|
||||
isActive,
|
||||
hideSponsoredMessages,
|
||||
messageFilters,
|
||||
onReset,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { setSharedSettingOption } = getActions();
|
||||
const fileInputRef = useRef<HTMLInputElement>();
|
||||
const [isClearConfirmOpen, openClearConfirm, closeClearConfirm] = useFlag(false);
|
||||
|
||||
useHistoryBack({ isActive, onBack: onReset });
|
||||
|
||||
const handleExport = useLastCallback(() => {
|
||||
const json = exportToAyuGram(messageFilters);
|
||||
const blob = new Blob([json], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
download(url, EXPORT_FILENAME);
|
||||
setTimeout(() => URL.revokeObjectURL(url), 10_000);
|
||||
});
|
||||
|
||||
const handleImportClick = useLastCallback(() => {
|
||||
fileInputRef.current?.click();
|
||||
});
|
||||
|
||||
const handleFileChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const imported = importFromAyuGram(JSON.parse(reader.result as string));
|
||||
setSharedSettingOption({
|
||||
ayuLike: { hideSponsoredMessages, messageFilters: imported },
|
||||
});
|
||||
} catch {
|
||||
// Silently ignore malformed files — user will see no change
|
||||
}
|
||||
// Reset so the same file can be re-imported
|
||||
e.target.value = '';
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
|
||||
const handleClearConfirmed = useLastCallback(() => {
|
||||
setSharedSettingOption({
|
||||
ayuLike: { hideSponsoredMessages, messageFilters: [] },
|
||||
});
|
||||
closeClearConfirm();
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="settings-content custom-scroll">
|
||||
<div className="settings-content-header no-border">
|
||||
<p className="settings-item-description pt-3" dir="auto">
|
||||
Local message filters and ad settings. Changes take effect immediately and are stored locally.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Island>
|
||||
<Checkbox
|
||||
label="Hide Sponsored Messages"
|
||||
checked={hideSponsoredMessages}
|
||||
onCheck={(checked) => setSharedSettingOption({
|
||||
ayuLike: { hideSponsoredMessages: checked, messageFilters },
|
||||
})}
|
||||
/>
|
||||
</Island>
|
||||
|
||||
<Island>
|
||||
<ListItem
|
||||
icon="download"
|
||||
onClick={handleImportClick}
|
||||
>
|
||||
<div className="title">Import Filters from AyuGram</div>
|
||||
{messageFilters.length > 0 && (
|
||||
<span className="settings-item__current-value">{messageFilters.length} rules</span>
|
||||
)}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="document"
|
||||
disabled={messageFilters.length === 0}
|
||||
onClick={handleExport}
|
||||
>
|
||||
<div className="title">Export Filters</div>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="delete"
|
||||
disabled={messageFilters.length === 0}
|
||||
onClick={openClearConfirm}
|
||||
>
|
||||
<div className="title">Clear All Filters</div>
|
||||
</ListItem>
|
||||
</Island>
|
||||
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".json,application/json"
|
||||
style="display: none"
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
isOpen={isClearConfirmOpen}
|
||||
title="Clear All Filters"
|
||||
text={`Remove all ${messageFilters.length} filter rules?`}
|
||||
confirmLabel="Clear"
|
||||
confirmIsDestructive
|
||||
onClose={closeClearConfirm}
|
||||
confirmHandler={handleClearConfirmed}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): Complete<StateProps> => {
|
||||
const { ayuLike } = selectSharedSettings(global);
|
||||
return {
|
||||
hideSponsoredMessages: ayuLike.hideSponsoredMessages,
|
||||
messageFilters: ayuLike.messageFilters,
|
||||
};
|
||||
},
|
||||
)(SettingsAyuLike));
|
||||
@ -167,6 +167,13 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
{lang('MenuStickers')}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="calendar-filter"
|
||||
narrow
|
||||
onClick={() => openSettingsScreen({ screen: SettingsScreens.AyuLikeSettings })}
|
||||
>
|
||||
Message Filters
|
||||
</ListItem>
|
||||
</Island>
|
||||
<Island>
|
||||
{canBuyPremium && (
|
||||
|
||||
@ -273,6 +273,7 @@ export enum SettingsScreens {
|
||||
PasscodeTurnOff,
|
||||
PasscodeCongratulations,
|
||||
Experimental,
|
||||
AyuLikeSettings,
|
||||
Stickers,
|
||||
QuickReaction,
|
||||
CustomEmoji,
|
||||
|
||||
@ -31,6 +31,50 @@ function getMessageMediaTypes(message: ApiMessage): string[] {
|
||||
].filter(Boolean) as string[];
|
||||
}
|
||||
|
||||
// AyuGram Desktop export format (version 2)
|
||||
interface AyuGramExport {
|
||||
filters: {
|
||||
id: string;
|
||||
text: string;
|
||||
enabled: boolean;
|
||||
reversed: boolean;
|
||||
caseInsensitive: boolean;
|
||||
dialogId: string | null;
|
||||
}[];
|
||||
exclusions?: unknown[];
|
||||
version?: number;
|
||||
}
|
||||
|
||||
export function importFromAyuGram(json: unknown): MessageFilterRule[] {
|
||||
const data = json as AyuGramExport;
|
||||
if (!Array.isArray(data?.filters)) throw new Error('Invalid AyuGram export format');
|
||||
|
||||
return data.filters.map((f) => ({
|
||||
id: f.id,
|
||||
enabled: Boolean(f.enabled),
|
||||
reversed: Boolean(f.reversed),
|
||||
caseInsensitive: Boolean(f.caseInsensitive),
|
||||
regex: f.text,
|
||||
chatIds: f.dialogId ? [String(f.dialogId)] : undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
export function exportToAyuGram(rules: MessageFilterRule[]): string {
|
||||
const data: AyuGramExport = {
|
||||
exclusions: [],
|
||||
filters: rules.map((r) => ({
|
||||
id: r.id,
|
||||
text: r.regex ?? r.keyword ?? '',
|
||||
enabled: r.enabled,
|
||||
reversed: Boolean(r.reversed),
|
||||
caseInsensitive: Boolean(r.caseInsensitive),
|
||||
dialogId: r.chatIds?.[0] ?? null,
|
||||
})),
|
||||
version: 2,
|
||||
};
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
export function shouldHideMessageByRules(
|
||||
message: ApiMessage,
|
||||
rules: MessageFilterRule[] = [],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user