mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-10 15:13:02 -04:00
Merge branch 'dev' into dev2
This commit is contained in:
commit
10ecc2e251
246 changed files with 6010 additions and 3000 deletions
|
@ -33,7 +33,7 @@ import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
|||
import { StartAt } from "@utils/types";
|
||||
|
||||
import { get as dsGet } from "./api/DataStore";
|
||||
import { showNotification } from "./api/Notifications";
|
||||
import { NotificationData, showNotification } from "./api/Notifications";
|
||||
import { PlainSettings, Settings } from "./api/Settings";
|
||||
import { patches, PMLogger, startAllPlugins } from "./plugins";
|
||||
import { localStorage } from "./utils/localStorage";
|
||||
|
@ -105,6 +105,46 @@ async function syncSettings() {
|
|||
}
|
||||
}
|
||||
|
||||
let notifiedForUpdatesThisSession = false;
|
||||
|
||||
async function runUpdateCheck() {
|
||||
const notify = (data: NotificationData) => {
|
||||
if (notifiedForUpdatesThisSession) return;
|
||||
notifiedForUpdatesThisSession = true;
|
||||
|
||||
setTimeout(() => showNotification({
|
||||
permanent: true,
|
||||
noPersist: true,
|
||||
...data
|
||||
}), 10_000);
|
||||
};
|
||||
|
||||
try {
|
||||
const isOutdated = await checkForUpdates();
|
||||
if (!isOutdated) return;
|
||||
|
||||
if (Settings.autoUpdate) {
|
||||
await update();
|
||||
if (Settings.autoUpdateNotification) {
|
||||
notify({
|
||||
title: "Equicord has been updated!",
|
||||
body: "Click here to restart",
|
||||
onClick: relaunch
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
notify({
|
||||
title: "A Equicord update is available!",
|
||||
body: "Click here to view the update",
|
||||
onClick: openUpdaterModal!
|
||||
});
|
||||
} catch (err) {
|
||||
UpdateLogger.error("Failed to check for updates", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await onceReady;
|
||||
startAllPlugins(StartAt.WebpackReady);
|
||||
|
@ -112,34 +152,8 @@ async function init() {
|
|||
syncSettings();
|
||||
|
||||
if (!IS_WEB && !IS_UPDATER_DISABLED) {
|
||||
try {
|
||||
const isOutdated = await checkForUpdates();
|
||||
if (!isOutdated) return;
|
||||
|
||||
if (Settings.autoUpdate) {
|
||||
await update();
|
||||
if (Settings.updateRelaunch) return relaunch;
|
||||
if (Settings.autoUpdateNotification)
|
||||
setTimeout(() => showNotification({
|
||||
title: "Equicord has been updated!",
|
||||
body: "Click here to restart",
|
||||
permanent: true,
|
||||
noPersist: true,
|
||||
onClick: relaunch
|
||||
}), 10_000);
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => showNotification({
|
||||
title: "A Equicord update is available!",
|
||||
body: "Click here to view the update",
|
||||
permanent: true,
|
||||
noPersist: true,
|
||||
onClick: openUpdaterModal!
|
||||
}), 10_000);
|
||||
} catch (err) {
|
||||
UpdateLogger.error("Failed to check for updates", err);
|
||||
}
|
||||
runUpdateCheck();
|
||||
setInterval(runUpdateCheck, 1000 * 60 * 30); // 30 minutes
|
||||
}
|
||||
|
||||
if (IS_DEV) {
|
||||
|
@ -149,7 +163,7 @@ async function init() {
|
|||
"Webpack has finished initialising, but some patches haven't been applied yet.",
|
||||
"This might be expected since some Modules are lazy loaded, but please verify",
|
||||
"that all plugins are working as intended.",
|
||||
"You are seeing this warning because this is a Development build of Vencord.",
|
||||
"You are seeing this warning because this is a Development build of Equicord.",
|
||||
"\nThe following patches have not been applied:",
|
||||
"\n\n" + pendingPatches.map(p => `${p.plugin}: ${p.find}`).join("\n")
|
||||
);
|
||||
|
|
|
@ -21,25 +21,14 @@ import { Channel, User } from "discord-types/general/index.js";
|
|||
import { JSX } from "react";
|
||||
|
||||
interface DecoratorProps {
|
||||
activities: any[];
|
||||
channel: Channel;
|
||||
/**
|
||||
* Only for DM members
|
||||
*/
|
||||
channelName?: string;
|
||||
/**
|
||||
* Only for server members
|
||||
*/
|
||||
currentUser?: User;
|
||||
guildId?: string;
|
||||
isMobile: boolean;
|
||||
isOwner?: boolean;
|
||||
isTyping: boolean;
|
||||
selected: boolean;
|
||||
status: string;
|
||||
type: "guild" | "dm";
|
||||
user: User;
|
||||
[key: string]: any;
|
||||
/** only present when this is a DM list item */
|
||||
channel: Channel;
|
||||
/** only present when this is a guild list item */
|
||||
isOwner: boolean;
|
||||
}
|
||||
|
||||
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
|
||||
type OnlyIn = "guilds" | "dms";
|
||||
|
||||
|
@ -53,18 +42,16 @@ export function removeMemberListDecorator(identifier: string) {
|
|||
decoratorsFactories.delete(identifier);
|
||||
}
|
||||
|
||||
export function __getDecorators(props: DecoratorProps): JSX.Element {
|
||||
const isInGuild = !!(props.guildId);
|
||||
|
||||
export function __getDecorators(props: DecoratorProps, type: "guild" | "dm"): JSX.Element {
|
||||
const decorators = Array.from(
|
||||
decoratorsFactories.entries(),
|
||||
([key, { render: Decorator, onlyIn }]) => {
|
||||
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
|
||||
if ((onlyIn === "guilds" && type !== "guild") || (onlyIn === "dms" && type !== "dm"))
|
||||
return null;
|
||||
|
||||
return (
|
||||
<ErrorBoundary noop key={key} message={`Failed to render ${key} Member List Decorator`}>
|
||||
<Decorator {...props} />
|
||||
<Decorator {...props} type={type} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
|
40
src/api/NicknameIcons.tsx
Normal file
40
src/api/NicknameIcons.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export interface NicknameIconProps {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export type NicknameIconFactory = (props: NicknameIconProps) => ReactNode | Promise<ReactNode>;
|
||||
|
||||
export interface NicknameIcon {
|
||||
priority: number;
|
||||
factory: NicknameIconFactory;
|
||||
}
|
||||
|
||||
const nicknameIcons = new Map<string, NicknameIcon>();
|
||||
const logger = new Logger("NicknameIcons");
|
||||
|
||||
export function addNicknameIcon(id: string, factory: NicknameIconFactory, priority = 0) {
|
||||
return nicknameIcons.set(id, {
|
||||
priority,
|
||||
factory: ErrorBoundary.wrap(factory, { noop: true, onError: error => logger.error(`Failed to render ${id}`, error) })
|
||||
});
|
||||
}
|
||||
|
||||
export function removeNicknameIcon(id: string) {
|
||||
return nicknameIcons.delete(id);
|
||||
}
|
||||
|
||||
export function _renderIcons(props: NicknameIconProps) {
|
||||
return Array.from(nicknameIcons)
|
||||
.sort((a, b) => b[1].priority - a[1].priority)
|
||||
.map(([id, { factory: NicknameIcon }]) => <NicknameIcon key={id} {...props} />);
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import { Settings } from "@api/Settings";
|
||||
import { Queue } from "@utils/Queue";
|
||||
import { ReactDOM } from "@webpack/common";
|
||||
import { createRoot } from "@webpack/common";
|
||||
import type { ReactNode } from "react";
|
||||
import type { Root } from "react-dom/client";
|
||||
|
||||
|
@ -35,7 +35,7 @@ function getRoot() {
|
|||
const container = document.createElement("div");
|
||||
container.id = "vc-notification-container";
|
||||
document.body.append(container);
|
||||
reactRoot = ReactDOM.createRoot(container);
|
||||
reactRoot = createRoot(container);
|
||||
}
|
||||
return reactRoot;
|
||||
}
|
||||
|
|
|
@ -11,10 +11,6 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.visual-refresh .vc-notification-root {
|
||||
background-color: var(--bg-overlay-floating, var(--background-base-low));
|
||||
}
|
||||
|
||||
.vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) {
|
||||
position: absolute;
|
||||
z-index: 2147483647;
|
||||
|
|
|
@ -39,7 +39,6 @@ export interface Settings {
|
|||
themeLinks: string[];
|
||||
frameless: boolean;
|
||||
transparent: boolean;
|
||||
updateRelaunch: boolean;
|
||||
winCtrlQ: boolean;
|
||||
macosVibrancyStyle:
|
||||
| "content"
|
||||
|
@ -101,7 +100,6 @@ const DefaultSettings: Settings = {
|
|||
winCtrlQ: false,
|
||||
macosVibrancyStyle: undefined,
|
||||
disableMinSize: false,
|
||||
updateRelaunch: false,
|
||||
winNativeTitleBar: false,
|
||||
plugins: {},
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import * as $MessageDecorations from "./MessageDecorations";
|
|||
import * as $MessageEventsAPI from "./MessageEvents";
|
||||
import * as $MessagePopover from "./MessagePopover";
|
||||
import * as $MessageUpdater from "./MessageUpdater";
|
||||
import * as $NicknameIcons from "./NicknameIcons";
|
||||
import * as $Notices from "./Notices";
|
||||
import * as $Notifications from "./Notifications";
|
||||
import * as $ServerList from "./ServerList";
|
||||
|
@ -123,6 +124,11 @@ export const MessageUpdater = $MessageUpdater;
|
|||
*/
|
||||
export const UserSettings = $UserSettings;
|
||||
|
||||
/**
|
||||
* An API allowing you to add icons to the nickname, in profiles
|
||||
*/
|
||||
export const NicknameIcons = $NicknameIcons;
|
||||
|
||||
/**
|
||||
* Just used to identify if user is on Equicord as Vencord doesnt have this
|
||||
*/
|
||||
|
|
|
@ -22,7 +22,26 @@ import { ButtonProps } from "@webpack/types";
|
|||
|
||||
import { Heart } from "./Heart";
|
||||
|
||||
export default function DonateButton({
|
||||
export function VCDonateButton({
|
||||
look = Button.Looks.LINK,
|
||||
color = Button.Colors.TRANSPARENT,
|
||||
...props
|
||||
}: Partial<ButtonProps>) {
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
look={look}
|
||||
color={color}
|
||||
onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")}
|
||||
innerClassName="vc-donate-button"
|
||||
>
|
||||
<Heart />
|
||||
Donate
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export function DonateButton({
|
||||
look = Button.Looks.LINK,
|
||||
color = Button.Colors.TRANSPARENT,
|
||||
...props
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { LazyComponent, LazyComponentWrapper } from "@utils/lazyReact";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { React } from "@webpack/common";
|
||||
import type { React } from "@webpack/common";
|
||||
|
||||
import { ErrorCard } from "./ErrorCard";
|
||||
|
||||
|
@ -46,7 +46,9 @@ const NO_ERROR = {};
|
|||
// We might want to import this in a place where React isn't ready yet.
|
||||
// Thus, wrap in a LazyComponent
|
||||
const ErrorBoundary = LazyComponent(() => {
|
||||
return class ErrorBoundary extends React.PureComponent<React.PropsWithChildren<Props>> {
|
||||
// This component is used in a lot of files which end up importing other Webpack commons and causing circular imports.
|
||||
// For this reason, use a non import access here.
|
||||
return class ErrorBoundary extends Vencord.Webpack.Common.React.PureComponent<React.PropsWithChildren<Props>> {
|
||||
state = {
|
||||
error: NO_ERROR as any,
|
||||
stack: "",
|
||||
|
@ -107,9 +109,9 @@ const ErrorBoundary = LazyComponent(() => {
|
|||
}
|
||||
};
|
||||
}) as
|
||||
React.ComponentType<React.PropsWithChildren<Props>> & {
|
||||
LazyComponentWrapper<React.ComponentType<React.PropsWithChildren<Props>> & {
|
||||
wrap<T extends object = any>(Component: React.ComponentType<T>, errorBoundaryProps?: Omit<Props<T>, "wrappedProps">): React.FunctionComponent<T>;
|
||||
};
|
||||
}>;
|
||||
|
||||
ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => (
|
||||
<ErrorBoundary {...errorBoundaryProps} wrappedProps={props}>
|
||||
|
|
|
@ -179,20 +179,18 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
}
|
||||
}
|
||||
|
||||
function renderMoreUsers(_label: string, count: number) {
|
||||
const sliceCount = plugin.authors.length - count;
|
||||
const sliceStart = plugin.authors.length - sliceCount;
|
||||
const sliceEnd = sliceStart + plugin.authors.length - count;
|
||||
function renderMoreUsers(_label: string) {
|
||||
const remainingAuthors = plugin.authors.slice(6);
|
||||
|
||||
return (
|
||||
<Tooltip text={plugin.authors.slice(sliceStart, sliceEnd).map(u => u.name).join(", ")}>
|
||||
<Tooltip text={remainingAuthors.map(u => u.name).join(", ")}>
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
<div
|
||||
className={AvatarStyles.moreUsers}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
+{sliceCount}
|
||||
+{remainingAuthors.length}
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
@ -250,7 +248,6 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
count={plugin.authors.length}
|
||||
guildId={undefined}
|
||||
renderIcon={false}
|
||||
max={6}
|
||||
showDefaultAvatarsForNullUsers
|
||||
showUserPopout
|
||||
renderMoreUsers={renderMoreUsers}
|
||||
|
|
|
@ -252,6 +252,24 @@
|
|||
}
|
||||
|
||||
.visual-refresh .button-danger-background:hover {
|
||||
background-color: var(--status-danger-background) !important;
|
||||
color: var(--status-danger-text) !important;
|
||||
background-color: var(--status-danger-background) !important;
|
||||
color: var(--status-danger-text) !important;
|
||||
}
|
||||
|
||||
.visual-refresh .vc-plugins-info-card {
|
||||
background-color: var(--card-primary-bg) !important;
|
||||
border: 1px solid var(--border-subtle) !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--card-primary-bg) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.visual-refresh .vc-plugin-stats {
|
||||
background-color: var(--card-primary-bg) !important;
|
||||
border: 1px solid var(--border-subtle) !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--card-primary-bg) !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,13 @@
|
|||
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
import { debounce } from "@shared/debounce";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
||||
import { makeCodeblock } from "@utils/text";
|
||||
import { Patch, ReplaceFn } from "@utils/types";
|
||||
import { search } from "@webpack";
|
||||
import { Button, Clipboard, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common";
|
||||
import { Button, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common";
|
||||
|
||||
import { SettingsTab, wrapTab } from "./shared";
|
||||
|
||||
|
@ -378,8 +379,8 @@ function PatchHelper() {
|
|||
<>
|
||||
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
|
||||
<CodeBlock lang="js" content={code} />
|
||||
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
|
||||
<Button className={Margins.top8} onClick={() => Clipboard.copy("```ts\n" + code + "\n```")}>Copy as Codeblock</Button>
|
||||
<Button onClick={() => copyToClipboard(code)}>Copy to Clipboard</Button>
|
||||
<Button className={Margins.top8} onClick={() => copyToClipboard("```ts\n" + code + "\n```")}>Copy as Codeblock</Button>
|
||||
</>
|
||||
)}
|
||||
</SettingsTab>
|
||||
|
|
|
@ -106,8 +106,6 @@ function Updatable(props: CommonProps) {
|
|||
const [updates, setUpdates] = React.useState(changes);
|
||||
const [isChecking, setIsChecking] = React.useState(false);
|
||||
const [isUpdating, setIsUpdating] = React.useState(false);
|
||||
|
||||
const settings = useSettings(["updateRelaunch"]);
|
||||
const isOutdated = (updates?.length ?? 0) > 0;
|
||||
|
||||
return (
|
||||
|
@ -119,7 +117,6 @@ function Updatable(props: CommonProps) {
|
|||
onClick={withDispatcher(setIsUpdating, async () => {
|
||||
if (await update()) {
|
||||
setUpdates([]);
|
||||
if (settings.updateRelaunch) return relaunch();
|
||||
return await new Promise<void>(r => {
|
||||
Alerts.show({
|
||||
title: "Update Success!",
|
||||
|
@ -191,7 +188,7 @@ function Newer(props: CommonProps) {
|
|||
}
|
||||
|
||||
function Updater() {
|
||||
const settings = useSettings(["autoUpdate", "updateRelaunch", "autoUpdateNotification"]);
|
||||
const settings = useSettings(["autoUpdate", "autoUpdateNotification"]);
|
||||
|
||||
const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." });
|
||||
|
||||
|
@ -217,30 +214,12 @@ function Updater() {
|
|||
</Switch>
|
||||
<Switch
|
||||
value={settings.autoUpdateNotification}
|
||||
onChange={(v: boolean) => {
|
||||
settings.autoUpdateNotification = v;
|
||||
if (settings.updateRelaunch) {
|
||||
settings.updateRelaunch = !v;
|
||||
}
|
||||
}}
|
||||
onChange={(v: boolean) => settings.autoUpdateNotification = v}
|
||||
note="Shows a notification when Equicord automatically updates"
|
||||
disabled={!settings.autoUpdate}
|
||||
>
|
||||
Get notified when an automatic update completes
|
||||
</Switch>
|
||||
<Switch
|
||||
value={settings.updateRelaunch}
|
||||
onChange={(v: boolean) => {
|
||||
settings.updateRelaunch = v;
|
||||
if (settings.autoUpdateNotification) {
|
||||
settings.autoUpdateNotification = !v;
|
||||
}
|
||||
}}
|
||||
note="Relaunches the app after updating with no prompt"
|
||||
disabled={!settings.autoUpdate}
|
||||
>
|
||||
Automatically relaunch after updating
|
||||
</Switch>
|
||||
|
||||
<Forms.FormTitle tag="h5">Repo</Forms.FormTitle>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import "./VencordTab.css";
|
|||
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import DonateButton, { InviteButton } from "@components/DonateButton";
|
||||
import { DonateButton, InviteButton } from "@components/DonateButton";
|
||||
import { openContributorModal } from "@components/PluginSettings/ContributorModal";
|
||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||
import { gitRemote } from "@shared/vencordUserAgent";
|
||||
|
@ -82,7 +82,7 @@ function EquicordSettings() {
|
|||
(!IS_DISCORD_DESKTOP || !isWindows
|
||||
? {
|
||||
key: "frameless",
|
||||
title: "Disable the window frame",
|
||||
title: "Disable the Window Frame",
|
||||
note: "Requires a full restart",
|
||||
warning: { enabled: false },
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ function EquicordSettings() {
|
|||
}),
|
||||
!IS_WEB && {
|
||||
key: "transparent",
|
||||
title: "Enable window transparency.",
|
||||
title: "Enable Window Transparency",
|
||||
note: "You need a theme that supports transparency or this will do nothing. Requires a full restart!",
|
||||
warning: {
|
||||
enabled: isWindows,
|
||||
|
@ -112,7 +112,7 @@ function EquicordSettings() {
|
|||
},
|
||||
IS_DISCORD_DESKTOP && {
|
||||
key: "disableMinSize",
|
||||
title: "Disable minimum window size",
|
||||
title: "Disable Minimum Window Size",
|
||||
note: "Requires a full restart",
|
||||
warning: { enabled: false },
|
||||
},
|
||||
|
|
|
@ -48,7 +48,7 @@ async function runReporter() {
|
|||
|
||||
for (const patch of patches) {
|
||||
if (!patch.all) {
|
||||
new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
|
||||
new Logger("WebpackPatcher").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
|
||||
if (IS_COMPANION_TEST)
|
||||
reporterData.failedPatches.foundNoModule.push(patch);
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ async function runReporter() {
|
|||
|
||||
for (const [plugin, moduleId, match, totalTime] of patchTimings) {
|
||||
if (totalTime > 5) {
|
||||
new Logger("WebpackInterceptor").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`);
|
||||
new Logger("WebpackPatcher").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,7 @@ async function runReporter() {
|
|||
result = Webpack[method](...args);
|
||||
}
|
||||
|
||||
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
||||
if (result == null || (result.$$vencordGetWrappedComponent != null && result.$$vencordGetWrappedComponent() == null)) throw new Error("Webpack Find Fail");
|
||||
} catch (e) {
|
||||
let logMessage = searchType;
|
||||
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||
|
|
|
@ -18,10 +18,26 @@
|
|||
|
||||
import "@equicordplugins/_misc/styles.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Forms } from "@webpack/common";
|
||||
|
||||
import clanBadges from "../_misc/clanBadges.css?managed";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
hideClanBadges: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Hide clan badges",
|
||||
default: false,
|
||||
onChange: value => {
|
||||
if (value) enableStyle(clanBadges);
|
||||
else disableStyle(clanBadges);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "EquicordHelper",
|
||||
description: "Fixes some misc issues with discord",
|
||||
|
@ -31,6 +47,7 @@ export default definePlugin({
|
|||
This Plugin is used for fixing misc issues with discord such as some crashes
|
||||
</Forms.FormText>
|
||||
</>,
|
||||
settings,
|
||||
required: true,
|
||||
patches: [
|
||||
{
|
||||
|
@ -45,13 +62,12 @@ export default definePlugin({
|
|||
replace: "return $1;"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: '"Slate: Unable to find syntax characters"',
|
||||
replacement: {
|
||||
match: /((let )(\i)=\i\.indexOf\(\i,(\i)\)),/,
|
||||
replace: "$1;if ($3 === -1) {return $4;}$2"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
start() {
|
||||
if (settings.store.hideClanBadges) enableStyle(clanBadges);
|
||||
},
|
||||
stop() {
|
||||
if (settings.store.hideClanBadges) disableStyle(clanBadges);
|
||||
}
|
||||
});
|
||||
|
|
3
src/equicordplugins/_misc/clanBadges.css
Normal file
3
src/equicordplugins/_misc/clanBadges.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
[class*="chipletContainerInner_"]:has([src *="/clan-badges/"]) {
|
||||
display: none;
|
||||
}
|
69
src/equicordplugins/autoJump/index.tsx
Normal file
69
src/equicordplugins/autoJump/index.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { ChannelStore, Menu, MessageActions, NavigationRouter } from "@webpack/common";
|
||||
|
||||
interface ChannelSelectEvent {
|
||||
type: "CHANNEL_SELECT";
|
||||
channelId: string | null;
|
||||
guildId: string | null;
|
||||
}
|
||||
|
||||
let lastChannelId = "0";
|
||||
|
||||
function autoJump({ guild_id, id: channelId }) {
|
||||
const guildId = guild_id ?? "@me";
|
||||
|
||||
lastChannelId = channelId;
|
||||
NavigationRouter.transitionTo(`/channels/${guildId}/${channelId}`);
|
||||
MessageActions.jumpToPresent(channelId, { limit: null });
|
||||
}
|
||||
|
||||
const MenuPatch: NavContextMenuPatchCallback = (children, { channel }) => {
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
id="auto-jump"
|
||||
label="Jump to Last Message"
|
||||
action={() => {
|
||||
autoJump(channel);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const settings = definePluginSettings({
|
||||
autoJumping: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Automatically jump to the last message in the channel when switching channels",
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "AutoJump",
|
||||
description: "Jumps to Last Message in Channel",
|
||||
authors: [EquicordDevs.omaw],
|
||||
settings,
|
||||
contextMenus: {
|
||||
"channel-context": MenuPatch,
|
||||
"user-context": MenuPatch,
|
||||
"thread-context": MenuPatch
|
||||
},
|
||||
flux: {
|
||||
async CHANNEL_SELECT({ guildId, channelId }: ChannelSelectEvent) {
|
||||
if (!settings.store.autoJumping || !channelId) return;
|
||||
|
||||
const channel = ChannelStore.getChannel(channelId);
|
||||
if (!channel || channel.id === lastChannelId) return;
|
||||
|
||||
autoJump({ guild_id: guildId, id: channelId });
|
||||
}
|
||||
}
|
||||
});
|
|
@ -58,8 +58,8 @@ export default definePlugin({
|
|||
replacement: [
|
||||
{
|
||||
// We add the banner as a property while we can still access the user id
|
||||
match: /(?<=nameplate:(\i).*?)verified:(\i).isVerifiedBot.*?name:null.*?(?=avatar:)/,
|
||||
replace: "$&banner:$self.memberListBannerHook($2, $1),",
|
||||
match: /user:(\i).{0,150}nameplate:(\i).*?name:null.*?(?=avatar:)/,
|
||||
replace: "$&banner:$self.memberListBannerHook($1, $2),",
|
||||
},
|
||||
{
|
||||
match: /(?<=\),nameplate:)(\i)/,
|
||||
|
@ -112,7 +112,7 @@ export default definePlugin({
|
|||
}
|
||||
|
||||
return (
|
||||
<img id={`vc-banners-everywhere-${user.id}`} src={url} className="vc-banners-everywhere-memberlist"></img>
|
||||
<img alt="" id={`vc-banners-everywhere-${user.id}`} src={url} className="vc-banners-everywhere-memberlist"></img>
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ export default definePlugin({
|
|||
patches: [
|
||||
{
|
||||
// Patch activity icons
|
||||
find: '"activity-status-web"',
|
||||
find: "isBlockedOrIgnored(null",
|
||||
replacement: {
|
||||
match: /(?<=hideTooltip:.{0,4}}=(\i).*?{}\))\]/,
|
||||
replace: ",$self.patchActivityList($1)]"
|
||||
|
@ -44,9 +44,9 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
// Show all activities in the user popout/sidebar
|
||||
find: '"UserProfilePopoutBody"',
|
||||
find: "hasAvatarForGuild(null",
|
||||
replacement: {
|
||||
match: /(?<=(\i)\.id\)\}\)\),(\i).*?)\(0,.{0,100}\i\.activity\}\)/,
|
||||
match: /(?<=(\i)\.id\)\}\)\),(\i).*?)\(0,.{0,100}\i\.id,onClose:\i\}\)/,
|
||||
replace: "$self.showAllActivitiesComponent({ activity: $2, user: $1 })"
|
||||
},
|
||||
predicate: () => settings.store.userPopout
|
||||
|
|
|
@ -66,7 +66,7 @@ async function addListeners(audioElement: HTMLAudioElement, url: string, parentB
|
|||
const madeURL = new URL(url);
|
||||
madeURL.searchParams.set("t", Date.now().toString());
|
||||
|
||||
const corsProxyUrl = "https://corsproxy.io?" + encodeURIComponent(madeURL.href);
|
||||
const corsProxyUrl = "https://corsproxy.io/?url=" + encodeURIComponent(madeURL.href);
|
||||
const response = await fetch(corsProxyUrl);
|
||||
const blob = await response.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
|
|
@ -4,53 +4,55 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
import "./styles.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { DeleteIcon, PlusIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getIntlMessage } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, Forms, TextInput } from "@webpack/common";
|
||||
|
||||
const cl = classNameFactory("vc-bbr-");
|
||||
|
||||
function ReasonsComponent() {
|
||||
const { reasons } = settings.use(["reasons"]);
|
||||
const { reasons } = settings.store;
|
||||
|
||||
return (
|
||||
<Forms.FormSection title="Reasons">
|
||||
{reasons.map((reason: string, index: number) => (
|
||||
{reasons.map((r, i) => (
|
||||
<div
|
||||
className="vc-bbr-reason-wrapper"
|
||||
key={index}
|
||||
key={i}
|
||||
className={cl("reason-wrapper")}
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
key={index}
|
||||
value={reason}
|
||||
value={r}
|
||||
onChange={v => {
|
||||
reasons[index] = v;
|
||||
settings.store.reasons = [...reasons];
|
||||
reasons[i] = v;
|
||||
settings.store.reasons = reasons;
|
||||
}}
|
||||
placeholder="Reason"
|
||||
/>
|
||||
<Button
|
||||
color={Button.Colors.RED}
|
||||
className="vc-bbr-remove-button"
|
||||
className={cl("remove-button")}
|
||||
color={Button.Colors.TRANSPARENT}
|
||||
onClick={() => {
|
||||
reasons.splice(index, 1);
|
||||
settings.store.reasons = [...reasons];
|
||||
reasons.splice(i, 1);
|
||||
settings.store.reasons = reasons;
|
||||
}}
|
||||
look={Button.Looks.BLANK}
|
||||
size={Button.Sizes.MIN}
|
||||
>
|
||||
Remove
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
onClick={() => {
|
||||
settings.store.reasons = [...reasons, ""];
|
||||
}}
|
||||
>
|
||||
Add new
|
||||
</Button>
|
||||
<div className={cl("reason-wrapper")}>
|
||||
<Button onClick={() => settings.store.reasons.push("")} className={cl("add-button")} size={Button.Sizes.LARGE} color={Button.Colors.TRANSPARENT}>
|
||||
<PlusIcon /> Add another reason
|
||||
</Button>
|
||||
</div>
|
||||
</Forms.FormSection>
|
||||
);
|
||||
}
|
||||
|
@ -59,10 +61,10 @@ const settings = definePluginSettings({
|
|||
reasons: {
|
||||
description: "Your custom reasons",
|
||||
type: OptionType.COMPONENT,
|
||||
default: [""],
|
||||
default: [] as string[],
|
||||
component: ReasonsComponent,
|
||||
},
|
||||
textInputDefault: {
|
||||
isTextInputDefault: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: 'Shows a text input instead of a select menu by default. (Equivalent to clicking the "Other" option)'
|
||||
}
|
||||
|
@ -74,9 +76,9 @@ export default definePlugin({
|
|||
authors: [Devs.Inbestigator],
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::BAN_MULTIPLE_CONFIRM_TITLE}",
|
||||
find: "#{intl::BAN_REASON_OPTION_SPAM_ACCOUNT}",
|
||||
replacement: [{
|
||||
match: /\[\{name:\i\.\i\.string\(\i\.\i#{intl::BAN_REASON_OPTION_SPAM_ACCOUNT}\).+?\}\]/,
|
||||
match: /\[(\{((name|value):\i\.\i\.string\(\i\.\i\.\i\),?){2}\},?){3}\]/,
|
||||
replace: "$self.getReasons()"
|
||||
},
|
||||
{
|
||||
|
@ -86,17 +88,16 @@ export default definePlugin({
|
|||
}
|
||||
],
|
||||
getReasons() {
|
||||
const reasons = settings.store.reasons.length
|
||||
? settings.store.reasons
|
||||
const storedReasons = settings.store.reasons.filter((r: string) => r.trim());
|
||||
const reasons: string[] = storedReasons.length
|
||||
? storedReasons
|
||||
: [
|
||||
getIntlMessage("BAN_REASON_OPTION_SPAM_ACCOUNT"),
|
||||
getIntlMessage("BAN_REASON_OPTION_HACKED_ACCOUNT"),
|
||||
getIntlMessage("BAN_REASON_OPTION_BREAKING_RULES")
|
||||
getIntlMessage("BAN_REASON_OPTION_BREAKING_RULES"),
|
||||
];
|
||||
return reasons.map(s => ({ name: s, value: s }));
|
||||
},
|
||||
getDefaultState() {
|
||||
return settings.store.textInputDefault ? 1 : 0;
|
||||
},
|
||||
getDefaultState: () => settings.store.isTextInputDefault ? 1 : 0,
|
||||
settings,
|
||||
});
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
.vc-bbr-reason-wrapper {
|
||||
display: grid;
|
||||
padding: 0;
|
||||
padding-bottom: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: 6fr 1fr;
|
||||
}
|
||||
|
||||
.vc-bbr-remove-button {
|
||||
height: 100%;
|
||||
}
|
31
src/equicordplugins/betterBanReasons/styles.css
Normal file
31
src/equicordplugins/betterBanReasons/styles.css
Normal file
|
@ -0,0 +1,31 @@
|
|||
.vc-bbr-reason-wrapper {
|
||||
display: grid;
|
||||
padding-bottom: 12px;
|
||||
gap: 4px 12px;
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr 24px;
|
||||
}
|
||||
|
||||
.vc-bbr-remove-button {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.vc-bbr-remove-button:hover {
|
||||
color: var(--button-danger-background-hover);
|
||||
}
|
||||
|
||||
.vc-bbr-add-button {
|
||||
justify-content: start !important;
|
||||
border: 0;
|
||||
padding: 2px 12px;
|
||||
color: var(--text-muted) !important;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.vc-bbr-add-button div {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 0 !important;
|
||||
}
|
|
@ -4,58 +4,23 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { openUserProfile } from "@utils/discord";
|
||||
import { openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, FluxDispatcher, React, RelationshipStore, Text, TextInput, UserStore } from "@webpack/common";
|
||||
import { ButtonProps } from "@webpack/types";
|
||||
import { User } from "discord-types/general";
|
||||
import "./styles.css";
|
||||
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { getIntlMessage, openUserProfile } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, React, RelationshipStore, TextInput, UserStore } from "@webpack/common";
|
||||
|
||||
const ChannelActions = findByPropsLazy("openPrivateChannel");
|
||||
|
||||
let lastSearch = "";
|
||||
let updateFunc = (v: any) => { };
|
||||
|
||||
const ChannelActions = findByPropsLazy("openPrivateChannel");
|
||||
const ButtonComponent = findComponentByCodeLazy('submittingStartedLabel","submittingFinishedLabel"]);');
|
||||
const ConfirmationModal = findByCodeLazy('"ConfirmModal")', "useLayoutEffect");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
addDmsButton: {
|
||||
default: true,
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Adds a 'View DMs' button to the users in the blocked/ignored list.",
|
||||
},
|
||||
hideBlockedWarning: {
|
||||
default: false,
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Skip the warning about blocked/ignored users when opening any profile anywhere on discord outside of the blocklist.",
|
||||
restartNeeded: true,
|
||||
},
|
||||
showUnblockConfirmation: {
|
||||
default: true,
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show a warning before unblocking a user from the blocklist.",
|
||||
},
|
||||
showUnblockConfirmationEverywhere: {
|
||||
default: false,
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show a warning before unblocking a user anywhere on discord.",
|
||||
restartNeeded: true,
|
||||
},
|
||||
unblockButtonDanger: {
|
||||
default: false,
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Color the unblock button in the blocklist red instead of gray.",
|
||||
},
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "BetterBlockedUsers",
|
||||
description: "Allows you to search in blocked users list and makes names clickable in settings.",
|
||||
authors: [EquicordDevs.TheArmagan, Devs.Elvyra],
|
||||
settings,
|
||||
description: "Allows you to search in blocked users list and makes names selectable in settings.",
|
||||
authors: [EquicordDevs.TheArmagan],
|
||||
patches: [
|
||||
{
|
||||
find: '"],{numberOfBlockedUsers:',
|
||||
|
@ -65,12 +30,8 @@ export default definePlugin({
|
|||
replace: ",$1.listType==='blocked'?$self.renderSearchInput():null"
|
||||
},
|
||||
{
|
||||
match: /(?<=className:\i.userInfo,)(?=children:.{0,20}user:(\i))/,
|
||||
replace: "style:{cursor:'pointer'},onClick:()=>$self.openUserProfile($1),"
|
||||
},
|
||||
{
|
||||
match: /(?<=children:null!=(\i).globalName\?.+?}\),).*?(\{color:.{0,65}?string\((\i).+?"8wXU9P"]\)})\)/,
|
||||
replace: "$self.generateButtons({user:$1, originalProps:$2, isBlocked:$3})",
|
||||
match: /(?<=userId:(\i).*?\}\)\]\}\),)(\(.*?\)\}\))/,
|
||||
replace: "$self.renderUser($1,$2),",
|
||||
},
|
||||
{
|
||||
match: /(?<=\}=(\i).{0,10}(\i).useState\(.{0,1}\);)/,
|
||||
|
@ -81,64 +42,6 @@ export default definePlugin({
|
|||
replace: "$1(searchResults.length?searchResults:$2)"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "UserProfileModalHeaderActionButtons",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=return \i)\|\|(\i)===.*?.FRIEND/,
|
||||
replace: (_, type) => `?null:${type} === 1|| ${type} === 2`,
|
||||
},
|
||||
{
|
||||
match: /(?<=\i.bot.{0,50}children:.*?onClose:)(\i)/,
|
||||
replace: "() => {$1();$self.closeSettingsWindow()}",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
find: ',["user"])',
|
||||
replacement: {
|
||||
match: /(?<=isIgnored:.*?,\[\i,\i]=\i.useState\()\i\|\|\i\|\|\i.*?]\);/,
|
||||
replace: "false);"
|
||||
},
|
||||
},
|
||||
|
||||
// If the users wishes to, they can disable the warning in all other places as well.
|
||||
...[
|
||||
"UserProfilePanelWrapper: currentUser cannot be undefined",
|
||||
"UserProfilePopoutWrapper: currentUser cannot be undefined",
|
||||
].map(x => ({
|
||||
find: x,
|
||||
replacement: {
|
||||
match: /(?<=isIgnored:.*?,\[\i,\i]=\i.useState\()\i\|\|\i\|\|\i\)(?:;\i.useEffect.*?]\))?/,
|
||||
replace: "false)",
|
||||
},
|
||||
predicate: () => settings.store.hideBlockedWarning,
|
||||
})),
|
||||
|
||||
{
|
||||
find: ".BLOCKED:return",
|
||||
replacement: {
|
||||
match: /(?<=\i.BLOCKED:return.{0,65}onClick:)\(\)=>\{(\i.\i.unblockUser\((\i).+?}\))/,
|
||||
replace: "(event) => {$self.openConfirmationModal(event,()=>{$1}, $2)",
|
||||
},
|
||||
predicate: () => settings.store.showUnblockConfirmationEverywhere,
|
||||
},
|
||||
{
|
||||
find: "#{intl::UNBLOCK}),",
|
||||
replacement: {
|
||||
match: /(?<=#{intl::UNBLOCK}.+?Click=)\(\)=>(\{.+?(\i.getRecipientId\(\))\)})/,
|
||||
replace: "event => $self.openConfirmationModal(event, ()=>$1, $2)",
|
||||
},
|
||||
predicate: () => settings.store.showUnblockConfirmationEverywhere,
|
||||
},
|
||||
{
|
||||
find: "#{intl::BLOCK}),action",
|
||||
replacement: {
|
||||
match: /(?<=id:"block".{0,100}action:\i\?)\(\)=>(\{.{0,25}unblockUser\((\i).{0,60}:void 0\)})/,
|
||||
replace: "event => {$self.openConfirmationModal(event, ()=>$1,$2)}",
|
||||
},
|
||||
predicate: () => settings.store.showUnblockConfirmationEverywhere,
|
||||
}
|
||||
],
|
||||
renderSearchInput() {
|
||||
|
@ -161,6 +64,19 @@ export default definePlugin({
|
|||
}} value={value}
|
||||
></TextInput>;
|
||||
},
|
||||
renderUser(userId: string, rest: any) {
|
||||
return (
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<Button color={Button.Colors.PRIMARY} onClick={() => openUserProfile(userId)}>
|
||||
{getIntlMessage("SHOW_USER_PROFILE")}
|
||||
</Button>
|
||||
{rest}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
getSearchResults() {
|
||||
return !!lastSearch;
|
||||
},
|
||||
setUpdateFunc(e, setResults) {
|
||||
if (e.listType !== "blocked") return;
|
||||
updateFunc = setResults;
|
||||
|
@ -173,72 +89,5 @@ export default definePlugin({
|
|||
if (!user) return id === search;
|
||||
return id === search || user?.username?.toLowerCase()?.includes(search) || user?.globalName?.toLowerCase()?.includes(search);
|
||||
}) as string[];
|
||||
},
|
||||
closeSettingsWindow() {
|
||||
FluxDispatcher.dispatch({ type: "LAYER_POP" });
|
||||
},
|
||||
openUserProfile(user: User) {
|
||||
openUserProfile(user.id);
|
||||
},
|
||||
generateButtons(props: { user: User, originalProps: ButtonProps, isBlocked: boolean; }) {
|
||||
const { user, originalProps, isBlocked } = props;
|
||||
|
||||
if (settings.store.unblockButtonDanger) originalProps.color = Button.Colors.RED;
|
||||
|
||||
// TODO add extra unblock confirmation after the click + setting.
|
||||
|
||||
if (settings.store.showUnblockConfirmation || settings.store.showUnblockConfirmationEverywhere) {
|
||||
const originalOnClick = originalProps.onClick!;
|
||||
originalProps.onClick = e => {
|
||||
if (!isBlocked) return originalOnClick(e);
|
||||
this.openConfirmationModal(e as unknown as MouseEvent, () => originalOnClick(e), user, true);
|
||||
};
|
||||
}
|
||||
|
||||
const unblockButton = <ButtonComponent {...originalProps} />;
|
||||
|
||||
if (!settings.store.addDmsButton) return unblockButton;
|
||||
|
||||
const dmButton = <ButtonComponent color={Button.Colors.BRAND_NEW} onClick={() => this.openDMChannel(user)}>Show DMs</ButtonComponent>;
|
||||
|
||||
return <div style={{ display: "flex", gap: "8px" }} className="vc-bbc-button-container">
|
||||
{dmButton}
|
||||
{unblockButton}
|
||||
</div>;
|
||||
},
|
||||
|
||||
openDMChannel(user: User) {
|
||||
ChannelActions.openPrivateChannel(user.id);
|
||||
this.closeSettingsWindow();
|
||||
return null;
|
||||
},
|
||||
openConfirmationModal(event: MouseEvent, callback: () => any, user: User | string, isSettingsOrigin: boolean = false) {
|
||||
if (event.shiftKey) return callback();
|
||||
|
||||
if (typeof user === "string") {
|
||||
user = UserStore.getUser(user);
|
||||
}
|
||||
|
||||
return openModal(m => <ConfirmationModal
|
||||
{...m}
|
||||
className="vc-bbc-confirmation-modal"
|
||||
header={`Unblock ${user?.username ?? "?"}?`}
|
||||
cancelText="Cancel"
|
||||
confirmText="Unblock"
|
||||
onConfirm={() => {
|
||||
callback();
|
||||
}}>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }} className="vc-bbc-confirmation-modal-text">
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
||||
<Text variant="text-md/semibold">{`Are you sure you want to unblock ${user?.username ?? "this user"}?`}</Text>
|
||||
<Text variant="text-md/normal">{`This will allow ${user?.username ?? "them"} to see your profile and message you again.`}</Text>
|
||||
</div>
|
||||
<Text variant="text-md/normal">{"You can always block them again later."}</Text>
|
||||
{isSettingsOrigin ? <div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
||||
<Text variant="text-sm/medium" style={{ color: "var(--text-muted)" }}>{"If you just want to read the chat logs instead, you can just click on their profile."}</Text>
|
||||
<Text variant="text-sm/normal" style={{ color: "var(--text-muted)" }}>{"Alternatively, you can enable a button to jump to DMs in the blocklist through the plugin settings."}</Text>
|
||||
</div> : <Text variant="text-sm/medium" style={{ color: "var(--text-muted)" }}>{"If you just want to read the chat logs, you can do this without unblocking them."}</Text>}
|
||||
</div>
|
||||
</ConfirmationModal>);
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
3
src/equicordplugins/betterBlockedUsers/styles.css
Normal file
3
src/equicordplugins/betterBlockedUsers/styles.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
[class*="usersList_"] [class*="text_"] {
|
||||
user-select: text !important;
|
||||
}
|
49
src/equicordplugins/bypassPinPrompt/index.ts
Normal file
49
src/equicordplugins/bypassPinPrompt/index.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "BypassPinPrompt",
|
||||
description: "Bypass the pin prompt when pinning messages",
|
||||
authors: [EquicordDevs.thororen],
|
||||
patches: [
|
||||
{
|
||||
find: '"Channel Pins"',
|
||||
replacement: {
|
||||
match: /(?<=(\i\.\i\.unpinMessage\(\i,\i\.id\)):)\i\.\i\.confirmUnpin\(\i,\i\)/,
|
||||
replace: "$1"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: 'source:"message-actions"',
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=(\i\.\i\.pinMessage\(\i,\i\.id\)):)\i\.\i\.confirmPin\(\i,\i\)/,
|
||||
replace: "$1"
|
||||
},
|
||||
{
|
||||
match: /(?<=(\i\.\i\.unpinMessage\(\i,\i\.id\)):)\i\.\i\.confirmUnpin\(\i,\i\)/,
|
||||
replace: "$1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: 'id:"pin"',
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=(\i\.\i\.pinMessage\(\i,\i\.id\)):)\i\.\i\.confirmPin\(\i,\i\)/,
|
||||
replace: "$1"
|
||||
},
|
||||
{
|
||||
match: /(?<=(\i\.\i\.unpinMessage\(\i,\i\.id\)):)\i\.\i\.confirmUnpin\(\i,\i\)/,
|
||||
replace: "$1"
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
});
|
|
@ -20,6 +20,8 @@ const PlusSmallIcon = findComponentByCodeLazy("0v-5h5a1");
|
|||
|
||||
const cl = classNameFactory("vc-channeltabs-");
|
||||
|
||||
const isMac = navigator.platform.toLowerCase().startsWith("mac");
|
||||
|
||||
export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
|
||||
const [userId, setUserId] = useState("");
|
||||
const { showBookmarkBar, widerTabsAndBookmarks } = settings.use(["showBookmarkBar", "widerTabsAndBookmarks"]);
|
||||
|
@ -66,6 +68,7 @@ export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
|
|||
className={cl("container")}
|
||||
ref={ref}
|
||||
onContextMenu={e => ContextMenuApi.openContextMenu(e, () => <BasicContextMenu />)}
|
||||
style={{ marginTop: isMac ? "28px" : "0" }}
|
||||
>
|
||||
<div className={cl("tab-container")}>
|
||||
{openedTabs.map((tab, i) =>
|
||||
|
|
|
@ -48,7 +48,7 @@ export default definePlugin({
|
|||
{
|
||||
find: ".COLLECTIBLES_SHOP_FULLSCREEN))",
|
||||
replacement: {
|
||||
match: /(\?void 0:(\i)\.channelId.{0,300}return)((.{0,15})"div",{.*?\])(\}\)\}\})/,
|
||||
match: /(\?void 0:(\i)\.channelId.{0,500}return)((.{0,15})"div",{.*?\])(\}\)\}\})/,
|
||||
replace: "$1$4$self.render,{currentChannel:$2,children:$3})$5"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -152,8 +152,8 @@ export default definePlugin({
|
|||
{
|
||||
find: "._areActivitiesExperimentallyHidden=(",
|
||||
replacement: {
|
||||
match: /BOOST_GEM_ICON\}\}\)\)\};/,
|
||||
replace: "$&if($self.shouldHideUser(this.props.user.id, this.props.channel.id)) return null; "
|
||||
match: /(?<=user:(\i),guildId:\i,channel:(\i).*?)BOOST_GEM_ICON.{0,10}\);/,
|
||||
replace: "$&if($self.shouldHideUser($1.id, $2.id)) return null; "
|
||||
}
|
||||
},
|
||||
// stop the role header from displaying if all users with that role are hidden (wip sorta)
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
|
||||
import { showNotification } from "@api/Notifications";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { relaunch, showItemInFolder } from "@utils/native";
|
||||
import { checkForUpdates, getRepo } from "@utils/updater";
|
||||
import { Clipboard, GuildStore, NavigationRouter, SettingsRouter, Toasts } from "@webpack/common";
|
||||
import { GuildStore, NavigationRouter, SettingsRouter, Toasts } from "@webpack/common";
|
||||
|
||||
import gitHash from "~git-hash";
|
||||
import gitRemote from "~git-remote";
|
||||
|
@ -89,7 +90,7 @@ export const actions: ButtonAction[] = [
|
|||
const newUrl = url.replace(/(https?:\/\/)?([a-zA-Z0-9-]+)\.([a-zA-Z0-9-]+)/, "https://$2.$3");
|
||||
const res = (await fetch(newUrl));
|
||||
const text = await res.text();
|
||||
Clipboard.copy(text);
|
||||
copyToClipboard(text);
|
||||
|
||||
Toasts.show({
|
||||
message: "Copied response to clipboard!",
|
||||
|
@ -115,7 +116,7 @@ export const actions: ButtonAction[] = [
|
|||
|
||||
{
|
||||
id: "copyGitInfo", label: "Copy Git Info", callback: async () => {
|
||||
Clipboard.copy(`gitHash: ${gitHash}\ngitRemote: ${gitRemote}`);
|
||||
copyToClipboard(`gitHash: ${gitHash}\ngitRemote: ${gitRemote}`);
|
||||
|
||||
Toasts.show({
|
||||
message: "Copied git info to clipboard!",
|
||||
|
|
107
src/equicordplugins/copyProfileColors/index.tsx
Normal file
107
src/equicordplugins/copyProfileColors/index.tsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu, Toasts, UserProfileStore } from "@webpack/common";
|
||||
|
||||
function getProfileColors(userId) {
|
||||
try {
|
||||
const profile = UserProfileStore.getUserProfile(userId);
|
||||
|
||||
if (!profile || !profile.themeColors || profile.themeColors.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const primaryColor = profile.themeColors[0].toString(16).padStart(6, "0");
|
||||
const secondaryColor = profile.themeColors[1].toString(16).padStart(6, "0");
|
||||
|
||||
return { primaryColor, secondaryColor };
|
||||
} catch (e) {
|
||||
console.error("Failed to get profile colors:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function copyProfileColors(userId) {
|
||||
const colors = getProfileColors(userId);
|
||||
|
||||
if (!colors) {
|
||||
Toasts.show({
|
||||
type: Toasts.Type.FAILURE,
|
||||
message: "No profile colors found!",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { primaryColor, secondaryColor } = colors;
|
||||
|
||||
// Formatting
|
||||
const formattedColors = `Primary-color #${primaryColor}, Secondary-Color #${secondaryColor}`;
|
||||
|
||||
try {
|
||||
copyToClipboard(formattedColors);
|
||||
Toasts.show({
|
||||
type: Toasts.Type.SUCCESS,
|
||||
message: "Profile colors copied to clipboard!",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to copy to clipboard:", e);
|
||||
Toasts.show({
|
||||
type: Toasts.Type.FAILURE,
|
||||
message: "Error copying profile colors!",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function ColorIcon() {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="#94b3e4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M17,4H15.82A3,3,0,0,0,13,2H11A3,3,0,0,0,8.18,4H7A3,3,0,0,0,4,7V19a3,3,0,0,0,3,3H17a3,3,0,0,0,3-3V7A3,3,0,0,0,17,4ZM10,5a1,1,0,0,1,1-1h2a1,1,0,0,1,1,1V6H10Zm8,14a1,1,0,0,1-1,1H7a1,1,0,0,1-1-1V7A1,1,0,0,1,7,6H8V7A1,1,0,0,0,9,8h6a1,1,0,0,0,1-1V6h1a1,1,0,0,1,1,1Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
// spawn in the context menu
|
||||
const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }) => {
|
||||
if (!user) return;
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
id="CopyProfileColors"
|
||||
icon={ColorIcon}
|
||||
label={<span style={{ color: "rgb(148, 179, 228)" }}>Copy Profile Colors</span>}
|
||||
action={() => copyProfileColors(user.id)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "CopyProfileColors",
|
||||
description: "A plugin to copy people's profile gradient colors to clipboard.",
|
||||
authors: [EquicordDevs.Crxa, EquicordDevs.Cortex], // Cortex is here because he showed me how to add icons <3
|
||||
|
||||
start() {
|
||||
addContextMenuPatch("user-context", userContextMenuPatch);
|
||||
addContextMenuPatch("user-profile-actions", userContextMenuPatch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
// bye bye menu options
|
||||
removeContextMenuPatch("user-context", userContextMenuPatch);
|
||||
removeContextMenuPatch("user-profile-actions", userContextMenuPatch);
|
||||
}
|
||||
});
|
135
src/equicordplugins/copyStickerLinks/index.tsx
Normal file
135
src/equicordplugins/copyStickerLinks/index.tsx
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { Menu, React } from "@webpack/common";
|
||||
import { Promisable } from "type-fest";
|
||||
|
||||
const StickersStore = findStoreLazy("StickersStore");
|
||||
|
||||
interface Sticker {
|
||||
t: "Sticker";
|
||||
format_type: number;
|
||||
id: string;
|
||||
type: number;
|
||||
}
|
||||
|
||||
const StickerExt = ["png", "png", "json", "gif"] as const;
|
||||
|
||||
function getUrl(data: Sticker) {
|
||||
if (data.format_type === 4)
|
||||
return `https:${window.GLOBAL_ENV.MEDIA_PROXY_ENDPOINT}/stickers/${data.id}.gif?size=4096&lossless=true`;
|
||||
|
||||
return `https://${window.GLOBAL_ENV.CDN_HOST}/stickers/${data.id}.${StickerExt[data.format_type]}?size=4096&lossless=true`;
|
||||
}
|
||||
|
||||
function buildMenuItem(Sticker, fetchData: () => Promisable<Omit<Sticker, "t">>) {
|
||||
return (
|
||||
<>
|
||||
<Menu.MenuSeparator></Menu.MenuSeparator>
|
||||
|
||||
<Menu.MenuItem
|
||||
id="copystickerurl"
|
||||
key="copystickerurl"
|
||||
label={"Copy URL"}
|
||||
action={async () => {
|
||||
const res = await fetchData();
|
||||
const data = { t: Sticker, ...res } as Sticker;
|
||||
const url = getUrl(data[0]);
|
||||
copyWithToast(url, "Link copied!");
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
<Menu.MenuItem
|
||||
id="openstickerlink"
|
||||
key="openstickerlink"
|
||||
label={"Open URL"}
|
||||
action={async () => {
|
||||
const res = await fetchData();
|
||||
const data = { t: Sticker, ...res } as Sticker;
|
||||
const url = getUrl(data[0]);
|
||||
VencordNative.native.openExternal(url);
|
||||
}
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function buildMenuExpression(Sticker, fetchData: () => Promisable<Omit<Sticker, "t">>) {
|
||||
return (
|
||||
<>
|
||||
<Menu.MenuSeparator></Menu.MenuSeparator>
|
||||
<Menu.MenuItem
|
||||
id="copystickerurl"
|
||||
key="copystickerurl"
|
||||
label={"Copy URL"}
|
||||
action={async () => {
|
||||
const res = await fetchData();
|
||||
const data = { t: Sticker, ...res } as Sticker;
|
||||
const url = getUrl(data);
|
||||
copyWithToast(url, "Link copied!");
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Menu.MenuItem
|
||||
id="openstickerlink"
|
||||
key="openstickerlink"
|
||||
label={"Open URL"}
|
||||
action={async () => {
|
||||
const res = await fetchData();
|
||||
const data = { t: Sticker, ...res } as Sticker;
|
||||
const url = getUrl(data);
|
||||
VencordNative.native.openExternal(url);
|
||||
}
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||
const { favoriteableId, favoriteableType } = props ?? {};
|
||||
if (!favoriteableId) return;
|
||||
const menuItem = (() => {
|
||||
const sticker = props.message.stickerItems.find(s => s.id === favoriteableId);
|
||||
if (sticker?.format_type === 3) return;
|
||||
switch (favoriteableType) {
|
||||
case "sticker":
|
||||
return buildMenuItem("Sticker", () => props.message.stickerItems);
|
||||
}
|
||||
})();
|
||||
|
||||
if (menuItem)
|
||||
findGroupChildrenByChildId("devmode-copy-id", children, true)?.push(menuItem);
|
||||
};
|
||||
|
||||
const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => {
|
||||
const { id } = props?.target?.dataset ?? {};
|
||||
if (!id) return;
|
||||
|
||||
if (!props.target.className?.includes("lottieCanvas")) {
|
||||
const stickerCache = StickersStore.getStickerById(id);
|
||||
if (stickerCache) {
|
||||
children.push(buildMenuExpression("Sticker", () => stickerCache));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "CopyStickerLinks",
|
||||
description: "Adds the ability to copy and open sticker links to your browser",
|
||||
authors: [Devs.Byeoon],
|
||||
contextMenus: {
|
||||
"message": messageContextMenuPatch,
|
||||
"expression-picker": expressionPickerPatch
|
||||
}
|
||||
});
|
|
@ -5,9 +5,10 @@
|
|||
*/
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Clipboard, Menu } from "@webpack/common";
|
||||
import { Menu } from "@webpack/common";
|
||||
import type { Channel, User } from "discord-types/general";
|
||||
|
||||
const MentionIcon = () => (
|
||||
|
@ -37,7 +38,7 @@ const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: U
|
|||
<Menu.MenuItem
|
||||
id="vc-copy-user-mention"
|
||||
label="Copy User Mention"
|
||||
action={() => Clipboard.copy(`<@${user.id}>`)}
|
||||
action={() => copyToClipboard(`<@${user.id}>`)}
|
||||
icon={MentionIcon}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -71,11 +71,11 @@ export default definePlugin({
|
|||
find: "#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=\i=null!=\i\?).{0,25}\((\i),"LT"\):\(0,\i\.\i\)\(\i,!0\)/,
|
||||
match: /(?<=null!=\i\?).{0,25}\((\i),"LT"\):\(0,\i\.\i\)\(\i,!0\)/,
|
||||
replace: '$self.format($1,"compactFormat","[calendar]"):$self.format($1,"cozyFormat","LT")',
|
||||
},
|
||||
{
|
||||
match: /(?<=text:)\(0,\i.\i\)\((\i),"LLLL"\)(?=,)/,
|
||||
match: /(?<=text:)\(\)=>\(0,\i.\i\)\((\i),"LLLL"\)(?=,)/,
|
||||
replace: '$self.format($1,"tooltipFormat","LLLL")',
|
||||
},
|
||||
]
|
||||
|
|
|
@ -62,12 +62,8 @@ const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: {
|
|||
};
|
||||
|
||||
export function getCustomColorString(userId: string, withHash?: boolean): string | undefined {
|
||||
if (!colors[userId] || !Settings.plugins.CustomUserColors.enabled)
|
||||
return;
|
||||
|
||||
if (withHash)
|
||||
return `#${colors[userId]}`;
|
||||
|
||||
if (!colors[userId] || !Settings.plugins.CustomUserColors.enabled) return;
|
||||
if (withHash) return `#${colors[userId]}`;
|
||||
return colors[userId];
|
||||
}
|
||||
|
||||
|
@ -98,21 +94,23 @@ export default definePlugin({
|
|||
// this also affects name headers in chats outside of servers
|
||||
find: '="SYSTEM_TAG"',
|
||||
replacement: {
|
||||
match: /\i.gradientClassName]\),style:/,
|
||||
replace: "$&{color:$self.colorIfServer(arguments[0])},_style:"
|
||||
// Override colorString with our custom color and disable gradients if applying the custom color.
|
||||
match: /&&null!=\i\.secondaryColor,(?<=colorString:(\i).+?(\i)=.+?)/,
|
||||
replace: (m, colorString, hasGradientColors) => `${m}` +
|
||||
`vcCustomUserColorsDummy=[${colorString},${hasGradientColors}]=$self.getMessageColorsVariables(arguments[0],${hasGradientColors}),`
|
||||
},
|
||||
predicate: () => !Settings.plugins.IrcColors.enabled
|
||||
},
|
||||
{
|
||||
find: "PrivateChannel.renderAvatar",
|
||||
replacement: {
|
||||
match: /(highlighted:\i,)/,
|
||||
match: /(subText:\i\(\),)/,
|
||||
replace: "$1style:{color:`${$self.colorDMList(arguments[0])}`},"
|
||||
},
|
||||
predicate: () => settings.store.dmList,
|
||||
},
|
||||
{
|
||||
find: "!1,wrapContent",
|
||||
find: '"AvatarWithText"',
|
||||
replacement: [
|
||||
{
|
||||
match: /(\}=\i)/,
|
||||
|
@ -125,22 +123,40 @@ export default definePlugin({
|
|||
],
|
||||
predicate: () => settings.store.dmList,
|
||||
},
|
||||
{
|
||||
find: '"Reply Chain Nudge")',
|
||||
replacement: {
|
||||
match: /(,color:)(\i),/,
|
||||
replace: "$1$self.colorInReplyingTo(arguments[0]) ?? $2,",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
colorDMList(a: any): string | undefined {
|
||||
const userId = a?.user?.id;
|
||||
if (!userId) return;
|
||||
const colorString = getCustomColorString(userId, true);
|
||||
if (colorString) return colorString;
|
||||
return "inherit";
|
||||
getMessageColorsVariables(context: any, hasGradientColors: boolean) {
|
||||
const colorString = this.colorIfServer(context);
|
||||
const originalColorString = context?.author?.colorString;
|
||||
|
||||
return [colorString, hasGradientColors && colorString === originalColorString];
|
||||
},
|
||||
|
||||
colorIfServer(a: any): string | undefined {
|
||||
const roleColor = a.author?.colorString;
|
||||
colorDMList(context: any): string | undefined {
|
||||
const userId = context?.user?.id;
|
||||
const colorString = getCustomColorString(userId, true);
|
||||
return colorString ?? "inherit";
|
||||
},
|
||||
|
||||
if (a?.channel?.guild_id && !settings.store.colorInServers) return roleColor;
|
||||
colorIfServer(context: any): string | undefined {
|
||||
const userId = context?.message?.author?.id;
|
||||
const colorString = context?.author?.colorString;
|
||||
|
||||
const color = getCustomColorString(a.message.author.id, true);
|
||||
return color ?? roleColor ?? undefined;
|
||||
}
|
||||
if (context?.channel?.guild_id && !settings.store.colorInServers) return colorString;
|
||||
|
||||
const color = getCustomColorString(userId, true);
|
||||
return color ?? colorString ?? undefined;
|
||||
},
|
||||
|
||||
colorInReplyingTo(a: any) {
|
||||
const { id } = a.reply.message.author;
|
||||
return getCustomColorString(id, true);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu } from "@webpack/common";
|
||||
import type { Guild } from "discord-types/general";
|
||||
import { zipSync } from "fflate";
|
||||
|
||||
const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => {
|
||||
// Assuming "privacy" is the correct ID for the group you want to modify.
|
||||
const group = findGroupChildrenByChildId("privacy", children);
|
||||
|
||||
if (group) {
|
||||
group.push(
|
||||
<Menu.MenuItem id="emoji.download" label="Download Emojis" action={() => zipServerEmojis(guild)}></Menu.MenuItem>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "EmojiDumper",
|
||||
description: "Context menu to dump and download a server's emojis.",
|
||||
authors: [EquicordDevs.Cortex, Devs.Samwich, EquicordDevs.Woosh],
|
||||
contextMenus: {
|
||||
"guild-context": Patch,
|
||||
"guild-header-popout": Patch
|
||||
}
|
||||
});
|
||||
|
||||
async function zipServerEmojis(guild: Guild) {
|
||||
|
||||
const emojis = Vencord.Webpack.Common.EmojiStore.getGuilds()[guild.id]?.emojis;
|
||||
if (!emojis) {
|
||||
return console.log("Server not found!");
|
||||
}
|
||||
|
||||
const fetchEmojis = async e => {
|
||||
const filename = e.id + (e.animated ? ".gif" : ".png");
|
||||
const emoji = await fetch("https://cdn.discordapp.com/emojis/" + filename + "?size=512&quality=lossless").then(res => res.blob());
|
||||
return { file: new Uint8Array(await emoji.arrayBuffer()), filename };
|
||||
};
|
||||
const emojiPromises = emojis.map(e => fetchEmojis(e));
|
||||
|
||||
Promise.all(emojiPromises)
|
||||
.then(results => {
|
||||
const emojis = zipSync(Object.fromEntries(results.map(({ file, filename }) => [filename, file])));
|
||||
const blob = new Blob([emojis], { type: "application/zip" });
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = `${guild.name}-emojis.zip`;
|
||||
link.click();
|
||||
link.remove();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from "@api/Commands";
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { sleep } from "@utils/misc";
|
||||
import definePlugin from "@utils/types";
|
||||
import {
|
||||
|
@ -140,7 +140,7 @@ const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
|||
export default definePlugin({
|
||||
name: "Encryptcord",
|
||||
description: "End-to-end encryption in Discord!",
|
||||
authors: [Devs.Inbestigator],
|
||||
authors: [Devs.Inbestigator, EquicordDevs.ItsAlex],
|
||||
patches: [
|
||||
{
|
||||
find: "INTERACTION_APPLICATION_COMMAND_INVALID_VERSION",
|
||||
|
@ -360,18 +360,28 @@ async function handleGroupData(groupData) {
|
|||
|
||||
// Handle joining group
|
||||
async function handleJoin(senderId: string, senderKey: string, encryptcordGroupMembers: object) {
|
||||
encryptcordGroupMembers[senderId] = { key: senderKey, parent: UserStore.getCurrentUser().id, child: null };
|
||||
encryptcordGroupMembers[UserStore.getCurrentUser().id].child = senderId;
|
||||
const currentUserId = UserStore.getCurrentUser().id;
|
||||
|
||||
if (!encryptcordGroupMembers[senderId]) {
|
||||
encryptcordGroupMembers[senderId] = { key: senderKey, parent: currentUserId, child: null };
|
||||
}
|
||||
|
||||
if (!encryptcordGroupMembers[currentUserId]) {
|
||||
encryptcordGroupMembers[currentUserId] = { key: "", parent: null, child: null };
|
||||
}
|
||||
encryptcordGroupMembers[currentUserId].child = senderId;
|
||||
|
||||
await DataStore.set("encryptcordGroupMembers", encryptcordGroupMembers);
|
||||
|
||||
const groupChannel = await DataStore.get("encryptcordChannelId");
|
||||
|
||||
const newMember = await UserUtils.getUser(senderId).catch(() => null);
|
||||
if (!newMember) return;
|
||||
|
||||
const membersData = {};
|
||||
Object.entries(encryptcordGroupMembers)
|
||||
.forEach(([memberId, value]) => {
|
||||
membersData[memberId] = value;
|
||||
});
|
||||
Object.entries(encryptcordGroupMembers).forEach(([memberId, value]) => {
|
||||
membersData[memberId] = value;
|
||||
});
|
||||
|
||||
const membersDataString = JSON.stringify({ members: membersData, channel: groupChannel });
|
||||
|
||||
|
@ -382,6 +392,7 @@ async function handleJoin(senderId: string, senderKey: string, encryptcordGroupM
|
|||
});
|
||||
|
||||
await Promise.all(dmPromises);
|
||||
|
||||
await MessageActions.receiveMessage(groupChannel, {
|
||||
...await createMessage("", senderId, groupChannel, 7), components: [{
|
||||
type: 1,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@import url("https://dablulite.github.io/css-snippets/BetterAuthApps/import.css");
|
|
@ -1 +0,0 @@
|
|||
@import url("https://dablulite.github.io/css-snippets/BetterStatusPicker/import.css");
|
|
@ -1 +0,0 @@
|
|||
@import url("https://raw.githubusercontent.com/gold-me/DiscordIcons/master/DiscordIcons.theme.css");
|
|
@ -1,128 +0,0 @@
|
|||
/* stylelint-disable selector-class-pattern */
|
||||
/* stylelint-disable color-function-notation */
|
||||
|
||||
:root {
|
||||
/* || Gradients */
|
||||
--gradient-special: 140deg, hsl(245deg, calc(var(--saturaton-factor, 1)*79%), 72%) 0%, hsl(287deg, calc(var(--saturaton-factor, 1)*80%), 70%) 100%;
|
||||
--gradient-blurple: 140deg, hsl(235deg, calc(var(--saturation-factor, 1)*85%), 72%) 0%, hsl(235deg, calc(var(--saturation-factor, 1)*85%), 60%) 100%;
|
||||
--gradient-green: 140deg, hsl(139deg, calc(var(--saturaton-factor, 1)*47%), 44%) 0%, hsl(139deg, calc(var(--saturaton-factor, 1)*66%), 24%) 100%;
|
||||
--gradient-yellow: 140deg, hsl(38deg, calc(var(--saturaton-factor, 1)*96%), 54%) 0%, hsl(38deg, calc(var(--saturaton-factor, 1)*82%), 41%) 100%;
|
||||
--gradient-red: 140deg, hsl(359deg, calc(var(--saturaton-factor, 1)*83%), 59%) 0%, hsl(359deg, calc(var(--saturaton-factor, 1)*54%), 37%) 100%;
|
||||
--gradient-grey: 140deg, hsl(214deg, calc(var(--saturaton-factor, 1)*10%), 50%) 0%, hsl(216deg, calc(var(--saturaton-factor, 1)*11%), 26%) 100%;
|
||||
|
||||
/* || Transitions */
|
||||
--button-transition: 0.1s linear;
|
||||
--font-default: 500;
|
||||
--font-hover: 525;
|
||||
--fontsize-hover: 15px;
|
||||
--transform-normal: scale(1);
|
||||
--transform-hover: scale(1.15);
|
||||
--button-transform-hover: scale(1.04);
|
||||
}
|
||||
|
||||
/* || Filled Buttons */
|
||||
.lookFilled-yCfaCM {
|
||||
transform: var(--transform-normal);
|
||||
transition: var(--button-transition);
|
||||
background: var(--gradient);
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM:hover {
|
||||
transform: var(--button-transform-hover);
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM[disabled] {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM.colorBrand-I6CyqQ {
|
||||
--gradient: linear-gradient(var(--gradient-blurple));
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM.colorGreen-3y-Z79,
|
||||
.lookFilled-yCfaCM.button_adcaac.buttonActive_adcaac {
|
||||
--gradient: linear-gradient(var(--gradient-green));
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM.colorYellow-Pgtmch {
|
||||
--gradient: linear-gradient(var(--gradient-yellow));
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM.colorRed-rQXKgM {
|
||||
--gradient: linear-gradient(var(--gradient-red));
|
||||
}
|
||||
|
||||
.lookFilled-yCfaCM.colorPrimary-2AuQVo,
|
||||
.lookFilled-yCfaCM.colorGrey-2iAG-B,
|
||||
.lookFilled-yCfaCM.buttonColor_adcaac {
|
||||
--gradient: linear-gradient(var(--gradient-grey));
|
||||
}
|
||||
|
||||
/* || Context Menus */
|
||||
.menu_d90b3d .item-1OdjEX:not(.hideInteraction-2jPGL_) {
|
||||
font-weight: var(--font-default);
|
||||
transition: var(--button-transition);
|
||||
}
|
||||
|
||||
.menu_d90b3d .item-1OdjEX:not(.hideInteraction-2jPGL_).focused-3qFvc8,
|
||||
.menu_d90b3d .item-1OdjEX:not(.hideInteraction-2jPGL_):active {
|
||||
font-size: var(--fontsize-hover);
|
||||
font-weight: var(--font-hover);
|
||||
background: var(--gradient);
|
||||
}
|
||||
|
||||
.menu_d90b3d .colorDefault-CDqZdO.focused-3qFvc8,
|
||||
.menu_d90b3d .colorDefault-CDqZdO:active {
|
||||
--gradient: linear-gradient(var(--gradient-blurple));
|
||||
}
|
||||
|
||||
.menu_d90b3d .colorDanger-3n-KnP.focused-3qFvc8,
|
||||
.menu_d90b3d .colorDanger-3n-KnP:active,
|
||||
.menu_d90b3d #status-picker-dnd.focused-3qFvc8,
|
||||
.menu_d90b3d #status-picker-dnd:active {
|
||||
--gradient: linear-gradient(var(--gradient-red));
|
||||
}
|
||||
|
||||
.menu_d90b3d .colorPremium-vwmYZQ.focused-3qFvc8,
|
||||
.menu_d90b3d .colorPremium-vwmYZQ:active {
|
||||
--gradient: linear-gradient(var(--gradient-special));
|
||||
}
|
||||
|
||||
.menu_d90b3d #status-picker-online.focused-3qFvc8,
|
||||
.menu_d90b3d #status-picker-online:active {
|
||||
--gradient: linear-gradient(var(--gradient-green));
|
||||
}
|
||||
|
||||
.menu_d90b3d #status-picker-idle.focused-3qFvc8,
|
||||
.menu_d90b3d #status-picker-idle:active {
|
||||
--gradient: linear-gradient(var(--gradient-yellow));
|
||||
}
|
||||
|
||||
.menu_d90b3d #status-picker-invisible.focused-3qFvc8,
|
||||
.menu_d90b3d #status-picker-invisible:active {
|
||||
--gradient: linear-gradient(var(--gradient-grey));
|
||||
}
|
||||
|
||||
/* || Message Actions */
|
||||
.wrapper_f7e168 .button_f7e168 {
|
||||
background: var(--gradient);
|
||||
}
|
||||
|
||||
.wrapper_f7e168 .button_f7e168 img,
|
||||
.wrapper_f7e168 .button_f7e168 svg {
|
||||
transition: var(--button-transition);
|
||||
transform: var(--transform-normal);
|
||||
}
|
||||
|
||||
.wrapper_f7e168 .button_f7e168:hover {
|
||||
--gradient: linear-gradient(var(--gradient-blurple));
|
||||
}
|
||||
|
||||
.wrapper_f7e168 .button_f7e168:hover svg {
|
||||
transform: var(--transform-hover);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.wrapper_f7e168 .button_f7e168.dangerous_f7e168:hover {
|
||||
--gradient: linear-gradient(var(--gradient-red));
|
||||
}
|
487
src/equicordplugins/equicordCSS/css/main.min.css
vendored
487
src/equicordplugins/equicordCSS/css/main.min.css
vendored
|
@ -1,487 +0,0 @@
|
|||
/* stylelint-disable property-no-vendor-prefix */
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
|
||||
:root {
|
||||
--settingsicons: 1;
|
||||
--si-size: 18px;
|
||||
--si-gap: 14px;
|
||||
--use-si: calc(var(--settingsicons, 1) / (var(--settingsicons, 1)));
|
||||
--si-myaccount: url("https://minidiscordthemes.github.io/SettingsIcons/svg/myaccount.svg");
|
||||
--si-profilecustomization: url("https://minidiscordthemes.github.io/SettingsIcons/svg/profilecustomization.svg");
|
||||
--si-privacysafety: url("https://minidiscordthemes.github.io/SettingsIcons/svg/privacysafety.svg");
|
||||
--si-familycenter: url("https://minidiscordthemes.github.io/SettingsIcons/svg/familycenter.svg");
|
||||
--si-authorizedapps: url("https://minidiscordthemes.github.io/SettingsIcons/svg/authorizedapps.svg");
|
||||
--si-sessions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/sessions.svg");
|
||||
--si-connections: url("https://minidiscordthemes.github.io/SettingsIcons/svg/connections.svg");
|
||||
--si-settingsclips: url("https://minidiscordthemes.github.io/SettingsIcons/svg/settingsclips.svg");
|
||||
--si-friendrequests: url("https://minidiscordthemes.github.io/SettingsIcons/svg/friendrequests.svg");
|
||||
--si-discordnitro: url("https://minidiscordthemes.github.io/SettingsIcons/svg/discordnitro.svg");
|
||||
--si-nitroserverboost: url("https://minidiscordthemes.github.io/SettingsIcons/svg/nitroserverboost.svg");
|
||||
--si-subscriptions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/subscriptions.svg");
|
||||
--si-libraryinventory: url("https://minidiscordthemes.github.io/SettingsIcons/svg/libraryinventory.svg");
|
||||
--si-billing: url("https://minidiscordthemes.github.io/SettingsIcons/svg/billing.svg");
|
||||
--si-appearance: url("https://minidiscordthemes.github.io/SettingsIcons/svg/appearance.svg");
|
||||
--si-accessibility: url("https://minidiscordthemes.github.io/SettingsIcons/svg/accessibility.svg");
|
||||
--si-voicevideo: url("https://minidiscordthemes.github.io/SettingsIcons/svg/voicevideo.svg");
|
||||
--si-textimages: url("https://minidiscordthemes.github.io/SettingsIcons/svg/textimages.svg");
|
||||
--si-notifications: url("https://minidiscordthemes.github.io/SettingsIcons/svg/notifications.svg");
|
||||
--si-keybinds: url("https://minidiscordthemes.github.io/SettingsIcons/svg/keybinds.svg");
|
||||
--si-language: url("https://minidiscordthemes.github.io/SettingsIcons/svg/language.svg");
|
||||
--si-windows: url("https://minidiscordthemes.github.io/SettingsIcons/svg/windows.svg");
|
||||
--si-streamermode: url("https://minidiscordthemes.github.io/SettingsIcons/svg/streamermode.svg");
|
||||
--si-rtcspeedtest: url("https://minidiscordthemes.github.io/SettingsIcons/svg/rtcspeedtest.svg");
|
||||
--si-advanced: url("https://minidiscordthemes.github.io/SettingsIcons/svg/advanced.svg");
|
||||
--si-activityprivacy: url("https://minidiscordthemes.github.io/SettingsIcons/svg/activityprivacy.svg");
|
||||
--si-gameactivity: url("https://minidiscordthemes.github.io/SettingsIcons/svg/gameactivity.svg");
|
||||
--si-overlay: url("https://minidiscordthemes.github.io/SettingsIcons/svg/overlay.svg");
|
||||
--si-changelog: url("https://minidiscordthemes.github.io/SettingsIcons/svg/changelog.svg");
|
||||
--si-merchandise: url("https://minidiscordthemes.github.io/SettingsIcons/svg/merchandise.svg");
|
||||
--si-hypesquadonline: url("https://minidiscordthemes.github.io/SettingsIcons/svg/hypesquadonline.svg");
|
||||
--si-powermodesettings: url("https://minidiscordthemes.github.io/SettingsIcons/svg/powermodesettings.svg");
|
||||
--si-experiments: url("https://minidiscordthemes.github.io/SettingsIcons/svg/experiments.svg");
|
||||
--si-developeroptions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/developeroptions.svg");
|
||||
--si-hotspotoptions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/hotspotoptions.svg");
|
||||
--si-dismissiblecontentoptions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/dismissiblecontentoptions.svg");
|
||||
--si-startuptimings: url("https://minidiscordthemes.github.io/SettingsIcons/svg/startuptimings.svg");
|
||||
--si-paymentflowmodals: url("https://minidiscordthemes.github.io/SettingsIcons/svg/paymentflowmodals.svg");
|
||||
--si-textplayground: url("https://minidiscordthemes.github.io/SettingsIcons/svg/textplayground.svg");
|
||||
--si-textcomponent: url("https://minidiscordthemes.github.io/SettingsIcons/svg/textcomponent.svg");
|
||||
--si-logout: url("https://minidiscordthemes.github.io/SettingsIcons/svg/logout.svg");
|
||||
--si-equicordsettings: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordsettings.svg");
|
||||
--si-equicordplugins: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordplugins.svg");
|
||||
--si-equicordthemes: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordthemes.svg");
|
||||
--si-equicordupdater: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordupdater.svg");
|
||||
--si-equicordcloud: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordcloud.svg");
|
||||
--si-equicordsettingssync: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordsettingssync.svg");
|
||||
--si-equicordpatchhelper: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vencordpatchhelper.svg");
|
||||
--si-equibop: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vesktop.svg");
|
||||
--si-vesktop: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vesktop.svg");
|
||||
--si-overview: url("https://minidiscordthemes.github.io/SettingsIcons/svg/overview.svg");
|
||||
--si-roles: url("https://minidiscordthemes.github.io/SettingsIcons/svg/roles.svg");
|
||||
--si-emoji: url("https://minidiscordthemes.github.io/SettingsIcons/svg/emoji.svg");
|
||||
--si-stickers: url("https://minidiscordthemes.github.io/SettingsIcons/svg/stickers.svg");
|
||||
--si-soundboard: url("https://minidiscordthemes.github.io/SettingsIcons/svg/soundboard.svg");
|
||||
--si-widget: url("https://minidiscordthemes.github.io/SettingsIcons/svg/widget.svg");
|
||||
--si-guildtemplates: url("https://minidiscordthemes.github.io/SettingsIcons/svg/guildtemplates.svg");
|
||||
--si-vanityurl: url("https://minidiscordthemes.github.io/SettingsIcons/svg/vanityurl.svg");
|
||||
--si-integrations: url("https://minidiscordthemes.github.io/SettingsIcons/svg/integrations.svg");
|
||||
--si-appdirectory: url("https://minidiscordthemes.github.io/SettingsIcons/svg/appdirectory.svg");
|
||||
--si-safety: url("https://minidiscordthemes.github.io/SettingsIcons/svg/safety.svg");
|
||||
--si-auditlog: url("https://minidiscordthemes.github.io/SettingsIcons/svg/auditlog.svg");
|
||||
--si-bans: url("https://minidiscordthemes.github.io/SettingsIcons/svg/bans.svg");
|
||||
--si-community: url("https://minidiscordthemes.github.io/SettingsIcons/svg/community.svg");
|
||||
--si-onboarding: url("https://minidiscordthemes.github.io/SettingsIcons/svg/onboarding.svg");
|
||||
--si-analytics: url("https://minidiscordthemes.github.io/SettingsIcons/svg/analytics.svg");
|
||||
--si-partner: url("https://minidiscordthemes.github.io/SettingsIcons/svg/partner.svg");
|
||||
--si-discovery: url("https://minidiscordthemes.github.io/SettingsIcons/svg/discovery.svg");
|
||||
--si-rolesubscriptions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/rolesubscriptions.svg");
|
||||
--si-guildpremium: url("https://minidiscordthemes.github.io/SettingsIcons/svg/guildpremium.svg");
|
||||
--si-members: url("https://minidiscordthemes.github.io/SettingsIcons/svg/members.svg");
|
||||
--si-instantinvites: url("https://minidiscordthemes.github.io/SettingsIcons/svg/instantinvites.svg");
|
||||
--si-delete: url("https://minidiscordthemes.github.io/SettingsIcons/svg/delete.svg");
|
||||
--si-permissions: url("https://minidiscordthemes.github.io/SettingsIcons/svg/permissions.svg");
|
||||
--si-default: url("https://minidiscordthemes.github.io/SettingsIcons/svg/default.svg");
|
||||
}
|
||||
|
||||
.sidebarRegion_c25c6d {
|
||||
flex-basis: calc(218px + var(--use-si)*(var(--si-size) + var(--si-gap))) !important
|
||||
}
|
||||
|
||||
.sidebar_c25c6d {
|
||||
width: calc(218px + var(--use-si)*(var(--si-size) + var(--si-gap))) !important
|
||||
}
|
||||
|
||||
.sidebar_c25c6d :is(.item_a0 .icon_f7189e, .premiumLabel_ae3c77>svg, .premiumLabel_ae3c77 img, .tabBarItemContainer_e7c031>svg, .tabBarItemContainer_e7c031 img) {
|
||||
transform: scaleX(calc(1 - var(--use-si)))
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0 {
|
||||
display: flex;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0::before {
|
||||
content: "";
|
||||
flex: 0 0 auto;
|
||||
width: calc(var(--use-si)*var(--si-size));
|
||||
height: calc(var(--use-si)*var(--si-size));
|
||||
margin-right: calc(var(--use-si)*var(--si-size)/2);
|
||||
background: currentcolor;
|
||||
z-index: 2;
|
||||
-webkit-mask: var(--si-default) center/contain no-repeat;
|
||||
mask: var(--si-default) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="My Account"]::before {
|
||||
-webkit-mask: var(--si-myaccount) center/contain no-repeat;
|
||||
mask: var(--si-myaccount) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Profile Customization"]::before {
|
||||
-webkit-mask: var(--si-profilecustomization) center/contain no-repeat;
|
||||
mask: var(--si-profilecustomization) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Privacy & Safety"]::before {
|
||||
-webkit-mask: var(--si-privacysafety) center/contain no-repeat;
|
||||
mask: var(--si-privacysafety) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Family Center"]::before {
|
||||
-webkit-mask: var(--si-familycenter) center/contain no-repeat;
|
||||
mask: var(--si-familycenter) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Authorized Apps"]::before {
|
||||
-webkit-mask: var(--si-authorizedapps) center/contain no-repeat;
|
||||
mask: var(--si-authorizedapps) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Sessions"]::before {
|
||||
-webkit-mask: var(--si-sessions) center/contain no-repeat;
|
||||
mask: var(--si-sessions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Connections"]::before {
|
||||
-webkit-mask: var(--si-connections) center/contain no-repeat;
|
||||
mask: var(--si-connections) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Settings Clips"]::before {
|
||||
-webkit-mask: var(--si-settingsclips) center/contain no-repeat;
|
||||
mask: var(--si-settingsclips) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Friend Requests"]::before {
|
||||
-webkit-mask: var(--si-friendrequests) center/contain no-repeat;
|
||||
mask: var(--si-friendrequests) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Discord Nitro"]::before {
|
||||
-webkit-mask: var(--si-discordnitro) center/contain no-repeat;
|
||||
mask: var(--si-discordnitro) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Nitro Server Boost"]::before {
|
||||
-webkit-mask: var(--si-nitroserverboost) center/contain no-repeat;
|
||||
mask: var(--si-nitroserverboost) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Subscriptions"]::before {
|
||||
-webkit-mask: var(--si-subscriptions) center/contain no-repeat;
|
||||
mask: var(--si-subscriptions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Library Inventory"]::before {
|
||||
-webkit-mask: var(--si-libraryinventory) center/contain no-repeat;
|
||||
mask: var(--si-libraryinventory) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Billing"]::before {
|
||||
-webkit-mask: var(--si-billing) center/contain no-repeat;
|
||||
mask: var(--si-billing) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Appearance"]::before {
|
||||
-webkit-mask: var(--si-appearance) center/contain no-repeat;
|
||||
mask: var(--si-appearance) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Accessibility"]::before {
|
||||
-webkit-mask: var(--si-accessibility) center/contain no-repeat;
|
||||
mask: var(--si-accessibility) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Voice & Video"]::before {
|
||||
-webkit-mask: var(--si-voicevideo) center/contain no-repeat;
|
||||
mask: var(--si-voicevideo) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Text & Images"]::before {
|
||||
-webkit-mask: var(--si-textimages) center/contain no-repeat;
|
||||
mask: var(--si-textimages) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Notifications"]::before {
|
||||
-webkit-mask: var(--si-notifications) center/contain no-repeat;
|
||||
mask: var(--si-notifications) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Keybinds"]::before {
|
||||
-webkit-mask: var(--si-keybinds) center/contain no-repeat;
|
||||
mask: var(--si-keybinds) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Language"]::before {
|
||||
-webkit-mask: var(--si-language) center/contain no-repeat;
|
||||
mask: var(--si-language) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Windows"]::before {
|
||||
-webkit-mask: var(--si-windows) center/contain no-repeat;
|
||||
mask: var(--si-windows) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Streamer Mode"]::before {
|
||||
-webkit-mask: var(--si-streamermode) center/contain no-repeat;
|
||||
mask: var(--si-streamermode) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="RTC Speed Test"]::before {
|
||||
-webkit-mask: var(--si-rtcspeedtest) center/contain no-repeat;
|
||||
mask: var(--si-rtcspeedtest) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Advanced"]::before {
|
||||
-webkit-mask: var(--si-advanced) center/contain no-repeat;
|
||||
mask: var(--si-advanced) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Activity Privacy"]::before {
|
||||
-webkit-mask: var(--si-activityprivacy) center/contain no-repeat;
|
||||
mask: var(--si-activityprivacy) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Game Activity"]::before {
|
||||
-webkit-mask: var(--si-gameactivity) center/contain no-repeat;
|
||||
mask: var(--si-gameactivity) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Overlay"]::before {
|
||||
-webkit-mask: var(--si-overlay) center/contain no-repeat;
|
||||
mask: var(--si-overlay) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="changelog"]::before {
|
||||
-webkit-mask: var(--si-changelog) center/contain no-repeat;
|
||||
mask: var(--si-changelog) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="merchandise"]::before {
|
||||
-webkit-mask: var(--si-merchandise) center/contain no-repeat;
|
||||
mask: var(--si-merchandise) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Hypesquad Online"]::before {
|
||||
-webkit-mask: var(--si-hypesquadonline) center/contain no-repeat;
|
||||
mask: var(--si-hypesquadonline) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Powermode Settings"]::before {
|
||||
-webkit-mask: var(--si-powermodesettings) center/contain no-repeat;
|
||||
mask: var(--si-powermodesettings) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Experiments"]::before {
|
||||
-webkit-mask: var(--si-experiments) center/contain no-repeat;
|
||||
mask: var(--si-experiments) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Developer Options"]::before {
|
||||
-webkit-mask: var(--si-developeroptions) center/contain no-repeat;
|
||||
mask: var(--si-developeroptions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Hotspot Options"]::before {
|
||||
-webkit-mask: var(--si-hotspotoptions) center/contain no-repeat;
|
||||
mask: var(--si-hotspotoptions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Dismissible Content Options"]::before {
|
||||
-webkit-mask: var(--si-dismissiblecontentoptions) center/contain no-repeat;
|
||||
mask: var(--si-dismissiblecontentoptions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="StartupTimings"]::before {
|
||||
-webkit-mask: var(--si-startuptimings) center/contain no-repeat;
|
||||
mask: var(--si-startuptimings) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Payment Flow Modals"]::before {
|
||||
-webkit-mask: var(--si-paymentflowmodals) center/contain no-repeat;
|
||||
mask: var(--si-paymentflowmodals) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Text Playground"]::before {
|
||||
-webkit-mask: var(--si-textplayground) center/contain no-repeat;
|
||||
mask: var(--si-textplayground) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Text Component"]::before {
|
||||
-webkit-mask: var(--si-textcomponent) center/contain no-repeat;
|
||||
mask: var(--si-textcomponent) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="logout"]::before {
|
||||
-webkit-mask: var(--si-logout) center/contain no-repeat;
|
||||
mask: var(--si-logout) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordSettings"]::before {
|
||||
-webkit-mask: var(--si-equicordsettings) center/contain no-repeat;
|
||||
mask: var(--si-equicordsettings) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordPlugins"]::before {
|
||||
-webkit-mask: var(--si-equicordplugins) center/contain no-repeat;
|
||||
mask: var(--si-equicordplugins) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordThemes"]::before {
|
||||
-webkit-mask: var(--si-equicordthemes) center/contain no-repeat;
|
||||
mask: var(--si-equicordthemes) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordUpdater"]::before {
|
||||
-webkit-mask: var(--si-equicordupdater) center/contain no-repeat;
|
||||
mask: var(--si-equicordupdater) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordCloud"]::before {
|
||||
-webkit-mask: var(--si-equicordcloud) center/contain no-repeat;
|
||||
mask: var(--si-equicordcloud) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordSettingsSync"]::before {
|
||||
-webkit-mask: var(--si-equicordsettingssync) center/contain no-repeat;
|
||||
mask: var(--si-equicordsettingssync) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EquicordPatchHelper"]::before {
|
||||
-webkit-mask: var(--si-equicordpatchhelper) center/contain no-repeat;
|
||||
mask: var(--si-equicordpatchhelper) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Equibop"]::before {
|
||||
-webkit-mask: var(--si-equibop) center/contain no-repeat;
|
||||
mask: var(--si-equibop) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="Vesktop"]::before {
|
||||
-webkit-mask: var(--si-vesktop) center/contain no-repeat;
|
||||
mask: var(--si-vesktop) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="OVERVIEW"]::before {
|
||||
-webkit-mask: var(--si-overview) center/contain no-repeat;
|
||||
mask: var(--si-overview) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="ROLES"]::before {
|
||||
-webkit-mask: var(--si-roles) center/contain no-repeat;
|
||||
mask: var(--si-roles) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="EMOJI"]::before {
|
||||
-webkit-mask: var(--si-emoji) center/contain no-repeat;
|
||||
mask: var(--si-emoji) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="STICKERS"]::before {
|
||||
-webkit-mask: var(--si-stickers) center/contain no-repeat;
|
||||
mask: var(--si-stickers) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="SOUNDBOARD"]::before {
|
||||
-webkit-mask: var(--si-soundboard) center/contain no-repeat;
|
||||
mask: var(--si-soundboard) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="WIDGET"]::before {
|
||||
-webkit-mask: var(--si-widget) center/contain no-repeat;
|
||||
mask: var(--si-widget) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="GUILD_TEMPLATES"]::before {
|
||||
-webkit-mask: var(--si-guildtemplates) center/contain no-repeat;
|
||||
mask: var(--si-guildtemplates) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="VANITY_URL"]::before {
|
||||
-webkit-mask: var(--si-vanityurl) center/contain no-repeat;
|
||||
mask: var(--si-vanityurl) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="INTEGRATIONS"]::before {
|
||||
-webkit-mask: var(--si-integrations) center/contain no-repeat;
|
||||
mask: var(--si-integrations) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="APP_DIRECTORY"]::before {
|
||||
-webkit-mask: var(--si-appdirectory) center/contain no-repeat;
|
||||
mask: var(--si-appdirectory) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="SAFETY"]::before {
|
||||
-webkit-mask: var(--si-safety) center/contain no-repeat;
|
||||
mask: var(--si-safety) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="AUDIT_LOG"]::before {
|
||||
-webkit-mask: var(--si-auditlog) center/contain no-repeat;
|
||||
mask: var(--si-auditlog) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="BANS"]::before {
|
||||
-webkit-mask: var(--si-bans) center/contain no-repeat;
|
||||
mask: var(--si-bans) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="COMMUNITY"]::before {
|
||||
-webkit-mask: var(--si-community) center/contain no-repeat;
|
||||
mask: var(--si-community) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="ONBOARDING"]::before {
|
||||
-webkit-mask: var(--si-onboarding) center/contain no-repeat;
|
||||
mask: var(--si-onboarding) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="ANALYTICS"]::before {
|
||||
-webkit-mask: var(--si-analytics) center/contain no-repeat;
|
||||
mask: var(--si-analytics) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="PARTNER"]::before {
|
||||
-webkit-mask: var(--si-partner) center/contain no-repeat;
|
||||
mask: var(--si-partner) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="DISCOVERY"]::before {
|
||||
-webkit-mask: var(--si-discovery) center/contain no-repeat;
|
||||
mask: var(--si-discovery) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="ROLE_SUBSCRIPTIONS"]::before {
|
||||
-webkit-mask: var(--si-rolesubscriptions) center/contain no-repeat;
|
||||
mask: var(--si-rolesubscriptions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="GUILD_PREMIUM"]::before {
|
||||
-webkit-mask: var(--si-guildpremium) center/contain no-repeat;
|
||||
mask: var(--si-guildpremium) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="MEMBERS"]::before {
|
||||
-webkit-mask: var(--si-members) center/contain no-repeat;
|
||||
mask: var(--si-members) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="INSTANT_INVITES"]::before {
|
||||
-webkit-mask: var(--si-instantinvites) center/contain no-repeat;
|
||||
mask: var(--si-instantinvites) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="DELETE"]::before {
|
||||
-webkit-mask: var(--si-delete) center/contain no-repeat;
|
||||
mask: var(--si-delete) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0[data-tab-id="PERMISSIONS"]::before {
|
||||
-webkit-mask: var(--si-permissions) center/contain no-repeat;
|
||||
mask: var(--si-permissions) center/contain no-repeat
|
||||
}
|
||||
|
||||
.sidebar_c25c6d .side_a0 .item_a0>div {
|
||||
flex: 1 1 auto
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
@import url("https://raw.githubusercontent.com/coolesding/snippets/main/import/fixnitrothemes.css");
|
|
@ -1,9 +0,0 @@
|
|||
@import url("https://raw.githubusercontent.com/Equicord/Equicord/main/src/equicordplugins/equicordCSS/css/main.min.css");
|
||||
|
||||
/* https://github.com/MiniDiscordThemes/SettingsIcons#customisation */
|
||||
|
||||
:root {
|
||||
--settingsicons: 1;
|
||||
--si-size: 18px;
|
||||
--si-gap: 14px;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
@import url("https://dablulite.github.io/css-snippets/UserReimagined/import.css");
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Import required modules and components
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
// Importing the style managed fixes on and off switch
|
||||
import betterauthapps from "./css/betterauthapps.css?managed";
|
||||
import betterstatuspicker from "./css/betterstatuspicker.css?managed";
|
||||
import discordicons from "./css/discordicons.css?managed";
|
||||
import gradientbuttons from "./css/gradientbuttons.css?managed";
|
||||
import nitrothemesfix from "./css/nitrothemesfix.css?managed";
|
||||
import settingsicons from "./css/settingsicons.css?managed";
|
||||
import userreimagined from "./css/userreimagined.css?managed";
|
||||
|
||||
// Forcing restartNeeded: true to not overcomplicate the live update of the settings using FluxDispatcher and making it complex
|
||||
const settings = definePluginSettings({
|
||||
betterAuthApps: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Better Auth Apps CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
betterStatusPicker: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Better Status Picker CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
discordicons: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Discord Icons CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
gradientButtons: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Gradient Buttons CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
nitroThemesFix: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Fix Nitro Themes CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
settingsIcons: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Settings Icons CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
userReimagined: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable User Reimagined CSS",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
let settingsArray: Array<any> = [];
|
||||
let cssArray: Array<any> = [];
|
||||
|
||||
export default definePlugin({
|
||||
name: "EquicordCSS",
|
||||
description: "CSS for Equicord users. You will need to look at the settings.",
|
||||
authors: [EquicordDevs.thororen, EquicordDevs.Panniku],
|
||||
dependencies: ["ThemeAttributes"],
|
||||
settings,
|
||||
start() {
|
||||
|
||||
// Push variables to array to iterate on start() and stop()
|
||||
settingsArray.push(
|
||||
settings.store.betterAuthApps,
|
||||
settings.store.betterStatusPicker,
|
||||
settings.store.discordicons,
|
||||
settings.store.gradientButtons,
|
||||
settings.store.nitroThemesFix,
|
||||
settings.store.settingsIcons,
|
||||
settings.store.userReimagined
|
||||
);
|
||||
cssArray.push(
|
||||
betterauthapps,
|
||||
betterstatuspicker,
|
||||
discordicons,
|
||||
gradientbuttons,
|
||||
nitrothemesfix,
|
||||
settingsicons,
|
||||
userreimagined
|
||||
);
|
||||
|
||||
settingsArray.forEach((s, i) => {
|
||||
if (s) enableStyle(cssArray[i]);
|
||||
});
|
||||
},
|
||||
stop() {
|
||||
|
||||
settingsArray.forEach((s, i) => {
|
||||
if (s) disableStyle(cssArray[i]);
|
||||
});
|
||||
|
||||
settingsArray = [];
|
||||
cssArray = [];
|
||||
}
|
||||
});
|
|
@ -24,7 +24,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { Menu, Popout, useState } from "@webpack/common";
|
||||
import { Menu, Popout, useRef, useState } from "@webpack/common";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
|
||||
|
@ -116,6 +116,7 @@ function VencordPopoutIcon() {
|
|||
}
|
||||
|
||||
function VencordPopoutButton() {
|
||||
const buttonRef = useRef(null);
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
return (
|
||||
|
@ -126,10 +127,12 @@ function VencordPopoutButton() {
|
|||
animation={Popout.Animation.NONE}
|
||||
shouldShow={show}
|
||||
onRequestClose={() => setShow(false)}
|
||||
targetElementRef={buttonRef}
|
||||
renderPopout={() => VencordPopout(() => setShow(false))}
|
||||
>
|
||||
{(_, { isShown }) => (
|
||||
<HeaderBarIcon
|
||||
ref={buttonRef}
|
||||
className="vc-toolbox-btn"
|
||||
onClick={() => setShow(v => !v)}
|
||||
tooltip={isShown ? null : "Equicord Toolbox"}
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
import "./styles.css";
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Clipboard, Toasts } from "@webpack/common";
|
||||
import { Toasts } from "@webpack/common";
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
|
@ -97,7 +98,7 @@ export default definePlugin({
|
|||
|
||||
copyContactToClipboard() {
|
||||
if (this.contactList) {
|
||||
Clipboard.copy(JSON.stringify(this.contactList));
|
||||
copyToClipboard(JSON.stringify(this.contactList));
|
||||
Toasts.show({
|
||||
message: "Contacts copied to clipboard successfully.",
|
||||
type: Toasts.Type.SUCCESS,
|
||||
|
|
|
@ -5,16 +5,17 @@
|
|||
*/
|
||||
|
||||
import { Text, Tooltip } from "@webpack/common";
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentProps, RefObject } from "react";
|
||||
|
||||
export interface BuilderButtonProps {
|
||||
label?: string | undefined;
|
||||
tooltip?: string | undefined;
|
||||
selectedStyle?: ComponentProps<"div">["style"];
|
||||
buttonProps?: ComponentProps<"div"> | undefined;
|
||||
buttonRef?: RefObject<null>;
|
||||
}
|
||||
|
||||
export const BuilderButton = ({ label, tooltip, selectedStyle, buttonProps }: BuilderButtonProps) => (
|
||||
export const BuilderButton = ({ buttonRef, label, tooltip, selectedStyle, buttonProps }: BuilderButtonProps) => (
|
||||
<Tooltip text={tooltip} shouldShow={!!tooltip}>
|
||||
{tooltipProps => (
|
||||
<div style={{ width: "60px" }}>
|
||||
|
@ -32,6 +33,7 @@ export const BuilderButton = ({ label, tooltip, selectedStyle, buttonProps }: Bu
|
|||
borderRadius: "4px",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
ref={buttonRef}
|
||||
>
|
||||
{!selectedStyle && (
|
||||
<svg
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Popout } from "@webpack/common";
|
||||
import { Popout, useRef } from "@webpack/common";
|
||||
|
||||
import { BuilderButton, type BuilderButtonProps, CustomColorPicker, type CustomColorPickerProps } from ".";
|
||||
|
||||
|
@ -13,29 +13,34 @@ export interface BuilderColorButtonProps extends Pick<BuilderButtonProps, "label
|
|||
setColor: (color: number | null) => void;
|
||||
}
|
||||
|
||||
export const BuilderColorButton = ({ label, color, setColor, suggestedColors }: BuilderColorButtonProps) => (
|
||||
<Popout
|
||||
position="bottom"
|
||||
renderPopout={() => (
|
||||
<CustomColorPicker
|
||||
value={color}
|
||||
onChange={setColor}
|
||||
showEyeDropper={true}
|
||||
suggestedColors={suggestedColors}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{popoutProps => {
|
||||
const hexColor = color ? "#" + color.toString(16).padStart(6, "0") : undefined;
|
||||
|
||||
return (
|
||||
<BuilderButton
|
||||
label={label}
|
||||
tooltip={hexColor}
|
||||
selectedStyle={hexColor ? { background: hexColor } : undefined}
|
||||
buttonProps={popoutProps}
|
||||
export function BuilderColorButton({ label, color, setColor, suggestedColors }: BuilderColorButtonProps) {
|
||||
const buttonRef = useRef(null);
|
||||
return (
|
||||
<Popout
|
||||
position="bottom"
|
||||
targetElementRef={buttonRef}
|
||||
renderPopout={() => (
|
||||
<CustomColorPicker
|
||||
value={color}
|
||||
onChange={setColor}
|
||||
showEyeDropper={true}
|
||||
suggestedColors={suggestedColors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Popout>
|
||||
);
|
||||
)}
|
||||
>
|
||||
{popoutProps => {
|
||||
const hexColor = color ? "#" + color.toString(16).padStart(6, "0") : undefined;
|
||||
|
||||
return (
|
||||
<BuilderButton
|
||||
label={label}
|
||||
tooltip={hexColor}
|
||||
selectedStyle={hexColor ? { background: hexColor } : undefined}
|
||||
buttonProps={popoutProps}
|
||||
buttonRef={buttonRef}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Popout>
|
||||
);
|
||||
}
|
||||
|
|
83
src/equicordplugins/fastDeleteChannels/index.tsx
Normal file
83
src/equicordplugins/fastDeleteChannels/index.tsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Constants, PermissionsBits, PermissionStore, React, RestAPI, useCallback, useEffect, useState } from "@webpack/common";
|
||||
|
||||
const showIcon = () => {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const handleKeys = useCallback(e => {
|
||||
const keysHeld = e.ctrlKey && e.altKey;
|
||||
setShow(keysHeld);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", handleKeys);
|
||||
window.addEventListener("keyup", handleKeys);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeys);
|
||||
window.removeEventListener("keyup", handleKeys);
|
||||
};
|
||||
}, [handleKeys]);
|
||||
|
||||
return show;
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "FastDeleteChannels",
|
||||
description: "Adds a trash icon to delete channels when holding ctrl + alt",
|
||||
authors: [EquicordDevs.thororen],
|
||||
patches: [
|
||||
// TY TypingIndicator
|
||||
{
|
||||
find: "UNREAD_IMPORTANT:",
|
||||
replacement: {
|
||||
match: /\.name,{.{0,140}\.children.+?:null(?<=,channel:(\i).+?)/,
|
||||
replace: "$&,$self.TrashIcon($1)"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "M11 9H4C2.89543 9 2 8.10457 2 7V1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1V7C0 9.20914 1.79086 11 4 11H11C11.5523 11 12 10.5523 12 10C12 9.44771 11.5523 9 11 9Z",
|
||||
replacement: {
|
||||
match: /mentionsCount:\i.+?null(?<=channel:(\i).+?)/,
|
||||
replace: "$&,$self.TrashIcon($1)"
|
||||
}
|
||||
}
|
||||
],
|
||||
TrashIcon: channel => {
|
||||
const show = showIcon();
|
||||
|
||||
if (!show || !PermissionStore.can(PermissionsBits.MANAGE_CHANNELS, channel)) return null;
|
||||
|
||||
return (
|
||||
<span
|
||||
onClick={() => RestAPI.del({ url: Constants.Endpoints.CHANNEL(channel.id) })}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
color="#ed4245"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M14.25 1c.41 0 .75.34.75.75V3h5.25c.41 0 .75.34.75.75v.5c0 .41-.34.75-.75.75H3.75A.75.75 0 0 1 3 4.25v-.5c0-.41.34-.75.75-.75H9V1.75c0-.41.34-.75.75-.75h4.5Z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.06 7a1 1 0 0 0-1 1.06l.76 12.13a3 3 0 0 0 3 2.81h8.36a3 3 0 0 0 3-2.81l.75-12.13a1 1 0 0 0-1-1.06H5.07ZM11 12a1 1 0 1 0-2 0v6a1 1 0 1 0 2 0v-6Zm3-1a1 1 0 1 1 1 1v6a1 1 0 1 1-2 0v-6a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -21,7 +21,7 @@ import { disableStyle, enableStyle } from "@api/Styles";
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { ChannelStore, MessageStore, ReactDOM, Toasts } from "@webpack/common";
|
||||
import { ChannelStore, createRoot, MessageStore, Toasts } from "@webpack/common";
|
||||
import Message from "discord-types/general/Message";
|
||||
import { Root } from "react-dom/client";
|
||||
|
||||
|
@ -101,7 +101,7 @@ export default definePlugin({
|
|||
madeComponent = true;
|
||||
element = document.createElement("div");
|
||||
document.querySelector("[class^=base_]")!.appendChild(element);
|
||||
root = ReactDOM.createRoot(element);
|
||||
root = createRoot(element);
|
||||
}
|
||||
root!.render(<ReplyNavigator replies={replies} />);
|
||||
}
|
||||
|
|
|
@ -34,11 +34,10 @@ export default definePlugin({
|
|||
patches: [
|
||||
// Taken from AnonymiseFileNames
|
||||
{
|
||||
find: "instantBatchUpload:",
|
||||
find: 'type:"UPLOAD_START"',
|
||||
replacement: {
|
||||
match: /uploadFiles:(\i),/,
|
||||
replace:
|
||||
"uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=$self.fixExt(f)),$1(...args)),",
|
||||
match: /await \i\.uploadFiles\((\i),/,
|
||||
replace: "$1.forEach($self.fixExt),$&"
|
||||
},
|
||||
predicate: () => !Settings.plugins.AnonymiseFileNames.enabled,
|
||||
},
|
||||
|
|
53
src/equicordplugins/forwardAnywhere/index.ts
Normal file
53
src/equicordplugins/forwardAnywhere/index.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { sendMessage } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
// Taken From Signature :)
|
||||
const settings = definePluginSettings({
|
||||
forwardPreface: {
|
||||
description: "What should forwarded from be prefaced with",
|
||||
type: OptionType.SELECT,
|
||||
options: [
|
||||
{ label: ">", value: ">", default: true },
|
||||
{ label: "-#", value: "-#" }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "ForwardAnywhere",
|
||||
description: "If a forward fails send it as a normal message also allows nsfw forwards",
|
||||
authors: [EquicordDevs.thororen],
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::MESSAGE_FORWARDING_NSFW_NOT_ALLOWED}",
|
||||
replacement: {
|
||||
match: /if\((\i)\.isNSFW\(\)&&.{0,25}\)\)\)/,
|
||||
replace: "if(false)",
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "#{intl::MESSAGE_ACTION_FORWARD_TO}",
|
||||
replacement: {
|
||||
match: /(?<=let (\i)=.{0,25}rejected.{0,25}\);)(?=.{0,25}message:(\i))/,
|
||||
replace: "if ($1) return $self.sendForward($1,$2);",
|
||||
}
|
||||
},
|
||||
],
|
||||
sendForward(channels: any, message: Message) {
|
||||
for (const c of channels) {
|
||||
sendMessage(c.id, {
|
||||
content: `${message.content}\n${settings.store.forwardPreface} Forwarded from <#${message.channel_id}>`
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -6,8 +6,9 @@
|
|||
|
||||
import "./styles.css";
|
||||
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, Clipboard, Flex, Forms, Parser, Text, useEffect, useState } from "@webpack/common";
|
||||
import { Button, Flex, Forms, Parser, Text, useEffect, useState } from "@webpack/common";
|
||||
|
||||
import { FriendInvite } from "./types";
|
||||
|
||||
|
@ -51,7 +52,7 @@ function FriendInviteCard({ invite }: { invite: FriendInvite; }) {
|
|||
<CopyButton
|
||||
copyText="Copy"
|
||||
copiedText="Copied!"
|
||||
onClick={() => Clipboard.copy(`https://discord.gg/${invite.code}`)}
|
||||
onClick={() => copyToClipboard(`https://discord.gg/${invite.code}`)}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
|
|
@ -17,9 +17,10 @@ export default definePlugin({
|
|||
{
|
||||
find: "#{intl::ADD_FRIEND})}),(",
|
||||
replacement: {
|
||||
match: /\.Fragment[^]*?children:\[[^]*?}\)/,
|
||||
match: /header,children:\[.*?\{\}\)/,
|
||||
replace: "$&,$self.FriendCodesPanel"
|
||||
}
|
||||
},
|
||||
noWarn: true,
|
||||
}
|
||||
],
|
||||
|
||||
|
|
|
@ -10,10 +10,11 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co
|
|||
import { DataStore } from "@api/index";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Alerts, Button, Clipboard, ContextMenuApi, FluxDispatcher, Forms, Menu, React, showToast, TextInput, Toasts, useCallback, useState } from "@webpack/common";
|
||||
import { Alerts, Button, ContextMenuApi, FluxDispatcher, Forms, Menu, React, showToast, TextInput, Toasts, useCallback, useState } from "@webpack/common";
|
||||
|
||||
import { addToCollection, cache_collections, createCollection, DATA_COLLECTION_NAME, deleteCollection, fixPrefix, getCollections, getGifById, getItemCollectionNameFromId, moveGifToCollection, refreshCacheCollection, removeFromCollection, renameCollection } from "./utils/collectionManager";
|
||||
import { getFormat } from "./utils/getFormat";
|
||||
|
@ -526,7 +527,7 @@ const RemoveItemContextMenu = ({ type, nameOrId, instance }) => (
|
|||
action={() => {
|
||||
const gifInfo = getGifById(nameOrId);
|
||||
if (!gifInfo) return;
|
||||
Clipboard.copy(gifInfo.url);
|
||||
copyToClipboard(gifInfo.url);
|
||||
showToast("URL copied to clipboard", Toasts.Type.SUCCESS);
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -76,7 +76,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: "action:\"PRESS_APP_CONNECTION\"",
|
||||
find: "#{intl::CONNECTIONS}),scrollIntoView",
|
||||
replacement: {
|
||||
match: /(?<=user:(\i).{0,15}displayProfile:(\i).*?CONNECTIONS.{0,100}\}\)\}\))/,
|
||||
replace: ",$self.ProfilePopoutComponent({ user: $1, displayProfile: $2 })"
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, Clipboard, Forms, TextInput, Toasts, useState } from "@webpack/common";
|
||||
import { Button, Forms, TextInput, Toasts, useState } from "@webpack/common";
|
||||
|
||||
import { darkenColorHex, generateRandomColorHex, saturateColorHex } from "./generateTheme";
|
||||
import { themes } from "./themeDefinitions";
|
||||
|
@ -58,10 +59,7 @@ function copyPreset(name: string) {
|
|||
name: "${name}"
|
||||
}
|
||||
`;
|
||||
if (Clipboard.SUPPORTS_COPY) {
|
||||
Clipboard.copy(template);
|
||||
}
|
||||
|
||||
copyToClipboard(template);
|
||||
}
|
||||
|
||||
function CopyPresetComponent() {
|
||||
|
@ -229,9 +227,7 @@ export function ColorPick({ propertyname }: { propertyname: string; }) {
|
|||
|
||||
|
||||
function copyCSS() {
|
||||
if (Clipboard.SUPPORTS_COPY) {
|
||||
Clipboard.copy(getCSS(parseFontContent()));
|
||||
}
|
||||
copyToClipboard(getCSS(parseFontContent()));
|
||||
}
|
||||
|
||||
function parseFontContent() {
|
||||
|
|
75
src/equicordplugins/guildPickerDumper/index.tsx
Normal file
75
src/equicordplugins/guildPickerDumper/index.tsx
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { migratePluginSettings } from "@api/Settings";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { EmojiStore, Menu, StickersStore } from "@webpack/common";
|
||||
import type { Guild } from "discord-types/general";
|
||||
import { zipSync } from "fflate";
|
||||
|
||||
const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => {
|
||||
// Assuming "privacy" is the correct ID for the group you want to modify.
|
||||
const group = findGroupChildrenByChildId("privacy", children);
|
||||
|
||||
if (group) {
|
||||
group.push(
|
||||
<>
|
||||
<Menu.MenuItem id="emoji.download" label="Download Emojis" action={() => zipGuildAssets(guild, "emojis")}></Menu.MenuItem>
|
||||
<Menu.MenuItem id="sticker.download" label="Download Stickers" action={() => zipGuildAssets(guild, "stickers")}></Menu.MenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
async function zipGuildAssets(guild: Guild, type: "emojis" | "stickers") {
|
||||
const isEmojis = type === "emojis";
|
||||
const items = isEmojis
|
||||
? EmojiStore.getGuilds()[guild.id]?.emojis
|
||||
: StickersStore.getStickersByGuildId(guild.id);
|
||||
|
||||
if (!items) {
|
||||
return console.log("Server not found!");
|
||||
}
|
||||
|
||||
const fetchAsset = async e => {
|
||||
const ext = e.animated ? ".gif" : ".png";
|
||||
const filename = e.id + ext;
|
||||
const url = isEmojis
|
||||
? `https://${window.GLOBAL_ENV.MEDIA_PROXY_ENDPOINT}/emojis/${filename}?size=512&quality=lossless`
|
||||
: `https://${window.GLOBAL_ENV.MEDIA_PROXY_ENDPOINT}/stickers/${filename}?size=4096&lossless=true`;
|
||||
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
return { file: new Uint8Array(await blob.arrayBuffer()), filename };
|
||||
};
|
||||
|
||||
const assetPromises = items.map(e => fetchAsset(e));
|
||||
|
||||
Promise.all(assetPromises)
|
||||
.then(results => {
|
||||
const zipped = zipSync(Object.fromEntries(results.map(({ file, filename }) => [filename, file])));
|
||||
const blob = new Blob([zipped], { type: "application/zip" });
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = `${guild.name}-${type}.zip`;
|
||||
link.click();
|
||||
link.remove();
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
migratePluginSettings("GuildPickerDumper", "EmojiDumper");
|
||||
export default definePlugin({
|
||||
name: "GuildPickerDumper",
|
||||
description: "Context menu to dump and download a server's emojis and stickers.",
|
||||
authors: [EquicordDevs.Cortex, Devs.Samwich, EquicordDevs.Synth, EquicordDevs.thororen],
|
||||
contextMenus: {
|
||||
"guild-context": Patch,
|
||||
"guild-header-popout": Patch
|
||||
}
|
||||
});
|
|
@ -24,7 +24,7 @@ export const HiddenServersStore = proxyLazyWebpack(() => {
|
|||
// id try to use .initialize() but i dont know how it works
|
||||
public async load() {
|
||||
const data = await DataStore.get(DB_KEY);
|
||||
if (data) {
|
||||
if (data && data instanceof Set) {
|
||||
this._hiddenGuilds = data;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,8 +77,8 @@ export default definePlugin({
|
|||
find: '("guildsnav")',
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=#{intl::SERVERS}\),children:)(\i)(\)?\.map\(\i\))/g,
|
||||
replace: "$self.useFilteredGuilds($1)$2",
|
||||
match: /(?<=#{intl::SERVERS}\),gap:"xs",children:.{0,100}?)(\i)(\.map\(.{5,30}\}\))/,
|
||||
replace: "$self.useFilteredGuilds($1)$2"
|
||||
},
|
||||
// despite my best efforts, the above doesnt trigger a rerender
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { classes } from "@utils/misc";
|
||||
import { findByCode } from "@webpack";
|
||||
import { Button, Clickable, Menu, Popout, React } from "@webpack/common";
|
||||
import { Button, Clickable, Menu, Popout, React, useRef } from "@webpack/common";
|
||||
|
||||
import { SvgOverFlowIcon } from "../icons/overFlowIcon";
|
||||
|
||||
|
@ -101,6 +101,8 @@ export function NoteBookTabs({ tabs, selectedTabId, onSelectTab }: { tabs: strin
|
|||
);
|
||||
}, [tabs, selectedTabId, onSelectTab, overflowedTabs]);
|
||||
|
||||
const buttonRef = useRef(null);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes("vc-notebook-tabbar")}
|
||||
|
@ -137,9 +139,11 @@ export function NoteBookTabs({ tabs, selectedTabId, onSelectTab }: { tabs: strin
|
|||
position="bottom"
|
||||
align="right"
|
||||
spacing={0}
|
||||
targetElementRef={buttonRef}
|
||||
>
|
||||
{props => (
|
||||
<Button
|
||||
ref={buttonRef}
|
||||
{...props}
|
||||
className={"vc-notebook-overflow-chevron"}
|
||||
size={Button.Sizes.ICON}
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { classes } from "@utils/misc";
|
||||
import { ModalProps } from "@utils/modal";
|
||||
import { findByCode, findByCodeLazy, findByProps, findComponentByCodeLazy } from "@webpack";
|
||||
import { Clipboard, ContextMenuApi, FluxDispatcher, Menu, NavigationRouter, React } from "@webpack/common";
|
||||
import { ContextMenuApi, FluxDispatcher, Menu, NavigationRouter, React } from "@webpack/common";
|
||||
|
||||
import noteHandler from "../../NoteHandler";
|
||||
import { HolyNotes } from "../../types";
|
||||
|
@ -139,13 +140,13 @@ const NoteContextMenu = (
|
|||
<Menu.MenuItem
|
||||
label="Copy Text"
|
||||
id="copy-text"
|
||||
action={() => Clipboard.copy(note.content)}
|
||||
action={() => copyToClipboard(note.content)}
|
||||
/>
|
||||
{note?.attachments.length ? (
|
||||
<Menu.MenuItem
|
||||
label="Copy Attachment URL"
|
||||
id="copy-url"
|
||||
action={() => Clipboard.copy(note.attachments[0].url)}
|
||||
action={() => copyToClipboard(note.attachments[0].url)}
|
||||
/>) : null}
|
||||
<Menu.MenuItem
|
||||
color="danger"
|
||||
|
@ -181,7 +182,7 @@ const NoteContextMenu = (
|
|||
<Menu.MenuItem
|
||||
label="Copy ID"
|
||||
id="copy-id"
|
||||
action={() => Clipboard.copy(note.id)}
|
||||
action={() => copyToClipboard(note.id)}
|
||||
/>
|
||||
</Menu.Menu>
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
import {
|
||||
|
@ -41,9 +42,7 @@ function ModalComponent(props: { func: Function; iconName: string; color: number
|
|||
color={Button.Colors.PRIMARY}
|
||||
className={"vc-iv-raw-modal-copy-button"}
|
||||
onClick={() => {
|
||||
// silly typescript
|
||||
// @ts-ignore
|
||||
Clipboard.copy(String(func));
|
||||
copyToClipboard(String(func));
|
||||
Toasts.show({
|
||||
id: Toasts.genId(),
|
||||
message: `Copied raw \`${iconName}\` to clipboard`,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { getIntlMessage } from "@utils/discord";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
|
@ -27,8 +28,7 @@ export type ClickableProps<T extends "a" | "div" | "span" | "li" = "div"> = Prop
|
|||
export function IconTooltip({ children, copy, className, ...props }: ClickableProps & { children: string; copy: string; }) {
|
||||
return <TooltipContainer text={"Click to copy"} className={className}>
|
||||
<Clickable onClick={() => {
|
||||
// @ts-ignore
|
||||
Clipboard.copy(copy);
|
||||
copyToClipboard(copy);
|
||||
}} {...props}>{children}</Clickable>
|
||||
</TooltipContainer>;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { saveFile } from "@utils/web";
|
||||
import { filters, findAll, findByPropsLazy, waitFor } from "@webpack";
|
||||
import { React, ReactDOM } from "@webpack/common";
|
||||
import { createRoot, React, ReactDOM } from "@webpack/common";
|
||||
import * as t from "@webpack/types";
|
||||
export let _cssColors: string[] = [];
|
||||
export type IconsDef = { [k: string]: t.Icon; };
|
||||
|
@ -82,7 +82,7 @@ export function saveIcon(iconName: string, icon: EventTarget & SVGSVGElement | E
|
|||
|
||||
export function convertComponentToHtml(component?: React.ReactElement): string {
|
||||
const container = document.createElement("div");
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = createRoot(container);
|
||||
|
||||
ReactDOM.flushSync(() => root.render(component));
|
||||
const content = container.innerHTML;
|
||||
|
|
|
@ -9,14 +9,11 @@ import "./styles.css";
|
|||
import { EquicordDevs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { StickersStore } from "@webpack/common";
|
||||
|
||||
import { getMimeType, isLinkAnImage, settings, stripDiscordParams } from "./settings";
|
||||
|
||||
const logger = new Logger("ImagePreview", "#FFFFFF");
|
||||
const StickerStore = findStoreLazy("StickersStore") as {
|
||||
getStickerById(id: string): any;
|
||||
};
|
||||
|
||||
let currentPreview: HTMLDivElement | null = null;
|
||||
let currentPreviewFile: HTMLImageElement | HTMLVideoElement | null = null;
|
||||
|
@ -124,7 +121,7 @@ function loadImagePreview(url: string, sticker: boolean) {
|
|||
|
||||
if (sticker) {
|
||||
const stickerId = url.split("/").pop()?.split(".")[0] ?? null;
|
||||
const stickerData = stickerId ? StickerStore.getStickerById(stickerId) : null;
|
||||
const stickerData = stickerId ? StickersStore.getStickerById(stickerId) : null;
|
||||
|
||||
if (stickerData) {
|
||||
switch (stickerData.type) {
|
||||
|
|
107
src/equicordplugins/ingtoninator/index.tsx
Normal file
107
src/equicordplugins/ingtoninator/index.tsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, ChatBarButtonFactory, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { React } from "@webpack/common";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
showIcon: {
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
description: "Show a button to toggle the Ingtoninator plugin",
|
||||
restartNeeded: true
|
||||
},
|
||||
isEnabled: {
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
description: "Enable or disable the Ingtoninator"
|
||||
}
|
||||
});
|
||||
|
||||
const isLegal = (word: string) => {
|
||||
if (word.startsWith("<@")) return false;
|
||||
if (word.endsWith("ington")) return false;
|
||||
if (/^https?:\/\//i.test(word)) return false;
|
||||
if (/[aeouy]$/i.test(word)) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleMessage = ((channelId, message) => {
|
||||
if (!settings.store.isEnabled) return;
|
||||
if (!message.content || !message.content.trim()) return;
|
||||
|
||||
const words = message.content.trim().split(/\s+/);
|
||||
if (words.length === 0) return;
|
||||
|
||||
let index = -1;
|
||||
let attempts = 0;
|
||||
do {
|
||||
index = Math.floor(Math.random() * words.length);
|
||||
attempts++;
|
||||
} while (!isLegal(words[index]) && attempts < words.length * 2);
|
||||
|
||||
if (isLegal(words[index])) {
|
||||
const word = words[index];
|
||||
if (word.endsWith("ing")) {
|
||||
words[index] = word === word.toUpperCase() ? word + "TON" : word + "ton";
|
||||
} else if (word.endsWith("i") || word.endsWith("I")) {
|
||||
words[index] = word === word.toUpperCase() ? word + "NGTON" : word + "ngton";
|
||||
} else if (word.endsWith("in") || word.endsWith("IN")) {
|
||||
words[index] = word === word.toUpperCase() ? word + "GTON" : word + "gton";
|
||||
} else if (word.endsWith("ing") || word.endsWith("ING")) {
|
||||
words[index] = word === word.toUpperCase() ? word + "TON" : word + "ton";
|
||||
} else if (word.endsWith("ingt") || word.endsWith("INGT")) {
|
||||
words[index] = word === word.toUpperCase() ? word + "ON" : word + "on";
|
||||
} else {
|
||||
words[index] = word === word.toUpperCase() ? word + "INGTON" : word + "ington";
|
||||
}
|
||||
}
|
||||
|
||||
message.content = words.join(" ");
|
||||
});
|
||||
|
||||
const IngtoninatorButton: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]);
|
||||
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
||||
|
||||
if (!isMainChat || !showIcon) return null;
|
||||
|
||||
return (
|
||||
<ChatBarButton
|
||||
tooltip={isEnabled ? "Ingtoninator Enabled" : "Ingtoninator Disabled"}
|
||||
onClick={toggle}
|
||||
>
|
||||
{isEnabled ? (
|
||||
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||
<path transform="translate(351 -153)" fill="currentcolor" d="M-177.7,334.5c6.3-2.3,12.6-5.2,19.8-8.6c31.9-16.4,51.7-41.7,51.7-41.7s-32.5,0.6-64.4,17 c-4,1.7-7.5,4-10.9,5.7c5.7-7.5,12.1-16.4,18.7-25c25-37.1,31.3-77.3,31.3-77.3s-34.8,21-59.2,58.6c-5.2,7.5-9.8,14.9-13.8,22.7 c1.1-10.3,1.1-22.1,1.1-33.6c0-50-19.8-91.1-19.8-91.1s-19.8,40.5-19.8,91.1c0,12.1,0.6,23.3,1.1,33.6c-4-7.5-8.6-14.9-13.8-22.7 c-25-37.1-59.2-58.6-59.2-58.6s6.3,40,31.3,77.3c6.3,9.2,12.1,17.5,18.7,25c-3.4-2.3-7.5-4-10.9-5.7c-31.9-16.4-64.4-17-64.4-17 s19.8,25.6,51.7,41.7c6.9,3.4,13.2,6.3,19.8,8.6c-4,0.6-8,1.1-12.1,2.3c-30.5,6.4-53.2,23.9-53.2,23.9s27.3,7.5,58.6,1.1 c9.8-2.3,19.8-4.6,27.3-7.5c-1.1,1.1,15.8-8.6,21.6-14.4v60.4h8.6v-61.8c6.3,6.3,22.7,16.4,22.1,14.9c8,2.9,17.5,5.2,27.3,7.5 c30.8,6.3,58.6-1.1,58.6-1.1s-22.1-17.5-53.4-23.8C-169.6,335.7-173.7,335.1-177.7,334.5z" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||
<path transform="translate(351 -153)" fill="var(--status-danger)" d="M-177.7,334.5c6.3-2.3,12.6-5.2,19.8-8.6c31.9-16.4,51.7-41.7,51.7-41.7s-32.5,0.6-64.4,17 c-4,1.7-7.5,4-10.9,5.7c5.7-7.5,12.1-16.4,18.7-25c25-37.1,31.3-77.3,31.3-77.3s-34.8,21-59.2,58.6c-5.2,7.5-9.8,14.9-13.8,22.7 c1.1-10.3,1.1-22.1,1.1-33.6c0-50-19.8-91.1-19.8-91.1s-19.8,40.5-19.8,91.1c0,12.1,0.6,23.3,1.1,33.6c-4-7.5-8.6-14.9-13.8-22.7 c-25-37.1-59.2-58.6-59.2-58.6s6.3,40,31.3,77.3c6.3,9.2,12.1,17.5,18.7,25c-3.4-2.3-7.5-4-10.9-5.7c-31.9-16.4-64.4-17-64.4-17 s19.8,25.6,51.7,41.7c6.9,3.4,13.2,6.3,19.8,8.6c-4,0.6-8,1.1-12.1,2.3c-30.5,6.4-53.2,23.9-53.2,23.9s27.3,7.5,58.6,1.1 c9.8-2.3,19.8-4.6,27.3-7.5c-1.1,1.1,15.8-8.6,21.6-14.4v60.4h8.6v-61.8c6.3,6.3,22.7,16.4,22.1,14.9c8,2.9,17.5,5.2,27.3,7.5 c30.8,6.3,58.6-1.1,58.6-1.1s-22.1-17.5-53.4-23.8C-169.6,335.7-173.7,335.1-177.7,334.5z" />
|
||||
</svg>
|
||||
)}
|
||||
</ChatBarButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "Ingtoninator",
|
||||
description: "Suffixes 'ington' to a random word in your message",
|
||||
authors: [EquicordDevs.zyqunix],
|
||||
settings,
|
||||
start() {
|
||||
addChatBarButton("Ingtoninator", IngtoninatorButton);
|
||||
addMessagePreSendListener(handleMessage);
|
||||
},
|
||||
stop() {
|
||||
removeChatBarButton("Ingtoninator");
|
||||
removeMessagePreSendListener(handleMessage);
|
||||
}
|
||||
});
|
|
@ -9,7 +9,7 @@ import "./styles.css";
|
|||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { FluxDispatcher, ReactDOM, useEffect, useState } from "@webpack/common";
|
||||
import { createRoot, FluxDispatcher, useEffect, useState } from "@webpack/common";
|
||||
import { Root } from "react-dom/client";
|
||||
|
||||
let jumpscareRoot: Root | undefined;
|
||||
|
@ -38,7 +38,7 @@ function getJumpscareRoot(): Root {
|
|||
element.id = "jumpscare-root";
|
||||
element.classList.add("jumpscare-root");
|
||||
document.body.append(element);
|
||||
jumpscareRoot = ReactDOM.createRoot(element);
|
||||
jumpscareRoot = createRoot(element);
|
||||
}
|
||||
|
||||
return jumpscareRoot;
|
||||
|
|
|
@ -33,7 +33,7 @@ const recentMentionsPopoutClass = findByPropsLazy("recentMentionsPopout");
|
|||
const tabClass = findByPropsLazy("inboxTitle", "tab");
|
||||
const buttonClass = findByPropsLazy("size36");
|
||||
const MenuHeader = findByCodeLazy(".getUnseenInviteCount())");
|
||||
const Popout = findByCodeLazy("#{intl::UNBLOCK_TO_JUMP_TITLE}", "canCloseAllMessages:");
|
||||
const Popout = findByCodeLazy("getProTip", "canCloseAllMessages:");
|
||||
const createMessageRecord = findByCodeLazy(".createFromServer(", ".isBlockedForMessage", "messageReference:");
|
||||
const KEYWORD_ENTRIES_KEY = "KeywordNotify_keywordEntries";
|
||||
const KEYWORD_LOG_KEY = "KeywordNotify_log";
|
||||
|
|
151
src/equicordplugins/lastActive/index.tsx
Normal file
151
src/equicordplugins/lastActive/index.tsx
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu, MessageActions, MessageStore, NavigationRouter, Toasts, UserStore } from "@webpack/common";
|
||||
|
||||
async function findLastMessageFromUser(channelId: string, userId: string) {
|
||||
try {
|
||||
const messageCollection = MessageStore.getMessages(channelId);
|
||||
let messages = messageCollection?.toArray() || [];
|
||||
let userMessage = messages.filter(m => m?.author?.id === userId).pop();
|
||||
if (userMessage) return userMessage.id;
|
||||
try {
|
||||
await MessageActions.fetchMessages({
|
||||
channelId: channelId,
|
||||
limit: 50
|
||||
});
|
||||
|
||||
const updatedCollection = MessageStore.getMessages(channelId);
|
||||
messages = updatedCollection?.toArray() || [];
|
||||
userMessage = messages.filter(m => m?.author?.id === userId).pop();
|
||||
|
||||
if (userMessage) return userMessage.id;
|
||||
} catch (fetchError) {
|
||||
console.error("Error fetching messages:", fetchError);
|
||||
}
|
||||
|
||||
Toasts.show({
|
||||
type: Toasts.Type.FAILURE,
|
||||
message: "Couldn't find any recent messages from this user.",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error("Error finding last message:", error);
|
||||
Toasts.show({
|
||||
type: Toasts.Type.FAILURE,
|
||||
message: "Failed to find messages. Check console for details.",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
async function jumpToLastActive(channel: any, targetUserId?: string) {
|
||||
try {
|
||||
if (!channel) {
|
||||
Toasts.show({
|
||||
type: Toasts.Type.FAILURE,
|
||||
message: "Channel information not available.",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
return;
|
||||
}
|
||||
const guildId = channel.guild_id !== null ? channel.guild_id : "@me";
|
||||
const channelId = channel.id;
|
||||
let userId: string;
|
||||
if (targetUserId) {
|
||||
|
||||
userId = targetUserId;
|
||||
} else {
|
||||
const currentUser = UserStore.getCurrentUser();
|
||||
userId = currentUser.id;
|
||||
}
|
||||
const messageId = await findLastMessageFromUser(channelId, userId);
|
||||
if (messageId) {
|
||||
const url = `/channels/${guildId}/${channelId}/${messageId}`;
|
||||
NavigationRouter.transitionTo(url);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in jumpToLastActive:", error);
|
||||
Toasts.show({
|
||||
type: Toasts.Type.FAILURE,
|
||||
message: "Failed to jump to message. Check console for details.",
|
||||
id: Toasts.genId()
|
||||
});
|
||||
}
|
||||
}
|
||||
const ChannelContextMenuPatch: NavContextMenuPatchCallback = (children, { channel }) => {
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
id="LastActive"
|
||||
label={<span style={{ color: "#aa6746" }}>Your Last Message</span>}
|
||||
icon={LastActiveIcon}
|
||||
action={() => {
|
||||
jumpToLastActive(channel);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user, channel }) => {
|
||||
if (!channel || !user?.id) return;
|
||||
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
id="LastActive"
|
||||
label={<span style={{ color: "#aa6746" }}>User's Last Message</span>}
|
||||
icon={UserLastActiveIcon}
|
||||
action={() => {
|
||||
jumpToLastActive(channel, user.id);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export function UserLastActiveIcon() {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 52 52"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="#aa6746"
|
||||
>
|
||||
<g>
|
||||
<path d="M11.4,21.6L24.9,7.9c0.6-0.6,1.6-0.6,2.2,0l13.5,13.7c0.6,0.6,0.6,1.6,0,2.2L38.4,26
|
||||
c-0.6,0.6-1.6,0.6-2.2,0l-9.1-9.4c-0.6-0.6-1.6-0.6-2.2,0l-9.1,9.3c-0.6,0.6-1.6,0.6-2.2,0l-2.2-2.2C10.9,23.1,10.9,22.2,11.4,21.6
|
||||
z"/>
|
||||
<path d="M11.4,39.7L24.9,26c0.6-0.6,1.6-0.6,2.2,0l13.5,13.7c0.6,0.6,0.6,1.6,0,2.2l-2.2,2.2
|
||||
c-0.6,0.6-1.6,0.6-2.2,0l-9.1-9.4c-0.6-0.6-1.6-0.6-2.2,0L15.8,44c-0.6,0.6-1.6,0.6-2.2,0l-2.2-2.2C10.9,41.2,10.9,40.2,11.4,39.7z
|
||||
"/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function LastActiveIcon() {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="#aa6746"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path fillRule="evenodd" d="M12,2 C17.5228475,2 22,6.4771525 22,12 C22,17.5228475 17.5228475,22 12,22 C6.4771525,22 2,17.5228475 2,12 C2,6.4771525 6.4771525,2 12,2 Z M12,4 C7.581722,4 4,7.581722 4,12 C4,16.418278 7.581722,20 12,20 C16.418278,20 20,16.418278 20,12 C20,7.581722 16.418278,4 12,4 Z M12,6 C12.5128358,6 12.9355072,6.38604019 12.9932723,6.88337887 L13,7 L13,11.5857864 L14.7071068,13.2928932 C15.0976311,13.6834175 15.0976311,14.3165825 14.7071068,14.7071068 C14.3466228,15.0675907 13.7793918,15.0953203 13.3871006,14.7902954 L13.2928932,14.7071068 L11.2928932,12.7071068 C11.1366129,12.5508265 11.0374017,12.3481451 11.0086724,12.131444 L11,12 L11,7 C11,6.44771525 11.4477153,6 12,6 Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export default definePlugin({
|
||||
name: "LastActive",
|
||||
description: "A plugin to jump to last active message from yourself or another user in a channel/server.",
|
||||
authors: [EquicordDevs.Crxa],
|
||||
contextMenus: {
|
||||
"channel-context": ChannelContextMenuPatch,
|
||||
"user-context": UserContextMenuPatch,
|
||||
"thread-context": ChannelContextMenuPatch
|
||||
}
|
||||
});
|
|
@ -30,7 +30,7 @@ type Spinner = ComponentType<Omit<HTMLAttributes<HTMLDivElement>, "children"> &
|
|||
};
|
||||
|
||||
// https://github.com/Kyuuhachi/VencordPlugins/blob/main/MessageLinkTooltip/index.tsx#L11-L33
|
||||
export const Spinner = findComponentByCodeLazy('"pulsingEllipsis"') as Spinner;
|
||||
export const Spinner = findComponentByCodeLazy('"pulsingEllipsis"') as unknown as Spinner;
|
||||
|
||||
export const QrCodeIcon = findComponentByCodeLazy("0v3ZM20");
|
||||
|
||||
|
|
|
@ -14,7 +14,8 @@ async function handleButtonClick() {
|
|||
sendMessage(getCurrentChannel().id, { content: "meow" });
|
||||
}
|
||||
|
||||
const ChatBarIcon: ChatBarButtonFactory = () => {
|
||||
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
if (!isMainChat) return null;
|
||||
return (
|
||||
<ChatBarButton tooltip="Meow" onClick={handleButtonClick}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 576 512"><path fill="currentColor" d="M320 192h17.1c22.1 38.3 63.5 64 110.9 64c11 0 21.8-1.4 32-4v228c0 17.7-14.3 32-32 32s-32-14.3-32-32V339.2L280 448h56c17.7 0 32 14.3 32 32s-14.3 32-32 32H192c-53 0-96-43-96-96V192.5c0-16.1-12-29.8-28-31.8l-7.9-1c-17.5-2.2-30-18.2-27.8-35.7S50.5 94 68 96.2l7.9 1c48 6 84.1 46.8 84.1 95.3v85.3c34.4-51.7 93.2-85.8 160-85.8m160 26.5c-10 3.5-20.8 5.5-32 5.5c-28.4 0-54-12.4-71.6-32c-3.7-4.1-7-8.5-9.9-13.2C357.3 164 352 146.6 352 128V10.7C352 4.8 356.7.1 362.6 0h.2c3.3 0 6.4 1.6 8.4 4.2v.1l12.8 17l27.2 36.3L416 64h64l4.8-6.4L512 21.3l12.8-17v-.1c2-2.6 5.1-4.2 8.4-4.2h.2c5.9.1 10.6 4.8 10.6 10.7V128c0 17.3-4.6 33.6-12.6 47.6c-11.3 19.8-29.6 35.2-51.4 42.9M432 128a16 16 0 1 0-32 0a16 16 0 1 0 32 0m48 16a16 16 0 1 0 0-32a16 16 0 1 0 0 32" /></svg>
|
||||
|
|
|
@ -89,7 +89,7 @@ export async function exportLogs() {
|
|||
const messages = await getAllMessagesIDB();
|
||||
const data = JSON.stringify({ messages }, null, 2);
|
||||
|
||||
if (IS_WEB || IS_VESKTOP) {
|
||||
if (IS_WEB || IS_VESKTOP || IS_EQUIBOP || !DiscordNative) {
|
||||
const file = new File([data], filename, { type: "application/json" });
|
||||
const a = document.createElement("a");
|
||||
a.href = URL.createObjectURL(file);
|
||||
|
|
|
@ -449,7 +449,7 @@ export const PickerHeader = ({ onQueryChange }: PickerHeaderProps) => {
|
|||
<div>
|
||||
<div className={clPicker("search-box")}>
|
||||
<TextInput
|
||||
style={{ height: "30px" }}
|
||||
style={{ height: "30px", border: "none" }}
|
||||
|
||||
placeholder="Search stickers"
|
||||
autoFocus={true}
|
||||
|
|
|
@ -35,54 +35,34 @@ export default definePlugin({
|
|||
find: "ChannelStickerPickerButton",
|
||||
replacement: [{
|
||||
match: /(children:\(0,\i\.jsx\)\()(.{0,10})({innerClassName.{10,30}\.stickerButton)/,
|
||||
replace: (_, head, button, tail) => {
|
||||
const isMoreStickers = "arguments[0]?.stickersType";
|
||||
return `${head}${isMoreStickers}?$self.stickerButton:${button}${tail}`;
|
||||
}
|
||||
replace: "$1arguments[0]?.stickersType?$self.stickerButton:$2$3"
|
||||
}, {
|
||||
match: /(\i=)(\i\.useCallback.{0,25}\.STICKER,.{0,10});/,
|
||||
replace: (_, decl, cb) => {
|
||||
const newCb = cb.replace(/(?<=\(\)=>\{\(.*?\)\().+?\.STICKER/, "\"stickers+\"");
|
||||
return `${decl}arguments[0]?.stickersType?${newCb}:${cb};`;
|
||||
}
|
||||
match: /(\i=)((\i\.useCallback\(\(\)=>\{\(.*?\)\().*?\.STICKER,(\i.{0,10}));/,
|
||||
replace: '$1arguments[0]?.stickersType?$3"stickers+",$4:$2;'
|
||||
}, {
|
||||
match: /(\i)=((\i)===\i\.\i\.STICKER)/,
|
||||
replace: (_, isActive, isStickerTab, currentTab) => {
|
||||
const c = "arguments[0].stickersType";
|
||||
return `${isActive}=${c}?(${currentTab}===${c}):(${isStickerTab})`;
|
||||
}
|
||||
replace: "$1=arguments[0].stickersType?($3===arguments[0].stickersType):($2)"
|
||||
}]
|
||||
},
|
||||
{
|
||||
find: ".gifts)",
|
||||
replacement: {
|
||||
match: /,.{0,5}\(null==\(\i=\i\.stickers\)\?void 0.*?(\i)\.push\((\(0,\w\.jsx\))\((.+?),{disabled:\i,type:(\i)},"sticker"\)\)\)/,
|
||||
replace: (m, _, jsx, compo, type) => {
|
||||
const c = "arguments[0].type";
|
||||
return `${m},${c}?.submit?.button&&${_}.push(${jsx}(${compo},{disabled:!${c}?.submit?.button,type:${type},stickersType:"stickers+"},"stickers+"))`;
|
||||
}
|
||||
match: /(?<=,.{0,5}\(null==\(\i=\i\.stickers\)\?void 0.*?(\i)\.push\((\(0,\i\.jsx\))\((.+?),{disabled:\i,type:(\i)},"sticker"\)\)\))/,
|
||||
replace: ",arguments[0].type?.submit?.button&&$1.push($2($3,{disabled:!arguments[0].type?.submit?.button,type:$4,stickersType:\"stickers+\"},\"stickers+\"))"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "#{intl::EXPRESSION_PICKER_CATEGORIES_A11Y_LABEL}",
|
||||
replacement: {
|
||||
match: /role:"tablist",.*?,?"aria-label":.+?\),children:(\[.*?\)\]}\)}\):null,)(.*?closePopout:\w.*?:null)/s,
|
||||
replace: m => {
|
||||
const stickerTabRegex = /(\w+?)\?(\([^()]+?\))\((.{1,2}),{.{0,128},isActive:(.{1,2})===.{1,6}\.STICKER.{1,140},children:(.{1,5}\.string\(.+?\)).*?:null/s;
|
||||
const res = m.replace(stickerTabRegex, (_m, canUseStickers, jsx, tabHeaderComp, currentTab, stickerText) => {
|
||||
const isActive = `${currentTab}==="stickers+"`;
|
||||
return (
|
||||
`${_m},${canUseStickers}?` +
|
||||
`${jsx}(${tabHeaderComp},{id:"stickers+-picker-tab","aria-controls":"more-stickers-picker-tab-panel","aria-selected":${isActive},isActive:${isActive},autoFocus:true,viewType:"stickers+",children:${jsx}("div",{children:${stickerText}+"+"})})` +
|
||||
":null"
|
||||
);
|
||||
});
|
||||
|
||||
return res.replace(/:null,((.{1,200})===.{1,30}\.STICKER&&\w+\?(\([^()]{1,10}\)).{1,15}?(\{.*?,onSelectSticker:.*?\})\):null)/s, (_, _m, currentTab, jsx, props) => {
|
||||
return `:null,${currentTab}==="stickers+"?${jsx}($self.moreStickersComponent,${props}):null,${_m}`;
|
||||
});
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=null,(\i)\?(\(.*?\))\((\i),{.{0,128},isActive:(\i)===.{0,200},children:(\i\.intl\.string\(.*?\))\}\)\}\):null,)/s,
|
||||
replace: '$1?$2($3,{id:"stickers+-picker-tab","aria-controls":"more-stickers-picker-tab-panel","aria-selected":$4==="stickers+",isActive:$4==="stickers+",autoFocus:true,viewType:"stickers+",children:$5+"+"}):null,'
|
||||
},
|
||||
{
|
||||
match: /:null,((.{1,200})===.{1,30}\.STICKER&&\w+\?(\([^()]{1,10}\)).{1,15}?(\{.*?,onSelectSticker:.*?\})\):null)/,
|
||||
replace: ':null,$2==="stickers+"?$3($self.moreStickersComponent,$4):null,$1'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: '==="remove_text"',
|
||||
|
@ -114,8 +94,8 @@ export default definePlugin({
|
|||
>
|
||||
<path d="M18.5 11c-4.136 0-7.5 3.364-7.5 7.5c0 .871.157 1.704.432 2.482l9.551-9.551A7.462 7.462 0 0 0 18.5 11z" />
|
||||
<path d="M12 2C6.486 2 2 6.486 2 12c0 4.583 3.158 8.585 7.563 9.69A9.431 9.431 0 0 1 9 18.5C9 13.262 13.262 9 18.5 9c1.12 0 2.191.205 3.19.563C20.585 5.158 16.583 2 12 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</svg >
|
||||
</button >
|
||||
);
|
||||
},
|
||||
moreStickersComponent({
|
||||
|
|
|
@ -116,7 +116,7 @@
|
|||
|
||||
.vc-more-stickers-category-scroller,
|
||||
.vc-more-stickers-picker-content-scroller {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-width: none;
|
||||
scrollbar-color: var(--scrollbar-thin-thumb) var(--scrollbar-thin-track);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,7 @@ import { ChannelStore, UploadHandler } from "@webpack/common";
|
|||
|
||||
import { FFmpegState, Sticker } from "./types";
|
||||
|
||||
|
||||
const MessageUpload = findByPropsLazy("instantBatchUpload");
|
||||
const MessageUpload = findByPropsLazy("uploadFiles");
|
||||
const CloudUpload = findLazy(m => m.prototype?.trackUploadFinished);
|
||||
const PendingReplyStore = findByPropsLazy("getPendingReply");
|
||||
const MessageUtils = findByPropsLazy("sendMessage");
|
||||
|
|
|
@ -14,7 +14,7 @@ import { FFmpegState } from "./types";
|
|||
export const cl = classNameFactory("vc-more-stickers-");
|
||||
export const clPicker = (className: string, ...args: any[]) => cl("picker-" + className, ...args);
|
||||
|
||||
const CORS_PROXY = "https://corsproxy.io?";
|
||||
const CORS_PROXY = "https://corsproxy.io/?url=";
|
||||
|
||||
function corsUrl(url: string | URL) {
|
||||
return CORS_PROXY + encodeURIComponent(url.toString());
|
||||
|
|
63
src/equicordplugins/moreUserTags/consts.ts
Normal file
63
src/equicordplugins/moreUserTags/consts.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findByCodeLazy, findLazy } from "@webpack";
|
||||
import { GuildStore } from "@webpack/common";
|
||||
import { RC } from "@webpack/types";
|
||||
import { Channel, Guild, Message, User } from "discord-types/general";
|
||||
|
||||
import type { ITag } from "./types";
|
||||
|
||||
export const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot();
|
||||
export const tags = [
|
||||
{
|
||||
name: "WEBHOOK",
|
||||
displayName: "Webhook",
|
||||
description: "Messages sent by webhooks",
|
||||
condition: isWebhook
|
||||
}, {
|
||||
name: "OWNER",
|
||||
displayName: "Owner",
|
||||
description: "Owns the server",
|
||||
condition: (_, user, channel) => GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id
|
||||
}, {
|
||||
name: "ADMINISTRATOR",
|
||||
displayName: "Admin",
|
||||
description: "Has the administrator permission",
|
||||
permissions: ["ADMINISTRATOR"]
|
||||
}, {
|
||||
name: "MODERATOR_STAFF",
|
||||
displayName: "Staff",
|
||||
description: "Can manage the server, channels or roles",
|
||||
permissions: ["MANAGE_GUILD", "MANAGE_CHANNELS", "MANAGE_ROLES"]
|
||||
}, {
|
||||
name: "MODERATOR",
|
||||
displayName: "Mod",
|
||||
description: "Can manage messages or kick/ban people",
|
||||
permissions: ["MANAGE_MESSAGES", "KICK_MEMBERS", "BAN_MEMBERS"]
|
||||
}, {
|
||||
name: "VOICE_MODERATOR",
|
||||
displayName: "VC Mod",
|
||||
description: "Can manage voice chats",
|
||||
permissions: ["MOVE_MEMBERS", "MUTE_MEMBERS", "DEAFEN_MEMBERS"]
|
||||
}, {
|
||||
name: "CHAT_MODERATOR",
|
||||
displayName: "Chat Mod",
|
||||
description: "Can timeout people",
|
||||
permissions: ["MODERATE_MEMBERS"]
|
||||
}
|
||||
] as const satisfies ITag[];
|
||||
|
||||
export const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number | null, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
|
||||
|
||||
// PermissionStore.computePermissions will not work here since it only gets permissions for the current user
|
||||
export const computePermissions: (options: {
|
||||
user?: { id: string; } | string | null;
|
||||
context?: Guild | Channel | null;
|
||||
overwrites?: Channel["permissionOverwrites"] | null;
|
||||
checkElevated?: boolean /* = true */;
|
||||
excludeGuildPermissions?: boolean /* = false */;
|
||||
}) => bigint = findByCodeLazy(".getCurrentUser()", ".computeLurkerPermissionsAllowList()");
|
185
src/equicordplugins/moreUserTags/index.tsx
Normal file
185
src/equicordplugins/moreUserTags/index.tsx
Normal file
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { getCurrentChannel, getIntlMessage } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore, GuildStore, PermissionsBits, SelectedChannelStore, UserStore } from "@webpack/common";
|
||||
import { Channel, Message, User } from "discord-types/general";
|
||||
|
||||
import { computePermissions, Tag, tags } from "./consts";
|
||||
import { settings } from "./settings";
|
||||
import { TagSettings } from "./types";
|
||||
|
||||
const cl = classNameFactory("vc-mut-");
|
||||
|
||||
const genTagTypes = () => {
|
||||
let i = 100;
|
||||
const obj = {};
|
||||
|
||||
for (const { name } of tags) {
|
||||
obj[name] = ++i;
|
||||
obj[i] = name;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "MoreUserTags",
|
||||
description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)",
|
||||
authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN, EquicordDevs.Hen],
|
||||
dependencies: ["MemberListDecoratorsAPI", "NicknameIconsAPI", "MessageDecorationsAPI"],
|
||||
settings,
|
||||
patches: [
|
||||
// Make discord actually use our tags
|
||||
{
|
||||
find: ".STAFF_ONLY_DM:",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=type:(\i).{10,1000}.REMIX.{10,100})default:(\i)=/,
|
||||
replace: "default:$2=$self.getTagText($self.localTags[$1]);",
|
||||
},
|
||||
{
|
||||
match: /(?<=type:(\i).{10,1000}.REMIX.{10,100})\.BOT:(?=default:)/,
|
||||
replace: "$&return null;",
|
||||
predicate: () => settings.store.dontShowBotTag
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
start() {
|
||||
const tagSettings = settings.store.tagSettings || {} as TagSettings;
|
||||
for (const tag of Object.values(tags)) {
|
||||
tagSettings[tag.name] ??= {
|
||||
showInChat: true,
|
||||
showInNotChat: true,
|
||||
text: tag.displayName
|
||||
};
|
||||
}
|
||||
|
||||
settings.store.tagSettings = tagSettings;
|
||||
},
|
||||
localTags: genTagTypes(),
|
||||
getChannelId() {
|
||||
return SelectedChannelStore.getChannelId();
|
||||
},
|
||||
renderNicknameIcon(props) {
|
||||
const tagId = this.getTag({
|
||||
user: UserStore.getUser(props.userId),
|
||||
channel: ChannelStore.getChannel(this.getChannelId()),
|
||||
channelId: this.getChannelId(),
|
||||
isChat: false
|
||||
});
|
||||
|
||||
return tagId && <Tag
|
||||
type={tagId}
|
||||
verified={false}>
|
||||
</Tag>;
|
||||
|
||||
},
|
||||
renderMessageDecoration(props) {
|
||||
const tagId = this.getTag({
|
||||
message: props.message,
|
||||
user: UserStore.getUser(props.message.author.id),
|
||||
channelId: props.message.channel_id,
|
||||
isChat: false
|
||||
});
|
||||
|
||||
return tagId && <Tag
|
||||
useRemSizes={true}
|
||||
className={cl("message-tag", props.message.author.isVerifiedBot() && "message-verified")}
|
||||
type={tagId}
|
||||
verified={false}>
|
||||
</Tag>;
|
||||
},
|
||||
renderMemberListDecorator(props) {
|
||||
const tagId = this.getTag({
|
||||
user: props.user,
|
||||
channel: getCurrentChannel(),
|
||||
channelId: this.getChannelId(),
|
||||
isChat: false
|
||||
});
|
||||
|
||||
return tagId && <Tag
|
||||
type={tagId}
|
||||
verified={false}>
|
||||
</Tag>;
|
||||
},
|
||||
|
||||
getTagText(tagName: string) {
|
||||
if (!tagName) return getIntlMessage("APP_TAG");
|
||||
const tag = tags.find(({ name }) => tagName === name);
|
||||
if (!tag) return tagName || getIntlMessage("APP_TAG");
|
||||
|
||||
return settings.store.tagSettings?.[tag.name]?.text || tag.displayName;
|
||||
},
|
||||
|
||||
getTag({
|
||||
message, user, channelId, isChat, channel
|
||||
}: {
|
||||
message?: Message,
|
||||
user?: User & { isClyde(): boolean; },
|
||||
channel?: Channel & { isForumPost(): boolean; isMediaPost(): boolean; },
|
||||
channelId?: string;
|
||||
isChat?: boolean;
|
||||
}): number | null {
|
||||
const settings = this.settings.store;
|
||||
|
||||
if (!user) return null;
|
||||
if (isChat && user.id === "1") return null;
|
||||
if (user.isClyde()) return null;
|
||||
if (user.bot && settings.dontShowForBots) return null;
|
||||
|
||||
channel ??= ChannelStore.getChannel(channelId!) as any;
|
||||
if (!channel) return null;
|
||||
|
||||
const perms = this.getPermissions(user, channel);
|
||||
|
||||
for (const tag of tags) {
|
||||
if (isChat && !settings.tagSettings[tag.name].showInChat)
|
||||
continue;
|
||||
if (!isChat && !settings.tagSettings[tag.name].showInNotChat)
|
||||
continue;
|
||||
|
||||
// If the owner tag is disabled, and the user is the owner of the guild,
|
||||
// avoid adding other tags because the owner will always match the condition for them
|
||||
if (
|
||||
(tag.name !== "OWNER" &&
|
||||
GuildStore.getGuild(channel?.guild_id)?.ownerId ===
|
||||
user.id &&
|
||||
isChat &&
|
||||
!settings.tagSettings.OWNER.showInChat) ||
|
||||
(!isChat &&
|
||||
!settings.tagSettings.OWNER.showInNotChat)
|
||||
)
|
||||
continue;
|
||||
|
||||
if ("permissions" in tag ?
|
||||
tag.permissions.some(perm => perms.includes(perm)) :
|
||||
tag.condition(message!, user, channel)) {
|
||||
|
||||
return this.localTags[tag.name];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
getPermissions(user: User, channel: Channel): string[] {
|
||||
const guild = GuildStore.getGuild(channel?.guild_id);
|
||||
if (!guild) return [];
|
||||
|
||||
const permissions = computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
|
||||
return Object.entries(PermissionsBits)
|
||||
.map(([perm, permInt]) =>
|
||||
permissions & permInt ? perm : ""
|
||||
)
|
||||
.filter(Boolean);
|
||||
},
|
||||
});
|
104
src/equicordplugins/moreUserTags/settings.tsx
Normal file
104
src/equicordplugins/moreUserTags/settings.tsx
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { OptionType } from "@utils/types";
|
||||
import { Card, Flex, Forms, Switch, TextInput, Tooltip } from "@webpack/common";
|
||||
|
||||
import { Tag, tags } from "./consts";
|
||||
import { TagSettings } from "./types";
|
||||
|
||||
function SettingsComponent() {
|
||||
const tagSettings = settings.store.tagSettings as TagSettings;
|
||||
const { localTags } = Vencord.Plugins.plugins.MoreUserTags as any;
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "16px",
|
||||
}}
|
||||
>
|
||||
{tags.map(t => (
|
||||
<Card
|
||||
key={t.name}
|
||||
style={{
|
||||
padding: "1em 1em 0",
|
||||
width: "calc(33.333% - 11px)",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
>
|
||||
<Forms.FormTitle style={{ width: "fit-content" }}>
|
||||
<Tooltip text={t.description}>
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
<div
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
{t.displayName} Tag
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
</Forms.FormTitle>
|
||||
|
||||
<div style={{ marginBottom: "10px" }}>
|
||||
<Forms.FormText style={{ fontSize: "13px" }}>
|
||||
Example:
|
||||
</Forms.FormText>
|
||||
<Tag type={localTags[t.name]} />
|
||||
</div>
|
||||
|
||||
<TextInput
|
||||
type="text"
|
||||
value={tagSettings[t.name]?.text ?? t.displayName}
|
||||
placeholder={`Text on tag (default: ${t.displayName})`}
|
||||
onChange={v => tagSettings[t.name].text = v}
|
||||
className={Margins.bottom16}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
value={tagSettings[t.name]?.showInChat ?? true}
|
||||
onChange={v => tagSettings[t.name].showInChat = v}
|
||||
hideBorder
|
||||
>
|
||||
Show in messages
|
||||
</Switch>
|
||||
|
||||
<Switch
|
||||
value={tagSettings[t.name]?.showInNotChat ?? true}
|
||||
onChange={v => tagSettings[t.name].showInNotChat = v}
|
||||
hideBorder
|
||||
>
|
||||
Show in member list and profiles
|
||||
</Switch>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
dontShowForBots: {
|
||||
description: "Don't show extra tags for bots (excluding webhooks)",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false
|
||||
},
|
||||
dontShowBotTag: {
|
||||
description: "Only show extra tags for bots / Hide [APP] text",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
restartNeeded: true
|
||||
},
|
||||
tagSettings: {
|
||||
type: OptionType.COMPONENT,
|
||||
component: SettingsComponent,
|
||||
description: "fill me"
|
||||
}
|
||||
});
|
27
src/equicordplugins/moreUserTags/styles.css
Normal file
27
src/equicordplugins/moreUserTags/styles.css
Normal file
|
@ -0,0 +1,27 @@
|
|||
.vc-message-decorations-wrapper .vc-mut-message-tag {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line no-descending-specificity */
|
||||
.vc-mut-message-tag {
|
||||
/* Remove default margin from tags in messages */
|
||||
margin-top: unset !important;
|
||||
|
||||
/* Align with Discord default tags in messages */
|
||||
/* stylelint-disable-next-line length-zero-no-unit */
|
||||
bottom: 0px;
|
||||
top: -2px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.vc-mut-message-verified {
|
||||
height: 1rem !important;
|
||||
}
|
||||
|
||||
span[class*="botTagCozy"][data-moreTags-darkFg="true"]>svg>path {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
span[class*="botTagCozy"][data-moreTags-darkFg="false"]>svg>path {
|
||||
fill: #fff;
|
||||
}
|
32
src/equicordplugins/moreUserTags/types.ts
Normal file
32
src/equicordplugins/moreUserTags/types.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Permissions } from "@webpack/types";
|
||||
import type { Channel, Message, User } from "discord-types/general";
|
||||
|
||||
import { tags } from "./consts";
|
||||
|
||||
export type ITag = {
|
||||
// name used for identifying, must be alphanumeric + underscores
|
||||
name: string;
|
||||
// name shown on the tag itself, can be anything probably; automatically uppercase'd
|
||||
displayName: string;
|
||||
description: string;
|
||||
} & ({
|
||||
permissions: Permissions[];
|
||||
} | {
|
||||
condition?(message: Message | null, user: User, channel: Channel): boolean;
|
||||
});
|
||||
|
||||
export interface TagSetting {
|
||||
text: string;
|
||||
showInChat: boolean;
|
||||
showInNotChat: boolean;
|
||||
}
|
||||
|
||||
export type TagSettings = {
|
||||
[k in typeof tags[number]["name"]]: TagSetting;
|
||||
};
|
73
src/equicordplugins/morse/index.ts
Normal file
73
src/equicordplugins/morse/index.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ApplicationCommandInputType, ApplicationCommandOptionType } from "@api/Commands";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
const morseMap = {
|
||||
A: ".-", B: "-...", C: "-.-.", D: "-..", E: ".", F: "..-.",
|
||||
G: "--.", H: "....", I: "..", J: ".---", K: "-.-", L: ".-..",
|
||||
M: "--", N: "-.", O: "---", P: ".--.", Q: "--.-", R: ".-.",
|
||||
S: "...", T: "-", U: "..-", V: "...-", W: ".--", X: "-..-",
|
||||
Y: "-.--", Z: "--..",
|
||||
0: "-----", 1: ".----", 2: "..---", 3: "...--", 4: "....-",
|
||||
5: ".....", 6: "-....", 7: "--...", 8: "---..", 9: "----.",
|
||||
" ": "/"
|
||||
};
|
||||
|
||||
const toMorse = (text: string) => {
|
||||
return text.toUpperCase().split("").map(char => morseMap[char] ?? "").join(" ");
|
||||
};
|
||||
|
||||
const fromMorse = (text: string) => {
|
||||
const reversedMap = Object.fromEntries(Object.entries(morseMap).map(([k, v]) => [v, k]));
|
||||
const raw = text.split(" ").map(code => reversedMap[code] ?? "").join("").toLowerCase();
|
||||
return raw.charAt(0).toUpperCase() + raw.slice(1);
|
||||
};
|
||||
|
||||
// boo regex
|
||||
const isMorse = (text: string) => /^[.\-/ ]+$/.test(text);
|
||||
|
||||
export default definePlugin({
|
||||
name: "Morse",
|
||||
description: "A slash command to translate to/from morse code.",
|
||||
authors: [EquicordDevs.zyqunix],
|
||||
commands: [
|
||||
{
|
||||
inputType: ApplicationCommandInputType.BUILT_IN_TEXT,
|
||||
name: "morse",
|
||||
description: "Translate to or from Morse code",
|
||||
options: [
|
||||
{
|
||||
name: "text",
|
||||
description: "Text to convert",
|
||||
type: ApplicationCommandOptionType.STRING,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
execute: opts => {
|
||||
const input = opts.find(o => o.name === "text")?.value as string;
|
||||
const output = isMorse(input) ? fromMorse(input) : toMorse(input);
|
||||
return {
|
||||
content: `${output}`
|
||||
};
|
||||
},
|
||||
}
|
||||
]
|
||||
});
|
62
src/equicordplugins/noOnboarding/index.tsx
Normal file
62
src/equicordplugins/noOnboarding/index.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { RestAPI } from "@webpack/common";
|
||||
|
||||
export default definePlugin({
|
||||
name: "NoOnboarding",
|
||||
description: "Bypasses Discord's onboarding process for quicker server entry.",
|
||||
authors: [EquicordDevs.omaw, Devs.Glitch],
|
||||
patches: [
|
||||
{
|
||||
find: ",acceptInvite(",
|
||||
replacement: {
|
||||
match: /INVITE_ACCEPT_SUCCESS.+?,(\i)=null!=.+?;/,
|
||||
replace: (m, guildId) => `${m}$self.bypassOnboard(${guildId});`
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "{joinGuild:",
|
||||
replacement: {
|
||||
match: /guildId:(\i),lurker:(\i).{0,20}}\)\);/,
|
||||
replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.bypassOnboard(${guildId});`
|
||||
}
|
||||
}
|
||||
],
|
||||
bypassOnboard(guild_id: string) {
|
||||
RestAPI.get({ url: `/guilds/${guild_id}/onboarding` }).then(res => {
|
||||
const data = res.body;
|
||||
if (!data?.prompts?.length) return;
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const prompts_seen: Record<string, number> = {};
|
||||
const responses_seen: Record<string, number> = {};
|
||||
const responses: string[] = [];
|
||||
|
||||
for (const prompt of data.prompts) {
|
||||
const options = prompt.options || [];
|
||||
if (!options.length) continue;
|
||||
prompts_seen[prompt.id] = now;
|
||||
for (const opt of options) responses_seen[opt.id] = now;
|
||||
responses.push(options[options.length - 1].id);
|
||||
}
|
||||
|
||||
const payload = {
|
||||
onboarding_responses: responses,
|
||||
onboarding_prompts_seen: prompts_seen,
|
||||
onboarding_responses_seen: responses_seen,
|
||||
};
|
||||
|
||||
RestAPI.post({
|
||||
url: `/guilds/${guild_id}/onboarding-responses`,
|
||||
body: payload
|
||||
}).catch(() => { });
|
||||
}).catch(() => { });
|
||||
}
|
||||
});
|
||||
|
|
@ -28,9 +28,9 @@ export default definePlugin({
|
|||
authors: [EquicordDevs.iamme],
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::MESSAGE_EDITED}),",
|
||||
find: "isUnsupported})",
|
||||
replacement: {
|
||||
match: /#{intl::MESSAGE_EDITED}\),(?:[^}]*[}]){3}\)/,
|
||||
match: /WITH_CONTENT\}\)/,
|
||||
replace: "$&,$self.PinnedIcon(arguments[0].message)"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,12 +21,9 @@ import "@equicordplugins/_misc/styles.css";
|
|||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Forms, MessageStore, UserStore } from "@webpack/common";
|
||||
import { Forms, MessageActions, MessageStore, UserStore } from "@webpack/common";
|
||||
import { Channel, Message } from "discord-types/general";
|
||||
|
||||
const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage");
|
||||
|
||||
async function deleteMessages(amount: number, channel: Channel, delay: number = 1500): Promise<number> {
|
||||
let deleted = 0;
|
||||
const userId = UserStore.getCurrentUser().id;
|
||||
|
|
|
@ -17,38 +17,16 @@
|
|||
*/
|
||||
|
||||
import "@equicordplugins/_misc/styles.css";
|
||||
|
||||
import { showNotification } from "@api/Notifications";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getTheme, Theme } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByProps, findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, FluxDispatcher, Forms, NavigationRouter, RestAPI, Tooltip, UserStore } from "@webpack/common";
|
||||
|
||||
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
|
||||
const isApp = navigator.userAgent.includes("Electron/");
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { showNotification } from "@api/Notifications";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getTheme, Theme } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByProps, findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, ChannelStore, FluxDispatcher, GuildChannelStore, NavigationRouter, RestAPI, Tooltip, UserStore } from "@webpack/common";
|
||||
|
||||
const ToolBarQuestsIcon = findComponentByCodeLazy("1 0 1 1.73Z");
|
||||
|
||||
function ToolBarHeader() {
|
||||
return (
|
||||
<ErrorBoundary noop={true}>
|
||||
<HeaderBarIcon
|
||||
tooltip="Complete Quest"
|
||||
position="bottom"
|
||||
className="vc-quest-completer"
|
||||
icon={ToolBarQuestsIcon}
|
||||
onClick={openCompleteQuestUI}
|
||||
>
|
||||
</HeaderBarIcon>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
const QuestIcon = findComponentByCodeLazy("10.47a.76.76");
|
||||
|
||||
async function openCompleteQuestUI() {
|
||||
const ApplicationStreamingStore = findByProps("getStreamerActiveStreamMetadata");
|
||||
|
@ -72,7 +50,7 @@ async function openCompleteQuestUI() {
|
|||
|
||||
const applicationId = quest.config.application.id;
|
||||
const applicationName = quest.config.application.name;
|
||||
const taskName = ["WATCH_VIDEO", "PLAY_ON_DESKTOP", "STREAM_ON_DESKTOP"].find(x => quest.config.taskConfig.tasks[x] != null);
|
||||
const taskName = ["WATCH_VIDEO", "PLAY_ON_DESKTOP", "STREAM_ON_DESKTOP", "PLAY_ACTIVITY"].find(x => quest.config.taskConfig.tasks[x] != null);
|
||||
// @ts-ignore
|
||||
const secondsNeeded = quest.config.taskConfig.tasks[taskName].target;
|
||||
// @ts-ignore
|
||||
|
@ -108,18 +86,9 @@ async function openCompleteQuestUI() {
|
|||
});
|
||||
console.log(`Spoofing video for ${applicationName}.`);
|
||||
} else if (taskName === "PLAY_ON_DESKTOP") {
|
||||
if (!isApp) {
|
||||
showNotification({
|
||||
title: `${applicationName} - Quest Completer`,
|
||||
body: `${applicationName}'s quest requires the desktop app.`,
|
||||
icon: icon,
|
||||
});
|
||||
}
|
||||
RestAPI.get({ url: `/applications/public?application_ids=${applicationId}` }).then(res => {
|
||||
const appData = res.body[0];
|
||||
const exeName = appData.executables.find(x => x.os === "win32").name.replace(">", "");
|
||||
|
||||
const games = RunningGameStore.getRunningGames();
|
||||
const fakeGame = {
|
||||
cmdLine: `C:\\Program Files\\${appData.name}\\${exeName}`,
|
||||
exeName,
|
||||
|
@ -133,8 +102,15 @@ async function openCompleteQuestUI() {
|
|||
processName: appData.name,
|
||||
start: Date.now(),
|
||||
};
|
||||
games.push(fakeGame);
|
||||
FluxDispatcher.dispatch({ type: "RUNNING_GAMES_CHANGE", removed: [], added: [fakeGame], games: games });
|
||||
const realGames = RunningGameStore.getRunningGames();
|
||||
const fakeGames = [fakeGame];
|
||||
const realGetRunningGames = RunningGameStore.getRunningGames;
|
||||
const realGetGameForPID = RunningGameStore.getGameForPID;
|
||||
RunningGameStore.getRunningGames = () => fakeGames;
|
||||
RunningGameStore.getGameForPID = pid => fakeGames.find(x => x.pid === pid);
|
||||
FluxDispatcher.dispatch({
|
||||
type: "RUNNING_GAMES_CHANGE", removed: realGames, added: [fakeGame], games: fakeGames
|
||||
});
|
||||
|
||||
const fn = data => {
|
||||
const progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.PLAY_ON_DESKTOP.value);
|
||||
|
@ -151,11 +127,9 @@ async function openCompleteQuestUI() {
|
|||
icon: icon,
|
||||
});
|
||||
|
||||
const idx = games.indexOf(fakeGame);
|
||||
if (idx > -1) {
|
||||
games.splice(idx, 1);
|
||||
FluxDispatcher.dispatch({ type: "RUNNING_GAMES_CHANGE", removed: [fakeGame], added: [], games: [] });
|
||||
}
|
||||
RunningGameStore.getRunningGames = realGetRunningGames;
|
||||
RunningGameStore.getGameForPID = realGetGameForPID;
|
||||
FluxDispatcher.dispatch({ type: "RUNNING_GAMES_CHANGE", removed: [fakeGame], added: [], games: [] });
|
||||
FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn);
|
||||
}
|
||||
};
|
||||
|
@ -163,13 +137,6 @@ async function openCompleteQuestUI() {
|
|||
console.log(`Spoofed your game to ${applicationName}.`);
|
||||
});
|
||||
} else if (taskName === "STREAM_ON_DESKTOP") {
|
||||
if (!isApp) {
|
||||
showNotification({
|
||||
title: `${applicationName} - Quest Completer`,
|
||||
body: `${applicationName}'s quest requires the desktop app.`,
|
||||
icon: icon,
|
||||
});
|
||||
}
|
||||
const stream = ApplicationStreamingStore.getAnyStreamForUser(UserStore.getCurrentUser()?.id);
|
||||
if (!stream) {
|
||||
showNotification({
|
||||
|
@ -211,84 +178,68 @@ async function openCompleteQuestUI() {
|
|||
};
|
||||
FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn);
|
||||
console.log(`Spoofed your stream to ${applicationName}.`);
|
||||
} else if (taskName === "PLAY_ACTIVITY") {
|
||||
const channelId = ChannelStore.getSortedPrivateChannels()[0]?.id ?? Object.values(GuildChannelStore.getAllGuilds() as any[]).find(x => x != null && x.VOCAL.length > 0).VOCAL[0].channel.id;
|
||||
const streamKey = `call:${channelId}:1`;
|
||||
|
||||
const fn = async () => {
|
||||
|
||||
while (true) {
|
||||
const res = await RestAPI.post({ url: `/quests/${quest.id}/heartbeat`, body: { stream_key: streamKey, terminal: false } });
|
||||
const progress = res.body.progress.PLAY_ACTIVITY.value;
|
||||
showNotification({
|
||||
title: `${applicationName} - Quest Completer`,
|
||||
body: `Current progress: ${progress}/${secondsNeeded} seconds.`,
|
||||
icon: icon,
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 20 * 1000));
|
||||
|
||||
if (progress >= secondsNeeded) {
|
||||
await RestAPI.post({ url: `/quests/${quest.id}/heartbeat`, body: { stream_key: streamKey, terminal: true } });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
showNotification({
|
||||
title: `${applicationName} - Quest Completer`,
|
||||
body: "Quest Completed.",
|
||||
icon: icon,
|
||||
});
|
||||
};
|
||||
fn();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
clickableQuestDiscovery: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Makes the quest button in discovery clickable",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "QuestCompleter",
|
||||
description: "A plugin to complete quests without having the game installed.",
|
||||
authors: [Devs.amia],
|
||||
settingsAboutComponent: () => <>
|
||||
<Forms.FormText className="plugin-warning">
|
||||
Game Quests do not work on Equibop/Web Platforms. Only Video Quests do.
|
||||
</Forms.FormText>
|
||||
</>,
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
find: "\"invite-button\"",
|
||||
find: "AppTitleBar",
|
||||
replacement: {
|
||||
match: /\i&&(\i\i\.push).{0,50}"current-speaker"/,
|
||||
replace: "$1($self.renderQuestButton()),$&"
|
||||
match: /(?<=trailing:.{0,70}\(\i\.Fragment,{children:\[)/,
|
||||
replace: "$self.renderQuestButton(),"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "toolbar:function",
|
||||
replacement: {
|
||||
match: /(function \i\(\i\){)(.{1,500}toolbar.{1,500}mobileToolbar)/,
|
||||
replace: "$1$self.toolbarAction(arguments[0]);$2"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "M7.5 21.7a8.95 8.95 0 0 1 9 0 1 1 0 0 0 1-1.73c",
|
||||
replacement: {
|
||||
match: /(?<=className:\i\}\))/,
|
||||
replace: ",onClick:()=>$self.openCompleteQuestUI()"
|
||||
},
|
||||
predicate: () => settings.store.clickableQuestDiscovery
|
||||
}
|
||||
],
|
||||
renderQuestButton() {
|
||||
return (
|
||||
<Tooltip text="Complete Quest">
|
||||
{tooltipProps => (
|
||||
<Button style={{ backgroundColor: "transparent" }}
|
||||
<Button style={{ backgroundColor: "transparent", border: "none" }}
|
||||
{...tooltipProps}
|
||||
size={"25"}
|
||||
size={Button.Sizes.SMALL}
|
||||
className={"vc-quest-completer-icon"}
|
||||
onClick={openCompleteQuestUI}
|
||||
>
|
||||
<ToolBarQuestsIcon />
|
||||
<QuestIcon width={20} height={20} size={Button.Sizes.SMALL} />
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
openCompleteQuestUI,
|
||||
toolbarAction(e) {
|
||||
if (Array.isArray(e.toolbar))
|
||||
return e.toolbar.push(
|
||||
<ErrorBoundary noop={true}>
|
||||
<ToolBarHeader />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
e.toolbar = [
|
||||
<ErrorBoundary noop={true} key={"QuestCompleter"} >
|
||||
<ToolBarHeader />
|
||||
</ErrorBoundary>,
|
||||
e.toolbar,
|
||||
];
|
||||
}
|
||||
});
|
12
src/equicordplugins/questCompleter.discordDesktop/style.css
Normal file
12
src/equicordplugins/questCompleter.discordDesktop/style.css
Normal file
|
@ -0,0 +1,12 @@
|
|||
.vc-quest-completer-icon {
|
||||
bottom: -2px;
|
||||
padding: 0;
|
||||
width: var(--space-32);
|
||||
min-width: 0;
|
||||
height: var(--space-32);
|
||||
color: var(--interactive-normal) !important;
|
||||
|
||||
&:hover{
|
||||
filter: brightness(2);
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
.vc-quest-completer-icon:hover{
|
||||
filter: brightness(10);
|
||||
}
|
|
@ -10,7 +10,7 @@ import { Devs } from "@utils/constants";
|
|||
import { getCurrentChannel } from "@utils/discord";
|
||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, Menu, Select, Switch, Text, TextInput, UploadHandler, useEffect, UserStore, useState } from "@webpack/common";
|
||||
import { Button, Menu, Select, Switch, Text, UploadHandler, useEffect, useState } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
import { QuoteIcon } from "./components";
|
||||
|
@ -49,7 +49,6 @@ let recentmessage: Message;
|
|||
let grayscale;
|
||||
let setStyle: ImageStyle = ImageStyle.inspirational;
|
||||
let customMessage: string = "";
|
||||
let isUserCustomCapable = false;
|
||||
|
||||
enum userIDOptions {
|
||||
displayName,
|
||||
|
@ -89,14 +88,7 @@ const preparingSentence: string[] = [];
|
|||
const lines: string[] = [];
|
||||
|
||||
async function createQuoteImage(avatarUrl: string, quoteOld: string, grayScale: boolean): Promise<Blob> {
|
||||
let quote;
|
||||
|
||||
if (isUserCustomCapable && customMessage.length > 0) {
|
||||
quote = FixUpQuote(customMessage);
|
||||
}
|
||||
else {
|
||||
quote = FixUpQuote(quoteOld);
|
||||
}
|
||||
const quote = FixUpQuote(quoteOld);
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
|
@ -141,7 +133,7 @@ async function createQuoteImage(avatarUrl: string, quoteOld: string, grayScale:
|
|||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const avatarBlob = await fetchImageAsBlob(avatarUrl);
|
||||
const fadeBlob = await fetchImageAsBlob("https://github.com/Equicord/Equibored/raw/main/misc/quoter.png");
|
||||
const fadeBlob = await fetchImageAsBlob("https://raw.githubusercontent.com/Equicord/Equibored/refs/heads/main/icons/quoter/quoter.png");
|
||||
|
||||
const avatar = new Image();
|
||||
const fade = new Image();
|
||||
|
@ -194,13 +186,7 @@ function registerStyleChange(style) {
|
|||
GeneratePreview();
|
||||
}
|
||||
|
||||
async function setIsUserCustomCapable() {
|
||||
const allowList: string[] = await fetch("https://equicord.org/quoter").then(e => e.json()); // Override for memes - IF THIS IS ABUSED WILL WE TAKEN AWAY
|
||||
isUserCustomCapable = allowList.includes(UserStore.getCurrentUser().id);
|
||||
}
|
||||
|
||||
function QuoteModal(props: ModalProps) {
|
||||
setIsUserCustomCapable();
|
||||
const [gray, setGray] = useState(true);
|
||||
useEffect(() => {
|
||||
grayscale = gray;
|
||||
|
@ -226,13 +212,6 @@ function QuoteModal(props: ModalProps) {
|
|||
<ModalContent scrollbarType="none">
|
||||
<img alt="" src="" id={"quoterPreview"} style={{ borderRadius: "20px", width: "100%" }}></img>
|
||||
<br></br><br></br>
|
||||
{isUserCustomCapable &&
|
||||
(
|
||||
<>
|
||||
<TextInput onChange={setCustom} value={custom} placeholder="Custom Message"></TextInput>
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
<Switch value={gray} onChange={setGray}>Grayscale</Switch>
|
||||
<Select look={1}
|
||||
options={Object.keys(ImageStyle).filter(key => isNaN(parseInt(key, 10))).map(key => ({
|
||||
|
@ -278,14 +257,7 @@ async function GeneratePreview() {
|
|||
}
|
||||
|
||||
function generateFileNamePreview(message) {
|
||||
let words;
|
||||
|
||||
if (isUserCustomCapable && customMessage.length) {
|
||||
words = customMessage.split(" ");
|
||||
}
|
||||
else {
|
||||
words = message.split(" ");
|
||||
}
|
||||
const words = message.split(" ");
|
||||
let preview;
|
||||
if (words.length >= 6) {
|
||||
preview = words.slice(0, 6).join(" ");
|
||||
|
|
|
@ -11,8 +11,8 @@ import { makeRange } from "@components/PluginSettings/components";
|
|||
import { debounce } from "@shared/debounce";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||
import { ChannelStore, ContextMenuApi, GuildStore, Menu, NavigationRouter, PermissionStore, React, SelectedChannelStore, Toasts, UserStore } from "@webpack/common";
|
||||
import { findByCode, findByProps, findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||
import { ChannelRouter, ChannelStore, ContextMenuApi, GuildStore, Menu, PermissionsBits, PermissionStore, React, SelectedChannelStore, Toasts, UserStore } from "@webpack/common";
|
||||
|
||||
import style from "./styles.css?managed";
|
||||
|
||||
|
@ -32,6 +32,7 @@ const valueOperation = [
|
|||
const CONNECT = 1n << 20n;
|
||||
const SPEAK = 1n << 21n;
|
||||
const STREAM = 1n << 9n;
|
||||
const VIDEO = 1 << 21;
|
||||
|
||||
const settings = definePluginSettings({
|
||||
UserAmountOperation: {
|
||||
|
@ -85,6 +86,11 @@ const settings = definePluginSettings({
|
|||
description: "Automatically turns on camera",
|
||||
default: false,
|
||||
},
|
||||
autoStream: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Automatically turns on stream",
|
||||
default: false,
|
||||
},
|
||||
selfMute: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Automatically mutes your mic when joining voice-channel.",
|
||||
|
@ -95,6 +101,11 @@ const settings = definePluginSettings({
|
|||
description: "Automatically deafems your mic when joining voice-channel.",
|
||||
default: false,
|
||||
},
|
||||
leaveEmpty: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Finds a random-call, when the voice chat is empty.",
|
||||
default: false,
|
||||
},
|
||||
avoidStages: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Avoids joining stage voice-channels.",
|
||||
|
@ -137,11 +148,25 @@ const settings = definePluginSettings({
|
|||
},
|
||||
});
|
||||
|
||||
interface VoiceState {
|
||||
userId: string;
|
||||
channelId?: string;
|
||||
oldChannelId?: string;
|
||||
deaf: boolean;
|
||||
mute: boolean;
|
||||
selfDeaf: boolean;
|
||||
selfMute: boolean;
|
||||
selfStream: boolean;
|
||||
selfVideo: boolean;
|
||||
sessionId: string;
|
||||
suppress: boolean;
|
||||
requestToSpeakTimestamp: string | null;
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "RandomVoice",
|
||||
description: "Adds a Button near the Mute button to join a random voice call.",
|
||||
authors: [EquicordDevs.xijexo, EquicordDevs.omaw],
|
||||
authors: [EquicordDevs.xijexo, EquicordDevs.omaw, EquicordDevs.thororen],
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
|
||||
|
@ -151,6 +176,22 @@ export default definePlugin({
|
|||
}
|
||||
}
|
||||
],
|
||||
flux: {
|
||||
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
|
||||
const currentUserId = UserStore.getCurrentUser().id;
|
||||
const myChannelId = VoiceStateStore.getVoiceStateForUser(currentUserId)?.channelId;
|
||||
if (!myChannelId || !settings.store.leaveEmpty) return;
|
||||
|
||||
const voiceStatesMap = VoiceStateStore.getVoiceStates() as Record<string, VoiceState>;
|
||||
const othersInChannel = Object.values(voiceStatesMap).filter(vs =>
|
||||
vs.channelId === myChannelId && vs.userId !== currentUserId
|
||||
);
|
||||
|
||||
if (othersInChannel.length === 0) {
|
||||
randomVoice();
|
||||
}
|
||||
},
|
||||
},
|
||||
start() {
|
||||
enableStyle(style);
|
||||
},
|
||||
|
@ -194,8 +235,7 @@ function ContextMenu() {
|
|||
});
|
||||
|
||||
ServerList = Array.from(new Set(ServerList));
|
||||
const Servers = ServerList.map(server => GuildStore.getGuild(server)).filter(guild => guild !== null);
|
||||
|
||||
const Servers = ServerList.map(server => GuildStore.getGuild(server)).filter(guild => guild && guild.id);
|
||||
const [servers, setServers] = React.useState(settings.store.Servers);
|
||||
const [SpacesLeftOperation, setSpacesLeftOperation] = React.useState(settings.store.spacesLeftOperation);
|
||||
const [userAmount, setuserAmount] = React.useState(settings.store.UserAmountOperation);
|
||||
|
@ -204,12 +244,13 @@ function ContextMenu() {
|
|||
const [stage, setStage] = React.useState(settings.store.avoidStages);
|
||||
const [afk, setAfk] = React.useState(settings.store.avoidAfk);
|
||||
const [camera, setCamera] = React.useState(settings.store.autoCamera);
|
||||
const [stream, setStream] = React.useState(settings.store.autoStream);
|
||||
const [empty, setEmpty] = React.useState(settings.store.leaveEmpty);
|
||||
const [muteself, setSelfMute] = React.useState(settings.store.selfMute);
|
||||
const [deafenself, setSelfDeafen] = React.useState(settings.store.selfDeafen);
|
||||
const [mute, setMute] = React.useState(settings.store.mute);
|
||||
const [deafen, setDeafen] = React.useState(settings.store.deafen);
|
||||
const [video, setVideo] = React.useState(settings.store.video);
|
||||
const [stream, setStream] = React.useState(settings.store.stream);
|
||||
const [state, setState] = React.useState(settings.store.includeStates);
|
||||
const [notstate, avoidState] = React.useState(settings.store.avoidStates);
|
||||
|
||||
|
@ -219,6 +260,8 @@ function ContextMenu() {
|
|||
onClose={() => { }}
|
||||
aria-label="Voice state modifier"
|
||||
>
|
||||
|
||||
|
||||
<Menu.MenuItem
|
||||
id="servers"
|
||||
label="Select Servers"
|
||||
|
@ -318,7 +361,7 @@ function ContextMenu() {
|
|||
<Menu.MenuCheckboxItem
|
||||
key="video"
|
||||
id="video"
|
||||
label="Video"
|
||||
label="Camera"
|
||||
action={() => {
|
||||
setVideo(!video);
|
||||
settings.store.video = !video;
|
||||
|
@ -545,7 +588,7 @@ function ContextMenu() {
|
|||
|
||||
<Menu.MenuSeparator />
|
||||
<Menu.MenuGroup
|
||||
label="SETTINGS"
|
||||
label="SELF SETTINGS"
|
||||
>
|
||||
<Menu.MenuItem id="voiceOptions" label="Voice Options" action={() => { }} >
|
||||
<>
|
||||
|
@ -577,6 +620,24 @@ function ContextMenu() {
|
|||
settings.store.autoCamera = !camera;
|
||||
}}
|
||||
checked={camera} />
|
||||
<Menu.MenuCheckboxItem
|
||||
key="autoStream"
|
||||
id="autoStream"
|
||||
label="Auto Stream"
|
||||
action={() => {
|
||||
setStream(!stream);
|
||||
settings.store.autoStream = !stream;
|
||||
}}
|
||||
checked={stream} />
|
||||
<Menu.MenuCheckboxItem
|
||||
key="leaveEmpty"
|
||||
id="leaveEmpty"
|
||||
label="Leave when Empty"
|
||||
action={() => {
|
||||
setEmpty(!empty);
|
||||
settings.store.leaveEmpty = !empty;
|
||||
}}
|
||||
checked={empty} />
|
||||
</>
|
||||
</Menu.MenuItem>
|
||||
|
||||
|
@ -704,15 +765,35 @@ function getChannels() {
|
|||
|
||||
function JoinVc(channelID) {
|
||||
const channel = ChannelStore.getChannel(channelID);
|
||||
const channel_link = `/channels/${channel.guild_id}/${channel.id}`;
|
||||
ChannelActions.selectVoiceChannel(channelID);
|
||||
if (settings.store.autoNavigate) NavigationRouter.transitionTo(channel_link);
|
||||
if (settings.store.autoCamera && PermissionStore.can(STREAM, channel)) autoCamera();
|
||||
if (settings.store.autoCamera && PermissionStore.can(STREAM, channel)) autoCamera();
|
||||
if (settings.store.autoNavigate) ChannelRouter.transitionToChannel(channel.id);
|
||||
if (settings.store.autoCamera && PermissionStore.can(VIDEO, channel)) autoCamera();
|
||||
if (settings.store.autoStream && PermissionStore.can(STREAM, channel)) autoStream();
|
||||
if (settings.store.selfMute && !MediaEngineStore.isSelfMute() && SelectedChannelStore.getVoiceChannelId()) toggleSelfMute();
|
||||
if (settings.store.selfDeafen && !MediaEngineStore.isSelfDeaf() && SelectedChannelStore.getVoiceChannelId()) toggleSelfDeaf();
|
||||
}
|
||||
|
||||
async function autoStream() {
|
||||
const startStream = findByCode('type:"STREAM_START"');
|
||||
const mediaEngine = findByProps("getMediaEngine").getMediaEngine();
|
||||
const getDesktopSources = findByCode("desktop sources");
|
||||
const selected = SelectedChannelStore.getVoiceChannelId();
|
||||
if (!selected) return;
|
||||
const channel = ChannelStore.getChannel(selected);
|
||||
const sources = await getDesktopSources(mediaEngine, ["screen"], null);
|
||||
if (!sources || sources.length === 0) return;
|
||||
const source = sources[0];
|
||||
if (channel.type === 13 || !PermissionStore.can(PermissionsBits.STREAM, channel)) return;
|
||||
startStream(channel.guild_id, selected, {
|
||||
"pid": null,
|
||||
"sourceId": source.id,
|
||||
"sourceName": source.name,
|
||||
"audioSourceId": null,
|
||||
"sound": true,
|
||||
"previewDisabled": false
|
||||
});
|
||||
}
|
||||
|
||||
function autoCamera() {
|
||||
const checkExist = setInterval(() => {
|
||||
const cameraOFF = document.querySelector('[aria-label="Turn off Camera" i]') as HTMLButtonElement;
|
||||
|
|
|
@ -55,7 +55,7 @@ interface ContextMenuProps {
|
|||
}
|
||||
|
||||
const ArrowsLeftRightIcon = findComponentByCodeLazy("18.58V3a1");
|
||||
const XSmallIcon = findComponentByCodeLazy("12l4.94-4.94a1.5")
|
||||
const XSmallIcon = findComponentByCodeLazy("12l4.94-4.94a1.5");
|
||||
|
||||
function MakeContextCallback(name: "user" | "channel"): NavContextMenuPatchCallback {
|
||||
return (children, { user, channel, guildId }: ContextMenuProps) => {
|
||||
|
@ -149,7 +149,7 @@ export default definePlugin({
|
|||
}}
|
||||
/>
|
||||
<HeaderBarIcon
|
||||
icon={() => (<XSmallIcon style={{width: "24px",height: "24px"}} />)}
|
||||
icon={() => (<XSmallIcon style={{ width: "24px",height: "24px" }} />)}
|
||||
tooltip="Close Sidebar Chat"
|
||||
onClick={() => {
|
||||
FluxDispatcher.dispatch({
|
||||
|
|
|
@ -22,6 +22,14 @@ const settings = definePluginSettings(
|
|||
description: "The signature that will be added to the end of your messages",
|
||||
default: "a chronic discord user"
|
||||
},
|
||||
textHeader: {
|
||||
description: "What header to preface text with",
|
||||
type: OptionType.SELECT,
|
||||
options: [
|
||||
{ label: ">", value: ">", default: true },
|
||||
{ label: "-#", value: "-#" }
|
||||
]
|
||||
},
|
||||
showIcon: {
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
|
@ -141,7 +149,7 @@ export default definePlugin({
|
|||
|
||||
// text processing injection processor
|
||||
function textProcessing(input: string) {
|
||||
return `${input}\n> ${settings.store.name}`;
|
||||
return `${input}\n${settings.store.textHeader} ${settings.store.name}`;
|
||||
}
|
||||
|
||||
|
||||
|
|
168
src/equicordplugins/soggy/index.tsx
Normal file
168
src/equicordplugins/soggy/index.tsx
Normal file
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { ModalProps, ModalRoot, openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { React } from "@webpack/common";
|
||||
|
||||
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
|
||||
|
||||
let preloadSong, preloadBoopSound, song, boopSound;
|
||||
|
||||
function SoggyModal(props: ModalProps) {
|
||||
if (settings.store.songVolume !== 0) {
|
||||
React.useEffect(() => {
|
||||
song = new Audio(preloadSong.src);
|
||||
song.volume = settings.store.songVolume;
|
||||
song.play();
|
||||
|
||||
return () => {
|
||||
song.pause();
|
||||
song.remove();
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
||||
const boop = (e: React.MouseEvent<HTMLImageElement>) => {
|
||||
const { offsetX, offsetY } = e.nativeEvent;
|
||||
|
||||
const region = { x: 155, y: 220, width: 70, height: 70 };
|
||||
|
||||
if (
|
||||
settings.store.boopVolume !== 0 &&
|
||||
offsetX >= region.x &&
|
||||
offsetX <= region.x + region.width &&
|
||||
offsetY >= region.y &&
|
||||
offsetY <= region.y + region.height
|
||||
) {
|
||||
boopSound = new Audio(preloadBoopSound.src);
|
||||
boopSound.volume = settings.store.boopVolume;
|
||||
boopSound.play();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalRoot {...props}>
|
||||
<img
|
||||
src={settings.store.imageLink}
|
||||
onClick={boop}
|
||||
style={{ display: "block" }}
|
||||
|
||||
/>
|
||||
</ModalRoot >
|
||||
);
|
||||
}
|
||||
|
||||
function buildSoggyModal(): any {
|
||||
openModal(props => <SoggyModal {...props} />);
|
||||
}
|
||||
|
||||
function SoggyButton() {
|
||||
return (
|
||||
<HeaderBarIcon
|
||||
className="soggy-button"
|
||||
tooltip={settings.store.tooltipText}
|
||||
icon={() => (
|
||||
<img
|
||||
alt=""
|
||||
src={settings.store.imageLink}
|
||||
width={24}
|
||||
height={24}
|
||||
draggable={false}
|
||||
style={{ pointerEvents: "none" }}
|
||||
/>
|
||||
)}
|
||||
onClick={() => buildSoggyModal()}
|
||||
selected={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
songVolume: {
|
||||
description: "Volume of the song. 0 to disable",
|
||||
type: OptionType.SLIDER,
|
||||
default: 0.25,
|
||||
markers: [0, 0.25, 0.5, 0.75, 1],
|
||||
stickToMarkers: false,
|
||||
|
||||
},
|
||||
boopVolume: {
|
||||
description: "Volume of the boop sound",
|
||||
type: OptionType.SLIDER,
|
||||
default: 0.2,
|
||||
markers: [0, 0.25, 0.5, 0.75, 1],
|
||||
stickToMarkers: false,
|
||||
},
|
||||
tooltipText: {
|
||||
description: "The text shown when hovering over the button",
|
||||
type: OptionType.STRING,
|
||||
default: "the soggy",
|
||||
},
|
||||
imageLink: {
|
||||
description: "URL for the image (button and modal)",
|
||||
type: OptionType.STRING,
|
||||
default: "https://soggy.cat/img/soggycat.webp",
|
||||
},
|
||||
songLink: {
|
||||
description: "URL for the song to play",
|
||||
type: OptionType.STRING,
|
||||
default: "https://github.com/Equicord/Equibored/raw/main/sounds/soggy/song.mp3?raw=true",
|
||||
onChange: (value: string) => {
|
||||
song = new Audio(value);
|
||||
}
|
||||
},
|
||||
boopLink: {
|
||||
description: "URL for the boop sound",
|
||||
type: OptionType.STRING,
|
||||
default: "https://github.com/Equicord/Equibored/raw/main/sounds/soggy/honk.wav?raw=true",
|
||||
onChange: (value: string) => {
|
||||
boopSound = new Audio(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "Soggy",
|
||||
description: "Adds a soggy button to the toolbox",
|
||||
authors: [EquicordDevs.sliwka],
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
find: "toolbar:function",
|
||||
replacement: {
|
||||
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,450}mobileToolbar)/,
|
||||
replace: "$1$self.addIconToToolBar(arguments[0]);$2"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
start: () => {
|
||||
preloadSong = new Audio(settings.store.songLink);
|
||||
preloadBoopSound = new Audio(settings.store.boopLink);
|
||||
},
|
||||
|
||||
// taken from message logger lol
|
||||
addIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) {
|
||||
if (Array.isArray(e.toolbar))
|
||||
return e.toolbar.push(
|
||||
<ErrorBoundary noop={true}>
|
||||
<SoggyButton />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
e.toolbar = [
|
||||
<ErrorBoundary noop={true} key={"MessageLoggerEnhanced"} >
|
||||
<SoggyButton />
|
||||
</ErrorBoundary>,
|
||||
e.toolbar,
|
||||
];
|
||||
},
|
||||
});
|
|
@ -58,7 +58,8 @@ export function IconWithTooltip({ text, icon, onClick }) {
|
|||
</Tooltip>;
|
||||
}
|
||||
|
||||
export const ChatBarIcon: ChatBarButtonFactory = () => {
|
||||
export const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
if (!isMainChat) return null;
|
||||
return (
|
||||
<ChatBarButton tooltip="Open SoundBoard Log"
|
||||
onClick={openSoundBoardLog}>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue