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 SettingsDataStorage from './SettingsDataStorage';
|
||||||
import SettingsDoNotTranslate from './SettingsDoNotTranslate';
|
import SettingsDoNotTranslate from './SettingsDoNotTranslate';
|
||||||
import SettingsEditProfile from './SettingsEditProfile';
|
import SettingsEditProfile from './SettingsEditProfile';
|
||||||
|
import SettingsAyuLike from './SettingsAyuLike';
|
||||||
import SettingsExperimental from './SettingsExperimental';
|
import SettingsExperimental from './SettingsExperimental';
|
||||||
import SettingsGeneral from './SettingsGeneral';
|
import SettingsGeneral from './SettingsGeneral';
|
||||||
import SettingsGeneralBackground from './SettingsGeneralBackground';
|
import SettingsGeneralBackground from './SettingsGeneralBackground';
|
||||||
@ -307,6 +308,10 @@ const Settings: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<SettingsStickers isActive={isScreenActive} onReset={handleReset} />
|
<SettingsStickers isActive={isScreenActive} onReset={handleReset} />
|
||||||
);
|
);
|
||||||
|
case SettingsScreens.AyuLikeSettings:
|
||||||
|
return (
|
||||||
|
<SettingsAyuLike isActive={isScreenActive} onReset={handleReset} />
|
||||||
|
);
|
||||||
case SettingsScreens.Experimental:
|
case SettingsScreens.Experimental:
|
||||||
return (
|
return (
|
||||||
<SettingsExperimental isActive={isScreenActive} onReset={handleReset} />
|
<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')}
|
{lang('MenuStickers')}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
<ListItem
|
||||||
|
icon="calendar-filter"
|
||||||
|
narrow
|
||||||
|
onClick={() => openSettingsScreen({ screen: SettingsScreens.AyuLikeSettings })}
|
||||||
|
>
|
||||||
|
Message Filters
|
||||||
|
</ListItem>
|
||||||
</Island>
|
</Island>
|
||||||
<Island>
|
<Island>
|
||||||
{canBuyPremium && (
|
{canBuyPremium && (
|
||||||
|
|||||||
@ -273,6 +273,7 @@ export enum SettingsScreens {
|
|||||||
PasscodeTurnOff,
|
PasscodeTurnOff,
|
||||||
PasscodeCongratulations,
|
PasscodeCongratulations,
|
||||||
Experimental,
|
Experimental,
|
||||||
|
AyuLikeSettings,
|
||||||
Stickers,
|
Stickers,
|
||||||
QuickReaction,
|
QuickReaction,
|
||||||
CustomEmoji,
|
CustomEmoji,
|
||||||
|
|||||||
@ -31,6 +31,50 @@ function getMessageMediaTypes(message: ApiMessage): string[] {
|
|||||||
].filter(Boolean) as 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(
|
export function shouldHideMessageByRules(
|
||||||
message: ApiMessage,
|
message: ApiMessage,
|
||||||
rules: MessageFilterRule[] = [],
|
rules: MessageFilterRule[] = [],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user