chore: separate vd plugins from bunny
This commit is contained in:
parent
06a852639f
commit
a8d2ddd0df
12 changed files with 279 additions and 98 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
import { Strings } from "@core/i18n";
|
import { Strings } from "@core/i18n";
|
||||||
|
import { VdPluginManager, VendettaPlugin } from "@core/vendetta/plugins";
|
||||||
import { ApplicationCommand, ApplicationCommandOptionType } from "@lib/api/commands/types";
|
import { ApplicationCommand, ApplicationCommandOptionType } from "@lib/api/commands/types";
|
||||||
import { BunnyPlugin, sourceStore as pluginStorage } from "@lib/managers/plugins";
|
|
||||||
import { messageUtil } from "@metro/common";
|
import { messageUtil } from "@metro/common";
|
||||||
|
|
||||||
export default () => <ApplicationCommand>{
|
export default () => <ApplicationCommand>{
|
||||||
|
|
@ -15,7 +15,7 @@ export default () => <ApplicationCommand>{
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
execute([ephemeral], ctx) {
|
execute([ephemeral], ctx) {
|
||||||
const plugins = Object.values(pluginStorage).filter(Boolean) as unknown as BunnyPlugin[];
|
const plugins = Object.values(VdPluginManager.plugins).filter(Boolean) as unknown as VendettaPlugin[];
|
||||||
plugins.sort((a, b) => a.manifest.name.localeCompare(b.manifest.name));
|
plugins.sort((a, b) => a.manifest.name.localeCompare(b.manifest.name));
|
||||||
|
|
||||||
const enabled = plugins.filter(p => p.enabled).map(p => p.manifest.name);
|
const enabled = plugins.filter(p => p.enabled).map(p => p.manifest.name);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { Strings } from "@core/i18n";
|
import { Strings } from "@core/i18n";
|
||||||
|
import { VdPluginManager } from "@core/vendetta/plugins";
|
||||||
import { findAssetId } from "@lib/api/assets";
|
import { findAssetId } from "@lib/api/assets";
|
||||||
import { isThemeSupported } from "@lib/api/native/loader";
|
import { isThemeSupported } from "@lib/api/native/loader";
|
||||||
import { after } from "@lib/api/patcher";
|
import { after } from "@lib/api/patcher";
|
||||||
import { useProxy } from "@lib/api/storage";
|
import { useProxy } from "@lib/api/storage";
|
||||||
import { installPlugin, removePlugin,sourceStore } from "@lib/managers/plugins";
|
|
||||||
import { installTheme, removeTheme, themes } from "@lib/managers/themes";
|
import { installTheme, removeTheme, themes } from "@lib/managers/themes";
|
||||||
import { DISCORD_SERVER_ID, HTTP_REGEX_MULTI, PLUGINS_CHANNEL_ID, THEMES_CHANNEL_ID, VD_PROXY_PREFIX } from "@lib/utils/constants";
|
import { DISCORD_SERVER_ID, HTTP_REGEX_MULTI, PLUGINS_CHANNEL_ID, THEMES_CHANNEL_ID, VD_PROXY_PREFIX } from "@lib/utils/constants";
|
||||||
import { lazyDestructure } from "@lib/utils/lazy";
|
import { lazyDestructure } from "@lib/utils/lazy";
|
||||||
|
|
@ -21,11 +21,11 @@ const forumReactions = findByPropsLazy("MostCommonForumPostReaction");
|
||||||
|
|
||||||
const postMap = {
|
const postMap = {
|
||||||
Plugin: {
|
Plugin: {
|
||||||
storage: sourceStore,
|
storage: VdPluginManager.plugins,
|
||||||
urlsFilter: (url: string) => url.startsWith(VD_PROXY_PREFIX),
|
urlsFilter: (url: string) => url.startsWith(VD_PROXY_PREFIX),
|
||||||
installOrRemove: (url: string) => {
|
installOrRemove: (url: string) => {
|
||||||
const isInstalled = postMap.Plugin.storage[url];
|
const isInstalled = postMap.Plugin.storage[url];
|
||||||
return isInstalled ? removePlugin(url) : installPlugin(url);
|
return isInstalled ? VdPluginManager.removePlugin(url) : VdPluginManager.installPlugin(url);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Theme: {
|
Theme: {
|
||||||
|
|
@ -63,7 +63,7 @@ function useExtractThreadContent(thread: any, _firstMessage = null, actionSheet
|
||||||
function useInstaller(thread: any, firstMessage = null, actionSheet = false): [true] | [false, PostType, boolean, boolean, () => Promise<void>] {
|
function useInstaller(thread: any, firstMessage = null, actionSheet = false): [true] | [false, PostType, boolean, boolean, () => Promise<void>] {
|
||||||
const [postType, url] = useExtractThreadContent(thread, firstMessage, actionSheet) ?? [];
|
const [postType, url] = useExtractThreadContent(thread, firstMessage, actionSheet) ?? [];
|
||||||
|
|
||||||
useProxy(sourceStore);
|
useProxy(VdPluginManager.plugins);
|
||||||
useProxy(themes);
|
useProxy(themes);
|
||||||
|
|
||||||
const [isInstalling, setIsInstalling] = React.useState(false);
|
const [isInstalling, setIsInstalling] = React.useState(false);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { formatString, Strings } from "@core/i18n";
|
import { formatString, Strings } from "@core/i18n";
|
||||||
|
import { VdPluginManager } from "@core/vendetta/plugins";
|
||||||
import { findAssetId } from "@lib/api/assets";
|
import { findAssetId } from "@lib/api/assets";
|
||||||
import { isThemeSupported } from "@lib/api/native/loader";
|
import { isThemeSupported } from "@lib/api/native/loader";
|
||||||
import { after, instead } from "@lib/api/patcher";
|
import { after, instead } from "@lib/api/patcher";
|
||||||
import { installPlugin } from "@lib/managers/plugins";
|
|
||||||
import { installTheme } from "@lib/managers/themes";
|
import { installTheme } from "@lib/managers/themes";
|
||||||
import { THEMES_CHANNEL_ID, VD_PROXY_PREFIX } from "@lib/utils/constants";
|
import { THEMES_CHANNEL_ID, VD_PROXY_PREFIX } from "@lib/utils/constants";
|
||||||
import { lazyDestructure } from "@lib/utils/lazy";
|
import { lazyDestructure } from "@lib/utils/lazy";
|
||||||
|
|
@ -28,7 +28,7 @@ function typeFromUrl(url: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function installWithToast(type: "plugin" | "theme", url: string) {
|
function installWithToast(type: "plugin" | "theme", url: string) {
|
||||||
(type === "plugin" ? installPlugin : installTheme)(url)
|
(type === "plugin" ? VdPluginManager.installPlugin.bind(VdPluginManager) : installTheme)(url)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
showToast(Strings.SUCCESSFULLY_INSTALLED, findAssetId("Check"));
|
showToast(Strings.SUCCESSFULLY_INSTALLED, findAssetId("Check"));
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { CardWrapper } from "@core/ui/components/AddonCard";
|
import { CardWrapper } from "@core/ui/components/AddonCard";
|
||||||
|
import { VdPluginManager, VendettaPlugin } from "@core/vendetta/plugins";
|
||||||
import { findAssetId } from "@lib/api/assets";
|
import { findAssetId } from "@lib/api/assets";
|
||||||
import { useProxy } from "@lib/api/storage";
|
import { useProxy } from "@lib/api/storage";
|
||||||
import { BunnyPlugin, getSettingsComponent, startPlugin, stopPlugin } from "@lib/managers/plugins";
|
|
||||||
import { showSheet } from "@lib/ui/sheets";
|
import { showSheet } from "@lib/ui/sheets";
|
||||||
import { lazyDestructure } from "@lib/utils/lazy";
|
import { lazyDestructure } from "@lib/utils/lazy";
|
||||||
import { findByProps } from "@metro";
|
import { findByProps } from "@metro";
|
||||||
import { NavigationNative, tokens } from "@metro/common";
|
import { NavigationNative } from "@metro/common";
|
||||||
import { Card, IconButton, Stack, TableSwitch, Text } from "@metro/common/components";
|
import { Card, IconButton, Stack, TableSwitch, Text } from "@metro/common/components";
|
||||||
import { createContext, memo, useContext } from "react";
|
import { createContext, memo, useContext } from "react";
|
||||||
import { Image, View } from "react-native";
|
import { Image, View } from "react-native";
|
||||||
|
|
@ -13,7 +13,7 @@ import { Image, View } from "react-native";
|
||||||
import { usePluginCardStyles } from "./usePluginCardStyles";
|
import { usePluginCardStyles } from "./usePluginCardStyles";
|
||||||
|
|
||||||
const { useToken } = lazyDestructure(() => findByProps("useToken"));
|
const { useToken } = lazyDestructure(() => findByProps("useToken"));
|
||||||
const PluginContext = createContext<BunnyPlugin>(null!);
|
const PluginContext = createContext<VendettaPlugin>(null!);
|
||||||
const usePlugin = () => useContext(PluginContext);
|
const usePlugin = () => useContext(PluginContext);
|
||||||
|
|
||||||
function Authors() {
|
function Authors() {
|
||||||
|
|
@ -65,25 +65,25 @@ function Title() {
|
||||||
|
|
||||||
// TODO: Wrap in a Card-ish component with red bg
|
// TODO: Wrap in a Card-ish component with red bg
|
||||||
// TODO: Allow glacing at the error's stack
|
// TODO: Allow glacing at the error's stack
|
||||||
function Status() {
|
// function Status() {
|
||||||
const plugin = usePlugin();
|
// const plugin = usePlugin();
|
||||||
const styles = usePluginCardStyles();
|
// const styles = usePluginCardStyles();
|
||||||
const INTERACTIVE_NORMAL = useToken(tokens.colors.INTERACTIVE_NORMAL);
|
// const INTERACTIVE_NORMAL = useToken(tokens.colors.INTERACTIVE_NORMAL);
|
||||||
|
|
||||||
if (!plugin.error) return null;
|
// if (!plugin.error) return null;
|
||||||
|
|
||||||
return <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
// return <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
||||||
<View style={styles.smallIcon}>
|
// <View style={styles.smallIcon}>
|
||||||
<Image
|
// <Image
|
||||||
tintColor={INTERACTIVE_NORMAL}
|
// tintColor={INTERACTIVE_NORMAL}
|
||||||
source={findAssetId("WarningIcon")}
|
// source={findAssetId("WarningIcon")}
|
||||||
/>
|
// />
|
||||||
</View>
|
// </View>
|
||||||
<Text variant="text-sm/semibold">
|
// <Text variant="text-sm/semibold">
|
||||||
There was an error while attempting to start this plugin.
|
// There was an error while attempting to start this plugin.
|
||||||
</Text>
|
// </Text>
|
||||||
</View>;
|
// </View>;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const Actions = memo(() => {
|
const Actions = memo(() => {
|
||||||
const plugin = usePlugin();
|
const plugin = usePlugin();
|
||||||
|
|
@ -94,10 +94,10 @@ const Actions = memo(() => {
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
icon={findAssetId("WrenchIcon")}
|
icon={findAssetId("WrenchIcon")}
|
||||||
disabled={!getSettingsComponent(plugin.id)}
|
disabled={!VdPluginManager.getSettings(plugin.id)}
|
||||||
onPress={() => navigation.push("VendettaCustomPage", {
|
onPress={() => navigation.push("VendettaCustomPage", {
|
||||||
title: plugin.manifest.name,
|
title: plugin.manifest.name,
|
||||||
render: getSettingsComponent(plugin.id),
|
render: VdPluginManager.getSettings(plugin.id),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
@ -113,14 +113,14 @@ const Actions = memo(() => {
|
||||||
</View>;
|
</View>;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function PluginCard({ item: plugin }: CardWrapper<BunnyPlugin>) {
|
export default function PluginCard({ item: plugin }: CardWrapper<VendettaPlugin>) {
|
||||||
useProxy(plugin);
|
useProxy(plugin);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PluginContext.Provider value={plugin}>
|
<PluginContext.Provider value={plugin}>
|
||||||
<Card>
|
<Card>
|
||||||
<Stack spacing={16}>
|
<Stack spacing={16}>
|
||||||
<Status />
|
{/* <Status /> */}
|
||||||
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
|
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
|
||||||
<View style={{ flexShrink: 1 }}>
|
<View style={{ flexShrink: 1 }}>
|
||||||
<Title />
|
<Title />
|
||||||
|
|
@ -132,8 +132,8 @@ export default function PluginCard({ item: plugin }: CardWrapper<BunnyPlugin>) {
|
||||||
<TableSwitch
|
<TableSwitch
|
||||||
value={plugin.enabled}
|
value={plugin.enabled}
|
||||||
onValueChange={(v: boolean) => {
|
onValueChange={(v: boolean) => {
|
||||||
if (v) startPlugin(plugin.id);
|
if (v) VdPluginManager.startPlugin(plugin.id);
|
||||||
else stopPlugin(plugin.id);
|
else VdPluginManager.stopPlugin(plugin.id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Strings } from "@core/i18n";
|
import { Strings } from "@core/i18n";
|
||||||
import AddonPage from "@core/ui/components/AddonPage";
|
import AddonPage from "@core/ui/components/AddonPage";
|
||||||
import PluginCard from "@core/ui/settings/pages/Plugins/PluginCard";
|
import PluginCard from "@core/ui/settings/pages/Plugins/PluginCard";
|
||||||
|
import { VdPluginManager, VendettaPlugin } from "@core/vendetta/plugins";
|
||||||
import { useProxy } from "@lib/api/storage";
|
import { useProxy } from "@lib/api/storage";
|
||||||
import { BunnyPlugin, installPlugin, preferredSourceStore, sourceStore } from "@lib/managers/plugins";
|
|
||||||
import { settings } from "@lib/settings";
|
import { settings } from "@lib/settings";
|
||||||
import { Author } from "@lib/utils/types";
|
import { Author } from "@lib/utils/types";
|
||||||
|
|
||||||
|
|
@ -10,16 +10,15 @@ export default function Plugins() {
|
||||||
useProxy(settings);
|
useProxy(settings);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AddonPage<BunnyPlugin>
|
<AddonPage<VendettaPlugin>
|
||||||
title={Strings.PLUGINS}
|
title={Strings.PLUGINS}
|
||||||
searchKeywords={[
|
searchKeywords={[
|
||||||
"manifest.name",
|
"manifest.name",
|
||||||
"manifest.description",
|
"manifest.description",
|
||||||
p => p.manifest.authors?.map((a: Author) => a.name).join()
|
p => p.manifest.authors?.map((a: Author) => a.name).join()
|
||||||
]}
|
]}
|
||||||
items={preferredSourceStore}
|
items={VdPluginManager.plugins}
|
||||||
resolveItem={source => sourceStore[source]}
|
fetchFunction={VdPluginManager.installPlugin.bind(VdPluginManager)}
|
||||||
fetchFunction={installPlugin}
|
|
||||||
safeModeMessage={Strings.SAFE_MODE_NOTICE_PLUGINS}
|
safeModeMessage={Strings.SAFE_MODE_NOTICE_PLUGINS}
|
||||||
card={PluginCard}
|
card={PluginCard}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { formatString, Strings } from "@core/i18n";
|
import { formatString, Strings } from "@core/i18n";
|
||||||
|
import { VdPluginManager, VendettaPlugin } from "@core/vendetta/plugins";
|
||||||
import { findAssetId } from "@lib/api/assets";
|
import { findAssetId } from "@lib/api/assets";
|
||||||
import { purgeStorage, useProxy } from "@lib/api/storage";
|
import { purgeStorage, useProxy } from "@lib/api/storage";
|
||||||
import { BunnyPlugin, fetchAndStorePlugin, getSettingsComponent, removePlugin, startPlugin, stopPlugin } from "@lib/managers/plugins";
|
|
||||||
import { showConfirmationAlert } from "@lib/ui/alerts";
|
import { showConfirmationAlert } from "@lib/ui/alerts";
|
||||||
import { hideSheet } from "@lib/ui/sheets";
|
import { hideSheet } from "@lib/ui/sheets";
|
||||||
import { showToast } from "@lib/ui/toasts";
|
import { showToast } from "@lib/ui/toasts";
|
||||||
|
|
@ -11,14 +11,14 @@ import { ActionSheet, ActionSheetRow, Button, TableRow, Text } from "@metro/comm
|
||||||
import { ScrollView, View } from "react-native";
|
import { ScrollView, View } from "react-native";
|
||||||
|
|
||||||
interface InfoProps {
|
interface InfoProps {
|
||||||
plugin: BunnyPlugin;
|
plugin: VendettaPlugin;
|
||||||
navigation: any;
|
navigation: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps) {
|
export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps) {
|
||||||
useProxy(plugin);
|
useProxy(plugin);
|
||||||
|
|
||||||
const Settings = getSettingsComponent(plugin.id);
|
const Settings = VdPluginManager.getSettings(plugin.id);
|
||||||
|
|
||||||
return <ActionSheet>
|
return <ActionSheet>
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
|
|
@ -47,16 +47,16 @@ export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps)
|
||||||
label={Strings.REFETCH}
|
label={Strings.REFETCH}
|
||||||
icon={<TableRow.Icon source={findAssetId("RetryIcon")} />}
|
icon={<TableRow.Icon source={findAssetId("RetryIcon")} />}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (plugin.enabled) stopPlugin(plugin.id, false);
|
if (plugin.enabled) VdPluginManager.stopPlugin(plugin.id, false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetchAndStorePlugin(plugin.id);
|
await VdPluginManager.fetchPlugin(plugin.id);
|
||||||
showToast(Strings.PLUGIN_REFETCH_SUCCESSFUL, findAssetId("toast_image_saved"));
|
showToast(Strings.PLUGIN_REFETCH_SUCCESSFUL, findAssetId("toast_image_saved"));
|
||||||
} catch {
|
} catch {
|
||||||
showToast(Strings.PLUGIN_REFETCH_FAILED, findAssetId("Small"));
|
showToast(Strings.PLUGIN_REFETCH_FAILED, findAssetId("Small"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plugin.enabled) await startPlugin(plugin.id);
|
if (plugin.enabled) await VdPluginManager.startPlugin(plugin.id);
|
||||||
hideSheet("PluginInfoActionSheet");
|
hideSheet("PluginInfoActionSheet");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
@ -89,10 +89,10 @@ export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps)
|
||||||
cancelText: Strings.CANCEL,
|
cancelText: Strings.CANCEL,
|
||||||
confirmColor: ButtonColors.RED,
|
confirmColor: ButtonColors.RED,
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
if (plugin.enabled) stopPlugin(plugin.id, false);
|
if (plugin.enabled) VdPluginManager.stopPlugin(plugin.id, false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetchAndStorePlugin(plugin.id);
|
await VdPluginManager.fetchPlugin(plugin.id);
|
||||||
showToast(Strings.PLUGIN_REFETCH_SUCCESSFUL, findAssetId("toast_image_saved"));
|
showToast(Strings.PLUGIN_REFETCH_SUCCESSFUL, findAssetId("toast_image_saved"));
|
||||||
} catch {
|
} catch {
|
||||||
showToast(Strings.PLUGIN_REFETCH_FAILED, findAssetId("Small"));
|
showToast(Strings.PLUGIN_REFETCH_FAILED, findAssetId("Small"));
|
||||||
|
|
@ -111,7 +111,7 @@ export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps)
|
||||||
findAssetId(message[1])
|
findAssetId(message[1])
|
||||||
);
|
);
|
||||||
|
|
||||||
if (plugin.enabled) await startPlugin(plugin.id);
|
if (plugin.enabled) await VdPluginManager.startPlugin(plugin.id);
|
||||||
hideSheet("PluginInfoActionSheet");
|
hideSheet("PluginInfoActionSheet");
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
|
@ -127,7 +127,7 @@ export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps)
|
||||||
confirmColor: ButtonColors.RED,
|
confirmColor: ButtonColors.RED,
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
try {
|
try {
|
||||||
removePlugin(plugin.id);
|
VdPluginManager.removePlugin(plugin.id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(String(e), findAssetId("Small"));
|
showToast(String(e), findAssetId("Small"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ import patcher from "@lib/api/patcher";
|
||||||
import * as storage from "@lib/api/storage";
|
import * as storage from "@lib/api/storage";
|
||||||
import { createStorage } from "@lib/api/storage";
|
import { createStorage } from "@lib/api/storage";
|
||||||
import * as debug from "@lib/debug";
|
import * as debug from "@lib/debug";
|
||||||
import * as plugins from "@lib/managers/plugins";
|
|
||||||
import * as themes from "@lib/managers/themes";
|
import * as themes from "@lib/managers/themes";
|
||||||
import { loaderConfig, settings } from "@lib/settings";
|
import { loaderConfig, settings } from "@lib/settings";
|
||||||
import * as utils from "@lib/utils";
|
import * as utils from "@lib/utils";
|
||||||
|
import { cyrb64Hash } from "@lib/utils/cyrb64";
|
||||||
import { DiscordLogger } from "@lib/utils/logger";
|
import { DiscordLogger } from "@lib/utils/logger";
|
||||||
import * as metro from "@metro";
|
import * as metro from "@metro";
|
||||||
import * as common from "@metro/common";
|
import * as common from "@metro/common";
|
||||||
|
|
@ -23,7 +23,9 @@ import { omit } from "es-toolkit";
|
||||||
import { createElement, useEffect } from "react";
|
import { createElement, useEffect } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
|
|
||||||
export async function createVdPluginObject(plugin: plugins.BunnyPlugin) {
|
import { VdPluginManager, VendettaPlugin } from "./plugins";
|
||||||
|
|
||||||
|
export async function createVdPluginObject(plugin: VendettaPlugin) {
|
||||||
return {
|
return {
|
||||||
...window.vendetta,
|
...window.vendetta,
|
||||||
plugin: {
|
plugin: {
|
||||||
|
|
@ -171,13 +173,13 @@ export const initVendettaObject = (): any => {
|
||||||
rawColors: color.rawColors
|
rawColors: color.rawColors
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
plugins: plugins.sourceStore,
|
plugins: VdPluginManager.plugins,
|
||||||
fetchPlugin: (source: string) => plugins.fetchAndStorePlugin(source),
|
fetchPlugin: (source: string) => VdPluginManager.fetchPlugin(source),
|
||||||
installPlugin: (source: string, enabled = true) => plugins.installPlugin(source, enabled),
|
installPlugin: (source: string, enabled = true) => VdPluginManager.installPlugin(source, enabled),
|
||||||
startPlugin: (id: string) => plugins.startPlugin(plugins.sourceStore[id]!.id),
|
startPlugin: (id: string) => VdPluginManager.startPlugin(id),
|
||||||
stopPlugin: (id: string, disable = true) => plugins.stopPlugin(plugins.sourceStore[id]!.id, disable),
|
stopPlugin: (id: string, disable = true) => VdPluginManager.stopPlugin(id, disable),
|
||||||
removePlugin: (id: string) => plugins.removePlugin(plugins.sourceStore[id]!.id),
|
removePlugin: (id: string) => VdPluginManager.removePlugin(id),
|
||||||
getSettings: (id: string) => plugins.getSettingsComponent(plugins.sourceStore[id]!.id)
|
getSettings: (id: string) => VdPluginManager.getSettings(id)
|
||||||
},
|
},
|
||||||
themes: {
|
themes: {
|
||||||
themes: themes.themes,
|
themes: themes.themes,
|
||||||
|
|
@ -229,32 +231,3 @@ export const initVendettaObject = (): any => {
|
||||||
|
|
||||||
return () => api.unload();
|
return () => api.unload();
|
||||||
};
|
};
|
||||||
|
|
||||||
// cyrb53 (c) 2018 bryc (github.com/bryc). License: Public domain. Attribution appreciated.
|
|
||||||
// A fast and simple 64-bit (or 53-bit) string hash function with decent collision resistance.
|
|
||||||
// Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
|
|
||||||
// See https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/52171480#52171480
|
|
||||||
// https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
|
|
||||||
const cyrb64 = (str: string, seed = 0) => {
|
|
||||||
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
|
|
||||||
for (let i = 0, ch; i < str.length; i++) {
|
|
||||||
ch = str.charCodeAt(i);
|
|
||||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
||||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
||||||
}
|
|
||||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
|
||||||
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
||||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
|
||||||
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
||||||
// For a single 53-bit numeric return value we could return
|
|
||||||
// 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
||||||
// but we instead return the full 64-bit value:
|
|
||||||
return [h2 >>> 0, h1 >>> 0];
|
|
||||||
};
|
|
||||||
|
|
||||||
// An improved, *insecure* 64-bit hash that's short, fast, and has no dependencies.
|
|
||||||
// Output is always 14 characters.
|
|
||||||
const cyrb64Hash = (str: string, seed = 0) => {
|
|
||||||
const [h2, h1] = cyrb64(str, seed);
|
|
||||||
return h2.toString(36).padStart(7, "0") + h1.toString(36).padStart(7, "0");
|
|
||||||
};
|
|
||||||
184
src/core/vendetta/plugins.ts
Normal file
184
src/core/vendetta/plugins.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
import { allSettled } from "@core/polyfills/allSettled";
|
||||||
|
import { awaitStorage, createMMKVBackend, createStorage, purgeStorage, wrapSync } from "@lib/api/storage";
|
||||||
|
import { settings } from "@lib/settings";
|
||||||
|
import { safeFetch } from "@lib/utils";
|
||||||
|
import { BUNNY_PROXY_PREFIX, VD_PROXY_PREFIX } from "@lib/utils/constants";
|
||||||
|
import { DiscordLogger, logger } from "@lib/utils/logger";
|
||||||
|
import { Author } from "@lib/utils/types";
|
||||||
|
|
||||||
|
type EvaledPlugin = {
|
||||||
|
onLoad?(): void;
|
||||||
|
onUnload(): void;
|
||||||
|
settings: JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
// See https://github.com/vendetta-mod/polymanifest
|
||||||
|
interface PluginManifest {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
authors: Author[];
|
||||||
|
main: string;
|
||||||
|
hash: string;
|
||||||
|
// Vendor-specific field, contains our own data
|
||||||
|
vendetta?: {
|
||||||
|
icon?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VendettaPlugin {
|
||||||
|
id: string;
|
||||||
|
manifest: PluginManifest;
|
||||||
|
enabled: boolean;
|
||||||
|
update: boolean;
|
||||||
|
js: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const plugins = wrapSync(createStorage<Record<string, VendettaPlugin>>(createMMKVBackend("VENDETTA_PLUGINS")));
|
||||||
|
const pluginInstance: Record<string, EvaledPlugin> = {};
|
||||||
|
|
||||||
|
export const VdPluginManager = {
|
||||||
|
plugins,
|
||||||
|
async pluginFetch(url: string) {
|
||||||
|
if (url.startsWith(VD_PROXY_PREFIX)) {
|
||||||
|
url = url.replace(VD_PROXY_PREFIX, BUNNY_PROXY_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await safeFetch(url, { cache: "no-store" });
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchPlugin(id: string) {
|
||||||
|
if (!id.endsWith("/")) id += "/";
|
||||||
|
const existingPlugin = plugins[id];
|
||||||
|
|
||||||
|
let pluginManifest: PluginManifest;
|
||||||
|
|
||||||
|
try {
|
||||||
|
pluginManifest = await (await this.pluginFetch(id + "manifest.json")).json();
|
||||||
|
} catch {
|
||||||
|
throw new Error(`Failed to fetch manifest for ${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pluginJs: string | undefined;
|
||||||
|
|
||||||
|
if (existingPlugin?.manifest.hash !== pluginManifest.hash) {
|
||||||
|
try {
|
||||||
|
// by polymanifest spec, plugins should always specify their main file, but just in case
|
||||||
|
pluginJs = await (await this.pluginFetch(id + (pluginManifest.main || "index.js"))).text();
|
||||||
|
} catch { } // Empty catch, checked below
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pluginJs && !existingPlugin) throw new Error(`Failed to fetch JS for ${id}`);
|
||||||
|
|
||||||
|
plugins[id] = {
|
||||||
|
id: id,
|
||||||
|
manifest: pluginManifest,
|
||||||
|
enabled: existingPlugin?.enabled ?? false,
|
||||||
|
update: existingPlugin?.update ?? true,
|
||||||
|
js: pluginJs ?? existingPlugin.js,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async installPlugin(id: string, enabled = true) {
|
||||||
|
if (!id.endsWith("/")) id += "/";
|
||||||
|
if (typeof id !== "string" || id in plugins) throw new Error("Plugin already installed");
|
||||||
|
await this.fetchPlugin(id);
|
||||||
|
if (enabled) await this.startPlugin(id);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
async evalPlugin(plugin: VendettaPlugin) {
|
||||||
|
const vendettaForPlugins = {
|
||||||
|
...window.vendetta,
|
||||||
|
plugin: {
|
||||||
|
id: plugin.id,
|
||||||
|
manifest: plugin.manifest,
|
||||||
|
// Wrapping this with wrapSync is NOT an option.
|
||||||
|
storage: await createStorage<Record<string, any>>(createMMKVBackend(plugin.id)),
|
||||||
|
},
|
||||||
|
logger: new DiscordLogger(`Bunny » ${plugin.manifest.name}`),
|
||||||
|
};
|
||||||
|
const pluginString = `vendetta=>{return ${plugin.js}}\n//# sourceURL=${plugin.id}`;
|
||||||
|
|
||||||
|
const raw = (0, eval)(pluginString)(vendettaForPlugins);
|
||||||
|
const ret = typeof raw === "function" ? raw() : raw;
|
||||||
|
return ret?.default ?? ret ?? {};
|
||||||
|
},
|
||||||
|
|
||||||
|
async startPlugin(id: string) {
|
||||||
|
if (!id.endsWith("/")) id += "/";
|
||||||
|
const plugin = plugins[id];
|
||||||
|
if (!plugin) throw new Error("Attempted to start non-existent plugin");
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!settings.safeMode?.enabled) {
|
||||||
|
const pluginRet: EvaledPlugin = await this.evalPlugin(plugin);
|
||||||
|
pluginInstance[id] = pluginRet;
|
||||||
|
pluginRet.onLoad?.();
|
||||||
|
}
|
||||||
|
plugin.enabled = true;
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`Plugin ${plugin.id} errored whilst loading, and will be unloaded`, e);
|
||||||
|
|
||||||
|
try {
|
||||||
|
pluginInstance[plugin.id]?.onUnload?.();
|
||||||
|
} catch (e2) {
|
||||||
|
logger.error(`Plugin ${plugin.id} errored whilst unloading`, e2);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete pluginInstance[id];
|
||||||
|
plugin.enabled = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stopPlugin(id: string, disable = true) {
|
||||||
|
if (!id.endsWith("/")) id += "/";
|
||||||
|
const plugin = plugins[id];
|
||||||
|
const pluginRet = pluginInstance[id];
|
||||||
|
if (!plugin) throw new Error("Attempted to stop non-existent plugin");
|
||||||
|
|
||||||
|
if (!settings.safeMode?.enabled) {
|
||||||
|
try {
|
||||||
|
pluginRet?.onUnload?.();
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`Plugin ${plugin.id} errored whilst unloading`, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete pluginInstance[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
disable && (plugin.enabled = false);
|
||||||
|
},
|
||||||
|
|
||||||
|
async removePlugin(id: string) {
|
||||||
|
if (!id.endsWith("/")) id += "/";
|
||||||
|
const plugin = plugins[id];
|
||||||
|
if (plugin.enabled) this.stopPlugin(id);
|
||||||
|
delete plugins[id];
|
||||||
|
await purgeStorage(id);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
async initPlugins() {
|
||||||
|
await awaitStorage(settings, plugins);
|
||||||
|
const allIds = Object.keys(plugins);
|
||||||
|
|
||||||
|
if (!settings.safeMode?.enabled) {
|
||||||
|
// Loop over any plugin that is enabled, update it if allowed, then start it.
|
||||||
|
await allSettled(allIds.filter(pl => plugins[pl].enabled).map(async pl => (plugins[pl].update && await this.fetchPlugin(pl).catch((e: Error) => logger.error(e.message)), await this.startPlugin(pl))));
|
||||||
|
// Wait for the above to finish, then update all disabled plugins that are allowed to.
|
||||||
|
allIds.filter(pl => !plugins[pl].enabled && plugins[pl].update).forEach(pl => this.fetchPlugin(pl));
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => this.stopAllPlugins();
|
||||||
|
},
|
||||||
|
|
||||||
|
stopAllPlugins() {
|
||||||
|
return Object.keys(pluginInstance).forEach(p => this.stopPlugin(p, false));
|
||||||
|
},
|
||||||
|
|
||||||
|
getSettings: (id: string) => pluginInstance[id]?.settings
|
||||||
|
};
|
||||||
10
src/index.ts
10
src/index.ts
|
|
@ -1,8 +1,9 @@
|
||||||
import initFixes from "@core/fixes";
|
import initFixes from "@core/fixes";
|
||||||
import { initFetchI18nStrings } from "@core/i18n";
|
import { initFetchI18nStrings } from "@core/i18n";
|
||||||
import { initCorePlugins } from "@core/plugins";
|
import { initCorePlugins } from "@core/plugins";
|
||||||
import { initVendettaObject } from "@core/polyfills/vendettaObject";
|
|
||||||
import initSettings from "@core/ui/settings";
|
import initSettings from "@core/ui/settings";
|
||||||
|
import { initVendettaObject } from "@core/vendetta/api";
|
||||||
|
import { VdPluginManager } from "@core/vendetta/plugins";
|
||||||
import { patchCommands } from "@lib/api/commands";
|
import { patchCommands } from "@lib/api/commands";
|
||||||
import { injectFluxInterceptor } from "@lib/api/flux";
|
import { injectFluxInterceptor } from "@lib/api/flux";
|
||||||
import { removeFile, writeFile } from "@lib/api/native/fs";
|
import { removeFile, writeFile } from "@lib/api/native/fs";
|
||||||
|
|
@ -10,7 +11,6 @@ import { isPyonLoader, isThemeSupported } from "@lib/api/native/loader";
|
||||||
import { FileManager } from "@lib/api/native/modules";
|
import { FileManager } from "@lib/api/native/modules";
|
||||||
import { patchLogHook } from "@lib/debug";
|
import { patchLogHook } from "@lib/debug";
|
||||||
import { updateFonts } from "@lib/managers/fonts";
|
import { updateFonts } from "@lib/managers/fonts";
|
||||||
import { initPlugins } from "@lib/managers/plugins";
|
|
||||||
import { initThemes, patchChatBackground } from "@lib/managers/themes";
|
import { initThemes, patchChatBackground } from "@lib/managers/themes";
|
||||||
import { logger } from "@lib/utils/logger";
|
import { logger } from "@lib/utils/logger";
|
||||||
import initSafeMode from "@ui/safeMode";
|
import initSafeMode from "@ui/safeMode";
|
||||||
|
|
@ -56,10 +56,10 @@ export default async () => {
|
||||||
// Assign window object
|
// Assign window object
|
||||||
window.bunny = lib;
|
window.bunny = lib;
|
||||||
|
|
||||||
// Once done, load plugins
|
// Once done, load Vendetta plugins
|
||||||
initPlugins()
|
VdPluginManager.initPlugins()
|
||||||
.then(u => lib.unload.push(u))
|
.then(u => lib.unload.push(u))
|
||||||
.catch(() => alert("Failed to initialize Bunny plugins"));
|
.catch(() => alert("Failed to initialize Vendetta plugins"));
|
||||||
|
|
||||||
// Update the fonts
|
// Update the fonts
|
||||||
updateFonts();
|
updateFonts();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { allSettled } from "@core/polyfills/allSettled";
|
import { allSettled } from "@core/polyfills/allSettled";
|
||||||
import { createVdPluginObject } from "@core/polyfills/vendettaObject";
|
import { createVdPluginObject } from "@core/vendetta/api";
|
||||||
import { awaitStorage, createMMKVBackend, createStorage, purgeStorage, wrapSync } from "@lib/api/storage";
|
import { awaitStorage, createMMKVBackend, createStorage, purgeStorage, wrapSync } from "@lib/api/storage";
|
||||||
import { settings } from "@lib/settings";
|
import { settings } from "@lib/settings";
|
||||||
import { safeFetch } from "@lib/utils";
|
import { safeFetch } from "@lib/utils";
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { findAssetId } from "@lib/api/assets";
|
||||||
import { lazyDestructure } from "@lib/utils/lazy";
|
import { lazyDestructure } from "@lib/utils/lazy";
|
||||||
import { toasts } from "@metro/common";
|
import { toasts } from "@metro/common";
|
||||||
import { findByProps } from "@metro/utils";
|
import { findByProps } from "@metro/utils";
|
||||||
import { Platform } from "react-native";
|
|
||||||
|
|
||||||
const { uuid4 } = lazyDestructure(() => findByProps("uuid4"));
|
const { uuid4 } = lazyDestructure(() => findByProps("uuid4"));
|
||||||
|
|
||||||
|
|
@ -17,8 +16,5 @@ export const showToast = (content: string, asset?: number) => toasts.open({
|
||||||
});
|
});
|
||||||
|
|
||||||
showToast.showCopyToClipboard = (message = Strings.COPIED_TO_CLIPBOARD) => {
|
showToast.showCopyToClipboard = (message = Strings.COPIED_TO_CLIPBOARD) => {
|
||||||
// On Android, only show toast for 12 and lower
|
showToast(message, findAssetId("toast_copy_link"));
|
||||||
if (Platform.OS !== "android" || Platform.Version <= 32) {
|
|
||||||
showToast(message, findAssetId("toast_copy_link"));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
29
src/lib/utils/cyrb64.ts
Normal file
29
src/lib/utils/cyrb64.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
// cyrb53 (c) 2018 bryc (github.com/bryc). License: Public domain. Attribution appreciated.
|
||||||
|
// A fast and simple 64-bit (or 53-bit) string hash function with decent collision resistance.
|
||||||
|
// Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
|
||||||
|
// See https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/52171480#52171480
|
||||||
|
// https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
|
||||||
|
export function cyrb64(str: string, seed = 0) {
|
||||||
|
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
|
||||||
|
for (let i = 0, ch; i < str.length; i++) {
|
||||||
|
ch = str.charCodeAt(i);
|
||||||
|
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||||
|
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||||
|
}
|
||||||
|
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
||||||
|
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||||
|
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
||||||
|
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||||
|
// For a single 53-bit numeric return value we could return
|
||||||
|
// 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||||
|
// but we instead return the full 64-bit value:
|
||||||
|
return [h2 >>> 0, h1 >>> 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// An improved, *insecure* 64-bit hash that's short, fast, and has no dependencies.
|
||||||
|
// Output is always 14 characters.
|
||||||
|
export function cyrb64Hash(str: string, seed = 0) {
|
||||||
|
const [h2, h1] = cyrb64(str, seed);
|
||||||
|
return h2.toString(36).padStart(7, "0") + h1.toString(36).padStart(7, "0");
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue