mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-23 05:17:02 -04:00
Update To Devs and Badges Sys
This commit is contained in:
parent
e4425ca98f
commit
44109d9979
58 changed files with 295 additions and 240 deletions
|
@ -4,11 +4,11 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "VCSupport",
|
||||
description: "Wumpus Dance + Support Warning",
|
||||
authors: [Devs.thororen],
|
||||
authors: [EquicordDevs.thororen],
|
||||
});
|
||||
|
|
|
@ -35,4 +35,4 @@ export function Timer({ time }: Readonly<{ time: number; }>) {
|
|||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,4 +23,4 @@ export function TimerIcon({ height = 16, width = 16, className }: Readonly<{
|
|||
C425,186.896,387.944,124.958,332.229,90.04z M355,272.5H212.5V130h30v112.5H355V272.5z"/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { FluxDispatcher, GuildStore, UserStore } from "@webpack/common";
|
||||
import { PassiveUpdateState, VoiceState } from "@webpack/types";
|
||||
|
@ -101,7 +101,7 @@ let runOneTime = true;
|
|||
export default definePlugin({
|
||||
name: "AllCallTimers",
|
||||
description: "Add call timer to all users in a server voice channel.",
|
||||
authors: [Devs.MaxHerbold, Devs.D3SOX],
|
||||
authors: [EquicordDevs.MaxHerbold, Devs.D3SOX],
|
||||
|
||||
settings,
|
||||
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
|
||||
export function TimerText({ text, className }: Readonly<{ text: string; className: string; }>) {
|
||||
return <div className={`timeCounter ${className}`} style={{
|
||||
marginTop: -6, // this margin value doesn't change the default size of the user container
|
||||
marginTop: -6, // this margin value doesn't change the default size of the user container
|
||||
fontWeight: "bold",
|
||||
fontFamily: "monospace",
|
||||
fontSize: 11, // good size that doesn't touch username
|
||||
fontSize: 11, // good size that doesn't touch username
|
||||
position: "relative",
|
||||
}}>{text}</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { addPreSendListener, MessageExtra, removePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { isNonNullish } from "@utils/guards";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Alerts, GuildStore, PermissionsBits, PermissionStore } from "@webpack/common";
|
||||
|
@ -17,7 +17,7 @@ import { AllowedMentions, AllowedMentionsBar, AllowedMentionsProps, AllowedMenti
|
|||
|
||||
export default definePlugin({
|
||||
name: "AllowedMentions",
|
||||
authors: [Devs.arHSM, Devs.amia],
|
||||
authors: [EquicordDevs.arHSM, Devs.amia],
|
||||
description: "Fine grained control over whom to ping when sending or editing a message.",
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
settings: definePluginSettings({
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Clipboard, Menu, showToast, Toasts } from "@webpack/common";
|
||||
|
||||
|
@ -54,7 +54,7 @@ function getEmojiFormattedString(target: Target): string {
|
|||
export default definePlugin({
|
||||
name: "CopyEmojiAsString",
|
||||
description: "Add's button to copy emoji as formatted string!",
|
||||
authors: [Devs.HAPPY_ENDERMAN, Devs.VISHNYA_NET_CHERESHNYA],
|
||||
authors: [EquicordDevs.HAPPY_ENDERMAN, EquicordDevs.VISHNYA_NET_CHERESHNYA],
|
||||
contextMenus: {
|
||||
"expression-picker"(children, { target }: { target: Target; }) {
|
||||
if (target.dataset.type !== "emoji") return;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Clipboard, Menu } from "@webpack/common";
|
||||
import type { Channel, User } from "discord-types/general";
|
||||
|
@ -45,7 +45,7 @@ const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: U
|
|||
|
||||
export default definePlugin({
|
||||
name: "CopyUserMention",
|
||||
authors: [Devs.Cortex, Devs.castdrian],
|
||||
authors: [EquicordDevs.Cortex, Devs.castdrian],
|
||||
description: "Adds a button to copy user's mention on the user context menu, works best with ValidUser.",
|
||||
contextMenus: {
|
||||
"user-context": UserContextMenuPatch
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import { Link } from "@components/Link";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { localStorage } from "@utils/localStorage";
|
||||
import { closeAllModals, openModal } from "@utils/modal";
|
||||
import definePlugin from "@utils/types";
|
||||
|
@ -51,7 +51,7 @@ function removeAppIcon() {
|
|||
export default definePlugin({
|
||||
name: "CustomAppIcons",
|
||||
description: "Add/upload custom (In-)App Icons.",
|
||||
authors: [Devs.HAPPY_ENDERMAN, Devs.SerStars],
|
||||
authors: [EquicordDevs.HAPPY_ENDERMAN, EquicordDevs.SerStars],
|
||||
patches: [
|
||||
{
|
||||
find: ".PremiumUpsellTypes.APP_ICON_UPSELL",
|
||||
|
@ -99,12 +99,12 @@ export default definePlugin({
|
|||
<><Forms.FormTitle>
|
||||
<Forms.FormTitle>How to use?</Forms.FormTitle>
|
||||
</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
<Forms.FormText>Go to <Link href="/settings/appearance" onClick={e => { e.preventDefault(); closeAllModals(); FluxDispatcher.dispatch({ type: "USER_SETTINGS_MODAL_SET_SECTION", section: "Appearance" }); }}>Appearance Settings</Link> tab.</Forms.FormText>
|
||||
<Forms.FormText>Scroll down to "In-app Icons" and click on "Preview App Icon".</Forms.FormText>
|
||||
<Forms.FormText>And upload your own custom icon!</Forms.FormText>
|
||||
<Forms.FormText>You can only use links when you are uploading your Custom Icon.</Forms.FormText>
|
||||
</Forms.FormText></>
|
||||
<Forms.FormText>
|
||||
<Forms.FormText>Go to <Link href="/settings/appearance" onClick={e => { e.preventDefault(); closeAllModals(); FluxDispatcher.dispatch({ type: "USER_SETTINGS_MODAL_SET_SECTION", section: "Appearance" }); }}>Appearance Settings</Link> tab.</Forms.FormText>
|
||||
<Forms.FormText>Scroll down to "In-app Icons" and click on "Preview App Icon".</Forms.FormText>
|
||||
<Forms.FormText>And upload your own custom icon!</Forms.FormText>
|
||||
<Forms.FormText>You can only use links when you are uploading your Custom Icon.</Forms.FormText>
|
||||
</Forms.FormText></>
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||
import { Forms, Menu, TextInput, useState } from "@webpack/common";
|
||||
|
||||
|
@ -89,7 +89,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "CustomScreenShare",
|
||||
description: "Stream any resolution and any FPS!",
|
||||
authors: [Devs.KawaiianPizza],
|
||||
authors: [EquicordDevs.KawaiianPizza],
|
||||
settingsAboutComponent: () => (
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle tag="h3">Usage</Forms.FormTitle>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
import styles from "./style.css?managed";
|
||||
|
@ -54,7 +54,7 @@ migratePluginSettings("DoNotLeak", "Do Not Leak!");
|
|||
export default definePlugin({
|
||||
name: "DoNotLeak",
|
||||
description: "Hide all message contents and attachments when you're streaming or sharing your screen.",
|
||||
authors: [Devs.Perny],
|
||||
authors: [EquicordDevs.Perny],
|
||||
settings,
|
||||
start() {
|
||||
document.addEventListener("keyup", keyUpHandler);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Alerts, Menu } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
@ -56,7 +56,7 @@ async function verify(link) {
|
|||
export default definePlugin({
|
||||
name: "DoubleCounterBypass",
|
||||
description: "Bypass Double Counter verifications easily.",
|
||||
authors: [Devs.nyx],
|
||||
authors: [EquicordDevs.nyx],
|
||||
|
||||
contextMenus: {
|
||||
"message": patchMessageContextMenu,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu } from "@webpack/common";
|
||||
import type { Guild } from "discord-types/general";
|
||||
|
@ -26,9 +26,9 @@ export default definePlugin({
|
|||
name: "emojiDumper",
|
||||
description: "Context menu to dump and download a server's emojis.",
|
||||
authors: [
|
||||
Devs.Cortex,
|
||||
EquicordDevs.Cortex,
|
||||
Devs.Samwich,
|
||||
Devs.Woosh,
|
||||
EquicordDevs.Woosh,
|
||||
],
|
||||
start() {
|
||||
addContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
// Import required modules and components
|
||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
// Importing the style managed fixes on and off switch
|
||||
|
@ -81,7 +81,7 @@ migratePluginSettings("EquicordCSS", "EquicordBuiltIn");
|
|||
export default definePlugin({
|
||||
name: "EquicordCSS",
|
||||
description: "CSS for Equicord users. You will need to look at the settings.",
|
||||
authors: [Devs.FoxStorm1, Devs.thororen],
|
||||
authors: [EquicordDevs.FoxStorm1, EquicordDevs.thororen],
|
||||
dependencies: ["ThemeAttributes"],
|
||||
settings,
|
||||
start() {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
import "./styles.css";
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Clipboard, Toasts } from "@webpack/common";
|
||||
|
||||
|
@ -57,7 +57,7 @@ function getUsernames(contacts: ContactsList[], type: number): string[] {
|
|||
export default definePlugin({
|
||||
name: "ExportContacts",
|
||||
description: "Export a list of friends to your clipboard. Adds a new button to the menu bar for the friends tab.",
|
||||
authors: [Devs.dat_insanity],
|
||||
authors: [EquicordDevs.dat_insanity],
|
||||
patches: [
|
||||
{
|
||||
find: "fetchRelationships(){",
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Text, Tooltip } from "@webpack/common";
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
interface BuilderButtonProps {
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
selectedStyle?: CSSProperties;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export function BuilderButton({ label, tooltip, selectedStyle, onClick }: BuilderButtonProps) {
|
||||
return (
|
||||
<Tooltip text={tooltip} shouldShow={!!tooltip}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<div style={{ width: "60px" }}>
|
||||
<div
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
style={{
|
||||
...selectedStyle || { border: "2px dashed var(--header-secondary)" },
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
display: "grid",
|
||||
height: "60px",
|
||||
placeItems: "center"
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
{!selectedStyle && (
|
||||
<svg
|
||||
fill="var(--header-secondary)"
|
||||
width="40%"
|
||||
height="40%"
|
||||
viewBox="0 0 144 144"
|
||||
>
|
||||
<path d="M144 64H80V0H64v64H0v16h64v64h16V80h64Z" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
{!!label && (
|
||||
<Text
|
||||
color="header-secondary"
|
||||
variant="text-xs/normal"
|
||||
tag="div"
|
||||
style={{ textAlign: "center" }}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { closeModal, ModalCloseButton, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { Button, Flex, Text, useRef, useState } from "@webpack/common";
|
||||
|
||||
import type { ColorPicker } from "../types";
|
||||
|
||||
interface ColorPickerModalProps {
|
||||
modalProps: ModalProps;
|
||||
ColorPicker: ColorPicker;
|
||||
onClose: () => void;
|
||||
onSubmit: (v: number) => void;
|
||||
initialColor: number;
|
||||
suggestedColors: string[];
|
||||
}
|
||||
|
||||
export function ColorPickerModal({ modalProps, ColorPicker, onClose, onSubmit, initialColor = 0, suggestedColors = [] }: ColorPickerModalProps) {
|
||||
const [color, setColor] = useState(initialColor);
|
||||
const [pos, setPos] = useState<[number, number]>([-1, -1]);
|
||||
const header = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: pos[0] === -1 || pos[1] === -1 ? "revert" : "fixed",
|
||||
left: `clamp(0px, ${pos[0]}px, calc(100vw - ${header.current?.getBoundingClientRect().width ?? 0}px))`,
|
||||
top: `clamp(22px, ${pos[1]}px, calc(100vh - ${header.current?.getBoundingClientRect().height ?? 0}px))`
|
||||
}}
|
||||
>
|
||||
<ModalRoot {...modalProps} size={ModalSize.DYNAMIC}>
|
||||
<style>{":has(>:not([class*=hidden__]) [class*=customColorPicker__])>[class*=backdrop__]{display:none!important}[class*=root_] [class*=customColorPicker__]{border:none!important;box-shadow:none!important}"}</style>
|
||||
<div
|
||||
ref={header}
|
||||
style={{ cursor: "move" }}
|
||||
onMouseDown={e => {
|
||||
const ref = header.current;
|
||||
if (ref === null) return;
|
||||
const rect = ref.getBoundingClientRect();
|
||||
const offsetX = e.pageX - rect.left;
|
||||
const offsetY = e.pageY - rect.top;
|
||||
const onDrag = (e: MouseEvent) => setPos([e.pageX - offsetX, e.pageY - offsetY]);
|
||||
document.addEventListener("mousemove", onDrag);
|
||||
document.addEventListener("mouseup",
|
||||
() => { document.removeEventListener("mousemove", onDrag); },
|
||||
{ once: true }
|
||||
);
|
||||
}}
|
||||
>
|
||||
<ModalHeader justify={Flex.Justify.BETWEEN}>
|
||||
<Text color="header-primary" variant="heading-lg/semibold" tag="h1">
|
||||
Color Picker
|
||||
</Text>
|
||||
<div onMouseDown={e => e.stopPropagation()}>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
</div>
|
||||
</ModalHeader>
|
||||
</div>
|
||||
<ColorPicker
|
||||
value={color}
|
||||
showEyeDropper={true}
|
||||
suggestedColors={suggestedColors}
|
||||
onChange={(e: number) => setColor(e)}
|
||||
/>
|
||||
<ModalFooter>
|
||||
<Button onClick={() => onSubmit(color)}>
|
||||
Apply
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalRoot>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function openColorPickerModal(
|
||||
ColorPicker: ColorPicker,
|
||||
onSubmit: (v: number) => void,
|
||||
initialColor: number = 0,
|
||||
suggestedColors: string[] = []
|
||||
) {
|
||||
const key = openModal(modalProps =>
|
||||
<ColorPickerModal
|
||||
modalProps={modalProps}
|
||||
ColorPicker={ColorPicker}
|
||||
onClose={() => closeModal(key)}
|
||||
onSubmit={onSubmit}
|
||||
initialColor={initialColor}
|
||||
suggestedColors={suggestedColors}
|
||||
/>
|
||||
);
|
||||
return key;
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { Button, Flex, showToast, Text, useState } from "@webpack/common";
|
||||
|
||||
import type { ProfileEffect } from "../types";
|
||||
|
||||
interface ProfileEffectModalProps {
|
||||
modalProps: ModalProps;
|
||||
onClose: () => void;
|
||||
onSubmit: (v: ProfileEffect) => void;
|
||||
classNames: { [k: string]: string; };
|
||||
profileEffects: ProfileEffect[];
|
||||
initialEffectID?: string;
|
||||
}
|
||||
|
||||
export function ProfileEffectModal({ modalProps, onClose, onSubmit, profileEffects, classNames = {}, initialEffectID }: ProfileEffectModalProps) {
|
||||
const [selected, setSelected] = useState(initialEffectID ? profileEffects.findIndex(e => e.id === initialEffectID) : -1);
|
||||
|
||||
return (
|
||||
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
|
||||
<ModalHeader justify={Flex.Justify.BETWEEN}>
|
||||
<Text color="header-primary" variant="heading-lg/semibold" tag="h1">
|
||||
Add Profile Effect
|
||||
</Text>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
</ModalHeader>
|
||||
<ModalContent
|
||||
paddingFix={false}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "12px",
|
||||
justifyContent: "center",
|
||||
padding: "16px 8px 16px 16px"
|
||||
}}
|
||||
>
|
||||
{profileEffects.map((e, i) => (
|
||||
<div
|
||||
className={classNames.effectGridItem + (i === selected ? " " + classNames.selected : "")}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
style={{ width: "80px", height: "80px" }}
|
||||
onClick={() => setSelected(i)}
|
||||
>
|
||||
<img
|
||||
className={classNames.presetEffectBackground}
|
||||
src="/assets/f328a6f8209d4f1f5022.png"
|
||||
alt={e.accessibilityLabel}
|
||||
/>
|
||||
<img
|
||||
className={classNames.presetEffectImg}
|
||||
src={e.thumbnailPreviewSrc}
|
||||
alt={e.title}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</ModalContent>
|
||||
<ModalFooter
|
||||
justify={Flex.Justify.BETWEEN}
|
||||
direction={Flex.Direction.HORIZONTAL}
|
||||
align={Flex.Align.CENTER}
|
||||
>
|
||||
<Text color="header-primary" variant="heading-lg/semibold" tag="h1">
|
||||
{selected === -1 ? "" : profileEffects[selected].title}
|
||||
</Text>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (selected !== -1)
|
||||
onSubmit(profileEffects[selected]);
|
||||
else
|
||||
showToast("No effect selected!");
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export function openProfileEffectModal(
|
||||
onSubmit: (v: ProfileEffect) => void,
|
||||
profileEffects: ProfileEffect[],
|
||||
classNames: { [k: string]: string; } = {},
|
||||
initialEffectID?: string
|
||||
) {
|
||||
const key = openModal(modalProps =>
|
||||
<ProfileEffectModal
|
||||
modalProps={modalProps}
|
||||
onClose={() => closeModal(key)}
|
||||
onSubmit={onSubmit}
|
||||
profileEffects={profileEffects}
|
||||
classNames={classNames}
|
||||
initialEffectID={initialEffectID}
|
||||
/>
|
||||
);
|
||||
return key;
|
||||
}
|
574
src/equicordplugins/fakeProfileThemes/index.tsx
Normal file
574
src/equicordplugins/fakeProfileThemes/index.tsx
Normal file
|
@ -0,0 +1,574 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { closeModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, FluxDispatcher, Forms, RestAPI, showToast, Switch, Toasts, useEffect, useRef, UserStore, useState } from "@webpack/common";
|
||||
|
||||
import { BuilderButton } from "./components/BuilderButton";
|
||||
import { openColorPickerModal } from "./components/ColorPickerModal";
|
||||
import { openProfileEffectModal } from "./components/ProfileEffectModal";
|
||||
import type { ColorPicker, CustomizationSection, ProfileEffect, RGBColor, UserProfile } from "./types";
|
||||
|
||||
let CustomizationSection: CustomizationSection = () => null;
|
||||
let ColorPicker: ColorPicker = () => null;
|
||||
let getPaletteForAvatar = (v: string) => Promise.resolve<RGBColor[]>([]);
|
||||
let getComplimentaryPaletteForColor = (v: RGBColor): RGBColor[] => [];
|
||||
const profileEffectModalClassNames: { [k: string]: string; } = {};
|
||||
let [primaryColor, setPrimaryColor] = [-1, (v: number) => { }];
|
||||
let [accentColor, setAccentColor] = [-1, (v: number) => { }];
|
||||
let [effect, setEffect]: [ProfileEffect | null, (v: ProfileEffect | null) => void] = [null, () => { }];
|
||||
let [preview, setPreview] = [true, (v: boolean) => { }];
|
||||
|
||||
/**
|
||||
* Builds a profile theme color string in the legacy format, [#primary,#accent] where
|
||||
* primary and accent are base-16 24-bit colors, with each code point offset by +0xE0000
|
||||
* @param primary The base-10 24-bit primary color to be encoded
|
||||
* @param accent The base-10 24-bit accent color to be encoded
|
||||
* @returns The legacy encoded profile theme color string
|
||||
*/
|
||||
function encodeColorsLegacy(primary: number, accent: number) {
|
||||
return String.fromCodePoint(...[...`[#${primary.toString(16)},#${accent.toString(16)}]`]
|
||||
.map(c => c.codePointAt(0)! + 0xE0000));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts profile theme colors from given legacy-format string
|
||||
* @param str The legacy-format string to extract profile theme colors from
|
||||
* @returns The profile theme colors. Colors will be -1 if not found.
|
||||
*/
|
||||
function decodeColorsLegacy(str: string): [number, number] {
|
||||
const colors = str.matchAll(/(?<=#)[\dA-Fa-f]{1,6}/g);
|
||||
return [parseInt(colors.next().value?.[0], 16) || -1, parseInt(colors.next().value?.[0], 16) || -1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given base-10 24-bit color to a base-4096 string with each code point offset by +0xE0000
|
||||
* @param color The base-10 24-bit color to be converted
|
||||
* @returns The converted base-4096 string with +0xE0000 offset
|
||||
*/
|
||||
function encodeColor(color: number) {
|
||||
if (color === 0) return "\u{e0000}";
|
||||
let str = "";
|
||||
for (; color > 0; color = Math.trunc(color / 4096))
|
||||
str = String.fromCodePoint(color % 4096 + 0xE0000) + str;
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given no-offset base-4096 string to a base-10 24-bit color
|
||||
* @param str The no-offset base-4096 string to be converted
|
||||
* @returns The converted base-10 24-bit color
|
||||
* Will be -1 if the given string is empty and -2 if greater than the maximum 24-bit color, 16,777,215
|
||||
*/
|
||||
function decodeColor(str: string) {
|
||||
if (str === "") return -1;
|
||||
let color = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (color > 16_777_215) return -2;
|
||||
color += str.codePointAt(i)! * 4096 ** (str.length - 1 - i);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given base-10 profile effect ID to a base-4096 string with each code point offset by +0xE0000
|
||||
* @param id The base-10 profile effect ID to be converted
|
||||
* @returns The converted base-4096 string with +0xE0000 offset
|
||||
*/
|
||||
function encodeEffect(id: bigint) {
|
||||
if (id === 0n) return "\u{e0000}";
|
||||
let str = "";
|
||||
for (; id > 0n; id /= 4096n)
|
||||
str = String.fromCodePoint(Number(id % 4096n) + 0xE0000) + str;
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given no-offset base-4096 string to a base-10 profile effect ID
|
||||
* @param str The no-offset base-4096 string to be converted
|
||||
* @returns The converted base-10 profile effect ID
|
||||
* Will be -1n if the given string is empty and -2n if greater than the maximum profile effect ID, 1.2 quintillion
|
||||
*/
|
||||
function decodeEffect(str: string) {
|
||||
if (str === "") return -1n;
|
||||
let id = 0n;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (id > 1_200_000_000_000_000_000n) return -2n;
|
||||
id += BigInt(str.codePointAt(i)!) * 4096n ** BigInt(str.length - 1 - i);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a FPTE string containing the given primary / accent colors and effect ID. If the FPTE Builder is NOT set to
|
||||
* backwards compatibility mode, the primary and accent colors will be converted to base-4096 before they are encoded.
|
||||
* @param primary The primary profile theme color. Must be -1 if unset.
|
||||
* @param accent The accent profile theme color. Must be -1 if unset.
|
||||
* @param effect The profile effect ID. Must be empty if unset.
|
||||
* @param legacy Whether the primary and accent colors should be legacy encoded
|
||||
* @returns The built FPTE string. Will be empty if the given colors and effect are all unset.
|
||||
*/
|
||||
function buildFPTE(primary: number, accent: number, effect: string, legacy: boolean) {
|
||||
const DELIM = "\u200b"; // The FPTE delimiter (zero-width space)
|
||||
|
||||
let fpte = ""; // The FPTE string to be returned
|
||||
|
||||
// If the FPTE Builder is set to backwards compatibility mode,
|
||||
// the primary and accent colors, if set, will be legacy encoded.
|
||||
if (legacy) {
|
||||
// Legacy FPTE strings must include both the primary and accent colors even if they are the same.
|
||||
|
||||
if (primary !== -1) {
|
||||
// If both the primary and accent colors are set, they will be legacy encoded and added to the
|
||||
// string; otherwise, if the accent color is unset, the primary color will be used in its place.
|
||||
if (accent !== -1)
|
||||
fpte = encodeColorsLegacy(primary, accent);
|
||||
else
|
||||
fpte = encodeColorsLegacy(primary, primary);
|
||||
|
||||
// If the effect ID is set, it will be encoded and added to the string prefixed by one delimiter.
|
||||
if (effect !== "")
|
||||
fpte += DELIM + encodeEffect(BigInt(effect));
|
||||
|
||||
return fpte;
|
||||
}
|
||||
|
||||
// Since the primary color is unset, the accent color, if set, will be used in its place.
|
||||
if (accent !== -1) {
|
||||
fpte = encodeColorsLegacy(accent, accent);
|
||||
|
||||
// If the effect ID is set, it will be encoded and added to the string prefixed by one delimiter.
|
||||
if (effect !== "")
|
||||
fpte += DELIM + encodeEffect(BigInt(effect));
|
||||
|
||||
return fpte;
|
||||
}
|
||||
}
|
||||
// If the primary color is set, it will be encoded and added to the string.
|
||||
else if (primary !== -1) {
|
||||
fpte = encodeColor(primary);
|
||||
|
||||
// If the accent color is set and different from the primary color, it
|
||||
// will be encoded and added to the string prefixed by one delimiter.
|
||||
if (accent !== -1 && primary !== accent) {
|
||||
fpte += DELIM + encodeColor(accent);
|
||||
|
||||
// If the effect ID is set, it will be encoded and added to the string prefixed by one delimiter.
|
||||
if (effect !== "")
|
||||
fpte += DELIM + encodeEffect(BigInt(effect));
|
||||
|
||||
return fpte;
|
||||
}
|
||||
}
|
||||
// If only the accent color is set, it will be encoded and added to the string.
|
||||
else if (accent !== -1)
|
||||
fpte = encodeColor(accent);
|
||||
|
||||
// Since either the primary / accent colors are the same, both are unset, or just one is set, only one color will be added
|
||||
// to the string; therefore, the effect ID, if set, will be encoded and added to the string prefixed by two delimiters.
|
||||
if (effect !== "")
|
||||
fpte += DELIM + DELIM + encodeEffect(BigInt(effect));
|
||||
|
||||
return fpte;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the delimiter-separated values of the first FPTE string found in the given string
|
||||
* @param str The string to be searched for a FPTE string
|
||||
* @returns An array of the extracted FPTE string's values. Values will be empty if not found.
|
||||
*/
|
||||
function extractFPTE(str: string) {
|
||||
const fpte: [string, string, string] = ["", "", ""]; // The array containing extracted FPTE values
|
||||
let i = 0; // The current index of fpte getting extracted
|
||||
|
||||
for (const char of str) {
|
||||
const cp = char.codePointAt(0)!; // The current character's code point
|
||||
|
||||
// If the current character is a delimiter, then the current index of fpte has been completed.
|
||||
if (cp === 0x200B) {
|
||||
// If the current index of fpte is the last, then the extraction is done.
|
||||
if (i >= 2) break;
|
||||
i++; // Start extracting the next index of fpte
|
||||
}
|
||||
// If the current character is not a delimiter but a valid FPTE
|
||||
// character, it will be added to the current index of fpte.
|
||||
else if (cp >= 0xE0000 && cp <= 0xE0FFF)
|
||||
fpte[i] += String.fromCodePoint(cp - 0xE0000);
|
||||
// If an FPTE string has been found and its end has been reached, then the extraction is done.
|
||||
else if (i > 0 || fpte[0] !== "") break;
|
||||
}
|
||||
|
||||
return fpte;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given RGB color to a hexadecimal string
|
||||
* @param rgb The RGB color to be converted
|
||||
* @returns The converted hexadecimal string
|
||||
* @example
|
||||
* // returns #ff0000
|
||||
* RGBtoHex([255, 0, 0])
|
||||
*/
|
||||
function RGBtoHex(rgb: RGBColor) {
|
||||
return "#" + ((rgb[0] << 16) + (rgb[1] << 8) + rgb[2]).toString(16).padStart(6, "0");
|
||||
}
|
||||
|
||||
function getSuggestedColors(callback: (v: string[]) => void) {
|
||||
const user = UserStore.getCurrentUser();
|
||||
getPaletteForAvatar(`https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.webp?size=80`)
|
||||
.then(avatarColors => {
|
||||
callback([
|
||||
...avatarColors.slice(0, 2),
|
||||
...getComplimentaryPaletteForColor(avatarColors[0]).slice(0, 3)
|
||||
].map(e => RGBtoHex(e)));
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
showToast("Unable to retrieve suggested colors.", Toasts.Type.FAILURE);
|
||||
callback([]);
|
||||
});
|
||||
}
|
||||
|
||||
function fetchProfileEffects(callback: (v: ProfileEffect[]) => void) {
|
||||
RestAPI.get({ url: "/user-profile-effects" })
|
||||
.then(res => callback(res.body.profile_effect_configs))
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
showToast("Unable to retrieve the list of profile effects.", Toasts.Type.FAILURE);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUserThemeColors(user: UserProfile, primary: number, accent: number) {
|
||||
if (primary > -1) {
|
||||
user.themeColors = [primary, accent > -1 ? accent : primary];
|
||||
user.premiumType = 2;
|
||||
} else if (accent > -1) {
|
||||
user.themeColors = [accent, accent];
|
||||
user.premiumType = 2;
|
||||
}
|
||||
}
|
||||
|
||||
function updateUserEffectId(user: UserProfile, id: bigint) {
|
||||
if (id > -1n) {
|
||||
user.profileEffectId = id.toString();
|
||||
user.premiumType = 2;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePreview() {
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SUBMIT_SUCCESS" });
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
prioritizeNitro: {
|
||||
description: "Source to use if profile theme colors / effects are set by both Nitro and About Me",
|
||||
type: OptionType.SELECT,
|
||||
options: [
|
||||
{ label: "Nitro", value: true },
|
||||
{ label: "About Me", value: false, default: true },
|
||||
]
|
||||
},
|
||||
hideBuilder: {
|
||||
description: "Hide the FPTE Builder in the profiles settings page",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "FakeProfileThemes",
|
||||
description: "Allows profile theming and the usage of profile effects by hiding the colors and effect ID in your About Me using invisible, zero-width characters",
|
||||
authors: [EquicordDevs.ryan],
|
||||
patches: [
|
||||
{
|
||||
find: '"UserProfileStore"',
|
||||
replacement: {
|
||||
match: /(?<=getUserProfile\(\i\){return )\i\[\i](?=})/,
|
||||
replace: "$self.decodeUserBioFPTEHook($&)"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: '"DefaultCustomizationSections"',
|
||||
replacement: {
|
||||
match: /\.sectionsContainer,children:\[/,
|
||||
replace: "$&$self.addFPTEBuilder(),"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".customizationSectionBackground",
|
||||
replacement: {
|
||||
match: /default:function\(\){return (\i)}.*?;/,
|
||||
replace: "$&$self.CustomizationSection=$1;"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "CustomColorPicker:function(){",
|
||||
replacement: {
|
||||
match: /CustomColorPicker:function\(\){return (\i)}.*? \1=(?=[^=])/,
|
||||
replace: "$&$self.ColorPicker="
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "getPaletteForAvatar:function(){",
|
||||
replacement: {
|
||||
match: /getPaletteForAvatar:function\(\){return (\i)}.*? \1=(?=[^=])/,
|
||||
replace: "$&$self.getPaletteForAvatar="
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "getComplimentaryPaletteForColor:function(){",
|
||||
replacement: {
|
||||
match: /getComplimentaryPaletteForColor:function\(\){return (\i)}.*?;/,
|
||||
replace: "$&$self.getComplimentaryPaletteForColor=$1;"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: 'effectGridItem:"',
|
||||
noWarn: true,
|
||||
replacement: {
|
||||
match: /(\i):"(.+?)"/g,
|
||||
replace: (m, k, v) => { profileEffectModalClassNames[k] = v; return m; }
|
||||
}
|
||||
},
|
||||
{
|
||||
find: '"ProfileCustomizationPreview"',
|
||||
replacement: {
|
||||
match: /let{(?=(?:[^}]+,)?pendingThemeColors:)(?=(?:[^}]+,)?pendingProfileEffectId:)[^}]+}=(\i)[,;]/,
|
||||
replace: "$self.profilePreviewHook($1);$&"
|
||||
}
|
||||
}
|
||||
],
|
||||
set CustomizationSection(c: CustomizationSection) {
|
||||
CustomizationSection = c;
|
||||
},
|
||||
set ColorPicker(c: ColorPicker) {
|
||||
ColorPicker = c;
|
||||
},
|
||||
set getPaletteForAvatar(f: (v: string) => Promise<RGBColor[]>) {
|
||||
getPaletteForAvatar = f;
|
||||
},
|
||||
set getComplimentaryPaletteForColor(f: (v: RGBColor) => RGBColor[]) {
|
||||
getComplimentaryPaletteForColor = f;
|
||||
},
|
||||
settingsAboutComponent: () => {
|
||||
return (
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle tag="h3">Usage</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
After enabling this plugin, you will see custom theme colors and effects in the profiles of other people using this plugin.
|
||||
<div className={Margins.top8}>
|
||||
<b>To set your own profile theme colors and effect:</b>
|
||||
</div>
|
||||
<ol
|
||||
className={Margins.bottom8}
|
||||
style={{ listStyle: "decimal", paddingLeft: "40px" }}
|
||||
>
|
||||
<li>Go to your profile settings</li>
|
||||
<li>Use the FPTE Builder to choose your profile theme colors and effect</li>
|
||||
<li>Click the "Copy FPTE" button</li>
|
||||
<li>Paste the invisible text anywhere in your About Me</li>
|
||||
</ol>
|
||||
</Forms.FormText>
|
||||
</Forms.FormSection>
|
||||
);
|
||||
},
|
||||
settings,
|
||||
decodeUserBioFPTEHook(user: UserProfile | undefined) {
|
||||
if (user === undefined) return user;
|
||||
|
||||
if (settings.store.prioritizeNitro) {
|
||||
if (user.themeColors !== undefined) {
|
||||
if (user.profileEffectId === undefined) {
|
||||
const fpte = extractFPTE(user.bio);
|
||||
if (decodeColor(fpte[0]) === -2)
|
||||
updateUserEffectId(user, decodeEffect(fpte[1]));
|
||||
else
|
||||
updateUserEffectId(user, decodeEffect(fpte[2]));
|
||||
}
|
||||
return user;
|
||||
} else if (user.profileEffectId !== undefined) {
|
||||
const fpte = extractFPTE(user.bio);
|
||||
const primaryColor = decodeColor(fpte[0]);
|
||||
if (primaryColor === -2)
|
||||
updateUserThemeColors(user, ...decodeColorsLegacy(fpte[0]));
|
||||
else
|
||||
updateUserThemeColors(user, primaryColor, decodeColor(fpte[1]));
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
const fpte = extractFPTE(user.bio);
|
||||
const primaryColor = decodeColor(fpte[0]);
|
||||
if (primaryColor === -2) {
|
||||
updateUserThemeColors(user, ...decodeColorsLegacy(fpte[0]));
|
||||
updateUserEffectId(user, decodeEffect(fpte[1]));
|
||||
} else {
|
||||
updateUserThemeColors(user, primaryColor, decodeColor(fpte[1]));
|
||||
updateUserEffectId(user, decodeEffect(fpte[2]));
|
||||
}
|
||||
|
||||
return user;
|
||||
},
|
||||
profilePreviewHook(props: any) {
|
||||
if (preview) {
|
||||
if (primaryColor !== -1) {
|
||||
props.pendingThemeColors = [primaryColor, accentColor === -1 ? primaryColor : accentColor];
|
||||
props.canUsePremiumCustomization = true;
|
||||
} else if (accentColor !== -1) {
|
||||
props.pendingThemeColors = [accentColor, accentColor];
|
||||
props.canUsePremiumCustomization = true;
|
||||
}
|
||||
if (effect) {
|
||||
props.pendingProfileEffectId = effect.id;
|
||||
props.canUsePremiumCustomization = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
addFPTEBuilder() {
|
||||
if (settings.store.hideBuilder) return null;
|
||||
|
||||
[primaryColor, setPrimaryColor] = useState(-1);
|
||||
[accentColor, setAccentColor] = useState(-1);
|
||||
[effect, setEffect] = useState<ProfileEffect | null>(null);
|
||||
[preview, setPreview] = useState(true);
|
||||
const [buildLegacy, setBuildLegacy] = useState(false);
|
||||
const currModal = useRef("");
|
||||
|
||||
useEffect(() => () => closeModal(currModal.current), []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomizationSection title="FPTE Builder">
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<BuilderButton
|
||||
label="Primary"
|
||||
{...primaryColor !== -1 ? (c => ({
|
||||
tooltip: c,
|
||||
selectedStyle: { background: c }
|
||||
}))("#" + primaryColor.toString(16).padStart(6, "0")) : {}}
|
||||
onClick={() => {
|
||||
getSuggestedColors(colors => {
|
||||
closeModal(currModal.current);
|
||||
currModal.current = openColorPickerModal(
|
||||
ColorPicker,
|
||||
c => {
|
||||
setPrimaryColor(c);
|
||||
if (preview) updatePreview();
|
||||
},
|
||||
primaryColor === -1 ? parseInt(colors[0]?.slice(1), 16) || 0 : primaryColor,
|
||||
colors
|
||||
);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<BuilderButton
|
||||
label="Accent"
|
||||
{...accentColor !== -1 ? (c => ({
|
||||
tooltip: c,
|
||||
selectedStyle: { background: c }
|
||||
}))("#" + accentColor.toString(16).padStart(6, "0")) : {}}
|
||||
onClick={() => {
|
||||
getSuggestedColors(colors => {
|
||||
closeModal(currModal.current);
|
||||
currModal.current = openColorPickerModal(
|
||||
ColorPicker,
|
||||
c => {
|
||||
setAccentColor(c);
|
||||
if (preview) updatePreview();
|
||||
},
|
||||
accentColor === -1 ? parseInt(colors[1]?.slice(1), 16) || 0 : accentColor,
|
||||
colors
|
||||
);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<BuilderButton
|
||||
label="Effect"
|
||||
{...effect && {
|
||||
tooltip: effect.title,
|
||||
selectedStyle: {
|
||||
background: `top / cover url(${effect.thumbnailPreviewSrc}), top / cover url(/assets/f328a6f8209d4f1f5022.png)`
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
fetchProfileEffects(effects => {
|
||||
if (effects) {
|
||||
closeModal(currModal.current);
|
||||
currModal.current = openProfileEffectModal(
|
||||
e => {
|
||||
setEffect(e);
|
||||
if (preview) updatePreview();
|
||||
},
|
||||
effects,
|
||||
profileEffectModalClassNames,
|
||||
effect?.id
|
||||
);
|
||||
} else
|
||||
showToast("The retrieved data did not match the expected format.", Toasts.Type.FAILURE);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size={Button.Sizes.SMALL}
|
||||
onClick={() => {
|
||||
const strToCopy = buildFPTE(primaryColor, accentColor, effect?.id ?? "", buildLegacy);
|
||||
if (strToCopy === "")
|
||||
showToast("FPTE Builder is empty; nothing to copy!");
|
||||
else
|
||||
copyWithToast(strToCopy, "FPTE copied to clipboard!");
|
||||
}}
|
||||
>
|
||||
Copy FPTE
|
||||
</Button>
|
||||
<Button
|
||||
look={Button.Looks.LINK}
|
||||
color={Button.Colors.PRIMARY}
|
||||
size={Button.Sizes.SMALL}
|
||||
style={{ display: primaryColor === -1 && accentColor === -1 && !effect ? "none" : "revert" }}
|
||||
onClick={() => {
|
||||
setPrimaryColor(-1);
|
||||
setAccentColor(-1);
|
||||
setEffect(null);
|
||||
if (preview) updatePreview();
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CustomizationSection>
|
||||
<Switch
|
||||
value={preview}
|
||||
onChange={value => {
|
||||
setPreview(value);
|
||||
updatePreview();
|
||||
}}
|
||||
>
|
||||
FPTE Builder Preview
|
||||
</Switch>
|
||||
<Switch
|
||||
value={buildLegacy}
|
||||
note="Will use more characters"
|
||||
onChange={value => setBuildLegacy(value)}
|
||||
>
|
||||
Build backwards compatible FPTE
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
68
src/equicordplugins/fakeProfileThemes/types.ts
Normal file
68
src/equicordplugins/fakeProfileThemes/types.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { User } from "discord-types/general";
|
||||
import type { ComponentType, PropsWithChildren, ReactNode } from "react";
|
||||
|
||||
export interface UserProfile extends User {
|
||||
themeColors: [number, number] | undefined;
|
||||
profileEffectId: string | undefined;
|
||||
}
|
||||
|
||||
export interface ProfileEffect {
|
||||
accessibilityLabel: string;
|
||||
animationType: number;
|
||||
description: string;
|
||||
effects: {
|
||||
duartion: number;
|
||||
height: number;
|
||||
loop: boolean;
|
||||
loopDelay: number;
|
||||
position: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
src: string;
|
||||
start: number;
|
||||
width: number;
|
||||
zIndex: number;
|
||||
}[];
|
||||
id: string;
|
||||
reducedMotionSrc: string;
|
||||
sku_id: string;
|
||||
staticFrameSrc?: string;
|
||||
thumbnailPreviewSrc: string;
|
||||
title: string;
|
||||
type: number;
|
||||
}
|
||||
|
||||
export type CustomizationSection = ComponentType<PropsWithChildren<{
|
||||
title?: ReactNode;
|
||||
titleIcon?: ReactNode;
|
||||
titleId?: string;
|
||||
description?: ReactNode;
|
||||
className?: string;
|
||||
errors?: string[];
|
||||
disabled?: boolean;
|
||||
hideDivider?: boolean;
|
||||
showBorder?: boolean;
|
||||
borderType?: "limited" | "premium";
|
||||
hasBackground?: boolean;
|
||||
forcedDivider?: boolean;
|
||||
showPremiumIcon?: boolean;
|
||||
}>>;
|
||||
|
||||
export type ColorPicker = ComponentType<{
|
||||
value?: number | null;
|
||||
onChange: (v: number) => void;
|
||||
onClose?: () => void;
|
||||
suggestedColors?: string[];
|
||||
middle?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
showEyeDropper?: boolean;
|
||||
}>;
|
||||
|
||||
export type RGBColor = [number, number, number];
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { React, Tooltip } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
|
@ -111,7 +111,7 @@ const showCustom = () => Vencord.Settings.plugins.GlobalBadges.showCustom;
|
|||
export default definePlugin({
|
||||
name: "GlobalBadges",
|
||||
description: "Adds global badges from other client mods",
|
||||
authors: [Devs.HypedDomi, Devs.Wolfie],
|
||||
authors: [Devs.HypedDomi, EquicordDevs.Wolfie],
|
||||
|
||||
start: () => addBadge(Badge),
|
||||
stop: () => removeBadge(Badge),
|
||||
|
|
|
@ -11,7 +11,7 @@ import { get, set } from "@api/DataStore";
|
|||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Menu } from "@webpack/common";
|
||||
|
||||
|
@ -129,7 +129,7 @@ export const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "HideMessage",
|
||||
description: "Adds a context menu option to hide messages",
|
||||
authors: [Devs.Hanzy],
|
||||
authors: [EquicordDevs.Hanzy],
|
||||
settings,
|
||||
|
||||
contextMenus: {
|
||||
|
|
|
@ -22,7 +22,7 @@ import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
|||
import { DataStore } from "@api/index";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { classes } from "@utils/misc";
|
||||
import { openModal } from "@utils/modal";
|
||||
import definePlugin from "@utils/types";
|
||||
|
@ -71,7 +71,7 @@ function ToolBarHeader() {
|
|||
export default definePlugin({
|
||||
name: "HolyNotes",
|
||||
description: "Holy Notes allows you to save messages",
|
||||
authors: [Devs.Wolfie],
|
||||
authors: [EquicordDevs.Wolfie],
|
||||
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI"],
|
||||
|
||||
patches: [
|
||||
|
|
|
@ -8,7 +8,7 @@ import { DataStore } from "@api/index";
|
|||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import ExpandableHeader from "@components/ExpandableHeader";
|
||||
import { Heart } from "@components/Heart";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { openUserProfile } from "@utils/discord";
|
||||
import * as Modal from "@utils/modal";
|
||||
import definePlugin from "@utils/types";
|
||||
|
@ -370,7 +370,7 @@ class DataUI {
|
|||
export default definePlugin({
|
||||
name: "IRememberYou",
|
||||
description: "Locally saves everyone you've been communicating with (including servers), in case of lose",
|
||||
authors: [Devs.zoodogood],
|
||||
authors: [EquicordDevs.zoodogood],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
patches: [],
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { DataStore } from "@api/index";
|
|||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { DeleteIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, Forms, TextInput, UserStore, UserUtils, useState } from "@webpack/common";
|
||||
|
@ -90,7 +90,7 @@ const settings = definePluginSettings({
|
|||
|
||||
export default definePlugin({
|
||||
name: "KeywordNotify",
|
||||
authors: [Devs.camila314],
|
||||
authors: [EquicordDevs.camila314],
|
||||
description: "Sends a notification if a given message matches certain keywords or regexes",
|
||||
settings,
|
||||
patches: [
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { isNonNullish } from "@utils/guards";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { waitFor } from "@webpack";
|
||||
|
@ -31,7 +31,7 @@ waitFor("HiddenVisually", mod => {
|
|||
export default definePlugin({
|
||||
name: "MessageLatency",
|
||||
description: "Displays an indicator for messages that took ≥n seconds to send",
|
||||
authors: [Devs.arHSM],
|
||||
authors: [EquicordDevs.arHSM],
|
||||
settings: definePluginSettings({
|
||||
latency: {
|
||||
type: OptionType.NUMBER,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { UserStore } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
|
@ -20,7 +20,7 @@ interface ModifiedUser extends User {
|
|||
export default definePlugin({
|
||||
name: "NoNitroUpsell",
|
||||
description: "Removes ALL of Discord's nitro upsells by tricking the client into thinking you have nitro.",
|
||||
authors: [Devs.thororen],
|
||||
authors: [EquicordDevs.thororen],
|
||||
ready(user: ModifiedUser): void {
|
||||
if (!user) return;
|
||||
if ("_realPremiumType" in user) return;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
|
@ -30,7 +30,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "PlatformSpoofer",
|
||||
description: "Spoof what platform or device you're on",
|
||||
authors: [Devs.Drag],
|
||||
authors: [EquicordDevs.Drag],
|
||||
settings: settings,
|
||||
patches: [
|
||||
{
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { MessageStore, UserStore } from "@webpack/common";
|
||||
|
@ -45,7 +45,7 @@ export default definePlugin({
|
|||
name: "MessagePurge",
|
||||
description: "Purges messages from a channel",
|
||||
dependencies: ["CommandsAPI"],
|
||||
authors: [Devs.bhop, Devs.nyx],
|
||||
authors: [EquicordDevs.bhop, EquicordDevs.nyx],
|
||||
commands: [
|
||||
{
|
||||
name: "purge",
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
|
@ -31,7 +31,7 @@ function replaceQuestionMarks(content: string): string {
|
|||
export default definePlugin({
|
||||
name: "QuestionMarkReplace",
|
||||
description: "Replace all question marks with chosen string, if message only contains question marks.",
|
||||
authors: [Devs.nyx],
|
||||
authors: [EquicordDevs.nyx],
|
||||
|
||||
settings,
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { MessageStore, showToast, UserStore } from "@webpack/common";
|
||||
import { MessageJSON } from "discord-types/general";
|
||||
|
@ -43,7 +43,7 @@ export const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "ReplyPingControl",
|
||||
description: "Control whether to always or never get pinged on message replies, with a whitelist feature",
|
||||
authors: [Devs.ant0n, Devs.MrDiamond],
|
||||
authors: [Devs.ant0n, EquicordDevs.MrDiamond],
|
||||
settings,
|
||||
|
||||
patches: [{
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
*/
|
||||
|
||||
import { ApplicationCommandOptionType, findOption } from "@api/Commands";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "Search",
|
||||
authors: [Devs.JacobTm, Devs.thororen],
|
||||
authors: [Devs.JacobTm, EquicordDevs.thororen],
|
||||
description: "Searchs the web.",
|
||||
dependencies: ["CommandsAPI"],
|
||||
commands: [{
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "SearchFix",
|
||||
description: 'Fixes the annoying "We dropped the magnifying glass!" error.',
|
||||
settingsAboutComponent: () => <span style={{ color: "white" }}><i><b>This fix isn't perfect, so you may have to reload the search bar to fix issues.</b></i> Discord only allows a max offset of 5000 (this is what causes the magnifying glass error). This means that you can only see precisely 5000 messages into the past, and 5000 messages into the future (when sorting by old). This plugin just jumps to the opposite sorting method to try get around Discord's restriction, but if there is a large search result, and you try to view a message that is unobtainable with both methods of sorting, the plugin will simply show offset 0 (either newest or oldest message depending on the sorting method).</span>,
|
||||
authors: [Devs.jaxx],
|
||||
authors: [EquicordDevs.jaxx],
|
||||
patches: [
|
||||
{
|
||||
find: '"SearchStore"',
|
||||
|
|
|
@ -5032,4 +5032,4 @@ export const characters = [
|
|||
}
|
||||
}
|
||||
|
||||
];
|
||||
];
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
|
|
|
@ -91,4 +91,4 @@ export default definePlugin({
|
|||
}
|
||||
],
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { isPluginDev } from "@utils/misc";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
|
@ -46,6 +46,19 @@ function CheckBadge({ badge, author }: { badge: string; author: any; }): JSX.Ele
|
|||
))}
|
||||
</span>
|
||||
);
|
||||
case "EquicordDonor":
|
||||
return (
|
||||
<span style={{ order: settings.store.VencordDonorPosition }}>
|
||||
{badges.getEquicordDonorBadges(author.id)?.map((badge: any) => (
|
||||
<RoleIconComponent
|
||||
className={roleIconClassName}
|
||||
name={badge.description}
|
||||
size={20}
|
||||
src={badge.image}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
case "VencordContributer":
|
||||
return isPluginDev(author.id) ? (
|
||||
<span style={{ order: settings.store.VencordContributorPosition }}>
|
||||
|
@ -96,6 +109,7 @@ function ChatBadges({ author }: any) {
|
|||
return (
|
||||
<span style={{ display: "inline-flex", marginLeft: 2, verticalAlign: "top" }}>
|
||||
{settings.store.showVencordDonor && <CheckBadge badge={"VencordDonor"} author={author} />}
|
||||
{settings.store.showVencordDonor && <CheckBadge badge={"EquicordDonor"} author={author} />}
|
||||
{settings.store.showVencordContributor && <CheckBadge badge={"VencordContributer"} author={author} />}
|
||||
{settings.store.showDiscordProfile && <CheckBadge badge={"DiscordProfile"} author={author} />}
|
||||
{settings.store.showDiscordNitro && <CheckBadge badge={"DiscordNitro"} author={author} />}
|
||||
|
@ -105,7 +119,7 @@ function ChatBadges({ author }: any) {
|
|||
|
||||
export default definePlugin({
|
||||
name: "ShowBadgesInChat",
|
||||
authors: [Devs.Inbestigator, Devs.KrystalSkull],
|
||||
authors: [Devs.Inbestigator, EquicordDevs.KrystalSkull],
|
||||
description: "Shows the message author's badges beside their name in chat.",
|
||||
dependencies: ["MessageDecorationsAPI"],
|
||||
patches: [
|
||||
|
|
|
@ -23,6 +23,18 @@ const settings = definePluginSettings({
|
|||
hidden: true,
|
||||
default: 0
|
||||
},
|
||||
showEquicordDonor: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable to show Vencord donor badges in chat.",
|
||||
hidden: true,
|
||||
default: true
|
||||
},
|
||||
EquicordDonorPosition: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "The position of the Vencord Donor badges.",
|
||||
hidden: true,
|
||||
default: 1
|
||||
},
|
||||
showVencordContributor: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable to show Vencord contributor badges in chat.",
|
||||
|
@ -33,7 +45,7 @@ const settings = definePluginSettings({
|
|||
type: OptionType.NUMBER,
|
||||
description: "The position of the Vencord Contributor badge.",
|
||||
hidden: true,
|
||||
default: 1
|
||||
default: 2
|
||||
},
|
||||
showDiscordProfile: {
|
||||
type: OptionType.BOOLEAN,
|
||||
|
@ -45,7 +57,7 @@ const settings = definePluginSettings({
|
|||
type: OptionType.NUMBER,
|
||||
description: "The position of the Discord profile badges.",
|
||||
hidden: true,
|
||||
default: 2
|
||||
default: 3
|
||||
},
|
||||
showDiscordNitro: {
|
||||
type: OptionType.BOOLEAN,
|
||||
|
@ -57,7 +69,7 @@ const settings = definePluginSettings({
|
|||
type: OptionType.NUMBER,
|
||||
description: "The position of the Discord Nitro badge.",
|
||||
hidden: true,
|
||||
default: 3
|
||||
default: 4
|
||||
},
|
||||
badgeSettings: {
|
||||
type: OptionType.COMPONENT,
|
||||
|
@ -71,6 +83,7 @@ export default settings;
|
|||
const BadgeSettings = () => {
|
||||
const [images, setImages] = useState([
|
||||
{ src: "https://cdn.discordapp.com/emojis/1026533070955872337.png", shown: settings.store.showVencordDonor, title: "Vencord donor badges", key: "VencordDonor", position: settings.store.VencordDonorPosition },
|
||||
{ src: "https://i.imgur.com/KsxHlbD.png", shown: settings.store.showEquicordDonor, title: "Equicord donor badges", key: "EquicordDonor", position: settings.store.EquicordDonorPosition },
|
||||
{ src: "https://i.imgur.com/OypoHrV.png", shown: settings.store.showVencordContributor, title: "Vencord/Equicord contributor badge", key: "VencordContributer", position: settings.store.VencordContributorPosition },
|
||||
{ src: "https://cdn.discordapp.com/badge-icons/bf01d1073931f921909045f3a39fd264.png", shown: settings.store.showDiscordProfile, title: "Discord profile badges (HypeSquad, Discord Staff, Active Developer, etc.)", key: "DiscordProfile", position: settings.store.DiscordProfilePosition },
|
||||
{ src: "https://cdn.discordapp.com/badge-icons/2ba85e8026a8614b640c2837bcdfe21b.png", shown: settings.store.showDiscordNitro, title: "Nitro badge", key: "DiscordNitro", position: settings.store.DiscordNitroPosition }
|
||||
|
@ -83,6 +96,10 @@ const BadgeSettings = () => {
|
|||
settings.store.VencordDonorPosition = image.position;
|
||||
settings.store.showVencordDonor = image.shown;
|
||||
break;
|
||||
case "EquiordDonor":
|
||||
settings.store.EquicordDonorPosition = image.position;
|
||||
settings.store.showEquicordDonor = image.shown;
|
||||
break;
|
||||
case "VencordContributer":
|
||||
settings.store.VencordContributorPosition = image.position;
|
||||
settings.store.showVencordContributor = image.shown;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
|
||||
|
@ -29,10 +29,10 @@ const chatBarIcon: ChatBarButton = () => {
|
|||
export default definePlugin({
|
||||
name: "SoundBoardLogger",
|
||||
authors: [
|
||||
Devs.ImpishMoxxie,
|
||||
Devs.fres,
|
||||
EquicordDevs.Moxxie,
|
||||
EquicordDevs.fres,
|
||||
Devs.echo,
|
||||
Devs.thororen
|
||||
EquicordDevs.thororen
|
||||
],
|
||||
settings,
|
||||
patches: [
|
||||
|
|
|
@ -16,13 +16,13 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "UnlimitedAccounts",
|
||||
description: "Increases the amount of accounts you can add.",
|
||||
authors: [Devs.Balaclava, Devs.thororen],
|
||||
authors: [EquicordDevs.Balaclava, EquicordDevs.thororen],
|
||||
patches: [
|
||||
{
|
||||
find: "multiaccount_cta_tooltip_seen",
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Link } from "@components/Link";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { User } from "discord-types/general";
|
||||
|
||||
|
@ -33,7 +33,7 @@ export default definePlugin({
|
|||
|
||||
name: "UserPFP",
|
||||
description: "Allows you to use an animated avatar without Nitro",
|
||||
authors: [Devs.nexpid, Devs.thororen, Devs.FoxStorm1, Devs.coolesding],
|
||||
authors: [EquicordDevs.nexpid, EquicordDevs.thororen, EquicordDevs.FoxStorm1, EquicordDevs.coolesding],
|
||||
settings,
|
||||
settingsAboutComponent: () => (
|
||||
<>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { GuildChannelStore, Menu, React, RestAPI, UserStore } from "@webpack/common";
|
||||
|
@ -125,8 +125,8 @@ const VoiceChannelContext: NavContextMenuPatchCallback = (children, { channel }:
|
|||
export default definePlugin({
|
||||
name: "VoiceChatUtilities",
|
||||
description: "This plugin allows you to perform multiple actions on an entire channel (move, mute, disconnect, etc.) (originally by dutake)",
|
||||
authors: [Devs.Dams, Devs.D3SOX],
|
||||
authors: [EquicordDevs.Dams, Devs.D3SOX],
|
||||
contextMenus: {
|
||||
"channel-context": VoiceChannelContext
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
import "./style.css";
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "VoiceDownload",
|
||||
description: "Download voice messages.",
|
||||
authors: [Devs.puv],
|
||||
authors: [EquicordDevs.puv],
|
||||
patches: [
|
||||
{
|
||||
find: ".rippleContainer",
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { Forms, i18n, RelationshipStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
|
||||
|
@ -54,7 +54,7 @@ const { encodeStreamKey }: {
|
|||
export default definePlugin({
|
||||
name: "WhosWatching",
|
||||
description: "Lets you view what users are watching your stream by hovering over the screenshare icon",
|
||||
authors: [Devs.fres],
|
||||
authors: [EquicordDevs.fres],
|
||||
patches: [
|
||||
{
|
||||
find: ".Masks.STATUS_SCREENSHARE,width:32",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue