Merge branch 'dev' into translation

This commit is contained in:
thororen1234 2025-12-03 13:07:58 -05:00
commit b51d882940
No known key found for this signature in database
96 changed files with 962 additions and 1310 deletions

View file

@ -77,13 +77,22 @@ jobs:
if: steps.check.outputs.release == 'true' if: steps.check.outputs.release == 'true'
run: pnpm buildWebStandalone run: pnpm buildWebStandalone
- name: Create sources zip
if: steps.check.outputs.release == 'true'
run: zip -r dist/extension-sources.zip . -x "node_modules/*" -x "dist/*" -x ".git/*"
- name: Publish extension - name: Publish extension
if: steps.check.outputs.release == 'true' if: steps.check.outputs.release == 'true'
run: | run: pnpx publish-browser-extension@latest --chrome-zip dist/extension-chrome.zip --firefox-zip dist/extension-firefox.zip --firefox-sources-zip dist/extension-sources.zip --edge-zip dist/extension-chrome.zip
cd dist/browser/chromium-unpacked
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish
env: env:
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }}
FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }}
FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }}
EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }}
EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }}
EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }}

View file

@ -1,7 +1,7 @@
{ {
"name": "equicord", "name": "equicord",
"private": "true", "private": "true",
"version": "1.13.6.2", "version": "1.13.6.4",
"description": "The other cutest Discord client mod", "description": "The other cutest Discord client mod",
"homepage": "https://github.com/Equicord/Equicord#readme", "homepage": "https://github.com/Equicord/Equicord#readme",
"bugs": { "bugs": {

View file

@ -1,7 +1,7 @@
{ {
"name": "@equicord/types", "name": "@equicord/types",
"private": false, "private": false,
"version": "1.13.4", "version": "1.13.6",
"description": "", "description": "",
"types": "index.d.ts", "types": "index.d.ts",
"scripts": { "scripts": {

View file

@ -17,9 +17,12 @@
*/ */
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import globalBadges from "@equicordplugins/globalBadges";
import BadgeAPIPlugin from "@plugins/_api/badges"; import BadgeAPIPlugin from "@plugins/_api/badges";
import { ComponentType, HTMLProps } from "react"; import { ComponentType, HTMLProps } from "react";
import { isPluginEnabled } from "./PluginManager";
export const enum BadgePosition { export const enum BadgePosition {
START, START,
END END
@ -99,6 +102,18 @@ export function _getBadges(args: BadgeUserArgs) {
const donorBadges = BadgeAPIPlugin.getDonorBadges(args.userId); const donorBadges = BadgeAPIPlugin.getDonorBadges(args.userId);
const equicordDonorBadges = BadgeAPIPlugin.getEquicordDonorBadges(args.userId); const equicordDonorBadges = BadgeAPIPlugin.getEquicordDonorBadges(args.userId);
const GlobalBadges = isPluginEnabled(globalBadges.name) ? globalBadges.getGlobalBadges(args.userId) : false;
// do globalbadges first so it shows before the contrib badges but after donor badges
if (GlobalBadges) {
badges.unshift(
...GlobalBadges.map(badge => ({
...args,
...badge,
}))
);
}
if (donorBadges) { if (donorBadges) {
badges.unshift( badges.unshift(
...donorBadges.map(badge => ({ ...donorBadges.map(badge => ({

62
src/api/HeaderBar.tsx Normal file
View 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 ErrorBoundary from "@components/ErrorBoundary";
import { useEffect, useState } from "@webpack/common";
import type { JSX } from "react";
export type HeaderBarButtonFactory = () => JSX.Element | null;
interface HeaderBarButton {
render: HeaderBarButtonFactory;
priority: number;
}
export const buttons = new Map<string, HeaderBarButton>();
const listeners = new Set<() => void>();
/**
* Add a button to the header bar.
* @param id Unique identifier for the button.
* @param render Function that renders the button component.
* @param priority Higher priority = more to the right. Default is 0.
*/
export function addHeaderBarButton(id: string, render: HeaderBarButtonFactory, priority = 0) {
buttons.set(id, { render, priority });
listeners.forEach(l => l());
}
/**
* Remove a button from the header bar.
* @param id The identifier of the button to remove.
*/
export function removeHeaderBarButton(id: string) {
buttons.delete(id);
listeners.forEach(l => l());
}
function HeaderBarButtons() {
const [, forceUpdate] = useState(0);
useEffect(() => {
const listener = () => forceUpdate(n => n + 1);
listeners.add(listener);
return () => { listeners.delete(listener); };
}, []);
const sorted = Array.from(buttons).sort(([, a], [, b]) => a.priority - b.priority);
return sorted.map(([id, { render: Button }]) => (
<ErrorBoundary noop key={id}>
<Button />
</ErrorBoundary>
));
}
export function _addButtons() {
return [<HeaderBarButtons key="vc-header-bar-buttons" />];
}

View file

@ -40,6 +40,8 @@ import Plugins from "~plugins";
export { Plugins as plugins }; export { Plugins as plugins };
import { traceFunction } from "../debug/Tracer"; import { traceFunction } from "../debug/Tracer";
import { addAudioProcessor, removeAudioProcessor } from "./AudioPlayer";
import { addHeaderBarButton, removeHeaderBarButton } from "./HeaderBar";
const logger = new Logger("PluginManager", "#a6d189"); const logger = new Logger("PluginManager", "#a6d189");
@ -186,7 +188,9 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
const { const {
name, commands, contextMenus, managedStyle, userProfileBadges, name, commands, contextMenus, managedStyle, userProfileBadges,
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton, renderNicknameIcon renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton,
// Custom
renderNicknameIcon, renderHeaderBarButton, audioProcessor
} = p; } = p;
if (p.start) { if (p.start) {
@ -240,13 +244,17 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
// @ts-expect-error: legacy code doesn't have icon // @ts-expect-error: legacy code doesn't have icon
else if (renderChatBarButton) addChatBarButton(name, renderChatBarButton); else if (renderChatBarButton) addChatBarButton(name, renderChatBarButton);
if (renderMemberListDecorator) addMemberListDecorator(name, renderMemberListDecorator); if (renderMemberListDecorator) addMemberListDecorator(name, renderMemberListDecorator);
if (renderNicknameIcon) addNicknameIcon(name, renderNicknameIcon);
if (renderMessageDecoration) addMessageDecoration(name, renderMessageDecoration); if (renderMessageDecoration) addMessageDecoration(name, renderMessageDecoration);
if (renderMessageAccessory) addMessageAccessory(name, renderMessageAccessory); if (renderMessageAccessory) addMessageAccessory(name, renderMessageAccessory);
if (messagePopoverButton) addMessagePopoverButton(name, messagePopoverButton.render, messagePopoverButton.icon); if (messagePopoverButton) addMessagePopoverButton(name, messagePopoverButton.render, messagePopoverButton.icon);
// @ts-expect-error: legacy code doesn't have icon // @ts-expect-error: legacy code doesn't have icon
else if (renderMessagePopoverButton) addMessagePopoverButton(name, renderMessagePopoverButton); else if (renderMessagePopoverButton) addMessagePopoverButton(name, renderMessagePopoverButton);
// Custom
if (renderNicknameIcon) addNicknameIcon(name, renderNicknameIcon);
if (renderHeaderBarButton) addHeaderBarButton(name, renderHeaderBarButton);
if (audioProcessor) addAudioProcessor(name, audioProcessor);
return true; return true;
}, p => `startPlugin ${p.name}`); }, p => `startPlugin ${p.name}`);
@ -254,7 +262,9 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
const { const {
name, commands, contextMenus, managedStyle, userProfileBadges, name, commands, contextMenus, managedStyle, userProfileBadges,
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton, renderNicknameIcon renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton,
// Custom
renderNicknameIcon, renderHeaderBarButton, audioProcessor
} = p; } = p;
if (p.stop) { if (p.stop) {
@ -304,11 +314,15 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
if (chatBarButton || renderChatBarButton) removeChatBarButton(name); if (chatBarButton || renderChatBarButton) removeChatBarButton(name);
if (renderMemberListDecorator) removeMemberListDecorator(name); if (renderMemberListDecorator) removeMemberListDecorator(name);
if (renderNicknameIcon) removeNicknameIcon(name);
if (renderMessageDecoration) removeMessageDecoration(name); if (renderMessageDecoration) removeMessageDecoration(name);
if (renderMessageAccessory) removeMessageAccessory(name); if (renderMessageAccessory) removeMessageAccessory(name);
if (messagePopoverButton || renderMessagePopoverButton) removeMessagePopoverButton(name); if (messagePopoverButton || renderMessagePopoverButton) removeMessagePopoverButton(name);
// Custom
if (renderNicknameIcon) removeNicknameIcon(name);
if (renderHeaderBarButton) removeHeaderBarButton(name);
if (audioProcessor) removeAudioProcessor(name);
return true; return true;
}, p => `stopPlugin ${p.name}`); }, p => `stopPlugin ${p.name}`);
@ -318,7 +332,9 @@ export const initPluginManager = onlyOnce(function init() {
const pluginKeysToBind: Array<keyof PluginDef & `${"on" | "render"}${string}`> = [ const pluginKeysToBind: Array<keyof PluginDef & `${"on" | "render"}${string}`> = [
"onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick", "onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick",
"renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton", "renderNicknameIcon" "renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton",
// Custom
"renderNicknameIcon", "renderHeaderBarButton"
]; ];
const neededApiPlugins = new Set<string>(); const neededApiPlugins = new Set<string>();
@ -353,9 +369,13 @@ export const initPluginManager = onlyOnce(function init() {
if (p.renderMessageAccessory) neededApiPlugins.add("MessageAccessoriesAPI"); if (p.renderMessageAccessory) neededApiPlugins.add("MessageAccessoriesAPI");
if (p.renderMessageDecoration) neededApiPlugins.add("MessageDecorationsAPI"); if (p.renderMessageDecoration) neededApiPlugins.add("MessageDecorationsAPI");
if (p.messagePopoverButton || p.renderMessagePopoverButton) neededApiPlugins.add("MessagePopoverAPI"); if (p.messagePopoverButton || p.renderMessagePopoverButton) neededApiPlugins.add("MessagePopoverAPI");
if (p.renderNicknameIcon) neededApiPlugins.add("NicknameIconsAPI");
if (p.userProfileBadge) neededApiPlugins.add("BadgeAPI"); if (p.userProfileBadge) neededApiPlugins.add("BadgeAPI");
// Custom
if (p.renderNicknameIcon) neededApiPlugins.add("NicknameIconsAPI");
if (p.renderHeaderBarButton) neededApiPlugins.add("HeaderBarAPI");
if (p.audioProcessor) neededApiPlugins.add("AudioPlayerAPI");
for (const key of pluginKeysToBind) { for (const key of pluginKeysToBind) {
p[key] &&= p[key].bind(p) as any; p[key] &&= p[key].bind(p) as any;
} }

View file

@ -22,6 +22,7 @@ import * as $ChatButtons from "./ChatButtons";
import * as $Commands from "./Commands"; import * as $Commands from "./Commands";
import * as $ContextMenu from "./ContextMenu"; import * as $ContextMenu from "./ContextMenu";
import * as $DataStore from "./DataStore"; import * as $DataStore from "./DataStore";
import * as $HeaderBar from "./HeaderBar";
import * as $MemberListDecorators from "./MemberListDecorators"; import * as $MemberListDecorators from "./MemberListDecorators";
import * as $MessageAccessories from "./MessageAccessories"; import * as $MessageAccessories from "./MessageAccessories";
import * as $MessageDecorations from "./MessageDecorations"; import * as $MessageDecorations from "./MessageDecorations";
@ -129,6 +130,11 @@ export const ContextMenu = $ContextMenu;
*/ */
export const ChatButtons = $ChatButtons; export const ChatButtons = $ChatButtons;
/**
* An API allowing you to add buttons to the header bar.
*/
export const HeaderBar = $HeaderBar;
/** /**
* An API allowing you to update and re-render messages. * An API allowing you to update and re-render messages.
*/ */

59
src/components/Alert.tsx Normal file
View file

@ -0,0 +1,59 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { filters, mapMangledModuleLazy } from "@webpack";
import type { ComponentType, CSSProperties, ReactNode } from "react";
const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE="positive', {
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING", "INFO", "ERROR"),
HelpMessage: filters.byCode(".iconDiv")
});
export interface AlertProps {
children: ReactNode;
className?: string;
style?: CSSProperties;
icon?: ComponentType<{ className?: string; color?: string; }>;
}
function Warning({ children, className, style, icon }: AlertProps) {
return (
<HelpMessage messageType={HelpMessageTypes.WARNING} className={className} style={style} icon={icon}>
{children}
</HelpMessage>
);
}
function Info({ children, className, style, icon }: AlertProps) {
return (
<HelpMessage messageType={HelpMessageTypes.INFO} className={className} style={style} icon={icon}>
{children}
</HelpMessage>
);
}
function Error({ children, className, style, icon }: AlertProps) {
return (
<HelpMessage messageType={HelpMessageTypes.ERROR} className={className} style={style} icon={icon}>
{children}
</HelpMessage>
);
}
function Positive({ children, className, style, icon }: AlertProps) {
return (
<HelpMessage messageType={HelpMessageTypes.POSITIVE} className={className} style={style} icon={icon}>
{children}
</HelpMessage>
);
}
export const Alert = {
Warning,
Info,
Error,
Positive
};

View file

@ -21,6 +21,7 @@ import "./themesStyles.css";
import { isPluginEnabled } from "@api/PluginManager"; import { isPluginEnabled } from "@api/PluginManager";
import { Settings, useSettings } from "@api/Settings"; import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { Button } from "@components/Button";
import { Card } from "@components/Card"; import { Card } from "@components/Card";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
@ -44,7 +45,7 @@ import { usercssParse } from "@utils/themes/usercss";
import { t } from "@utils/translation"; import { t } from "@utils/translation";
import { getStylusWebStoreUrl } from "@utils/web"; import { getStylusWebStoreUrl } from "@utils/web";
import { findLazy } from "@webpack"; import { findLazy } from "@webpack";
import { Alerts, Button, React, showToast, TabBar, TextInput, Tooltip, useEffect, useMemo, useRef, useState } from "@webpack/common"; import { Alerts, React, showToast, TabBar, TextInput, Tooltip, useEffect, useMemo, useRef, useState } from "@webpack/common";
import type { ComponentType, Ref, SyntheticEvent } from "react"; import type { ComponentType, Ref, SyntheticEvent } from "react";
import type { UserstyleHeader } from "usercss-meta"; import type { UserstyleHeader } from "usercss-meta";
@ -600,7 +601,7 @@ export function CspErrorCard() {
{i !== 0 && <Divider className={Margins.bottom8} />} {i !== 0 && <Divider className={Margins.bottom8} />}
<div className="vc-settings-csp-row"> <div className="vc-settings-csp-row">
<Link href={url}>{url}</Link> <Link href={url}>{url}</Link>
<Button color={Button.Colors.PRIMARY} onClick={() => allowUrl(url)} disabled={isImgurHtmlDomain(url)}> <Button variant="secondary" onClick={() => allowUrl(url)} disabled={isImgurHtmlDomain(url)}>
Allow Allow
</Button> </Button>
</div> </div>

View file

@ -90,8 +90,8 @@ function ResetButton({ themeSettings, themeId, close, onReset }: ResetButtonProp
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onClick={async () => { onClick={() => {
await close(); // close the modal first to stop rendering close(); // close the modal first to stop rendering
delete themeSettings[themeId]; delete themeSettings[themeId];
onReset(); onReset();
}}> }}>

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
export * from "./Alert";
export * from "./BaseText"; export * from "./BaseText";
export * from "./Button"; export * from "./Button";
export * from "./Card"; export * from "./Card";

View file

@ -16,23 +16,24 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Button } from "@components/Button";
import { Heart } from "@components/Heart"; import { Heart } from "@components/Heart";
import { openInviteModal } from "@utils/discord"; import { openInviteModal } from "@utils/discord";
import { t } from "@utils/translation"; import { t } from "@utils/translation";
import { ButtonProps } from "@vencord/discord-types"; import { ButtonProps } from "@vencord/discord-types";
import { Button, showToast } from "@webpack/common"; import { showToast } from "@webpack/common";
export function DonateButton({ export function DonateButton({
look = Button.Looks.LINK, equicord = false,
color = Button.Colors.TRANSPARENT,
link,
...props ...props
}: Partial<ButtonProps> & { link: string; }) { }: Partial<ButtonProps> & { equicord?: boolean; }) {
const link = equicord ? "https://github.com/sponsors/thororen1234" : "https://github.com/sponsors/Vendicated";
return ( return (
<Button <Button
{...props} {...props}
look={look} variant="none"
color={color} size="medium"
type="button"
onClick={() => VencordNative.native.openExternal(link)} onClick={() => VencordNative.native.openExternal(link)}
className="vc-donate-button" className="vc-donate-button"
> >
@ -43,15 +44,14 @@ export function DonateButton({
} }
export function InviteButton({ export function InviteButton({
look = Button.Looks.LINK,
color = Button.Colors.TRANSPARENT,
...props ...props
}: Partial<ButtonProps>) { }: Partial<ButtonProps>) {
return ( return (
<Button <Button
{...props} {...props}
look={look} variant="none"
color={color} size="medium"
type="button"
onClick={async e => { onClick={async e => {
e.preventDefault(); e.preventDefault();
openInviteModal("equicord-1173279886065029291").catch(() => openInviteModal("equicord-1173279886065029291").catch(() =>

View file

@ -1,5 +1,6 @@
.vc-donate-button { .vc-donate-button {
overflow: visible !important; overflow: visible !important;
color: var(--text-default)
} }
.vc-donate-button .vc-heart-icon { .vc-donate-button .vc-heart-icon {

View file

@ -6,6 +6,7 @@
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { Button } from "@components/Button";
import { Card } from "@components/Card"; import { Card } from "@components/Card";
import { Heading } from "@components/Heading"; import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
@ -14,7 +15,7 @@ import { ChangeList } from "@utils/ChangeList";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { useForceUpdater } from "@utils/react"; import { useForceUpdater } from "@utils/react";
import { t } from "@utils/translation"; import { t } from "@utils/translation";
import { Button, React, Tooltip } from "@webpack/common"; import { React, Tooltip } from "@webpack/common";
import Plugins from "~plugins"; import Plugins from "~plugins";
@ -166,8 +167,8 @@ export function NewPluginsSection({
{tooltipProps => ( {tooltipProps => (
<Button <Button
{...tooltipProps} {...tooltipProps}
color={Button.Colors.LINK} variant="link"
size={Button.Sizes.SMALL} size="small"
onClick={() => location.reload()} onClick={() => location.reload()}
className={Margins.top16} className={Margins.top16}
> >

View file

@ -6,6 +6,7 @@
import "./styles.css"; import "./styles.css";
import { Button } from "@components/Button";
import { Card } from "@components/Card"; import { Card } from "@components/Card";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
@ -20,7 +21,7 @@ import { Margins } from "@utils/margins";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { t } from "@utils/translation"; import { t } from "@utils/translation";
import { getRepo, UpdateLogger } from "@utils/updater"; import { getRepo, UpdateLogger } from "@utils/updater";
import { Alerts, Button, React, Toasts } from "@webpack/common"; import { Alerts, React, Toasts } from "@webpack/common";
import { import {
ChangelogEntry, ChangelogEntry,
@ -116,9 +117,8 @@ function UpdateLogCard({
} }
</span> </span>
<Button <Button
size={Button.Sizes.NONE} size="min"
color={Button.Colors.TRANSPARENT} variant="secondary"
look={Button.Looks.FILLED}
className="vc-changelog-delete-button" className="vc-changelog-delete-button"
style={{ style={{
padding: "4px", padding: "4px",
@ -500,14 +500,10 @@ function ChangelogContent() {
<div className="vc-changelog-controls"> <div className="vc-changelog-controls">
<Button <Button
size={Button.Sizes.SMALL} size="small"
disabled={isLoading || repoPending || !!repoErr} disabled={isLoading || repoPending || !!repoErr}
onClick={fetchChangelog} onClick={fetchChangelog}
color={ variant={recentlyChecked ? "positive" : "primary"}
recentlyChecked
? Button.Colors.GREEN
: Button.Colors.BRAND
}
> >
{isLoading {isLoading
? t("vencord.loading") ? t("vencord.loading")
@ -520,20 +516,16 @@ function ChangelogContent() {
{changelogHistory.length > 0 && ( {changelogHistory.length > 0 && (
<> <>
<Button <Button
size={Button.Sizes.SMALL} size="small"
color={ variant={showHistory ? "primary" : "secondary"}
showHistory
? Button.Colors.PRIMARY
: Button.Colors.BRAND
}
onClick={() => setShowHistory(!showHistory)} onClick={() => setShowHistory(!showHistory)}
style={{ marginLeft: "8px" }} style={{ marginLeft: "8px" }}
> >
{showHistory ? t("vencord.logs.hideLogs") : t("vencord.logs.showLogs")} {showHistory ? t("vencord.logs.hideLogs") : t("vencord.logs.showLogs")}
</Button> </Button>
<Button <Button
size={Button.Sizes.SMALL} size="small"
color={Button.Colors.RED} variant="dangerPrimary"
onClick={() => { onClick={() => {
Alerts.show({ Alerts.show({
title: t("vencord.logs.clearAllLogs"), title: t("vencord.logs.clearAllLogs"),

View file

@ -22,6 +22,7 @@ import { generateId } from "@api/Commands";
import { Settings, useSettings } from "@api/Settings"; import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText"; import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
@ -35,7 +36,7 @@ import { t } from "@utils/translation";
import { OptionType, Plugin } from "@utils/types"; import { OptionType, Plugin } from "@utils/types";
import { User } from "@vencord/discord-types"; import { User } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Button, Clickable, FluxDispatcher, React, Toasts, Tooltip, useEffect, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common"; import { Clickable, FluxDispatcher, React, Toasts, Tooltip, useEffect, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common";
import { Constructor } from "type-fest"; import { Constructor } from "type-fest";
import { PluginMeta } from "~plugins"; import { PluginMeta } from "~plugins";
@ -230,8 +231,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
{({ onMouseEnter, onMouseLeave }) => ( {({ onMouseEnter, onMouseLeave }) => (
<Button <Button
className={cl("disable-warning")} className={cl("disable-warning")}
size={Button.Sizes.SMALL} size="small"
color={Button.Colors.BRAND} variant="primary"
onClick={handleResetClick} onClick={handleResetClick}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
@ -352,16 +353,15 @@ export function openWarningModal(plugin?: Plugin | null, pluginModalProps?: Moda
<Flex className="vc-button-container"> <Flex className="vc-button-container">
<Flex className="button-group"> <Flex className="button-group">
<Button <Button
size={Button.Sizes.SMALL} size="small"
color={Button.Colors.PRIMARY} variant="secondary"
onClick={warningModalProps.onClose} onClick={warningModalProps.onClose}
look={Button.Looks.FILLED}
> >
{t("vencord.cancel")} {t("vencord.cancel")}
</Button> </Button>
{!Settings.ignoreResetWarning && ( {!Settings.ignoreResetWarning && (
<Button <Button
size={Button.Sizes.SMALL} size="small"
className={cl("disable-warning")} className={cl("disable-warning")}
onClick={() => { onClick={() => {
Settings.ignoreResetWarning = true; Settings.ignoreResetWarning = true;
@ -371,7 +371,7 @@ export function openWarningModal(plugin?: Plugin | null, pluginModalProps?: Moda
</Button> </Button>
)} )}
<Button <Button
size={Button.Sizes.SMALL} size="small"
onClick={() => { onClick={() => {
if (isPlugin) { if (isPlugin) {
if (plugin && pluginModalProps) if (plugin && pluginModalProps)

View file

@ -17,66 +17,65 @@
*/ */
import { downloadSettingsBackup, uploadSettingsBackup } from "@api/SettingsSync/offline"; import { downloadSettingsBackup, uploadSettingsBackup } from "@api/SettingsSync/offline";
import { Card } from "@components/Card"; import { Alert } from "@components/Alert";
import { Button } from "@components/Button";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Heading } from "@components/Heading"; import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { Button, Text } from "@webpack/common";
function BackupAndRestoreTab() { function BackupAndRestoreTab() {
return ( return (
<SettingsTab title="Backup & Restore"> <SettingsTab title="Backup & Restore">
<Flex flexDirection="column" gap="0.5em"> <Flex flexDirection="column" gap="0.5em">
<Card variant="warning"> <Alert.Warning>
<Heading tag="h4">Warning</Heading> Importing a settings file will overwrite your current settings.
<Paragraph>Importing a settings file will overwrite your current settings.</Paragraph> </Alert.Warning>
</Card>
<Text variant="text-md/normal" className={Margins.bottom8}> <Paragraph className={Margins.bottom8}>
You can import and export your Vencord settings as a JSON file. You can import and export your Vencord settings as a JSON file.
This allows you to easily transfer your settings to another device, This allows you to easily transfer your settings to another device,
or recover your settings after reinstalling Vencord or Discord. or recover your settings after reinstalling Vencord or Discord.
</Text> </Paragraph>
<Heading tag="h4">Settings Export contains:</Heading> <Heading tag="h4">Settings Export contains:</Heading>
<Text variant="text-md/normal" className={Margins.bottom8}> <Paragraph className={Margins.bottom8}>
<ul> <ul>
<li>&mdash; Custom QuickCSS</li> <li>&mdash; Custom QuickCSS</li>
<li>&mdash; Theme Links</li> <li>&mdash; Theme Links</li>
<li>&mdash; Plugin Settings</li> <li>&mdash; Plugin Settings</li>
</ul> </ul>
</Text> </Paragraph>
<Flex> <Flex>
<Button <Button
onClick={() => uploadSettingsBackup()} onClick={() => uploadSettingsBackup()}
size={Button.Sizes.SMALL} size="small"
> >
Import Settings Import Settings
</Button> </Button>
<Button <Button
onClick={() => downloadSettingsBackup("settings")} onClick={() => downloadSettingsBackup("settings")}
size={Button.Sizes.SMALL} size="small"
> >
Export Settings Export Settings
</Button> </Button>
<Button <Button
onClick={() => downloadSettingsBackup("plugins")} onClick={() => downloadSettingsBackup("plugins")}
size={Button.Sizes.SMALL} size="small"
> >
Export Plugins Export Plugins
</Button> </Button>
<Button <Button
onClick={() => downloadSettingsBackup("css")} onClick={() => downloadSettingsBackup("css")}
size={Button.Sizes.SMALL} size="small"
> >
Export CSS Export CSS
</Button> </Button>
<Button <Button
onClick={() => downloadSettingsBackup("datastore")} onClick={() => downloadSettingsBackup("datastore")}
size={Button.Sizes.SMALL} size="small"
> >
Export DataStores Export DataStores
</Button> </Button>

View file

@ -19,6 +19,7 @@
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { authorizeCloud, deauthorizeCloud } from "@api/SettingsSync/cloudSetup"; import { authorizeCloud, deauthorizeCloud } from "@api/SettingsSync/cloudSetup";
import { deleteCloudSettings, eraseAllCloudData, getCloudSettings, putCloudSettings } from "@api/SettingsSync/cloudSync"; import { deleteCloudSettings, eraseAllCloudData, getCloudSettings, putCloudSettings } from "@api/SettingsSync/cloudSync";
import { Button } from "@components/Button";
import { CheckedTextInput } from "@components/CheckedTextInput"; import { CheckedTextInput } from "@components/CheckedTextInput";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import { FormSwitch } from "@components/FormSwitch"; import { FormSwitch } from "@components/FormSwitch";
@ -28,7 +29,7 @@ import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { Alerts, Button, Tooltip, useState } from "@webpack/common"; import { Alerts, Tooltip, useState } from "@webpack/common";
function validateUrl(url: string) { function validateUrl(url: string) {
try { try {
@ -60,7 +61,7 @@ function SettingsSyncSection() {
/> />
<div className="vc-cloud-settings-sync-grid"> <div className="vc-cloud-settings-sync-grid">
<Button <Button
size={Button.Sizes.SMALL} size="small"
disabled={!sectionEnabled} disabled={!sectionEnabled}
onClick={() => putCloudSettings(true)} onClick={() => putCloudSettings(true)}
> >
@ -71,8 +72,8 @@ function SettingsSyncSection() {
<Button <Button
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
size={Button.Sizes.SMALL} size="small"
color={Button.Colors.RED} variant="dangerPrimary"
disabled={!sectionEnabled} disabled={!sectionEnabled}
onClick={() => getCloudSettings(true, true)} onClick={() => getCloudSettings(true, true)}
> >
@ -81,8 +82,8 @@ function SettingsSyncSection() {
)} )}
</Tooltip> </Tooltip>
<Button <Button
size={Button.Sizes.SMALL} size="small"
color={Button.Colors.RED} variant="dangerPrimary"
disabled={!sectionEnabled} disabled={!sectionEnabled}
onClick={() => deleteCloudSettings()} onClick={() => deleteCloudSettings()}
> >
@ -152,7 +153,7 @@ function CloudTab() {
<Grid columns={2} gap="1em" className={Margins.top8}> <Grid columns={2} gap="1em" className={Margins.top8}>
<Button <Button
size={Button.Sizes.MEDIUM} size="medium"
disabled={!settings.cloud.authenticated} disabled={!settings.cloud.authenticated}
onClick={async () => { onClick={async () => {
settings.cloud.authenticated = false; settings.cloud.authenticated = false;
@ -163,8 +164,8 @@ function CloudTab() {
Reauthorize Reauthorize
</Button> </Button>
<Button <Button
size={Button.Sizes.MEDIUM} size="medium"
color={Button.Colors.RED} variant="dangerPrimary"
disabled={!settings.cloud.authenticated} disabled={!settings.cloud.authenticated}
onClick={() => Alerts.show({ onClick={() => Alerts.show({
title: "Are you sure?", title: "Are you sure?",
@ -177,10 +178,10 @@ function CloudTab() {
> >
Erase All Data Erase All Data
</Button> </Button>
<Button size={Button.Sizes.MEDIUM} onClick={() => changeUrl("https://cloud.equicord.org/")}> <Button size="medium" onClick={() => changeUrl("https://cloud.equicord.org/")}>
Use Equicord's Cloud Use Equicord's Cloud
</Button> </Button>
<Button size={Button.Sizes.MEDIUM} onClick={() => changeUrl("https://api.vencord.dev/")}> <Button size="medium" onClick={() => changeUrl("https://api.vencord.dev/")}>
Use Vencord's Cloud Use Vencord's Cloud
</Button> </Button>
</Grid> </Grid>

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Button } from "@components/Button";
import { Card } from "@components/Card"; import { Card } from "@components/Card";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
@ -13,7 +14,7 @@ import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { changes, checkForUpdates, update, updateError } from "@utils/updater"; import { changes, checkForUpdates, update, updateError } from "@utils/updater";
import { Alerts, Button, React, Toasts, useState } from "@webpack/common"; import { Alerts, React, Toasts, useState } from "@webpack/common";
import { runWithDispatch } from "./runWithDispatch"; import { runWithDispatch } from "./runWithDispatch";
@ -95,7 +96,7 @@ export function Updatable(props: CommonProps) {
<Flex className={classes(Margins.bottom8, Margins.top8)}> <Flex className={classes(Margins.bottom8, Margins.top8)}>
{isOutdated && ( {isOutdated && (
<Button <Button
size={Button.Sizes.SMALL} size="small"
disabled={isUpdating || isChecking} disabled={isUpdating || isChecking}
onClick={runWithDispatch(setIsUpdating, async () => { onClick={runWithDispatch(setIsUpdating, async () => {
if (await update()) { if (await update()) {
@ -121,7 +122,7 @@ export function Updatable(props: CommonProps) {
</Button> </Button>
)} )}
<Button <Button
size={Button.Sizes.SMALL} size="small"
disabled={isUpdating || isChecking} disabled={isUpdating || isChecking}
onClick={runWithDispatch(setIsChecking, async () => { onClick={runWithDispatch(setIsChecking, async () => {
const outdated = await checkForUpdates(); const outdated = await checkForUpdates();

View file

@ -17,6 +17,7 @@
*/ */
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { Button } from "@components/Button";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
@ -31,7 +32,7 @@ import { relaunch } from "@utils/native";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { t } from "@utils/translation"; import { t } from "@utils/translation";
import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater"; import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater";
import { Alerts, Button, Parser, React, Toasts } from "@webpack/common"; import { Alerts, Parser, React, Toasts } from "@webpack/common";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
@ -92,7 +93,7 @@ function Updatable(props: CommonProps) {
<> <>
<Flex className={classes(Margins.bottom8, Margins.top8)}> <Flex className={classes(Margins.bottom8, Margins.top8)}>
{isOutdated && <Button {isOutdated && <Button
size={Button.Sizes.SMALL} size="small"
disabled={isUpdating || isChecking} disabled={isUpdating || isChecking}
onClick={withDispatcher(setIsUpdating, async () => { onClick={withDispatcher(setIsUpdating, async () => {
if (await update()) { if (await update()) {
@ -116,7 +117,7 @@ function Updatable(props: CommonProps) {
Update Now Update Now
</Button>} </Button>}
<Button <Button
size={Button.Sizes.SMALL} size="small"
disabled={isUpdating || isChecking} disabled={isUpdating || isChecking}
onClick={withDispatcher(setIsChecking, async () => { onClick={withDispatcher(setIsChecking, async () => {
const outdated = await checkForUpdates(); const outdated = await checkForUpdates();

View file

@ -7,7 +7,7 @@
import { DonateButton } from "@components/settings/DonateButton"; import { DonateButton } from "@components/settings/DonateButton";
import BadgeAPI from "@plugins/_api/badges"; import BadgeAPI from "@plugins/_api/badges";
import { DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants"; import { DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants";
import { Button, GuildMemberStore } from "@webpack/common"; import { GuildMemberStore } from "@webpack/common";
export const isDonor = (userId: string) => !!( export const isDonor = (userId: string) => !!(
BadgeAPI.getDonorBadges(userId)?.length > 0 BadgeAPI.getDonorBadges(userId)?.length > 0
@ -17,8 +17,6 @@ export const isDonor = (userId: string) => !!(
export function DonateButtonComponent() { export function DonateButtonComponent() {
return ( return (
<DonateButton <DonateButton
look={Button.Looks.FILLED}
color={Button.Colors.WHITE}
style={{ marginTop: "1em" }} style={{ marginTop: "1em" }}
/> />
); );

View file

@ -7,6 +7,7 @@
import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { BaseText } from "@components/BaseText"; import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { FormSwitch } from "@components/FormSwitch"; import { FormSwitch } from "@components/FormSwitch";
@ -16,7 +17,7 @@ import { Margins } from "@utils/margins";
import { identity } from "@utils/misc"; import { identity } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { t } from "@utils/translation"; import { t } from "@utils/translation";
import { Button, Select, Slider } from "@webpack/common"; import { Select, Slider } from "@webpack/common";
export function NotificationSection() { export function NotificationSection() {
return ( return (

View file

@ -47,3 +47,8 @@
text-align: center; text-align: center;
margin-top: 10px; margin-top: 10px;
} }
.vc-vencord-tab-settings-link {
color: var(--text-link);
display: inline-block;
}

View file

@ -8,6 +8,9 @@ import "./VencordTab.css";
import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Alert } from "@components/Alert";
import { Button } from "@components/Button";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { FormSwitch } from "@components/FormSwitch"; import { FormSwitch } from "@components/FormSwitch";
@ -18,12 +21,12 @@ import { DonateButton, InviteButton, openContributorModal, openPluginModal, Sett
import { QuickAction, QuickActionCard } from "@components/settings/QuickAction"; import { QuickAction, QuickActionCard } from "@components/settings/QuickAction";
import { SpecialCard } from "@components/settings/SpecialCard"; import { SpecialCard } from "@components/settings/SpecialCard";
import { gitRemote } from "@shared/vencordUserAgent"; import { gitRemote } from "@shared/vencordUserAgent";
import { DONOR_ROLE_ID, GUILD_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants"; import { DONOR_ROLE_ID, GUILD_ID, IS_MAC, IS_WINDOWS, VC_DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { identity, isAnyPluginDev } from "@utils/misc"; import { identity, isAnyPluginDev } from "@utils/misc";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { t, Translate } from "@utils/translation"; import { t, Translate } from "@utils/translation";
import { Button, GuildMemberStore, React, Select, UserStore } from "@webpack/common"; import { GuildMemberStore, React, Select, UserStore } from "@webpack/common";
import BadgeAPI from "plugins/_api/badges"; import BadgeAPI from "plugins/_api/badges";
import { openNotificationSettingsModal } from "./NotificationSettings"; import { openNotificationSettingsModal } from "./NotificationSettings";
@ -37,6 +40,8 @@ const COZY_CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/102653307095587233
const DONOR_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070116305436712.png?size=2048"; const DONOR_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070116305436712.png?size=2048";
const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070166481895484.png?size=2048"; const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070166481895484.png?size=2048";
const cl = classNameFactory("vc-vencord-tab-");
type KeysOfType<Object, Type> = { type KeysOfType<Object, Type> = {
[K in keyof Object]: Object[K] extends Type ? K : never; [K in keyof Object]: Object[K] extends Type ? K : never;
}[keyof Object]; }[keyof Object];
@ -49,9 +54,7 @@ function EquicordSettings() {
[], [],
); );
const isWindows = navigator.platform.toLowerCase().startsWith("win"); const needsVibrancySettings = IS_DISCORD_DESKTOP && IS_MAC;
const isMac = navigator.platform.toLowerCase().startsWith("mac");
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
const user = UserStore?.getCurrentUser(); const user = UserStore?.getCurrentUser();
@ -77,7 +80,7 @@ function EquicordSettings() {
warning: { enabled: false }, warning: { enabled: false },
}, },
!IS_WEB && !IS_WEB &&
(!IS_DISCORD_DESKTOP || !isWindows (!IS_DISCORD_DESKTOP || !IS_WINDOWS
? { ? {
key: "frameless", key: "frameless",
title: t("vencord.settings.frameless.title"), title: t("vencord.settings.frameless.title"),
@ -96,7 +99,7 @@ function EquicordSettings() {
description: t("vencord.settings.transparent.description"), description: t("vencord.settings.transparent.description"),
restartRequired: true, restartRequired: true,
warning: { warning: {
enabled: isWindows, enabled: IS_WINDOWS,
message: "Enabling this will prevent you from snapping this window.", message: "Enabling this will prevent you from snapping this window.",
}, },
}, },
@ -107,7 +110,7 @@ function EquicordSettings() {
warning: { enabled: false }, warning: { enabled: false },
}, },
!IS_WEB && !IS_WEB &&
isWindows && { IS_WINDOWS && {
key: "winCtrlQ", key: "winCtrlQ",
title: t("vencord.settings.winCtrlQ.title"), title: t("vencord.settings.winCtrlQ.title"),
restartRequired: true, restartRequired: true,
@ -206,8 +209,9 @@ function EquicordSettings() {
<Translate i18nKey="vencord.settings.hint"> <Translate i18nKey="vencord.settings.hint">
Hint: You can change the position of this settings section in the Hint: You can change the position of this settings section in the
<Button <Button
look={Button.Looks.LINK} variant="none"
style={{ color: "var(--text-link)", display: "inline-block" }} size="small"
className={cl("settings-link")}
onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)} onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}
> >
settings of the Settings plugin settings of the Settings plugin
@ -228,9 +232,9 @@ function EquicordSettings() {
s.warning.enabled ? ( s.warning.enabled ? (
<> <>
{s.description} {s.description}
<div className="form-switch-warning"> <Alert.Warning className={Margins.top8} style={{ width: "100%" }}>
{s.warning.message} {s.warning.message}
</div> </Alert.Warning>
</> </>
) : ( ) : (
s.description s.description
@ -332,16 +336,10 @@ function DonateButtonComponent() {
return ( return (
<Flex> <Flex>
<DonateButton <DonateButton
look={Button.Looks.FILLED} equicord={true}
color={Button.Colors.TRANSPARENT} style={{ marginTop: "1em" }} />
style={{ marginTop: "1em" }}
link="https://github.com/sponsors/thororen1234"
/>
<InviteButton <InviteButton
look={Button.Looks.FILLED} style={{ marginTop: "1em" }} />
color={Button.Colors.TRANSPARENT}
style={{ marginTop: "1em" }}
/>
</Flex> </Flex>
); );
} }

View file

@ -1,11 +0,0 @@
.plugin-warning {
font-size: 16px !important;
background-color: var(--info-warning-background) !important;
color: var(--info-warning-foreground) !important;
border: 1px solid var(--info-warning-foreground) !important;
border-radius: 5px !important;
font-weight: bold;
padding: 6px 10px;
text-align: center;
margin-top: 10px;
}

View file

@ -17,13 +17,13 @@ export default definePlugin({
{ {
find: '"view-profile"', find: '"view-profile"',
replacement: { replacement: {
match: /(user:(\i).*?"PRESS_VIEW_PROFILE".{0,200})return/, match: /(user:(\i).*?"PRESS_VIEW_PROFILE".{0,200}return)/,
replace: "$1return $self.openUserProfile($2);" replace: "$1 $self.openUserModal($2);"
}, },
all: true all: true
}, },
], ],
openUserProfile(user: User) { openUserModal(user: User) {
openUserProfile(user.id); openUserProfile(user.id);
} }
}); });

View file

@ -110,7 +110,6 @@ export default definePlugin({
name: "DecodeBase64", name: "DecodeBase64",
description: "Decode base64 content of any message and copy the decoded content.", description: "Decode base64 content of any message and copy the decoded content.",
authors: [EquicordDevs.ThePirateStoner], authors: [EquicordDevs.ThePirateStoner],
dependencies: ["MessagePopoverAPI"],
settings, settings,
messagePopoverButton: { messagePopoverButton: {
icon: DecodeIcon, icon: DecodeIcon,

View file

@ -163,8 +163,8 @@ export function Boo({ channel }: { channel: Channel; }) {
return; return;
} }
// if exempted, remove from ghost tracking // if exempted or bot (if setting enabled), remove from ghost tracking
if (isExempted) { if (isExempted || (settings.store.ignoreBots && lastMessage.author.bot)) {
if (countedChannels.has(id)) { if (countedChannels.has(id)) {
countedChannels.delete(id); countedChannels.delete(id);
setBooCount(getBooCount() - 1); setBooCount(getBooCount() - 1);
@ -186,7 +186,7 @@ export function Boo({ channel }: { channel: Channel; }) {
} }
}, [state.isCurrentUser, state.isDataProcessed, id, lastMessage?.id]); }, [state.isCurrentUser, state.isDataProcessed, id, lastMessage?.id]);
if (!state.isDataProcessed || !currentUserId || !lastMessage || state.isCurrentUser || isChannelExempted(id) || isCleared) if (!state.isDataProcessed || !currentUserId || !lastMessage || state.isCurrentUser || isChannelExempted(id) || isCleared || (settings.store.ignoreBots && lastMessage.author.bot))
return null; return null;
if (!settings.store.showDmIcons) return null; if (!settings.store.showDmIcons) return null;

View file

@ -84,6 +84,12 @@ export const settings = definePluginSettings({
description: "Something might happen if you ignore someone for too long...", description: "Something might happen if you ignore someone for too long...",
default: false, default: false,
restartNeeded: false restartNeeded: false
},
ignoreBots: {
type: OptionType.BOOLEAN,
description: "Ignore DMs from bots",
default: true,
restartNeeded: false
} }
}); });

View file

@ -6,7 +6,7 @@
import "./styles.css"; import "./styles.css";
import { addAudioProcessor, AudioProcessor, PreprocessAudioData, removeAudioProcessor } from "@api/AudioPlayer"; import { AudioProcessor, PreprocessAudioData } from "@api/AudioPlayer";
import { get as getFromDataStore } from "@api/DataStore"; import { get as getFromDataStore } from "@api/DataStore";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
@ -394,15 +394,12 @@ export default definePlugin({
name: "CustomSounds", name: "CustomSounds",
description: "Customize Discord's sounds.", description: "Customize Discord's sounds.",
authors: [Devs.ScattrdBlade, Devs.TheKodeToad], authors: [Devs.ScattrdBlade, Devs.TheKodeToad],
dependencies: ["AudioPlayerAPI"],
settings, settings,
getCustomSoundURL,
startAt: StartAt.Init, startAt: StartAt.Init,
audioProcessor: getCustomSoundURL,
async start() { async start() {
console.log("[CustomSounds] Plugin starting..."); console.log("[CustomSounds] Plugin starting...");
addAudioProcessor("CustomSounds", this.getCustomSoundURL);
try { try {
await preloadDataURIs(); await preloadDataURIs();
@ -413,7 +410,6 @@ export default definePlugin({
}, },
stop() { stop() {
removeAudioProcessor("CustomSounds");
console.log("[CustomSounds] Plugin stopped"); console.log("[CustomSounds] Plugin stopped");
} }
}); });

File diff suppressed because one or more lines are too long

View file

@ -1,71 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { Paragraph } from "@components/Paragraph";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { Alerts, ChannelStore, PermissionsBits, PermissionStore } from "@webpack/common";
import filterList from "./constants";
function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function warningEmbedNotice(trigger) {
return new Promise<boolean>(resolve => {
Alerts.show({
title: "Hold on!",
body: <div>
<Paragraph>
Your message contains a term on the automod preset list. (Term "{trigger}")
</Paragraph>
<Paragraph>
There is a high chance your message will be blocked and potentially moderated by a server moderator.
</Paragraph>
</div>,
confirmText: "Send Anyway",
cancelText: t("vencord.cancel"),
onConfirm: () => resolve(true),
onCloseCallback: () => setImmediate(() => resolve(false)),
});
});
}
const handleMessage = async (channelId, messageObj) => {
const channel = ChannelStore.getChannel(channelId);
if (channel.isDM()) return { cancel: false };
if (PermissionStore.can(PermissionsBits.ADMINISTRATOR, channel) || PermissionStore.can(PermissionsBits.MANAGE_GUILD, channel)) return { cancel: false };
const escapedStrings = filterList.map(escapeRegex);
const regexString = escapedStrings.join("|");
const regex = new RegExp(`(${regexString})`, "i");
const matches = regex.exec(messageObj.content);
console.log(matches);
if (matches) {
if (!await warningEmbedNotice(matches[0])) {
return { cancel: true };
}
}
return { cancel: false };
};
export default definePlugin({
name: "DontFilterMe",
description: "Warns you if your message contains a term in the automod preset list",
authors: [Devs.Samwich],
dependencies: ["MessageEventsAPI"],
start() {
addMessagePreSendListener(handleMessage);
},
stop() {
removeMessagePreSendListener(handleMessage);
}
});

View file

@ -4,8 +4,6 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import "@equicordplugins/_misc/styles.css";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs, EquicordDevs } from "@utils/constants"; import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -14,12 +12,6 @@ import { PluginButtons } from "./pluginButtons";
import { PluginCards } from "./pluginCards"; import { PluginCards } from "./pluginCards";
const settings = definePluginSettings({ const settings = definePluginSettings({
disableCreateDMButton: {
type: OptionType.BOOLEAN,
description: "Disables the create dm button",
restartNeeded: true,
default: false,
},
disableDMContextMenu: { disableDMContextMenu: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Disables the DM list context menu in favor of the x button", description: "Disables the DM list context menu in favor of the x button",
@ -59,15 +51,6 @@ export default definePlugin({
} }
] ]
}, },
// Disable Giant Create DM Button
{
find: ".createDMButtonContainer,",
predicate: () => settings.store.disableCreateDMButton,
replacement: {
match: /"create-dm"\)/,
replace: "$&&&false"
},
},
// Remove DM Context Menu // Remove DM Context Menu
{ {
find: "#{intl::DM_OPTIONS}", find: "#{intl::DM_OPTIONS}",

View file

@ -18,13 +18,12 @@
import "./styles.css"; import "./styles.css";
import { addHeaderBarButton, removeHeaderBarButton } from "@api/HeaderBar";
import { definePluginSettings, migratePluginSettings } from "@api/Settings"; import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import { Popout, useRef, useState } from "@webpack/common"; import { Popout, useRef, useState } from "@webpack/common";
import type { PropsWithChildren } from "react";
import { renderPopout } from "./menu"; import { renderPopout } from "./menu";
@ -48,7 +47,7 @@ function Icon() {
); );
} }
function VencordPopoutButton({ buttonClass }: { buttonClass: string; }) { function VencordPopoutButton() {
const buttonRef = useRef(null); const buttonRef = useRef(null);
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
@ -66,10 +65,10 @@ function VencordPopoutButton({ buttonClass }: { buttonClass: string; }) {
{(_, { isShown }) => ( {(_, { isShown }) => (
<HeaderBarIcon <HeaderBarIcon
ref={buttonRef} ref={buttonRef}
className={`vc-toolbox-btn ${buttonClass}`} className="vc-toolbox-btn"
onClick={() => setShow(v => !v)} onClick={() => setShow(v => !v)}
tooltip={isShown ? null : "Equicord Toolbox"} tooltip={isShown ? null : "Equicord Toolbox"}
icon={() => <Icon />} icon={Icon}
selected={isShown} selected={isShown}
/> />
)} )}
@ -82,27 +81,15 @@ export default definePlugin({
name: "EquicordToolbox", name: "EquicordToolbox",
description: "Adds a button next to the inbox button in the channel header that houses Equicord quick actions", description: "Adds a button next to the inbox button in the channel header that houses Equicord quick actions",
authors: [Devs.Ven, Devs.AutumnVN], authors: [Devs.Ven, Devs.AutumnVN],
dependencies: ["HeaderBarAPI"],
settings, settings,
patches: [ start() {
{ addHeaderBarButton("EquicordToolbox", VencordPopoutButton, 1000);
find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
match: /(?<=trailing:.{0,50})\i\.Fragment,\{(?=.+?className:(\i))/,
replace: "$self.TrailingWrapper,{className:$1,"
}
}
],
TrailingWrapper({ children, className }: PropsWithChildren<{ className: string; }>) {
return (
<>
{children}
<ErrorBoundary noop>
<VencordPopoutButton buttonClass={className} />
</ErrorBoundary>
</>
);
}, },
stop() {
removeHeaderBarButton("EquicordToolbox");
}
}); });

View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { removeMessagePopoverButton } from "@api/MessagePopover";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -154,7 +153,6 @@ export default definePlugin({
enableStyle(styles); enableStyle(styles);
}, },
stop() { stop() {
removeMessagePopoverButton("vc-findreply");
root && root.unmount(); root && root.unmount();
element?.remove(); element?.remove();
disableStyle(styles); disableStyle(styles);

View file

@ -4,11 +4,9 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import "@equicordplugins/_misc/styles.css";
import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Paragraph } from "@components/Paragraph"; import { Alert } from "@components/Alert";
import { EquicordDevs } from "@utils/constants"; import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Channel, User, VoiceState } from "@vencord/discord-types"; import { Channel, User, VoiceState } from "@vencord/discord-types";
@ -79,11 +77,11 @@ export default definePlugin({
description: "Follow a friend in voice chat.", description: "Follow a friend in voice chat.",
authors: [EquicordDevs.TheArmagan], authors: [EquicordDevs.TheArmagan],
settings, settings,
settingsAboutComponent: () => <> settingsAboutComponent: () => (
<Paragraph className="plugin-warning"> <Alert.Info>
This Plugin is used to follow a Friend/Friends into voice chat(s). This Plugin is used to follow a Friend/Friends into voice chat(s).
</Paragraph> </Alert.Info>
</>, ),
flux: { flux: {
async VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) { async VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
if (!followedUserInfo) return; if (!followedUserInfo) return;

View file

@ -31,8 +31,8 @@ export default definePlugin({
{ {
find: "#{intl::MESSAGE_FORWARDING_NSFW_NOT_ALLOWED}", find: "#{intl::MESSAGE_FORWARDING_NSFW_NOT_ALLOWED}",
replacement: { replacement: {
match: /if\(.{0,50}\)(return.{0,25}#{intl::MESSAGE_FORWARDING_NSFW_NOT_ALLOWED})/, match: /(\{if\().{0,50}(\)return.{0,25}#{intl::MESSAGE_FORWARDING_NSFW_NOT_ALLOWED})/,
replace: "if(false)$1", replace: "$1false$2",
} }
}, },
{ {

View file

@ -1,79 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { React, Tooltip, UserStore } from "@webpack/common";
import { JSX } from "react";
import { openBadgeModal } from "./badgeModal";
import { settings } from "./settings";
import { BadgeCache } from "./types";
import { cl, fetchBadges } from "./utils";
interface BadgeModalItem {
name: string;
rawName: string;
}
export let badgeData;
export const BadgeComponent = ({ name, img }: { name: string, img: string; }) => {
return (
<Tooltip text={name} >
{(tooltipProps: any) => (
<img
{...tooltipProps}
src={img}
className={cl("badge")}
style={{
transform: name.includes("Replugged") ? "scale(0.9)" : null
}}
/>
)}
</Tooltip>
);
};
export const GlobalBadges = ({ userId }: { userId: string; }) => {
const [badges, setBadges] = React.useState<BadgeCache["badges"]>({});
React.useEffect(() => setBadges(fetchBadges(userId) ?? {}), [userId]);
if (!badges) return null;
const globalBadges: JSX.Element[] = [];
const badgeModal: BadgeModalItem[] = [];
Object.keys(badges).forEach(mod => {
if (!badges[mod] || !Array.isArray(badges[mod]) || badges[mod].length === 0) return;
badges[mod].forEach(badge => {
if (!badge || !badge.tooltip || !badge.badge) return;
const prefix = settings.store.showPrefix ? `(${mod})` : "";
const suffix = settings.store.showSuffix ? `(${mod})` : "";
const displayName = `${prefix} ${badge.tooltip} ${suffix}`;
if (mod === "BadgeVault") {
badge.custom = true;
}
globalBadges.push(<BadgeComponent name={displayName} img={badge.badge} />);
badgeModal.push({
name: displayName,
rawName: badge.tooltip,
...badge
});
});
});
badgeData = badgeModal;
return (
<div
className={cl("badges")}
onClick={_ => openBadgeModal(UserStore.getUser(userId))}
>
{globalBadges}
</div>
);
};

View file

@ -13,18 +13,18 @@ import { ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@u
import { User } from "@vencord/discord-types"; import { User } from "@vencord/discord-types";
import { React, Tooltip } from "@webpack/common"; import { React, Tooltip } from "@webpack/common";
import { badgeData } from "./badgeComponent"; import { cl, GlobalBadges, INVITE_LINK } from "./utils";
import { cl, INVITE_LINK } from "./utils";
export const BadgeModalComponent = ({ name, img }: { name: string, img: string; }) => { export const BadgeModalComponent = ({ key, tooltip, badge }: { key: string, tooltip: string, badge: string; }) => {
return ( return (
<> <>
<Tooltip text={name} > <Tooltip text={tooltip} >
{(tooltipProps: any) => ( {(tooltipProps: any) => (
<img <img
className={cl("modal-badge")} className={cl("modal-badge")}
key={key}
{...tooltipProps} {...tooltipProps}
src={img} src={badge}
/> />
)} )}
</Tooltip> </Tooltip>
@ -33,8 +33,8 @@ export const BadgeModalComponent = ({ name, img }: { name: string, img: string;
}; };
export function openBadgeModal(user: User) { export function openBadgeModal(user: User) {
const badgeDataElements = badgeData.map(badge => ( const badgeDataElements = GlobalBadges[user.id].map(badge => (
<BadgeModalComponent key={badge.rawName} name={badge.name} img={badge.badge} /> <BadgeModalComponent key={badge.key} tooltip={badge.tooltip} badge={badge.badge} />
)); ));
openModal(modalprops => ( openModal(modalprops => (

View file

@ -6,23 +6,19 @@
import "./styles.css"; import "./styles.css";
import { addProfileBadge, BadgePosition, ProfileBadge, removeProfileBadge } from "@api/Badges"; import { BadgePosition, ProfileBadge } from "@api/Badges";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { BadgeContextMenu } from "@plugins/_api/badges";
import { Devs, EquicordDevs } from "@utils/constants"; import { Devs, EquicordDevs } from "@utils/constants";
import { openInviteModal } from "@utils/discord"; import { openInviteModal } from "@utils/discord";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { React } from "@webpack/common"; import { ContextMenuApi, React, Toasts, UserStore } from "@webpack/common";
import { GlobalBadges } from "./badgeComponent"; import { openBadgeModal } from "./badgeModal";
import { settings } from "./settings"; import { settings } from "./settings";
import { cl, fetchBadges, INVITE_LINK } from "./utils"; import { cl, GlobalBadges, INVITE_LINK, loadBadges } from "./utils";
const Badge: ProfileBadge = { let intervalId: any;
component: b => <GlobalBadges {...b} />,
position: BadgePosition.START,
shouldShow: userInfo => !!Object.keys(fetchBadges(userInfo.userId) ?? {}).length,
key: "GlobalBadges"
};
export default definePlugin({ export default definePlugin({
name: "GlobalBadges", name: "GlobalBadges",
@ -40,6 +36,44 @@ export default definePlugin({
</Button> </Button>
</> </>
), ),
start: () => addProfileBadge(Badge), async start() {
stop: () => removeProfileBadge(Badge), await loadBadges();
clearInterval(intervalId);
intervalId = setInterval(loadBadges, 1000 * 60 * 30);
},
async stop() {
clearInterval(intervalId);
},
toolboxActions: {
async "Refetch Global Badges"() {
await loadBadges();
Toasts.show({
id: Toasts.genId(),
message: "Successfully refetched global badges!",
type: Toasts.Type.SUCCESS
});
}
},
get GlobalBadges() {
return GlobalBadges;
},
getGlobalBadges(userId: string) {
return GlobalBadges[userId]?.map(badge => ({
iconSrc: badge.badge,
description: badge.tooltip,
position: BadgePosition.START,
props: {
style: {
borderRadius: "50%",
transform: "scale(0.9)"
}
},
onContextMenu(event, badge) {
ContextMenuApi.openContextMenu(event, () => <BadgeContextMenu badge={badge} />);
},
onClick() {
return openBadgeModal(UserStore.getUser(userId));
},
} satisfies ProfileBadge));
}
}); });

View file

@ -78,12 +78,6 @@ export const settings = definePluginSettings({
default: true, default: true,
restartNeeded: false restartNeeded: false
}, },
showReplugged: {
type: OptionType.BOOLEAN,
description: "Show Replugged Badges",
default: true,
restartNeeded: false
},
apiUrl: { apiUrl: {
type: OptionType.STRING, type: OptionType.STRING,
description: "API to use", description: "API to use",

View file

@ -1,15 +1,3 @@
.vc-global-badges-badges {
align-items: center;
display: flex;
flex-wrap: nowrap;
}
.vc-global-badges-badge {
width: 20px;
height: 20px;
gap: 1px;
}
.vc-global-badges-modal-name { .vc-global-badges-modal-name {
text-transform: none; text-transform: none;
flex-grow: 0; flex-grow: 0;

View file

@ -1,16 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export type CustomBadge = {
tooltip: string;
badge: string;
custom?: boolean;
};
export interface BadgeCache {
badges: { [mod: string]: CustomBadge[]; };
expires: number;
}

View file

@ -4,44 +4,67 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import "./styles.css";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { settings } from "./settings"; import { settings } from "./settings";
import { BadgeCache } from "./types";
const cache = new Map<string, BadgeCache>(); export let GlobalBadges = {};
const EXPIRES = 1000 * 60 * 15;
export const cl = classNameFactory("vc-global-badges-");
export const INVITE_LINK = "kwHCJPxp8t"; export const INVITE_LINK = "kwHCJPxp8t";
export const cl = classNameFactory("vc-global-badges-");
export const fetchBadges = (id: string): BadgeCache["badges"] | undefined => { export const serviceMap: Record<string, string> = {
const cachedValue = cache.get(id); nekocord: "Nekocord",
if (!cache.has(id) || (cachedValue && cachedValue.expires < Date.now())) { reviewdb: "ReviewDB",
const services: string[] = []; aero: "Aero",
if (settings.store.showNekocord) services.push("nekocord"); aliucord: "Aliucord",
if (settings.store.showReviewDB) services.push("reviewdb"); ra1ncord: "Ra1ncord",
if (settings.store.showAero) services.push("aero"); velocity: "Velocity",
if (settings.store.showAliucord) services.push("aliucord"); enmity: "Enmity",
if (settings.store.showRa1ncord) services.push("ra1ncord"); badgevault: "BadgeVault",
if (settings.store.showVelocity) services.push("velocity"); vencord: "Vencord",
if (settings.store.showEnmity) services.push("enmity"); equicord: "Equicord",
if (settings.store.showReplugged) services.push("replugged");
if (settings.store.showCustom) services.push("badgevault");
if (services.length === 0) {
cache.set(id, { badges: {}, expires: Date.now() + EXPIRES });
return {};
}
fetch(`${settings.store.apiUrl}${id}?seperated=true&capitalize=true&services=${services.join(",")}`)
.then(res => res.json() as Promise<{ status: number; badges: BadgeCache["badges"]; }>)
.then(body => {
cache.set(id, { badges: body.badges, expires: Date.now() + EXPIRES });
return body.badges;
})
.catch(() => null);
} else if (cachedValue) {
return cachedValue.badges;
}
}; };
export async function loadBadges() {
const url = settings.store.apiUrl.endsWith("/") ? settings.store.apiUrl + "users" : settings.store.apiUrl + "/users";
const globalBadges = await fetch(url, { cache: "no-cache" }).then(r => r.json());
const filteredUsers: Record<string, typeof globalBadges.users[string]> = {};
for (const key in globalBadges.users) {
filteredUsers[key] = globalBadges.users[key].filter(b => {
const { mod } = b;
if (!mod) return false;
const blockedMods = ["vencord", "equicord"];
if (blockedMods.includes(mod)) return false;
const conditionalMods = {
aero: settings.store.showAero,
velocity: settings.store.showVelocity,
badgevault: settings.store.showCustom,
nekocord: settings.store.showNekocord,
reviewdb: settings.store.showReviewDB,
aliucord: settings.store.showAliucord,
ra1ncord: settings.store.showRa1ncord,
enmity: settings.store.showEnmity
};
if (mod in conditionalMods && !conditionalMods[mod]) return false;
return true;
}).map(b => {
const modFormatted = serviceMap[b.mod];
const prefix = settings.store.showPrefix ? `${modFormatted} - ` : "";
const suffix = settings.store.showSuffix ? ` - ${modFormatted}` : "";
const tooltip = prefix + b.tooltip + suffix;
return {
...b,
key: b.tooltip,
tooltip
};
});
}
GlobalBadges = filteredUsers;
}

View file

@ -1,205 +0,0 @@
/*
* 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 { DataStore } from "@api/index";
import { definePluginSettings } from "@api/Settings";
import { Button } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
import { Heading } from "@components/Heading";
import { EquicordDevs } from "@utils/constants";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Message, User } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack";
import { Menu, React, TextInput, UserStore } from "@webpack/common";
const UserBoxIcon = findComponentByCodeLazy("0-3-3H5Zm10 6a3");
type NicknamesData = Record<string, string>;
let nicknames: NicknamesData = {};
const settings = definePluginSettings({
enableMessages: {
type: OptionType.BOOLEAN,
description: "Show global nicknames in chat messages.",
default: true,
restartNeeded: true
},
enableMemberList: {
type: OptionType.BOOLEAN,
description: "Show global nicknames in the member list.",
default: true,
restartNeeded: true
},
enableVoiceChannels: {
type: OptionType.BOOLEAN,
description: "Show global nicknames in voice channels.",
default: true,
restartNeeded: true
},
enableTypingIndicator: {
type: OptionType.BOOLEAN,
description: "Show global nicknames in the typing indicator.",
default: true,
restartNeeded: true
}
});
function NicknameModal({ modalProps, user }: { modalProps: ModalProps; user: User; }) {
const [value, setValue] = React.useState(nicknames[user.id] ?? "");
return (
<ModalRoot {...modalProps}>
<ModalHeader>
<Heading tag="h2" style={{ flexGrow: 1, margin: 0 }}>
Change Nickname
</Heading>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent>
<div style={{ marginBottom: 20 }}>
<Heading tag="h3" style={{ marginBottom: 8, fontSize: "14px", fontWeight: 600 }}>
Nickname
</Heading>
<TextInput
value={value}
onChange={setValue}
placeholder={user.globalName ?? user.username}
style={{ width: "100%" }}
/>
</div>
</ModalContent>
<ModalFooter>
<Button
variant="primary"
onClick={async () => {
const trimmed = value.trim();
if (trimmed) {
nicknames[user.id] = trimmed;
} else {
delete nicknames[user.id];
}
await DataStore.set("GlobalNicknames", nicknames);
modalProps.onClose();
}}
>
Save
</Button>
<Button
variant="secondary"
style={{ marginRight: "8px" }}
onClick={modalProps.onClose}
>
{t("vencord.cancel")}
</Button>
</ModalFooter>
</ModalRoot>
);
}
const userContextPatch: NavContextMenuPatchCallback = (children, { user }) => {
if (!user) return;
const currentUser = UserStore.getCurrentUser();
if (!currentUser || user.id === currentUser.id) return;
children.push(
<Menu.MenuSeparator />,
nicknames[user.id] ? (
<Menu.MenuItem
id="remove-nickname"
label="Remove Nickname"
color="danger"
icon={UserBoxIcon}
action={async () => {
delete nicknames[user.id];
await DataStore.set("GlobalNicknames", nicknames);
}}
/>
) : (
<Menu.MenuItem
id="change-nickname"
label="Change Nickname"
icon={UserBoxIcon}
action={() => openModal(props => (
<ErrorBoundary>
<NicknameModal modalProps={props} user={user} />
</ErrorBoundary>
))}
/>
)
);
};
export default definePlugin({
name: "GlobalNicknames",
description: "Set custom nicknames for any user globally.",
authors: [EquicordDevs.Prism],
dependencies: ["ContextMenuAPI"],
settings,
patches: [
{
find: '="SYSTEM_TAG"',
predicate: () => settings.store.enableMessages,
replacement: {
match: /children:(.+?),"data-text":/,
replace: 'children:$self.patchMessageName(arguments[0])??($1),"data-text":'
}
},
{
find: "let{colorRoleName:",
predicate: () => settings.store.enableMemberList,
replacement: {
match: /,name:(null!=\i\?\i:\i),colorStrings/,
replace: ",name:$self.patchName($1,arguments[0].user)||($1),colorStrings"
}
},
{
find: ",connectUserDragSource:",
predicate: () => settings.store.enableVoiceChannels,
replacement: {
match: /nick:(\i),collapsed/,
replace: "nick:$self.patchName($1,arguments[0].user)||$1,collapsed"
}
},
{
find: "#{intl::THREE_USERS_TYPING}",
predicate: () => settings.store.enableTypingIndicator,
replacement: {
match: /(\i\.\i\.getName\(\i\.guild_id,\i\.id),(\i)\)\)/,
replace: "$1,$self.patchTypingName($2)))"
}
}
],
async start() {
const data = await DataStore.get<NicknamesData>("GlobalNicknames");
nicknames = data ?? {};
},
patchMessageName(props: { message: Message; }): string | null {
const userId = props?.message?.author?.id;
return userId ? nicknames[userId] ?? null : null;
},
patchName(_: string, user: User): string | null {
return nicknames[user.id] ?? null;
},
patchTypingName(user: User): User {
const nickname = nicknames[user.id];
if (!nickname) return user;
return { ...user, globalName: nickname, username: nickname } as User;
},
contextMenus: {
"user-context": userContextPatch
}
});

View file

@ -16,8 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import "@equicordplugins/_misc/styles.css";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";

View file

@ -20,8 +20,6 @@ import "./style.css";
import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { DataStore } from "@api/index"; import { DataStore } from "@api/index";
import { removeMessagePopoverButton } from "@api/MessagePopover";
import ErrorBoundary from "@components/ErrorBoundary";
import { EquicordDevs } from "@utils/constants"; import { EquicordDevs } from "@utils/constants";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { openModal } from "@utils/modal"; import { openModal } from "@utils/modal";
@ -58,15 +56,13 @@ function ToolBarHeader() {
const iconClasses = findByProps("iconWrapper", "clickable"); const iconClasses = findByProps("iconWrapper", "clickable");
return ( return (
<ErrorBoundary noop={true}> <HeaderBarIcon
<HeaderBarIcon tooltip="Holy Notes"
tooltip="Holy Notes" position="bottom"
position="bottom" className={classes("vc-note-button", iconClasses.iconWrapper, iconClasses.clickable)}
className={classes("vc-note-button", iconClasses.iconWrapper, iconClasses.clickable)} icon={Popover}
icon={e => Popover(e)} onClick={() => openModal(props => <NoteModal {...props} />)}
onClick={() => openModal(props => <NoteModal {...props} />)} />
/>
</ErrorBoundary>
); );
} }
@ -75,17 +71,6 @@ export default definePlugin({
name: "HolyNotes", name: "HolyNotes",
description: "Holy Notes allows you to save messages", description: "Holy Notes allows you to save messages",
authors: [EquicordDevs.Wolfie], authors: [EquicordDevs.Wolfie],
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI"],
patches: [
{
find: ".controlButtonWrapper,",
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,100}mobileToolbar)/,
replace: "$1$self.toolbarAction(arguments[0]);$2"
}
}
],
toolboxActions: { toolboxActions: {
async "Open Notes"() { async "Open Notes"() {
@ -97,21 +82,8 @@ export default definePlugin({
"message": messageContextMenuPatch "message": messageContextMenuPatch
}, },
toolbarAction(e) { renderHeaderBarButton: ToolBarHeader,
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<ToolBarHeader />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"HolyNotes"}>
<ToolBarHeader />
</ErrorBoundary>,
e.toolbar,
];
},
messagePopoverButton: { messagePopoverButton: {
icon: NoteButtonPopover, icon: NoteButtonPopover,
render(message) { render(message) {
@ -129,8 +101,4 @@ export default definePlugin({
if (await DataStore.keys(HolyNoteStore).then(keys => !keys.includes("Main"))) return noteHandler.newNoteBook("Main"); if (await DataStore.keys(HolyNoteStore).then(keys => !keys.includes("Main"))) return noteHandler.newNoteBook("Main");
if (!noteHandlerCache.has("Main")) await DataStoreToCache(); if (!noteHandlerCache.has("Main")) await DataStoreToCache();
}, },
async stop() {
removeMessagePopoverButton("HolyNotes");
}
}); });

View file

@ -102,7 +102,6 @@ export default definePlugin({
name: "Husk", name: "Husk",
description: "Adds Husk button (check settings to change used emoji)", description: "Adds Husk button (check settings to change used emoji)",
authors: [Devs.nin0dev], authors: [Devs.nin0dev],
dependencies: ["MessagePopoverAPI"],
settings, settings,
messagePopoverButton: { messagePopoverButton: {
icon: Husk, icon: Husk,

View file

@ -6,7 +6,7 @@
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText"; import { BaseText } from "@components/BaseText";
import { Flex } from "@components/Flex"; import { HeadingPrimary } from "@components/Heading";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { openUserProfile } from "@utils/discord"; import { openUserProfile } from "@utils/discord";
import { Avatar, Clickable, React, TextInput, Tooltip, } from "@webpack/common"; import { Avatar, Clickable, React, TextInput, Tooltip, } from "@webpack/common";
@ -44,11 +44,9 @@ function UsersCollectionRows({ usersCollection }: { usersCollection: Data["users
.map(({ name, users }) => ( .map(({ name, users }) => (
<aside key={name}> <aside key={name}>
<div className={cl("header-container")}> <div className={cl("header-container")}>
<BaseText>{name.toUpperCase()}</BaseText> <HeadingPrimary className={cl("header-name")}>{name}</HeadingPrimary>
<div className={cl("header-btns")}> <div className={cl("header-btns")}>
<Flex> {users.map(u => <UserRow key={u.id} user={u} />)}
{users.map(u => <UserRow key={u.id} user={u} />)}
</Flex>
</div> </div>
</div> </div>
</aside> </aside>
@ -59,14 +57,14 @@ function UsersCollectionRows({ usersCollection }: { usersCollection: Data["users
function UserRow({ user, allowOwner = true }: { user: IStorageUser, allowOwner?: boolean; }) { function UserRow({ user, allowOwner = true }: { user: IStorageUser, allowOwner?: boolean; }) {
return ( return (
<Flex key={user.id} className={cl("user-row")}> <div key={user.id} className={cl("user-row")}>
<span className={cl("user")}> <div className={cl("user")}>
<Flex> <Clickable onClick={() => openUserProfile(user.id)}>
<Clickable onClick={() => openUserProfile(user.id)}> <span className={cl("user-avatar")}>
<span className={cl("user-avatar")}> <Avatar src={user.iconURL} size="SIZE_24" />
<Avatar src={user.iconURL} size="SIZE_24" /> </span>
</span> </Clickable>
</Clickable> <div className={cl("user-tooltip")}>
<Tooltip text={tooltipText(user)}> <Tooltip text={tooltipText(user)}>
{props => {props =>
<Paragraph {...props} className={cl("user-username")}> <Paragraph {...props} className={cl("user-username")}>
@ -74,15 +72,14 @@ function UserRow({ user, allowOwner = true }: { user: IStorageUser, allowOwner?:
</Paragraph> </Paragraph>
} }
</Tooltip> </Tooltip>
</Flex> <span className={cl("user-id")}>
</span> <Paragraph>
{user.id}
<span className={cl("user-id")}> </Paragraph>
<Paragraph> </span>
{user.id} </div>
</Paragraph> </div>
</span> </div>
</Flex>
); );
} }
@ -94,10 +91,10 @@ function SearchElement({ usersCollection }: { usersCollection: Data["usersCollec
<section className={cl("search")}> <section className={cl("search")}>
<TextInput placeholder="Filter by tag, username" name="Filter" onChange={setCurrent} /> <TextInput placeholder="Filter by tag, username" name="Filter" onChange={setCurrent} />
{current && ( {current && (
<Flex className={cl("search-user")}> <div className={cl("search-user")}>
{list.filter(user => user.tag.includes(current) || user.username.includes(current)) {list.filter(user => user.tag.includes(current) || user.username.includes(current))
.map(user => <UserRow key={user.id} user={user} allowOwner={false} />)} .map(user => <UserRow key={user.id} user={user} allowOwner={false} />)}
</Flex> </div>
)} )}
</section> </section>
); );
@ -112,9 +109,9 @@ export function DataUI({ plugin, usersCollection }: { plugin: any; usersCollecti
<SectionDescription /> <SectionDescription />
<SearchElement usersCollection={usersCollection} /> <SearchElement usersCollection={usersCollection} />
<Flex className={cl("rows")}> <div className={cl("rows")}>
<UsersCollectionRows usersCollection={usersCollection} /> <UsersCollectionRows usersCollection={usersCollection} />
</Flex> </div>
</main> </main>
); );
} }

View file

@ -5,8 +5,15 @@
flex-direction: column; flex-direction: column;
} }
.vc-i-remember-you-header-name {
margin-top: 16px;
}
.vc-i-remember-you-header-btns { .vc-i-remember-you-header-btns {
gap: 8px; display: grid;
grid-template-columns: repeat(2, minmax(0, 350px));
gap: 10px;
margin-top: 8px;
} }
.vc-i-remember-you-header { .vc-i-remember-you-header {
@ -29,49 +36,38 @@
.vc-i-remember-you-user-avatar { .vc-i-remember-you-user-avatar {
cursor: pointer; cursor: pointer;
margin-right: 30px; margin-right: 0;
} }
.vc-i-remember-you-user-row { .vc-i-remember-you-user-row {
margin: 0; display: flex;
width: 100%; flex-direction: column;
flex-wrap: wrap; gap: 6px;
align-items: center; padding: 4px;
align-items: flex-start;
} }
.vc-i-remember-you-user-id { .vc-i-remember-you-user-id {
height: min-content; align-self: flex-start;
opacity: 0.75; opacity: 0.75;
height: min-content;
} }
.vc-i-remember-you-user { .vc-i-remember-you-user {
width: 500px; display: flex;
gap: 250px; flex-direction: row;
align-items: center; align-items: center;
overflow-wrap: break-word; gap: 8px;
margin-top: 5px; width: 100%;
margin-top: 0;
} }
.vc-i-remember-you-user-username { .vc-i-remember-you-user-username {
margin-top: 2px; margin-top: 2px;
} }
.vc-i-remember-you-buttons { .vc-i-remember-you-user-tooltip {
margin-top: 10px; display: flex;
} flex-direction: column;
align-items: flex-start;
.vc-i-remember-you-buttons-save,
.vc-i-remember-you-buttons-cancel {
margin-left: 490px;
margin-top: 5px;
margin-bottom: 10px;
}
.vc-i-remember-you-buttons-cancel {
margin-left: 12px;
}
.vc-i-remember-you-buttons-reset {
cursor: pointer;
margin-left: 12px;
} }

View file

@ -79,24 +79,39 @@ const IngtoninatorButton: ChatBarButtonFactory = ({ isMainChat }) => {
onClick={toggle} onClick={toggle}
> >
{isEnabled ? ( {isEnabled ? (
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"> enabledIcon()
<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"> disabledIcon()
<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> </ChatBarButton>
); );
}; };
function enabledIcon() {
return (
<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>
);
}
function disabledIcon() {
return (
<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>
);
}
export default definePlugin({ export default definePlugin({
name: "Ingtoninator", name: "Ingtoninator",
description: "Suffixes 'ington' to a random word in your message", description: "Suffixes 'ington' to a random word in your message",
authors: [EquicordDevs.zyqunix], authors: [EquicordDevs.zyqunix],
settings, settings,
renderChatBarButton: IngtoninatorButton, chatBarButton: {
icon: disabledIcon,
render: IngtoninatorButton
},
start() { start() {
addMessagePreSendListener(handleMessage); addMessagePreSendListener(handleMessage);
}, },

View file

@ -65,7 +65,7 @@ function Indicator() {
} }
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => { const ChatBarRender: ChatBarButtonFactory = ({ isMainChat }) => {
if (!isMainChat) return null; if (!isMainChat) return null;
return ( return (
@ -76,20 +76,26 @@ const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
"aria-haspopup": "dialog", "aria-haspopup": "dialog",
}} }}
> >
<svg {ChatBarIcon()}
aria-hidden
role="img"
width="20"
height="20"
viewBox={"0 0 64 64"}
style={{ scale: "1.39", translate: "0 -1px" }}
>
<path fill="currentColor" d="M 32 9 C 24.832 9 19 14.832 19 22 L 19 27.347656 C 16.670659 28.171862 15 30.388126 15 33 L 15 49 C 15 52.314 17.686 55 21 55 L 43 55 C 46.314 55 49 52.314 49 49 L 49 33 C 49 30.388126 47.329341 28.171862 45 27.347656 L 45 22 C 45 14.832 39.168 9 32 9 z M 32 13 C 36.963 13 41 17.038 41 22 L 41 27 L 23 27 L 23 22 C 23 17.038 27.037 13 32 13 z" />
</svg>
</ChatBarButton> </ChatBarButton>
); );
}; };
function ChatBarIcon() {
return (
<svg
aria-hidden
role="img"
width="20"
height="20"
viewBox={"0 0 64 64"}
style={{ scale: "1.39", translate: "0 -1px" }}
>
<path fill="currentColor" d="M 32 9 C 24.832 9 19 14.832 19 22 L 19 27.347656 C 16.670659 28.171862 15 30.388126 15 33 L 15 49 C 15 52.314 17.686 55 21 55 L 43 55 C 46.314 55 49 52.314 49 49 L 49 33 C 49 30.388126 47.329341 28.171862 45 27.347656 L 45 22 C 45 14.832 39.168 9 32 9 z M 32 13 C 36.963 13 41 17.038 41 22 L 41 27 L 23 27 L 23 22 C 23 17.038 27.037 13 32 13 z" />
</svg>
);
}
const settings = definePluginSettings({ const settings = definePluginSettings({
savedPasswords: { savedPasswords: {
type: OptionType.STRING, type: OptionType.STRING,
@ -149,6 +155,11 @@ export default definePlugin({
reporterTestable: ReporterTestable.Patches, reporterTestable: ReporterTestable.Patches,
settings, settings,
chatBarButton: {
icon: ChatBarIcon,
render: ChatBarRender
},
patches: [ patches: [
{ {
// Indicator // Indicator
@ -165,7 +176,6 @@ export default definePlugin({
const { default: StegCloak } = await getStegCloak(); const { default: StegCloak } = await getStegCloak();
steggo = new StegCloak(true, false); steggo = new StegCloak(true, false);
}, },
renderChatBarButton: ChatBarIcon,
messagePopoverButton: { messagePopoverButton: {
icon: PopOverIcon, icon: PopOverIcon,
render(message) { render(message) {

View file

@ -72,14 +72,7 @@ const FetchTimeButton: ChatBarButtonFactory = ({ isMainChat }) => {
alignItems: "center", alignItems: "center",
gap: "4px" gap: "4px"
}}> }}>
<svg {FetchTimeIcon()}
width="16"
height="16"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z" />
</svg>
<span style={{ <span style={{
fontSize: "12px", fontSize: "12px",
color: iconColor, color: iconColor,
@ -92,6 +85,19 @@ const FetchTimeButton: ChatBarButtonFactory = ({ isMainChat }) => {
); );
}; };
function FetchTimeIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z" />
</svg>
);
}
function formatTimeAgo(timestamp: Date): string { function formatTimeAgo(timestamp: Date): string {
const now = new Date(); const now = new Date();
const diff = now.getTime() - timestamp.getTime(); const diff = now.getTime() - timestamp.getTime();
@ -145,6 +151,11 @@ export default definePlugin({
authors: [EquicordDevs.GroupXyz], authors: [EquicordDevs.GroupXyz],
settings, settings,
chatBarButton: {
icon: FetchTimeIcon,
render: FetchTimeButton
},
start() { start() {
FluxDispatcher.subscribe("CHANNEL_SELECT", handleChannelSelect); FluxDispatcher.subscribe("CHANNEL_SELECT", handleChannelSelect);
FluxDispatcher.subscribe("LOAD_MESSAGES_SUCCESS", handleMessageLoad); FluxDispatcher.subscribe("LOAD_MESSAGES_SUCCESS", handleMessageLoad);
@ -164,7 +175,5 @@ export default definePlugin({
currentFetch = null; currentFetch = null;
channelTimings.clear(); channelTimings.clear();
currentChannelId = null; currentChannelId = null;
}, }
renderChatBarButton: FetchTimeButton,
}); });

View file

@ -21,28 +21,7 @@ import { findComponentByCodeLazy } from "@webpack";
import { openLogModal } from "./LogsModal"; import { openLogModal } from "./LogsModal";
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"'); const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
const Icon = findComponentByCodeLazy("0-1.27-.97l-2.5.7a3");
export function OpenLogsIcon() {
return (
<svg
stroke="currentColor"
fill="none"
strokeWidth="0"
viewBox="0 0 15 15"
height={24}
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
fill="currentColor"
d="M3 2.5C3 2.22386 3.22386 2 3.5 2H9.08579C9.21839 2 9.34557 2.05268 9.43934 2.14645L11.8536 4.56066C11.9473 4.65443 12 4.78161 12 4.91421V12.5C12 12.7761 11.7761 13 11.5 13H3.5C3.22386 13 3 12.7761 3 12.5V2.5ZM3.5 1C2.67157 1 2 1.67157 2 2.5V12.5C2 13.3284 2.67157 14 3.5 14H11.5C12.3284 14 13 13.3284 13 12.5V4.91421C13 4.51639 12.842 4.13486 12.5607 3.85355L10.1464 1.43934C9.86514 1.15804 9.48361 1 9.08579 1H3.5ZM4.5 4C4.22386 4 4 4.22386 4 4.5C4 4.77614 4.22386 5 4.5 5H7.5C7.77614 5 8 4.77614 8 4.5C8 4.22386 7.77614 4 7.5 4H4.5ZM4.5 7C4.22386 7 4 7.22386 4 7.5C4 7.77614 4.22386 8 4.5 8H10.5C10.7761 8 11 7.77614 11 7.5C11 7.22386 10.7761 7 10.5 7H4.5ZM4.5 10C4.22386 10 4 10.2239 4 10.5C4 10.7761 4.22386 11 4.5 11H10.5C10.7761 11 11 10.7761 11 10.5C11 10.2239 10.7761 10 10.5 10H4.5Z"
>
</path>
</svg>
);
}
export function OpenLogsButton() { export function OpenLogsButton() {
return ( return (
@ -50,7 +29,7 @@ export function OpenLogsButton() {
className="vc-log-toolbox-btn" className="vc-log-toolbox-btn"
onClick={() => openLogModal()} onClick={() => openLogModal()}
tooltip={"Open Logs"} tooltip={"Open Logs"}
icon={OpenLogsIcon} icon={Icon}
/> />
); );
} }

View file

@ -8,12 +8,11 @@ export const Native = getNative();
import "./styles.css"; import "./styles.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, MessageStore, React, UserStore } from "@webpack/common"; import { FluxDispatcher, MessageStore, UserStore } from "@webpack/common";
import { OpenLogsButton } from "./components/LogsButton"; import { OpenLogsButton } from "./components/LogsButton";
import { openLogModal } from "./components/LogsModal"; import { openLogModal } from "./components/LogsModal";
@ -256,15 +255,6 @@ export default definePlugin({
replace: "deleted:$self.getDeleted(...arguments), editHistory:$self.getEdited(...arguments)," replace: "deleted:$self.getDeleted(...arguments), editHistory:$self.getEdited(...arguments),"
} }
}, },
{
find: ".controlButtonWrapper,",
predicate: () => settings.store.ShowLogsButton,
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,100}mobileToolbar)/,
replace: "$1$self.addIconToToolBar(arguments[0]);$2"
}
},
{ {
find: "childrenMessageContent:null", find: "childrenMessageContent:null",
replacement: { replacement: {
@ -310,21 +300,9 @@ export default definePlugin({
openLogModal(); openLogModal();
} }
}, },
renderHeaderBarButton() {
addIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) { if (!settings.store.ShowLogsButton) return null;
if (Array.isArray(e.toolbar)) return OpenLogsButton();
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<OpenLogsButton />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"MessageLoggerEnhanced"} >
<OpenLogsButton />
</ErrorBoundary>,
e.toolbar,
];
}, },
processMessageFetch, processMessageFetch,

View file

@ -50,12 +50,12 @@ export default definePlugin({
find: ".gifts)", find: ".gifts)",
replacement: [ replacement: [
{ {
match: /(?<="submit"\)\}\);)(?=.{0,50}null!=(\i))/, match: /(?<=(,!\i&&\(null==\(\i=\i\.stickers\)\?void 0.*?\i\.push\(\{).{0,15}node:(.{0,45})},"sticker"\)\}\))/,
replace: '$1["stickers+"]=3;' replace: "$1key:\"stickers+\",node:$2,stickersType:\"stickers+\"},\"stickers+\")})"
}, },
{ {
match: /(?<=,.{0,5}\(null==\(\i=\i\.stickers\)\?void 0.*?(\i)\.push\(\{.{0,15}node:(.{0,15}{disabled:\i,type:\i)},"sticker"\)\}\))/, match: /(?<="submit"\)\}\);)(?=.{0,50}null!=(\i))/,
replace: ",arguments[0].type?.submit?.button&&$1.push({key:\"stickers+\",node:$2,stickersType:\"stickers+\"},\"stickers+\")})" replace: '$1["stickers+"]=3;'
} }
] ]
}, },

View file

@ -35,7 +35,6 @@ export default definePlugin({
name: "MoreUserTags", name: "MoreUserTags",
description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)", description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)",
authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN, EquicordDevs.Hen], authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN, EquicordDevs.Hen],
dependencies: ["MemberListDecoratorsAPI", "NicknameIconsAPI", "MessageDecorationsAPI"],
settings, settings,
patches: [ patches: [
// Make discord actually use our tags // Make discord actually use our tags

View file

@ -16,9 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import "@equicordplugins/_misc/styles.css"; import { Alert } from "@components/Alert";
import { Paragraph } from "@components/Paragraph";
import { EquicordDevs } from "@utils/constants"; import { EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
@ -26,11 +24,11 @@ export default definePlugin({
name: "NeverPausePreviews", name: "NeverPausePreviews",
description: "Prevents in-call/PiP previews (screenshare, streams, etc) from pausing even if the client loses focus", description: "Prevents in-call/PiP previews (screenshare, streams, etc) from pausing even if the client loses focus",
authors: [EquicordDevs.vappstar], authors: [EquicordDevs.vappstar],
settingsAboutComponent: () => <> settingsAboutComponent: () => (
<Paragraph className="plugin-warning"> <Alert.Warning>
This plugin will cause discord to use more resources than normal This plugin will cause discord to use more resources than normal
</Paragraph> </Alert.Warning>
</>, ),
patches: [ patches: [
{ {
find: "streamerPaused()", find: "streamerPaused()",

View file

@ -0,0 +1,23 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 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: "NoAppsAllowed",
description: "returns the bot's tag :skulk:",
authors: [EquicordDevs.meowabyte],
patches: [
{
find: '"#{intl::APP_TAG::hash}":',
replacement: {
match: /(#{intl::APP_TAG::hash}":\[").*?("\])/,
replace: "$1BOT$2"
}
}
]
});

View file

@ -4,23 +4,14 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
export default definePlugin({ export default definePlugin({
name: "NoBulletPoints", name: "NoBulletPoints",
description: "Stops you from typing markdown bullet points (stinky)", description: "Stops you from typing markdown bullet points (stinky)",
authors: [Devs.Samwich], authors: [Devs.Samwich],
dependencies: ["MessageEventsAPI"], onBeforeMessageSend(channelId, msg) {
start() { msg.content = textProcessing(msg.content);
this.preSend = addMessagePreSendListener((channelId, msg) => {
msg.content = textProcessing(msg.content);
});
},
stop() {
this.preSend = removeMessagePreSendListener((channelId, msg) => {
msg.content = textProcessing(msg.content);
});
}, },
}); });

View file

@ -4,10 +4,8 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import "@equicordplugins/_misc/styles.css";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Paragraph } from "@components/Paragraph"; import { Alert } from "@components/Alert";
import { EquicordDevs } from "@utils/constants"; import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { UserStore } from "@webpack/common"; import { UserStore } from "@webpack/common";
@ -51,11 +49,11 @@ export default definePlugin({
name: "PlatformSpoofer", name: "PlatformSpoofer",
description: "Spoof what platform or device you're on", description: "Spoof what platform or device you're on",
authors: [EquicordDevs.Drag], authors: [EquicordDevs.Drag],
settingsAboutComponent: () => <> settingsAboutComponent: () => (
<Paragraph className="plugin-warning"> <Alert.Warning>
We can't guarantee this plugin won't get you warned or banned. We can't guarantee this plugin won't get you warned or banned.
</Paragraph> </Alert.Warning>
</>, ),
settings: settings, settings: settings,
patches: [ patches: [
{ {

View file

@ -5,9 +5,7 @@
*/ */
import { import {
addMessagePreSendListener,
MessageSendListener, MessageSendListener,
removeMessagePreSendListener,
} from "@api/MessageEvents"; } from "@api/MessageEvents";
import { import {
definePluginSettings, definePluginSettings,
@ -67,9 +65,7 @@ export default definePlugin({
name: "PolishWording", name: "PolishWording",
description: "Tweaks your messages to make them look nicer and have better grammar. See settings", description: "Tweaks your messages to make them look nicer and have better grammar. See settings",
authors: [Devs.Samwich, EquicordDevs.WKoA], authors: [Devs.Samwich, EquicordDevs.WKoA],
dependencies: ["MessageEventsAPI"], onBeforeMessageSend: presendObject,
start: () => addMessagePreSendListener(presendObject),
stop: () => removeMessagePreSendListener(presendObject),
settings, settings,
}); });

View file

@ -16,17 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import "@equicordplugins/_misc/styles.css";
import "./style.css"; import "./style.css";
import { showNotification } from "@api/Notifications"; import { showNotification } from "@api/Notifications";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { ErrorBoundary } from "@components/index";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getTheme, Theme } from "@utils/discord"; import { getTheme, Theme } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Button, ChannelStore, FluxDispatcher, GuildChannelStore, NavigationRouter, RestAPI, Tooltip, UserStore } from "@webpack/common"; import { ChannelStore, FluxDispatcher, GuildChannelStore, NavigationRouter, RestAPI, UserStore } from "@webpack/common";
const QuestIcon = findComponentByCodeLazy("10.47a.76.76"); const QuestIcon = findComponentByCodeLazy("10.47a.76.76");
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"'); const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
@ -38,16 +36,13 @@ let questIdCheck = 0;
function ToolBarHeader() { function ToolBarHeader() {
return ( return (
<ErrorBoundary noop={true}> <HeaderBarIcon
<HeaderBarIcon tooltip="Complete Quest"
tooltip="Complete Quest" position="bottom"
position="bottom" className="vc-quest-completer"
className="vc-quest-completer" icon={QuestIcon}
icon={QuestIcon} onClick={openCompleteQuestUI}
onClick={openCompleteQuestUI} />
>
</HeaderBarIcon>
</ErrorBoundary>
); );
} }
@ -213,11 +208,6 @@ async function openCompleteQuestUI() {
} }
const settings = definePluginSettings({ const settings = definePluginSettings({
useNavBar: {
description: "Move quest button down to the server nav bar",
type: OptionType.BOOLEAN,
default: false,
},
disableNotifications: { disableNotifications: {
description: "Disable notifications when no quests are available or when a quest is completed", description: "Disable notifications when no quests are available or when a quest is completed",
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
@ -238,22 +228,6 @@ export default definePlugin({
replace: "onClick:()=>$self.mobileQuestPatch($1)" replace: "onClick:()=>$self.mobileQuestPatch($1)"
}, },
}, },
{
find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
match: /"HELP".{0,100}className:\i\}\)(?=\])/,
replace: "$&,$self.renderTitleBar()"
},
predicate: () => !settings.store.useNavBar
},
{
find: ".controlButtonWrapper,",
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,200}mobileToolbar)/,
replace: "$1$self.renderNavBar(arguments[0]);$2"
},
predicate: () => settings.store.useNavBar
}
], ],
mobileQuestPatch(questId) { mobileQuestPatch(questId) {
if (questId === questIdCheck) return; if (questId === questIdCheck) return;
@ -265,37 +239,5 @@ export default definePlugin({
} }
}); });
}, },
renderTitleBar() { renderHeaderBarButton: ToolBarHeader
return (
<ErrorBoundary noop>
<Tooltip text="Complete Quest">
{tooltipProps => (
<Button style={{ backgroundColor: "transparent", border: "none" }}
{...tooltipProps}
size={Button.Sizes.SMALL}
className={"vc-quest-completer-icon"}
onClick={openCompleteQuestUI}
>
<QuestIcon width={20} height={20} />
</Button>
)}
</Tooltip>
</ErrorBoundary>
);
},
renderNavBar(e) {
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<ToolBarHeader />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"QuestCompleter"}>
<ToolBarHeader />
</ErrorBoundary>,
e.toolbar,
];
}
}); });

View file

@ -4,20 +4,18 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import "@equicordplugins/_misc/styles.css"; import { Alert } from "@components/Alert";
import { Paragraph } from "@components/Paragraph";
import { EquicordDevs } from "@utils/constants"; import { EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
export default definePlugin({ export default definePlugin({
name: "QuestFocused", name: "QuestFocused",
description: "Prevent the quests player from pausing and possibly skip it all together.", description: "Prevent the quests player from pausing and possibly skip it all together.",
settingsAboutComponent: () => <> settingsAboutComponent: () => (
<Paragraph className="plugin-warning"> <Alert.Warning>
You might need to spam left mouse button on the video to skip it. You might need to spam left mouse button on the video to skip it.
</Paragraph> </Alert.Warning>
</>, ),
authors: [EquicordDevs.secp192k1], authors: [EquicordDevs.secp192k1],
patches: [ patches: [
// Block pausing // Block pausing

View file

@ -1073,7 +1073,7 @@ export default definePlugin({
}, },
{ {
// Adds a maxDigits prop to the LowerBadge component which allows for not truncating, or for truncating at a specific threshold. // Adds a maxDigits prop to the LowerBadge component which allows for not truncating, or for truncating at a specific threshold.
find: "STATUS_DANGER,disableColor", find: '"renderBadgeCount"])',
group: true, group: true,
replacement: [ replacement: [
{ {
@ -1251,7 +1251,7 @@ export default definePlugin({
}, },
{ {
// Add the trigger to the memo for rerendering the progress label. // Add the trigger to the memo for rerendering the progress label.
match: /(\i.intl.string\(\i.\i#{intl::QUESTS_SEE_CODE}\)}\)}},\[)/, match: /(\i\.intl\.string\(\i\.\i#{intl::QUESTS_SEE_CODE}\)\}\)\}\},\[|\)\}\}\},\[)/,
replace: "$1questRerenderTrigger," replace: "$1questRerenderTrigger,"
}, },
{ {

View file

@ -14,8 +14,8 @@ import { formatLowerBadge } from "./misc";
// GuildlessServerListItem's built-in pill does not support unread state. // GuildlessServerListItem's built-in pill does not support unread state.
export const GuildlessServerListItemComponent = findComponentByCodeLazy("tooltip:", "lowerBadgeSize:"); export const GuildlessServerListItemComponent = findComponentByCodeLazy("tooltip:", "lowerBadgeSize:");
export const GuildedServerListItemPillComponent = findComponentByCodeLazy('"pill":"empty"'); export const GuildedServerListItemPillComponent = findComponentByCodeLazy('"pill":"empty"');
export const ServerListItemLowerBadgeComponent = findComponentByCodeLazy("STATUS_DANGER,disableColor:"); export const ServerListItemLowerBadgeComponent = findComponentByCodeLazy("numberBadge", '"renderBadgeCount"');
export const ServerListItemUpperBadgeComponent = findComponentByCodeLazy("STATUS_DANGER,shape:", "iconBadge,"); export const ServerListItemUpperBadgeComponent = findComponentByCodeLazy("iconBadge", ".ROUND,disableColor");
export const RadioGroup = findComponentByCodeLazy(',["label","description"'); export const RadioGroup = findComponentByCodeLazy(',["label","description"');
export const QuestTile = findComponentByCodeLazy(".rowIndex,trackGuildAndChannelMetadata"); export const QuestTile = findComponentByCodeLazy(".rowIndex,trackGuildAndChannelMetadata");

View file

@ -34,9 +34,11 @@ export default definePlugin({
name: "SekaiStickers", name: "SekaiStickers",
description: "Sekai Stickers built in discord originally from github.com/TheOriginalAyaka", description: "Sekai Stickers built in discord originally from github.com/TheOriginalAyaka",
authors: [Devs.MaiKokain], authors: [Devs.MaiKokain],
dependencies: ["ChatInputButtonAPI"],
settings, settings,
renderChatBarButton: SekaiStickerChatButton, chatBarButton: {
icon: kanadeSvg,
render: SekaiStickerChatButton
},
async start() { async start() {
const fonts = [{ name: "YurukaStd", url: "https://raw.githubusercontent.com/TheOriginalAyaka/sekai-stickers/47a2ca33b8cb35f59800e8faad48980e4ce5ea71/src/fonts/YurukaStd.woff2" }, { name: "SSFangTangTi", url: "https://raw.githubusercontent.com/TheOriginalAyaka/sekai-stickers/main/src/fonts/ShangShouFangTangTi.woff2" }]; const fonts = [{ name: "YurukaStd", url: "https://raw.githubusercontent.com/TheOriginalAyaka/sekai-stickers/47a2ca33b8cb35f59800e8faad48980e4ce5ea71/src/fonts/YurukaStd.woff2" }, { name: "SSFangTangTi", url: "https://raw.githubusercontent.com/TheOriginalAyaka/sekai-stickers/main/src/fonts/ShangShouFangTangTi.woff2" }];
if (!IS_FONTS_LOADED) { if (!IS_FONTS_LOADED) {

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
import { Devs, EquicordDevs } from "@utils/constants"; import { Devs, EquicordDevs } from "@utils/constants";
import { isEquicordPluginDev, isPluginDev } from "@utils/misc"; import { isEquicordPluginDev, isPluginDev } from "@utils/misc";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
@ -142,12 +141,8 @@ export default definePlugin({
name: "ShowBadgesInChat", name: "ShowBadgesInChat",
authors: [Devs.Inbestigator, EquicordDevs.KrystalSkull], authors: [Devs.Inbestigator, EquicordDevs.KrystalSkull],
description: "Shows the message author's badges beside their name in chat.", description: "Shows the message author's badges beside their name in chat.",
dependencies: ["MessageDecorationsAPI"],
settings, settings,
start: () => { renderMessageDecoration(props) {
addMessageDecoration("vc-show-badges-in-chat", props => props.message?.author ? <ChatBadges author={props.message.author} /> : null); return props.message?.author ? <ChatBadges author={props.message.author} /> : null;
},
stop: () => {
removeMessageDecoration("vc-show-badges-in-chat");
} }
}); });

View file

@ -11,11 +11,15 @@ export default definePlugin({
name: "ShowResourceChannels", name: "ShowResourceChannels",
description: "shows the channels hidden behind the server resources in the channel list", description: "shows the channels hidden behind the server resources in the channel list",
authors: [EquicordDevs.VillainsRule], authors: [EquicordDevs.VillainsRule],
patches: [{ patches: [
find: '"should_show_in_recents"', {
replacement: [{ find: ".GUILD_DIRECTORY:null",
match: /\i\.hideResourceChannels&&/, replacement: [
replace: "false&&" {
}] match: /\i\.hideResourceChannels&&/,
}] replace: "false&&"
}
]
}
]
}); });

View file

@ -7,7 +7,6 @@
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands"; import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs, EquicordDevs } from "@utils/constants"; import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -70,13 +69,13 @@ const SignatureToggle: ChatBarButtonFactory = ({ isMainChat }) => {
); );
}; };
const handleMessage = ((channelId, msg) => { function SignatureIcon() {
if (!settings.store.isEnabled) { return (
msg.content = msg.content; <svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 21.333">
} else { <path fill="currentColor" d="M2 4.621a.5.5 0 0 1 .854-.353l6.01 6.01c.126.126.17.31.15.487a2 2 0 1 0 1.751-1.751a.59.59 0 0 1-.487-.15l-6.01-6.01A.5.5 0 0 1 4.62 2H11a9 9 0 0 1 8.468 12.054l2.24 2.239a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.415 0l-2.239-2.239A9 9 0 0 1 2 11z" />
msg.content = textProcessing(msg.content); </svg>
} );
}); }
const ChatBarContextCheckbox: NavContextMenuPatchCallback = children => { const ChatBarContextCheckbox: NavContextMenuPatchCallback = children => {
const { isEnabled, contextMenu } = settings.use(["isEnabled", "contextMenu"]); const { isEnabled, contextMenu } = settings.use(["isEnabled", "contextMenu"]);
@ -102,17 +101,23 @@ export default definePlugin({
name: "Signature", name: "Signature",
description: "Automated fingerprint/end text", description: "Automated fingerprint/end text",
authors: [Devs.Ven, Devs.Rini, Devs.ImBanana, EquicordDevs.KrystalSkull], authors: [Devs.Ven, Devs.Rini, Devs.ImBanana, EquicordDevs.KrystalSkull],
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"], onBeforeMessageSend(channelId, msg) {
renderChatBarButton: SignatureToggle, if (!settings.store.isEnabled) {
msg.content = msg.content;
} else {
msg.content = textProcessing(msg.content);
}
},
start: () => { start: () => {
if (settings.store.isEnabled) true; if (settings.store.isEnabled) true;
addMessagePreSendListener(handleMessage);
}, },
stop: () => { stop: () => {
if (settings.store.isEnabled) false; if (settings.store.isEnabled) false;
removeMessagePreSendListener(handleMessage); },
chatBarButton: {
icon: SignatureIcon,
render: SignatureToggle
}, },
settings, settings,

View file

@ -6,7 +6,6 @@
import { AudioPlayerInterface, createAudioPlayer } from "@api/AudioPlayer"; import { AudioPlayerInterface, createAudioPlayer } from "@api/AudioPlayer";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { EquicordDevs } from "@utils/constants"; import { EquicordDevs } from "@utils/constants";
import { ModalProps, ModalRoot, openModal } from "@utils/modal"; import { ModalProps, ModalRoot, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -143,15 +142,7 @@ export default definePlugin({
authors: [EquicordDevs.sliwka], authors: [EquicordDevs.sliwka],
settings, settings,
dependencies: ["AudioPlayerAPI"], dependencies: ["AudioPlayerAPI"],
patches: [ renderHeaderBarButton: SoggyButton,
{
find: ".controlButtonWrapper,",
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,450}mobileToolbar)/,
replace: "$1$self.addIconToToolBar(arguments[0]);$2"
}
}
],
start() { start() {
assignBoop(settings.store.boopLink, settings.store.boopVolume * 100); assignBoop(settings.store.boopLink, settings.store.boopVolume * 100);
@ -162,21 +153,4 @@ export default definePlugin({
boopSound?.delete(); boopSound?.delete();
song?.delete(); song?.delete();
}, },
// taken from message logger lol
addIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) {
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<SoggyButton />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"MessageLoggerEnhanced"} >
<SoggyButton />
</ErrorBoundary>,
e.toolbar,
];
},
}); });

View file

@ -4,14 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants"; import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { FluxDispatcher } from "@webpack/common"; import { FluxDispatcher } from "@webpack/common";
import { CharBarButton, ChatBarIcon } from "./components/Icons";
import { OpenSBLogsButton } from "./components/SoundBoardLog"; import { OpenSBLogsButton } from "./components/SoundBoardLog";
import settings from "./settings"; import settings from "./settings";
import { updateLoggedSounds } from "./store"; import { updateLoggedSounds } from "./store";
@ -22,33 +19,9 @@ export default definePlugin({
name: "SoundBoardLogger", name: "SoundBoardLogger",
authors: [Devs.Moxxie, EquicordDevs.Fres, Devs.amy, Devs.thororen], authors: [Devs.Moxxie, EquicordDevs.Fres, Devs.amy, Devs.thororen],
description: "Logs all soundboards that are played in a voice chat and allows you to download them", description: "Logs all soundboards that are played in a voice chat and allows you to download them",
dependencies: ["AudioPlayerAPI", "ChatInputButtonAPI"], dependencies: ["AudioPlayerAPI"],
patches: [
{
predicate: () => settings.store.IconLocation === "toolbar",
find: ".controlButtonWrapper,",
replacement: {
match: /(function \i\(\i\){)(.{1,500}toolbar.{1,500}mobileToolbar)/,
replace: "$1$self.addSBIconToToolBar(arguments[0]);$2"
}
}
],
settings, settings,
addSBIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) { renderHeaderBarButton: OpenSBLogsButton,
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<OpenSBLogsButton />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"SoundBoardLogger"} >
<OpenSBLogsButton />
</ErrorBoundary>,
e.toolbar,
];
},
start() { start() {
enableStyle(styles); enableStyle(styles);
FluxDispatcher.subscribe("VOICE_CHANNEL_EFFECT_SEND", async sound => { FluxDispatcher.subscribe("VOICE_CHANNEL_EFFECT_SEND", async sound => {
@ -56,10 +29,8 @@ export default definePlugin({
await updateLoggedSounds(sound); await updateLoggedSounds(sound);
getListeners().forEach(cb => cb()); getListeners().forEach(cb => cb());
}); });
if (settings.store.IconLocation === "chat") addChatBarButton("vc-soundlog-button", CharBarButton, ChatBarIcon);
}, },
stop() { stop() {
disableStyle(styles); disableStyle(styles);
if (settings.store.IconLocation === "chat") removeChatBarButton("vc-soundlog-button");
} }
}); });

View file

@ -85,15 +85,6 @@ const settings = definePluginSettings({
default: 0.5, default: 0.5,
markers: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] markers: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
}, },
IconLocation: {
description: "choose where to show the SoundBoard Log icon (requires restart)",
type: OptionType.SELECT,
options: [
{ label: "Toolbar", value: "toolbar", default: true },
{ label: "Chat input", value: "chat" }
],
restartNeeded: true
},
}); });
export default settings; export default settings;

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents"; import { MessageSendListener } from "@api/MessageEvents";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants"; import { EquicordDevs } from "@utils/constants";
import { getCurrentChannel, sendMessage } from "@utils/discord"; import { getCurrentChannel, sendMessage } from "@utils/discord";
@ -117,16 +117,11 @@ export default definePlugin({
name: "SplitLargeMessages", name: "SplitLargeMessages",
description: "Splits large messages into multiple to fit Discord's message limit.", description: "Splits large messages into multiple to fit Discord's message limit.",
authors: [EquicordDevs.Reycko], authors: [EquicordDevs.Reycko],
dependencies: ["MessageEventsAPI"],
settings, settings,
onBeforeMessageSend: listener,
start() { start() {
if (settings.store.maxLength === 0) maxLength = autoMaxLength(); if (settings.store.maxLength === 0) maxLength = autoMaxLength();
addMessagePreSendListener(listener);
},
stop() {
removeMessagePreSendListener(listener);
}, },
patches: [ patches: [

View file

@ -54,10 +54,21 @@ const ReverseMessageToggle: ChatBarButtonFactory = ({ isMainChat }) => {
); );
}; };
function ReverseMessageIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20">
<path fill={"currentColor"} d="M482-160q-134 0-228-93t-94-227v-7l-36 36q-11 11-28 11t-28-11q-11-11-11-28t11-28l104-104q12-12 28-12t28 12l104 104q11 11 11 28t-11 28q-11 11-28 11t-28-11l-36-36v7q0 100 70.5 170T482-240q16 0 31.5-2t30.5-7q17-5 32 1t23 21q8 16 1.5 31.5T577-175q-23 8-47 11.5t-48 3.5Zm-4-560q-16 0-31.5 2t-30.5 7q-17 5-32.5-1T360-733q-8-15-1.5-30.5T381-784q24-8 48-12t49-4q134 0 228 93t94 227v7l36-36q11-11 28-11t28 11q11 11 11 28t-11 28L788-349q-12 12-28 12t-28-12L628-453q-11-11-11-28t11-28q11-11 28-11t28 11l36 36v-7q0-100-70.5-170T478-720Z" />
</svg>
);
}
export default definePlugin({ export default definePlugin({
name: "TalkInReverse", name: "TalkInReverse",
authors: [EquicordDevs.Tolgchu], authors: [EquicordDevs.Tolgchu],
description: "Reverses the message content before sending it.", description: "Reverses the message content before sending it.",
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"], dependencies: ["MessageEventsAPI"],
renderChatBarButton: ReverseMessageToggle, chatBarButton: {
icon: ReverseMessageIcon,
render: ReverseMessageToggle
}
}); });

View file

@ -46,7 +46,6 @@ const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) =>
export default definePlugin({ export default definePlugin({
name: "Translate+", name: "Translate+",
description: "Vencord's translate plugin but with support for artistic languages!", description: "Vencord's translate plugin but with support for artistic languages!",
dependencies: ["MessageAccessoriesAPI"],
authors: [Devs.Ven, EquicordDevs.Prince527], authors: [Devs.Ven, EquicordDevs.Prince527],
settings, settings,
contextMenus: { contextMenus: {

View file

@ -5,7 +5,7 @@
*/ */
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Paragraph } from "@components/Paragraph"; import { Alert } from "@components/Alert";
import { EquicordDevs } from "@utils/constants"; import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types"; import { User } from "@vencord/discord-types";
@ -41,9 +41,9 @@ export default definePlugin({
description: "Mention any user, regardless of channel access.", description: "Mention any user, regardless of channel access.",
settings, settings,
settingsAboutComponent: () => ( settingsAboutComponent: () => (
<Paragraph className="plugin-warning"> <Alert.Warning>
Using Global Mention can cause performance issues and show an absurd amount of users in the autocomplete. Using Global Mention can cause performance issues and show an absurd amount of users in the autocomplete.
</Paragraph> </Alert.Warning>
), ),
patches: [ patches: [

View file

@ -14,7 +14,7 @@ import { ReadStateStore, useStateFromStores } from "@webpack/common";
const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore"); const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore");
const JoinedThreadsStore = findStoreLazy("JoinedThreadsStore"); const JoinedThreadsStore = findStoreLazy("JoinedThreadsStore");
const NumberBadge = findComponentByCodeLazy("numberBadge", ".STATUS_DANGER,"); const NumberBadge = findComponentByCodeLazy("numberBadge", '"renderBadgeCount"');
const settings = definePluginSettings({ const settings = definePluginSettings({
showOnMutedChannels: { showOnMutedChannels: {

View file

@ -1,96 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Tooltip, useRef, useState } from "@webpack/common";
const settings = definePluginSettings(
{
preservePitch: {
type: OptionType.BOOLEAN,
description: "Should pitch be preserved when changing speed?",
default: false,
restartNeeded: true
},
});
export default definePlugin({
name: "VideoSpeed",
description: "Allows you to change the speed of videos",
authors: [Devs.Samwich],
settings,
patches: [
{
find: /\.VIDEO\?\i\.videoControls:/,
replacement: {
match: /children:\[this\.renderPlayIcon\(\),.{0,200}\.setDurationRef}\),/,
replace: "$&$self.SpeedButton(),"
},
},
],
SpeedButton: ErrorBoundary.wrap(() => {
const elementRef = useRef<HTMLDivElement>(null);
const [speed, setSpeed] = useState(1);
const speedPaths =
{
1.5: "M320 48a48 48 0 1 1 96 0a48 48 0 1 1-96 0m-115.5 73.3c-5.4-2.5-11.7-1.9-16.4 1.7l-40.9 30.7c-14.1 10.6-34.2 7.7-44.8-6.4s-7.7-34.2 6.4-44.8l40.9-30.7c23.7-17.8 55.3-21 82.1-8.4l90.4 42.5c29.1 13.7 36.8 51.6 15.2 75.5L299.1 224h97.4c30.3 0 53 27.7 47.1 57.4l-28.2 140.9c-3.5 17.3-20.3 28.6-37.7 25.1s-28.6-20.3-25.1-37.7L377 288h-70.3c8.6 19.6 13.3 41.2 13.3 64c0 88.4-71.6 160-160 160S0 440.4 0 352s71.6-160 160-160c11.1 0 22 1.1 32.4 3.3l54.2-54.2zM160 448a96 96 0 1 0 0-192a96 96 0 1 0 0 192",
1.25: "M320 48a48 48 0 1 0-96 0a48 48 0 1 0 96 0M125.7 175.5c9.9-9.9 23.4-15.5 37.5-15.5c1.9 0 3.8.1 5.6.3L137.6 254c-9.3 28 1.7 58.8 26.8 74.5l86.2 53.9l-25.4 88.8c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l28.7-100.4c5.9-20.6-2.6-42.6-20.7-53.9L238 299l30.9-82.4l5.1 12.3c15 35.8 49.9 59.1 88.7 59.1H384c17.7 0 32-14.3 32-32s-14.3-32-32-32h-21.3c-12.9 0-24.6-7.8-29.5-19.7l-6.3-15c-14.6-35.1-44.1-61.9-80.5-73.1l-48.7-15C186.6 97.8 175 96 163.3 96c-31 0-60.8 12.3-82.7 34.3l-23.2 23.1c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l23.1-23.1zM91.2 352H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h69.6c19 0 36.2-11.2 43.9-28.5l11.5-25.9l-9.5-6a95.394 95.394 0 0 1-37.9-44.9z",
1: "M112 48a48 48 0 1 1 96 0a48 48 0 1 1-96 0m40 304v128c0 17.7-14.3 32-32 32s-32-14.3-32-32V256.9l-28.6 47.6c-9.1 15.1-28.8 20-43.9 10.9s-20-28.8-10.9-43.9l58.3-97c17.4-28.9 48.6-46.6 82.3-46.6h29.7c33.7 0 64.9 17.7 82.3 46.6l58.3 97c9.1 15.1 4.2 34.8-10.9 43.9s-34.8 4.2-43.9-10.9L232 256.9V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V352z",
0.5: "M368 32c41.7 0 75.9 31.8 79.7 72.5l85.6 26.3c25.4 7.8 42.8 31.3 42.8 57.9c0 21.8-11.7 41.9-30.7 52.7l-144.6 82.1l92.5 92.5H544c17.7 0 32 14.3 32 32s-14.3 32-32 32h-64c-8.5 0-16.6-3.4-22.6-9.4L346.9 360.2c11.7-36 3.2-77.1-25.4-105.7c-40.6-40.6-106.3-40.6-146.9-.1l-73.6 70c-6.4 6.1-6.7 16.2-.6 22.6s16.2 6.6 22.6.6l73.8-70.2l.1-.1l.1-.1c3.5-3.5 7.3-6.6 11.3-9.2c27.9-18.5 65.9-15.4 90.5 9.2c24.7 24.7 27.7 62.9 9 90.9c-2.6 3.8-5.6 7.5-9 10.9l-37 37H352c17.7 0 32 14.3 32 32s-14.3 32-32 32H64c-35.3 0-64-28.7-64-64C0 249.6 127 112.9 289.3 97.5C296.2 60.2 328.8 32 368 32m0 104a24 24 0 1 0 0-48a24 24 0 1 0 0 48"
};
return <div ref={elementRef}>
<Tooltip text={`Toggle Speed (${speed})`}>
{tooltipProps => (
<div
{...tooltipProps}
role="button"
style={{
cursor: "pointer",
paddingTop: "4px",
paddingLeft: "4px",
paddingRight: "4px",
}}
onClick={e => {
console.log(e);
let newSpeed;
switch (speed) {
case 1:
newSpeed = 1.25;
break;
case 1.25:
newSpeed = 1.5;
break;
case 1.5:
newSpeed = 0.5;
break;
case 0.5:
newSpeed = 1;
break;
}
const parent = e.currentTarget.parentNode!.parentNode!.parentNode!;
// this works with audio too as it is but it doesnt always select the right audio tag
const media = parent.querySelector("video")! /* ?? document.querySelector("source")?.parentElement*/;
console.log(media);
media.playbackRate = newSpeed;
media.preservesPitch = settings.store.preservePitch;
setSpeed(newSpeed);
}}
>
<svg xmlns="http://www.w3.org/2000/svg" width="19px" height="19px" viewBox="0 0 448 512"><path fill="currentColor" d={speedPaths[speed]}></path></svg>
</div>
)
}
</Tooltip >
</div >;
}, { noop: true })
});

View file

@ -100,7 +100,7 @@ async function loadAllBadges(noCache = false) {
let intervalId: any; let intervalId: any;
function BadgeContextMenu({ badge }: { badge: ProfileBadge & BadgeUserArgs; }) { export function BadgeContextMenu({ badge }: { badge: ProfileBadge & BadgeUserArgs; }) {
return ( return (
<Menu.Menu <Menu.Menu
navId="vc-badge-context" navId="vc-badge-context"

View file

@ -57,7 +57,7 @@ export function VencordDonorModal() {
</ModalContent> </ModalContent>
<ModalFooter> <ModalFooter>
<Flex justifyContent="center" style={{ width: "100%" }}> <Flex justifyContent="center" style={{ width: "100%" }}>
<DonateButton link="https://github.com/sponsors/Vendicated" /> <DonateButton />
</Flex> </Flex>
</ModalFooter> </ModalFooter>
</ModalRoot> </ModalRoot>
@ -113,7 +113,7 @@ export function EquicordDonorModal() {
</ModalContent> </ModalContent>
<ModalFooter> <ModalFooter>
<Flex justifyContent="center" style={{ width: "100%" }}> <Flex justifyContent="center" style={{ width: "100%" }}>
<DonateButton link="https://github.com/sponsors/thororen1234" /> <DonateButton equicord={true} />
</Flex> </Flex>
</ModalFooter> </ModalFooter>
</ModalRoot> </ModalRoot>

View file

@ -0,0 +1,24 @@
/*
* 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: "HeaderBarAPI",
description: "API to add buttons to the header bar.",
authors: [EquicordDevs.Prism],
patches: [
{
find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
match: /(?<=\i\.Fragment,\{children:\[.{1,500}className:\i\}\))(?=\])/,
replace: ",...Vencord.Api.HeaderBar._addButtons()"
}
}
]
});

View file

@ -4,14 +4,16 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { isPluginEnabled } from "@api/PluginManager";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import alwaysExpandProfiles from "@equicordplugins/alwaysExpandProfiles";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord"; import { getCurrentChannel } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types"; import { User } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import { ContextMenuApi, Menu } from "@webpack/common"; import { ContextMenuApi, Menu, UserStore } from "@webpack/common";
interface UserProfileProps { interface UserProfileProps {
popoutProps: Record<string, any>; popoutProps: Record<string, any>;
@ -37,6 +39,10 @@ const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
label={prioritizeServerProfile ? "View Account Profile" : "View Server Profile"} label={prioritizeServerProfile ? "View Account Profile" : "View Server Profile"}
disabled={getCurrentChannel()?.getGuildId() == null} disabled={getCurrentChannel()?.getGuildId() == null}
action={e => { action={e => {
if (isPluginEnabled(alwaysExpandProfiles.name)) {
const user = UserStore.getCurrentUser();
return alwaysExpandProfiles.openUserModal(user);
}
openAlternatePopout = true; openAlternatePopout = true;
accountPanelRef.current?.click(); accountPanelRef.current?.click();
}} }}

View file

@ -91,21 +91,24 @@ export default definePlugin({
replacement: [ replacement: [
// Use Decor avatar decoration hook // Use Decor avatar decoration hook
{ {
match: /(?<=\i\)\({avatarDecoration:)(\i)(?=,)(?<=currentUser:(\i).+?)/, match: /(?<=\i\)\({avatarDecoration:)\i(?=,)(?<=currentUser:(\i).+?)/,
replace: "$self.useUserDecorAvatarDecoration($1)??$&" replace: "$self.useUserDecorAvatarDecoration($1)??$&"
} }
] ]
}, },
// Messages ...[
{ '"Message Username"', // Messages
find: '"Message Username"', ".nameplatePreview,{", // Nameplate preview
"#{intl::ayozFl::raw}", // Avatar preview
].map(find => ({
find,
replacement: [ replacement: [
{ {
match: /(?<=userValue.{0,25}void 0:)((\i)\.avatarDecoration)/, match: /(?<=userValue.{0,25}void 0:)((\i)\.avatarDecoration)/,
replace: "$self.useUserDecorAvatarDecoration($2)??$1" replace: "$self.useUserDecorAvatarDecoration($2)??$1"
} }
] ]
}, })),
], ],
settings, settings,

View file

@ -10,5 +10,5 @@ import { extractAndLoadChunksLazy, findByPropsLazy } from "@webpack";
export const cl = classNameFactory("vc-decor-"); export const cl = classNameFactory("vc-decor-");
export const DecorationModalStyles = findByPropsLazy("modalPreview", "modalCloseButton", "spinner", "modal"); export const DecorationModalStyles = findByPropsLazy("modalPreview", "modalCloseButton", "spinner", "modal");
export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["initialSelectedDecoration:", /initialSelectedDecoration:\i,.{0,300}Promise\.all/]); export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["initialSelectedDecoration:", /initialSelectedDecoration:\i,.{0,300}\i\.e\(/]);
export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]); export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]);

View file

@ -27,7 +27,7 @@ export default definePlugin({
{ {
find: "#{intl::UNIGNORE_TO_JUMP_BODY}", find: "#{intl::UNIGNORE_TO_JUMP_BODY}",
replacement: { replacement: {
match: /return \i\.\i\.isBlockedForMessage\(/, match: /if\(\i\.\i\.isBlockedForMessage\(/,
replace: "return true;$&" replace: "return true;$&"
} }
} }

View file

@ -18,9 +18,6 @@
import "./style.css"; import "./style.css";
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons";
import { definePluginSettings, migratePluginSetting, Settings } from "@api/Settings"; import { definePluginSettings, migratePluginSetting, Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
@ -192,49 +189,22 @@ const PlatformIndicator = ({ user, isProfile, isMessage, isMemberList }: Platfor
); );
}; };
function toggleMemberListDecorators(enabled: boolean) {
if (enabled) {
addMemberListDecorator("PlatformIndicators", props => <PlatformIndicator user={props.user} isMemberList />);
} else {
removeMemberListDecorator("PlatformIndicators");
}
}
function toggleNicknameIcons(enabled: boolean) {
if (enabled) {
addNicknameIcon("PlatformIndicators", props => <PlatformIndicator user={UserStore.getUser(props.userId)} isProfile />, 1);
} else {
removeNicknameIcon("PlatformIndicators");
}
}
function toggleMessageDecorators(enabled: boolean) {
if (enabled) {
addMessageDecoration("PlatformIndicators", props => <PlatformIndicator user={props.message?.author} isMessage />);
} else {
removeMessageDecoration("PlatformIndicators");
}
}
migratePluginSetting("PlatformIndicators", "profiles", "badges"); migratePluginSetting("PlatformIndicators", "profiles", "badges");
const settings = definePluginSettings({ const settings = definePluginSettings({
list: { list: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Show indicators in the member list", description: "Show indicators in the member list",
default: true, default: true,
onChange: toggleMemberListDecorators
}, },
profiles: { profiles: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Show indicators in user profiles", description: "Show indicators in user profiles",
default: true, default: true,
onChange: toggleNicknameIcons
}, },
messages: { messages: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Show indicators inside messages", description: "Show indicators inside messages",
default: true, default: true,
onChange: toggleMessageDecorators
}, },
colorMobileIndicator: { colorMobileIndicator: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
@ -278,19 +248,21 @@ export default definePlugin({
name: "PlatformIndicators", name: "PlatformIndicators",
description: "Adds platform indicators (Desktop, Mobile, Web...) to users", description: "Adds platform indicators (Desktop, Mobile, Web...) to users",
authors: [Devs.kemo, Devs.TheSun, Devs.Nuckyz, Devs.Ven], authors: [Devs.kemo, Devs.TheSun, Devs.Nuckyz, Devs.Ven],
dependencies: ["MemberListDecoratorsAPI", "NicknameIconsAPI", "MessageDecorationsAPI"],
settings, settings,
renderNicknameIcon(props) {
start() { if (!settings.store.profiles) return null;
if (settings.store.list) toggleMemberListDecorators(true); return (
if (settings.store.profiles) toggleNicknameIcons(true); <PlatformIndicator user={UserStore.getUser(props.userId)} isProfile />
if (settings.store.messages) toggleMessageDecorators(true); );
}, },
renderMemberListDecorator(props) {
if (!settings.store.list) return null;
return <PlatformIndicator user={props.user} isMemberList />;
stop() { },
if (settings.store.list) toggleMemberListDecorators(false); renderMessageDecoration(props) {
if (settings.store.profiles) toggleNicknameIcons; if (!settings.store.messages) return null;
if (settings.store.messages) toggleMessageDecorators(false); return <PlatformIndicator user={props.message?.author} isMessage />;
}, },
patches: [ patches: [

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { DataStore } from "@api/index";
import "./style.css"; import "./style.css";
import { definePluginSettings, Settings } from "@api/Settings"; import { definePluginSettings, Settings } from "@api/Settings";
@ -11,15 +12,21 @@ import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { GuildMember, Message, User } from "@vencord/discord-types"; import { GuildMember, Message, User } from "@vencord/discord-types";
import { findByCodeLazy, findStoreLazy } from "@webpack"; import { findByCodeLazy, findStoreLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, GuildStore, MessageStore, RelationshipStore, StreamerModeStore, useEffect, UserStore } from "@webpack/common"; import { ChannelStore, GuildMemberStore, GuildStore, Menu, MessageStore, RelationshipStore, StreamerModeStore, TextInput, useEffect, UserStore, useState } from "@webpack/common";
import { JSX } from "react"; import { JSX } from "react";
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { Button, ErrorBoundary, Heading, TextButton } from "@components/index";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/index";
const wrapEmojis = findByCodeLazy("lastIndex;return"); const wrapEmojis = findByCodeLazy("lastIndex;return");
const AccessibilityStore = findStoreLazy("AccessibilityStore"); const AccessibilityStore = findStoreLazy("AccessibilityStore");
const colorPattern = /^#(?:[\da-f]{3}){1,2}$|^#(?:[\da-f]{4}){1,2}$|(rgb|hsl)a?\((\s*-?\d+%?\s*,){2}(\s*-?\d+%?\s*)\)|(rgb|hsl)a?\((\s*-?\d+%?\s*,){3}\s*(0|(0?\.\d+)|1)\)$/iu; const colorPattern = /^#(?:[\da-f]{3}){1,2}$|^#(?:[\da-f]{4}){1,2}$|(rgb|hsl)a?\((\s*-?\d+%?\s*,){2}(\s*-?\d+%?\s*)\)|(rgb|hsl)a?\((\s*-?\d+%?\s*,){3}\s*(0|(0?\.\d+)|1)\)$/iu;
const roleColorPattern = /^role((?:\+|-)\d{0,4})?$/iu; const roleColorPattern = /^role((?:\+|-)\d{0,4})?$/iu;
const symbolPattern = /^[\p{S}\p{P}]{1,3}$/iu; const symbolPattern = /^[\p{S}\p{P}]{1,3}$/iu;
const templatePattern = /(?:\{(?:friend|nick|display|user)(?:,\s*(?:friend|nick|display|user))*\})/iu; const templatePattern = /(?:\{(?:custom|friend|nick|display|user)(?:,\s*(?:custom|friend|nick|display|user))*\})/iu;
type CustomNicknameData = Record<string, string>;
let customNicknames: CustomNicknameData = {};
function adjustHex(color: string, percent: number): string { function adjustHex(color: string, percent: number): string {
let hex = color.replace("#", ""); let hex = color.replace("#", "");
@ -208,8 +215,8 @@ function validTemplate(value: string) {
suffix.length > 3 || suffix.length > 3 ||
(!!prefix && !symbolPattern.test(prefix)) || (!!prefix && !symbolPattern.test(prefix)) ||
(!!suffix && !symbolPattern.test(suffix)) || (!!suffix && !symbolPattern.test(suffix)) ||
targetProcessedNames.length > 4 || targetProcessedNames.length > 5 ||
targetProcessedNames.some(name => !["friend", "nick", "display", "user"].includes(name.trim())) targetProcessedNames.some(name => !["custom", "friend", "nick", "display", "user"].includes(name.trim()))
); );
}); });
@ -218,7 +225,13 @@ function validTemplate(value: string) {
} }
} }
function getProcessedNames(author: any, truncateAllNamesWithStreamerMode: boolean, discriminators: boolean): [string | null, string | null, string | null, string | null] { function getProcessedNames(author: any, truncateAllNamesWithStreamerMode: boolean, discriminators: boolean): {
username: string | null;
display: string | null;
nick: string | null;
friend: string | null;
custom: string | null;
} {
let discriminator: string | null = null; let discriminator: string | null = null;
if (discriminators) { if (discriminators) {
@ -254,7 +267,13 @@ function getProcessedNames(author: any, truncateAllNamesWithStreamerMode: boolea
? friendName[0] + "..." ? friendName[0] + "..."
: friendName as string; : friendName as string;
return [username, display, nick, friend]; const customName: string | null = !author?.id ? null : customNicknames[author.id] || null;
const custom: string | null = !customName ? null
: StreamerModeStore.enabled && (truncateAllNamesWithStreamerMode || customName.toLowerCase() === author.username.toLowerCase())
? customName[0] + "..."
: customName;
return { username, display, nick, friend, custom };
} }
interface mentionProps { interface mentionProps {
@ -351,8 +370,8 @@ function renderUsername(
const isReaction = isReactionsTooltip || isReactionsPopout; const isReaction = isReactionsTooltip || isReactionsPopout;
const isVoice = type === "voiceChannel"; const isVoice = type === "voiceChannel";
const config = hookless ? settings.store : settings.use(["messages", "replies", "mentions", "memberList", "profilePopout", "reactions", "discriminators", "hideDefaultAtSign", "truncateAllNamesWithStreamerMode", "removeDuplicates", "ignoreGradients", "ignoreFonts", "animateGradients", "includedNames", "friendNameColor", "nicknameColor", "displayNameColor", "usernameColor", "nameSeparator", "triggerNameRerender"]); const config = hookless ? settings.store : settings.use(["messages", "replies", "mentions", "memberList", "profilePopout", "reactions", "discriminators", "hideDefaultAtSign", "truncateAllNamesWithStreamerMode", "removeDuplicates", "ignoreGradients", "ignoreFonts", "animateGradients", "includedNames", "customNameColor", "friendNameColor", "nicknameColor", "displayNameColor", "usernameColor", "nameSeparator", "triggerNameRerender"]);
const { messages, replies, mentions, memberList, profilePopout, reactions, discriminators, hideDefaultAtSign, truncateAllNamesWithStreamerMode, removeDuplicates, ignoreGradients, ignoreFonts, animateGradients, includedNames, friendNameColor, nicknameColor, displayNameColor, usernameColor, nameSeparator, triggerNameRerender } = config; const { messages, replies, mentions, memberList, profilePopout, reactions, discriminators, hideDefaultAtSign, truncateAllNamesWithStreamerMode, removeDuplicates, ignoreGradients, ignoreFonts, animateGradients, includedNames, customNameColor, friendNameColor, nicknameColor, displayNameColor, usernameColor, nameSeparator, triggerNameRerender } = config;
const canUseGradient = ((author as GuildMember)?.guildId ? (GuildStore.getGuild((author as GuildMember).guildId) ?? {}).premiumFeatures?.features.includes("ENHANCED_ROLE_COLORS") : false); const canUseGradient = ((author as GuildMember)?.guildId ? (GuildStore.getGuild((author as GuildMember).guildId) ?? {}).premiumFeatures?.features.includes("ENHANCED_ROLE_COLORS") : false);
const textMutedValue = getComputedStyle(document.documentElement)?.getPropertyValue("--text-muted")?.trim() || "#72767d"; const textMutedValue = getComputedStyle(document.documentElement)?.getPropertyValue("--text-muted")?.trim() || "#72767d";
@ -361,14 +380,16 @@ function renderUsername(
const resolvedDisplayNameColor = author ? resolveColor(author, displayNameColor.trim(), "", canUseGradient) : null; const resolvedDisplayNameColor = author ? resolveColor(author, displayNameColor.trim(), "", canUseGradient) : null;
const resolvedNicknameColor = author ? resolveColor(author, nicknameColor.trim(), "", canUseGradient) : null; const resolvedNicknameColor = author ? resolveColor(author, nicknameColor.trim(), "", canUseGradient) : null;
const resolvedFriendNameColor = author ? resolveColor(author, friendNameColor.trim(), "", canUseGradient) : null; const resolvedFriendNameColor = author ? resolveColor(author, friendNameColor.trim(), "", canUseGradient) : null;
const resolvedCustomNameColor = author ? resolveColor(author, customNameColor.trim(), "", canUseGradient) : null;
const affixColor = { color: textMutedValue, "-webkit-text-fill-color": textMutedValue, isolation: "isolate", "white-space": "pre", "font-family": "var(--font-primary)", "letter-spacing": "normal" }; const affixColor = { color: textMutedValue, "-webkit-text-fill-color": textMutedValue, isolation: "isolate", "white-space": "pre", "font-family": "var(--font-primary)", "letter-spacing": "normal" };
const [username, display, nick, friend] = getProcessedNames(author, truncateAllNamesWithStreamerMode, discriminators); const { username, display, nick, friend, custom } = getProcessedNames(author, truncateAllNamesWithStreamerMode, discriminators);
const names: Record<string, [string | null, object | null]> = { const names: Record<string, [string | null, object | null]> = {
user: [username, resolvedUsernameColor], user: [username, resolvedUsernameColor],
display: [display, resolvedDisplayNameColor], display: [display, resolvedDisplayNameColor],
nick: [nick, resolvedNicknameColor], nick: [nick, resolvedNicknameColor],
friend: [friend, resolvedFriendNameColor], friend: [friend, resolvedFriendNameColor],
custom: [custom, resolvedCustomNameColor]
}; };
const outputs: any[] = []; const outputs: any[] = [];
@ -421,6 +442,7 @@ function renderUsername(
let second = outputs.shift(); let second = outputs.shift();
let third = outputs.shift(); let third = outputs.shift();
let fourth = outputs.shift(); let fourth = outputs.shift();
let fifth = outputs.shift();
const firstValueWrapped = hookless ? const firstValueWrapped = hookless ?
(first.name || "") (first.name || "")
@ -438,10 +460,15 @@ function renderUsername(
((fourth ?? {}).name || "") ((fourth ?? {}).name || "")
: wrapEmojis((fourth ?? {}).name || ""); : wrapEmojis((fourth ?? {}).name || "");
const fifthValueWrapped = hookless ?
((fifth ?? {}).name || "")
: wrapEmojis((fifth ?? {}).name || "");
first.wrapped = firstValueWrapped; first.wrapped = firstValueWrapped;
second && (second.wrapped = secondValueWrapped); second && (second.wrapped = secondValueWrapped);
third && (third.wrapped = thirdValueWrapped); third && (third.wrapped = thirdValueWrapped);
fourth && (fourth.wrapped = fourthValueWrapped); fourth && (fourth.wrapped = fourthValueWrapped);
fifth && (fifth.wrapped = fifthValueWrapped);
if (isMessage && !messages) { if (isMessage && !messages) {
return [null, null, null]; return [null, null, null];
@ -462,10 +489,14 @@ function renderUsername(
return [fallbackText, <>{fallbackText}</>, fallbackText]; return [fallbackText, <>{fallbackText}</>, fallbackText];
} }
const prioritizeUsername = (new Set([first, second, third, fourth].filter(Boolean).map(pos => pos.name.toLowerCase())).size > 1); const prioritizeUsername = (new Set([first, second, third, fourth, fifth].filter(Boolean).map(pos => pos.name.toLowerCase())).size > 1);
if (removeDuplicates) { if (removeDuplicates) {
// Remove duplicates from back to front. Prioritize the earlier name, unless it's the username and there's more than one unique option, then prioritize it. // Remove duplicates from back to front. Prioritize the earlier name, unless it's the username and there's more than one unique option, then prioritize it.
fifth && fourth && fifth.name.toLowerCase() === fourth.name.toLowerCase() ? fifth.type === "user" && prioritizeUsername ? fourth = null : fifth = null : null;
fifth && third && fifth.name.toLowerCase() === third.name.toLowerCase() ? fifth.type === "user" && prioritizeUsername ? third = null : fifth = null : null;
fifth && second && fifth.name.toLowerCase() === second.name.toLowerCase() ? fifth.type === "user" && prioritizeUsername ? second = null : fifth = null : null;
fifth && first && fifth.name.toLowerCase() === first.name.toLowerCase() ? fifth.type === "user" && prioritizeUsername ? first = null : fifth = null : null;
fourth && third && fourth.name.toLowerCase() === third.name.toLowerCase() ? fourth.type === "user" && prioritizeUsername ? third = null : fourth = null : null; fourth && third && fourth.name.toLowerCase() === third.name.toLowerCase() ? fourth.type === "user" && prioritizeUsername ? third = null : fourth = null : null;
fourth && second && fourth.name.toLowerCase() === second.name.toLowerCase() ? fourth.type === "user" && prioritizeUsername ? second = null : fourth = null : null; fourth && second && fourth.name.toLowerCase() === second.name.toLowerCase() ? fourth.type === "user" && prioritizeUsername ? second = null : fourth = null : null;
fourth && first && fourth.name.toLowerCase() === first.name.toLowerCase() ? fourth.type === "user" && prioritizeUsername ? first = null : fourth = null : null; fourth && first && fourth.name.toLowerCase() === first.name.toLowerCase() ? fourth.type === "user" && prioritizeUsername ? first = null : fourth = null : null;
@ -474,11 +505,12 @@ function renderUsername(
second && first && second.name.toLowerCase() === first.name.toLowerCase() ? second.type === "user" && prioritizeUsername ? first = null : second = null : null; second && first && second.name.toLowerCase() === first.name.toLowerCase() ? second.type === "user" && prioritizeUsername ? first = null : second = null : null;
} }
const remainingNames = [first, second, third, fourth].filter(Boolean); const remainingNames = [first, second, third, fourth, fifth].filter(Boolean);
first = remainingNames.shift(); first = remainingNames.shift();
second = remainingNames.shift(); second = remainingNames.shift();
third = remainingNames.shift(); third = remainingNames.shift();
fourth = remainingNames.shift(); fourth = remainingNames.shift();
fifth = remainingNames.shift();
const useTopRoleStyle = isMention || isReactionsPopout; const useTopRoleStyle = isMention || isReactionsPopout;
const topRoleStyle = resolveColor(author, "Role", "", canUseGradient); const topRoleStyle = resolveColor(author, "Role", "", canUseGradient);
@ -500,7 +532,8 @@ function renderUsername(
const secondDataText = second && shouldAnimateSecondaryNames ? (second.prefix + second.name + second.suffix) : ""; const secondDataText = second && shouldAnimateSecondaryNames ? (second.prefix + second.name + second.suffix) : "";
const thirdDataText = third && shouldAnimateSecondaryNames ? (third.prefix + third.name + third.suffix) : ""; const thirdDataText = third && shouldAnimateSecondaryNames ? (third.prefix + third.name + third.suffix) : "";
const fourthDataText = fourth && shouldAnimateSecondaryNames ? (fourth.prefix + fourth.name + fourth.suffix) : ""; const fourthDataText = fourth && shouldAnimateSecondaryNames ? (fourth.prefix + fourth.name + fourth.suffix) : "";
const allDataText = [firstDataText, secondDataText, thirdDataText, fourthDataText].filter(Boolean).join(nameSeparator).trim(); const fifthDataText = fifth && shouldAnimateSecondaryNames ? (fifth.prefix + fifth.name + fifth.suffix) : "";
const allDataText = [firstDataText, secondDataText, thirdDataText, fourthDataText, fifthDataText].filter(Boolean).join(nameSeparator).trim();
// Only mentions and reactions popouts should patch in the gradient glow or else a double glow will appear on messages. // Only mentions and reactions popouts should patch in the gradient glow or else a double glow will appear on messages.
const hoveringClass = (isHovering ? " show-me-your-name-gradient-hovered" : ""); const hoveringClass = (isHovering ? " show-me-your-name-gradient-hovered" : "");
@ -512,10 +545,12 @@ function renderUsername(
const secondGroupClasses = "show-me-your-name-name-group show-me-your-name-second-name-group"; const secondGroupClasses = "show-me-your-name-name-group show-me-your-name-second-name-group";
const thirdGroupClasses = "show-me-your-name-name-group show-me-your-name-third-name-group"; const thirdGroupClasses = "show-me-your-name-name-group show-me-your-name-third-name-group";
const fourthGroupClasses = "show-me-your-name-name-group show-me-your-name-fourth-name-group"; const fourthGroupClasses = "show-me-your-name-name-group show-me-your-name-fourth-name-group";
const fifthGroupClasses = "show-me-your-name-name-group show-me-your-name-fifth-name-group";
const firstNameClasses = "show-me-your-name-name show-me-your-name-first-name"; const firstNameClasses = "show-me-your-name-name show-me-your-name-first-name";
const secondNameClasses = "show-me-your-name-name show-me-your-name-second-name"; const secondNameClasses = "show-me-your-name-name show-me-your-name-second-name";
const thirdNameClasses = "show-me-your-name-name show-me-your-name-third-name"; const thirdNameClasses = "show-me-your-name-name show-me-your-name-third-name";
const fourthNameClasses = "show-me-your-name-name show-me-your-name-fourth-name"; const fourthNameClasses = "show-me-your-name-name show-me-your-name-fourth-name";
const fifthNameClasses = "show-me-your-name-name show-me-your-name-fifth-name";
const prefixClasses = "show-me-your-name-affix show-me-your-name-prefix"; const prefixClasses = "show-me-your-name-affix show-me-your-name-prefix";
const suffixClasses = "show-me-your-name-affix show-me-your-name-suffix"; const suffixClasses = "show-me-your-name-affix show-me-your-name-suffix";
@ -549,85 +584,39 @@ function renderUsername(
{first.wrapped}</span> {first.wrapped}</span>
</span> </span>
)} )}
{second && ( {[
{ name: second, dataText: secondDataText, groupClass: secondGroupClasses, nameClass: secondNameClasses, position: "second" },
{ name: third, dataText: thirdDataText, groupClass: thirdGroupClasses, nameClass: thirdNameClasses, position: "third" },
{ name: fourth, dataText: fourthDataText, groupClass: fourthGroupClasses, nameClass: fourthNameClasses, position: "fourth" },
{ name: fifth, dataText: fifthDataText, groupClass: fifthGroupClasses, nameClass: fifthNameClasses, position: "fifth" }
].map(({ name, dataText, groupClass, nameClass, position }) => name && (
<span <span
className={(shouldGradientGlow && shouldAnimateSecondaryNames ? (gradientClasses + " " + secondGroupClasses) : secondGroupClasses)} key={position}
data-text={shouldGradientGlow && secondDataText ? secondDataText : undefined} className={(shouldGradientGlow && shouldAnimateSecondaryNames ? (gradientClasses + " " + groupClass) : groupClass)}
style={(shouldGradientGlow && shouldAnimateSecondaryNames ? second.style.gradient.animated : undefined) as React.CSSProperties} data-text={shouldGradientGlow && dataText ? dataText : undefined}
style={(shouldGradientGlow && shouldAnimateSecondaryNames ? name.style.gradient.animated : undefined) as React.CSSProperties}
> >
<span style={affixColor as React.CSSProperties} className={prefixClasses}> <span style={affixColor as React.CSSProperties} className={prefixClasses}>
<span>{nameSeparator}</span> <span>{nameSeparator}</span>
{second.prefix}</span> {name.prefix}</span>
<span <span
// On non-primary names, allow disabling the gradients completely, or just their animation & glow. // On non-primary names, allow disabling the gradients completely, or just their animation & glow.
className={secondNameClasses} className={nameClass}
style={{ style={{
...(ignoreFonts ? { "font-family": "var(--font-primary)", "letter-spacing": "normal" } : {}), ...(ignoreFonts ? { "font-family": "var(--font-primary)", "letter-spacing": "normal" } : {}),
...(ignoreGradients ...(ignoreGradients
? second.style.normal.adjusted ? name.style.normal.adjusted
: shouldAnimateGradients && shouldAnimateSecondaryNames && second.style.gradient : shouldAnimateGradients && shouldAnimateSecondaryNames && name.style.gradient
? second.style.gradient.animated ? name.style.gradient.animated
: second.style.gradient : name.style.gradient
? second.style.gradient.static.original ? name.style.gradient.static.original
: second.style.normal.adjusted) : name.style.normal.adjusted)
}}> }}>
{second.wrapped}</span> {name.wrapped}</span>
<span style={affixColor as React.CSSProperties} className={suffixClasses}> <span style={affixColor as React.CSSProperties} className={suffixClasses}>
{second.suffix}</span> {name.suffix}</span>
</span> </span>
)} ))}
{third && (
<span
className={(shouldGradientGlow && shouldAnimateSecondaryNames ? (gradientClasses + " " + thirdGroupClasses) : thirdGroupClasses)}
data-text={shouldGradientGlow && thirdDataText ? thirdDataText : undefined}
style={(shouldGradientGlow && shouldAnimateSecondaryNames ? third.style.gradient.animated : undefined) as React.CSSProperties}
>
<span style={affixColor as React.CSSProperties} className={prefixClasses}>
<span>{nameSeparator}</span>
{third.prefix}</span>
<span
className={thirdNameClasses}
style={{
...(ignoreFonts ? { "font-family": "var(--font-primary)", "letter-spacing": "normal" } : {}),
...(ignoreGradients
? third.style.normal.adjusted
: shouldAnimateGradients && shouldAnimateSecondaryNames && third.style.gradient
? third.style.gradient.animated
: third.style.gradient
? third.style.gradient.static.original
: third.style.normal.adjusted)
}}>
{third.wrapped}</span>
<span style={affixColor as React.CSSProperties} className={suffixClasses}>
{third.suffix}</span>
</span>
)}
{fourth && (
<span
className={(shouldGradientGlow && shouldAnimateSecondaryNames ? (gradientClasses + " " + fourthGroupClasses) : fourthGroupClasses)}
data-text={shouldGradientGlow && fourthDataText ? fourthDataText : undefined}
style={(shouldGradientGlow && shouldAnimateSecondaryNames ? fourth.style.gradient.animated : undefined) as React.CSSProperties}
>
<span style={affixColor as React.CSSProperties} className={prefixClasses}>
<span>{nameSeparator}</span>
{fourth.prefix}</span>
<span
className={fourthNameClasses}
style={{
...(ignoreFonts ? { "font-family": "var(--font-primary)", "letter-spacing": "normal" } : {}),
...(ignoreGradients
? fourth.style.normal.adjusted
: shouldAnimateGradients && shouldAnimateSecondaryNames && fourth.style.gradient
? fourth.style.gradient.animated
: fourth.style.gradient
? fourth.style.gradient.static.original
: fourth.style.normal.adjusted)
}}>
{fourth.wrapped}</span>
<span style={affixColor as React.CSSProperties} className={suffixClasses}>
{fourth.suffix}</span>
</span>
)}
</span> </span>
); );
@ -688,6 +677,98 @@ function removeHoveringReactionPopout(id: string) {
settings.store.triggerNameRerender = !settings.store.triggerNameRerender; settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
} }
function CustomNicknameModal({ modalProps, user }: { modalProps: ModalProps; user: User; }) {
const [value, setValue] = useState(customNicknames[user.id] ?? "");
return (
<ModalRoot {...modalProps}>
<ModalHeader>
<Heading tag="h1" style={{ flexGrow: 1, margin: 0 }}>
{customNicknames[user.id] ? "Change SMYN Nickname" : "Add SMYN Nickname"}
</Heading>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent>
<Heading tag="h3" style={{ marginBottom: 8, fontSize: "16px", fontWeight: "400", lineHeight: "1.25", color: "var(--text-secondary)" }}>
{"Set a custom SMYN nickname for this user. Make use of it by specifying {custom} in the SMYN template settings."}
</Heading>
<div style={{ paddingTop: "10px", flexGrow: 0 }}></div>
<Heading tag="h3" style={{ marginBottom: 8, fontSize: "14px", fontWeight: 600 }}>
SMYN Nickname
</Heading>
<TextInput
value={value}
maxLength={32}
onChange={setValue}
placeholder={user.globalName ?? user.username}
style={{ width: "100%" }}
/>
<TextButton
className="show-me-your-name-reset-button"
onClick={async () => {
setValue("");
delete customNicknames[user.id];
await DataStore.set("SMYNCustomNicknames", customNicknames);
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
}}
>
Reset SMYN Nickname
</TextButton>
<div style={{ paddingTop: "10px", flexGrow: 0 }}></div>
</ModalContent>
<ModalFooter className="show-me-your-name-modal-footer-container">
<Button
variant="primary"
onClick={async () => {
const trimmed = value.trim().slice(0, 32).trim();
if (trimmed) {
customNicknames[user.id] = trimmed;
} else {
delete customNicknames[user.id];
}
await DataStore.set("SMYNCustomNicknames", customNicknames);
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
modalProps.onClose();
}}
>
Save
</Button>
<Button
variant="secondary"
style={{ marginRight: "8px" }}
onClick={modalProps.onClose}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
);
}
const userContextPatch: NavContextMenuPatchCallback = (children, { user }) => {
if (!user) return;
const currentUser = UserStore.getCurrentUser();
if (!currentUser || user.id === currentUser.id) return;
const group = findGroupChildrenByChildId("user-profile", children);
!group && children.push(<Menu.MenuSeparator />);
(group || children).push(
<Menu.MenuItem
id="smyn-custom-nickname"
label={customNicknames[user.id] ? "Change SMYN Nickname" : "Add SMYN Nickname"}
action={() => openModal(props => (
<ErrorBoundary>
<CustomNicknameModal modalProps={props} user={user} />
</ErrorBoundary>
))}
/>
);
};
const settings = definePluginSettings({ const settings = definePluginSettings({
messages: { messages: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
@ -747,17 +828,17 @@ const settings = definePluginSettings({
ignoreFonts: { ignoreFonts: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: false, default: false,
description: "For the second, third, and fourth names, use GG SANS regardless of the user's custom font.", description: "For the non-primary names, use Discord's default fonts regardless of the user's custom nitro font.",
}, },
ignoreGradients: { ignoreGradients: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: true, default: true,
description: "For the second, third, and fourth names, if the role has a gradient, ignore it in favor of the value below.", description: "For the non-primary names, if the role has a gradient, ignore it in favor of the color set below.",
}, },
animateGradients: { animateGradients: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: false, default: false,
description: "For the second, third, and fourth names, if the role has a gradient, animate it. This is disabled by \"Ignore Gradients\" and reduced motion.", description: "For the non-primary names, if the role has a gradient, animate it. This is disabled by \"Ignore Gradients\" and reduced motion.",
}, },
nameSeparator: { nameSeparator: {
type: OptionType.STRING, type: OptionType.STRING,
@ -766,10 +847,16 @@ const settings = definePluginSettings({
}, },
includedNames: { includedNames: {
type: OptionType.STRING, type: OptionType.STRING,
description: "The order to display usernames, display names, nicknames, and friend names. Use the following placeholders: {user}, {display}, {nick}, {friend}. You can provide multiple name options to use as fallbacks if one is unavailable by separating them with commas as such: {friend, nick, display}. You can have up to three prefixes and three suffixes per name.", description: "The order to display usernames, display names, nicknames, friend names, and custom names. Use the following placeholders: {user}, {display}, {nick}, {friend}, {custom}. You can provide multiple name options to use as fallbacks if one is unavailable by separating them with commas as such: {custom, friend, nick}. You can have up to three prefixes and three suffixes per name.",
default: "{friend, nick} [{display}] (@{user})", default: "{custom, friend, nick} [{display}] (@{user})",
isValid: validTemplate, isValid: validTemplate,
}, },
customNameColor: {
type: OptionType.STRING,
description: "The color to use for the custom name you assigned a user if it's not the first displayed. Leave blank for default. Accepts hex(a), rgb(a), or hsl(a) input. Use \"Role\" to follow the user's top role color. Use \"Role+-#\" to adjust the brightness by that percentage (ex: \"Role+15\")",
default: "Role-25",
isValid: validColor,
},
friendNameColor: { friendNameColor: {
type: OptionType.STRING, type: OptionType.STRING,
description: "The color to use for a friend's nickname if it's not the first displayed. Leave blank for default. Accepts hex(a), rgb(a), or hsl(a) input. Use \"Role\" to follow the user's top role color. Use \"Role+-#\" to adjust the brightness by that percentage (ex: \"Role+15\")", description: "The color to use for a friend's nickname if it's not the first displayed. Leave blank for default. Accepts hex(a), rgb(a), or hsl(a) input. Use \"Role\" to follow the user's top role color. Use \"Role+-#\" to adjust the brightness by that percentage (ex: \"Role+15\")",
@ -804,8 +891,9 @@ const settings = definePluginSettings({
export default definePlugin({ export default definePlugin({
name: "ShowMeYourName", name: "ShowMeYourName",
description: "Display any permutation of nicknames, display names, and usernames in chat.", description: "Display any permutation of custom nicknames, friend nicknames, server nicknames, display names, and usernames in chat.",
authors: [Devs.Rini, Devs.TheKodeToad, EquicordDevs.Etorix, Devs.sadan], authors: [Devs.Rini, Devs.TheKodeToad, EquicordDevs.Etorix, Devs.sadan, EquicordDevs.Prism],
tags: ["SMYN", "Nicknames", "Custom Nicknames",],
isModified: true, isModified: true,
settings, settings,
@ -943,6 +1031,15 @@ export default definePlugin({
} }
], ],
async start() {
const data = await DataStore.get<CustomNicknameData>("SMYNCustomNicknames");
customNicknames = data ?? {};
},
contextMenus: {
"user-context": userContextPatch
},
flux: { flux: {
RELATIONSHIP_UPDATE(data) { RELATIONSHIP_UPDATE(data) {
// Allows rerendering when changing friend names. // Allows rerendering when changing friend names.

View file

@ -38,3 +38,13 @@
opacity: .7; opacity: .7;
transition: opacity .1s ease-in-out transition: opacity .1s ease-in-out
} }
.show-me-your-name-modal-footer-container button {
width: 100%;
}
.show-me-your-name-reset-button {
margin-top: 8px;
font-size: 14px;
font-weight: 500;
}

View file

@ -201,6 +201,14 @@ const SilentTypingChatToggle: ChatBarButtonFactory = ({ channel, type }) => {
); );
}; };
function SilentTypingChatIcon() {
return (
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style={{ scale: "1.2" }}>
<path fill="currentColor" d="M18.333 15.556H1.667a1.667 1.667 0 0 1 -1.667 -1.667v-10a1.667 1.667 0 0 1 1.667 -1.667h16.667a1.667 1.667 0 0 1 1.667 1.667v10a1.667 1.667 0 0 1 -1.667 1.667M4.444 6.25V4.861a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333V8.194a0.417 0.417 0 0 0 -0.417 -0.417H4.306a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417H7.639a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m10 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h8.056a0.417 0.417 0 0 0 0.417 -0.417m3.333 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417" transform="translate(2, 3)" />
</svg>
);
}
function getEffectiveList(): string[] { function getEffectiveList(): string[] {
if (settings.store.defaultHidden) { if (settings.store.defaultHidden) {
if (!settings.store.disabledLocations) { if (!settings.store.disabledLocations) {
@ -288,7 +296,6 @@ export default definePlugin({
name: "SilentTyping", name: "SilentTyping",
authors: [Devs.Ven, Devs.Rini, Devs.ImBanana, EquicordDevs.Etorix], authors: [Devs.Ven, Devs.Rini, Devs.ImBanana, EquicordDevs.Etorix],
description: "Hide your typing indicator from chat.", description: "Hide your typing indicator from chat.",
dependencies: ["ChatInputButtonAPI"],
isModified: true, isModified: true,
settings, settings,
@ -298,6 +305,10 @@ export default definePlugin({
contextMenus: { contextMenus: {
"textarea-context": ChatBarContextCheckbox "textarea-context": ChatBarContextCheckbox
}, },
chatBarButton: {
icon: SilentTypingChatIcon,
render: SilentTypingChatToggle
},
patches: [ patches: [
{ {
@ -425,6 +436,4 @@ export default definePlugin({
if (checkEnabled(channelId)) return; if (checkEnabled(channelId)) return;
FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId }); FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId });
}, },
renderChatBarButton: SilentTypingChatToggle,
}); });

View file

@ -18,9 +18,6 @@
import "./style.css"; import "./style.css";
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs, EquicordDevs } from "@utils/constants"; import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -52,9 +49,22 @@ export default definePlugin({
name: "UserVoiceShow", name: "UserVoiceShow",
description: "Shows an indicator when a user is in a Voice Channel", description: "Shows an indicator when a user is in a Voice Channel",
authors: [Devs.Nuckyz, Devs.LordElias, EquicordDevs.omaw], authors: [Devs.Nuckyz, Devs.LordElias, EquicordDevs.omaw],
dependencies: ["NicknameIconsAPI", "MemberListDecoratorsAPI", "MessageDecorationsAPI"],
settings, settings,
renderNicknameIcon({ userId }) {
if (!settings.store.showInUserProfileModal) return null;
return (
<VoiceChannelIndicator userId={userId} isProfile />
);
},
renderMemberListDecorator({ user }) {
if (!settings.store.showInMemberList) return null;
return user == null ? null : <VoiceChannelIndicator userId={user.id} />;
},
renderMessageDecoration({ message }) {
if (!settings.store.showInMessages) return null;
return message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />;
},
patches: [ patches: [
// Friends List // Friends List
{ {
@ -67,23 +77,5 @@ export default definePlugin({
} }
], ],
start() {
if (settings.store.showInUserProfileModal) {
addNicknameIcon("UserVoiceShow", ({ userId }) => <VoiceChannelIndicator userId={userId} isProfile />);
}
if (settings.store.showInMemberList) {
addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
}
if (settings.store.showInMessages) {
addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />);
}
},
stop() {
removeNicknameIcon("UserVoiceShow");
removeMemberListDecorator("UserVoiceShow");
removeMessageDecoration("UserVoiceShow");
},
VoiceChannelIndicator VoiceChannelIndicator
}); });

View file

@ -632,7 +632,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({
alfred: { alfred: {
name: "alfred", name: "alfred",
id: 1038466644353232967n id: 1038466644353232967n
} },
} satisfies Record<string, Dev>); } satisfies Record<string, Dev>);
export const EquicordDevs = Object.freeze({ export const EquicordDevs = Object.freeze({
@ -1205,7 +1205,7 @@ export const EquicordDevs = Object.freeze({
name: "neoarz", name: "neoarz",
id: 1015372540937502851n id: 1015372540937502851n
}, },
KamiRu:{ KamiRu: {
name: "KamiRu", name: "KamiRu",
id: 819191621676695563n id: 819191621676695563n
}, },

View file

@ -24,9 +24,11 @@ import { Patch, PatchReplacement, ReplaceFn } from "./types";
// @ts-expect-error "RegExp.escape" is very new and not yet in DOM types // @ts-expect-error "RegExp.escape" is very new and not yet in DOM types
const escapeRegex = RegExp.escape || ((s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")); const escapeRegex = RegExp.escape || ((s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
export function getReplacement(isString: boolean, hashed: string) { export function getReplacement(isString: boolean, hashed: string, hash: boolean = false) {
const hasSpecialChars = !Number.isNaN(Number(hashed[0])) || hashed.includes("+") || hashed.includes("/"); const hasSpecialChars = !Number.isNaN(Number(hashed[0])) || hashed.includes("+") || hashed.includes("/");
if (hash) return hashed;
if (hasSpecialChars) { if (hasSpecialChars) {
return isString return isString
? `["${hashed}"]` ? `["${hashed}"]`
@ -48,7 +50,7 @@ function canonicalizeMatchCompatString(str: string) {
const re = /#{intl::([\w$+/]*)(?:::(\w+))?}/g; const re = /#{intl::([\w$+/]*)(?:::(\w+))?}/g;
for (const match of str.matchAll(re)) { for (const match of str.matchAll(re)) {
result += escapeRegex(str.slice(lastIndex, match.index)); result += escapeRegex(str.slice(lastIndex, match.index));
result += getCompatReplacement(match[1]); result += match[2] === "raw" ? getReplacement(false, match[1]) : match[2] === "hash" ? getReplacement(false, runtimeHashMessageKey(match[1]), true) : getCompatReplacement(match[1]);
lastIndex = (match.index ?? 0) + match[0].length; lastIndex = (match.index ?? 0) + match[0].length;
} }
result += escapeRegex(str.slice(lastIndex)); result += escapeRegex(str.slice(lastIndex));
@ -63,6 +65,7 @@ export function canonicalizeMatch<T extends RegExp | string>(match: T): T extend
let partialCanon = typeof match === "string" ? match : match.source; let partialCanon = typeof match === "string" ? match : match.source;
partialCanon = partialCanon.replaceAll(/#{intl::([\w$+/]*)(?:::(\w+))?}/g, (_, key, modifier) => { partialCanon = partialCanon.replaceAll(/#{intl::([\w$+/]*)(?:::(\w+))?}/g, (_, key, modifier) => {
if (modifier === "raw") return getReplacement(false, key); if (modifier === "raw") return getReplacement(false, key);
if (modifier === "hash") return getReplacement(false, runtimeHashMessageKey(key), true);
return getCompatReplacement(key); return getCompatReplacement(key);
}); });

View file

@ -16,9 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { AudioProcessor } from "@api/AudioPlayer";
import type { ProfileBadge } from "@api/Badges"; import type { ProfileBadge } from "@api/Badges";
import type { ChatBarButtonData, ChatBarButtonFactory } from "@api/ChatButtons"; import type { ChatBarButtonData, ChatBarButtonFactory } from "@api/ChatButtons";
import type { NavContextMenuPatchCallback } from "@api/ContextMenu"; import type { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { HeaderBarButtonFactory } from "@api/HeaderBar";
import type { MemberListDecoratorFactory } from "@api/MemberListDecorators"; import type { MemberListDecoratorFactory } from "@api/MemberListDecorators";
import type { MessageAccessoryFactory } from "@api/MessageAccessories"; import type { MessageAccessoryFactory } from "@api/MessageAccessories";
import type { MessageDecorationFactory } from "@api/MessageDecorations"; import type { MessageDecorationFactory } from "@api/MessageDecorations";
@ -195,7 +197,11 @@ export interface PluginDef {
renderMessageDecoration?: MessageDecorationFactory; renderMessageDecoration?: MessageDecorationFactory;
renderMemberListDecorator?: MemberListDecoratorFactory; renderMemberListDecorator?: MemberListDecoratorFactory;
// Custom
renderNicknameIcon?: NicknameIconFactory; renderNicknameIcon?: NicknameIconFactory;
renderHeaderBarButton?: HeaderBarButtonFactory;
audioProcessor?: AudioProcessor;
// TODO: Remove eventually // TODO: Remove eventually
/** /**

View file

@ -18,6 +18,7 @@
import type * as t from "@vencord/discord-types"; import type * as t from "@vencord/discord-types";
import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "@webpack"; import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "@webpack";
import type * as TSPattern from "ts-pattern";
export let FluxDispatcher: t.FluxDispatcher; export let FluxDispatcher: t.FluxDispatcher;
waitFor(["dispatch", "subscribe"], m => { waitFor(["dispatch", "subscribe"], m => {
@ -51,7 +52,7 @@ export const useDrag = findByCodeLazy("useDrag::spec.begin was deprecated");
// you cant make a better finder i love that they remove display names sm // you cant make a better finder i love that they remove display names sm
export const useDrop = findByCodeLazy(".options);return", ".collect,"); export const useDrop = findByCodeLazy(".options);return", ".collect,");
export const { match, P }: Pick<typeof import("ts-pattern"), "match" | "P"> = mapMangledModuleLazy("@ts-pattern/matcher", { export const { match, P }: Pick<typeof TSPattern, "match" | "P"> = mapMangledModuleLazy("@ts-pattern/matcher", {
match: filters.byCode("return new"), match: filters.byCode("return new"),
P: filters.byProps("when") P: filters.byProps("when")
}); });