vd-plugins/nexxstuff/migration.tsx
kmmiio99o b98ce51645
feat: add nexx lib and better Header text
nexx shouldn't be mad, right?
2026-01-25 17:09:24 +01:00

294 lines
7.4 KiB
TypeScript

import { React, ReactNative } from "@vendetta/metro/common";
import { installPlugin, plugins, removePlugin } from "@vendetta/plugins";
import { createMMKVBackend } from "@vendetta/storage";
import { showConfirmationAlert } from "@vendetta/ui/alerts";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { showToast } from "@vendetta/ui/toasts";
import { RichText } from "./components/RichText";
import Text from "./components/Text";
import { Reanimated, RNFileModule } from "./deps";
import { Stack } from "./lib/redesign";
const { Pressable } = ReactNative;
interface InvalidDomain {
match: RegExp;
label: string;
}
const specialIDs = {
usrpfp: "userpfp",
} as Record<string, string>;
const allowedPluginIDs = [
PLUGINS_LIST,
Object.keys(specialIDs),
].flat();
const invalidDomains: InvalidDomain[] = [{
match: /vendetta\.nexpid\.xyz\/(?<plugin>.+)\/$/i,
label: "vendetta.nexpid.xyz",
}, {
match: /dev\.bunny\.nexpid\.xyz\/(?<plugin>.+)\/$/i,
label: "dev.bunny.nexpid.xyz",
}, {
match: /bunny\.nexpid\.xyz\/(?<plugin>.+)\/$/i,
label: "bunny.nexpid.xyz",
}];
const migrationLockFilePath = "nexpid/revengemigration.txt";
function MigrationModal(
{ affectedPlugin, domains }: {
affectedPlugin?: string;
domains: string[];
},
) {
const jumper = Reanimated.useSharedValue(0);
const spinner = Reanimated.useSharedValue("0deg");
const squisherX = Reanimated.useSharedValue(1);
const squisherY = Reanimated.useSharedValue(1);
const [toggle, setToggle] = React.useState(true);
const duration = 300;
const fromY = -10;
const toY = 30;
const spinBetween = 11;
const squishConfig = { duration: 0 };
const squishVal = 1.25;
const releaseConfig = {
duration: duration * 2,
easing: Reanimated.Easing.out(Reanimated.Easing.quad),
};
const releaseVal = 0.9;
React.useEffect(() => {
if (!toggle) {
jumper.value = 0;
spinner.value = "0deg";
squisherX.value = 1;
squisherY.value = 1;
return;
}
jumper.value = Reanimated.withRepeat(
Reanimated.withSequence(
Reanimated.withTiming(toY, {
easing: Reanimated.Easing.out(Reanimated.Easing.quad),
duration,
}),
Reanimated.withTiming(fromY, {
easing: Reanimated.Easing.quad,
duration,
}),
),
-1,
);
spinner.value = Reanimated.withRepeat(
Reanimated.withSequence(
Reanimated.withTiming("0deg", { duration: (duration * 2) * spinBetween }),
Reanimated.withTiming("-360deg", {
duration: duration * 2,
easing: Reanimated.Easing.out(Reanimated.Easing.exp),
}),
),
-1,
);
squisherX.value = Reanimated.withRepeat(
Reanimated.withSequence(
Reanimated.withTiming(squishVal, squishConfig),
Reanimated.withTiming(releaseVal, releaseConfig),
),
-1,
);
squisherY.value = Reanimated.withRepeat(
Reanimated.withSequence(
Reanimated.withTiming(1 / squishVal, squishConfig),
Reanimated.withTiming(1 / releaseVal, releaseConfig),
),
-1,
);
}, [toggle]);
return (
<Stack direction="vertical" spacing={8}>
<Stack
direction="horizontal"
spacing={14}
style={{
justifyContent: "center",
alignItems: "center",
marginBottom: 18,
marginHorizontal: 8,
}}
>
<Pressable
style={{ width: 80, height: 80 }}
pointerEvents="box-only"
onPress={() => setToggle(!toggle)}
accessibilityRole="togglebutton"
accessibilityLabel="toggle jumping animation"
>
<Reanimated.default.Image
source={{
uri: "https://api.lanyard.rest/853550207039832084.webp?size=128",
width: 128,
height: 128,
}}
style={[{
borderRadius: 40,
width: 80,
height: 80,
position: "absolute",
bottom: 0,
}, {
bottom: jumper,
transform: [{ scaleX: squisherX }, { scaleY: squisherY }, {
rotate: spinner,
}],
}]}
/>
</Pressable>
<Stack direction="vertical" style={{ flex: 1 }}>
<Text
variant="heading-xl/extrabold"
color="TEXT_DEFAULT"
style={{ flex: 0 }}
>
Hey, it's me, nexpid!
</Text>
<Text
variant="heading-md/medium"
color="TEXT_DEFAULT"
style={{ flex: 0 }}
>
You might know me. I made {affectedPlugin
? (
<>
a plugin you use, <RichText.Bold>{affectedPlugin}</RichText.Bold>
</>
)
: "some of the plugins you use"}!
</Text>
</Stack>
</Stack>
<Text
variant="text-md/medium"
color="TEXT_DEFAULT"
>
{affectedPlugin
? "The plugin you use has"
: "Some of my plugins you use have"} one of these domains:
</Text>
<Text
variant="text-md/bold"
color="TEXT_DEFAULT"
style={{ marginHorizontal: 10 }}
>
{domains.map(label => `• ${label}`).join("\n")}
</Text>
<Text
variant="text-md/medium"
color="TEXT_DEFAULT"
>
These domains are being deprecated and plugins on them won't receive any new updates. Please
use the new domain instead:
</Text>
<Text
variant="text-lg/bold"
color="TEXT_DEFAULT"
style={{ textDecorationLine: "underline" }}
align="center"
>
revenge.nexpid.xyz
</Text>
</Stack>
);
}
// thanks to the shim, this only gets run once even if multiple plugins are installed
export async function runMigration() {
if (
await RNFileModule.fileExists(
`${RNFileModule.DocumentsDirPath}/${migrationLockFilePath}`,
)
) return;
const affected: {
id: string;
link: string;
name: string;
}[] = [];
const isOutdated = new Set<string>();
for (const plugin of Object.keys(plugins)) {
const parser = invalidDomains.find(x => x.match.test(plugin));
const id = parser && plugin.match(parser.match)?.groups?.plugin;
if (id && allowedPluginIDs.includes(id)) {
affected.push({
id,
link: plugin,
name: plugins[plugin].manifest.name,
});
isOutdated.add(parser.label);
if (plugin.includes("bn-plugins.github.io/vd-proxy")) {
isOutdated.add("proxied plugin links");
}
}
}
if (isOutdated.size) {
showConfirmationAlert({
// @ts-expect-error Missing from typings
children: (
<MigrationModal
affectedPlugin={affected.length === 1 ? affected[0].name : undefined}
domains={[...isOutdated.values()]}
/>
),
confirmText: "Migrate now",
async onConfirm() {
showToast("Starting migration");
console.log("MIGRATION STARTS HERE, AFFECTED", affected);
try {
// take all plugins that are bad & make good :blush:
for (const { id, link, name } of affected) {
const newPlugin = `https://revenge.nexpid.xyz/${specialIDs[id] ?? id}/`;
console.log(
"FROM",
link,
"TO",
newPlugin,
);
await createMMKVBackend(newPlugin).set(await createMMKVBackend(link).get());
if (!plugins[newPlugin]) await installPlugin(newPlugin);
// returns a promise (bug in types)
await removePlugin(link);
showToast(`Migrated ${name}`);
}
console.log("MIGRATION ENDS HERE");
} catch (e) {
console.error("Migration error", e);
return showToast(
`Got an error! ${e} (ping nexpid about this!!!)`,
getAssetIDByName("CircleXIcon-primary"),
);
}
showToast(
"Migrated successfully! Please reload your client to apply changes",
getAssetIDByName("CircleCheckIcon-primary"),
);
},
cancelText: "Remind me later",
secondaryConfirmText: "I understand (don't show again)",
onConfirmSecondary() {
RNFileModule.writeFile("documents", migrationLockFilePath, "revenged", "utf8");
},
});
}
}