refactor(plugins)
This commit is contained in:
parent
b3c1b748a0
commit
29caf1d09b
10 changed files with 73 additions and 67 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { Strings } from "@core/i18n";
|
||||
import { ApplicationCommand, ApplicationCommandOptionType } from "@lib/api/commands/types";
|
||||
import { BunnyPlugin, sourceToObject as pluginStorage } from "@lib/managers/plugins";
|
||||
import { BunnyPlugin, sourceStore as pluginStorage } from "@lib/managers/plugins";
|
||||
import { messageUtil } from "@metro/common";
|
||||
|
||||
export default () => <ApplicationCommand>{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { requireAssetIndex } from "@lib/api/assets";
|
|||
import { isThemeSupported } from "@lib/api/native/loader";
|
||||
import { after } from "@lib/api/patcher";
|
||||
import { useProxy } from "@lib/api/storage";
|
||||
import { installPlugin, sourceToObject, removePlugin } from "@lib/managers/plugins";
|
||||
import { installPlugin, removePlugin,sourceStore } from "@lib/managers/plugins";
|
||||
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 { lazyDestructure } from "@lib/utils/lazy";
|
||||
|
|
@ -21,7 +21,7 @@ const forumReactions = findByPropsProxy("MostCommonForumPostReaction");
|
|||
|
||||
const postMap = {
|
||||
Plugin: {
|
||||
storage: sourceToObject,
|
||||
storage: sourceStore,
|
||||
urlsFilter: (url: string) => url.startsWith(VD_PROXY_PREFIX),
|
||||
installOrRemove: (url: string) => {
|
||||
const isInstalled = postMap.Plugin.storage[url];
|
||||
|
|
@ -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>] {
|
||||
const [postType, url] = useExtractThreadContent(thread, firstMessage, actionSheet) ?? [];
|
||||
|
||||
useProxy(sourceToObject);
|
||||
useProxy(sourceStore);
|
||||
useProxy(themes);
|
||||
|
||||
const [isInstalling, setIsInstalling] = React.useState(false);
|
||||
|
|
|
|||
|
|
@ -170,13 +170,13 @@ export const initVendettaObject = (): any => {
|
|||
rawColors: color.rawColors
|
||||
},
|
||||
plugins: {
|
||||
plugins: plugins.sourceToObject,
|
||||
fetchPlugin: (source: string) => plugins.fetchPlugin(source),
|
||||
plugins: plugins.sourceStore,
|
||||
fetchPlugin: (source: string) => plugins.fetchAndStorePlugin(source),
|
||||
installPlugin: (source: string, enabled = true) => plugins.installPlugin(source, enabled),
|
||||
startPlugin: (id: string) => plugins.startPlugin(plugins.sourceToObject[id]!.pluginId),
|
||||
stopPlugin: (id: string, disable = true) => plugins.stopPlugin(plugins.sourceToObject[id]!.pluginId, disable),
|
||||
removePlugin: (id: string) => plugins.removePlugin(plugins.sourceToObject[id]!.pluginId),
|
||||
getSettings: (id: string) => plugins.getSettingsComponent(plugins.sourceToObject[id]!.pluginId)
|
||||
startPlugin: (id: string) => plugins.startPlugin(plugins.sourceStore[id]!.pluginId),
|
||||
stopPlugin: (id: string, disable = true) => plugins.stopPlugin(plugins.sourceStore[id]!.pluginId, disable),
|
||||
removePlugin: (id: string) => plugins.removePlugin(plugins.sourceStore[id]!.pluginId),
|
||||
getSettings: (id: string) => plugins.getSettingsComponent(plugins.sourceStore[id]!.pluginId)
|
||||
},
|
||||
themes: {
|
||||
themes: themes.themes,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { View } from "react-native";
|
|||
|
||||
type SearchKeywords = Array<string | ((obj: any & {}) => string)>;
|
||||
|
||||
interface AddonPageProps<T> {
|
||||
interface AddonPageProps<T extends object> {
|
||||
title: string;
|
||||
fetchFunction: (url: string) => Promise<void>;
|
||||
items: Record<string, any>;
|
||||
|
|
@ -30,7 +30,7 @@ interface AddonPageProps<T> {
|
|||
// TODO: Move to somewhere else
|
||||
const { FlashList } = findByProps("FlashList");
|
||||
|
||||
export default function AddonPage<T>({ card: CardComponent, ...props }: AddonPageProps<T>) {
|
||||
export default function AddonPage<T extends object>({ card: CardComponent, ...props }: AddonPageProps<T>) {
|
||||
useProxy(settings);
|
||||
useProxy(props.items);
|
||||
|
||||
|
|
@ -67,6 +67,7 @@ export default function AddonPage<T>({ card: CardComponent, ...props }: AddonPag
|
|||
data={data}
|
||||
estimatedItemSize={136}
|
||||
ListHeaderComponent={headerElement}
|
||||
onLoad={console.log}
|
||||
contentContainerStyle={{ paddingBottom: 90, paddingHorizontal: 5 }}
|
||||
renderItem={({ item }: any) => (
|
||||
<View style={{ paddingVertical: 6, paddingHorizontal: 8 }}>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { lazyDestructure } from "@lib/utils/lazy";
|
|||
import { findByProps } from "@metro";
|
||||
import { NavigationNative, tokens } from "@metro/common";
|
||||
import { Card, IconButton, Stack, TableSwitch, Text } from "@metro/common/components";
|
||||
import { createContext, useContext } from "react";
|
||||
import { createContext, memo, useContext } from "react";
|
||||
import { Image, View } from "react-native";
|
||||
|
||||
import { usePluginCardStyles } from "./usePluginCardStyles";
|
||||
|
|
@ -48,14 +48,11 @@ function Title() {
|
|||
</Text>
|
||||
);
|
||||
|
||||
if (!icon) return textElement;
|
||||
|
||||
return <View
|
||||
return !icon ? textElement : <View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: 4
|
||||
gap: 6
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
|
|
@ -88,9 +85,35 @@ function Status() {
|
|||
</View>;
|
||||
}
|
||||
|
||||
export default function PluginCard({ item: plugin }: CardWrapper<BunnyPlugin>) {
|
||||
const Actions = memo(() => {
|
||||
const plugin = usePlugin();
|
||||
const navigation = NavigationNative.useNavigation();
|
||||
|
||||
return <View style={{ flexDirection: "row", gap: 6 }}>
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon={requireAssetIndex("WrenchIcon")}
|
||||
disabled={!getSettingsComponent(plugin.id)}
|
||||
onPress={() => navigation.push("VendettaCustomPage", {
|
||||
title: plugin.manifest.name,
|
||||
render: getSettingsComponent(plugin.id),
|
||||
})}
|
||||
/>
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon={requireAssetIndex("CircleInformationIcon-primary")}
|
||||
onPress={() => void showSheet(
|
||||
"PluginInfoActionSheet",
|
||||
import("./sheets/PluginInfoActionSheet"),
|
||||
{ plugin, navigation }
|
||||
)}
|
||||
/>
|
||||
</View>;
|
||||
});
|
||||
|
||||
export default function PluginCard({ item: plugin }: CardWrapper<BunnyPlugin>) {
|
||||
useProxy(plugin);
|
||||
|
||||
return (
|
||||
|
|
@ -105,28 +128,7 @@ export default function PluginCard({ item: plugin }: CardWrapper<BunnyPlugin>) {
|
|||
</View>
|
||||
<View>
|
||||
<Stack spacing={12} direction="horizontal">
|
||||
<View style={{ flexDirection: "row", gap: 6 }}>
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon={requireAssetIndex("WrenchIcon")}
|
||||
disabled={!getSettingsComponent(plugin.id)}
|
||||
onPress={() => navigation.push("VendettaCustomPage", {
|
||||
title: plugin.manifest.name,
|
||||
render: getSettingsComponent(plugin.id),
|
||||
})}
|
||||
/>
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon={requireAssetIndex("CircleInformationIcon-primary")}
|
||||
onPress={() => void showSheet(
|
||||
"PluginInfoActionSheet",
|
||||
import("./sheets/PluginInfoActionSheet"),
|
||||
{ plugin, navigation }
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
<Actions />
|
||||
<TableSwitch
|
||||
value={plugin.enabled}
|
||||
onValueChange={(v: boolean) => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Strings } from "@core/i18n";
|
|||
import AddonPage from "@core/ui/components/AddonPage";
|
||||
import PluginCard from "@core/ui/settings/pages/Plugins/PluginCard";
|
||||
import { useProxy } from "@lib/api/storage";
|
||||
import { BunnyPlugin, idToSource, installPlugin, sourceToObject } from "@lib/managers/plugins";
|
||||
import { BunnyPlugin, installPlugin, selectedSources, sourceStore } from "@lib/managers/plugins";
|
||||
import { settings } from "@lib/settings";
|
||||
import { Author } from "@lib/utils/types";
|
||||
|
||||
|
|
@ -17,8 +17,8 @@ export default function Plugins() {
|
|||
"manifest.description",
|
||||
p => p.manifest.authors?.map((a: Author) => a.name).join()
|
||||
]}
|
||||
items={idToSource}
|
||||
resolveItem={id => sourceToObject[id]}
|
||||
items={selectedSources}
|
||||
resolveItem={source => sourceStore[source]}
|
||||
fetchFunction={installPlugin}
|
||||
safeModeMessage={Strings.SAFE_MODE_NOTICE_PLUGINS}
|
||||
card={PluginCard}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { formatString, Strings } from "@core/i18n";
|
||||
import { requireAssetIndex } from "@lib/api/assets";
|
||||
import { purgeStorage, useProxy } from "@lib/api/storage";
|
||||
import { BunnyPlugin, fetchPlugin, getSettingsComponent, removePlugin, startPlugin, stopPlugin } from "@lib/managers/plugins";
|
||||
import { BunnyPlugin, fetchAndStorePlugin, getSettingsComponent, removePlugin, startPlugin, stopPlugin } from "@lib/managers/plugins";
|
||||
import { showConfirmationAlert } from "@lib/ui/alerts";
|
||||
import { hideSheet } from "@lib/ui/sheets";
|
||||
import { showToast } from "@lib/ui/toasts";
|
||||
|
|
@ -50,7 +50,7 @@ export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps)
|
|||
if (plugin.enabled) stopPlugin(plugin.id, false);
|
||||
|
||||
try {
|
||||
await fetchPlugin(plugin.id);
|
||||
await fetchAndStorePlugin(plugin.id);
|
||||
showToast(Strings.PLUGIN_REFETCH_SUCCESSFUL, requireAssetIndex("toast_image_saved"));
|
||||
} catch {
|
||||
showToast(Strings.PLUGIN_REFETCH_FAILED, requireAssetIndex("Small"));
|
||||
|
|
@ -92,7 +92,7 @@ export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps)
|
|||
if (plugin.enabled) stopPlugin(plugin.id, false);
|
||||
|
||||
try {
|
||||
await fetchPlugin(plugin.id);
|
||||
await fetchAndStorePlugin(plugin.id);
|
||||
showToast(Strings.PLUGIN_REFETCH_SUCCESSFUL, requireAssetIndex("toast_image_saved"));
|
||||
} catch {
|
||||
showToast(Strings.PLUGIN_REFETCH_FAILED, requireAssetIndex("Small"));
|
||||
|
|
|
|||
|
|
@ -57,7 +57,9 @@ export default async () => {
|
|||
window.bunny = lib;
|
||||
|
||||
// Once done, load plugins
|
||||
initPlugins().then(u => lib.unload.push(u));
|
||||
initPlugins()
|
||||
.then(u => lib.unload.push(u))
|
||||
.catch(() => alert("Failed to initialize Bunny plugins"));
|
||||
|
||||
// Update the fonts
|
||||
updateFonts();
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export interface BunnyPlugin {
|
|||
* Message of plugin startup error. Gone after plugin boots successfully
|
||||
* */
|
||||
error?: string;
|
||||
|
||||
// TODO: Use fs to avoid unnecessary memory usage
|
||||
js: string;
|
||||
|
||||
|
|
@ -46,16 +47,16 @@ export interface BunnyPlugin {
|
|||
id: string;
|
||||
}
|
||||
|
||||
const BUNNY_PROXY_PREFIX = "https://bn-plugins.github.io/vd-proxy";
|
||||
export const BUNNY_PROXY_PREFIX = "https://bn-plugins.github.io/vd-proxy";
|
||||
const VD_PROXY_PREFIX = "https://vd-plugins.github.io/proxy";
|
||||
const OLD_BUNNY_PROXY_PREFIX = "https://bunny-mod.github.io/plugins-proxy";
|
||||
|
||||
const pluginsEnabled = () => !settings.safeMode?.enabled;
|
||||
|
||||
const _pluginInstances: Record<string, EvaledPlugin> = {};
|
||||
|
||||
export const idToSource = wrapSync(createStorage<Record<string, string | undefined>>(createMMKVBackend("SELECTED_PLUGINS")));
|
||||
|
||||
export const sourceToObject = proxyLazy(() => {
|
||||
export const selectedSources = wrapSync(createStorage<Record<string, string | undefined>>(createMMKVBackend("SELECTED_PLUGIN_SOURCES")));
|
||||
export const sourceStore = proxyLazy(() => {
|
||||
type StorageInterface = Record<string, BunnyPlugin | undefined>;
|
||||
const storagePromise = createStorage<StorageInterface>(createMMKVBackend("VENDETTA_PLUGINS"));
|
||||
|
||||
|
|
@ -70,21 +71,21 @@ export const sourceToObject = proxyLazy(() => {
|
|||
});
|
||||
|
||||
export function getPluginById(id: string) {
|
||||
if (!idToSource[id]) {
|
||||
for (const plugin of Object.values(sourceToObject)) {
|
||||
if (!selectedSources[id]) {
|
||||
for (const plugin of Object.values(sourceStore)) {
|
||||
if (plugin?.pluginId === id) {
|
||||
idToSource[id] = plugin.source;
|
||||
selectedSources[id] = plugin.source;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return idToSource[id] ? sourceToObject[idToSource[id]!] : undefined;
|
||||
return selectedSources[id] ? sourceStore[selectedSources[id]!] : undefined;
|
||||
}
|
||||
|
||||
export async function fetchPlugin(source: string) {
|
||||
export async function fetchAndStorePlugin(source: string) {
|
||||
if (!source.endsWith("/")) source += "/";
|
||||
const existingPlugin = sourceToObject[source];
|
||||
const existingPlugin = sourceStore[source];
|
||||
|
||||
const fetch = (url: string) => safeFetch(
|
||||
url
|
||||
|
|
@ -112,7 +113,7 @@ export async function fetchPlugin(source: string) {
|
|||
|
||||
invariant(pluginJs || existingPlugin, `Failed to fetch JS from ${source}`);
|
||||
|
||||
return sourceToObject[source] = {
|
||||
return sourceStore[source] = {
|
||||
id: source,
|
||||
source: source,
|
||||
pluginId: pluginManifest.name,
|
||||
|
|
@ -126,9 +127,9 @@ export async function fetchPlugin(source: string) {
|
|||
|
||||
export async function installPlugin(source: string, enabled = true) {
|
||||
if (!source.endsWith("/")) source += "/";
|
||||
invariant(!(source in sourceToObject), "Source was already installed");
|
||||
invariant(!(source in sourceStore), "Source was already installed");
|
||||
|
||||
const plugin = await fetchPlugin(source);
|
||||
const plugin = await fetchAndStorePlugin(source);
|
||||
if (enabled) await startPlugin(plugin.pluginId);
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +195,7 @@ export async function removePlugin(id: string) {
|
|||
const plugin = getPluginById(id);
|
||||
invariant(plugin, "Removing non-existent plugin");
|
||||
if (plugin.enabled) stopPlugin(id);
|
||||
delete sourceToObject[plugin.source];
|
||||
delete sourceStore[plugin.source];
|
||||
await purgeStorage(plugin.source);
|
||||
}
|
||||
|
||||
|
|
@ -202,29 +203,30 @@ export async function removePlugin(id: string) {
|
|||
* @internal
|
||||
*/
|
||||
export async function initPlugins() {
|
||||
await awaitStorage(sourceToObject, idToSource, settings);
|
||||
await awaitStorage(sourceStore, selectedSources, settings);
|
||||
|
||||
// TODO: Optimize this
|
||||
if (pluginsEnabled()) {
|
||||
const allIds = [...new Set(Object.values(sourceToObject).map(s => s!.pluginId))];
|
||||
const allIds = [...new Set(Object.values(sourceStore).map(s => s!.pluginId))];
|
||||
|
||||
// Loop over enabled plugins, update and start them
|
||||
const enabledPlugins = allIds.filter(id => getPluginById(id)?.enabled);
|
||||
await Promise.all(enabledPlugins.map(async pl => {
|
||||
if (getPluginById(pl)!.update) {
|
||||
try {
|
||||
await fetchPlugin(pl);
|
||||
await fetchAndStorePlugin(pl);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
await startPlugin(pl);
|
||||
}));
|
||||
|
||||
// Loop over disabled plugins that are allowed to update
|
||||
allIds
|
||||
.filter(id => !getPluginById(id)?.enabled && getPluginById(id)!.update)
|
||||
.forEach(id => fetchPlugin(id));
|
||||
.forEach(id => fetchAndStorePlugin(id));
|
||||
}
|
||||
|
||||
return () => Object.keys(_pluginInstances).forEach(p => stopPlugin(p, false));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
export const DISCORD_SERVER = "https://discord.gg/XjYgWXHb9Q";
|
||||
export const GITHUB = "https://github.com/pyoncord";
|
||||
export const VD_PROXY_PREFIX = "https://vd-plugins.github.io/proxy";
|
||||
export const HTTP_REGEX = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)$/;
|
||||
export const HTTP_REGEX_MULTI = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&//=]*)/g;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue