feat: Re-do Commands plugin settings page
- Re-do fully the Commands plugin settings page - Add sidebar based of Multi Scrobbler one - Rework header (because why not)
This commit is contained in:
parent
0a59dc8cc5
commit
22bbcbf91d
18 changed files with 2119 additions and 1458 deletions
|
|
@ -25,7 +25,8 @@ import {
|
|||
friendInviteViewCommand,
|
||||
friendInviteRevokeCommand,
|
||||
} from "./src/commands/friendinvites";
|
||||
import settings from "./settings";
|
||||
import settings from "./settings/settings";
|
||||
import patchSidebar from "./sidebar";
|
||||
|
||||
if (!storage.factSettings) {
|
||||
storage.factSettings = {
|
||||
|
|
@ -76,6 +77,10 @@ if (!storage.hiddenSettings) {
|
|||
konochanBypassNsfw: false,
|
||||
};
|
||||
}
|
||||
// sidebar enabled setting
|
||||
if (storage.sidebarEnabled === undefined) {
|
||||
storage.sidebarEnabled = true;
|
||||
}
|
||||
|
||||
const commandMap = {
|
||||
catfact: catFactCommand,
|
||||
|
|
@ -102,18 +107,60 @@ const commandMap = {
|
|||
};
|
||||
|
||||
let commands: Array<() => void> = [];
|
||||
let sidebarUnpatch: (() => void) | undefined;
|
||||
|
||||
export default {
|
||||
onLoad: () => {
|
||||
console.log("[Commands Plugin] Loading...");
|
||||
|
||||
// Patch sidebar if enabled
|
||||
if (storage.sidebarEnabled !== false) {
|
||||
try {
|
||||
sidebarUnpatch = patchSidebar();
|
||||
console.log("[Commands Plugin] Sidebar patched successfully");
|
||||
} catch (error) {
|
||||
console.error("[Commands Plugin] Failed to patch sidebar:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Register commands
|
||||
for (const [key, command] of Object.entries(commandMap)) {
|
||||
if (storage.enabledCommands[key]) {
|
||||
commands.push(registerCommand(command));
|
||||
try {
|
||||
commands.push(registerCommand(command));
|
||||
console.log(`[Commands Plugin] Registered command: ${key}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[Commands Plugin] Failed to register command ${key}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onUnload: () => {
|
||||
commands.forEach((unregister) => unregister());
|
||||
console.log("[Commands Plugin] Unloading...");
|
||||
|
||||
// Unregister commands
|
||||
commands.forEach((unregister) => {
|
||||
try {
|
||||
unregister();
|
||||
} catch (error) {
|
||||
// Ignore errors during cleanup
|
||||
}
|
||||
});
|
||||
commands = [];
|
||||
|
||||
// Unpatch sidebar
|
||||
if (sidebarUnpatch) {
|
||||
try {
|
||||
sidebarUnpatch();
|
||||
sidebarUnpatch = undefined;
|
||||
console.log("[Commands Plugin] Sidebar unpatched");
|
||||
} catch (error) {
|
||||
console.error("[Commands Plugin] Failed to unpatch sidebar:", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
settings,
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
72
plugins/Commands/settings/components/BetterTableRowGroup.tsx
Normal file
72
plugins/Commands/settings/components/BetterTableRowGroup.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
|
||||
export default function BetterTableRowGroup({
|
||||
title,
|
||||
icon,
|
||||
children,
|
||||
padding = false,
|
||||
}: React.PropsWithChildren<{
|
||||
title?: string;
|
||||
icon?: number;
|
||||
padding?: boolean;
|
||||
}>) {
|
||||
const styles = stylesheet.createThemedStyleSheet({
|
||||
main: {
|
||||
backgroundColor: semanticColors.CARD_PRIMARY_BG,
|
||||
borderColor: semanticColors.BORDER_FAINT,
|
||||
borderWidth: 1,
|
||||
borderRadius: 16,
|
||||
overflow: "hidden",
|
||||
flex: 1,
|
||||
maxWidth: "100%",
|
||||
},
|
||||
titleContainer: {
|
||||
marginBottom: 8,
|
||||
marginHorizontal: 16,
|
||||
marginTop: 8,
|
||||
gap: 4,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
icon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
marginTop: 1.5,
|
||||
tintColor: semanticColors.TEXT_MUTED,
|
||||
},
|
||||
titleText: {
|
||||
fontSize: 14,
|
||||
fontWeight: "600",
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
},
|
||||
wrapper: {
|
||||
marginHorizontal: 16,
|
||||
marginTop: 16,
|
||||
maxWidth: "100%",
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<RN.View style={styles.wrapper}>
|
||||
{title && (
|
||||
<RN.View style={styles.titleContainer}>
|
||||
{icon && (
|
||||
<RN.Image style={styles.icon} source={icon} resizeMode="cover" />
|
||||
)}
|
||||
<RN.Text style={styles.titleText}>{title.toUpperCase()}</RN.Text>
|
||||
</RN.View>
|
||||
)}
|
||||
<RN.View style={styles.main}>
|
||||
{padding ? (
|
||||
<RN.View style={{ paddingHorizontal: 16, paddingVertical: 16 }}>
|
||||
{children}
|
||||
</RN.View>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</RN.View>
|
||||
</RN.View>
|
||||
);
|
||||
}
|
||||
269
plugins/Commands/settings/components/Header.tsx
Normal file
269
plugins/Commands/settings/components/Header.tsx
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import { findByProps } from "@vendetta/metro";
|
||||
|
||||
export default function Header() {
|
||||
const [clickCounter, setClickCounter] = React.useState(0);
|
||||
const [clickTimeout, setClickTimeout] = React.useState<NodeJS.Timeout | null>(
|
||||
null,
|
||||
);
|
||||
const [avatarUrl, setAvatarUrl] = React.useState<string | null>(null);
|
||||
const [isAnimatedAvatar, setIsAnimatedAvatar] = React.useState(false);
|
||||
|
||||
// Initialize sidebar enabled setting if not exists
|
||||
React.useEffect(() => {
|
||||
if (storage.sidebarEnabled === undefined) {
|
||||
storage.sidebarEnabled = true;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Get current user from Discord's user store
|
||||
const users = findByProps("getUser", "getCurrentUser");
|
||||
const currentUser = users?.getCurrentUser?.();
|
||||
|
||||
// Get avatar URL utility
|
||||
const { getUserAvatarURL } = findByProps("getUserAvatarURL") || {};
|
||||
const { getDefaultAvatarURL } = findByProps("getDefaultAvatarURL") || {};
|
||||
|
||||
const styles = stylesheet.createThemedStyleSheet({
|
||||
container: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: 24,
|
||||
paddingHorizontal: 16,
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
},
|
||||
leftSection: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
flex: 1,
|
||||
gap: 20,
|
||||
maxWidth: 500,
|
||||
},
|
||||
avatarContainer: {
|
||||
position: "relative",
|
||||
width: 96,
|
||||
height: 96,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
avatarWrapper: {
|
||||
width: 96,
|
||||
height: 96,
|
||||
borderRadius: 24,
|
||||
backgroundColor: semanticColors.BACKGROUND_SECONDARY,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
overflow: "hidden",
|
||||
borderWidth: 2,
|
||||
borderColor: semanticColors.BACKGROUND_TERTIARY,
|
||||
},
|
||||
avatar: {
|
||||
width: 92,
|
||||
height: 92,
|
||||
borderRadius: 22,
|
||||
},
|
||||
cogwheelBackground: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
borderRadius: 24,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
opacity: 0,
|
||||
},
|
||||
cogwheelIcon: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
tintColor: semanticColors.INTERACTIVE_ACTIVE,
|
||||
},
|
||||
textContainer: {
|
||||
flex: 1,
|
||||
marginLeft: 4,
|
||||
},
|
||||
title: {
|
||||
fontSize: 28,
|
||||
fontWeight: "700",
|
||||
color: semanticColors.HEADER_PRIMARY,
|
||||
marginBottom: 4,
|
||||
textAlign: "left",
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 14,
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
textAlign: "left",
|
||||
lineHeight: 20,
|
||||
},
|
||||
avatarPlaceholder: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
tintColor: semanticColors.INTERACTIVE_NORMAL,
|
||||
},
|
||||
animatedIndicator: {
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
right: 8,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
||||
borderRadius: 10,
|
||||
width: 20,
|
||||
height: 20,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: 1,
|
||||
},
|
||||
gifText: {
|
||||
color: "#FFFFFF",
|
||||
fontSize: 10,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
});
|
||||
|
||||
// Fetch user's avatar on component mount
|
||||
React.useEffect(() => {
|
||||
if (currentUser) {
|
||||
let url;
|
||||
let animated = false;
|
||||
|
||||
// Check if avatar is animated (starts with "a_")
|
||||
const hasAnimatedAvatar = currentUser.avatar?.startsWith("a_");
|
||||
|
||||
// Try to get the user's custom avatar first
|
||||
if (getUserAvatarURL && currentUser.avatar) {
|
||||
// Use animated parameter for GIF avatars
|
||||
url = getUserAvatarURL(currentUser, hasAnimatedAvatar);
|
||||
animated = hasAnimatedAvatar;
|
||||
}
|
||||
// Fall back to default avatar
|
||||
else if (getDefaultAvatarURL) {
|
||||
url = getDefaultAvatarURL(currentUser);
|
||||
}
|
||||
// Last resort: Construct Discord CDN URL
|
||||
else if (currentUser.avatar) {
|
||||
const isGif = currentUser.avatar.startsWith("a_");
|
||||
url = `https://cdn.discordapp.com/avatars/${currentUser.id}/${currentUser.avatar}.${isGif ? "gif" : "png"}?size=256`;
|
||||
animated = isGif;
|
||||
} else {
|
||||
// Default Discord avatar based on discriminator or id
|
||||
const defaultAvatarIndex = currentUser.discriminator
|
||||
? parseInt(currentUser.discriminator) % 5
|
||||
: (parseInt(currentUser.id) >> 22) % 6;
|
||||
url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarIndex}.png`;
|
||||
}
|
||||
|
||||
setAvatarUrl(url);
|
||||
setIsAnimatedAvatar(animated);
|
||||
}
|
||||
}, [currentUser]);
|
||||
|
||||
const handleAvatarPress = () => {
|
||||
if (storage.hiddenSettings?.enabled) {
|
||||
storage.hiddenSettings.visible = !storage.hiddenSettings.visible;
|
||||
showToast(
|
||||
`Hidden settings ${storage.hiddenSettings.visible ? "visible" : "hidden"}`,
|
||||
getAssetIDByName("SettingsIcon"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (clickTimeout) {
|
||||
clearTimeout(clickTimeout);
|
||||
}
|
||||
|
||||
const newTimeout = setTimeout(() => {
|
||||
setClickCounter(0);
|
||||
}, 1000);
|
||||
setClickTimeout(newTimeout);
|
||||
|
||||
const newCounter = clickCounter + 1;
|
||||
setClickCounter(newCounter);
|
||||
|
||||
if (newCounter < 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
showToast("Hidden settings unlocked!", getAssetIDByName("CheckmarkIcon"));
|
||||
storage.hiddenSettings = {
|
||||
...storage.hiddenSettings,
|
||||
enabled: true,
|
||||
visible: true,
|
||||
};
|
||||
setClickCounter(0);
|
||||
};
|
||||
|
||||
const handleAvatarLongPress = () => {
|
||||
const newState = !storage.sidebarEnabled;
|
||||
storage.sidebarEnabled = newState;
|
||||
storage.pendingRestart = true;
|
||||
showToast(
|
||||
`Sidebar ${newState ? "enabled" : "disabled"}. Restart Discord to apply.`,
|
||||
getAssetIDByName(newState ? "CheckmarkIcon" : "XIcon"),
|
||||
);
|
||||
};
|
||||
|
||||
const [showCogwheel, setShowCogwheel] = React.useState(false);
|
||||
|
||||
return (
|
||||
<RN.View style={styles.container}>
|
||||
<RN.View style={styles.leftSection}>
|
||||
<RN.Pressable
|
||||
style={styles.avatarContainer}
|
||||
onPress={handleAvatarPress}
|
||||
onPressIn={() => setShowCogwheel(true)}
|
||||
onPressOut={() => setShowCogwheel(false)}
|
||||
delayLongPress={500}
|
||||
onLongPress={handleAvatarLongPress}
|
||||
>
|
||||
<RN.View style={styles.avatarWrapper}>
|
||||
{avatarUrl ? (
|
||||
<>
|
||||
<RN.Image
|
||||
source={{ uri: avatarUrl }}
|
||||
style={styles.avatar}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
{isAnimatedAvatar && (
|
||||
<RN.View style={styles.animatedIndicator}>
|
||||
<RN.Text style={styles.gifText}>GIF</RN.Text>
|
||||
</RN.View>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<RN.Image
|
||||
source={getAssetIDByName("ic_account_circle_24px")}
|
||||
style={styles.avatarPlaceholder}
|
||||
/>
|
||||
)}
|
||||
</RN.View>
|
||||
|
||||
{/* Cogwheel background overlay (appears on press) */}
|
||||
<RN.View
|
||||
style={[
|
||||
styles.cogwheelBackground,
|
||||
{ opacity: showCogwheel ? 0.8 : 0 },
|
||||
]}
|
||||
>
|
||||
<RN.Image
|
||||
source={getAssetIDByName("SettingsIcon")}
|
||||
style={styles.cogwheelIcon}
|
||||
/>
|
||||
</RN.View>
|
||||
</RN.Pressable>
|
||||
|
||||
<RN.View style={styles.textContainer}>
|
||||
<RN.Text style={styles.title}>Commands</RN.Text>
|
||||
<RN.Text style={styles.subtitle}>
|
||||
A collection of useful commands for Vendetta like clients
|
||||
</RN.Text>
|
||||
</RN.View>
|
||||
</RN.View>
|
||||
</RN.View>
|
||||
);
|
||||
}
|
||||
12
plugins/Commands/settings/exports.ts
Normal file
12
plugins/Commands/settings/exports.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export { default as Header } from "./components/Header";
|
||||
export { default as BetterTableRowGroup } from "./components/BetterTableRowGroup";
|
||||
export { default as FactsSettingsPage } from "./pages/FactsSettingsPage";
|
||||
export { default as ListSettingsPage } from "./pages/ListSettingsPage";
|
||||
export { default as ImageSettingsPage } from "./pages/ImageSettingsPage";
|
||||
export { default as GaryAPIPage } from "./pages/GaryAPIPage";
|
||||
export { default as SpotifySettingsPage } from "./pages/SpotifySettingsPage";
|
||||
export { default as OtherSettingsPage } from "./pages/OtherSettingsPage";
|
||||
export { default as AliucordPage } from "./pages/AliucordPage";
|
||||
export { default as HiddenSettingsPage } from "./pages/HiddenSettingsPage";
|
||||
export { default as CreditsPage } from "./pages/CreditsPage";
|
||||
export { default as NekosLifeCategoriesPage } from "./pages/NekosLifeCategoriesPage";
|
||||
76
plugins/Commands/settings/pages/AliucordPage.tsx
Normal file
76
plugins/Commands/settings/pages/AliucordPage.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
|
||||
import { NavigationNative } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import BetterTableRowGroup from "../components/BetterTableRowGroup";
|
||||
import NekosLifeCategoriesPage from "./NekosLifeCategoriesPage";
|
||||
|
||||
const { FormSwitchRow, FormRow } = Forms;
|
||||
|
||||
export default function AliucordPage() {
|
||||
useProxy(storage);
|
||||
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
||||
const navigation = NavigationNative.useNavigation();
|
||||
|
||||
return (
|
||||
<RN.ScrollView
|
||||
style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 16,
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
<BetterTableRowGroup
|
||||
title="IP Commands"
|
||||
icon={getAssetIDByName("GlobeEarthIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="/ip"
|
||||
subLabel="Get your current IP address"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("GlobeEarthIcon")} />}
|
||||
value={storage.enabledCommands.ip}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.ip = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="NekosLife Commands"
|
||||
icon={getAssetIDByName("ImageIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="/nekoslife"
|
||||
subLabel="Get images/gifs from nekos.life"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
|
||||
value={storage.enabledCommands.nekoslife}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.nekoslife = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
<FormRow
|
||||
label="View Categories"
|
||||
subLabel="See all 16 available categories"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("BookOpenIcon")} />}
|
||||
trailing={<FormRow.Arrow />}
|
||||
onPress={() =>
|
||||
navigation.push("VendettaCustomPage", {
|
||||
title: "NekosLife Categories",
|
||||
render: NekosLifeCategoriesPage,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<RN.View style={{ height: 32 }} />
|
||||
</RN.ScrollView>
|
||||
);
|
||||
}
|
||||
184
plugins/Commands/settings/pages/CreditsPage.tsx
Normal file
184
plugins/Commands/settings/pages/CreditsPage.tsx
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import BetterTableRowGroup from "../components/BetterTableRowGroup";
|
||||
|
||||
export default function CreditsPage() {
|
||||
const styles = stylesheet.createThemedStyleSheet({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
|
||||
},
|
||||
creditItem: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 20,
|
||||
backgroundColor: semanticColors.CARD_PRIMARY_BG,
|
||||
borderRadius: 12,
|
||||
marginHorizontal: 16,
|
||||
marginVertical: 6,
|
||||
},
|
||||
avatar: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 24,
|
||||
marginRight: 16,
|
||||
backgroundColor: semanticColors.BACKGROUND_SECONDARY,
|
||||
},
|
||||
textContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
commandText: {
|
||||
fontSize: 17,
|
||||
fontWeight: "600",
|
||||
color: semanticColors.HEADER_PRIMARY,
|
||||
marginBottom: 4,
|
||||
},
|
||||
authorText: {
|
||||
fontSize: 15,
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
marginBottom: 2,
|
||||
},
|
||||
linkText: {
|
||||
fontSize: 13,
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
fontWeight: "500",
|
||||
},
|
||||
infoText: {
|
||||
fontSize: 14,
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
marginBottom: 20,
|
||||
textAlign: "center",
|
||||
lineHeight: 20,
|
||||
},
|
||||
versionText: {
|
||||
fontSize: 15,
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
textAlign: "center",
|
||||
fontWeight: "600",
|
||||
lineHeight: 22,
|
||||
},
|
||||
});
|
||||
|
||||
const credits = [
|
||||
{
|
||||
command: "Facts Commands",
|
||||
author: "jdev082",
|
||||
avatar: "https://github.com/jdev082.png",
|
||||
github: "https://github.com/jdev082",
|
||||
},
|
||||
{
|
||||
command: "List Commands",
|
||||
author: "Kitomanari",
|
||||
avatar: "https://github.com/Kitosight.png",
|
||||
github: "https://github.com/Kitosight",
|
||||
},
|
||||
{
|
||||
command: "PetPet",
|
||||
author: "wolfieeee",
|
||||
avatar: "https://github.com/WolfPlugs.png",
|
||||
github: "https://github.com/WolfPlugs",
|
||||
},
|
||||
{
|
||||
command: "KonoChan Commands",
|
||||
author: "btmc727 & Rico040",
|
||||
avatar: "https://github.com/OTKUSteyler.png",
|
||||
github: "https://github.com/OTKUSteyler",
|
||||
},
|
||||
{
|
||||
command: "FirstMessage Command",
|
||||
author: "sapphire",
|
||||
avatar: "https://github.com/aeongdesu.png",
|
||||
github: "https://github.com/aeongdesu",
|
||||
},
|
||||
{
|
||||
command: "Sysinfo Command",
|
||||
author: "mugman",
|
||||
avatar: "https://github.com/mugman174.png",
|
||||
github: "https://github.com/mugman174",
|
||||
},
|
||||
{
|
||||
command: "Spotify Commands",
|
||||
author: "Kitomanari",
|
||||
avatar: "https://github.com/Kitosight.png",
|
||||
github: "https://github.com/Kitosight",
|
||||
},
|
||||
{
|
||||
command: "Gary Command",
|
||||
author: "Zach Orange",
|
||||
avatar: "https://github.com/IAmGaryTheCat.png",
|
||||
github: "https://github.com/IAmGaryTheCat",
|
||||
},
|
||||
{
|
||||
command: "IP & NekosLife Commands",
|
||||
author: "scruzism",
|
||||
avatar: "https://github.com/scrazzz.png",
|
||||
github: "https://github.com/scrazzz",
|
||||
},
|
||||
{
|
||||
command: "FriendInvites",
|
||||
author: "nikosszzz",
|
||||
avatar: "https://github.com/nikosszzz.png",
|
||||
github: "https://github.com/nikosszzz",
|
||||
},
|
||||
];
|
||||
|
||||
const handleProfilePress = (githubUrl: string) => {
|
||||
RN.Linking.openURL(githubUrl);
|
||||
};
|
||||
|
||||
return (
|
||||
<RN.ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 16,
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
<BetterTableRowGroup
|
||||
title="Plugin Authors"
|
||||
icon={getAssetIDByName("HeartIcon")}
|
||||
padding={true}
|
||||
>
|
||||
<RN.Text style={styles.infoText}>
|
||||
Thanks to this developers for creating such a nice plugins!{"\n"}
|
||||
Tap on any developer to visit their GitHub profile.
|
||||
</RN.Text>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
{credits.map((credit, index) => (
|
||||
<RN.Pressable
|
||||
key={index}
|
||||
style={styles.creditItem}
|
||||
onPress={() => handleProfilePress(credit.github)}
|
||||
android_ripple={{
|
||||
color: semanticColors.ANDROID_RIPPLE,
|
||||
radius: 200,
|
||||
}}
|
||||
>
|
||||
<RN.Image source={{ uri: credit.avatar }} style={styles.avatar} />
|
||||
<RN.View style={styles.textContainer}>
|
||||
<RN.Text style={styles.commandText}>{credit.command}</RN.Text>
|
||||
<RN.Text style={styles.authorText}>by {credit.author}</RN.Text>
|
||||
<RN.Text style={styles.linkText}>
|
||||
{credit.github.replace("https://github.com/", "@")}
|
||||
</RN.Text>
|
||||
</RN.View>
|
||||
</RN.Pressable>
|
||||
))}
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="About"
|
||||
icon={getAssetIDByName("InfoIcon")}
|
||||
padding={true}
|
||||
>
|
||||
<RN.Text style={styles.versionText}>
|
||||
Commands Plugin Collection{"\n"}
|
||||
Version 1.3.0
|
||||
</RN.Text>
|
||||
</BetterTableRowGroup>
|
||||
</RN.ScrollView>
|
||||
);
|
||||
}
|
||||
90
plugins/Commands/settings/pages/FactsSettingsPage.tsx
Normal file
90
plugins/Commands/settings/pages/FactsSettingsPage.tsx
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import BetterTableRowGroup from "../components/BetterTableRowGroup";
|
||||
|
||||
const { FormSwitchRow, FormRow } = Forms;
|
||||
|
||||
export default function FactsSettingsPage() {
|
||||
useProxy(storage);
|
||||
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
||||
|
||||
return (
|
||||
<RN.ScrollView
|
||||
style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 16,
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
<BetterTableRowGroup
|
||||
title="Fact Display Settings"
|
||||
icon={getAssetIDByName("SettingsIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="Send as Reply"
|
||||
subLabel="Send facts as a reply to the command message"
|
||||
leading={
|
||||
<FormRow.Icon source={getAssetIDByName("ArrowAngleLeftUpIcon")} />
|
||||
}
|
||||
value={storage.factSettings.sendAsReply}
|
||||
onValueChange={(v) => {
|
||||
storage.factSettings.sendAsReply = v;
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="Include Source Citation"
|
||||
subLabel="Include the source of facts when available"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("LinkIcon")} />}
|
||||
value={storage.factSettings.includeCitation}
|
||||
onValueChange={(v) => {
|
||||
storage.factSettings.includeCitation = v;
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="Available Fact Commands"
|
||||
icon={getAssetIDByName("BookmarkIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="/catfact"
|
||||
subLabel="Get random cat facts"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("BookCheckIcon")} />}
|
||||
value={storage.enabledCommands.catfact}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.catfact = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="/dogfact"
|
||||
subLabel="Get random dog facts"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("BookCheckIcon")} />}
|
||||
value={storage.enabledCommands.dogfact}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.dogfact = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="/useless"
|
||||
subLabel="Get random useless facts"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("BookCheckIcon")} />}
|
||||
value={storage.enabledCommands.useless}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.useless = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
</RN.ScrollView>
|
||||
);
|
||||
}
|
||||
146
plugins/Commands/settings/pages/GaryAPIPage.tsx
Normal file
146
plugins/Commands/settings/pages/GaryAPIPage.tsx
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import BetterTableRowGroup from "../components/BetterTableRowGroup";
|
||||
|
||||
const { FormSwitchRow, FormRow } = Forms;
|
||||
|
||||
export default function GaryAPIPage() {
|
||||
useProxy(storage);
|
||||
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
||||
|
||||
const styles = stylesheet.createThemedStyleSheet({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
|
||||
},
|
||||
infoText: {
|
||||
fontSize: 14,
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
marginBottom: 16,
|
||||
textAlign: "center",
|
||||
},
|
||||
currentText: {
|
||||
fontSize: 14,
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
textAlign: "center",
|
||||
fontWeight: "600",
|
||||
},
|
||||
});
|
||||
|
||||
const currentSource = storage.garySettings.imageSource;
|
||||
|
||||
return (
|
||||
<RN.ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 16,
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
<BetterTableRowGroup
|
||||
title="Gary Command Settings"
|
||||
icon={getAssetIDByName("SettingsIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="/gary"
|
||||
subLabel="Send random Gary images to channel"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("AttachmentIcon")} />}
|
||||
value={storage.enabledCommands.gary}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.gary = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="Image Source Selection"
|
||||
icon={getAssetIDByName("DownloadIcon")}
|
||||
padding={true}
|
||||
>
|
||||
<RN.Text style={styles.infoText}>
|
||||
Choose which API the /gary command should use to fetch images.
|
||||
</RN.Text>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="API Options"
|
||||
icon={getAssetIDByName("CloudIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="Gary API"
|
||||
subLabel="Original Gary the cat images from api.garythe.cat"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
|
||||
value={currentSource === "gary"}
|
||||
onValueChange={(v) => {
|
||||
if (v) {
|
||||
storage.garySettings.imageSource = "gary";
|
||||
forceUpdate();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="Cat API"
|
||||
subLabel="Random cat pictures from thecatapi.com"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
|
||||
value={currentSource === "catapi"}
|
||||
onValueChange={(v) => {
|
||||
if (v) {
|
||||
storage.garySettings.imageSource = "catapi";
|
||||
forceUpdate();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="Minker API"
|
||||
subLabel="Minky images from minky.materii.dev"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
|
||||
value={currentSource === "minker"}
|
||||
onValueChange={(v) => {
|
||||
if (v) {
|
||||
storage.garySettings.imageSource = "minker";
|
||||
forceUpdate();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="Goober API"
|
||||
subLabel="Goober images from api.garythe.cat/goober"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
|
||||
value={currentSource === "goober"}
|
||||
onValueChange={(v) => {
|
||||
if (v) {
|
||||
storage.garySettings.imageSource = "goober";
|
||||
forceUpdate();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="Current Selection"
|
||||
icon={getAssetIDByName("CheckmarkIcon")}
|
||||
padding={true}
|
||||
>
|
||||
<RN.Text style={styles.currentText}>
|
||||
Currently using:{" "}
|
||||
{currentSource === "gary"
|
||||
? "Gary API"
|
||||
: currentSource === "catapi"
|
||||
? "Cat API"
|
||||
: currentSource === "minker"
|
||||
? "Minker API"
|
||||
: currentSource === "goober"
|
||||
? "Goober API"
|
||||
: "Gary API"}
|
||||
</RN.Text>
|
||||
</BetterTableRowGroup>
|
||||
</RN.ScrollView>
|
||||
);
|
||||
}
|
||||
152
plugins/Commands/settings/pages/HiddenSettingsPage.tsx
Normal file
152
plugins/Commands/settings/pages/HiddenSettingsPage.tsx
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { alerts } from "@vendetta/ui";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import BetterTableRowGroup from "../components/BetterTableRowGroup";
|
||||
|
||||
const { FormSwitchRow, FormRow } = Forms;
|
||||
|
||||
export default function HiddenSettingsPage() {
|
||||
useProxy(storage);
|
||||
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
||||
|
||||
const styles = stylesheet.createThemedStyleSheet({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
|
||||
},
|
||||
warningText: {
|
||||
fontSize: 14,
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
textAlign: "center",
|
||||
lineHeight: 20,
|
||||
fontWeight: "600",
|
||||
},
|
||||
infoText: {
|
||||
fontSize: 14,
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
textAlign: "center",
|
||||
lineHeight: 20,
|
||||
marginTop: 8,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<RN.ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 16,
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
<BetterTableRowGroup
|
||||
title="⚠️ Warning"
|
||||
icon={getAssetIDByName("WarningIcon")}
|
||||
padding={true}
|
||||
>
|
||||
<RN.Text style={styles.warningText}>
|
||||
These are hidden commands that may contain mature content or
|
||||
experimental features.
|
||||
</RN.Text>
|
||||
<RN.Text style={styles.infoText}>
|
||||
Use at your own discretion. Commands in this section are disabled by
|
||||
default.
|
||||
</RN.Text>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="Hidden Commands"
|
||||
icon={getAssetIDByName("EyeIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="/lovefemboys"
|
||||
subLabel="Get random femboy images from r/femboys (NSFW content available)"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("HeartIcon")} />}
|
||||
value={storage.enabledCommands.lovefemboys}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.lovefemboys = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="NSFW Bypass Options"
|
||||
icon={getAssetIDByName("WarningIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="KonoChan NSFW Bypass"
|
||||
subLabel="Allow NSFW KonoChan content in non-NSFW channels (USE WITH CAUTION)"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("ShieldIcon")} />}
|
||||
value={storage.hiddenSettings.konochanBypassNsfw}
|
||||
onValueChange={(v) => {
|
||||
if (v) {
|
||||
alerts.showConfirmationAlert({
|
||||
title: "⚠️ NSFW Bypass Warning",
|
||||
content:
|
||||
"Enabling this allows NSFW content from KonoChan to be sent in any channel, including non-NSFW channels. This could violate server rules or Discord ToS. Use responsibly!",
|
||||
confirmText: "I Understand",
|
||||
cancelText: "Cancel",
|
||||
onConfirm: () => {
|
||||
storage.hiddenSettings.konochanBypassNsfw = true;
|
||||
forceUpdate();
|
||||
},
|
||||
onCancel: () => {},
|
||||
});
|
||||
} else {
|
||||
storage.hiddenSettings.konochanBypassNsfw = false;
|
||||
forceUpdate();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="Hidden Settings Control"
|
||||
icon={getAssetIDByName("SettingsIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="Keep Hidden Settings Visible"
|
||||
subLabel="Keep this section visible even when navigating away"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("EyeIcon")} />}
|
||||
value={storage.hiddenSettings.visible}
|
||||
onValueChange={(v) => {
|
||||
storage.hiddenSettings.visible = v;
|
||||
}}
|
||||
/>
|
||||
<FormRow
|
||||
label="Reset Hidden Settings"
|
||||
subLabel="Hide this section and disable all hidden commands"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("TrashIcon")} />}
|
||||
onPress={() => {
|
||||
alerts.showConfirmationAlert({
|
||||
title: "Reset Hidden Settings",
|
||||
content:
|
||||
"This will hide the hidden settings section and disable all hidden commands. Are you sure?",
|
||||
confirmText: "Reset",
|
||||
onConfirm: () => {
|
||||
storage.hiddenSettings.enabled = false;
|
||||
storage.hiddenSettings.visible = false;
|
||||
storage.hiddenSettings.konochanBypassNsfw = false;
|
||||
storage.enabledCommands.lovefemboys = false;
|
||||
|
||||
storage.pendingRestart = true;
|
||||
showToast(
|
||||
"Hidden settings reset",
|
||||
getAssetIDByName("CheckmarkIcon"),
|
||||
);
|
||||
},
|
||||
cancelText: "Cancel",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
</RN.ScrollView>
|
||||
);
|
||||
}
|
||||
72
plugins/Commands/settings/pages/ImageSettingsPage.tsx
Normal file
72
plugins/Commands/settings/pages/ImageSettingsPage.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import BetterTableRowGroup from "../components/BetterTableRowGroup";
|
||||
|
||||
const { FormSwitchRow, FormRow } = Forms;
|
||||
|
||||
export default function ImageSettingsPage() {
|
||||
useProxy(storage);
|
||||
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
||||
|
||||
return (
|
||||
<RN.ScrollView
|
||||
style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 16,
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
<BetterTableRowGroup
|
||||
title="Image Commands"
|
||||
icon={getAssetIDByName("ImageIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="/petpet"
|
||||
subLabel="Create pet-pet GIF of a user"
|
||||
leading={
|
||||
<FormRow.Icon source={getAssetIDByName("HandRequestSpeakIcon")} />
|
||||
}
|
||||
value={storage.enabledCommands.petpet}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.petpet = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="KonoChan Commands"
|
||||
icon={getAssetIDByName("ImageIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="/konoself"
|
||||
subLabel="Get random image from KonoChan (private)"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("EyeIcon")} />}
|
||||
value={storage.enabledCommands.konoself}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.konoself = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="/konosend"
|
||||
subLabel="Send random image from KonoChan to channel"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
|
||||
value={storage.enabledCommands.konosend}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.konosend = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
</RN.ScrollView>
|
||||
);
|
||||
}
|
||||
85
plugins/Commands/settings/pages/ListSettingsPage.tsx
Normal file
85
plugins/Commands/settings/pages/ListSettingsPage.tsx
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import BetterTableRowGroup from "../components/BetterTableRowGroup";
|
||||
|
||||
const { FormSwitchRow, FormRow } = Forms;
|
||||
|
||||
export default function ListSettingsPage() {
|
||||
useProxy(storage);
|
||||
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
||||
|
||||
return (
|
||||
<RN.ScrollView
|
||||
style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 16,
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
<BetterTableRowGroup
|
||||
title="List Display Settings"
|
||||
icon={getAssetIDByName("SettingsIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="Always Send Detailed Plugin List"
|
||||
subLabel="Always use detailed mode when listing plugins"
|
||||
leading={
|
||||
<FormRow.Icon source={getAssetIDByName("PuzzlePieceIcon")} />
|
||||
}
|
||||
value={storage.listSettings.pluginListAlwaysDetailed}
|
||||
onValueChange={(v) => {
|
||||
storage.listSettings.pluginListAlwaysDetailed = v;
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="Always Send Detailed Theme List"
|
||||
subLabel="Always use detailed mode when listing themes"
|
||||
leading={
|
||||
<FormRow.Icon source={getAssetIDByName("PaintPaletteIcon")} />
|
||||
}
|
||||
value={storage.listSettings.themeListAlwaysDetailed}
|
||||
onValueChange={(v) => {
|
||||
storage.listSettings.themeListAlwaysDetailed = v;
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="Available List Commands"
|
||||
icon={getAssetIDByName("ListViewIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="/plugin-list"
|
||||
subLabel="List all installed plugins"
|
||||
leading={
|
||||
<FormRow.Icon source={getAssetIDByName("PuzzlePieceIcon")} />
|
||||
}
|
||||
value={storage.enabledCommands.pluginList}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.pluginList = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="/theme-list"
|
||||
subLabel="List all installed themes"
|
||||
leading={
|
||||
<FormRow.Icon source={getAssetIDByName("PaintPaletteIcon")} />
|
||||
}
|
||||
value={storage.enabledCommands.themeList}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.themeList = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
</RN.ScrollView>
|
||||
);
|
||||
}
|
||||
161
plugins/Commands/settings/pages/NekosLifeCategoriesPage.tsx
Normal file
161
plugins/Commands/settings/pages/NekosLifeCategoriesPage.tsx
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import BetterTableRowGroup from "../components/BetterTableRowGroup";
|
||||
|
||||
export default function NekosLifeCategoriesPage() {
|
||||
const styles = stylesheet.createThemedStyleSheet({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
|
||||
},
|
||||
tableHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
backgroundColor: semanticColors.BACKGROUND_MODIFIER_ACCENT,
|
||||
borderBottomWidth: 2,
|
||||
borderBottomColor: semanticColors.BORDER_STRONG,
|
||||
},
|
||||
headerText: {
|
||||
fontSize: 14,
|
||||
fontWeight: "700",
|
||||
color: semanticColors.HEADER_PRIMARY,
|
||||
textTransform: "uppercase",
|
||||
},
|
||||
nameHeader: {
|
||||
flex: 2,
|
||||
},
|
||||
codeHeader: {
|
||||
flex: 2,
|
||||
textAlign: "center",
|
||||
},
|
||||
badgeHeader: {
|
||||
flex: 1,
|
||||
textAlign: "center",
|
||||
},
|
||||
categoryItem: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: semanticColors.BORDER_FAINT,
|
||||
},
|
||||
categoryName: {
|
||||
fontSize: 16,
|
||||
fontWeight: "500",
|
||||
color: semanticColors.HEADER_PRIMARY,
|
||||
flex: 2,
|
||||
},
|
||||
categoryValue: {
|
||||
fontSize: 14,
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
fontFamily: "monospace",
|
||||
flex: 2,
|
||||
textAlign: "center",
|
||||
},
|
||||
sfwBadgeContainer: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
},
|
||||
sfwBadge: {
|
||||
backgroundColor: semanticColors.STATUS_POSITIVE,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 12,
|
||||
},
|
||||
badgeText: {
|
||||
fontSize: 12,
|
||||
fontWeight: "600",
|
||||
color: semanticColors.TEXT_NORMAL,
|
||||
},
|
||||
infoText: {
|
||||
fontSize: 14,
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
textAlign: "center",
|
||||
lineHeight: 20,
|
||||
},
|
||||
});
|
||||
|
||||
// nekos.life categories
|
||||
const sfwCategories = [
|
||||
{ name: "Avatar", value: "avatar" },
|
||||
{ name: "Classic", value: "classic" },
|
||||
{ name: "Cuddle", value: "cuddle" },
|
||||
{ name: "Fox Girl", value: "fox_girl" },
|
||||
{ name: "Gecg", value: "gecg" },
|
||||
{ name: "Holo", value: "holo" },
|
||||
{ name: "Kemonomimi", value: "kemonomimi" },
|
||||
{ name: "Kiss", value: "kiss" },
|
||||
{ name: "Neko", value: "neko" },
|
||||
{ name: "Neko GIF", value: "ngif" },
|
||||
{ name: "Smug", value: "smug" },
|
||||
{ name: "Spank", value: "spank" },
|
||||
{ name: "Tickle", value: "tickle" },
|
||||
{ name: "Waifu", value: "waifu" },
|
||||
{ name: "Wallpaper", value: "wallpaper" },
|
||||
{ name: "Woof", value: "woof" },
|
||||
];
|
||||
|
||||
// Sort categories alphabetically
|
||||
const sortedCategories = sfwCategories.sort((a, b) =>
|
||||
a.name.localeCompare(b.name),
|
||||
);
|
||||
|
||||
return (
|
||||
<RN.ScrollView
|
||||
style={styles.container}
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 16,
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
<BetterTableRowGroup
|
||||
title="📊 Category Statistics"
|
||||
icon={getAssetIDByName("ChartIcon")}
|
||||
padding={true}
|
||||
>
|
||||
<RN.Text style={styles.infoText}>
|
||||
Total SFW Categories: {sfwCategories.length}
|
||||
{"\n\n"}
|
||||
Use these category names with the /nekoslife command.{"\n"}
|
||||
All categories are Safe For Work (SFW).
|
||||
</RN.Text>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="📋 SFW Categories"
|
||||
icon={getAssetIDByName("ListViewIcon")}
|
||||
>
|
||||
{/* Table Header */}
|
||||
<RN.View style={styles.tableHeader}>
|
||||
<RN.Text style={[styles.headerText, styles.nameHeader]}>
|
||||
Category Name
|
||||
</RN.Text>
|
||||
<RN.Text style={[styles.headerText, styles.codeHeader]}>
|
||||
Command Code
|
||||
</RN.Text>
|
||||
<RN.Text style={[styles.headerText, styles.badgeHeader]}>
|
||||
Type
|
||||
</RN.Text>
|
||||
</RN.View>
|
||||
|
||||
{/* Table Rows */}
|
||||
{sortedCategories.map((category, index) => (
|
||||
<RN.View key={index} style={styles.categoryItem}>
|
||||
<RN.Text style={styles.categoryName}>{category.name}</RN.Text>
|
||||
<RN.Text style={styles.categoryValue}>{category.value}</RN.Text>
|
||||
<RN.View style={styles.sfwBadgeContainer}>
|
||||
<RN.View style={styles.sfwBadge}>
|
||||
<RN.Text style={styles.badgeText}>SFW</RN.Text>
|
||||
</RN.View>
|
||||
</RN.View>
|
||||
</RN.View>
|
||||
))}
|
||||
</BetterTableRowGroup>
|
||||
</RN.ScrollView>
|
||||
);
|
||||
}
|
||||
95
plugins/Commands/settings/pages/OtherSettingsPage.tsx
Normal file
95
plugins/Commands/settings/pages/OtherSettingsPage.tsx
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import BetterTableRowGroup from "../components/BetterTableRowGroup";
|
||||
|
||||
const { FormSwitchRow, FormRow } = Forms;
|
||||
|
||||
export default function OtherSettingsPage() {
|
||||
useProxy(storage);
|
||||
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
||||
|
||||
return (
|
||||
<RN.ScrollView
|
||||
style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 16,
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
<BetterTableRowGroup
|
||||
title="Message Commands"
|
||||
icon={getAssetIDByName("ChatIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="/firstmessage"
|
||||
subLabel="Get the first message in a channel"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("ChatIcon")} />}
|
||||
value={storage.enabledCommands.firstmessage}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.firstmessage = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="System Commands"
|
||||
icon={getAssetIDByName("SettingsIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="/sysinfo"
|
||||
subLabel="Display system information"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("SettingsIcon")} />}
|
||||
value={storage.enabledCommands.sysinfo}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.sysinfo = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="Friend Invites"
|
||||
icon={getAssetIDByName("UserAddIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="/invite create"
|
||||
subLabel="Generate a friend invite link"
|
||||
value={storage.enabledCommands.friendInviteCreate}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.friendInviteCreate = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="/view invites"
|
||||
subLabel="View your current friend invites"
|
||||
value={storage.enabledCommands.friendInviteView}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.friendInviteView = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="/revoke invites"
|
||||
subLabel="Revoke all your friend invites"
|
||||
value={storage.enabledCommands.friendInviteRevoke}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.friendInviteRevoke = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
</RN.ScrollView>
|
||||
);
|
||||
}
|
||||
104
plugins/Commands/settings/pages/SpotifySettingsPage.tsx
Normal file
104
plugins/Commands/settings/pages/SpotifySettingsPage.tsx
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import BetterTableRowGroup from "../components/BetterTableRowGroup";
|
||||
|
||||
const { FormSwitchRow, FormRow } = Forms;
|
||||
|
||||
export default function SpotifySettingsPage() {
|
||||
useProxy(storage);
|
||||
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
||||
|
||||
const styles = stylesheet.createThemedStyleSheet({
|
||||
infoText: {
|
||||
fontSize: 14,
|
||||
color: semanticColors.TEXT_MUTED,
|
||||
textAlign: "center",
|
||||
lineHeight: 20,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<RN.ScrollView
|
||||
style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 16,
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
<BetterTableRowGroup
|
||||
title="Spotify Commands"
|
||||
icon={getAssetIDByName("SpotifyNeutralIcon")}
|
||||
>
|
||||
<FormSwitchRow
|
||||
label="/spotify track"
|
||||
subLabel="Share your current Spotify track"
|
||||
leading={
|
||||
<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />
|
||||
}
|
||||
value={storage.enabledCommands.spotifyTrack}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.spotifyTrack = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="/spotify album"
|
||||
subLabel="Share your current track's album"
|
||||
leading={
|
||||
<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />
|
||||
}
|
||||
value={storage.enabledCommands.spotifyAlbum}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.spotifyAlbum = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="/spotify artists"
|
||||
subLabel="Share your current track's artists"
|
||||
leading={
|
||||
<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />
|
||||
}
|
||||
value={storage.enabledCommands.spotifyArtists}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.spotifyArtists = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
<FormSwitchRow
|
||||
label="/spotify cover"
|
||||
subLabel="Share your current track's cover"
|
||||
leading={
|
||||
<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />
|
||||
}
|
||||
value={storage.enabledCommands.spotifyCover}
|
||||
onValueChange={(v) => {
|
||||
storage.enabledCommands.spotifyCover = v;
|
||||
storage.pendingRestart = true;
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="About Spotify Commands"
|
||||
icon={getAssetIDByName("InfoIcon")}
|
||||
padding={true}
|
||||
>
|
||||
<RN.Text style={styles.infoText}>
|
||||
These commands allow you to share your current Spotify activity in
|
||||
Discord. Make sure you have Spotify connected to Discord for these
|
||||
commands to work properly.
|
||||
</RN.Text>
|
||||
</BetterTableRowGroup>
|
||||
</RN.ScrollView>
|
||||
);
|
||||
}
|
||||
264
plugins/Commands/settings/settings.tsx
Normal file
264
plugins/Commands/settings/settings.tsx
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
import { storage } from "@vendetta/plugin";
|
||||
import { useProxy } from "@vendetta/storage";
|
||||
import { React, ReactNative, stylesheet } from "@vendetta/metro/common";
|
||||
import { NavigationNative } from "@vendetta/metro/common";
|
||||
import { semanticColors } from "@vendetta/ui";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { alerts } from "@vendetta/ui";
|
||||
import { showToast } from "@vendetta/ui/toasts";
|
||||
import Header from "./components/Header";
|
||||
import BetterTableRowGroup from "./components/BetterTableRowGroup";
|
||||
|
||||
// Import all pages directly
|
||||
import FactsSettingsPage from "./pages/FactsSettingsPage";
|
||||
import ListSettingsPage from "./pages/ListSettingsPage";
|
||||
import ImageSettingsPage from "./pages/ImageSettingsPage";
|
||||
import GaryAPIPage from "./pages/GaryAPIPage";
|
||||
import SpotifySettingsPage from "./pages/SpotifySettingsPage";
|
||||
import AliucordPage from "./pages/AliucordPage";
|
||||
import OtherSettingsPage from "./pages/OtherSettingsPage";
|
||||
import HiddenSettingsPage from "./pages/HiddenSettingsPage";
|
||||
import CreditsPage from "./pages/CreditsPage";
|
||||
|
||||
const { FormRow } = Forms;
|
||||
|
||||
// Initialize storage with default values
|
||||
storage.factSettings ??= {
|
||||
sendAsReply: true,
|
||||
includeCitation: false,
|
||||
};
|
||||
|
||||
storage.listSettings ??= {
|
||||
pluginListAlwaysDetailed: false,
|
||||
themeListAlwaysDetailed: false,
|
||||
};
|
||||
|
||||
storage.garySettings ??= {
|
||||
imageSource: "gary",
|
||||
};
|
||||
|
||||
storage.enabledCommands ??= {
|
||||
catfact: true,
|
||||
dogfact: true,
|
||||
useless: true,
|
||||
petpet: true,
|
||||
pluginList: true,
|
||||
themeList: true,
|
||||
konoself: true,
|
||||
konosend: true,
|
||||
firstmessage: true,
|
||||
sysinfo: true,
|
||||
spotifyTrack: true,
|
||||
spotifyAlbum: true,
|
||||
spotifyArtists: true,
|
||||
spotifyCover: true,
|
||||
gary: true,
|
||||
ip: true,
|
||||
lovefemboys: false,
|
||||
nekoslife: false,
|
||||
friendInviteCreate: true,
|
||||
friendInviteView: true,
|
||||
friendInviteRevoke: true,
|
||||
};
|
||||
|
||||
storage.pendingRestart ??= false;
|
||||
|
||||
// Hidden settings storage with NSFW bypass option
|
||||
storage.hiddenSettings ??= {
|
||||
enabled: false,
|
||||
visible: false,
|
||||
konochanBypassNsfw: false,
|
||||
};
|
||||
|
||||
// Sidebar setting (enabled by default)
|
||||
storage.sidebarEnabled ??= true;
|
||||
|
||||
// MAIN SETTINGS COMPONENT
|
||||
export default function Settings() {
|
||||
useProxy(storage);
|
||||
const navigation = NavigationNative.useNavigation();
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (storage.pendingRestart) {
|
||||
storage.pendingRestart = false;
|
||||
alerts.showConfirmationAlert({
|
||||
title: "Restart Required",
|
||||
content:
|
||||
"You have made changes to commands. Please restart Discord to apply these changes.",
|
||||
confirmText: "Okay",
|
||||
cancelText: null,
|
||||
onConfirm: () => {},
|
||||
});
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ReactNative.View style={{ flex: 1 }}>
|
||||
<ReactNative.ScrollView
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<Header />
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="Command Categories"
|
||||
icon={getAssetIDByName("ChannelListIcon")}
|
||||
>
|
||||
<FormRow
|
||||
label="Facts Commands"
|
||||
subLabel="Cat facts, dog facts, and useless facts"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("BookmarkIcon")} />}
|
||||
trailing={<FormRow.Arrow />}
|
||||
onPress={() =>
|
||||
navigation.push("VendettaCustomPage", {
|
||||
title: "Facts Commands",
|
||||
render: FactsSettingsPage,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<FormRow
|
||||
label="List Commands"
|
||||
subLabel="Plugin lists and theme lists"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("ListViewIcon")} />}
|
||||
trailing={<FormRow.Arrow />}
|
||||
onPress={() =>
|
||||
navigation.push("VendettaCustomPage", {
|
||||
title: "List Commands",
|
||||
render: ListSettingsPage,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<FormRow
|
||||
label="Image Commands"
|
||||
subLabel="PetPet, KonoChan, and image utilities"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
|
||||
trailing={<FormRow.Arrow />}
|
||||
onPress={() =>
|
||||
navigation.push("VendettaCustomPage", {
|
||||
title: "Image Commands",
|
||||
render: ImageSettingsPage,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<FormRow
|
||||
label="Gary Commands"
|
||||
subLabel={`Gary images - Current: ${
|
||||
storage.garySettings.imageSource === "gary"
|
||||
? "Gary API"
|
||||
: storage.garySettings.imageSource === "catapi"
|
||||
? "Cat API"
|
||||
: storage.garySettings.imageSource === "minker"
|
||||
? "Minker API"
|
||||
: storage.garySettings.imageSource === "goober"
|
||||
? "Goober API"
|
||||
: "Gary API"
|
||||
}`}
|
||||
leading={<FormRow.Icon source={getAssetIDByName("CameraIcon")} />}
|
||||
trailing={<FormRow.Arrow />}
|
||||
onPress={() =>
|
||||
navigation.push("VendettaCustomPage", {
|
||||
title: "Gary Commands",
|
||||
render: GaryAPIPage,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<FormRow
|
||||
label="Spotify Commands"
|
||||
subLabel="Share your Spotify activity"
|
||||
leading={
|
||||
<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />
|
||||
}
|
||||
trailing={<FormRow.Arrow />}
|
||||
onPress={() =>
|
||||
navigation.push("VendettaCustomPage", {
|
||||
title: "Spotify Commands",
|
||||
render: SpotifySettingsPage,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<FormRow
|
||||
label="Aliucord Commands"
|
||||
subLabel="Commands from Aliucord"
|
||||
leading={
|
||||
<FormRow.Icon
|
||||
source={{
|
||||
uri: "https://avatars.githubusercontent.com/u/78881422?s=200&v=4",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
trailing={<FormRow.Arrow />}
|
||||
onPress={() =>
|
||||
navigation.push("VendettaCustomPage", {
|
||||
title: "Aliucord",
|
||||
render: AliucordPage,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<FormRow
|
||||
label="Other Commands"
|
||||
subLabel="Utility and system commands"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("WrenchIcon")} />}
|
||||
trailing={<FormRow.Arrow />}
|
||||
onPress={() =>
|
||||
navigation.push("VendettaCustomPage", {
|
||||
title: "Other Commands",
|
||||
render: OtherSettingsPage,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
{storage.hiddenSettings?.visible && (
|
||||
<BetterTableRowGroup
|
||||
title="Hidden Settings"
|
||||
icon={getAssetIDByName("EyeIcon")}
|
||||
>
|
||||
<FormRow
|
||||
label="Hidden Commands"
|
||||
subLabel="Access to experimental and NSFW commands"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("EyeIcon")} />}
|
||||
trailing={<FormRow.Arrow />}
|
||||
onPress={() =>
|
||||
navigation.push("VendettaCustomPage", {
|
||||
title: "Hidden Settings",
|
||||
render: HiddenSettingsPage,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
)}
|
||||
|
||||
<BetterTableRowGroup
|
||||
title="Other"
|
||||
icon={getAssetIDByName("InformationIcon")}
|
||||
>
|
||||
<FormRow
|
||||
label="Credits"
|
||||
subLabel="View original authors of the plugins"
|
||||
leading={<FormRow.Icon source={getAssetIDByName("HeartIcon")} />}
|
||||
trailing={<FormRow.Arrow />}
|
||||
onPress={() =>
|
||||
navigation.push("VendettaCustomPage", {
|
||||
title: "Credits",
|
||||
render: CreditsPage,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</BetterTableRowGroup>
|
||||
|
||||
{/* Add sufficient bottom padding for scrolling */}
|
||||
<ReactNative.View style={{ height: 32 }} />
|
||||
</ReactNative.ScrollView>
|
||||
</ReactNative.View>
|
||||
);
|
||||
}
|
||||
52
plugins/Commands/settings/types/index.ts
Normal file
52
plugins/Commands/settings/types/index.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
export interface FactSettings {
|
||||
sendAsReply: boolean;
|
||||
includeCitation: boolean;
|
||||
}
|
||||
|
||||
export interface ListSettings {
|
||||
pluginListAlwaysDetailed: boolean;
|
||||
themeListAlwaysDetailed: boolean;
|
||||
}
|
||||
|
||||
export interface GarySettings {
|
||||
imageSource: "gary" | "catapi" | "minker" | "goober";
|
||||
}
|
||||
|
||||
export interface EnabledCommands {
|
||||
catfact: boolean;
|
||||
dogfact: boolean;
|
||||
useless: boolean;
|
||||
petpet: boolean;
|
||||
pluginList: boolean;
|
||||
themeList: boolean;
|
||||
konoself: boolean;
|
||||
konosend: boolean;
|
||||
firstmessage: boolean;
|
||||
sysinfo: boolean;
|
||||
spotifyTrack: boolean;
|
||||
spotifyAlbum: boolean;
|
||||
spotifyArtists: boolean;
|
||||
spotifyCover: boolean;
|
||||
gary: boolean;
|
||||
ip: boolean;
|
||||
lovefemboys: boolean;
|
||||
nekoslife: boolean;
|
||||
friendInviteCreate: boolean;
|
||||
friendInviteView: boolean;
|
||||
friendInviteRevoke: boolean;
|
||||
}
|
||||
|
||||
export interface HiddenSettings {
|
||||
enabled: boolean;
|
||||
visible: boolean;
|
||||
konochanBypassNsfw: boolean;
|
||||
}
|
||||
|
||||
export interface StorageType {
|
||||
factSettings: FactSettings;
|
||||
listSettings: ListSettings;
|
||||
garySettings: GarySettings;
|
||||
enabledCommands: EnabledCommands;
|
||||
pendingRestart: boolean;
|
||||
hiddenSettings: HiddenSettings;
|
||||
}
|
||||
235
plugins/Commands/sidebar.tsx
Normal file
235
plugins/Commands/sidebar.tsx
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
import { storage } from "@vendetta/plugin";
|
||||
import { React, NavigationNative } from "@vendetta/metro/common";
|
||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||
import { after } from "@vendetta/patcher";
|
||||
import { Forms } from "@vendetta/ui/components";
|
||||
import { findInReactTree } from "@vendetta/utils";
|
||||
import { findByProps } from "@vendetta/metro";
|
||||
import { logger } from "@vendetta";
|
||||
import Settings from "./settings/settings";
|
||||
|
||||
const { FormSection, FormRow } = Forms;
|
||||
const { TableRowIcon } = findByProps("TableRowIcon");
|
||||
const bunny = window.bunny;
|
||||
declare global {
|
||||
interface Window {
|
||||
bunny: any;
|
||||
}
|
||||
}
|
||||
|
||||
const tabsNavigationRef = bunny?.metro?.findByPropsLazy("getRootNavigationRef");
|
||||
const settingConstants = bunny?.metro?.findByPropsLazy(
|
||||
"SETTING_RENDERER_CONFIG",
|
||||
);
|
||||
const createListModule = bunny?.metro?.findByPropsLazy("createList");
|
||||
const SettingsOverviewScreen = bunny?.metro?.findByNameLazy(
|
||||
"SettingsOverviewScreen",
|
||||
false,
|
||||
);
|
||||
|
||||
function Section({ tabs }) {
|
||||
const navigation = NavigationNative.useNavigation();
|
||||
|
||||
return React.createElement(FormRow, {
|
||||
label: tabs.title(),
|
||||
leading: React.createElement(FormRow.Icon, { source: tabs.icon }),
|
||||
trailing: React.createElement(React.Fragment, {}, [
|
||||
tabs.trailing ? tabs.trailing() : null,
|
||||
React.createElement(FormRow.Arrow, { key: "arrow" }),
|
||||
]),
|
||||
onPress: () => {
|
||||
const Component = tabs.page;
|
||||
navigation.navigate("VendettaCustomPage", {
|
||||
title: tabs.title(),
|
||||
render: () => React.createElement(Component),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function patchPanelUI(tabs, patches) {
|
||||
try {
|
||||
patches.push(
|
||||
after(
|
||||
"default",
|
||||
bunny?.metro?.findByPropsLazy(["renderTitle", "sections"], false),
|
||||
(_, ret) => {
|
||||
const UserSettingsOverview = findInReactTree(
|
||||
ret.props.children,
|
||||
(n) => n.type?.name === "UserSettingsOverview",
|
||||
);
|
||||
|
||||
if (UserSettingsOverview) {
|
||||
patches.push(
|
||||
after(
|
||||
"render",
|
||||
UserSettingsOverview.type.prototype,
|
||||
(_args, res) => {
|
||||
const sections = findInReactTree(
|
||||
res.props.children,
|
||||
(n) => n?.children?.[1]?.type === FormSection,
|
||||
)?.children;
|
||||
|
||||
if (sections) {
|
||||
const index = sections.findIndex((c) =>
|
||||
["BILLING_SETTINGS", "PREMIUM_SETTINGS"].includes(
|
||||
c?.props?.label,
|
||||
),
|
||||
);
|
||||
|
||||
sections.splice(
|
||||
-~index || 4,
|
||||
0,
|
||||
React.createElement(Section, { key: tabs.key, tabs }),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
true,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
logger.info("Panel UI patch failed graciously 💔", error);
|
||||
}
|
||||
}
|
||||
|
||||
function patchTabsUI(tabs, patches) {
|
||||
if (!settingConstants || !tabsNavigationRef) {
|
||||
console.warn(
|
||||
"[Commands Plugin] Missing required constants for tabs UI patch",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const row = {
|
||||
[tabs.key]: {
|
||||
type: "pressable",
|
||||
title: tabs.title,
|
||||
icon: tabs.icon,
|
||||
IconComponent:
|
||||
tabs.icon &&
|
||||
(() => React.createElement(TableRowIcon, { source: tabs.icon })),
|
||||
usePredicate: tabs.predicate,
|
||||
useTrailing: tabs.trailing,
|
||||
onPress: () => {
|
||||
const navigation = tabsNavigationRef.getRootNavigationRef();
|
||||
const Component = tabs.page;
|
||||
|
||||
navigation.navigate("VendettaCustomPage", {
|
||||
title: tabs.title(),
|
||||
render: () => React.createElement(Component),
|
||||
});
|
||||
},
|
||||
withArrow: true,
|
||||
},
|
||||
};
|
||||
|
||||
let rendererConfigValue = settingConstants.SETTING_RENDERER_CONFIG;
|
||||
|
||||
Object.defineProperty(settingConstants, "SETTING_RENDERER_CONFIG", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get: () => ({
|
||||
...rendererConfigValue,
|
||||
...row,
|
||||
}),
|
||||
set: (v) => (rendererConfigValue = v),
|
||||
});
|
||||
|
||||
const firstRender = Symbol("pinToSettings meow meow");
|
||||
|
||||
try {
|
||||
if (!createListModule) return;
|
||||
patches.push(
|
||||
after("createList", createListModule, function (args, ret) {
|
||||
if (!args[0][firstRender]) {
|
||||
args[0][firstRender] = true;
|
||||
|
||||
const [config] = args;
|
||||
const sections = config.sections;
|
||||
|
||||
const section = sections?.find((x: any) =>
|
||||
["Bunny", "Revenge", "Kettu", "Vencore", "ShiggyCord"].some(
|
||||
(mod) => x.label === mod && x.title === mod,
|
||||
),
|
||||
);
|
||||
|
||||
if (section?.settings) {
|
||||
section.settings = [...section.settings, tabs.key];
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
if (!SettingsOverviewScreen) return;
|
||||
patches.push(
|
||||
after("default", SettingsOverviewScreen, (args, ret) => {
|
||||
if (!args[0][firstRender]) {
|
||||
args[0][firstRender] = true;
|
||||
|
||||
const { sections } = findInReactTree(
|
||||
ret,
|
||||
(i) => i.props?.sections,
|
||||
).props;
|
||||
const section = sections?.find((x: any) =>
|
||||
["Bunny", "Revenge", "Kettu", "Vencore", "ShiggyCord"].some(
|
||||
(mod) => x.label === mod && x.title === mod,
|
||||
),
|
||||
);
|
||||
|
||||
if (section?.settings) {
|
||||
section.settings = [...section.settings, tabs.key];
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function patchSettingsPin(tabs) {
|
||||
const patches = [];
|
||||
|
||||
let disabled = false;
|
||||
|
||||
const realPredicate = tabs.predicate || (() => true);
|
||||
tabs.predicate = () => (disabled ? false : realPredicate());
|
||||
|
||||
patchPanelUI(tabs, patches);
|
||||
patchTabsUI(tabs, patches);
|
||||
patches.push(() => (disabled = true));
|
||||
|
||||
return () => {
|
||||
for (const x of patches) {
|
||||
x();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default function patchSidebar() {
|
||||
// Check if sidebar is enabled in storage
|
||||
if (storage.sidebarEnabled === false) {
|
||||
console.log("[Commands Plugin] Sidebar disabled in settings");
|
||||
return () => {};
|
||||
}
|
||||
|
||||
console.log("[Commands Plugin] Patching sidebar...");
|
||||
|
||||
try {
|
||||
const unpatch = patchSettingsPin({
|
||||
key: "CommandsPlugin",
|
||||
icon: getAssetIDByName("SettingsIcon"),
|
||||
title: () => "Commands",
|
||||
predicate: () => storage.sidebarEnabled !== false,
|
||||
page: Settings,
|
||||
});
|
||||
|
||||
console.log("[Commands Plugin] Successfully patched sidebar");
|
||||
return unpatch;
|
||||
} catch (error) {
|
||||
console.error("[Commands Plugin] Failed to patch sidebar:", error);
|
||||
return () => {};
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue