Add files via upload
This commit is contained in:
parent
d45e9ef61f
commit
021e0c15fc
3 changed files with 386 additions and 0 deletions
138
src/core/plugins/nodelete/index.js
Normal file
138
src/core/plugins/nodelete/index.js
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
import settings from "./settings.jsx";
|
||||||
|
import * as common from "../../common";
|
||||||
|
import { FluxDispatcher, moment } from "@vendetta/metro/common";
|
||||||
|
import { storage } from "@vendetta/plugin";
|
||||||
|
import { before as patchBefore } from "@vendetta/patcher";
|
||||||
|
import { findByProps, findByStoreName } from "@vendetta/metro";
|
||||||
|
import { findInReactTree } from "@vendetta/utils";
|
||||||
|
import { showToast } from "@vendetta/ui/toasts";
|
||||||
|
import { getTranslation, massive } from "./translations.js";
|
||||||
|
//import { dispatcherPatch } from "./patches/dispatcher.js";
|
||||||
|
//import { contextMenuPatch } from "./patches/contextMenu.js"
|
||||||
|
|
||||||
|
common.makeDefaults(storage, {
|
||||||
|
ignore: {
|
||||||
|
users: [],
|
||||||
|
channels: [],
|
||||||
|
bots: false,
|
||||||
|
},
|
||||||
|
timestamps: false,
|
||||||
|
ew: false,
|
||||||
|
onlyTimestamps: false,
|
||||||
|
});
|
||||||
|
let MessageStore,
|
||||||
|
deleteable = [];
|
||||||
|
|
||||||
|
const patches = []
|
||||||
|
export default {
|
||||||
|
settings,
|
||||||
|
onUnload() {
|
||||||
|
for (const unpatch of patches) unpatch()
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
try {
|
||||||
|
patches.push(
|
||||||
|
patchBefore("dispatch", FluxDispatcher, (args) => {
|
||||||
|
try {
|
||||||
|
if (!MessageStore) MessageStore = findByStoreName("MessageStore");
|
||||||
|
const event = args[0];
|
||||||
|
|
||||||
|
if (!event || event?.type !== "MESSAGE_DELETE") return;
|
||||||
|
if (!event?.id || !event?.channelId) return;
|
||||||
|
|
||||||
|
const message = MessageStore.getMessage(event.channelId, event.id);
|
||||||
|
|
||||||
|
if (storage["ignore"]["users"].includes(message?.author?.id)) return;
|
||||||
|
if (storage["ignore"]["bots"] && message?.author?.bot) return;
|
||||||
|
|
||||||
|
if (deleteable.includes(event.id)) {
|
||||||
|
deleteable.splice(deleteable.indexOf(event.id), 1);
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
deleteable.push(event.id);
|
||||||
|
|
||||||
|
let automodMessage = getTranslation("thisMessageWasDeleted");
|
||||||
|
if (storage["timestamps"]) automodMessage += ` (${moment().format(storage["ew"] ? "hh:mm:ss.SS a" : "HH:mm:ss.SS")})`;
|
||||||
|
|
||||||
|
// overwrite the message delete event with the automod edit fail one
|
||||||
|
args[0] = {
|
||||||
|
type: "MESSAGE_EDIT_FAILED_AUTOMOD",
|
||||||
|
messageData: {
|
||||||
|
type: 1,
|
||||||
|
message: {
|
||||||
|
channelId: event.channelId,
|
||||||
|
messageId: event.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errorResponseBody: {
|
||||||
|
code: 200000,
|
||||||
|
message: automodMessage,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return args;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
alert(`[Nodelete → dispatcher patch] died\n${e.stack}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
/* thanks fres#2400 (<@843448897737064448>) for example patch
|
||||||
|
* add ignore user button
|
||||||
|
*/
|
||||||
|
const contextMenuUnpatch = patchBefore("render", findByProps("ScrollView").View, (args) => {
|
||||||
|
try {
|
||||||
|
const a = findInReactTree(args, (r) => r.key === ".$UserProfileOverflow");
|
||||||
|
if (!a || !a.props || a.props.sheetKey !== "UserProfileOverflow") return;
|
||||||
|
const props = a.props.content.props;
|
||||||
|
const _labels = massive.optionLabels.flatMap(Object.values);
|
||||||
|
if (props.options.some((option) => _labels.includes(option?.label))) return;
|
||||||
|
|
||||||
|
const focusedUserId = Object.keys(a._owner.stateNode._keyChildMapping)
|
||||||
|
.find((str) => a._owner.stateNode._keyChildMapping[str] && str.match(/(?<=\$UserProfile)\d+/))
|
||||||
|
?.slice?.(".$UserProfile".length);
|
||||||
|
|
||||||
|
const optionPosition = props.options.findLastIndex((option) => option.isDestructive);
|
||||||
|
if (!storage["ignore"]["users"].includes(focusedUserId)) {
|
||||||
|
props.options.splice(optionPosition + 1, 0, {
|
||||||
|
isDestructive: true,
|
||||||
|
label: getTranslation("optionLabels.0"), // START IGNORING
|
||||||
|
onPress: () => {
|
||||||
|
storage["ignore"]["users"].push(focusedUserId);
|
||||||
|
showToast(getTranslation("toastLabels.0").replaceAll("{user}", props.header.title));
|
||||||
|
|
||||||
|
props.hideActionSheet();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
props.options.splice(optionPosition + 1, 0, {
|
||||||
|
label: getTranslation("optionLabels.1"), // STOP IGNORING
|
||||||
|
onPress: () => {
|
||||||
|
storage["ignore"]["users"].splice(
|
||||||
|
storage["ignore"]["users"].findIndex((userId) => userId === focusedUserId),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
showToast(getTranslation("toastLabels.1").replaceAll("{user}", props.header.title));
|
||||||
|
|
||||||
|
props.hideActionSheet();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
let successful = false;
|
||||||
|
try {
|
||||||
|
successful = contextMenuUnpatch();
|
||||||
|
} catch (_) {
|
||||||
|
successful = false;
|
||||||
|
}
|
||||||
|
alert(`[NoDelete → context menu patch] failed. Patch ${successful ? "dis" : "en"}abled\n${e.stack}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
patches.push(contextMenuUnpatch);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
alert(`[NoDelete] dead\n${e.stack}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
90
src/core/plugins/nodelete/settings.jsx
Normal file
90
src/core/plugins/nodelete/settings.jsx
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { React, ReactNative } from "@vendetta/metro/common";
|
||||||
|
import { storage } from "@vendetta/plugin";
|
||||||
|
import { useProxy } from "@vendetta/storage";
|
||||||
|
import { showConfirmationAlert } from "@vendetta/ui/alerts";
|
||||||
|
import { Forms } from "@vendetta/ui/components";
|
||||||
|
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||||
|
import { getTranslation } from "./translations.js";
|
||||||
|
import ItemWithRemove from "../../common/ui/ItemWithRemove.jsx";
|
||||||
|
import { findByStoreName, findByProps } from "@vendetta/metro";
|
||||||
|
let UserStore, UncachedUserManager, Profiles;
|
||||||
|
export default (props) => {
|
||||||
|
UserStore ??= findByStoreName("UserStore");
|
||||||
|
UncachedUserManager ??= findByProps("fetchProfile", "getUser", "setFlag");
|
||||||
|
Profiles ??= findByProps("showUserProfile");
|
||||||
|
async function openProfile(userId) {
|
||||||
|
const show = Profiles.showUserProfile;
|
||||||
|
|
||||||
|
UserStore.getUser(userId) ? show({ userId }) : UncachedUserManager.getUser(userId).then(({ id }) => show({ userId: id }));
|
||||||
|
}
|
||||||
|
|
||||||
|
useProxy(storage);
|
||||||
|
const [users, setUsers] = React.useState(storage["ignore"]["users"]);
|
||||||
|
|
||||||
|
const handleRemoveUser = (userId) => {
|
||||||
|
const newArr = users.filter((id) => id !== userId);
|
||||||
|
storage["ignore"].users = newArr;
|
||||||
|
setUsers(newArr);
|
||||||
|
};
|
||||||
|
const handleClearUsers = () => {
|
||||||
|
storage["ignore"].users = [];
|
||||||
|
setUsers([]);
|
||||||
|
};
|
||||||
|
let uncached = 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactNative.ScrollView style={{ flex: 1 }}>
|
||||||
|
<Forms.FormSection title={getTranslation("settings.titles.settings")} titleStyleType="no_border">
|
||||||
|
<Forms.FormRow label={getTranslation("settings.showTimestamps")} trailing={<Forms.FormSwitch value={storage.timestamps} onValueChange={(v) => (storage.timestamps = v)} />} />
|
||||||
|
<Forms.FormRow label={getTranslation("settings.ewTimestampFormat")} trailing={<Forms.FormSwitch value={storage["ew"]} onValueChange={(v) => (storage.ew = v)} />} />
|
||||||
|
<Forms.FormDivider />
|
||||||
|
<Forms.FormRow label={getTranslation("settings.youDeletedItWarning")} />
|
||||||
|
</Forms.FormSection>
|
||||||
|
<Forms.FormSection title={getTranslation("settings.titles.filters")}>
|
||||||
|
<Forms.FormRow label={getTranslation("settings.ignoreBots")} trailing={<Forms.FormSwitch value={storage["ignore"].bots} onValueChange={(value) => (storage["ignore"].bots = value)} />} />
|
||||||
|
<Forms.FormRow
|
||||||
|
label={getTranslation("settings.clearUsersLabel", true)?.make?.(users.length)}
|
||||||
|
trailing={<Forms.FormRow.Icon source={getAssetIDByName("ic_trash_24px")} />}
|
||||||
|
onPress={() => {
|
||||||
|
if (users.length !== 0)
|
||||||
|
showConfirmationAlert({
|
||||||
|
title: getTranslation("settings.confirmClear.title"),
|
||||||
|
content: getTranslation("settings.confirmClear.description", true)?.make?.(users.length),
|
||||||
|
confirmText: getTranslation("settings.confirmClear.yes"),
|
||||||
|
cancelText: getTranslation("settings.confirmClear.no"),
|
||||||
|
confirmColor: "brand",
|
||||||
|
onConfirm: handleClearUsers,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ReactNative.ScrollView style={{ flex: 1, gap: 3, marginLeft: 15 }}>
|
||||||
|
{users.map((id) => {
|
||||||
|
const User = UserStore.getUser(id) ?? {};
|
||||||
|
let pfp = User?.getAvatarURL?.(null,26)?.replace?.(/\.(gif|webp)/, ".png");
|
||||||
|
if (!pfp) {
|
||||||
|
pfp = "https://cdn.discordapp.com/embed/avatars/1.png?size=48";
|
||||||
|
User.username = `${id} Uncached`;
|
||||||
|
User.discriminator = "0";
|
||||||
|
if (uncached === 0) User.username += ", press the avatar";
|
||||||
|
uncached++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemWithRemove
|
||||||
|
imageSource={{ uri: pfp }}
|
||||||
|
onImagePress={() => {
|
||||||
|
openProfile(id);
|
||||||
|
}}
|
||||||
|
onRemove={() => handleRemoveUser(id)}
|
||||||
|
label={User.username + (User.discriminator == 0 ? "" : `#${User.discriminator}`)}
|
||||||
|
labelRemove={getTranslation("settings.removeUserButton")}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ReactNative.ScrollView>
|
||||||
|
<Forms.FormDivider />
|
||||||
|
<Forms.FormRow label={getTranslation("settings.addUsersInfo")} />
|
||||||
|
</Forms.FormSection>
|
||||||
|
</ReactNative.ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
158
src/core/plugins/nodelete/translations.js
Normal file
158
src/core/plugins/nodelete/translations.js
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
function slavicNoun(number, $1, $2, $5) {
|
||||||
|
let n = Math.abs(number);
|
||||||
|
n %= 100;
|
||||||
|
if (n >= 5 && n <= 20) return $5;
|
||||||
|
|
||||||
|
n %= 10;
|
||||||
|
if (n === 1) return $1;
|
||||||
|
if (n >= 2 && n <= 4) return $2;
|
||||||
|
return $5;
|
||||||
|
}
|
||||||
|
function latinNoun(number, $1, $x) {
|
||||||
|
return number === 1 ? $1 : $x;
|
||||||
|
}
|
||||||
|
export let massive = {};
|
||||||
|
massive = {
|
||||||
|
settings: {
|
||||||
|
titles: {
|
||||||
|
settings: {
|
||||||
|
"en-GB": "Settings",
|
||||||
|
uk: "Налаштування",
|
||||||
|
ru: "Настройки",
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
"en-GB": "Filters",
|
||||||
|
uk: "Фільтри",
|
||||||
|
ru: "Фильтры",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
showTimestamps: {
|
||||||
|
"en-GB": "Show the time of deletion",
|
||||||
|
uk: "Показувати час видалення",
|
||||||
|
ru: "Показывать время удаления",
|
||||||
|
},
|
||||||
|
ewTimestampFormat: {
|
||||||
|
"en-GB": "Use 12-hour format",
|
||||||
|
uk: "Використовувати 12-годинний формат",
|
||||||
|
ru: "Использовать 12-часовой формат",
|
||||||
|
},
|
||||||
|
youDeletedItWarning: {
|
||||||
|
"en-GB": "The messages YOU deleted - are not saved",
|
||||||
|
uk: "Повідомлення які видалили ВИ - не зберігаются",
|
||||||
|
ru: "Сообщения удаленные ВАМИ - не сохраняются",
|
||||||
|
},
|
||||||
|
addUsersInfo: {
|
||||||
|
"en-GB": () =>
|
||||||
|
`To add or remove users from the ignore list, follow these steps:\n` +
|
||||||
|
`1. open their profile\n` +
|
||||||
|
`2. press the •••\n` +
|
||||||
|
`3. press "${massive.optionLabels[0]["en-GB"]}"\n` +
|
||||||
|
`4. 🎉`,
|
||||||
|
uk: () =>
|
||||||
|
`Щоб добавити когось до списку ігнорованих користувачів, виконайте ці дії:\n` +
|
||||||
|
`1. відкрите їх профіль\n` +
|
||||||
|
`2. натисніть •••\n` +
|
||||||
|
`3. натисніть "${massive.optionLabels[0]["uk"]}"\n` +
|
||||||
|
`4. 🎉`,
|
||||||
|
ru: () =>
|
||||||
|
`Чтобы добавить кого-то в список игнорированных пользователей - следуйте этим шагам\n` +
|
||||||
|
`1. откройте их профиль\n` +
|
||||||
|
`2. нажмите •••\n` +
|
||||||
|
`3. нажмите "${massive.optionLabels[0]["ru"]}"\n` +
|
||||||
|
`4. 🎉`,
|
||||||
|
},
|
||||||
|
ignoreBots: {
|
||||||
|
"en-GB": "Ignore bots",
|
||||||
|
uk: "Ігнорувати ботів",
|
||||||
|
ru: "Игнорировать ботов",
|
||||||
|
},
|
||||||
|
clearUsersLabel: {
|
||||||
|
"en-GB": (amount) =>
|
||||||
|
`You have ${amount} user${latinNoun(amount, "", "s")} in the ignored users list`,
|
||||||
|
uk: (amount) =>
|
||||||
|
`Ви маєте ${amount} користувач${slavicNoun(amount, "а", "а", "ів")} у списку ігнорованих користувачів`,
|
||||||
|
ru: (amount) =>
|
||||||
|
`Вы имеете ${amount} пользовател${slavicNoun(amount, "я", "я", "ей")} в списке игнорированных пользователей`,
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmClear: {
|
||||||
|
title: {
|
||||||
|
"en-GB": "Hold on!",
|
||||||
|
uk: "Почекай-но!",
|
||||||
|
ru: "Положди-ка!",
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
"en-GB": (amount) =>
|
||||||
|
`This will remove ${amount} user${latinNoun(amount, "", "s")} from the ignored users list.\nDo you you really want to do that?`,
|
||||||
|
uk: (amount) =>
|
||||||
|
`Це прибере ${amount} користувач${slavicNoun(amount, "а", "а", "ів")} зі списку ігнорованих користувачів.\nВи дійсно хочете продовжити?`,
|
||||||
|
ru: (amount) =>
|
||||||
|
`Это уберет ${amount} пользовател${slavicNoun(amount, "я", "я", "ей")} из списка игнорированных пользователей.\nВы действительно хотите продолжить?`,
|
||||||
|
},
|
||||||
|
yes: {
|
||||||
|
"en-GB": "Yes",
|
||||||
|
uk: "Так",
|
||||||
|
ru: "Да",
|
||||||
|
},
|
||||||
|
no: {
|
||||||
|
"en-GB": "Cancel",
|
||||||
|
uk: "Відминити",
|
||||||
|
ru: "Отменить",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
removeUserButton: {
|
||||||
|
"en-GB": "REMOVE",
|
||||||
|
uk: "ПРИБРАТИ",
|
||||||
|
ru: "УБРАТЬ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
optionLabels: [
|
||||||
|
{
|
||||||
|
"en-GB": "Add to NoDelete ignored users list",
|
||||||
|
uk: "Добавити до списку ігнорованих користувачів у NoDelete",
|
||||||
|
ru: "Добавить в список игнорированных пользователей в NoDelete",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"en-GB": "Remove from the NoDelete ignore list",
|
||||||
|
uk: "Прибрати з списку ігнорованих користувачів у NoDelete",
|
||||||
|
ru: "Убрать из списка игнорированных пользователей в NoDelete",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
toastLabels: [
|
||||||
|
{
|
||||||
|
"en-GB": "Added {user} to the ignored users list",
|
||||||
|
uk: "{user} добавлено до списку ігнорованих користувачів",
|
||||||
|
ru: "{user} добавлены в список игнорированных пользователей",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"en-GB": "Removed {user} from the ignored users list",
|
||||||
|
uk: "{user} прибрано зі списку ігнорованих користувачів",
|
||||||
|
ru: "{user} убраны из списка игнорированных пользователей",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
thisMessageWasDeleted: {
|
||||||
|
"en-GB": "This message was deleted",
|
||||||
|
uk: "Це повідомлення було видалено",
|
||||||
|
ru: "Это сообщение было удалено",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const locale = () =>
|
||||||
|
vendetta.metro.findByStoreName("LocaleStore").locale;
|
||||||
|
const defaultLocale = "en-GB";
|
||||||
|
export function getTranslation(find, fn) {
|
||||||
|
let value = massive;
|
||||||
|
|
||||||
|
for (const key of find.split(".")) {
|
||||||
|
// biome-ignore lint/suspicious/noPrototypeBuiltins: meow
|
||||||
|
if (value?.hasOwnProperty(key)) {
|
||||||
|
value = value[key];
|
||||||
|
} else {
|
||||||
|
value = massive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value === massive) return find;
|
||||||
|
let localized = value[locale()] ?? value[defaultLocale] ?? find;
|
||||||
|
if (typeof localized === "function" && !fn) localized = localized();
|
||||||
|
return fn ? { make: localized } : localized;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue