Merge branch 'main' of https://github.com/Vendicated/Vencord
This commit is contained in:
commit
4029ac976b
100 changed files with 1622 additions and 1566 deletions
|
@ -105,7 +105,13 @@ export default tseslint.config(
|
||||||
"no-invalid-regexp": "error",
|
"no-invalid-regexp": "error",
|
||||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||||
"no-duplicate-imports": "error",
|
"no-duplicate-imports": "error",
|
||||||
"dot-notation": "error",
|
"@typescript-eslint/dot-notation": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allowPrivateClassPropertyAccess": true,
|
||||||
|
"allowProtectedClassPropertyAccess": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"no-useless-escape": [
|
"no-useless-escape": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.11.2",
|
"version": "1.11.3",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
@ -57,7 +57,7 @@ const Badges = new Set<ProfileBadge>();
|
||||||
* Register a new badge with the Badges API
|
* Register a new badge with the Badges API
|
||||||
* @param badge The badge to register
|
* @param badge The badge to register
|
||||||
*/
|
*/
|
||||||
export function addBadge(badge: ProfileBadge) {
|
export function addProfileBadge(badge: ProfileBadge) {
|
||||||
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
||||||
Badges.add(badge);
|
Badges.add(badge);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ export function addBadge(badge: ProfileBadge) {
|
||||||
* Unregister a badge from the Badges API
|
* Unregister a badge from the Badges API
|
||||||
* @param badge The badge to remove
|
* @param badge The badge to remove
|
||||||
*/
|
*/
|
||||||
export function removeBadge(badge: ProfileBadge) {
|
export function removeProfileBadge(badge: ProfileBadge) {
|
||||||
return Badges.delete(badge);
|
return Badges.delete(badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,20 +100,3 @@ export interface BadgeUserArgs {
|
||||||
userId: string;
|
userId: string;
|
||||||
guildId: string;
|
guildId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConnectedAccount {
|
|
||||||
type: string;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
verified: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Profile {
|
|
||||||
connectedAccounts: ConnectedAccount[];
|
|
||||||
premiumType: number;
|
|
||||||
premiumSince: string;
|
|
||||||
premiumGuildSince?: any;
|
|
||||||
lastFetched: number;
|
|
||||||
profileFetchFailed: boolean;
|
|
||||||
application?: any;
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import "./ChatButton.css";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { waitFor } from "@webpack";
|
import { waitFor } from "@webpack";
|
||||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||||
import { Channel } from "discord-types/general";
|
import { Channel } from "discord-types/general";
|
||||||
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
|
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
|
||||||
|
|
||||||
|
@ -74,9 +74,9 @@ export interface ChatBarProps {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
||||||
|
|
||||||
const buttonFactories = new Map<string, ChatBarButton>();
|
const buttonFactories = new Map<string, ChatBarButtonFactory>();
|
||||||
const logger = new Logger("ChatButtons");
|
const logger = new Logger("ChatButtons");
|
||||||
|
|
||||||
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||||
|
@ -91,7 +91,7 @@ export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button);
|
export const addChatBarButton = (id: string, button: ChatBarButtonFactory) => buttonFactories.set(id, button);
|
||||||
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
||||||
|
|
||||||
export interface ChatBarButtonProps {
|
export interface ChatBarButtonProps {
|
||||||
|
@ -110,7 +110,7 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
||||||
<Button
|
<Button
|
||||||
aria-label={props.tooltip}
|
aria-label={props.tooltip}
|
||||||
size=""
|
size=""
|
||||||
look={ButtonLooks.BLANK}
|
look={Button.Looks.BLANK}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
||||||
|
|
|
@ -122,7 +122,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContextMenuProps {
|
interface ContextMenuProps {
|
||||||
contextMenuApiArguments?: Array<any>;
|
contextMenuAPIArguments?: Array<any>;
|
||||||
navId: string;
|
navId: string;
|
||||||
children: Array<ReactElement<any> | null>;
|
children: Array<ReactElement<any> | null>;
|
||||||
"aria-label": string;
|
"aria-label": string;
|
||||||
|
@ -136,7 +136,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
children: cloneMenuChildren(props.children),
|
children: cloneMenuChildren(props.children),
|
||||||
};
|
};
|
||||||
|
|
||||||
props.contextMenuApiArguments ??= [];
|
props.contextMenuAPIArguments ??= [];
|
||||||
const contextMenuPatches = navPatches.get(props.navId);
|
const contextMenuPatches = navPatches.get(props.navId);
|
||||||
|
|
||||||
if (!Array.isArray(props.children)) props.children = [props.children];
|
if (!Array.isArray(props.children)) props.children = [props.children];
|
||||||
|
@ -144,7 +144,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
if (contextMenuPatches) {
|
if (contextMenuPatches) {
|
||||||
for (const patch of contextMenuPatches) {
|
for (const patch of contextMenuPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.children, ...props.contextMenuApiArguments);
|
patch(props.children, ...props.contextMenuAPIArguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
|
|
||||||
for (const patch of globalPatches) {
|
for (const patch of globalPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
patch(props.navId, props.children, ...props.contextMenuAPIArguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error("Global patch errored,", err);
|
ContextMenuLogger.error("Global patch errored,", err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +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 ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Channel, User } from "discord-types/general/index.js";
|
import { Channel, User } from "discord-types/general/index.js";
|
||||||
import { JSX } from "react";
|
import { JSX } from "react";
|
||||||
|
|
||||||
|
@ -39,27 +40,32 @@ interface DecoratorProps {
|
||||||
user: User;
|
user: User;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export type Decorator = (props: DecoratorProps) => JSX.Element | null;
|
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
|
||||||
type OnlyIn = "guilds" | "dms";
|
type OnlyIn = "guilds" | "dms";
|
||||||
|
|
||||||
export const decorators = new Map<string, { decorator: Decorator, onlyIn?: OnlyIn; }>();
|
export const decorators = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>();
|
||||||
|
|
||||||
export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) {
|
export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) {
|
||||||
decorators.set(identifier, { decorator, onlyIn });
|
decorators.set(identifier, { render, onlyIn });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeDecorator(identifier: string) {
|
export function removeMemberListDecorator(identifier: string) {
|
||||||
decorators.delete(identifier);
|
decorators.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
||||||
const isInGuild = !!(props.guildId);
|
const isInGuild = !!(props.guildId);
|
||||||
return Array.from(decorators.values(), decoratorObj => {
|
return Array.from(
|
||||||
const { decorator, onlyIn } = decoratorObj;
|
decorators.entries(),
|
||||||
// this can most likely be done cleaner
|
([key, { render: Decorator, onlyIn }]) => {
|
||||||
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
|
||||||
return decorator(props);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
});
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary noop key={key} message={`Failed to render ${key} Member List Decorator`}>
|
||||||
|
<Decorator {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -16,28 +16,29 @@
|
||||||
* 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 { JSX } from "react";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { JSX, ReactNode } from "react";
|
||||||
|
|
||||||
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>;
|
export type MessageAccessoryFactory = (props: Record<string, any>) => ReactNode;
|
||||||
export type Accessory = {
|
export type MessageAccessory = {
|
||||||
callback: AccessoryCallback;
|
render: MessageAccessoryFactory;
|
||||||
position?: number;
|
position?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accessories = new Map<String, Accessory>();
|
export const accessories = new Map<string, MessageAccessory>();
|
||||||
|
|
||||||
export function addAccessory(
|
export function addMessageAccessory(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
callback: AccessoryCallback,
|
render: MessageAccessoryFactory,
|
||||||
position?: number
|
position?: number
|
||||||
) {
|
) {
|
||||||
accessories.set(identifier, {
|
accessories.set(identifier, {
|
||||||
callback,
|
render,
|
||||||
position,
|
position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeAccessory(identifier: string) {
|
export function removeMessageAccessory(identifier: string) {
|
||||||
accessories.delete(identifier);
|
accessories.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,15 +46,12 @@ export function _modifyAccessories(
|
||||||
elements: JSX.Element[],
|
elements: JSX.Element[],
|
||||||
props: Record<string, any>
|
props: Record<string, any>
|
||||||
) {
|
) {
|
||||||
for (const accessory of accessories.values()) {
|
for (const [key, accessory] of accessories.entries()) {
|
||||||
let accessories = accessory.callback(props);
|
const res = (
|
||||||
if (accessories == null)
|
<ErrorBoundary message={`Failed to render ${key} Message Accessory`} key={key}>
|
||||||
continue;
|
<accessory.render {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
if (!Array.isArray(accessories))
|
);
|
||||||
accessories = [accessories];
|
|
||||||
else if (accessories.length === 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
elements.splice(
|
elements.splice(
|
||||||
accessory.position != null
|
accessory.position != null
|
||||||
|
@ -62,7 +60,7 @@ export function _modifyAccessories(
|
||||||
: accessory.position
|
: accessory.position
|
||||||
: elements.length,
|
: elements.length,
|
||||||
0,
|
0,
|
||||||
...accessories.filter(e => e != null) as JSX.Element[]
|
res
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +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 ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Channel, Message } from "discord-types/general/index.js";
|
import { Channel, Message } from "discord-types/general/index.js";
|
||||||
import { JSX } from "react";
|
import { JSX } from "react";
|
||||||
|
|
||||||
interface DecorationProps {
|
export interface MessageDecorationProps {
|
||||||
author: {
|
author: {
|
||||||
/**
|
/**
|
||||||
* Will be username if the user has no nickname
|
* Will be username if the user has no nickname
|
||||||
|
@ -45,20 +46,25 @@ interface DecorationProps {
|
||||||
message: Message;
|
message: Message;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export type Decoration = (props: DecorationProps) => JSX.Element | null;
|
export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null;
|
||||||
|
|
||||||
export const decorations = new Map<string, Decoration>();
|
export const decorations = new Map<string, MessageDecorationFactory>();
|
||||||
|
|
||||||
export function addDecoration(identifier: string, decoration: Decoration) {
|
export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) {
|
||||||
decorations.set(identifier, decoration);
|
decorations.set(identifier, decoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeDecoration(identifier: string) {
|
export function removeMessageDecoration(identifier: string) {
|
||||||
decorations.delete(identifier);
|
decorations.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] {
|
export function __addDecorationsToMessage(props: MessageDecorationProps): (JSX.Element | null)[] {
|
||||||
return [...decorations.values()].map(decoration => {
|
return Array.from(
|
||||||
return decoration(props);
|
decorations.entries(),
|
||||||
});
|
([key, Decoration]) => (
|
||||||
|
<ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}>
|
||||||
|
<Decoration {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -73,11 +73,11 @@ export interface MessageExtra {
|
||||||
openWarningPopout: (props: any) => any;
|
openWarningPopout: (props: any) => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
||||||
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
||||||
|
|
||||||
const sendListeners = new Set<SendListener>();
|
const sendListeners = new Set<MessageSendListener>();
|
||||||
const editListeners = new Set<EditListener>();
|
const editListeners = new Set<MessageEditListener>();
|
||||||
|
|
||||||
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
||||||
extra.replyOptions = replyOptions;
|
extra.replyOptions = replyOptions;
|
||||||
|
@ -111,29 +111,29 @@ export async function _handlePreEdit(channelId: string, messageId: string, messa
|
||||||
/**
|
/**
|
||||||
* Note: This event fires off before a message is sent, allowing you to edit the message.
|
* Note: This event fires off before a message is sent, allowing you to edit the message.
|
||||||
*/
|
*/
|
||||||
export function addPreSendListener(listener: SendListener) {
|
export function addMessagePreSendListener(listener: MessageSendListener) {
|
||||||
sendListeners.add(listener);
|
sendListeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
|
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
|
||||||
*/
|
*/
|
||||||
export function addPreEditListener(listener: EditListener) {
|
export function addMessagePreEditListener(listener: MessageEditListener) {
|
||||||
editListeners.add(listener);
|
editListeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
export function removePreSendListener(listener: SendListener) {
|
export function removeMessagePreSendListener(listener: MessageSendListener) {
|
||||||
return sendListeners.delete(listener);
|
return sendListeners.delete(listener);
|
||||||
}
|
}
|
||||||
export function removePreEditListener(listener: EditListener) {
|
export function removeMessagePreEditListener(listener: MessageEditListener) {
|
||||||
return editListeners.delete(listener);
|
return editListeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Message clicks
|
// Message clicks
|
||||||
type ClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
export type MessageClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
||||||
|
|
||||||
const listeners = new Set<ClickListener>();
|
const listeners = new Set<MessageClickListener>();
|
||||||
|
|
||||||
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
|
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
|
||||||
// message object may be outdated, so (try to) fetch latest one
|
// message object may be outdated, so (try to) fetch latest one
|
||||||
|
@ -147,11 +147,11 @@ export function _handleClick(message: Message, channel: Channel, event: MouseEve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addClickListener(listener: ClickListener) {
|
export function addMessageClickListener(listener: MessageClickListener) {
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeClickListener(listener: ClickListener) {
|
export function removeMessageClickListener(listener: MessageClickListener) {
|
||||||
return listeners.delete(listener);
|
return listeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import type { ComponentType, MouseEventHandler } from "react";
|
||||||
|
|
||||||
const logger = new Logger("MessagePopover");
|
const logger = new Logger("MessagePopover");
|
||||||
|
|
||||||
export interface ButtonItem {
|
export interface MessagePopoverButtonItem {
|
||||||
key?: string,
|
key?: string,
|
||||||
label: string,
|
label: string,
|
||||||
icon: ComponentType<any>,
|
icon: ComponentType<any>,
|
||||||
|
@ -33,23 +33,23 @@ export interface ButtonItem {
|
||||||
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type getButtonItem = (message: Message) => ButtonItem | null;
|
export type MessagePopoverButtonFactory = (message: Message) => MessagePopoverButtonItem | null;
|
||||||
|
|
||||||
export const buttons = new Map<string, getButtonItem>();
|
export const buttons = new Map<string, MessagePopoverButtonFactory>();
|
||||||
|
|
||||||
export function addButton(
|
export function addMessagePopoverButton(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
item: getButtonItem,
|
item: MessagePopoverButtonFactory,
|
||||||
) {
|
) {
|
||||||
buttons.set(identifier, item);
|
buttons.set(identifier, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeButton(identifier: string) {
|
export function removeMessagePopoverButton(identifier: string) {
|
||||||
buttons.delete(identifier);
|
buttons.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _buildPopoverElements(
|
export function _buildPopoverElements(
|
||||||
Component: React.ComponentType<ButtonItem>,
|
Component: React.ComponentType<MessagePopoverButtonItem>,
|
||||||
message: Message
|
message: Message
|
||||||
) {
|
) {
|
||||||
const items: React.ReactNode[] = [];
|
const items: React.ReactNode[] = [];
|
||||||
|
|
|
@ -16,41 +16,36 @@
|
||||||
* 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 { Logger } from "@utils/Logger";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { JSX } from "react";
|
import { ComponentType } from "react";
|
||||||
|
|
||||||
const logger = new Logger("ServerListAPI");
|
|
||||||
|
|
||||||
export const enum ServerListRenderPosition {
|
export const enum ServerListRenderPosition {
|
||||||
Above,
|
Above,
|
||||||
In,
|
In,
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderFunctionsAbove = new Set<Function>();
|
const componentsAbove = new Set<ComponentType>();
|
||||||
const renderFunctionsIn = new Set<Function>();
|
const componentsBelow = new Set<ComponentType>();
|
||||||
|
|
||||||
function getRenderFunctions(position: ServerListRenderPosition) {
|
function getRenderFunctions(position: ServerListRenderPosition) {
|
||||||
return position === ServerListRenderPosition.Above ? renderFunctionsAbove : renderFunctionsIn;
|
return position === ServerListRenderPosition.Above ? componentsAbove : componentsBelow;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
export function addServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||||
getRenderFunctions(position).add(renderFunction);
|
getRenderFunctions(position).add(renderFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||||
getRenderFunctions(position).delete(renderFunction);
|
getRenderFunctions(position).delete(renderFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const renderAll = (position: ServerListRenderPosition) => {
|
export const renderAll = (position: ServerListRenderPosition) => {
|
||||||
const ret: Array<JSX.Element> = [];
|
return Array.from(
|
||||||
|
getRenderFunctions(position),
|
||||||
for (const renderFunction of getRenderFunctions(position)) {
|
(Component, i) => (
|
||||||
try {
|
<ErrorBoundary noop key={i}>
|
||||||
ret.unshift(renderFunction());
|
<Component />
|
||||||
} catch (e) {
|
</ErrorBoundary>
|
||||||
logger.error("Failed to render server list element:", e);
|
)
|
||||||
}
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
};
|
|
@ -220,6 +220,17 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) {
|
||||||
|
const settings = SettingsStore.plain.plugins[pluginName];
|
||||||
|
if (!settings) return;
|
||||||
|
|
||||||
|
if (!Object.hasOwn(settings, oldSetting) || Object.hasOwn(settings, newSetting)) return;
|
||||||
|
|
||||||
|
settings[newSetting] = settings[oldSetting];
|
||||||
|
delete settings[oldSetting];
|
||||||
|
SettingsStore.markAsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
export function definePluginSettings<
|
export function definePluginSettings<
|
||||||
Def extends SettingsDefinition,
|
Def extends SettingsDefinition,
|
||||||
Checks extends SettingsChecks<Def>,
|
Checks extends SettingsChecks<Def>,
|
||||||
|
|
|
@ -70,8 +70,7 @@ const ErrorBoundary = LazyComponent(() => {
|
||||||
|
|
||||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||||
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
||||||
logger.error("A component threw an Error\n", error);
|
logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack);
|
||||||
logger.error("Component Stack", errorInfo.componentStack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -37,6 +37,7 @@ import { Constructor } from "type-fest";
|
||||||
import { PluginMeta } from "~plugins";
|
import { PluginMeta } from "~plugins";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ISettingCustomElementProps,
|
||||||
ISettingElementProps,
|
ISettingElementProps,
|
||||||
SettingBooleanComponent,
|
SettingBooleanComponent,
|
||||||
SettingCustomComponent,
|
SettingCustomComponent,
|
||||||
|
@ -74,14 +75,15 @@ function makeDummyUser(user: { username: string; id?: string; avatar?: string; }
|
||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any>>> = {
|
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any> | ISettingCustomElementProps<any>>> = {
|
||||||
[OptionType.STRING]: SettingTextComponent,
|
[OptionType.STRING]: SettingTextComponent,
|
||||||
[OptionType.NUMBER]: SettingNumericComponent,
|
[OptionType.NUMBER]: SettingNumericComponent,
|
||||||
[OptionType.BIGINT]: SettingNumericComponent,
|
[OptionType.BIGINT]: SettingNumericComponent,
|
||||||
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
||||||
[OptionType.SELECT]: SettingSelectComponent,
|
[OptionType.SELECT]: SettingSelectComponent,
|
||||||
[OptionType.SLIDER]: SettingSliderComponent,
|
[OptionType.SLIDER]: SettingSliderComponent,
|
||||||
[OptionType.COMPONENT]: SettingCustomComponent
|
[OptionType.COMPONENT]: SettingCustomComponent,
|
||||||
|
[OptionType.CUSTOM]: () => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||||
|
@ -129,7 +131,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
for (const [key, value] of Object.entries(tempSettings)) {
|
for (const [key, value] of Object.entries(tempSettings)) {
|
||||||
const option = plugin.options[key];
|
const option = plugin.options[key];
|
||||||
pluginSettings[key] = value;
|
pluginSettings[key] = value;
|
||||||
option?.onChange?.(value);
|
|
||||||
|
if (option.type === OptionType.CUSTOM) continue;
|
||||||
if (option?.restartNeeded) restartNeeded = true;
|
if (option?.restartNeeded) restartNeeded = true;
|
||||||
}
|
}
|
||||||
if (restartNeeded) onRestartNeeded();
|
if (restartNeeded) onRestartNeeded();
|
||||||
|
@ -141,7 +144,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
||||||
} else {
|
} else {
|
||||||
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
||||||
if (setting.hidden) return null;
|
if (setting.type === OptionType.CUSTOM || setting.hidden) return null;
|
||||||
|
|
||||||
function onChange(newValue: any) {
|
function onChange(newValue: any) {
|
||||||
setTempSettings(s => ({ ...s, [key]: newValue }));
|
setTempSettings(s => ({ ...s, [key]: newValue }));
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
import { PluginOptionComponent } from "@utils/types";
|
import { PluginOptionComponent } from "@utils/types";
|
||||||
|
|
||||||
import { ISettingElementProps } from ".";
|
import { ISettingCustomElementProps } from ".";
|
||||||
|
|
||||||
export function SettingCustomComponent({ option, onChange, onError }: ISettingElementProps<PluginOptionComponent>) {
|
export function SettingCustomComponent({ option, onChange, onError }: ISettingCustomElementProps<PluginOptionComponent>) {
|
||||||
return option.component({ setValue: onChange, setError: onError, option });
|
return option.component({ setValue: onChange, setError: onError, option });
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
||||||
|
|
||||||
export interface ISettingElementProps<T extends PluginOptionBase> {
|
interface ISettingElementPropsBase<T> {
|
||||||
option: T;
|
option: T;
|
||||||
onChange(newValue: any): void;
|
onChange(newValue: any): void;
|
||||||
pluginSettings: {
|
pluginSettings: {
|
||||||
|
@ -30,6 +30,9 @@ export interface ISettingElementProps<T extends PluginOptionBase> {
|
||||||
definedSettings?: DefinedSettings;
|
definedSettings?: DefinedSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ISettingElementProps<T extends PluginOptionBase> = ISettingElementPropsBase<T>;
|
||||||
|
export type ISettingCustomElementProps<T extends Omit<PluginOptionBase, "description" | "placeholder">> = ISettingElementPropsBase<T>;
|
||||||
|
|
||||||
export * from "../../Badge";
|
export * from "../../Badge";
|
||||||
export * from "./SettingBooleanComponent";
|
export * from "./SettingBooleanComponent";
|
||||||
export * from "./SettingCustomComponent";
|
export * from "./SettingCustomComponent";
|
||||||
|
|
|
@ -62,14 +62,21 @@ async function runReporter() {
|
||||||
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let logMessage = searchType;
|
let logMessage = searchType;
|
||||||
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||||
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
if (args[0].$$vencordProps != null) {
|
||||||
else if (method === "mapMangledModule") {
|
logMessage += `(${args[0].$$vencordProps.map(arg => `"${arg}"`).join(", ")})`;
|
||||||
|
} else {
|
||||||
|
logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
||||||
|
}
|
||||||
|
} else if (method === "extractAndLoadChunks") {
|
||||||
|
logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
||||||
|
} else if (method === "mapMangledModule") {
|
||||||
const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null);
|
const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null);
|
||||||
|
|
||||||
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
|
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
|
||||||
|
} else {
|
||||||
|
logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
||||||
}
|
}
|
||||||
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
|
||||||
|
|
||||||
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { isPluginDev } from "@utils/misc";
|
import { isPluginDev } from "@utils/misc";
|
||||||
import { closeModal, Modals, openModal } from "@utils/modal";
|
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Forms, Toasts, UserStore } from "@webpack/common";
|
import { Forms, Toasts, UserStore } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
@ -102,8 +102,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
userProfileBadge: ContributorBadge,
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
Vencord.Api.Badges.addBadge(ContributorBadge);
|
|
||||||
await loadBadges();
|
await loadBadges();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -143,8 +144,8 @@ export default definePlugin({
|
||||||
closeModal(modalKey);
|
closeModal(modalKey);
|
||||||
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
|
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
|
||||||
}}>
|
}}>
|
||||||
<Modals.ModalRoot {...props}>
|
<ModalRoot {...props}>
|
||||||
<Modals.ModalHeader>
|
<ModalHeader>
|
||||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||||
<Forms.FormTitle
|
<Forms.FormTitle
|
||||||
tag="h2"
|
tag="h2"
|
||||||
|
@ -158,8 +159,8 @@ export default definePlugin({
|
||||||
Vencord Donor
|
Vencord Donor
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Modals.ModalHeader>
|
</ModalHeader>
|
||||||
<Modals.ModalContent>
|
<ModalContent>
|
||||||
<Flex>
|
<Flex>
|
||||||
<img
|
<img
|
||||||
role="presentation"
|
role="presentation"
|
||||||
|
@ -182,13 +183,13 @@ export default definePlugin({
|
||||||
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</div>
|
</div>
|
||||||
</Modals.ModalContent>
|
</ModalContent>
|
||||||
<Modals.ModalFooter>
|
<ModalFooter>
|
||||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||||
<DonateButton />
|
<DonateButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Modals.ModalFooter>
|
</ModalFooter>
|
||||||
</Modals.ModalRoot>
|
</ModalRoot>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,11 +12,15 @@ export default definePlugin({
|
||||||
description: "API to add buttons to the chat input",
|
description: "API to add buttons to the chat input",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
|
|
||||||
patches: [{
|
patches: [
|
||||||
|
{
|
||||||
find: '"sticker")',
|
find: '"sticker")',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/,
|
match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(\i\.isDM.+?(\i)\.push)/,
|
||||||
replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&"
|
replace: (m, not, children) => not
|
||||||
|
? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&`
|
||||||
|
: `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||`
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,12 +34,22 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Menu,{",
|
find: "navId:",
|
||||||
all: true,
|
all: true,
|
||||||
replacement: {
|
noWarn: true,
|
||||||
match: /Menu,{(?<=\.jsxs?\)\(\i\.Menu,{)/g,
|
replacement: [
|
||||||
replace: "$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[],"
|
{
|
||||||
|
match: /navId:(?=.+?([,}].*?\)))/g,
|
||||||
|
replace: (m, rest) => {
|
||||||
|
// Check if this navId: match is a destructuring statement, ignore it if it is
|
||||||
|
const destructuringMatch = rest.match(/}=.+/);
|
||||||
|
if (destructuringMatch == null) {
|
||||||
|
return `contextMenuAPIArguments:typeof arguments!=='undefined'?arguments:[],${m}`;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
68
src/plugins/_api/menuItemDemangler.ts
Normal file
68
src/plugins/_api/menuItemDemangler.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
// duplicate values have multiple branches with different types. Just include all to be safe
|
||||||
|
const nameMap = {
|
||||||
|
radio: "MenuRadioItem",
|
||||||
|
separator: "MenuSeparator",
|
||||||
|
checkbox: "MenuCheckboxItem",
|
||||||
|
groupstart: "MenuGroup",
|
||||||
|
|
||||||
|
control: "MenuControlItem",
|
||||||
|
compositecontrol: "MenuControlItem",
|
||||||
|
|
||||||
|
item: "MenuItem",
|
||||||
|
customitem: "MenuItem",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "MenuItemDemanglerAPI",
|
||||||
|
description: "Demangles Discord's Menu Item module",
|
||||||
|
authors: [Devs.Ven],
|
||||||
|
required: true,
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: '"Menu API',
|
||||||
|
replacement: {
|
||||||
|
match: /function.{0,80}type===(\i\.\i)\).{0,50}navigable:.+?Menu API/s,
|
||||||
|
replace: (m, mod) => {
|
||||||
|
const nameAssignments = [] as string[];
|
||||||
|
|
||||||
|
// if (t.type === m.MenuItem)
|
||||||
|
const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g);
|
||||||
|
// push({type:"item"})
|
||||||
|
const pushTypeRe = /type:"(\w+)"/g;
|
||||||
|
|
||||||
|
let typeMatch: RegExpExecArray | null;
|
||||||
|
// for each if (t.type === ...)
|
||||||
|
while ((typeMatch = typeCheckRe.exec(m)) !== null) {
|
||||||
|
// extract the current menu item
|
||||||
|
const item = typeMatch[1];
|
||||||
|
// Set the starting index of the second regex to that of the first to start
|
||||||
|
// matching from after the if
|
||||||
|
pushTypeRe.lastIndex = typeCheckRe.lastIndex;
|
||||||
|
// extract the first type: "..."
|
||||||
|
const type = pushTypeRe.exec(m)?.[1];
|
||||||
|
if (type && type in nameMap) {
|
||||||
|
const name = nameMap[type];
|
||||||
|
nameAssignments.push(`Object.defineProperty(${item},"name",{value:"${name}"})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nameAssignments.length < 6) {
|
||||||
|
console.warn("[MenuItemDemanglerAPI] Expected to at least remap 6 items, only remapped", nameAssignments.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge all our redefines with the actual module
|
||||||
|
return `${nameAssignments.join(";")};${m}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
|
@ -65,7 +65,7 @@ export default definePlugin({
|
||||||
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
|
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?(?:function\(\){return |\(\)=>))\2/,
|
||||||
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -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 { addAccessory } from "@api/MessageAccessories";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
@ -143,7 +142,7 @@ export default definePlugin({
|
||||||
required: true,
|
required: true,
|
||||||
description: "Helps us provide support to you",
|
description: "Helps us provide support to you",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"],
|
dependencies: ["UserSettingsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
@ -236,23 +235,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
renderMessageAccessory(props) {
|
||||||
const userId = channel.getRecipientId();
|
|
||||||
if (!isPluginDev(userId)) return null;
|
|
||||||
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
|
||||||
Please do not private message Vencord plugin developers for support!
|
|
||||||
<br />
|
|
||||||
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")}
|
|
||||||
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}, { noop: true }),
|
|
||||||
|
|
||||||
start() {
|
|
||||||
addAccessory("vencord-debug", props => {
|
|
||||||
const buttons = [] as JSX.Element[];
|
const buttons = [] as JSX.Element[];
|
||||||
|
|
||||||
const shouldAddUpdateButton =
|
const shouldAddUpdateButton =
|
||||||
|
@ -329,6 +312,20 @@ export default definePlugin({
|
||||||
return buttons.length
|
return buttons.length
|
||||||
? <Flex>{buttons}</Flex>
|
? <Flex>{buttons}</Flex>
|
||||||
: null;
|
: null;
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
||||||
|
const userId = channel.getRecipientId();
|
||||||
|
if (!isPluginDev(userId)) return null;
|
||||||
|
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
||||||
|
Please do not private message Vencord plugin developers for support!
|
||||||
|
<br />
|
||||||
|
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")}
|
||||||
|
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}, { noop: true }),
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@ const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cann
|
||||||
const styles = findByPropsLazy("accountProfilePopoutWrapper");
|
const styles = findByPropsLazy("accountProfilePopoutWrapper");
|
||||||
|
|
||||||
let openAlternatePopout = false;
|
let openAlternatePopout = false;
|
||||||
let accountPanelRef: React.MutableRefObject<Record<PropertyKey, any> | null> = { current: null };
|
let accountPanelRef: React.RefObject<Record<PropertyKey, any> | null> = { current: null };
|
||||||
|
|
||||||
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
|
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
|
||||||
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
|
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
|
||||||
|
|
|
@ -43,8 +43,8 @@ export default definePlugin({
|
||||||
// Status emojis
|
// Status emojis
|
||||||
find: "#{intl::GUILD_OWNER}),children:",
|
find: "#{intl::GUILD_OWNER}),children:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.activityEmoji,.+?animate:)\i/,
|
match: /(\.CUSTOM_STATUS.+?animate:)\i/,
|
||||||
replace: "!0"
|
replace: (_, rest) => `${rest}!0`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,14 +17,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { useStateFromStores } from "@webpack/common";
|
import { Animations, useStateFromStores } from "@webpack/common";
|
||||||
import type { CSSProperties } from "react";
|
import type { CSSProperties } from "react";
|
||||||
|
|
||||||
import { ExpandedGuildFolderStore, settings } from ".";
|
import { ExpandedGuildFolderStore, settings } from ".";
|
||||||
|
|
||||||
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
||||||
const Animations = findByPropsLazy("a", "animated", "useTransition");
|
|
||||||
const GuildsBar = findComponentByCodeLazy('("guildsnav")');
|
const GuildsBar = findComponentByCodeLazy('("guildsnav")');
|
||||||
|
|
||||||
export default ErrorBoundary.wrap(guildsBarProps => {
|
export default ErrorBoundary.wrap(guildsBarProps => {
|
||||||
|
|
|
@ -173,8 +173,8 @@ export default definePlugin({
|
||||||
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
||||||
{
|
{
|
||||||
predicate: () => !settings.store.keepIcons,
|
predicate: () => !settings.store.keepIcons,
|
||||||
match: /(?<=#{intl::SERVER_FOLDER_PLACEHOLDER}.+?useTransition\)\()/,
|
match: /(?=,\{from:\{height)/,
|
||||||
replace: "$self.shouldShowTransition(arguments[0])&&"
|
replace: "&&$self.shouldShowTransition(arguments[0])"
|
||||||
},
|
},
|
||||||
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
||||||
{
|
{
|
||||||
|
|
|
@ -83,7 +83,7 @@ export default definePlugin({
|
||||||
if (!role) return;
|
if (!role) return;
|
||||||
|
|
||||||
if (role.colorString) {
|
if (role.colorString) {
|
||||||
children.push(
|
children.unshift(
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="vc-copy-role-color"
|
id="vc-copy-role-color"
|
||||||
label="Copy Role Color"
|
label="Copy Role Color"
|
||||||
|
@ -93,6 +93,20 @@ export default definePlugin({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
|
||||||
|
children.unshift(
|
||||||
|
<Menu.MenuItem
|
||||||
|
id="vc-edit-role"
|
||||||
|
label="Edit Role"
|
||||||
|
action={async () => {
|
||||||
|
await GuildSettingsActions.open(guild.id, "ROLES");
|
||||||
|
GuildSettingsActions.selectRole(id);
|
||||||
|
}}
|
||||||
|
icon={PencilIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (role.icon) {
|
if (role.icon) {
|
||||||
children.push(
|
children.push(
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
|
@ -110,20 +124,6 @@ export default definePlugin({
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
|
|
||||||
children.push(
|
|
||||||
<Menu.MenuItem
|
|
||||||
id="vc-edit-role"
|
|
||||||
label="Edit Role"
|
|
||||||
action={async () => {
|
|
||||||
await GuildSettingsActions.open(guild.id, "ROLES");
|
|
||||||
GuildSettingsActions.selectRole(id);
|
|
||||||
}}
|
|
||||||
icon={PencilIcon}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
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 { findByPropsLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
|
import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import { RenameButton } from "./components/RenameButton";
|
import { RenameButton } from "./components/RenameButton";
|
||||||
|
@ -34,7 +34,7 @@ const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open");
|
||||||
const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer");
|
const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer");
|
||||||
const SessionIconClasses = findByPropsLazy("sessionIcon");
|
const SessionIconClasses = findByPropsLazy("sessionIcon");
|
||||||
|
|
||||||
const BlobMask = findExportedComponentLazy("BlobMask");
|
const BlobMask = findComponentByCodeLazy("!1,lowerBadgeSize:");
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
backgroundCheck: {
|
backgroundCheck: {
|
||||||
|
|
|
@ -101,8 +101,8 @@ export default definePlugin({
|
||||||
find: 'minimal:"contentColumnMinimal"',
|
find: 'minimal:"contentColumnMinimal"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /\(0,\i\.useTransition\)\((\i)/,
|
match: /(?=\(0,\i\.\i\)\((\i),\{from:\{position:"absolute")/,
|
||||||
replace: "(_cb=>_cb(void 0,$1))||$&"
|
replace: "(_cb=>_cb(void 0,$1))||"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /\i\.animated\.div/,
|
match: /\i\.animated\.div/,
|
||||||
|
|
|
@ -75,8 +75,8 @@ export default definePlugin({
|
||||||
patches: [{
|
patches: [{
|
||||||
find: "renderConnectionStatus(){",
|
find: "renderConnectionStatus(){",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=renderConnectionStatus\(\){.+\.channel,children:).+?}\):\i(?=}\))/,
|
match: /(renderConnectionStatus\(\){.+\.channel,children:)(.+?}\):\i)(?=}\))/,
|
||||||
replace: "[$&, $self.renderTimer(this.props.channel.id)]"
|
replace: "$1[$2,$self.renderTimer(this.props.channel.id)]"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addPreEditListener,
|
MessageObject
|
||||||
addPreSendListener,
|
|
||||||
MessageObject,
|
|
||||||
removePreEditListener,
|
|
||||||
removePreSendListener
|
|
||||||
} from "@api/MessageEvents";
|
} from "@api/MessageEvents";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
@ -36,7 +32,18 @@ export default definePlugin({
|
||||||
name: "ClearURLs",
|
name: "ClearURLs",
|
||||||
description: "Removes tracking garbage from URLs",
|
description: "Removes tracking garbage from URLs",
|
||||||
authors: [Devs.adryd],
|
authors: [Devs.adryd],
|
||||||
dependencies: ["MessageEventsAPI"],
|
|
||||||
|
start() {
|
||||||
|
this.createRules();
|
||||||
|
},
|
||||||
|
|
||||||
|
onBeforeMessageSend(_, msg) {
|
||||||
|
return this.onSend(msg);
|
||||||
|
},
|
||||||
|
|
||||||
|
onBeforeMessageEdit(_cid, _mid, msg) {
|
||||||
|
return this.onSend(msg);
|
||||||
|
},
|
||||||
|
|
||||||
escapeRegExp(str: string) {
|
escapeRegExp(str: string) {
|
||||||
return (str && reHasRegExpChar.test(str))
|
return (str && reHasRegExpChar.test(str))
|
||||||
|
@ -133,17 +140,4 @@ export default definePlugin({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
start() {
|
|
||||||
this.createRules();
|
|
||||||
this.preSend = addPreSendListener((_, msg) => this.onSend(msg));
|
|
||||||
this.preEdit = addPreEditListener((_cid, _mid, msg) =>
|
|
||||||
this.onSend(msg)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removePreSendListener(this.preSend);
|
|
||||||
removePreEditListener(this.preEdit);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -91,15 +91,12 @@ function ThemeSettings() {
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
color: {
|
color: {
|
||||||
description: "Color your Discord client theme will be based around. Light mode isn't supported",
|
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
default: "313338",
|
default: "313338",
|
||||||
component: () => <ThemeSettings />
|
component: ThemeSettings
|
||||||
},
|
},
|
||||||
resetColor: {
|
resetColor: {
|
||||||
description: "Reset Theme Color",
|
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
default: "313338",
|
|
||||||
component: () => (
|
component: () => (
|
||||||
<Button onClick={() => onPickColor(0x313338)}>
|
<Button onClick={() => onPickColor(0x313338)}>
|
||||||
Reset Theme Color
|
Reset Theme Color
|
||||||
|
|
|
@ -69,8 +69,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "https://github.com/highlightjs/highlight.js/issues/2277",
|
find: "https://github.com/highlightjs/highlight.js/issues/2277",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=&&\()console.log\(`Deprecated.+?`\),/,
|
match: /\(console.log\(`Deprecated.+?`\),/,
|
||||||
replace: ""
|
replace: "("
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -95,10 +95,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: 'console.warn("[DEPRECATED] Please use `subscribeWithSelector` middleware");',
|
find: '"AppCrashedFatalReport: getLastCrash not supported."',
|
||||||
all: true,
|
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\);/,
|
match: /console\.log\("AppCrashedFatalReport: getLastCrash not supported\."\);/,
|
||||||
replace: ""
|
replace: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -63,7 +63,7 @@ function makeShortcuts() {
|
||||||
default:
|
default:
|
||||||
const uniqueMatches = [...new Set(matches)];
|
const uniqueMatches = [...new Set(matches)];
|
||||||
if (uniqueMatches.length > 1)
|
if (uniqueMatches.length > 1)
|
||||||
console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches);
|
console.warn(`Warning: This filter matches ${uniqueMatches.length} exports. Make it more specific!\n`, uniqueMatches);
|
||||||
|
|
||||||
return matches[0];
|
return matches[0];
|
||||||
}
|
}
|
||||||
|
@ -165,11 +165,38 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {
|
||||||
const currentVal = val.getter();
|
const currentVal = val.getter();
|
||||||
if (!currentVal || val.preload === false) return currentVal;
|
if (!currentVal || val.preload === false) return currentVal;
|
||||||
|
|
||||||
const value = currentVal[SYM_LAZY_GET]
|
function unwrapProxy(value: any) {
|
||||||
? forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED]
|
if (value[SYM_LAZY_GET]) {
|
||||||
: currentVal;
|
forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED];
|
||||||
|
} else if (value.$$vencordInternal) {
|
||||||
|
return forceLoad ? value.$$vencordInternal() : value;
|
||||||
|
}
|
||||||
|
|
||||||
if (value) define(window.shortcutList, key, { value });
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = unwrapProxy(currentVal);
|
||||||
|
if (typeof value === "object" && value !== null) {
|
||||||
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
||||||
|
|
||||||
|
for (const propKey in descriptors) {
|
||||||
|
if (value[propKey] == null) continue;
|
||||||
|
|
||||||
|
const descriptor = descriptors[propKey];
|
||||||
|
if (descriptor.writable === true || descriptor.set != null) {
|
||||||
|
const currentValue = value[propKey];
|
||||||
|
const newValue = unwrapProxy(currentValue);
|
||||||
|
if (newValue != null && currentValue !== newValue) {
|
||||||
|
value[propKey] = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
define(window.shortcutList, key, { value });
|
||||||
|
define(window, key, { value });
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,10 +42,10 @@ export default definePlugin({
|
||||||
// Only one of the two patches will be at effect; Discord often updates to switch between them.
|
// Only one of the two patches will be at effect; Discord often updates to switch between them.
|
||||||
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
|
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
|
||||||
{
|
{
|
||||||
find: ".ENTER&&(!",
|
find: ".selectPreviousCommandOption(",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/,
|
match: /(?<=(\i)\.which(?:!==|===)\i\.\i.ENTER(\|\||&&)).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=(?:\|\||&&)\(\i\.preventDefault)/,
|
||||||
replace: "$self.shouldSubmit($1, $2)"
|
replace: (_, event, condition, codeblock) => `${condition === "||" ? "!" : ""}$self.shouldSubmit(${event},${codeblock})`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,7 +17,6 @@ import DecorSection from "./ui/components/DecorSection";
|
||||||
export const settings = definePluginSettings({
|
export const settings = definePluginSettings({
|
||||||
changeDecoration: {
|
changeDecoration: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "Change your avatar decoration",
|
|
||||||
component() {
|
component() {
|
||||||
if (!Vencord.Plugins.plugins.Decor.started) return <Forms.FormText>
|
if (!Vencord.Plugins.plugins.Decor.started) return <Forms.FormText>
|
||||||
Enable Decor and restart your client to change your avatar decoration.
|
Enable Decor and restart your client to change your avatar decoration.
|
||||||
|
|
|
@ -16,7 +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 { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
|
import { addMessagePreEditListener, addMessagePreSendListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
||||||
|
@ -235,7 +235,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".PREMIUM_LOCKED;",
|
find: ".GUILD_SUBSCRIPTION_UNAVAILABLE;",
|
||||||
group: true,
|
group: true,
|
||||||
predicate: () => settings.store.enableEmojiBypass,
|
predicate: () => settings.store.enableEmojiBypass,
|
||||||
replacement: [
|
replacement: [
|
||||||
|
@ -256,8 +256,10 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Disallow the emoji for premium locked if the intention doesn't allow it
|
// Disallow the emoji for premium locked if the intention doesn't allow it
|
||||||
match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/,
|
match: /(!)?(\i\.\i\.canUseEmojisEverywhere\(\i\))/,
|
||||||
replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
|
replace: (m, not) => not
|
||||||
|
? `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
|
||||||
|
: `(${m}||${IS_BYPASSEABLE_INTENTION})`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Allow animated emojis to be used if the intention allows it
|
// Allow animated emojis to be used if the intention allows it
|
||||||
|
@ -853,7 +855,7 @@ export default definePlugin({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.preSend = addPreSendListener(async (channelId, messageObj, extra) => {
|
this.preSend = addMessagePreSendListener(async (channelId, messageObj, extra) => {
|
||||||
const { guildId } = this;
|
const { guildId } = this;
|
||||||
|
|
||||||
let hasBypass = false;
|
let hasBypass = false;
|
||||||
|
@ -941,7 +943,7 @@ export default definePlugin({
|
||||||
return { cancel: false };
|
return { cancel: false };
|
||||||
});
|
});
|
||||||
|
|
||||||
this.preEdit = addPreEditListener(async (channelId, __, messageObj) => {
|
this.preEdit = addMessagePreEditListener(async (channelId, __, messageObj) => {
|
||||||
if (!s.enableEmojiBypass) return;
|
if (!s.enableEmojiBypass) return;
|
||||||
|
|
||||||
let hasBypass = false;
|
let hasBypass = false;
|
||||||
|
@ -973,7 +975,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
removePreSendListener(this.preSend);
|
removeMessagePreSendListener(this.preSend);
|
||||||
removePreEditListener(this.preEdit);
|
removeMessagePreEditListener(this.preEdit);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,11 +22,11 @@ import { Devs } from "@utils/constants";
|
||||||
import { getIntlMessage } from "@utils/discord";
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import { NoopComponent } from "@utils/react";
|
import { NoopComponent } from "@utils/react";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { filters, findByPropsLazy, waitFor } from "@webpack";
|
import { filters, findByCodeLazy, waitFor } from "@webpack";
|
||||||
import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common";
|
import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
const { useMessageMenu } = findByPropsLazy("useMessageMenu");
|
const useMessageMenu = findByCodeLazy(".MESSAGE,commandTargetId:");
|
||||||
|
|
||||||
interface CopyIdMenuItemProps {
|
interface CopyIdMenuItemProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { findComponentByCodeLazy } from "@webpack";
|
||||||
|
|
||||||
import style from "./style.css?managed";
|
import style from "./style.css?managed";
|
||||||
|
|
||||||
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:");
|
const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON");
|
||||||
|
|
||||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { get, set } from "@api/DataStore";
|
import { get, set } from "@api/DataStore";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
|
||||||
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { ChannelStore } from "@webpack/common";
|
import { ChannelStore } from "@webpack/common";
|
||||||
|
import { MessageSnapshot } from "@webpack/types";
|
||||||
|
|
||||||
let style: HTMLStyleElement;
|
let style: HTMLStyleElement;
|
||||||
|
|
||||||
|
@ -38,18 +38,14 @@ export default definePlugin({
|
||||||
name: "HideAttachments",
|
name: "HideAttachments",
|
||||||
description: "Hide attachments and Embeds for individual messages via hover button",
|
description: "Hide attachments and Embeds for individual messages via hover button",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["MessagePopoverAPI"],
|
|
||||||
|
|
||||||
async start() {
|
renderMessagePopoverButton(msg) {
|
||||||
style = document.createElement("style");
|
// @ts-ignore - discord-types lags behind discord.
|
||||||
style.id = "VencordHideAttachments";
|
const hasAttachmentsInShapshots = msg.messageSnapshots.some(
|
||||||
document.head.appendChild(style);
|
(snapshot: MessageSnapshot) => snapshot?.message.attachments.length
|
||||||
|
);
|
||||||
|
|
||||||
await getHiddenMessages();
|
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length && !hasAttachmentsInShapshots) return null;
|
||||||
await this.buildCss();
|
|
||||||
|
|
||||||
addButton("HideAttachments", msg => {
|
|
||||||
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length) return null;
|
|
||||||
|
|
||||||
const isHidden = hiddenMessages.has(msg.id);
|
const isHidden = hiddenMessages.has(msg.id);
|
||||||
|
|
||||||
|
@ -60,13 +56,20 @@ export default definePlugin({
|
||||||
channel: ChannelStore.getChannel(msg.channel_id),
|
channel: ChannelStore.getChannel(msg.channel_id),
|
||||||
onClick: () => this.toggleHide(msg.id)
|
onClick: () => this.toggleHide(msg.id)
|
||||||
};
|
};
|
||||||
});
|
},
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
style = document.createElement("style");
|
||||||
|
style.id = "VencordHideAttachments";
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
await getHiddenMessages();
|
||||||
|
await this.buildCss();
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
style.remove();
|
style.remove();
|
||||||
hiddenMessages.clear();
|
hiddenMessages.clear();
|
||||||
removeButton("HideAttachments");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async buildCss() {
|
async buildCss() {
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "hasFlag:{writable",
|
find: "hasFlag:{writable",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /if\((\i)<=(?:1<<30|1073741824)\)return/,
|
match: /if\((\i)<=(?:0x40000000|(?:1<<30|1073741824))\)return/,
|
||||||
replace: "if($1===(1<<20))return false;$&",
|
replace: "if($1===(1<<20))return false;$&",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
|
||||||
import { definePluginSettings, Settings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
@ -62,7 +61,7 @@ const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(ac
|
||||||
|
|
||||||
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
||||||
const s = settings.use(["ignoredActivities"]);
|
const s = settings.use(["ignoredActivities"]);
|
||||||
const { ignoredActivities = [] } = s;
|
const { ignoredActivities } = s;
|
||||||
|
|
||||||
if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
|
if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
|
||||||
return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)");
|
return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)");
|
||||||
|
@ -71,11 +70,9 @@ function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
||||||
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) {
|
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const ignoredActivityIndex = getIgnoredActivities().findIndex(act => act.id === activity.id);
|
const ignoredActivityIndex = settings.store.ignoredActivities.findIndex(act => act.id === activity.id);
|
||||||
if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity);
|
if (ignoredActivityIndex === -1) settings.store.ignoredActivities.push(activity);
|
||||||
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
|
else settings.store.ignoredActivities.splice(ignoredActivityIndex, 1);
|
||||||
|
|
||||||
recalculateActivities();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function recalculateActivities() {
|
function recalculateActivities() {
|
||||||
|
@ -150,8 +147,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
importCustomRPC: {
|
importCustomRPC: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "",
|
component: ImportCustomRPCComponent
|
||||||
component: () => <ImportCustomRPCComponent />
|
|
||||||
},
|
},
|
||||||
listMode: {
|
listMode: {
|
||||||
type: OptionType.SELECT,
|
type: OptionType.SELECT,
|
||||||
|
@ -171,7 +167,6 @@ const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
idsList: {
|
idsList: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "",
|
|
||||||
default: "",
|
default: "",
|
||||||
onChange(newValue: string) {
|
onChange(newValue: string) {
|
||||||
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
|
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
|
||||||
|
@ -209,14 +204,13 @@ const settings = definePluginSettings({
|
||||||
description: "Ignore all competing activities (These are normally special game activities)",
|
description: "Ignore all competing activities (These are normally special game activities)",
|
||||||
default: false,
|
default: false,
|
||||||
onChange: recalculateActivities
|
onChange: recalculateActivities
|
||||||
|
},
|
||||||
|
ignoredActivities: {
|
||||||
|
type: OptionType.CUSTOM,
|
||||||
|
default: [] as IgnoredActivity[],
|
||||||
|
onChange: recalculateActivities
|
||||||
}
|
}
|
||||||
}).withPrivateSettings<{
|
});
|
||||||
ignoredActivities: IgnoredActivity[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
function getIgnoredActivities() {
|
|
||||||
return settings.store.ignoredActivities ??= [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function isActivityTypeIgnored(type: number, id?: string) {
|
function isActivityTypeIgnored(type: number, id?: string) {
|
||||||
if (id && settings.store.idsList.includes(id)) {
|
if (id && settings.store.idsList.includes(id)) {
|
||||||
|
@ -247,7 +241,7 @@ export default definePlugin({
|
||||||
find: '"LocalActivityStore"',
|
find: '"LocalActivityStore"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /HANG_STATUS.+?(?=!\i\(\)\(\i,\i\)&&)(?<=(\i)\.push.+?)/,
|
match: /\.LISTENING.+?(?=!?\i\(\)\(\i,\i\))(?<=(\i)\.push.+?)/,
|
||||||
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -284,29 +278,14 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
// Migrate allowedIds
|
if (settings.store.ignoredActivities.length !== 0) {
|
||||||
if (Settings.plugins.IgnoreActivities.allowedIds) {
|
|
||||||
settings.store.idsList = Settings.plugins.IgnoreActivities.allowedIds;
|
|
||||||
delete Settings.plugins.IgnoreActivities.allowedIds; // Remove allowedIds
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldIgnoredActivitiesData = await DataStore.get<Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities");
|
|
||||||
|
|
||||||
if (oldIgnoredActivitiesData != null) {
|
|
||||||
settings.store.ignoredActivities = Array.from(oldIgnoredActivitiesData.values())
|
|
||||||
.map(activity => ({ ...activity, name: "Unknown Name" }));
|
|
||||||
|
|
||||||
DataStore.del("IgnoreActivities_ignoredActivities");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getIgnoredActivities().length !== 0) {
|
|
||||||
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
|
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
|
||||||
|
|
||||||
for (const [index, ignoredActivity] of getIgnoredActivities().entries()) {
|
for (const [index, ignoredActivity] of settings.store.ignoredActivities.entries()) {
|
||||||
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
|
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
|
||||||
|
|
||||||
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
||||||
getIgnoredActivities().splice(index, 1);
|
settings.store.ignoredActivities.splice(index, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,11 +295,11 @@ export default definePlugin({
|
||||||
if (isActivityTypeIgnored(props.type, props.application_id)) return false;
|
if (isActivityTypeIgnored(props.type, props.application_id)) return false;
|
||||||
|
|
||||||
if (props.application_id != null) {
|
if (props.application_id != null) {
|
||||||
return !getIgnoredActivities().some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id));
|
return !settings.store.ignoredActivities.some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id));
|
||||||
} else {
|
} else {
|
||||||
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
||||||
if (exePath) {
|
if (exePath) {
|
||||||
return !getIgnoredActivities().some(activity => activity.id === exePath);
|
return !settings.store.ignoredActivities.some(activity => activity.id === exePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,12 @@ export const settings = definePluginSettings({
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const imageContextMenuPatch: NavContextMenuPatchCallback = children => {
|
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||||
|
// Discord re-uses the image context menu for links to for the copy and open buttons
|
||||||
|
if ("href" in props) return;
|
||||||
|
// emojis in user statuses
|
||||||
|
if (props.target?.classList?.contains("emoji")) return;
|
||||||
|
|
||||||
const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]);
|
const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]);
|
||||||
|
|
||||||
children.push(
|
children.push(
|
||||||
|
|
|
@ -50,7 +50,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "#{intl::FRIENDS_SECTION_ONLINE}",
|
find: "#{intl::FRIENDS_SECTION_ONLINE}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\(0,\i\.jsx\)\(\i\.TabBar\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.string\(\i\.\i#{intl::BLOCKED}\)\}\)/,
|
match: /(\(0,\i\.jsx\)\(\i\.\i\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.string\(\i\.\i#{intl::BLOCKED}\)\}\)/,
|
||||||
replace: "$1.IMPLICIT,className:$2.item,children:\"Implicit\"}),$&"
|
replace: "$1.IMPLICIT,className:$2.item,children:\"Implicit\"}),$&"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,12 +16,19 @@
|
||||||
* 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 { addProfileBadge, removeProfileBadge } from "@api/Badges";
|
||||||
|
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||||
import { registerCommand, unregisterCommand } from "@api/Commands";
|
import { registerCommand, unregisterCommand } from "@api/Commands";
|
||||||
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
|
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
|
||||||
import { Settings } from "@api/Settings";
|
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
|
||||||
|
import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories";
|
||||||
|
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
|
||||||
|
import { addMessageClickListener, addMessagePreEditListener, addMessagePreSendListener, removeMessageClickListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||||
|
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
|
||||||
|
import { Settings, SettingsStore } from "@api/Settings";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { canonicalizeFind } from "@utils/patches";
|
import { canonicalizeFind } from "@utils/patches";
|
||||||
import { Patch, Plugin, ReporterTestable, StartAt } from "@utils/types";
|
import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
|
||||||
import { FluxDispatcher } from "@webpack/common";
|
import { FluxDispatcher } from "@webpack/common";
|
||||||
import { FluxEvents } from "@webpack/types";
|
import { FluxEvents } from "@webpack/types";
|
||||||
|
|
||||||
|
@ -83,6 +90,13 @@ function isReporterTestable(p: Plugin, part: ReporterTestable) {
|
||||||
: (p.reporterTestable & part) === part;
|
: (p.reporterTestable & part) === part;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pluginKeysToBind: Array<keyof PluginDef & `${"on" | "render"}${string}`> = [
|
||||||
|
"onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick",
|
||||||
|
"renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton"
|
||||||
|
];
|
||||||
|
|
||||||
|
const neededApiPlugins = new Set<string>();
|
||||||
|
|
||||||
// First round-trip to mark and force enable dependencies
|
// First round-trip to mark and force enable dependencies
|
||||||
//
|
//
|
||||||
// FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only
|
// FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only
|
||||||
|
@ -106,22 +120,46 @@ for (const p of pluginsValues) if (isPluginEnabled(p.name)) {
|
||||||
dep.isDependency = true;
|
dep.isDependency = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (p.commands?.length) {
|
if (p.commands?.length) neededApiPlugins.add("CommandsAPI");
|
||||||
Plugins.CommandsAPI.isDependency = true;
|
if (p.onBeforeMessageEdit || p.onBeforeMessageSend || p.onMessageClick) neededApiPlugins.add("MessageEventsAPI");
|
||||||
settings.CommandsAPI.enabled = true;
|
if (p.renderChatBarButton) neededApiPlugins.add("ChatInputButtonAPI");
|
||||||
|
if (p.renderMemberListDecorator) neededApiPlugins.add("MemberListDecoratorsAPI");
|
||||||
|
if (p.renderMessageAccessory) neededApiPlugins.add("MessageAccessoriesAPI");
|
||||||
|
if (p.renderMessageDecoration) neededApiPlugins.add("MessageDecorationsAPI");
|
||||||
|
if (p.renderMessagePopoverButton) neededApiPlugins.add("MessagePopoverAPI");
|
||||||
|
if (p.userProfileBadge) neededApiPlugins.add("BadgeAPI");
|
||||||
|
|
||||||
|
for (const key of pluginKeysToBind) {
|
||||||
|
p[key] &&= p[key].bind(p) as any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const p of neededApiPlugins) {
|
||||||
|
Plugins[p].isDependency = true;
|
||||||
|
settings[p].enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
for (const p of pluginsValues) {
|
for (const p of pluginsValues) {
|
||||||
if (p.settings) {
|
if (p.settings) {
|
||||||
p.settings.pluginName = p.name;
|
|
||||||
p.options ??= {};
|
p.options ??= {};
|
||||||
for (const [name, def] of Object.entries(p.settings.def)) {
|
|
||||||
|
p.settings.pluginName = p.name;
|
||||||
|
for (const name in p.settings.def) {
|
||||||
|
const def = p.settings.def[name];
|
||||||
const checks = p.settings.checks?.[name];
|
const checks = p.settings.checks?.[name];
|
||||||
p.options[name] = { ...def, ...checks };
|
p.options[name] = { ...def, ...checks };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (p.options) {
|
||||||
|
for (const name in p.options) {
|
||||||
|
const opt = p.options[name];
|
||||||
|
if (opt.onChange != null) {
|
||||||
|
SettingsStore.addChangeListener(`plugins.${p.name}.${name}`, opt.onChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (p.patches && isPluginEnabled(p.name)) {
|
if (p.patches && isPluginEnabled(p.name)) {
|
||||||
if (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) {
|
if (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) {
|
||||||
for (const patch of p.patches) {
|
for (const patch of p.patches) {
|
||||||
|
@ -215,7 +253,11 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc
|
||||||
}
|
}
|
||||||
|
|
||||||
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
|
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
|
||||||
const { name, commands, contextMenus } = p;
|
const {
|
||||||
|
name, commands, contextMenus, userProfileBadge,
|
||||||
|
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
|
||||||
|
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
|
||||||
|
} = p;
|
||||||
|
|
||||||
if (p.start) {
|
if (p.start) {
|
||||||
logger.info("Starting plugin", name);
|
logger.info("Starting plugin", name);
|
||||||
|
@ -249,7 +291,6 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
||||||
subscribePluginFluxEvents(p, FluxDispatcher);
|
subscribePluginFluxEvents(p, FluxDispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (contextMenus) {
|
if (contextMenus) {
|
||||||
logger.debug("Adding context menus patches of plugin", name);
|
logger.debug("Adding context menus patches of plugin", name);
|
||||||
for (const navId in contextMenus) {
|
for (const navId in contextMenus) {
|
||||||
|
@ -257,11 +298,27 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userProfileBadge) addProfileBadge(userProfileBadge);
|
||||||
|
|
||||||
|
if (onBeforeMessageEdit) addMessagePreEditListener(onBeforeMessageEdit);
|
||||||
|
if (onBeforeMessageSend) addMessagePreSendListener(onBeforeMessageSend);
|
||||||
|
if (onMessageClick) addMessageClickListener(onMessageClick);
|
||||||
|
|
||||||
|
if (renderChatBarButton) addChatBarButton(name, renderChatBarButton);
|
||||||
|
if (renderMemberListDecorator) addMemberListDecorator(name, renderMemberListDecorator);
|
||||||
|
if (renderMessageDecoration) addMessageDecoration(name, renderMessageDecoration);
|
||||||
|
if (renderMessageAccessory) addMessageAccessory(name, renderMessageAccessory);
|
||||||
|
if (renderMessagePopoverButton) addMessagePopoverButton(name, renderMessagePopoverButton);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}, p => `startPlugin ${p.name}`);
|
}, p => `startPlugin ${p.name}`);
|
||||||
|
|
||||||
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
||||||
const { name, commands, contextMenus } = p;
|
const {
|
||||||
|
name, commands, contextMenus, userProfileBadge,
|
||||||
|
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
|
||||||
|
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
|
||||||
|
} = p;
|
||||||
|
|
||||||
if (p.stop) {
|
if (p.stop) {
|
||||||
logger.info("Stopping plugin", name);
|
logger.info("Stopping plugin", name);
|
||||||
|
@ -300,5 +357,17 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userProfileBadge) removeProfileBadge(userProfileBadge);
|
||||||
|
|
||||||
|
if (onBeforeMessageEdit) removeMessagePreEditListener(onBeforeMessageEdit);
|
||||||
|
if (onBeforeMessageSend) removeMessagePreSendListener(onBeforeMessageSend);
|
||||||
|
if (onMessageClick) removeMessageClickListener(onMessageClick);
|
||||||
|
|
||||||
|
if (renderChatBarButton) removeChatBarButton(name);
|
||||||
|
if (renderMemberListDecorator) removeMemberListDecorator(name);
|
||||||
|
if (renderMessageDecoration) removeMessageDecoration(name);
|
||||||
|
if (renderMessageAccessory) removeMessageAccessory(name);
|
||||||
|
if (renderMessagePopoverButton) removeMessagePopoverButton(name);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}, p => `stopPlugin ${p.name}`);
|
}, p => `stopPlugin ${p.name}`);
|
||||||
|
|
|
@ -16,8 +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 { addChatBarButton, ChatBarButton } from "@api/ChatButtons";
|
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
|
||||||
import { updateMessage } from "@api/MessageUpdater";
|
import { updateMessage } from "@api/MessageUpdater";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
@ -66,7 +65,7 @@ function Indicator() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||||
if (!isMainChat) return null;
|
if (!isMainChat) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -104,7 +103,7 @@ export default definePlugin({
|
||||||
name: "InvisibleChat",
|
name: "InvisibleChat",
|
||||||
description: "Encrypt your Messages in a non-suspicious way!",
|
description: "Encrypt your Messages in a non-suspicious way!",
|
||||||
authors: [Devs.SammCheese],
|
authors: [Devs.SammCheese],
|
||||||
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI", "MessageUpdaterAPI"],
|
dependencies: ["MessageUpdaterAPI"],
|
||||||
reporterTestable: ReporterTestable.Patches,
|
reporterTestable: ReporterTestable.Patches,
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
@ -125,7 +124,11 @@ export default definePlugin({
|
||||||
/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/,
|
/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/,
|
||||||
),
|
),
|
||||||
async start() {
|
async start() {
|
||||||
addButton("InvisibleChat", message => {
|
const { default: StegCloak } = await getStegCloak();
|
||||||
|
steggo = new StegCloak(true, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMessagePopoverButton(message) {
|
||||||
return this.INV_REGEX.test(message?.content)
|
return this.INV_REGEX.test(message?.content)
|
||||||
? {
|
? {
|
||||||
label: "Decrypt Message",
|
label: "Decrypt Message",
|
||||||
|
@ -142,18 +145,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
});
|
|
||||||
|
|
||||||
addChatBarButton("InvisibleChat", ChatBarIcon);
|
|
||||||
|
|
||||||
const { default: StegCloak } = await getStegCloak();
|
|
||||||
steggo = new StegCloak(true, false);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
renderChatBarButton: ChatBarIcon,
|
||||||
removeButton("InvisibleChat");
|
|
||||||
removeButton("InvisibleChat");
|
|
||||||
},
|
|
||||||
|
|
||||||
// Gets the Embed of a Link
|
// Gets the Embed of a Link
|
||||||
async getEmbed(url: URL): Promise<Object | {}> {
|
async getEmbed(url: URL): Promise<Object | {}> {
|
||||||
|
|
17
src/plugins/ircColors/README.md
Normal file
17
src/plugins/ircColors/README.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# IrcColors
|
||||||
|
|
||||||
|
Makes username colors in chat unique, like in IRC clients
|
||||||
|
|
||||||
|
![Chat with IrcColors and Compact++ enabled](https://github.com/Vendicated/Vencord/assets/33988779/88e05c0b-a60a-4d10-949e-8b46e1d7226c)
|
||||||
|
|
||||||
|
Improves chat readability by assigning every user an unique nickname color,
|
||||||
|
making distinguishing between different users easier. Inspired by the feature
|
||||||
|
in many IRC clients, such as HexChat or WeeChat.
|
||||||
|
|
||||||
|
Keep in mind this overrides role colors in chat, so if you wish to know
|
||||||
|
someone's role color without checking their profile, enable the role dot: go to
|
||||||
|
**User Settings**, **Accessibility** and switch **Role Colors** to **Show role
|
||||||
|
colors next to names**.
|
||||||
|
|
||||||
|
Created for use with the [Compact++](https://gitlab.com/Grzesiek11/compactplusplus-discord-theme)
|
||||||
|
theme.
|
85
src/plugins/ircColors/index.ts
Normal file
85
src/plugins/ircColors/index.ts
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { hash as h64 } from "@intrnl/xxhash64";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { useMemo } from "@webpack/common";
|
||||||
|
|
||||||
|
// Calculate a CSS color string based on the user ID
|
||||||
|
function calculateNameColorForUser(id: string) {
|
||||||
|
const { lightness } = settings.use(["lightness"]);
|
||||||
|
const idHash = useMemo(() => h64(id), [id]);
|
||||||
|
|
||||||
|
return `hsl(${idHash % 360n}, 100%, ${lightness}%)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
lightness: {
|
||||||
|
description: "Lightness, in %. Change if the colors are too light or too dark",
|
||||||
|
type: OptionType.NUMBER,
|
||||||
|
default: 70,
|
||||||
|
},
|
||||||
|
memberListColors: {
|
||||||
|
description: "Replace role colors in the member list",
|
||||||
|
restartNeeded: true,
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "IrcColors",
|
||||||
|
description: "Makes username colors in chat unique, like in IRC clients",
|
||||||
|
authors: [Devs.Grzesiek11],
|
||||||
|
settings,
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: '="SYSTEM_TAG"',
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=className:\i\.username,style:.{0,50}:void 0,)/,
|
||||||
|
replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "#{intl::GUILD_OWNER}),children:",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=\.MEMBER_LIST}\),\[\]\),)(.+?color:)null!=.{0,50}?(?=,)/,
|
||||||
|
replace: (_, rest) => `ircColor=$self.calculateNameColorForListContext(arguments[0]),${rest}ircColor`
|
||||||
|
},
|
||||||
|
predicate: () => settings.store.memberListColors
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
calculateNameColorForMessageContext(context: any) {
|
||||||
|
const id = context?.message?.author?.id;
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return calculateNameColorForUser(id);
|
||||||
|
},
|
||||||
|
calculateNameColorForListContext(context: any) {
|
||||||
|
const id = context?.user?.id;
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return calculateNameColorForUser(id);
|
||||||
|
}
|
||||||
|
});
|
|
@ -86,7 +86,7 @@ const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
|
||||||
|
|
||||||
const logger = new Logger("LastFMRichPresence");
|
const logger = new Logger("LastFMRichPresence");
|
||||||
|
|
||||||
const presenceStore = findByPropsLazy("getLocalPresence");
|
const PresenceStore = findByPropsLazy("getLocalPresence");
|
||||||
|
|
||||||
async function getApplicationAsset(key: string): Promise<string> {
|
async function getApplicationAsset(key: string): Promise<string> {
|
||||||
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
||||||
|
@ -124,6 +124,11 @@ const settings = definePluginSettings({
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
hideWithActivity: {
|
||||||
|
description: "Hide Last.fm presence if you have any other presence",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
statusName: {
|
statusName: {
|
||||||
description: "custom status text",
|
description: "custom status text",
|
||||||
type: OptionType.STRING,
|
type: OptionType.STRING,
|
||||||
|
@ -274,13 +279,17 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
async getActivity(): Promise<Activity | null> {
|
async getActivity(): Promise<Activity | null> {
|
||||||
if (settings.store.hideWithSpotify) {
|
if (settings.store.hideWithActivity) {
|
||||||
for (const activity of presenceStore.getActivities()) {
|
if (PresenceStore.getActivities().some(a => a.application_id !== applicationId)) {
|
||||||
if (activity.type === ActivityType.LISTENING && activity.application_id !== applicationId) {
|
|
||||||
// there is already music status because of Spotify or richerCider (probably more)
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings.store.hideWithSpotify) {
|
||||||
|
if (PresenceStore.getActivities().some(a => a.type === ActivityType.LISTENING && a.application_id !== applicationId)) {
|
||||||
|
// there is already music status because of Spotify or richerCider (probably more)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const trackData = await this.fetchTrackData();
|
const trackData = await this.fetchTrackData();
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".ROLE_MENTION)",
|
find: ".ROLE_MENTION)",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /children:\[\i&&.{0,50}\.RoleDot.{0,300},\i(?=\])/,
|
match: /children:\[\i&&.{0,100}className:\i.roleDot,.{0,200},\i(?=\])/,
|
||||||
replace: "$&,$self.renderRoleIcon(arguments[0])"
|
replace: "$&,$self.renderRoleIcon(arguments[0])"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
|
@ -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 { addClickListener, removeClickListener } from "@api/MessageEvents";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
@ -57,15 +56,20 @@ export default definePlugin({
|
||||||
name: "MessageClickActions",
|
name: "MessageClickActions",
|
||||||
description: "Hold Backspace and click to delete, double click to edit/reply",
|
description: "Hold Backspace and click to delete, double click to edit/reply",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["MessageEventsAPI"],
|
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
document.addEventListener("keydown", keydown);
|
document.addEventListener("keydown", keydown);
|
||||||
document.addEventListener("keyup", keyup);
|
document.addEventListener("keyup", keyup);
|
||||||
|
},
|
||||||
|
|
||||||
this.onClick = addClickListener((msg: any, channel, event) => {
|
stop() {
|
||||||
|
document.removeEventListener("keydown", keydown);
|
||||||
|
document.removeEventListener("keyup", keyup);
|
||||||
|
},
|
||||||
|
|
||||||
|
onMessageClick(msg: any, channel, event) {
|
||||||
const isMe = msg.author.id === UserStore.getCurrentUser().id;
|
const isMe = msg.author.id === UserStore.getCurrentUser().id;
|
||||||
if (!isDeletePressed) {
|
if (!isDeletePressed) {
|
||||||
if (event.detail < 2) return;
|
if (event.detail < 2) return;
|
||||||
|
@ -111,12 +115,5 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeClickListener(this.onClick);
|
|
||||||
document.removeEventListener("keydown", keydown);
|
|
||||||
document.removeEventListener("keyup", keyup);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { isNonNullish } from "@utils/guards";
|
import { isNonNullish } from "@utils/guards";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findExportedComponentLazy } from "@webpack";
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
import { SnowflakeUtils, Tooltip } from "@webpack/common";
|
import { SnowflakeUtils, Tooltip } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ interface Diff {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DISCORD_KT_DELAY = 1471228928;
|
const DISCORD_KT_DELAY = 1471228928;
|
||||||
const HiddenVisually = findExportedComponentLazy("HiddenVisually");
|
const HiddenVisually = findComponentByCodeLazy(".hiddenVisually]:");
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MessageLatency",
|
name: "MessageLatency",
|
||||||
|
@ -162,7 +162,7 @@ export default definePlugin({
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</Tooltip>;
|
</Tooltip>;
|
||||||
});
|
}, { noop: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
Icon({ delta, fill, props }: {
|
Icon({ delta, fill, props }: {
|
||||||
|
|
|
@ -16,7 +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 { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories";
|
||||||
import { updateMessage } from "@api/MessageUpdater";
|
import { updateMessage } from "@api/MessageUpdater";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
|
@ -120,11 +120,11 @@ const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
clearMessageCache: {
|
clearMessageCache: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "Clear the linked message cache",
|
component: () => (
|
||||||
component: () =>
|
|
||||||
<Button onClick={() => messageCache.clear()}>
|
<Button onClick={() => messageCache.clear()}>
|
||||||
Clear the linked message cache
|
Clear the linked message cache
|
||||||
</Button>
|
</Button>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -373,7 +373,7 @@ export default definePlugin({
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
addAccessory("messageLinkEmbed", props => {
|
addMessageAccessory("messageLinkEmbed", props => {
|
||||||
if (!messageLinkRegex.test(props.message.content))
|
if (!messageLinkRegex.test(props.message.content))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -391,6 +391,6 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
removeAccessory("messageLinkEmbed");
|
removeMessageAccessory("messageLinkEmbed");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -211,7 +211,8 @@ export default definePlugin({
|
||||||
collapseDeleted: {
|
collapseDeleted: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Whether to collapse deleted messages, similar to blocked messages",
|
description: "Whether to collapse deleted messages, similar to blocked messages",
|
||||||
default: false
|
default: false,
|
||||||
|
restartNeeded: true,
|
||||||
},
|
},
|
||||||
logEdits: {
|
logEdits: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
|
@ -500,7 +501,7 @@ export default definePlugin({
|
||||||
|
|
||||||
{
|
{
|
||||||
// Message context base menu
|
// Message context base menu
|
||||||
find: "useMessageMenu:",
|
find: ".MESSAGE,commandTargetId:",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Remove the first section if message is deleted
|
// Remove the first section if message is deleted
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands";
|
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands";
|
||||||
import * as DataStore from "@api/DataStore";
|
import * as DataStore from "@api/DataStore";
|
||||||
import { Settings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
@ -29,23 +29,23 @@ const MessageTagsMarker = Symbol("MessageTags");
|
||||||
interface Tag {
|
interface Tag {
|
||||||
name: string;
|
name: string;
|
||||||
message: string;
|
message: string;
|
||||||
enabled: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTags = () => DataStore.get(DATA_KEY).then<Tag[]>(t => t ?? []);
|
function getTags() {
|
||||||
const getTag = (name: string) => DataStore.get(DATA_KEY).then<Tag | null>((t: Tag[]) => (t ?? []).find((tt: Tag) => tt.name === name) ?? null);
|
return settings.store.tagsList;
|
||||||
const addTag = async (tag: Tag) => {
|
}
|
||||||
const tags = await getTags();
|
|
||||||
tags.push(tag);
|
function getTag(name: string) {
|
||||||
DataStore.set(DATA_KEY, tags);
|
return settings.store.tagsList[name] ?? null;
|
||||||
return tags;
|
}
|
||||||
};
|
|
||||||
const removeTag = async (name: string) => {
|
function addTag(tag: Tag) {
|
||||||
let tags = await getTags();
|
settings.store.tagsList[tag.name] = tag;
|
||||||
tags = await tags.filter((t: Tag) => t.name !== name);
|
}
|
||||||
DataStore.set(DATA_KEY, tags);
|
|
||||||
return tags;
|
function removeTag(name: string) {
|
||||||
};
|
delete settings.store.tagsList[name];
|
||||||
|
}
|
||||||
|
|
||||||
function createTagCommand(tag: Tag) {
|
function createTagCommand(tag: Tag) {
|
||||||
registerCommand({
|
registerCommand({
|
||||||
|
@ -53,14 +53,14 @@ function createTagCommand(tag: Tag) {
|
||||||
description: tag.name,
|
description: tag.name,
|
||||||
inputType: ApplicationCommandInputType.BUILT_IN_TEXT,
|
inputType: ApplicationCommandInputType.BUILT_IN_TEXT,
|
||||||
execute: async (_, ctx) => {
|
execute: async (_, ctx) => {
|
||||||
if (!await getTag(tag.name)) {
|
if (!getTag(tag.name)) {
|
||||||
sendBotMessage(ctx.channel.id, {
|
sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)`
|
content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)`
|
||||||
});
|
});
|
||||||
return { content: `/${tag.name}` };
|
return { content: `/${tag.name}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.plugins.MessageTags.clyde) sendBotMessage(ctx.channel.id, {
|
if (settings.store.clyde) sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} The tag **${tag.name}** has been sent!`
|
content: `${EMOTE} The tag **${tag.name}** has been sent!`
|
||||||
});
|
});
|
||||||
return { content: tag.message.replaceAll("\\n", "\n") };
|
return { content: tag.message.replaceAll("\\n", "\n") };
|
||||||
|
@ -69,22 +69,38 @@ function createTagCommand(tag: Tag) {
|
||||||
}, "CustomTags");
|
}, "CustomTags");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
export default definePlugin({
|
|
||||||
name: "MessageTags",
|
|
||||||
description: "Allows you to save messages and to use them with a simple command.",
|
|
||||||
authors: [Devs.Luna],
|
|
||||||
options: {
|
|
||||||
clyde: {
|
clyde: {
|
||||||
name: "Clyde message on send",
|
name: "Clyde message on send",
|
||||||
description: "If enabled, clyde will send you an ephemeral message when a tag was used.",
|
description: "If enabled, clyde will send you an ephemeral message when a tag was used.",
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
default: true
|
default: true
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
tagsList: {
|
||||||
|
type: OptionType.CUSTOM,
|
||||||
|
default: {} as Record<string, Tag>,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "MessageTags",
|
||||||
|
description: "Allows you to save messages and to use them with a simple command.",
|
||||||
|
authors: [Devs.Luna],
|
||||||
|
settings,
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
for (const tag of await getTags()) createTagCommand(tag);
|
// TODO: Remove DataStore tags migration once enough time has passed
|
||||||
|
const oldTags = await DataStore.get<Tag[]>(DATA_KEY);
|
||||||
|
if (oldTags != null) {
|
||||||
|
// @ts-ignore
|
||||||
|
settings.store.tagsList = Object.fromEntries(oldTags.map(oldTag => (delete oldTag.enabled, [oldTag.name, oldTag])));
|
||||||
|
await DataStore.del(DATA_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = getTags();
|
||||||
|
for (const tagName in tags) {
|
||||||
|
createTagCommand(tags[tagName]);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
commands: [
|
commands: [
|
||||||
|
@ -153,19 +169,18 @@ export default definePlugin({
|
||||||
const name: string = findOption(args[0].options, "tag-name", "");
|
const name: string = findOption(args[0].options, "tag-name", "");
|
||||||
const message: string = findOption(args[0].options, "message", "");
|
const message: string = findOption(args[0].options, "message", "");
|
||||||
|
|
||||||
if (await getTag(name))
|
if (getTag(name))
|
||||||
return sendBotMessage(ctx.channel.id, {
|
return sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} A Tag with the name **${name}** already exists!`
|
content: `${EMOTE} A Tag with the name **${name}** already exists!`
|
||||||
});
|
});
|
||||||
|
|
||||||
const tag = {
|
const tag = {
|
||||||
name: name,
|
name: name,
|
||||||
enabled: true,
|
|
||||||
message: message
|
message: message
|
||||||
};
|
};
|
||||||
|
|
||||||
createTagCommand(tag);
|
createTagCommand(tag);
|
||||||
await addTag(tag);
|
addTag(tag);
|
||||||
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} Successfully created the tag **${name}**!`
|
content: `${EMOTE} Successfully created the tag **${name}**!`
|
||||||
|
@ -175,13 +190,13 @@ export default definePlugin({
|
||||||
case "delete": {
|
case "delete": {
|
||||||
const name: string = findOption(args[0].options, "tag-name", "");
|
const name: string = findOption(args[0].options, "tag-name", "");
|
||||||
|
|
||||||
if (!await getTag(name))
|
if (!getTag(name))
|
||||||
return sendBotMessage(ctx.channel.id, {
|
return sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} A Tag with the name **${name}** does not exist!`
|
content: `${EMOTE} A Tag with the name **${name}** does not exist!`
|
||||||
});
|
});
|
||||||
|
|
||||||
unregisterCommand(name);
|
unregisterCommand(name);
|
||||||
await removeTag(name);
|
removeTag(name);
|
||||||
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} Successfully deleted the tag **${name}**!`
|
content: `${EMOTE} Successfully deleted the tag **${name}**!`
|
||||||
|
@ -192,10 +207,8 @@ export default definePlugin({
|
||||||
sendBotMessage(ctx.channel.id, {
|
sendBotMessage(ctx.channel.id, {
|
||||||
embeds: [
|
embeds: [
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
|
||||||
title: "All Tags:",
|
title: "All Tags:",
|
||||||
// @ts-ignore
|
description: Object.values(getTags())
|
||||||
description: (await getTags())
|
|
||||||
.map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`)
|
.map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`)
|
||||||
.join("\n") || `${EMOTE} Woops! There are no tags yet, use \`/tags create\` to create one!`,
|
.join("\n") || `${EMOTE} Woops! There are no tags yet, use \`/tags create\` to create one!`,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -208,7 +221,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
case "preview": {
|
case "preview": {
|
||||||
const name: string = findOption(args[0].options, "tag-name", "");
|
const name: string = findOption(args[0].options, "tag-name", "");
|
||||||
const tag = await getTag(name);
|
const tag = getTag(name);
|
||||||
|
|
||||||
if (!tag)
|
if (!tag)
|
||||||
return sendBotMessage(ctx.channel.id, {
|
return sendBotMessage(ctx.channel.id, {
|
||||||
|
|
|
@ -1,372 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { Flex } from "@components/Flex";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import { getIntlMessage } from "@utils/discord";
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
|
||||||
import { findByCodeLazy, findLazy } from "@webpack";
|
|
||||||
import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip } from "@webpack/common";
|
|
||||||
import type { Permissions, RC } from "@webpack/types";
|
|
||||||
import type { Channel, Guild, Message, User } from "discord-types/general";
|
|
||||||
|
|
||||||
interface Tag {
|
|
||||||
// name used for identifying, must be alphanumeric + underscores
|
|
||||||
name: string;
|
|
||||||
// name shown on the tag itself, can be anything probably; automatically uppercase'd
|
|
||||||
displayName: string;
|
|
||||||
description: string;
|
|
||||||
permissions?: Permissions[];
|
|
||||||
condition?(message: Message | null, user: User, channel: Channel): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TagSetting {
|
|
||||||
text: string;
|
|
||||||
showInChat: boolean;
|
|
||||||
showInNotChat: boolean;
|
|
||||||
}
|
|
||||||
interface TagSettings {
|
|
||||||
WEBHOOK: TagSetting,
|
|
||||||
OWNER: TagSetting,
|
|
||||||
ADMINISTRATOR: TagSetting,
|
|
||||||
MODERATOR_STAFF: TagSetting,
|
|
||||||
MODERATOR: TagSetting,
|
|
||||||
VOICE_MODERATOR: TagSetting,
|
|
||||||
TRIAL_MODERATOR: TagSetting,
|
|
||||||
[k: string]: TagSetting;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PermissionStore.computePermissions will not work here since it only gets permissions for the current user
|
|
||||||
const computePermissions: (options: {
|
|
||||||
user?: { id: string; } | string | null;
|
|
||||||
context?: Guild | Channel | null;
|
|
||||||
overwrites?: Channel["permissionOverwrites"] | null;
|
|
||||||
checkElevated?: boolean /* = true */;
|
|
||||||
excludeGuildPermissions?: boolean /* = false */;
|
|
||||||
}) => bigint = findByCodeLazy(".getCurrentUser()", ".computeLurkerPermissionsAllowList()");
|
|
||||||
|
|
||||||
const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
|
|
||||||
|
|
||||||
const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot();
|
|
||||||
|
|
||||||
const tags: Tag[] = [
|
|
||||||
{
|
|
||||||
name: "WEBHOOK",
|
|
||||||
displayName: "Webhook",
|
|
||||||
description: "Messages sent by webhooks",
|
|
||||||
condition: isWebhook
|
|
||||||
}, {
|
|
||||||
name: "OWNER",
|
|
||||||
displayName: "Owner",
|
|
||||||
description: "Owns the server",
|
|
||||||
condition: (_, user, channel) => GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id
|
|
||||||
}, {
|
|
||||||
name: "ADMINISTRATOR",
|
|
||||||
displayName: "Admin",
|
|
||||||
description: "Has the administrator permission",
|
|
||||||
permissions: ["ADMINISTRATOR"]
|
|
||||||
}, {
|
|
||||||
name: "MODERATOR_STAFF",
|
|
||||||
displayName: "Staff",
|
|
||||||
description: "Can manage the server, channels or roles",
|
|
||||||
permissions: ["MANAGE_GUILD", "MANAGE_CHANNELS", "MANAGE_ROLES"]
|
|
||||||
}, {
|
|
||||||
name: "MODERATOR",
|
|
||||||
displayName: "Mod",
|
|
||||||
description: "Can manage messages or kick/ban people",
|
|
||||||
permissions: ["MANAGE_MESSAGES", "KICK_MEMBERS", "BAN_MEMBERS"]
|
|
||||||
}, {
|
|
||||||
name: "VOICE_MODERATOR",
|
|
||||||
displayName: "VC Mod",
|
|
||||||
description: "Can manage voice chats",
|
|
||||||
permissions: ["MOVE_MEMBERS", "MUTE_MEMBERS", "DEAFEN_MEMBERS"]
|
|
||||||
}, {
|
|
||||||
name: "CHAT_MODERATOR",
|
|
||||||
displayName: "Chat Mod",
|
|
||||||
description: "Can timeout people",
|
|
||||||
permissions: ["MODERATE_MEMBERS"]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const defaultSettings = Object.fromEntries(
|
|
||||||
tags.map(({ name, displayName }) => [name, { text: displayName, showInChat: true, showInNotChat: true }])
|
|
||||||
) as TagSettings;
|
|
||||||
|
|
||||||
function SettingsComponent() {
|
|
||||||
const tagSettings = settings.store.tagSettings ??= defaultSettings;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex flexDirection="column">
|
|
||||||
{tags.map(t => (
|
|
||||||
<Card key={t.name} style={{ padding: "1em 1em 0" }}>
|
|
||||||
<Forms.FormTitle style={{ width: "fit-content" }}>
|
|
||||||
<Tooltip text={t.description}>
|
|
||||||
{({ onMouseEnter, onMouseLeave }) => (
|
|
||||||
<div
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
>
|
|
||||||
{t.displayName} Tag <Tag type={Tag.Types[t.name]} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
</Forms.FormTitle>
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
type="text"
|
|
||||||
value={tagSettings[t.name]?.text ?? t.displayName}
|
|
||||||
placeholder={`Text on tag (default: ${t.displayName})`}
|
|
||||||
onChange={v => tagSettings[t.name].text = v}
|
|
||||||
className={Margins.bottom16}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Switch
|
|
||||||
value={tagSettings[t.name]?.showInChat ?? true}
|
|
||||||
onChange={v => tagSettings[t.name].showInChat = v}
|
|
||||||
hideBorder
|
|
||||||
>
|
|
||||||
Show in messages
|
|
||||||
</Switch>
|
|
||||||
|
|
||||||
<Switch
|
|
||||||
value={tagSettings[t.name]?.showInNotChat ?? true}
|
|
||||||
onChange={v => tagSettings[t.name].showInNotChat = v}
|
|
||||||
hideBorder
|
|
||||||
>
|
|
||||||
Show in member list and profiles
|
|
||||||
</Switch>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
dontShowForBots: {
|
|
||||||
description: "Don't show extra tags for bots (excluding webhooks)",
|
|
||||||
type: OptionType.BOOLEAN
|
|
||||||
},
|
|
||||||
dontShowBotTag: {
|
|
||||||
description: "Only show extra tags for bots / Hide [BOT] text",
|
|
||||||
type: OptionType.BOOLEAN
|
|
||||||
},
|
|
||||||
tagSettings: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
component: SettingsComponent,
|
|
||||||
description: "fill me"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "MoreUserTags",
|
|
||||||
description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)",
|
|
||||||
authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN],
|
|
||||||
settings,
|
|
||||||
patches: [
|
|
||||||
// add tags to the tag list
|
|
||||||
{
|
|
||||||
find: ".ORIGINAL_POSTER=",
|
|
||||||
replacement: {
|
|
||||||
match: /(?=(\i)\[\i\.BOT)/,
|
|
||||||
replace: "$self.genTagTypes($1);"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: "#{intl::DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL}",
|
|
||||||
replacement: [
|
|
||||||
// make the tag show the right text
|
|
||||||
{
|
|
||||||
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(.{0,40}#{intl::APP_TAG}\))/,
|
|
||||||
replace: (_, origSwitch, variant, tags, displayedText, originalText) =>
|
|
||||||
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}],${originalText})}`
|
|
||||||
},
|
|
||||||
// show OP tags correctly
|
|
||||||
{
|
|
||||||
match: /(\i)=(\i)===\i(?:\.\i)?\.ORIGINAL_POSTER/,
|
|
||||||
replace: "$1=$self.isOPTag($2)"
|
|
||||||
},
|
|
||||||
// add HTML data attributes (for easier theming)
|
|
||||||
{
|
|
||||||
match: /.botText,children:(\i)}\)]/,
|
|
||||||
replace: "$&,'data-tag':$1.toLowerCase()"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// in messages
|
|
||||||
{
|
|
||||||
find: ".Types.ORIGINAL_POSTER",
|
|
||||||
replacement: {
|
|
||||||
match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/,
|
|
||||||
replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// in the member list
|
|
||||||
{
|
|
||||||
find: "#{intl::GUILD_OWNER}),children:",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<type>\i)=\(null==.{0,100}\.BOT;return null!=(?<user>\i)&&\i\.bot/,
|
|
||||||
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }); return typeof $<type> === 'number'"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// pass channel id down props to be used in profiles
|
|
||||||
{
|
|
||||||
find: ".hasAvatarForGuild(null==",
|
|
||||||
replacement: {
|
|
||||||
match: /(?=usernameIcon:)/,
|
|
||||||
replace: "moreTags_channelId:arguments[0].channelId,"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: "#{intl::USER_PROFILE_PRONOUNS}",
|
|
||||||
replacement: {
|
|
||||||
match: /(?=,hideBotTag:!0)/,
|
|
||||||
replace: ",moreTags_channelId:arguments[0].moreTags_channelId"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// in profiles
|
|
||||||
{
|
|
||||||
find: ",overrideDiscriminator:",
|
|
||||||
group: true,
|
|
||||||
replacement: [
|
|
||||||
{
|
|
||||||
// prevent channel id from getting ghosted
|
|
||||||
// it's either this or extremely long lookbehind
|
|
||||||
match: /user:\i,nick:\i,/,
|
|
||||||
replace: "$&moreTags_channelId,"
|
|
||||||
}, {
|
|
||||||
match: /,botType:(\i),botVerified:(\i),(?!discriminatorClass:)(?<=user:(\i).+?)/g,
|
|
||||||
replace: ",botType:$self.getTag({user:$3,channelId:moreTags_channelId,origType:$1,location:'not-chat'}),botVerified:$2,"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
start() {
|
|
||||||
settings.store.tagSettings ??= defaultSettings;
|
|
||||||
|
|
||||||
// newly added field might be missing from old users
|
|
||||||
settings.store.tagSettings.CHAT_MODERATOR ??= {
|
|
||||||
text: "Chat Mod",
|
|
||||||
showInChat: true,
|
|
||||||
showInNotChat: true
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getPermissions(user: User, channel: Channel): string[] {
|
|
||||||
const guild = GuildStore.getGuild(channel?.guild_id);
|
|
||||||
if (!guild) return [];
|
|
||||||
|
|
||||||
const permissions = computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
|
|
||||||
return Object.entries(PermissionsBits)
|
|
||||||
.map(([perm, permInt]) =>
|
|
||||||
permissions & permInt ? perm : ""
|
|
||||||
)
|
|
||||||
.filter(Boolean);
|
|
||||||
},
|
|
||||||
|
|
||||||
genTagTypes(obj) {
|
|
||||||
let i = 100;
|
|
||||||
tags.forEach(({ name }) => {
|
|
||||||
obj[name] = ++i;
|
|
||||||
obj[i] = name;
|
|
||||||
obj[`${name}-BOT`] = ++i;
|
|
||||||
obj[i] = `${name}-BOT`;
|
|
||||||
obj[`${name}-OP`] = ++i;
|
|
||||||
obj[i] = `${name}-OP`;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]),
|
|
||||||
|
|
||||||
getTagText(passedTagName: string, originalText: string) {
|
|
||||||
try {
|
|
||||||
const [tagName, variant] = passedTagName.split("-");
|
|
||||||
if (!passedTagName) return getIntlMessage("APP_TAG");
|
|
||||||
const tag = tags.find(({ name }) => tagName === name);
|
|
||||||
if (!tag) return getIntlMessage("APP_TAG");
|
|
||||||
if (variant === "BOT" && tagName !== "WEBHOOK" && this.settings.store.dontShowForBots) return getIntlMessage("APP_TAG");
|
|
||||||
|
|
||||||
const tagText = settings.store.tagSettings?.[tag.name]?.text || tag.displayName;
|
|
||||||
switch (variant) {
|
|
||||||
case "OP":
|
|
||||||
return `${getIntlMessage("BOT_TAG_FORUM_ORIGINAL_POSTER")} • ${tagText}`;
|
|
||||||
case "BOT":
|
|
||||||
return `${getIntlMessage("APP_TAG")} • ${tagText}`;
|
|
||||||
default:
|
|
||||||
return tagText;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return originalText;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getTag({
|
|
||||||
message, user, channelId, origType, location, channel
|
|
||||||
}: {
|
|
||||||
message?: Message,
|
|
||||||
user: User & { isClyde(): boolean; },
|
|
||||||
channel?: Channel & { isForumPost(): boolean; isMediaPost(): boolean; },
|
|
||||||
channelId?: string;
|
|
||||||
origType?: number;
|
|
||||||
location: "chat" | "not-chat";
|
|
||||||
}): number | null {
|
|
||||||
if (!user)
|
|
||||||
return null;
|
|
||||||
if (location === "chat" && user.id === "1")
|
|
||||||
return Tag.Types.OFFICIAL;
|
|
||||||
if (user.isClyde())
|
|
||||||
return Tag.Types.AI;
|
|
||||||
|
|
||||||
let type = typeof origType === "number" ? origType : null;
|
|
||||||
|
|
||||||
channel ??= ChannelStore.getChannel(channelId!) as any;
|
|
||||||
if (!channel) return type;
|
|
||||||
|
|
||||||
const settings = this.settings.store;
|
|
||||||
const perms = this.getPermissions(user, channel);
|
|
||||||
|
|
||||||
for (const tag of tags) {
|
|
||||||
if (location === "chat" && !settings.tagSettings[tag.name].showInChat) continue;
|
|
||||||
if (location === "not-chat" && !settings.tagSettings[tag.name].showInNotChat) continue;
|
|
||||||
|
|
||||||
// If the owner tag is disabled, and the user is the owner of the guild,
|
|
||||||
// avoid adding other tags because the owner will always match the condition for them
|
|
||||||
if (
|
|
||||||
tag.name !== "OWNER" &&
|
|
||||||
GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id &&
|
|
||||||
(location === "chat" && !settings.tagSettings.OWNER.showInChat) ||
|
|
||||||
(location === "not-chat" && !settings.tagSettings.OWNER.showInNotChat)
|
|
||||||
) continue;
|
|
||||||
|
|
||||||
if (
|
|
||||||
tag.permissions?.some(perm => perms.includes(perm)) ||
|
|
||||||
(tag.condition?.(message!, user, channel))
|
|
||||||
) {
|
|
||||||
if ((channel.isForumPost() || channel.isMediaPost()) && channel.ownerId === user.id)
|
|
||||||
type = Tag.Types[`${tag.name}-OP`];
|
|
||||||
else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag)
|
|
||||||
type = Tag.Types[`${tag.name}-BOT`];
|
|
||||||
else
|
|
||||||
type = Tag.Types[tag.name];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -16,7 +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 { Settings } from "@api/Settings";
|
import { definePluginSettings, migratePluginSetting } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { runtimeHashMessageKey } from "@utils/intlHash";
|
import { runtimeHashMessageKey } from "@utils/intlHash";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
|
@ -32,10 +32,29 @@ interface MessageDeleteProps {
|
||||||
collapsedReason: () => any;
|
collapsedReason: () => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove this migration once enough time has passed
|
||||||
|
migratePluginSetting("NoBlockedMessages", "ignoreBlockedMessages", "ignoreMessages");
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
ignoreMessages: {
|
||||||
|
description: "Completely ignores incoming messages from blocked and ignored (if enabled) users",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: false,
|
||||||
|
restartNeeded: true
|
||||||
|
},
|
||||||
|
applyToIgnoredUsers: {
|
||||||
|
description: "Additionally apply to 'ignored' users",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
restartNeeded: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NoBlockedMessages",
|
name: "NoBlockedMessages",
|
||||||
description: "Hides all blocked messages from chat completely.",
|
description: "Hides all blocked/ignored messages from chat completely",
|
||||||
authors: [Devs.rushii, Devs.Samu],
|
authors: [Devs.rushii, Devs.Samu, Devs.jamesbt365],
|
||||||
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "#{intl::BLOCKED_MESSAGES_HIDE}",
|
find: "#{intl::BLOCKED_MESSAGES_HIDE}",
|
||||||
|
@ -51,38 +70,40 @@ export default definePlugin({
|
||||||
'"ReadStateStore"'
|
'"ReadStateStore"'
|
||||||
].map(find => ({
|
].map(find => ({
|
||||||
find,
|
find,
|
||||||
predicate: () => Settings.plugins.NoBlockedMessages.ignoreBlockedMessages === true,
|
predicate: () => settings.store.ignoreMessages,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<=function (\i)\((\i)\){)(?=.*MESSAGE_CREATE:\1)/,
|
match: /(?<=function (\i)\((\i)\){)(?=.*MESSAGE_CREATE:\1)/,
|
||||||
replace: (_, _funcName, props) => `if($self.isBlocked(${props}.message))return;`
|
replace: (_, _funcName, props) => `if($self.shouldIgnoreMessage(${props}.message))return;`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}))
|
}))
|
||||||
],
|
],
|
||||||
options: {
|
|
||||||
ignoreBlockedMessages: {
|
|
||||||
description: "Completely ignores (recent) incoming messages from blocked users (locally).",
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
default: false,
|
|
||||||
restartNeeded: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
isBlocked(message: Message) {
|
shouldIgnoreMessage(message: Message) {
|
||||||
try {
|
try {
|
||||||
return RelationshipStore.isBlocked(message.author.id);
|
if (RelationshipStore.isBlocked(message.author.id)) {
|
||||||
} catch (e) {
|
return true;
|
||||||
new Logger("NoBlockedMessages").error("Failed to check if user is blocked:", e);
|
|
||||||
}
|
}
|
||||||
},
|
return settings.store.applyToIgnoredUsers && RelationshipStore.isIgnored(message.author.id);
|
||||||
|
|
||||||
shouldHide(props: MessageDeleteProps) {
|
|
||||||
try {
|
|
||||||
return props.collapsedReason() === i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")]();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
new Logger("NoBlockedMessages").error("Failed to check if user is blocked or ignored:", e);
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldHide(props: MessageDeleteProps): boolean {
|
||||||
|
try {
|
||||||
|
const collapsedReason = props.collapsedReason();
|
||||||
|
const blockedReason = i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")]();
|
||||||
|
const ignoredReason = settings.store.applyToIgnoredUsers
|
||||||
|
? i18n.t[runtimeHashMessageKey("IGNORED_MESSAGE_COUNT")]()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return collapsedReason === blockedReason || collapsedReason === ignoredReason;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -100,8 +100,8 @@ export default definePlugin({
|
||||||
replace: "true"
|
replace: "true"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /!\(0,\i\.isDesktop\)\(\)/,
|
match: /(!)?\(0,\i\.isDesktop\)\(\)/,
|
||||||
replace: "false"
|
replace: (_, not) => not ? "false" : "true"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -46,8 +46,8 @@ export default definePlugin({
|
||||||
find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}",
|
find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /{(\i:function\(\){return \i},?){2}}/,
|
match: /{(?:\i:(?:function\(\){return |\(\)=>)\i}?,?){2}}/,
|
||||||
replace: m => m.replaceAll(canonicalizeMatch(/return \i/g), "return ()=>Promise.resolve(true)")
|
replace: m => m.replaceAll(canonicalizeMatch(/(function\(\){return |\(\)=>)\i/g), "$1()=>Promise.resolve(true)")
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
predicate: () => settings.store.onboarding
|
predicate: () => settings.store.onboarding
|
||||||
|
|
|
@ -6,12 +6,11 @@
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal";
|
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal";
|
||||||
import { extractAndLoadChunksLazy, findComponentByCodeLazy, findExportedComponentLazy } from "@webpack";
|
import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { Button, Forms, Text, TextInput, Toasts, useEffect, useState } from "@webpack/common";
|
import { Button, Forms, Text, TextInput, Toasts, useMemo, useState } from "@webpack/common";
|
||||||
|
|
||||||
import { DEFAULT_COLOR, SWATCHES } from "../constants";
|
import { DEFAULT_COLOR, SWATCHES } from "../constants";
|
||||||
import { categories, Category, createCategory, getCategory, updateCategory } from "../data";
|
import { categoryLen, createCategory, getCategory } from "../data";
|
||||||
import { forceUpdate } from "../index";
|
|
||||||
|
|
||||||
interface ColorPickerProps {
|
interface ColorPickerProps {
|
||||||
color: number | null;
|
color: number | null;
|
||||||
|
@ -31,7 +30,7 @@ interface ColorPickerWithSwatchesProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
|
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
|
||||||
const ColorPickerWithSwatches = findExportedComponentLazy<ColorPickerWithSwatchesProps>("ColorPicker", "CustomColorPicker");
|
const ColorPickerWithSwatches = findComponentByCodeLazy<ColorPickerWithSwatchesProps>('id:"color-picker"');
|
||||||
|
|
||||||
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/);
|
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/);
|
||||||
|
|
||||||
|
@ -39,45 +38,45 @@ const cl = classNameFactory("vc-pindms-modal-");
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
categoryId: string | null;
|
categoryId: string | null;
|
||||||
initalChannelId: string | null;
|
initialChannelId: string | null;
|
||||||
modalProps: ModalProps;
|
modalProps: ModalProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useCategory(categoryId: string | null, initalChannelId: string | null) {
|
function useCategory(categoryId: string | null, initalChannelId: string | null) {
|
||||||
const [category, setCategory] = useState<Category | null>(null);
|
const category = useMemo(() => {
|
||||||
|
if (categoryId) {
|
||||||
useEffect(() => {
|
return getCategory(categoryId);
|
||||||
if (categoryId)
|
} else if (initalChannelId) {
|
||||||
setCategory(getCategory(categoryId)!);
|
return {
|
||||||
else if (initalChannelId)
|
|
||||||
setCategory({
|
|
||||||
id: Toasts.genId(),
|
id: Toasts.genId(),
|
||||||
name: `Pin Category ${categories.length + 1}`,
|
name: `Pin Category ${categoryLen() + 1}`,
|
||||||
color: DEFAULT_COLOR,
|
color: DEFAULT_COLOR,
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
channels: [initalChannelId]
|
channels: [initalChannelId]
|
||||||
});
|
|
||||||
}, [categoryId, initalChannelId]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
category,
|
|
||||||
setCategory
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}, [categoryId, initalChannelId]);
|
||||||
|
|
||||||
export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Props) {
|
return category;
|
||||||
const { category, setCategory } = useCategory(categoryId, initalChannelId);
|
}
|
||||||
|
|
||||||
|
export function NewCategoryModal({ categoryId, modalProps, initialChannelId }: Props) {
|
||||||
|
const category = useCategory(categoryId, initialChannelId);
|
||||||
if (!category) return null;
|
if (!category) return null;
|
||||||
|
|
||||||
const onSave = async (e: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const [name, setName] = useState(category.name);
|
||||||
e.preventDefault();
|
const [color, setColor] = useState(category.color);
|
||||||
if (!categoryId)
|
|
||||||
await createCategory(category);
|
const onSave = (e: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
else
|
e.preventDefault();
|
||||||
await updateCategory(category);
|
|
||||||
|
category.name = name;
|
||||||
|
category.color = color;
|
||||||
|
|
||||||
|
if (!categoryId) {
|
||||||
|
createCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
forceUpdate();
|
|
||||||
modalProps.onClose();
|
modalProps.onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -93,25 +92,25 @@ export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Pr
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle>Name</Forms.FormTitle>
|
<Forms.FormTitle>Name</Forms.FormTitle>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={category.name}
|
value={name}
|
||||||
onChange={e => setCategory({ ...category, name: e })}
|
onChange={e => setName(e)}
|
||||||
/>
|
/>
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
<Forms.FormDivider />
|
<Forms.FormDivider />
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle>Color</Forms.FormTitle>
|
<Forms.FormTitle>Color</Forms.FormTitle>
|
||||||
<ColorPickerWithSwatches
|
<ColorPickerWithSwatches
|
||||||
key={category.name}
|
key={category.id}
|
||||||
defaultColor={DEFAULT_COLOR}
|
defaultColor={DEFAULT_COLOR}
|
||||||
colors={SWATCHES}
|
colors={SWATCHES}
|
||||||
onChange={c => setCategory({ ...category, color: c! })}
|
onChange={c => setColor(c!)}
|
||||||
value={category.color}
|
value={color}
|
||||||
renderDefaultButton={() => null}
|
renderDefaultButton={() => null}
|
||||||
renderCustomButton={() => (
|
renderCustomButton={() => (
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
color={category.color}
|
color={color}
|
||||||
onChange={c => setCategory({ ...category, color: c! })}
|
onChange={c => setColor(c!)}
|
||||||
key={category.name}
|
key={category.id}
|
||||||
showEyeDropper={false}
|
showEyeDropper={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -119,7 +118,7 @@ export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Pr
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button type="submit" onClick={onSave} disabled={!category.name}>{categoryId ? "Save" : "Create"}</Button>
|
<Button type="submit" onClick={onSave} disabled={!name}>{categoryId ? "Save" : "Create"}</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</form>
|
</form>
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
|
@ -129,6 +128,6 @@ export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Pr
|
||||||
export const openCategoryModal = (categoryId: string | null, channelId: string | null) =>
|
export const openCategoryModal = (categoryId: string | null, channelId: string | null) =>
|
||||||
openModalLazy(async () => {
|
openModalLazy(async () => {
|
||||||
await requireSettingsMenu();
|
await requireSettingsMenu();
|
||||||
return modalProps => <NewCategoryModal categoryId={categoryId} modalProps={modalProps} initalChannelId={channelId} />;
|
return modalProps => <NewCategoryModal categoryId={categoryId} modalProps={modalProps} initialChannelId={channelId} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { Menu } from "@webpack/common";
|
import { Menu } from "@webpack/common";
|
||||||
|
|
||||||
import { addChannelToCategory, canMoveChannelInDirection, categories, isPinned, moveChannel, removeChannelFromCategory } from "../data";
|
import { addChannelToCategory, canMoveChannelInDirection, currentUserCategories, isPinned, moveChannel, removeChannelFromCategory } from "../data";
|
||||||
import { forceUpdate, PinOrder, settings } from "../index";
|
import { PinOrder, settings } from "../index";
|
||||||
import { openCategoryModal } from "./CreateCategoryModal";
|
import { openCategoryModal } from "./CreateCategoryModal";
|
||||||
|
|
||||||
function createPinMenuItem(channelId: string) {
|
function createPinMenuItem(channelId: string) {
|
||||||
|
@ -31,12 +31,12 @@ function createPinMenuItem(channelId: string) {
|
||||||
<Menu.MenuSeparator />
|
<Menu.MenuSeparator />
|
||||||
|
|
||||||
{
|
{
|
||||||
categories.map(category => (
|
currentUserCategories.map(category => (
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
key={category.id}
|
key={category.id}
|
||||||
id={`pin-category-${category.id}`}
|
id={`pin-category-${category.id}`}
|
||||||
label={category.name}
|
label={category.name}
|
||||||
action={() => addChannelToCategory(channelId, category.id).then(forceUpdate)}
|
action={() => addChannelToCategory(channelId, category.id)}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ function createPinMenuItem(channelId: string) {
|
||||||
id="unpin-dm"
|
id="unpin-dm"
|
||||||
label="Unpin DM"
|
label="Unpin DM"
|
||||||
color="danger"
|
color="danger"
|
||||||
action={() => removeChannelFromCategory(channelId).then(forceUpdate)}
|
action={() => removeChannelFromCategory(channelId)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -57,7 +57,7 @@ function createPinMenuItem(channelId: string) {
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="move-up"
|
id="move-up"
|
||||||
label="Move Up"
|
label="Move Up"
|
||||||
action={() => moveChannel(channelId, -1).then(forceUpdate)}
|
action={() => moveChannel(channelId, -1)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ function createPinMenuItem(channelId: string) {
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="move-down"
|
id="move-down"
|
||||||
label="Move Down"
|
label="Move Down"
|
||||||
action={() => moveChannel(channelId, 1).then(forceUpdate)}
|
action={() => moveChannel(channelId, 1)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
import * as DataStore from "@api/DataStore";
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
|
import { useForceUpdater } from "@utils/react";
|
||||||
import { UserStore } from "@webpack/common";
|
import { UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import { DEFAULT_COLOR } from "./constants";
|
import { PinOrder, PrivateChannelSortStore, settings } from "./index";
|
||||||
import { forceUpdate, PinOrder, PrivateChannelSortStore, settings } from "./index";
|
|
||||||
|
|
||||||
export interface Category {
|
export interface Category {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -24,104 +24,92 @@ const CATEGORY_MIGRATED_PINDMS_KEY = "PinDMsMigratedPinDMs";
|
||||||
const CATEGORY_MIGRATED_KEY = "PinDMsMigratedOldCategories";
|
const CATEGORY_MIGRATED_KEY = "PinDMsMigratedOldCategories";
|
||||||
const OLD_CATEGORY_KEY = "BetterPinDMsCategories-";
|
const OLD_CATEGORY_KEY = "BetterPinDMsCategories-";
|
||||||
|
|
||||||
|
let forceUpdateDms: (() => void) | undefined = undefined;
|
||||||
export let categories: Category[] = [];
|
export let currentUserCategories: Category[] = [];
|
||||||
|
|
||||||
export async function saveCats(cats: Category[]) {
|
|
||||||
const { id } = UserStore.getCurrentUser();
|
|
||||||
await DataStore.set(CATEGORY_BASE_KEY + id, cats);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function init() {
|
export async function init() {
|
||||||
const id = UserStore.getCurrentUser()?.id;
|
await migrateData();
|
||||||
await initCategories(id);
|
|
||||||
await migrateData(id);
|
const userId = UserStore.getCurrentUser()?.id;
|
||||||
forceUpdate();
|
if (userId == null) return;
|
||||||
|
|
||||||
|
currentUserCategories = settings.store.userBasedCategoryList[userId] ??= [];
|
||||||
|
forceUpdateDms?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initCategories(userId: string) {
|
export function usePinnedDms() {
|
||||||
categories = await DataStore.get<Category[]>(CATEGORY_BASE_KEY + userId) ?? [];
|
forceUpdateDms = useForceUpdater();
|
||||||
|
settings.use(["pinOrder", "canCollapseDmSection", "dmSectionCollapsed", "userBasedCategoryList"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCategory(id: string) {
|
export function getCategory(id: string) {
|
||||||
return categories.find(c => c.id === id);
|
return currentUserCategories.find(c => c.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createCategory(category: Category) {
|
export function getCategoryByIndex(index: number) {
|
||||||
categories.push(category);
|
return currentUserCategories[index];
|
||||||
await saveCats(categories);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateCategory(category: Category) {
|
export function createCategory(category: Category) {
|
||||||
const index = categories.findIndex(c => c.id === category.id);
|
currentUserCategories.push(category);
|
||||||
if (index === -1) return;
|
|
||||||
|
|
||||||
categories[index] = category;
|
|
||||||
await saveCats(categories);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addChannelToCategory(channelId: string, categoryId: string) {
|
export function addChannelToCategory(channelId: string, categoryId: string) {
|
||||||
const category = categories.find(c => c.id === categoryId);
|
const category = currentUserCategories.find(c => c.id === categoryId);
|
||||||
if (!category) return;
|
if (category == null) return;
|
||||||
|
|
||||||
if (category.channels.includes(channelId)) return;
|
if (category.channels.includes(channelId)) return;
|
||||||
|
|
||||||
category.channels.push(channelId);
|
category.channels.push(channelId);
|
||||||
await saveCats(categories);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeChannelFromCategory(channelId: string) {
|
export function removeChannelFromCategory(channelId: string) {
|
||||||
const category = categories.find(c => c.channels.includes(channelId));
|
const category = currentUserCategories.find(c => c.channels.includes(channelId));
|
||||||
if (!category) return;
|
if (category == null) return;
|
||||||
|
|
||||||
category.channels = category.channels.filter(c => c !== channelId);
|
category.channels = category.channels.filter(c => c !== channelId);
|
||||||
await saveCats(categories);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeCategory(categoryId: string) {
|
export function removeCategory(categoryId: string) {
|
||||||
const catagory = categories.find(c => c.id === categoryId);
|
const categoryIndex = currentUserCategories.findIndex(c => c.id === categoryId);
|
||||||
if (!catagory) return;
|
if (categoryIndex === -1) return;
|
||||||
|
|
||||||
// catagory?.channels.forEach(c => removeChannelFromCategory(c));
|
currentUserCategories.splice(categoryIndex, 1);
|
||||||
categories = categories.filter(c => c.id !== categoryId);
|
|
||||||
await saveCats(categories);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function collapseCategory(id: string, value = true) {
|
export function collapseCategory(id: string, value = true) {
|
||||||
const category = categories.find(c => c.id === id);
|
const category = currentUserCategories.find(c => c.id === id);
|
||||||
if (!category) return;
|
if (category == null) return;
|
||||||
|
|
||||||
category.collapsed = value;
|
category.collapsed = value;
|
||||||
await saveCats(categories);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// utils
|
// Utils
|
||||||
export function isPinned(id: string) {
|
export function isPinned(id: string) {
|
||||||
return categories.some(c => c.channels.includes(id));
|
return currentUserCategories.some(c => c.channels.includes(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function categoryLen() {
|
export function categoryLen() {
|
||||||
return categories.length;
|
return currentUserCategories.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAllUncollapsedChannels() {
|
export function getAllUncollapsedChannels() {
|
||||||
if (settings.store.pinOrder === PinOrder.LastMessage) {
|
if (settings.store.pinOrder === PinOrder.LastMessage) {
|
||||||
const sortedChannels = PrivateChannelSortStore.getPrivateChannelIds();
|
const sortedChannels = PrivateChannelSortStore.getPrivateChannelIds();
|
||||||
return categories.filter(c => !c.collapsed).flatMap(c => sortedChannels.filter(channel => c.channels.includes(channel)));
|
return currentUserCategories.filter(c => !c.collapsed).flatMap(c => sortedChannels.filter(channel => c.channels.includes(channel)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return categories.filter(c => !c.collapsed).flatMap(c => c.channels);
|
return currentUserCategories.filter(c => !c.collapsed).flatMap(c => c.channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSections() {
|
export function getSections() {
|
||||||
return categories.reduce((acc, category) => {
|
return currentUserCategories.reduce((acc, category) => {
|
||||||
acc.push(category.channels.length === 0 ? 1 : category.channels.length);
|
acc.push(category.channels.length === 0 ? 1 : category.channels.length);
|
||||||
return acc;
|
return acc;
|
||||||
}, [] as number[]);
|
}, [] as number[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// move categories
|
// Move categories
|
||||||
export const canMoveArrayInDirection = (array: any[], index: number, direction: -1 | 1) => {
|
export const canMoveArrayInDirection = (array: any[], index: number, direction: -1 | 1) => {
|
||||||
const a = array[index];
|
const a = array[index];
|
||||||
const b = array[index + direction];
|
const b = array[index + direction];
|
||||||
|
@ -130,18 +118,18 @@ export const canMoveArrayInDirection = (array: any[], index: number, direction:
|
||||||
};
|
};
|
||||||
|
|
||||||
export const canMoveCategoryInDirection = (id: string, direction: -1 | 1) => {
|
export const canMoveCategoryInDirection = (id: string, direction: -1 | 1) => {
|
||||||
const index = categories.findIndex(m => m.id === id);
|
const categoryIndex = currentUserCategories.findIndex(m => m.id === id);
|
||||||
return canMoveArrayInDirection(categories, index, direction);
|
return canMoveArrayInDirection(currentUserCategories, categoryIndex, direction);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const canMoveCategory = (id: string) => canMoveCategoryInDirection(id, -1) || canMoveCategoryInDirection(id, 1);
|
export const canMoveCategory = (id: string) => canMoveCategoryInDirection(id, -1) || canMoveCategoryInDirection(id, 1);
|
||||||
|
|
||||||
export const canMoveChannelInDirection = (channelId: string, direction: -1 | 1) => {
|
export const canMoveChannelInDirection = (channelId: string, direction: -1 | 1) => {
|
||||||
const category = categories.find(c => c.channels.includes(channelId));
|
const category = currentUserCategories.find(c => c.channels.includes(channelId));
|
||||||
if (!category) return false;
|
if (category == null) return false;
|
||||||
|
|
||||||
const index = category.channels.indexOf(channelId);
|
const channelIndex = category.channels.indexOf(channelId);
|
||||||
return canMoveArrayInDirection(category.channels, index, direction);
|
return canMoveArrayInDirection(category.channels, channelIndex, direction);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -150,70 +138,44 @@ function swapElementsInArray(array: any[], index1: number, index2: number) {
|
||||||
[array[index1], array[index2]] = [array[index2], array[index1]];
|
[array[index1], array[index2]] = [array[index2], array[index1]];
|
||||||
}
|
}
|
||||||
|
|
||||||
// stolen from PinDMs
|
export function moveCategory(id: string, direction: -1 | 1) {
|
||||||
export async function moveCategory(id: string, direction: -1 | 1) {
|
const a = currentUserCategories.findIndex(m => m.id === id);
|
||||||
const a = categories.findIndex(m => m.id === id);
|
|
||||||
const b = a + direction;
|
const b = a + direction;
|
||||||
|
|
||||||
swapElementsInArray(categories, a, b);
|
swapElementsInArray(currentUserCategories, a, b);
|
||||||
|
|
||||||
await saveCats(categories);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function moveChannel(channelId: string, direction: -1 | 1) {
|
export function moveChannel(channelId: string, direction: -1 | 1) {
|
||||||
const category = categories.find(c => c.channels.includes(channelId));
|
const category = currentUserCategories.find(c => c.channels.includes(channelId));
|
||||||
if (!category) return;
|
if (category == null) return;
|
||||||
|
|
||||||
const a = category.channels.indexOf(channelId);
|
const a = category.channels.indexOf(channelId);
|
||||||
const b = a + direction;
|
const b = a + direction;
|
||||||
|
|
||||||
swapElementsInArray(category.channels, a, b);
|
swapElementsInArray(category.channels, a, b);
|
||||||
|
|
||||||
await saveCats(categories);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove DataStore PinnedDms migration once enough time has passed
|
||||||
|
async function migrateData() {
|
||||||
// migrate data
|
if (Settings.plugins.PinDMs.dmSectioncollapsed != null) {
|
||||||
const getPinDMsPins = () => (Settings.plugins.PinDMs.pinnedDMs || void 0)?.split(",") as string[] | undefined;
|
settings.store.dmSectionCollapsed = Settings.plugins.PinDMs.dmSectioncollapsed;
|
||||||
|
delete Settings.plugins.PinDMs.dmSectioncollapsed;
|
||||||
async function migratePinDMs() {
|
|
||||||
if (categories.some(m => m.id === "oldPins")) {
|
|
||||||
return await DataStore.set(CATEGORY_MIGRATED_PINDMS_KEY, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pindmspins = getPinDMsPins();
|
const dataStoreKeys = await DataStore.keys();
|
||||||
|
const pinDmsKeys = dataStoreKeys.map(key => String(key)).filter(key => key.startsWith(CATEGORY_BASE_KEY));
|
||||||
|
|
||||||
// we dont want duplicate pins
|
if (pinDmsKeys.length === 0) return;
|
||||||
const difference = [...new Set(pindmspins)]?.filter(m => !categories.some(c => c.channels.includes(m)));
|
|
||||||
if (difference?.length) {
|
for (const pinDmsKey of pinDmsKeys) {
|
||||||
categories.push({
|
const categories = await DataStore.get<Category[]>(pinDmsKey);
|
||||||
id: "oldPins",
|
if (categories == null) continue;
|
||||||
name: "Pins",
|
|
||||||
color: DEFAULT_COLOR,
|
const userId = pinDmsKey.replace(CATEGORY_BASE_KEY, "");
|
||||||
channels: difference
|
settings.store.userBasedCategoryList[userId] = categories;
|
||||||
});
|
|
||||||
|
await DataStore.del(pinDmsKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
await DataStore.set(CATEGORY_MIGRATED_PINDMS_KEY, true);
|
await Promise.all([DataStore.del(CATEGORY_MIGRATED_PINDMS_KEY), DataStore.del(CATEGORY_MIGRATED_KEY), DataStore.del(OLD_CATEGORY_KEY)]);
|
||||||
}
|
|
||||||
|
|
||||||
async function migrateOldCategories(userId: string) {
|
|
||||||
const oldCats = await DataStore.get<Category[]>(OLD_CATEGORY_KEY + userId);
|
|
||||||
// dont want to migrate if the user has already has categories.
|
|
||||||
if (categories.length === 0 && oldCats?.length) {
|
|
||||||
categories.push(...(oldCats.filter(m => m.id !== "oldPins")));
|
|
||||||
}
|
|
||||||
await DataStore.set(CATEGORY_MIGRATED_KEY, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function migrateData(userId: string) {
|
|
||||||
const m1 = await DataStore.get(CATEGORY_MIGRATED_KEY), m2 = await DataStore.get(CATEGORY_MIGRATED_PINDMS_KEY);
|
|
||||||
if (m1 && m2) return;
|
|
||||||
|
|
||||||
// want to migrate the old categories first and then slove any conflicts with the PinDMs pins
|
|
||||||
if (!m1) await migrateOldCategories(userId);
|
|
||||||
if (!m2) await migratePinDMs();
|
|
||||||
|
|
||||||
await saveCats(categories);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,13 +12,13 @@ import { Devs } from "@utils/constants";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||||
import { ContextMenuApi, FluxDispatcher, Menu, React } from "@webpack/common";
|
import { Clickable, ContextMenuApi, FluxDispatcher, Menu, React } from "@webpack/common";
|
||||||
import { Channel } from "discord-types/general";
|
import { Channel } from "discord-types/general";
|
||||||
|
|
||||||
import { contextMenus } from "./components/contextMenu";
|
import { contextMenus } from "./components/contextMenu";
|
||||||
import { openCategoryModal, requireSettingsMenu } from "./components/CreateCategoryModal";
|
import { openCategoryModal, requireSettingsMenu } from "./components/CreateCategoryModal";
|
||||||
import { DEFAULT_CHUNK_SIZE } from "./constants";
|
import { DEFAULT_CHUNK_SIZE } from "./constants";
|
||||||
import { canMoveCategory, canMoveCategoryInDirection, categories, Category, categoryLen, collapseCategory, getAllUncollapsedChannels, getSections, init, isPinned, moveCategory, removeCategory } from "./data";
|
import { canMoveCategory, canMoveCategoryInDirection, Category, categoryLen, collapseCategory, getAllUncollapsedChannels, getCategoryByIndex, getSections, init, isPinned, moveCategory, removeCategory, usePinnedDms } from "./data";
|
||||||
|
|
||||||
interface ChannelComponentProps {
|
interface ChannelComponentProps {
|
||||||
children: React.ReactNode,
|
children: React.ReactNode,
|
||||||
|
@ -26,13 +26,11 @@ interface ChannelComponentProps {
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const headerClasses = findByPropsLazy("privateChannelsHeaderContainer");
|
const headerClasses = findByPropsLazy("privateChannelsHeaderContainer");
|
||||||
|
|
||||||
export const PrivateChannelSortStore = findStoreLazy("PrivateChannelSortStore") as { getPrivateChannelIds: () => string[]; };
|
export const PrivateChannelSortStore = findStoreLazy("PrivateChannelSortStore") as { getPrivateChannelIds: () => string[]; };
|
||||||
|
|
||||||
export let instance: any;
|
export let instance: any;
|
||||||
export const forceUpdate = () => instance?.props?._forceUpdate?.();
|
|
||||||
|
|
||||||
export const enum PinOrder {
|
export const enum PinOrder {
|
||||||
LastMessage,
|
LastMessage,
|
||||||
|
@ -46,21 +44,28 @@ export const settings = definePluginSettings({
|
||||||
options: [
|
options: [
|
||||||
{ label: "Most recent message", value: PinOrder.LastMessage, default: true },
|
{ label: "Most recent message", value: PinOrder.LastMessage, default: true },
|
||||||
{ label: "Custom (right click channels to reorder)", value: PinOrder.Custom }
|
{ label: "Custom (right click channels to reorder)", value: PinOrder.Custom }
|
||||||
],
|
]
|
||||||
onChange: () => forceUpdate()
|
|
||||||
},
|
},
|
||||||
|
canCollapseDmSection: {
|
||||||
dmSectioncollapsed: {
|
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Collapse DM sections",
|
description: "Allow uncategorised DMs section to be collapsable",
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
dmSectionCollapsed: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Collapse DM section",
|
||||||
default: false,
|
default: false,
|
||||||
onChange: () => forceUpdate()
|
hidden: true
|
||||||
|
},
|
||||||
|
userBasedCategoryList: {
|
||||||
|
type: OptionType.CUSTOM,
|
||||||
|
default: {} as Record<string, Category[]>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "PinDMs",
|
name: "PinDMs",
|
||||||
description: "Allows you to pin private channels to the top of your DM list. To pin/unpin or reorder pins, right click DMs",
|
description: "Allows you to pin private channels to the top of your DM list. To pin/unpin or re-order pins, right click DMs",
|
||||||
authors: [Devs.Ven, Devs.Aria],
|
authors: [Devs.Ven, Devs.Aria],
|
||||||
settings,
|
settings,
|
||||||
contextMenus,
|
contextMenus,
|
||||||
|
@ -124,8 +129,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".FRIENDS},\"friends\"",
|
find: ".FRIENDS},\"friends\"",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /let{showLibrary:\i,.+?showDMHeader:.+?,/,
|
match: /let{showLibrary:\i,/,
|
||||||
replace: "let forceUpdate = Vencord.Util.useForceUpdater();$&_forceUpdate:forceUpdate,"
|
replace: "$self.usePinnedDms();$&"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -149,6 +154,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
sections: null as number[] | null,
|
sections: null as number[] | null,
|
||||||
|
|
||||||
set _instance(i: any) {
|
set _instance(i: any) {
|
||||||
|
@ -162,6 +168,7 @@ export default definePlugin({
|
||||||
CONNECTION_OPEN: init,
|
CONNECTION_OPEN: init,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
usePinnedDms,
|
||||||
isPinned,
|
isPinned,
|
||||||
categoryLen,
|
categoryLen,
|
||||||
getSections,
|
getSections,
|
||||||
|
@ -186,11 +193,11 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
makeSpanProps() {
|
makeSpanProps() {
|
||||||
return {
|
return settings.store.canCollapseDmSection ? {
|
||||||
onClick: () => this.collapseDMList(),
|
onClick: () => this.collapseDMList(),
|
||||||
role: "button",
|
role: "button",
|
||||||
style: { cursor: "pointer" }
|
style: { cursor: "pointer" }
|
||||||
};
|
} : undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
getChunkSize() {
|
getChunkSize() {
|
||||||
|
@ -210,30 +217,27 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
isChannelIndex(sectionIndex: number, channelIndex: number) {
|
isChannelIndex(sectionIndex: number, channelIndex: number) {
|
||||||
if (settings.store.dmSectioncollapsed && sectionIndex !== 0)
|
if (settings.store.canCollapseDmSection && settings.store.dmSectionCollapsed && sectionIndex !== 0) {
|
||||||
return true;
|
return true;
|
||||||
const cat = categories[sectionIndex - 1];
|
}
|
||||||
return this.isCategoryIndex(sectionIndex) && (cat?.channels?.length === 0 || cat?.channels[channelIndex]);
|
|
||||||
},
|
|
||||||
|
|
||||||
isDMSectioncollapsed() {
|
const category = getCategoryByIndex(sectionIndex - 1);
|
||||||
return settings.store.dmSectioncollapsed;
|
return this.isCategoryIndex(sectionIndex) && (category?.channels?.length === 0 || category?.channels[channelIndex]);
|
||||||
},
|
},
|
||||||
|
|
||||||
collapseDMList() {
|
collapseDMList() {
|
||||||
settings.store.dmSectioncollapsed = !settings.store.dmSectioncollapsed;
|
settings.store.dmSectionCollapsed = !settings.store.dmSectionCollapsed;
|
||||||
forceUpdate();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
isChannelHidden(categoryIndex: number, channelIndex: number) {
|
isChannelHidden(categoryIndex: number, channelIndex: number) {
|
||||||
if (categoryIndex === 0) return false;
|
if (categoryIndex === 0) return false;
|
||||||
|
|
||||||
if (settings.store.dmSectioncollapsed && this.getSections().length + 1 === categoryIndex)
|
if (settings.store.canCollapseDmSection && settings.store.dmSectionCollapsed && this.getSections().length + 1 === categoryIndex)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!this.instance || !this.isChannelIndex(categoryIndex, channelIndex)) return false;
|
if (!this.instance || !this.isChannelIndex(categoryIndex, channelIndex)) return false;
|
||||||
|
|
||||||
const category = categories[categoryIndex - 1];
|
const category = getCategoryByIndex(categoryIndex - 1);
|
||||||
if (!category) return false;
|
if (!category) return false;
|
||||||
|
|
||||||
return category.collapsed && this.instance.props.selectedChannelId !== this.getCategoryChannels(category)[channelIndex];
|
return category.collapsed && this.instance.props.selectedChannelId !== this.getCategoryChannels(category)[channelIndex];
|
||||||
|
@ -251,18 +255,12 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
renderCategory: ErrorBoundary.wrap(({ section }: { section: number; }) => {
|
renderCategory: ErrorBoundary.wrap(({ section }: { section: number; }) => {
|
||||||
const category = categories[section - 1];
|
const category = getCategoryByIndex(section - 1);
|
||||||
|
|
||||||
if (!category) return null;
|
if (!category) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<h2
|
<Clickable
|
||||||
className={classes(headerClasses.privateChannelsHeaderContainer, "vc-pindms-section-container", category.collapsed ? "vc-pindms-collapsed" : "")}
|
onClick={() => collapseCategory(category.id, !category.collapsed)}
|
||||||
style={{ color: `#${category.color.toString(16).padStart(6, "0")}` }}
|
|
||||||
onClick={async () => {
|
|
||||||
await collapseCategory(category.id, !category.collapsed);
|
|
||||||
forceUpdate();
|
|
||||||
}}
|
|
||||||
onContextMenu={e => {
|
onContextMenu={e => {
|
||||||
ContextMenuApi.openContextMenu(e, () => (
|
ContextMenuApi.openContextMenu(e, () => (
|
||||||
<Menu.Menu
|
<Menu.Menu
|
||||||
|
@ -284,14 +282,14 @@ export default definePlugin({
|
||||||
canMoveCategoryInDirection(category.id, -1) && <Menu.MenuItem
|
canMoveCategoryInDirection(category.id, -1) && <Menu.MenuItem
|
||||||
id="vc-pindms-move-category-up"
|
id="vc-pindms-move-category-up"
|
||||||
label="Move Up"
|
label="Move Up"
|
||||||
action={() => moveCategory(category.id, -1).then(() => forceUpdate())}
|
action={() => moveCategory(category.id, -1)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
canMoveCategoryInDirection(category.id, 1) && <Menu.MenuItem
|
canMoveCategoryInDirection(category.id, 1) && <Menu.MenuItem
|
||||||
id="vc-pindms-move-category-down"
|
id="vc-pindms-move-category-down"
|
||||||
label="Move Down"
|
label="Move Down"
|
||||||
action={() => moveCategory(category.id, 1).then(() => forceUpdate())}
|
action={() => moveCategory(category.id, 1)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
|
@ -304,13 +302,17 @@ export default definePlugin({
|
||||||
id="vc-pindms-delete-category"
|
id="vc-pindms-delete-category"
|
||||||
color="danger"
|
color="danger"
|
||||||
label="Delete Category"
|
label="Delete Category"
|
||||||
action={() => removeCategory(category.id).then(() => forceUpdate())}
|
action={() => removeCategory(category.id)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
</Menu.Menu>
|
</Menu.Menu>
|
||||||
));
|
));
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
className={classes(headerClasses.privateChannelsHeaderContainer, "vc-pindms-section-container", category.collapsed ? "vc-pindms-collapsed" : "")}
|
||||||
|
style={{ color: `#${category.color.toString(16).padStart(6, "0")}` }}
|
||||||
>
|
>
|
||||||
<span className={headerClasses.headerText}>
|
<span className={headerClasses.headerText}>
|
||||||
{category?.name ?? "uh oh"}
|
{category?.name ?? "uh oh"}
|
||||||
|
@ -319,6 +321,7 @@ export default definePlugin({
|
||||||
<path fill="currentColor" d="M9.3 5.3a1 1 0 0 0 0 1.4l5.29 5.3-5.3 5.3a1 1 0 1 0 1.42 1.4l6-6a1 1 0 0 0 0-1.4l-6-6a1 1 0 0 0-1.42 0Z"></path>
|
<path fill="currentColor" d="M9.3 5.3a1 1 0 0 0 0 1.4l5.29 5.3-5.3 5.3a1 1 0 1 0 1.42 1.4l6-6a1 1 0 0 0 0-1.4l-6-6a1 1 0 0 0-1.42 0Z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</h2>
|
</h2>
|
||||||
|
</Clickable>
|
||||||
);
|
);
|
||||||
}, { noop: true }),
|
}, { noop: true }),
|
||||||
|
|
||||||
|
@ -341,7 +344,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
getChannel(sectionIndex: number, index: number, channels: Record<string, Channel>) {
|
getChannel(sectionIndex: number, index: number, channels: Record<string, Channel>) {
|
||||||
const category = categories[sectionIndex - 1];
|
const category = getCategoryByIndex(sectionIndex - 1);
|
||||||
if (!category) return { channel: null, category: null };
|
if (!category) return { channel: null, category: null };
|
||||||
|
|
||||||
const channelId = this.getCategoryChannels(category)[index];
|
const channelId = this.getCategoryChannels(category)[index];
|
||||||
|
|
|
@ -18,14 +18,14 @@
|
||||||
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
|
|
||||||
import { addBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeBadge } from "@api/Badges";
|
import { addProfileBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeProfileBadge } from "@api/Badges";
|
||||||
import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
|
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
|
||||||
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
|
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
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 { findByPropsLazy, findStoreLazy } from "@webpack";
|
import { filters, findStoreLazy, mapMangledModuleLazy } from "@webpack";
|
||||||
import { PresenceStore, Tooltip, UserStore } from "@webpack/common";
|
import { PresenceStore, Tooltip, UserStore } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
|
@ -70,7 +70,9 @@ const Icons = {
|
||||||
};
|
};
|
||||||
type Platform = keyof typeof Icons;
|
type Platform = keyof typeof Icons;
|
||||||
|
|
||||||
const StatusUtils = findByPropsLazy("useStatusFillColor", "StatusTypes");
|
const { useStatusFillColor } = mapMangledModuleLazy(".concat(.5625*", {
|
||||||
|
useStatusFillColor: filters.byCode(".hex")
|
||||||
|
});
|
||||||
|
|
||||||
const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => {
|
const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => {
|
||||||
const tooltip = platform === "embedded"
|
const tooltip = platform === "embedded"
|
||||||
|
@ -79,7 +81,7 @@ const PlatformIcon = ({ platform, status, small }: { platform: Platform, status:
|
||||||
|
|
||||||
const Icon = Icons[platform] ?? Icons.desktop;
|
const Icon = Icons[platform] ?? Icons.desktop;
|
||||||
|
|
||||||
return <Icon color={StatusUtils.useStatusFillColor(status)} tooltip={tooltip} small={small} />;
|
return <Icon color={useStatusFillColor(status)} tooltip={tooltip} small={small} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
function ensureOwnStatus(user: User) {
|
function ensureOwnStatus(user: User) {
|
||||||
|
@ -172,26 +174,26 @@ const badge: ProfileBadge = {
|
||||||
const indicatorLocations = {
|
const indicatorLocations = {
|
||||||
list: {
|
list: {
|
||||||
description: "In the member list",
|
description: "In the member list",
|
||||||
onEnable: () => addDecorator("platform-indicator", props =>
|
onEnable: () => addMemberListDecorator("platform-indicator", props =>
|
||||||
<ErrorBoundary noop>
|
<ErrorBoundary noop>
|
||||||
<PlatformIndicator user={props.user} small={true} />
|
<PlatformIndicator user={props.user} small={true} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
),
|
),
|
||||||
onDisable: () => removeDecorator("platform-indicator")
|
onDisable: () => removeMemberListDecorator("platform-indicator")
|
||||||
},
|
},
|
||||||
badges: {
|
badges: {
|
||||||
description: "In user profiles, as badges",
|
description: "In user profiles, as badges",
|
||||||
onEnable: () => addBadge(badge),
|
onEnable: () => addProfileBadge(badge),
|
||||||
onDisable: () => removeBadge(badge)
|
onDisable: () => removeProfileBadge(badge)
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
description: "Inside messages",
|
description: "Inside messages",
|
||||||
onEnable: () => addDecoration("platform-indicator", props =>
|
onEnable: () => addMessageDecoration("platform-indicator", props =>
|
||||||
<ErrorBoundary noop>
|
<ErrorBoundary noop>
|
||||||
<PlatformIndicator user={props.message?.author} wantTopMargin={true} />
|
<PlatformIndicator user={props.message?.author} wantTopMargin={true} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
),
|
),
|
||||||
onDisable: () => removeDecoration("platform-indicator")
|
onDisable: () => removeMessageDecoration("platform-indicator")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,7 +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 { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||||
import { generateId, sendBotMessage } from "@api/Commands";
|
import { generateId, sendBotMessage } from "@api/Commands";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { StartAt } from "@utils/types";
|
import definePlugin, { StartAt } from "@utils/types";
|
||||||
|
@ -73,7 +73,7 @@ const getAttachments = async (channelId: string) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const PreviewButton: ChatBarButton = ({ isMainChat, isEmpty, type: { attachments } }) => {
|
const PreviewButton: ChatBarButtonFactory = ({ isMainChat, isEmpty, type: { attachments } }) => {
|
||||||
const channelId = SelectedChannelStore.getChannelId();
|
const channelId = SelectedChannelStore.getChannelId();
|
||||||
const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
|
const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
|
||||||
|
|
||||||
|
@ -121,11 +121,9 @@ export default definePlugin({
|
||||||
name: "PreviewMessage",
|
name: "PreviewMessage",
|
||||||
description: "Lets you preview your message before sending it.",
|
description: "Lets you preview your message before sending it.",
|
||||||
authors: [Devs.Aria],
|
authors: [Devs.Aria],
|
||||||
dependencies: ["ChatInputButtonAPI"],
|
|
||||||
// start early to ensure we're the first plugin to add our button
|
// start early to ensure we're the first plugin to add our button
|
||||||
// This makes the popping in less awkward
|
// This makes the popping in less awkward
|
||||||
startAt: StartAt.Init,
|
startAt: StartAt.Init,
|
||||||
|
|
||||||
start: () => addChatBarButton("previewMessage", PreviewButton),
|
renderChatBarButton: PreviewButton,
|
||||||
stop: () => removeChatBarButton("previewMessage"),
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 { addButton, removeButton } from "@api/MessagePopover";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { insertTextIntoChatInputBox } from "@utils/discord";
|
import { insertTextIntoChatInputBox } from "@utils/discord";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
@ -26,10 +25,8 @@ export default definePlugin({
|
||||||
name: "QuickMention",
|
name: "QuickMention",
|
||||||
authors: [Devs.kemo],
|
authors: [Devs.kemo],
|
||||||
description: "Adds a quick mention button to the message actions bar",
|
description: "Adds a quick mention button to the message actions bar",
|
||||||
dependencies: ["MessagePopoverAPI"],
|
|
||||||
|
|
||||||
start() {
|
renderMessagePopoverButton(msg) {
|
||||||
addButton("QuickMention", msg => {
|
|
||||||
const channel = ChannelStore.getChannel(msg.channel_id);
|
const channel = ChannelStore.getChannel(msg.channel_id);
|
||||||
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return null;
|
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return null;
|
||||||
|
|
||||||
|
@ -40,10 +37,6 @@ export default definePlugin({
|
||||||
channel,
|
channel,
|
||||||
onClick: () => insertTextIntoChatInputBox(`<@${msg.author.id}> `)
|
onClick: () => insertTextIntoChatInputBox(`<@${msg.author.id}> `)
|
||||||
};
|
};
|
||||||
});
|
|
||||||
},
|
|
||||||
stop() {
|
|
||||||
removeButton("QuickMention");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Icon: () => (
|
Icon: () => (
|
||||||
|
|
|
@ -196,7 +196,7 @@ function nextReply(isUp: boolean) {
|
||||||
channel,
|
channel,
|
||||||
message,
|
message,
|
||||||
shouldMention: shouldMention(message),
|
shouldMention: shouldMention(message),
|
||||||
showMentionToggle: channel.isPrivate() && message.author.id !== meId,
|
showMentionToggle: !channel.isPrivate() && message.author.id !== meId,
|
||||||
_isQuickReply: true
|
_isQuickReply: true
|
||||||
});
|
});
|
||||||
ComponentDispatch.dispatchToLastSubscribed("TEXTAREA_FOCUS");
|
ComponentDispatch.dispatchToLastSubscribed("TEXTAREA_FOCUS");
|
||||||
|
|
|
@ -7,15 +7,12 @@
|
||||||
import { DataStore } from "@api/index";
|
import { DataStore } from "@api/index";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { openModal } from "@utils/modal";
|
import { openModal } from "@utils/modal";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { OAuth2AuthorizeModal, showToast, Toasts, UserStore } from "@webpack/common";
|
||||||
import { showToast, Toasts, UserStore } from "@webpack/common";
|
|
||||||
|
|
||||||
import { ReviewDBAuth } from "./entities";
|
import { ReviewDBAuth } from "./entities";
|
||||||
|
|
||||||
const DATA_STORE_KEY = "rdb-auth";
|
const DATA_STORE_KEY = "rdb-auth";
|
||||||
|
|
||||||
const { OAuth2AuthorizeModal } = findByPropsLazy("OAuth2AuthorizeModal");
|
|
||||||
|
|
||||||
export let Auth: ReviewDBAuth = {};
|
export let Auth: ReviewDBAuth = {};
|
||||||
|
|
||||||
export async function initAuth() {
|
export async function initAuth() {
|
||||||
|
|
|
@ -27,7 +27,6 @@ import { cl } from "./utils";
|
||||||
export const settings = definePluginSettings({
|
export const settings = definePluginSettings({
|
||||||
authorize: {
|
authorize: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "Authorize with ReviewDB",
|
|
||||||
component: () => (
|
component: () => (
|
||||||
<Button onClick={() => authorize()}>
|
<Button onClick={() => authorize()}>
|
||||||
Authorize with ReviewDB
|
Authorize with ReviewDB
|
||||||
|
@ -56,7 +55,6 @@ export const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
buttons: {
|
buttons: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "ReviewDB buttons",
|
|
||||||
component: () => (
|
component: () => (
|
||||||
<div className={cl("button-grid")} >
|
<div className={cl("button-grid")} >
|
||||||
<Button onClick={openBlockModal}>Manage Blocked Users</Button>
|
<Button onClick={openBlockModal}>Manage Blocked Users</Button>
|
||||||
|
|
|
@ -18,8 +18,7 @@
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
@ -123,7 +122,7 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||||
if (!isMainChat) return null;
|
if (!isMainChat) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -160,22 +159,14 @@ export default definePlugin({
|
||||||
name: "SendTimestamps",
|
name: "SendTimestamps",
|
||||||
description: "Send timestamps easily via chat box button & text shortcuts. Read the extended description!",
|
description: "Send timestamps easily via chat box button & text shortcuts. Read the extended description!",
|
||||||
authors: [Devs.Ven, Devs.Tyler, Devs.Grzesiek11],
|
authors: [Devs.Ven, Devs.Tyler, Devs.Grzesiek11],
|
||||||
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
|
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
start() {
|
renderChatBarButton: ChatBarIcon,
|
||||||
addChatBarButton("SendTimestamps", ChatBarIcon);
|
|
||||||
this.listener = addPreSendListener((_, msg) => {
|
onBeforeMessageSend(_, msg) {
|
||||||
if (settings.store.replaceMessageContents) {
|
if (settings.store.replaceMessageContents) {
|
||||||
msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime);
|
msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeChatBarButton("SendTimestamps");
|
|
||||||
removePreSendListener(this.listener);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
settingsAboutComponent() {
|
settingsAboutComponent() {
|
||||||
|
|
|
@ -31,7 +31,8 @@ export function openGuildInfoModal(guild: Guild) {
|
||||||
const enum Tabs {
|
const enum Tabs {
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
Friends,
|
Friends,
|
||||||
BlockedUsers
|
BlockedUsers,
|
||||||
|
IgnoredUsers
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GuildProps {
|
interface GuildProps {
|
||||||
|
@ -44,7 +45,8 @@ interface RelationshipProps extends GuildProps {
|
||||||
|
|
||||||
const fetched = {
|
const fetched = {
|
||||||
friends: false,
|
friends: false,
|
||||||
blocked: false
|
blocked: false,
|
||||||
|
ignored: false
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderTimestamp(timestamp: number) {
|
function renderTimestamp(timestamp: number) {
|
||||||
|
@ -56,10 +58,12 @@ function renderTimestamp(timestamp: number) {
|
||||||
function GuildInfoModal({ guild }: GuildProps) {
|
function GuildInfoModal({ guild }: GuildProps) {
|
||||||
const [friendCount, setFriendCount] = useState<number>();
|
const [friendCount, setFriendCount] = useState<number>();
|
||||||
const [blockedCount, setBlockedCount] = useState<number>();
|
const [blockedCount, setBlockedCount] = useState<number>();
|
||||||
|
const [ignoredCount, setIgnoredCount] = useState<number>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetched.friends = false;
|
fetched.friends = false;
|
||||||
fetched.blocked = false;
|
fetched.blocked = false;
|
||||||
|
fetched.ignored = false;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
|
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
|
||||||
|
@ -132,12 +136,19 @@ function GuildInfoModal({ guild }: GuildProps) {
|
||||||
>
|
>
|
||||||
Blocked Users{blockedCount !== undefined ? ` (${blockedCount})` : ""}
|
Blocked Users{blockedCount !== undefined ? ` (${blockedCount})` : ""}
|
||||||
</TabBar.Item>
|
</TabBar.Item>
|
||||||
|
<TabBar.Item
|
||||||
|
className={cl("tab", { selected: currentTab === Tabs.IgnoredUsers })}
|
||||||
|
id={Tabs.IgnoredUsers}
|
||||||
|
>
|
||||||
|
Ignored Users{ignoredCount !== undefined ? ` (${ignoredCount})` : ""}
|
||||||
|
</TabBar.Item>
|
||||||
</TabBar>
|
</TabBar>
|
||||||
|
|
||||||
<div className={cl("tab-content")}>
|
<div className={cl("tab-content")}>
|
||||||
{currentTab === Tabs.ServerInfo && <ServerInfoTab guild={guild} />}
|
{currentTab === Tabs.ServerInfo && <ServerInfoTab guild={guild} />}
|
||||||
{currentTab === Tabs.Friends && <FriendsTab guild={guild} setCount={setFriendCount} />}
|
{currentTab === Tabs.Friends && <FriendsTab guild={guild} setCount={setFriendCount} />}
|
||||||
{currentTab === Tabs.BlockedUsers && <BlockedUsersTab guild={guild} setCount={setBlockedCount} />}
|
{currentTab === Tabs.BlockedUsers && <BlockedUsersTab guild={guild} setCount={setBlockedCount} />}
|
||||||
|
{currentTab === Tabs.IgnoredUsers && <IgnoredUserTab guild={guild} setCount={setIgnoredCount} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -211,7 +222,13 @@ function BlockedUsersTab({ guild, setCount }: RelationshipProps) {
|
||||||
return UserList("blocked", guild, blockedIds, setCount);
|
return UserList("blocked", guild, blockedIds, setCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setCount: (count: number) => void) {
|
function IgnoredUserTab({ guild, setCount }: RelationshipProps) {
|
||||||
|
const ignoredIds = Object.keys(RelationshipStore.getRelationships()).filter(id => RelationshipStore.isIgnored(id));
|
||||||
|
return UserList("ignored", guild, ignoredIds, setCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function UserList(type: "friends" | "blocked" | "ignored", guild: Guild, ids: string[], setCount: (count: number) => void) {
|
||||||
const missing = [] as string[];
|
const missing = [] as string[];
|
||||||
const members = [] as string[];
|
const members = [] as string[];
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Make channels we dont have access to be the same level as normal ones
|
// Make channels we dont have access to be the same level as normal ones
|
||||||
{
|
{
|
||||||
match: /(activeJoinedRelevantThreads:.{0,50}VIEW_CHANNEL.+?renderLevel:(.+?),threadIds.+?renderLevel:).+?(?=,threadIds)/g,
|
match: /(this\.record\)\?{renderLevel:(.+?),threadIds.+?renderLevel:).+?(?=,threadIds)/g,
|
||||||
replace: (_, rest, defaultRenderLevel) => `${rest}${defaultRenderLevel}`
|
replace: (_, rest, defaultRenderLevel) => `${rest}${defaultRenderLevel}`
|
||||||
},
|
},
|
||||||
// Remove permission checking for getRenderLevel function
|
// Remove permission checking for getRenderLevel function
|
||||||
|
@ -108,8 +108,10 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Prevent Discord from trying to connect to hidden voice channels
|
// Prevent Discord from trying to connect to hidden voice channels
|
||||||
match: /(?=&&\i\.\i\.selectVoiceChannel\((\i)\.id\))/,
|
match: /(?=(\|\||&&)\i\.\i\.selectVoiceChannel\((\i)\.id\))/,
|
||||||
replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
|
replace: (_, condition, channel) => condition === "||"
|
||||||
|
? `||$self.isHiddenChannel(${channel})`
|
||||||
|
: `&&!$self.isHiddenChannel(${channel})`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Make Discord show inside the channel if clicking on a hidden or locked channel
|
// Make Discord show inside the channel if clicking on a hidden or locked channel
|
||||||
|
@ -122,8 +124,10 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".AUDIENCE),{isSubscriptionGated",
|
find: ".AUDIENCE),{isSubscriptionGated",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /!(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/,
|
match: /(!)?(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/,
|
||||||
replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})`
|
replace: (m, not, channel) => not
|
||||||
|
? `${m}&&!$self.isHiddenChannel(${channel})`
|
||||||
|
: `${m}||$self.isHiddenChannel(${channel})`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -173,8 +177,10 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Make voice channels also appear as muted if they are muted
|
// Make voice channels also appear as muted if they are muted
|
||||||
{
|
{
|
||||||
match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)if\((\i)\)return (\i\.MUTED);/,
|
match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)(if\()?(\i)(?:\)return |\?)(\i\.MUTED)/,
|
||||||
replace: (_, otherClasses, isMuted, mutedClassExpression) => `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return "";`
|
replace: (_, otherClasses, isIf, isMuted, mutedClassExpression) => isIf
|
||||||
|
? `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return ""`
|
||||||
|
: `${isMuted}?${mutedClassExpression}:"",${otherClasses}${isMuted}?""`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -184,8 +190,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
// Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden
|
// Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden
|
||||||
predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle,
|
predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle,
|
||||||
match: /\.LOCKED;if\((?<={channel:(\i).+?)/,
|
match: /(?<=\.LOCKED(?:;if\(|:))(?<={channel:(\i).+?)/,
|
||||||
replace: (m, channel) => `${m}!$self.isHiddenChannel(${channel})&&`
|
replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Hide unreads
|
// Hide unreads
|
||||||
|
@ -224,12 +230,12 @@ export default definePlugin({
|
||||||
find: "Missing channel in Channel.renderHeaderToolbar",
|
find: "Missing channel in Channel.renderHeaderToolbar",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<="renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_TEXT:)(?=.+?(\i\.push.{0,50}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
|
match: /"renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_TEXT:(?=.+?(\i\.push.{0,50}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
|
||||||
replace: (_, pushNotificationButtonExpression, channel, isLurking) => `if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
|
replace: (m, pushNotificationButtonExpression, channel, isLurking) => `${m}if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<="renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_MEDIA:)(?=.+?(\i\.push.{0,40}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
|
match: /"renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_MEDIA:(?=.+?(\i\.push.{0,40}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
|
||||||
replace: (_, pushNotificationButtonExpression, channel, isLurking) => `if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
|
replace: (m, pushNotificationButtonExpression, channel, isLurking) => `${m}if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /"renderMobileToolbar",\(\)=>{.+?case \i\.\i\.GUILD_DIRECTORY:(?<=let{channel:(\i).+?)/,
|
match: /"renderMobileToolbar",\(\)=>{.+?case \i\.\i\.GUILD_DIRECTORY:(?<=let{channel:(\i).+?)/,
|
||||||
|
|
|
@ -76,8 +76,8 @@ export default definePlugin({
|
||||||
find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}",
|
find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(\i)\.Tooltip,{(text:.{0,30}#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}\))/,
|
match: /\i\.\i,{(text:.{0,30}#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}\))/,
|
||||||
replace: "$self.TooltipWrapper,{message:arguments[0].message,$2"
|
replace: "$self.TooltipWrapper,{message:arguments[0].message,$1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
* 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 { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||||
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
@ -41,7 +41,7 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
const SilentMessageToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||||
const [enabled, setEnabled] = useState(lastState);
|
const [enabled, setEnabled] = useState(lastState);
|
||||||
|
|
||||||
function setEnabledValue(value: boolean) {
|
function setEnabledValue(value: boolean) {
|
||||||
|
@ -50,15 +50,15 @@ const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener: SendListener = (_, message) => {
|
const listener: MessageSendListener = (_, message) => {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (settings.store.autoDisable) setEnabledValue(false);
|
if (settings.store.autoDisable) setEnabledValue(false);
|
||||||
if (!message.content.startsWith("@silent ")) message.content = "@silent " + message.content;
|
if (!message.content.startsWith("@silent ")) message.content = "@silent " + message.content;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addPreSendListener(listener);
|
addMessagePreSendListener(listener);
|
||||||
return () => void removePreSendListener(listener);
|
return () => void removeMessagePreSendListener(listener);
|
||||||
}, [enabled]);
|
}, [enabled]);
|
||||||
|
|
||||||
if (!isMainChat) return null;
|
if (!isMainChat) return null;
|
||||||
|
@ -91,9 +91,7 @@ export default definePlugin({
|
||||||
name: "SilentMessageToggle",
|
name: "SilentMessageToggle",
|
||||||
authors: [Devs.Nuckyz, Devs.CatNoir],
|
authors: [Devs.Nuckyz, Devs.CatNoir],
|
||||||
description: "Adds a button to the chat bar to toggle sending a silent message.",
|
description: "Adds a button to the chat bar to toggle sending a silent message.",
|
||||||
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
start: () => addChatBarButton("SilentMessageToggle", SilentMessageToggle),
|
renderChatBarButton: SilentMessageToggle,
|
||||||
stop: () => removeChatBarButton("SilentMessageToggle")
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +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 { addChatBarButton, ChatBarButton, removeChatBarButton } 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 { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
@ -43,7 +43,7 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const SilentTypingToggle: ChatBarButton = ({ isMainChat }) => {
|
const SilentTypingToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||||
const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]);
|
const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]);
|
||||||
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
||||||
|
|
||||||
|
@ -96,11 +96,12 @@ export default definePlugin({
|
||||||
name: "SilentTyping",
|
name: "SilentTyping",
|
||||||
authors: [Devs.Ven, Devs.Rini, Devs.ImBanana],
|
authors: [Devs.Ven, Devs.Rini, Devs.ImBanana],
|
||||||
description: "Hide that you are typing",
|
description: "Hide that you are typing",
|
||||||
dependencies: ["ChatInputButtonAPI"],
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
contextMenus: {
|
contextMenus: {
|
||||||
"textarea-context": ChatBarContextCheckbox
|
"textarea-context": ChatBarContextCheckbox
|
||||||
},
|
},
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '.dispatch({type:"TYPING_START_LOCAL"',
|
find: '.dispatch({type:"TYPING_START_LOCAL"',
|
||||||
|
@ -136,6 +137,5 @@ export default definePlugin({
|
||||||
FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId });
|
FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId });
|
||||||
},
|
},
|
||||||
|
|
||||||
start: () => addChatBarButton("SilentTyping", SilentTypingToggle),
|
renderChatBarButton: SilentTypingToggle,
|
||||||
stop: () => removeChatBarButton("SilentTyping"),
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,13 +17,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DataStore } from "@api/index";
|
import { DataStore } from "@api/index";
|
||||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { DeleteIcon } from "@components/Icons";
|
import { DeleteIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { useForceUpdater } from "@utils/react";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { Button, Forms, React, TextInput, useState } from "@webpack/common";
|
import { Button, Forms, React, TextInput, useState } from "@webpack/common";
|
||||||
|
|
||||||
|
@ -35,8 +33,6 @@ type Rule = Record<"find" | "replace" | "onlyIfIncludes", string>;
|
||||||
interface TextReplaceProps {
|
interface TextReplaceProps {
|
||||||
title: string;
|
title: string;
|
||||||
rulesArray: Rule[];
|
rulesArray: Rule[];
|
||||||
rulesKey: string;
|
|
||||||
update: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeEmptyRule: () => Rule = () => ({
|
const makeEmptyRule: () => Rule = () => ({
|
||||||
|
@ -46,34 +42,35 @@ const makeEmptyRule: () => Rule = () => ({
|
||||||
});
|
});
|
||||||
const makeEmptyRuleArray = () => [makeEmptyRule()];
|
const makeEmptyRuleArray = () => [makeEmptyRule()];
|
||||||
|
|
||||||
let stringRules = makeEmptyRuleArray();
|
|
||||||
let regexRules = makeEmptyRuleArray();
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
replace: {
|
replace: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "",
|
|
||||||
component: () => {
|
component: () => {
|
||||||
const update = useForceUpdater();
|
const { stringRules, regexRules } = settings.use(["stringRules", "regexRules"]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TextReplace
|
<TextReplace
|
||||||
title="Using String"
|
title="Using String"
|
||||||
rulesArray={stringRules}
|
rulesArray={stringRules}
|
||||||
rulesKey={STRING_RULES_KEY}
|
|
||||||
update={update}
|
|
||||||
/>
|
/>
|
||||||
<TextReplace
|
<TextReplace
|
||||||
title="Using Regex"
|
title="Using Regex"
|
||||||
rulesArray={regexRules}
|
rulesArray={regexRules}
|
||||||
rulesKey={REGEX_RULES_KEY}
|
|
||||||
update={update}
|
|
||||||
/>
|
/>
|
||||||
<TextReplaceTesting />
|
<TextReplaceTesting />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
stringRules: {
|
||||||
|
type: OptionType.CUSTOM,
|
||||||
|
default: makeEmptyRuleArray(),
|
||||||
|
},
|
||||||
|
regexRules: {
|
||||||
|
type: OptionType.CUSTOM,
|
||||||
|
default: makeEmptyRuleArray(),
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function stringToRegex(str: string) {
|
function stringToRegex(str: string) {
|
||||||
|
@ -120,28 +117,24 @@ function Input({ initialValue, onChange, placeholder }: {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextReplace({ title, rulesArray, rulesKey, update }: TextReplaceProps) {
|
function TextReplace({ title, rulesArray }: TextReplaceProps) {
|
||||||
const isRegexRules = title === "Using Regex";
|
const isRegexRules = title === "Using Regex";
|
||||||
|
|
||||||
async function onClickRemove(index: number) {
|
async function onClickRemove(index: number) {
|
||||||
if (index === rulesArray.length - 1) return;
|
if (index === rulesArray.length - 1) return;
|
||||||
rulesArray.splice(index, 1);
|
rulesArray.splice(index, 1);
|
||||||
|
|
||||||
await DataStore.set(rulesKey, rulesArray);
|
|
||||||
update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onChange(e: string, index: number, key: string) {
|
async function onChange(e: string, index: number, key: string) {
|
||||||
if (index === rulesArray.length - 1)
|
if (index === rulesArray.length - 1) {
|
||||||
rulesArray.push(makeEmptyRule());
|
rulesArray.push(makeEmptyRule());
|
||||||
|
}
|
||||||
|
|
||||||
rulesArray[index][key] = e;
|
rulesArray[index][key] = e;
|
||||||
|
|
||||||
if (rulesArray[index].find === "" && rulesArray[index].replace === "" && rulesArray[index].onlyIfIncludes === "" && index !== rulesArray.length - 1)
|
if (rulesArray[index].find === "" && rulesArray[index].replace === "" && rulesArray[index].onlyIfIncludes === "" && index !== rulesArray.length - 1) {
|
||||||
rulesArray.splice(index, 1);
|
rulesArray.splice(index, 1);
|
||||||
|
}
|
||||||
await DataStore.set(rulesKey, rulesArray);
|
|
||||||
update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -208,20 +201,18 @@ function TextReplaceTesting() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyRules(content: string): string {
|
function applyRules(content: string): string {
|
||||||
if (content.length === 0)
|
if (content.length === 0) {
|
||||||
return content;
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
if (stringRules) {
|
for (const rule of settings.store.stringRules) {
|
||||||
for (const rule of stringRules) {
|
|
||||||
if (!rule.find) continue;
|
if (!rule.find) continue;
|
||||||
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
||||||
|
|
||||||
content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, "");
|
content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, "");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (regexRules) {
|
for (const rule of settings.store.regexRules) {
|
||||||
for (const rule of regexRules) {
|
|
||||||
if (!rule.find) continue;
|
if (!rule.find) continue;
|
||||||
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
||||||
|
|
||||||
|
@ -232,7 +223,6 @@ function applyRules(content: string): string {
|
||||||
new Logger("TextReplace").error(`Invalid regex: ${rule.find}`);
|
new Logger("TextReplace").error(`Invalid regex: ${rule.find}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
content = content.trim();
|
content = content.trim();
|
||||||
return content;
|
return content;
|
||||||
|
@ -244,22 +234,27 @@ export default definePlugin({
|
||||||
name: "TextReplace",
|
name: "TextReplace",
|
||||||
description: "Replace text in your messages. You can find pre-made rules in the #textreplace-rules channel in Vencord's Server",
|
description: "Replace text in your messages. You can find pre-made rules in the #textreplace-rules channel in Vencord's Server",
|
||||||
authors: [Devs.AutumnVN, Devs.TheKodeToad],
|
authors: [Devs.AutumnVN, Devs.TheKodeToad],
|
||||||
dependencies: ["MessageEventsAPI"],
|
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
async start() {
|
onBeforeMessageSend(channelId, msg) {
|
||||||
stringRules = await DataStore.get(STRING_RULES_KEY) ?? makeEmptyRuleArray();
|
|
||||||
regexRules = await DataStore.get(REGEX_RULES_KEY) ?? makeEmptyRuleArray();
|
|
||||||
|
|
||||||
this.preSend = addPreSendListener((channelId, msg) => {
|
|
||||||
// Channel used for sharing rules, applying rules here would be messy
|
// Channel used for sharing rules, applying rules here would be messy
|
||||||
if (channelId === TEXT_REPLACE_RULES_CHANNEL_ID) return;
|
if (channelId === TEXT_REPLACE_RULES_CHANNEL_ID) return;
|
||||||
msg.content = applyRules(msg.content);
|
msg.content = applyRules(msg.content);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
async start() {
|
||||||
removePreSendListener(this.preSend);
|
// TODO: Remove DataStore rules migrations once enough time has passed
|
||||||
|
const oldStringRules = await DataStore.get<Rule[]>(STRING_RULES_KEY);
|
||||||
|
if (oldStringRules != null) {
|
||||||
|
settings.store.stringRules = oldStringRules;
|
||||||
|
await DataStore.del(STRING_RULES_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldRegexRules = await DataStore.get<Rule[]>(REGEX_RULES_KEY);
|
||||||
|
if (oldRegexRules != null) {
|
||||||
|
settings.store.regexRules = oldRegexRules;
|
||||||
|
await DataStore.del(REGEX_RULES_KEY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +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 { ChatBarButton } from "@api/ChatButtons";
|
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { openModal } from "@utils/modal";
|
import { openModal } from "@utils/modal";
|
||||||
import { Alerts, Forms, Tooltip, useEffect, useState } from "@webpack/common";
|
import { Alerts, Forms, Tooltip, useEffect, useState } from "@webpack/common";
|
||||||
|
@ -40,7 +40,7 @@ export function TranslateIcon({ height = 24, width = 24, className }: { height?:
|
||||||
|
|
||||||
export let setShouldShowTranslateEnabledTooltip: undefined | ((show: boolean) => void);
|
export let setShouldShowTranslateEnabledTooltip: undefined | ((show: boolean) => void);
|
||||||
|
|
||||||
export const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
export const TranslateChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||||
const { autoTranslate, showChatBarButton } = settings.use(["autoTranslate", "showChatBarButton"]);
|
const { autoTranslate, showChatBarButton } = settings.use(["autoTranslate", "showChatBarButton"]);
|
||||||
|
|
||||||
const [shouldShowTranslateEnabledTooltip, setter] = useState(false);
|
const [shouldShowTranslateEnabledTooltip, setter] = useState(false);
|
||||||
|
|
|
@ -18,11 +18,7 @@
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
|
||||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
|
||||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { ChannelStore, Menu } from "@webpack/common";
|
import { ChannelStore, Menu } from "@webpack/common";
|
||||||
|
@ -51,11 +47,12 @@ const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) =>
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let tooltipTimeout: any;
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "Translate",
|
name: "Translate",
|
||||||
description: "Translate messages with Google Translate or DeepL",
|
description: "Translate messages with Google Translate or DeepL",
|
||||||
authors: [Devs.Ven, Devs.AshtonMemer],
|
authors: [Devs.Ven, Devs.AshtonMemer],
|
||||||
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"],
|
|
||||||
settings,
|
settings,
|
||||||
contextMenus: {
|
contextMenus: {
|
||||||
"message": messageCtxPatch
|
"message": messageCtxPatch
|
||||||
|
@ -63,12 +60,11 @@ export default definePlugin({
|
||||||
// not used, just here in case some other plugin wants it or w/e
|
// not used, just here in case some other plugin wants it or w/e
|
||||||
translate,
|
translate,
|
||||||
|
|
||||||
start() {
|
renderMessageAccessory: props => <TranslationAccessory message={props.message} />,
|
||||||
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
|
||||||
|
|
||||||
addChatBarButton("vc-translate", TranslateChatBarIcon);
|
renderChatBarButton: TranslateChatBarIcon,
|
||||||
|
|
||||||
addButton("vc-translate", message => {
|
renderMessagePopoverButton(message) {
|
||||||
if (!message.content) return null;
|
if (!message.content) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -81,10 +77,9 @@ export default definePlugin({
|
||||||
handleTranslate(message.id, trans);
|
handleTranslate(message.id, trans);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
},
|
||||||
|
|
||||||
let tooltipTimeout: any;
|
async onBeforeMessageSend(_, message) {
|
||||||
this.preSend = addPreSendListener(async (_, message) => {
|
|
||||||
if (!settings.store.autoTranslate) return;
|
if (!settings.store.autoTranslate) return;
|
||||||
if (!message.content) return;
|
if (!message.content) return;
|
||||||
|
|
||||||
|
@ -94,14 +89,5 @@ export default definePlugin({
|
||||||
|
|
||||||
const trans = await translate("sent", message.content);
|
const trans = await translate("sent", message.content);
|
||||||
message.content = trans.text;
|
message.content = trans.text;
|
||||||
|
}
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removePreSendListener(this.preSend);
|
|
||||||
removeChatBarButton("vc-translate");
|
|
||||||
removeButton("vc-translate");
|
|
||||||
removeAccessory("vc-translation");
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,12 +23,12 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getIntlMessage } from "@utils/discord";
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findComponentByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
|
import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
|
import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
|
||||||
|
|
||||||
import { buildSeveralUsers } from "../typingTweaks";
|
import { buildSeveralUsers } from "../typingTweaks";
|
||||||
|
|
||||||
const ThreeDots = findExportedComponentLazy("Dots", "AnimatedDots");
|
const ThreeDots = findComponentByCodeLazy(".dots,", "dotRadius:");
|
||||||
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
||||||
|
|
||||||
const TypingStore = findStoreLazy("TypingStore");
|
const TypingStore = findStoreLazy("TypingStore");
|
||||||
|
@ -100,6 +100,13 @@ function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: s
|
||||||
{props => (
|
{props => (
|
||||||
<div className="vc-typing-indicator" {...props}>
|
<div className="vc-typing-indicator" {...props}>
|
||||||
{((settings.store.indicatorMode & IndicatorMode.Avatars) === IndicatorMode.Avatars) && (
|
{((settings.store.indicatorMode & IndicatorMode.Avatars) === IndicatorMode.Avatars) && (
|
||||||
|
<div
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
onKeyPress={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
<UserSummaryItem
|
<UserSummaryItem
|
||||||
users={typingUsersArray.map(id => UserStore.getUser(id))}
|
users={typingUsersArray.map(id => UserStore.getUser(id))}
|
||||||
guildId={guildId}
|
guildId={guildId}
|
||||||
|
@ -110,6 +117,7 @@ function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: s
|
||||||
size={16}
|
size={16}
|
||||||
className="vc-typing-indicator-avatars"
|
className="vc-typing-indicator-avatars"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{((settings.store.indicatorMode & IndicatorMode.Dots) === IndicatorMode.Dots) && (
|
{((settings.store.indicatorMode & IndicatorMode.Dots) === IndicatorMode.Dots) && (
|
||||||
<div className="vc-typing-indicator-dots">
|
<div className="vc-typing-indicator-dots">
|
||||||
|
|
|
@ -16,7 +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 { addPreEditListener, addPreSendListener, MessageObject, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
|
import { MessageObject } from "@api/MessageEvents";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ export default definePlugin({
|
||||||
name: "Unindent",
|
name: "Unindent",
|
||||||
description: "Trims leading indentation from codeblocks",
|
description: "Trims leading indentation from codeblocks",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["MessageEventsAPI"],
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "inQuote:",
|
find: "inQuote:",
|
||||||
|
@ -55,13 +55,11 @@ export default definePlugin({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
start() {
|
onBeforeMessageSend(_, msg) {
|
||||||
this.preSend = addPreSendListener((_, msg) => this.unindentMsg(msg));
|
return this.unindentMsg(msg);
|
||||||
this.preEdit = addPreEditListener((_cid, _mid, msg) => this.unindentMsg(msg));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
onBeforeMessageEdit(_cid, _mid, msg) {
|
||||||
removePreSendListener(this.preSend);
|
return this.unindentMsg(msg);
|
||||||
removePreEditListener(this.preEdit);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,12 +21,18 @@ import { ImageInvisible, ImageVisible } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common";
|
import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common";
|
||||||
|
import { MessageSnapshot } from "@webpack/types";
|
||||||
|
|
||||||
|
|
||||||
const EMBED_SUPPRESSED = 1 << 2;
|
const EMBED_SUPPRESSED = 1 << 2;
|
||||||
|
|
||||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => {
|
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, messageSnapshots, embeds, flags, id: messageId } }) => {
|
||||||
const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0;
|
const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0;
|
||||||
if (!isEmbedSuppressed && !embeds.length) return;
|
const hasEmbedsInSnapshots = messageSnapshots.some(
|
||||||
|
(snapshot: MessageSnapshot) => snapshot?.message.embeds.length
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isEmbedSuppressed && !embeds.length && !hasEmbedsInSnapshots) return;
|
||||||
|
|
||||||
const hasEmbedPerms = channel.isPrivate() || !!(PermissionStore.getChannelPermissions({ id: channel.id }) & PermissionsBits.EMBED_LINKS);
|
const hasEmbedPerms = channel.isPrivate() || !!(PermissionStore.getChannelPermissions({ id: channel.id }) & PermissionsBits.EMBED_LINKS);
|
||||||
if (author.id === UserStore.getCurrentUser().id && !hasEmbedPerms) return;
|
if (author.id === UserStore.getCurrentUser().id && !hasEmbedPerms) return;
|
||||||
|
|
|
@ -22,7 +22,7 @@ const VoiceStateStore = findStoreLazy("VoiceStateStore");
|
||||||
|
|
||||||
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
||||||
const Avatar = findComponentByCodeLazy(".status)/2):0");
|
const Avatar = findComponentByCodeLazy(".status)/2):0");
|
||||||
const GroupDMAvatars = findComponentByCodeLazy(".AvatarSizeSpecs[", "getAvatarURL");
|
const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL");
|
||||||
|
|
||||||
const ActionButtonClasses = findByPropsLazy("actionButton", "highlight");
|
const ActionButtonClasses = findByPropsLazy("actionButton", "highlight");
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
|
|
||||||
import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
|
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
|
||||||
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
|
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
@ -96,16 +96,16 @@ export default definePlugin({
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if (settings.store.showInMemberList) {
|
if (settings.store.showInMemberList) {
|
||||||
addDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
|
addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
|
||||||
}
|
}
|
||||||
if (settings.store.showInMessages) {
|
if (settings.store.showInMessages) {
|
||||||
addDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />);
|
addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
removeDecorator("UserVoiceShow");
|
removeMemberListDecorator("UserVoiceShow");
|
||||||
removeDecoration("UserVoiceShow");
|
removeMessageDecoration("UserVoiceShow");
|
||||||
},
|
},
|
||||||
|
|
||||||
VoiceChannelIndicator
|
VoiceChannelIndicator
|
||||||
|
|
|
@ -23,11 +23,11 @@ import { Settings, useSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findExportedComponentLazy } from "@webpack";
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
import { Menu, Popout, useState } from "@webpack/common";
|
import { Menu, Popout, useState } from "@webpack/common";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider");
|
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
|
||||||
|
|
||||||
function VencordPopout(onClose: () => void) {
|
function VencordPopout(onClose: () => void) {
|
||||||
const { useQuickCss } = useSettings(["useQuickCss"]);
|
const { useQuickCss } = useSettings(["useQuickCss"]);
|
||||||
|
|
|
@ -220,16 +220,16 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".cursorPointer:null,children",
|
find: ".cursorPointer:null,children",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
|
match: /(?=,src:(\i.getAvatarURL\(.+?[)]))/,
|
||||||
replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})`
|
replace: (_, avatarUrl) => `,onClick:()=>$self.openAvatar(${avatarUrl})`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// User Dms top large icon
|
// User Dms top large icon
|
||||||
{
|
{
|
||||||
find: 'experimentLocation:"empty_messages"',
|
find: 'experimentLocation:"empty_messages"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
|
match: /(?<=SIZE_80,)(?=src:(.+?\))[,}])/,
|
||||||
replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})`
|
replace: (_, avatarUrl) => `onClick:()=>$self.openAvatar(${avatarUrl}),`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -17,18 +17,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { CodeBlock } from "@components/CodeBlock";
|
import { CodeBlock } from "@components/CodeBlock";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getIntlMessage } from "@utils/discord";
|
import { getCurrentGuild, getIntlMessage } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { copyWithToast } from "@utils/misc";
|
import { copyWithToast } from "@utils/misc";
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { Button, ChannelStore, Forms, Menu, Text } from "@webpack/common";
|
import { Button, ChannelStore, Forms, GuildStore, Menu, Text } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
|
||||||
|
@ -119,7 +118,7 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenuPatchCallback {
|
function MakeContextCallback(name: "Guild" | "Role" | "User" | "Channel"): NavContextMenuPatchCallback {
|
||||||
return (children, props) => {
|
return (children, props) => {
|
||||||
const value = props[name.toLowerCase()];
|
const value = props[name.toLowerCase()];
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
|
@ -145,22 +144,40 @@ function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenu
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const devContextCallback: NavContextMenuPatchCallback = (children, { id }: { id: string; }) => {
|
||||||
|
const guild = getCurrentGuild();
|
||||||
|
if (!guild) return;
|
||||||
|
|
||||||
|
const role = GuildStore.getRole(guild.id, id);
|
||||||
|
if (!role) return;
|
||||||
|
|
||||||
|
children.push(
|
||||||
|
<Menu.MenuItem
|
||||||
|
id={"vc-view-role-raw"}
|
||||||
|
label="View Raw"
|
||||||
|
action={() => openViewRawModal(JSON.stringify(role, null, 4), "Role")}
|
||||||
|
icon={CopyIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "ViewRaw",
|
name: "ViewRaw",
|
||||||
description: "Copy and view the raw content/data of any message, channel or guild",
|
description: "Copy and view the raw content/data of any message, channel or guild",
|
||||||
authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna],
|
authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna],
|
||||||
dependencies: ["MessagePopoverAPI"],
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
contextMenus: {
|
contextMenus: {
|
||||||
"guild-context": MakeContextCallback("Guild"),
|
"guild-context": MakeContextCallback("Guild"),
|
||||||
|
"guild-settings-role-context": MakeContextCallback("Role"),
|
||||||
"channel-context": MakeContextCallback("Channel"),
|
"channel-context": MakeContextCallback("Channel"),
|
||||||
"thread-context": MakeContextCallback("Channel"),
|
"thread-context": MakeContextCallback("Channel"),
|
||||||
"gdm-context": MakeContextCallback("Channel"),
|
"gdm-context": MakeContextCallback("Channel"),
|
||||||
"user-context": MakeContextCallback("User")
|
"user-context": MakeContextCallback("User"),
|
||||||
|
"dev-context": devContextCallback
|
||||||
},
|
},
|
||||||
|
|
||||||
start() {
|
renderMessagePopoverButton(msg) {
|
||||||
addButton("ViewRaw", msg => {
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (settings.store.clickMethod === "Right") {
|
if (settings.store.clickMethod === "Right") {
|
||||||
copyWithToast(msg.content);
|
copyWithToast(msg.content);
|
||||||
|
@ -193,10 +210,5 @@ export default definePlugin({
|
||||||
onClick: handleClick,
|
onClick: handleClick,
|
||||||
onContextMenu: handleContextMenu
|
onContextMenu: handleContextMenu
|
||||||
};
|
};
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeButton("ViewRaw");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,10 +20,13 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { saveFile } from "@utils/web";
|
import { saveFile } from "@utils/web";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { filters, mapMangledModuleLazy } from "@webpack";
|
||||||
import { Clipboard, ComponentDispatch } from "@webpack/common";
|
import { Clipboard, ComponentDispatch } from "@webpack/common";
|
||||||
|
|
||||||
const ctxMenuCallbacks = findByPropsLazy("contextMenuCallbackNative");
|
const ctxMenuCallbacks = mapMangledModuleLazy('.tagName)==="TEXTAREA"||', {
|
||||||
|
contextMenuCallbackWeb: filters.byCode('.tagName)==="INPUT"||'),
|
||||||
|
contextMenuCallbackNative: filters.byCode('.tagName)==="TEXTAREA"||')
|
||||||
|
});
|
||||||
|
|
||||||
async function fetchImage(url: string) {
|
async function fetchImage(url: string) {
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
|
|
|
@ -93,7 +93,7 @@ function makeRenderMoreUsers(users: User[]) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClickAvatar(event: React.MouseEvent<HTMLElement, MouseEvent>) {
|
function handleClickAvatar(event: React.UIEvent<HTMLElement, Event>) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ export default definePlugin({
|
||||||
<div
|
<div
|
||||||
style={{ marginLeft: "0.5em", transform: "scale(0.9)" }}
|
style={{ marginLeft: "0.5em", transform: "scale(0.9)" }}
|
||||||
>
|
>
|
||||||
<div onClick={handleClickAvatar}>
|
<div onClick={handleClickAvatar} onKeyPress={handleClickAvatar}>
|
||||||
<UserSummaryItem
|
<UserSummaryItem
|
||||||
users={users}
|
users={users}
|
||||||
guildId={ChannelStore.getChannel(message.channel_id)?.guild_id}
|
guildId={ChannelStore.getChannel(message.channel_id)?.guild_id}
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
import { LiteralUnion } from "type-fest";
|
import { LiteralUnion } from "type-fest";
|
||||||
|
|
||||||
|
export const SYM_IS_PROXY = Symbol("SettingsStore.isProxy");
|
||||||
|
export const SYM_GET_RAW_TARGET = Symbol("SettingsStore.getRawTarget");
|
||||||
|
|
||||||
// Resolves a possibly nested prop in the form of "some.nested.prop" to type of T.some.nested.prop
|
// Resolves a possibly nested prop in the form of "some.nested.prop" to type of T.some.nested.prop
|
||||||
type ResolvePropDeep<T, P> = P extends `${infer Pre}.${infer Suf}`
|
type ResolvePropDeep<T, P> = P extends `${infer Pre}.${infer Suf}`
|
||||||
? Pre extends keyof T
|
? Pre extends keyof T
|
||||||
|
@ -28,6 +31,11 @@ interface SettingsStoreOptions {
|
||||||
// merges the SettingsStoreOptions type into the class
|
// merges the SettingsStoreOptions type into the class
|
||||||
export interface SettingsStore<T extends object> extends SettingsStoreOptions { }
|
export interface SettingsStore<T extends object> extends SettingsStoreOptions { }
|
||||||
|
|
||||||
|
interface ProxyContext<T extends object = any> {
|
||||||
|
root: T;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The SettingsStore allows you to easily create a mutable store that
|
* The SettingsStore allows you to easily create a mutable store that
|
||||||
* has support for global and path-based change listeners.
|
* has support for global and path-based change listeners.
|
||||||
|
@ -35,6 +43,90 @@ export interface SettingsStore<T extends object> extends SettingsStoreOptions {
|
||||||
export class SettingsStore<T extends object> {
|
export class SettingsStore<T extends object> {
|
||||||
private pathListeners = new Map<string, Set<(newData: any) => void>>();
|
private pathListeners = new Map<string, Set<(newData: any) => void>>();
|
||||||
private globalListeners = new Set<(newData: T, path: string) => void>();
|
private globalListeners = new Set<(newData: T, path: string) => void>();
|
||||||
|
private readonly proxyContexts = new WeakMap<any, ProxyContext<T>>();
|
||||||
|
|
||||||
|
private readonly proxyHandler: ProxyHandler<any> = (() => {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
get(target, key: any, receiver) {
|
||||||
|
if (key === SYM_IS_PROXY) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === SYM_GET_RAW_TARGET) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
let v = Reflect.get(target, key, receiver);
|
||||||
|
|
||||||
|
const proxyContext = self.proxyContexts.get(target);
|
||||||
|
if (proxyContext == null) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { root, path } = proxyContext;
|
||||||
|
|
||||||
|
if (!(key in target) && self.getDefaultValue != null) {
|
||||||
|
v = self.getDefaultValue({
|
||||||
|
target,
|
||||||
|
key,
|
||||||
|
root,
|
||||||
|
path
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof v === "object" && v !== null && !v[SYM_IS_PROXY]) {
|
||||||
|
const getPath = `${path}${path && "."}${key}`;
|
||||||
|
return self.makeProxy(v, root, getPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
},
|
||||||
|
set(target, key: string, value) {
|
||||||
|
if (value?.[SYM_IS_PROXY]) {
|
||||||
|
value = value[SYM_GET_RAW_TARGET];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target[key] === value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Reflect.set(target, key, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyContext = self.proxyContexts.get(target);
|
||||||
|
if (proxyContext == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { root, path } = proxyContext;
|
||||||
|
|
||||||
|
const setPath = `${path}${path && "."}${key}`;
|
||||||
|
self.notifyListeners(setPath, value, root);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
deleteProperty(target, key: string) {
|
||||||
|
if (!Reflect.deleteProperty(target, key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyContext = self.proxyContexts.get(target);
|
||||||
|
if (proxyContext == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { root, path } = proxyContext;
|
||||||
|
|
||||||
|
const deletePath = `${path}${path && "."}${key}`;
|
||||||
|
self.notifyListeners(deletePath, undefined, root);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The store object. Making changes to this object will trigger the applicable change listeners
|
* The store object. Making changes to this object will trigger the applicable change listeners
|
||||||
|
@ -51,39 +143,35 @@ export class SettingsStore<T extends object> {
|
||||||
Object.assign(this, options);
|
Object.assign(this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private makeProxy(object: any, root: T = object, path: string = "") {
|
private makeProxy(object: any, root: T = object, path = "") {
|
||||||
const self = this;
|
this.proxyContexts.set(object, {
|
||||||
|
|
||||||
return new Proxy(object, {
|
|
||||||
get(target, key: string) {
|
|
||||||
let v = target[key];
|
|
||||||
|
|
||||||
if (!(key in target) && self.getDefaultValue) {
|
|
||||||
v = self.getDefaultValue({
|
|
||||||
target,
|
|
||||||
key,
|
|
||||||
root,
|
root,
|
||||||
path
|
path
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return new Proxy(object, this.proxyHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof v === "object" && v !== null && !Array.isArray(v))
|
private notifyListeners(pathStr: string, value: any, root: T) {
|
||||||
return self.makeProxy(v, root, `${path}${path && "."}${key}`);
|
const paths = pathStr.split(".");
|
||||||
|
|
||||||
return v;
|
// Because we support any type of settings with OptionType.CUSTOM, and those objects get proxied recursively,
|
||||||
},
|
// the path ends up including all the nested paths (plugins.pluginName.settingName.example.one).
|
||||||
set(target, key: string, value) {
|
// So, we need to extract the top-level setting path (plugins.pluginName.settingName),
|
||||||
if (target[key] === value) return true;
|
// to be able to notify globalListeners and top-level setting name listeners (let { settingName } = settings.use(["settingName"]),
|
||||||
|
// with the new value
|
||||||
|
if (paths.length > 2 && paths[0] === "plugins") {
|
||||||
|
const settingPath = paths.slice(0, 3);
|
||||||
|
const settingPathStr = settingPath.join(".");
|
||||||
|
const settingValue = settingPath.reduce((acc, curr) => acc[curr], root);
|
||||||
|
|
||||||
Reflect.set(target, key, value);
|
this.globalListeners.forEach(cb => cb(root, settingPathStr));
|
||||||
const setPath = `${path}${path && "."}${key}`;
|
this.pathListeners.get(settingPathStr)?.forEach(cb => cb(settingValue));
|
||||||
|
} else {
|
||||||
self.globalListeners.forEach(cb => cb(value, setPath));
|
this.globalListeners.forEach(cb => cb(root, pathStr));
|
||||||
self.pathListeners.get(setPath)?.forEach(cb => cb(value));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
this.pathListeners.get(pathStr)?.forEach(cb => cb(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,7 +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 { findByPropsLazy, findModuleId, proxyLazyWebpack, wreq } from "@webpack";
|
import { filters, findModuleId, mapMangledModuleLazy, proxyLazyWebpack, wreq } from "@webpack";
|
||||||
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
|
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
|
||||||
|
|
||||||
import { LazyComponent } from "./react";
|
import { LazyComponent } from "./react";
|
||||||
|
@ -49,7 +49,7 @@ export interface ModalOptions {
|
||||||
|
|
||||||
type RenderFunction = (props: ModalProps) => ReactNode | Promise<ReactNode>;
|
type RenderFunction = (props: ModalProps) => ReactNode | Promise<ReactNode>;
|
||||||
|
|
||||||
export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as {
|
interface Modals {
|
||||||
ModalRoot: ComponentType<PropsWithChildren<{
|
ModalRoot: ComponentType<PropsWithChildren<{
|
||||||
transitionState: ModalTransitionState;
|
transitionState: ModalTransitionState;
|
||||||
size?: ModalSize;
|
size?: ModalSize;
|
||||||
|
@ -99,7 +99,21 @@ export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as {
|
||||||
hideOnFullscreen?: boolean;
|
hideOnFullscreen?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
}>;
|
}>;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export const Modals: Modals = mapMangledModuleLazy(':"thin")', {
|
||||||
|
ModalRoot: filters.componentByCode('.MODAL,"aria-labelledby":'),
|
||||||
|
ModalHeader: filters.componentByCode(",id:"),
|
||||||
|
ModalContent: filters.componentByCode(".content,"),
|
||||||
|
ModalFooter: filters.componentByCode(".footer,"),
|
||||||
|
ModalCloseButton: filters.componentByCode(".close]:")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ModalRoot = LazyComponent(() => Modals.ModalRoot);
|
||||||
|
export const ModalHeader = LazyComponent(() => Modals.ModalHeader);
|
||||||
|
export const ModalContent = LazyComponent(() => Modals.ModalContent);
|
||||||
|
export const ModalFooter = LazyComponent(() => Modals.ModalFooter);
|
||||||
|
export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);
|
||||||
|
|
||||||
export type MediaModalItem = {
|
export type MediaModalItem = {
|
||||||
url: string;
|
url: string;
|
||||||
|
@ -135,38 +149,33 @@ export const openMediaModal: (props: MediaModalProps) => void = proxyLazyWebpack
|
||||||
return Object.values<any>(openMediaModalModule).find(v => String(v).includes("modalKey:"));
|
return Object.values<any>(openMediaModalModule).find(v => String(v).includes("modalKey:"));
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ModalRoot = LazyComponent(() => Modals.ModalRoot);
|
interface ModalAPI {
|
||||||
export const ModalHeader = LazyComponent(() => Modals.ModalHeader);
|
|
||||||
export const ModalContent = LazyComponent(() => Modals.ModalContent);
|
|
||||||
export const ModalFooter = LazyComponent(() => Modals.ModalFooter);
|
|
||||||
export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);
|
|
||||||
|
|
||||||
export const ModalAPI = findByPropsLazy("openModalLazy");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for the render promise to resolve, then open a modal with it.
|
* Wait for the render promise to resolve, then open a modal with it.
|
||||||
* This is equivalent to render().then(openModal)
|
* This is equivalent to render().then(openModal)
|
||||||
* You should use the Modal components exported by this file
|
* You should use the Modal components exported by this file
|
||||||
*/
|
*/
|
||||||
export const openModalLazy: (render: () => Promise<RenderFunction>, options?: ModalOptions & { contextKey?: string; }) => Promise<string>
|
openModalLazy: (render: () => Promise<RenderFunction>, options?: ModalOptions & { contextKey?: string; }) => Promise<string>;
|
||||||
= proxyLazyWebpack(() => ModalAPI.openModalLazy);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a Modal with the given render function.
|
* Open a Modal with the given render function.
|
||||||
* You should use the Modal components exported by this file
|
* You should use the Modal components exported by this file
|
||||||
*/
|
*/
|
||||||
export const openModal: (render: RenderFunction, options?: ModalOptions, contextKey?: string) => string
|
openModal: (render: RenderFunction, options?: ModalOptions, contextKey?: string) => string;
|
||||||
= proxyLazyWebpack(() => ModalAPI.openModal);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close a modal by its key
|
* Close a modal by its key
|
||||||
*/
|
*/
|
||||||
export const closeModal: (modalKey: string, contextKey?: string) => void
|
closeModal: (modalKey: string, contextKey?: string) => void;
|
||||||
= proxyLazyWebpack(() => ModalAPI.closeModal);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close all open modals
|
* Close all open modals
|
||||||
*/
|
*/
|
||||||
export const closeAllModals: () => void
|
closeAllModals: () => void;
|
||||||
= proxyLazyWebpack(() => ModalAPI.closeAllModals);
|
}
|
||||||
|
|
||||||
|
export const ModalAPI: ModalAPI = mapMangledModuleLazy(".modalKey?", {
|
||||||
|
openModalLazy: filters.byCode(".modalKey?"),
|
||||||
|
openModal: filters.byCode(",instant:"),
|
||||||
|
closeModal: filters.byCode(".onCloseCallback()"),
|
||||||
|
closeAllModals: filters.byCode(".getState();for")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { openModalLazy, openModal, closeModal, closeAllModals } = ModalAPI;
|
||||||
|
|
|
@ -16,8 +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 { ProfileBadge } from "@api/Badges";
|
||||||
|
import { ChatBarButtonFactory } from "@api/ChatButtons";
|
||||||
import { Command } from "@api/Commands";
|
import { Command } from "@api/Commands";
|
||||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
|
import { MemberListDecoratorFactory } from "@api/MemberListDecorators";
|
||||||
|
import { MessageAccessoryFactory } from "@api/MessageAccessories";
|
||||||
|
import { MessageDecorationFactory } from "@api/MessageDecorations";
|
||||||
|
import { MessageClickListener, MessageEditListener, MessageSendListener } from "@api/MessageEvents";
|
||||||
|
import { MessagePopoverButtonFactory } from "@api/MessagePopover";
|
||||||
import { FluxEvents } from "@webpack/types";
|
import { FluxEvents } from "@webpack/types";
|
||||||
import { JSX } from "react";
|
import { JSX } from "react";
|
||||||
import { Promisable } from "type-fest";
|
import { Promisable } from "type-fest";
|
||||||
|
@ -142,6 +149,20 @@ export interface PluginDef {
|
||||||
toolboxActions?: Record<string, () => void>;
|
toolboxActions?: Record<string, () => void>;
|
||||||
|
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
|
|
||||||
|
userProfileBadge?: ProfileBadge;
|
||||||
|
|
||||||
|
onMessageClick?: MessageClickListener;
|
||||||
|
onBeforeMessageSend?: MessageSendListener;
|
||||||
|
onBeforeMessageEdit?: MessageEditListener;
|
||||||
|
|
||||||
|
renderMessagePopoverButton?: MessagePopoverButtonFactory;
|
||||||
|
renderMessageAccessory?: MessageAccessoryFactory;
|
||||||
|
renderMessageDecoration?: MessageDecorationFactory;
|
||||||
|
|
||||||
|
renderMemberListDecorator?: MemberListDecoratorFactory;
|
||||||
|
|
||||||
|
renderChatBarButton?: ChatBarButtonFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum StartAt {
|
export const enum StartAt {
|
||||||
|
@ -168,6 +189,7 @@ export const enum OptionType {
|
||||||
SELECT,
|
SELECT,
|
||||||
SLIDER,
|
SLIDER,
|
||||||
COMPONENT,
|
COMPONENT,
|
||||||
|
CUSTOM
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SettingsDefinition = Record<string, PluginSettingDef>;
|
export type SettingsDefinition = Record<string, PluginSettingDef>;
|
||||||
|
@ -176,15 +198,16 @@ export type SettingsChecks<D extends SettingsDefinition> = {
|
||||||
(IsDisabled<DefinedSettings<D>> & IsValid<PluginSettingType<D[K]>, DefinedSettings<D>>);
|
(IsDisabled<DefinedSettings<D>> & IsValid<PluginSettingType<D[K]>, DefinedSettings<D>>);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PluginSettingDef = (
|
export type PluginSettingDef =
|
||||||
|
(PluginSettingCustomDef & Pick<PluginSettingCommon, "onChange">) |
|
||||||
|
(PluginSettingComponentDef & Omit<PluginSettingCommon, "description" | "placeholder">) | ((
|
||||||
| PluginSettingStringDef
|
| PluginSettingStringDef
|
||||||
| PluginSettingNumberDef
|
| PluginSettingNumberDef
|
||||||
| PluginSettingBooleanDef
|
| PluginSettingBooleanDef
|
||||||
| PluginSettingSelectDef
|
| PluginSettingSelectDef
|
||||||
| PluginSettingSliderDef
|
| PluginSettingSliderDef
|
||||||
| PluginSettingComponentDef
|
|
||||||
| PluginSettingBigIntDef
|
| PluginSettingBigIntDef
|
||||||
) & PluginSettingCommon;
|
) & PluginSettingCommon);
|
||||||
|
|
||||||
export interface PluginSettingCommon {
|
export interface PluginSettingCommon {
|
||||||
description: string;
|
description: string;
|
||||||
|
@ -204,12 +227,14 @@ export interface PluginSettingCommon {
|
||||||
*/
|
*/
|
||||||
target?: "WEB" | "DESKTOP" | "BOTH";
|
target?: "WEB" | "DESKTOP" | "BOTH";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IsDisabled<D = unknown> {
|
interface IsDisabled<D = unknown> {
|
||||||
/**
|
/**
|
||||||
* Checks if this setting should be disabled
|
* Checks if this setting should be disabled
|
||||||
*/
|
*/
|
||||||
disabled?(this: D): boolean;
|
disabled?(this: D): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IsValid<T, D = unknown> {
|
interface IsValid<T, D = unknown> {
|
||||||
/**
|
/**
|
||||||
* Prevents the user from saving settings if this is false or a string
|
* Prevents the user from saving settings if this is false or a string
|
||||||
|
@ -238,12 +263,18 @@ export interface PluginSettingSelectDef {
|
||||||
type: OptionType.SELECT;
|
type: OptionType.SELECT;
|
||||||
options: readonly PluginSettingSelectOption[];
|
options: readonly PluginSettingSelectOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginSettingSelectOption {
|
export interface PluginSettingSelectOption {
|
||||||
label: string;
|
label: string;
|
||||||
value: string | number | boolean;
|
value: string | number | boolean;
|
||||||
default?: boolean;
|
default?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PluginSettingCustomDef {
|
||||||
|
type: OptionType.CUSTOM;
|
||||||
|
default?: any;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PluginSettingSliderDef {
|
export interface PluginSettingSliderDef {
|
||||||
type: OptionType.SLIDER;
|
type: OptionType.SLIDER;
|
||||||
/**
|
/**
|
||||||
|
@ -292,8 +323,10 @@ type PluginSettingType<O extends PluginSettingDef> = O extends PluginSettingStri
|
||||||
O extends PluginSettingBooleanDef ? boolean :
|
O extends PluginSettingBooleanDef ? boolean :
|
||||||
O extends PluginSettingSelectDef ? O["options"][number]["value"] :
|
O extends PluginSettingSelectDef ? O["options"][number]["value"] :
|
||||||
O extends PluginSettingSliderDef ? number :
|
O extends PluginSettingSliderDef ? number :
|
||||||
O extends PluginSettingComponentDef ? any :
|
O extends PluginSettingComponentDef ? O extends { default: infer Default; } ? Default : any :
|
||||||
|
O extends PluginSettingCustomDef ? O extends { default: infer Default; } ? Default : any :
|
||||||
never;
|
never;
|
||||||
|
|
||||||
type PluginSettingDefaultType<O extends PluginSettingDef> = O extends PluginSettingSelectDef ? (
|
type PluginSettingDefaultType<O extends PluginSettingDef> = O extends PluginSettingSelectDef ? (
|
||||||
O["options"] extends { default?: boolean; }[] ? O["options"][number]["value"] : undefined
|
O["options"] extends { default?: boolean; }[] ? O["options"][number]["value"] : undefined
|
||||||
) : O extends { default: infer T; } ? T : undefined;
|
) : O extends { default: infer T; } ? T : undefined;
|
||||||
|
@ -345,13 +378,15 @@ export type PluginOptionsItem =
|
||||||
| PluginOptionBoolean
|
| PluginOptionBoolean
|
||||||
| PluginOptionSelect
|
| PluginOptionSelect
|
||||||
| PluginOptionSlider
|
| PluginOptionSlider
|
||||||
| PluginOptionComponent;
|
| PluginOptionComponent
|
||||||
|
| PluginOptionCustom;
|
||||||
export type PluginOptionString = PluginSettingStringDef & PluginSettingCommon & IsDisabled & IsValid<string>;
|
export type PluginOptionString = PluginSettingStringDef & PluginSettingCommon & IsDisabled & IsValid<string>;
|
||||||
export type PluginOptionNumber = (PluginSettingNumberDef | PluginSettingBigIntDef) & PluginSettingCommon & IsDisabled & IsValid<number | BigInt>;
|
export type PluginOptionNumber = (PluginSettingNumberDef | PluginSettingBigIntDef) & PluginSettingCommon & IsDisabled & IsValid<number | BigInt>;
|
||||||
export type PluginOptionBoolean = PluginSettingBooleanDef & PluginSettingCommon & IsDisabled & IsValid<boolean>;
|
export type PluginOptionBoolean = PluginSettingBooleanDef & PluginSettingCommon & IsDisabled & IsValid<boolean>;
|
||||||
export type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid<PluginSettingSelectOption>;
|
export type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid<PluginSettingSelectOption>;
|
||||||
export type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid<number>;
|
export type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid<number>;
|
||||||
export type PluginOptionComponent = PluginSettingComponentDef & PluginSettingCommon;
|
export type PluginOptionComponent = PluginSettingComponentDef & Omit<PluginSettingCommon, "description" | "placeholder">;
|
||||||
|
export type PluginOptionCustom = PluginSettingCustomDef & Pick<PluginSettingCommon, "onChange">;
|
||||||
|
|
||||||
export type PluginNative<PluginExports extends Record<string, (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any>> = {
|
export type PluginNative<PluginExports extends Record<string, (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any>> = {
|
||||||
[key in keyof PluginExports]:
|
[key in keyof PluginExports]:
|
||||||
|
|
|
@ -16,76 +16,87 @@
|
||||||
* 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 { filters, findByPropsLazy, waitFor } from "@webpack";
|
import { LazyComponent } from "@utils/lazyReact";
|
||||||
|
import { filters, mapMangledModuleLazy, waitFor } from "@webpack";
|
||||||
|
|
||||||
import { waitForComponent } from "./internal";
|
import { waitForComponent } from "./internal";
|
||||||
import * as t from "./types/components";
|
import * as t from "./types/components";
|
||||||
|
|
||||||
export let Forms = {} as {
|
|
||||||
FormTitle: t.FormTitle,
|
const FormTitle = waitForComponent<t.FormTitle>("FormTitle", filters.componentByCode('["defaultMargin".concat', '="h5"'));
|
||||||
FormSection: t.FormSection,
|
const FormText = waitForComponent<t.FormText>("FormText", filters.componentByCode(".SELECTABLE),", ".DISABLED:"));
|
||||||
FormDivider: t.FormDivider,
|
const FormSection = waitForComponent<t.FormSection>("FormSection", filters.componentByCode(".titleId)&&"));
|
||||||
FormText: t.FormText,
|
const FormDivider = waitForComponent<t.FormDivider>("FormDivider", filters.componentByCode(".divider,", ",style:", '"div"', /\.divider,\i\),style:/));
|
||||||
|
|
||||||
|
export const Forms = {
|
||||||
|
FormTitle,
|
||||||
|
FormText,
|
||||||
|
FormSection,
|
||||||
|
FormDivider
|
||||||
};
|
};
|
||||||
|
|
||||||
export let Icons = {} as t.Icons;
|
export const Card = waitForComponent<t.Card>("Card", filters.componentByCode(".editable),", ".outline:"));
|
||||||
|
export const Button = waitForComponent<t.Button>("Button", filters.componentByCode("#{intl::A11Y_LOADING_STARTED}))),!1"));
|
||||||
|
export const Switch = waitForComponent<t.Switch>("Switch", filters.componentByCode(".labelRow,ref:", ".disabledText"));
|
||||||
|
|
||||||
|
const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", {
|
||||||
|
Tooltip: filters.componentByCode("this.renderTooltip()]"),
|
||||||
|
TooltipContainer: filters.componentByCode('="div",')
|
||||||
|
}) as {
|
||||||
|
Tooltip: t.Tooltip,
|
||||||
|
TooltipContainer: t.TooltipContainer;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Tooltip = LazyComponent(() => Tooltips.Tooltip);
|
||||||
|
export const TooltipContainer = LazyComponent(() => Tooltips.TooltipContainer);
|
||||||
|
|
||||||
|
export const TextInput = waitForComponent<t.TextInput>("TextInput", filters.componentByCode(".error]:this.hasError()"));
|
||||||
|
export const TextArea = waitForComponent<t.TextArea>("TextArea", filters.componentByCode("this.getPaddingRight()},id:"));
|
||||||
|
export const Text = waitForComponent<t.Text>("Text", filters.componentByCode('case"always-white"'));
|
||||||
|
export const Heading = waitForComponent<t.Heading>("Heading", filters.componentByCode(">6?{", "variant:"));
|
||||||
|
export const Select = waitForComponent<t.Select>("Select", filters.componentByCode('.selectPositionTop]:"top"===', '"Escape"==='));
|
||||||
|
export const SearchableSelect = waitForComponent<t.SearchableSelect>("SearchableSelect", filters.componentByCode('.selectPositionTop]:"top"===', ".multi]:"));
|
||||||
|
export const Slider = waitForComponent<t.Slider>("Slider", filters.componentByCode('"markDash".concat('));
|
||||||
|
export const Popout = waitForComponent<t.Popout>("Popout", filters.componentByCode("ref:this.ref,preload:"));
|
||||||
|
export const Dialog = waitForComponent<t.Dialog>("Dialog", filters.componentByCode('role:"dialog",tabIndex:-1'));
|
||||||
|
export const TabBar = waitForComponent("TabBar", filters.componentByCode("ref:this.tabBarRef,className:"));
|
||||||
|
export const Paginator = waitForComponent<t.Paginator>("Paginator", filters.componentByCode('rel:"prev",children:'));
|
||||||
|
export const Clickable = waitForComponent<t.Clickable>("Clickable", filters.componentByCode("this.context?this.renderNonInteractive():"));
|
||||||
|
export const Avatar = waitForComponent<t.Avatar>("Avatar", filters.componentByCode(".size-1.375*"));
|
||||||
|
|
||||||
|
export let createScroller: (scrollbarClassName: string, fadeClassName: string, customThemeClassName: string) => t.ScrollerThin;
|
||||||
|
export let scrollerClasses: Record<string, string>;
|
||||||
|
waitFor(filters.byCode('="ltr",orientation:', "customTheme:", "forwardRef"), m => createScroller = m);
|
||||||
|
waitFor(["thin", "auto", "customTheme"], m => scrollerClasses = m);
|
||||||
|
|
||||||
|
export const ScrollerNone = LazyComponent(() => createScroller(scrollerClasses.none, scrollerClasses.fade, scrollerClasses.customTheme));
|
||||||
|
export const ScrollerThin = LazyComponent(() => createScroller(scrollerClasses.thin, scrollerClasses.fade, scrollerClasses.customTheme));
|
||||||
|
export const ScrollerAuto = LazyComponent(() => createScroller(scrollerClasses.auto, scrollerClasses.fade, scrollerClasses.customTheme));
|
||||||
|
|
||||||
|
const { FocusLock_ } = mapMangledModuleLazy("attachTo:null!==", {
|
||||||
|
FocusLock_: filters.componentByCode(".containerRef")
|
||||||
|
}) as {
|
||||||
|
FocusLock_: t.FocusLock;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FocusLock = LazyComponent(() => FocusLock_);
|
||||||
|
|
||||||
export let Card: t.Card;
|
|
||||||
export let Button: t.Button;
|
|
||||||
export let Switch: t.Switch;
|
|
||||||
export let Tooltip: t.Tooltip;
|
|
||||||
export let TooltipContainer: t.TooltipContainer;
|
|
||||||
export let TextInput: t.TextInput;
|
|
||||||
export let TextArea: t.TextArea;
|
|
||||||
export let Text: t.Text;
|
|
||||||
export let Heading: t.Heading;
|
|
||||||
export let Select: t.Select;
|
|
||||||
export let SearchableSelect: t.SearchableSelect;
|
|
||||||
export let Slider: t.Slider;
|
|
||||||
export let ButtonLooks: t.ButtonLooks;
|
|
||||||
export let Popout: t.Popout;
|
|
||||||
export let Dialog: t.Dialog;
|
|
||||||
export let TabBar: any;
|
|
||||||
export let Paginator: t.Paginator;
|
|
||||||
export let ScrollerThin: t.ScrollerThin;
|
|
||||||
export let Clickable: t.Clickable;
|
|
||||||
export let Avatar: t.Avatar;
|
|
||||||
export let FocusLock: t.FocusLock;
|
|
||||||
// token lagger real
|
|
||||||
/** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */
|
|
||||||
export let useToken: t.useToken;
|
export let useToken: t.useToken;
|
||||||
|
waitFor(m => {
|
||||||
|
if (typeof m !== "function") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const str = String(m);
|
||||||
|
return str.includes(".resolve({theme:null") && !str.includes("useMemo");
|
||||||
|
}, m => useToken = m);
|
||||||
|
|
||||||
export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", filters.componentByCode("MASKED_LINK)"));
|
export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", filters.componentByCode("MASKED_LINK)"));
|
||||||
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode("#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}"));
|
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.componentByCode("#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}"));
|
||||||
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
|
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
|
||||||
|
export const OAuth2AuthorizeModal = waitForComponent("OAuth2AuthorizeModal", filters.componentByCode(".authorize),children:", ".contentBackground"));
|
||||||
|
|
||||||
export const { OAuth2AuthorizeModal } = findByPropsLazy("OAuth2AuthorizeModal");
|
export const Animations = mapMangledModuleLazy(".assign({colorNames:", {
|
||||||
|
Transition: filters.componentByCode('["items","children"]', ",null,"),
|
||||||
waitFor(["FormItem", "Button"], m => {
|
animated: filters.byProps("div", "text")
|
||||||
({
|
|
||||||
useToken,
|
|
||||||
Card,
|
|
||||||
Button,
|
|
||||||
FormSwitch: Switch,
|
|
||||||
Tooltip,
|
|
||||||
TooltipContainer,
|
|
||||||
TextInput,
|
|
||||||
TextArea,
|
|
||||||
Text,
|
|
||||||
Select,
|
|
||||||
SearchableSelect,
|
|
||||||
Slider,
|
|
||||||
ButtonLooks,
|
|
||||||
TabBar,
|
|
||||||
Popout,
|
|
||||||
Dialog,
|
|
||||||
Paginator,
|
|
||||||
ScrollerThin,
|
|
||||||
Clickable,
|
|
||||||
Avatar,
|
|
||||||
FocusLock,
|
|
||||||
Heading
|
|
||||||
} = m);
|
|
||||||
Forms = m;
|
|
||||||
Icons = m;
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,16 +17,29 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line path-alias/no-relative
|
// eslint-disable-next-line path-alias/no-relative
|
||||||
import { filters, mapMangledModuleLazy, waitFor } from "../webpack";
|
import { filters, mapMangledModuleLazy, waitFor, wreq } from "../webpack";
|
||||||
import type * as t from "./types/menu";
|
import type * as t from "./types/menu";
|
||||||
|
|
||||||
export let Menu = {} as t.Menu;
|
export const Menu = {} as t.Menu;
|
||||||
|
|
||||||
waitFor(["MenuItem", "MenuSliderControl"], m => Menu = m);
|
// Relies on .name properties added by the MenuItemDemanglerAPI
|
||||||
|
waitFor(m => m.name === "MenuCheckboxItem", (_, id) => {
|
||||||
|
// we have to do this manual require by ID because m is in this case the MenuCheckBoxItem instead of the entire module
|
||||||
|
const module = wreq(id as any);
|
||||||
|
|
||||||
|
for (const e of Object.values(module)) {
|
||||||
|
if (typeof e === "function" && e.name.startsWith("Menu")) {
|
||||||
|
Menu[e.name] = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
waitFor(filters.componentByCode('path:["empty"]'), m => Menu.Menu = m);
|
||||||
|
waitFor(filters.componentByCode("sliderContainer", "slider", "handleSize:16", "=100"), m => Menu.MenuSliderControl = m);
|
||||||
|
waitFor(filters.componentByCode('role:"searchbox', "top:2", "query:"), m => Menu.MenuSearchControl = m);
|
||||||
|
|
||||||
export const ContextMenuApi: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN', {
|
export const ContextMenuApi: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN', {
|
||||||
closeContextMenu: filters.byCode("CONTEXT_MENU_CLOSE"),
|
closeContextMenu: filters.byCode("CONTEXT_MENU_CLOSE"),
|
||||||
openContextMenu: filters.byCode("renderLazy:"),
|
openContextMenu: filters.byCode("renderLazy:"),
|
||||||
openContextMenuLazy: e => typeof e === "function" && e.toString().length < 100
|
openContextMenuLazy: e => typeof e === "function" && e.toString().length < 100
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ export let GuildMemberStore: Stores.GuildMemberStore & t.FluxStore;
|
||||||
export let RelationshipStore: Stores.RelationshipStore & t.FluxStore & {
|
export let RelationshipStore: Stores.RelationshipStore & t.FluxStore & {
|
||||||
/** Get the date (as a string) that the relationship was created */
|
/** Get the date (as a string) that the relationship was created */
|
||||||
getSince(userId: string): string;
|
getSince(userId: string): string;
|
||||||
|
isIgnored(userId: string): boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export let EmojiStore: t.EmojiStore;
|
export let EmojiStore: t.EmojiStore;
|
||||||
|
|
17
src/webpack/common/types/components.d.ts
vendored
17
src/webpack/common/types/components.d.ts
vendored
|
@ -16,16 +16,14 @@
|
||||||
* 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 type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react";
|
import type { ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react";
|
||||||
|
|
||||||
import { IconNames } from "./iconNames";
|
|
||||||
|
|
||||||
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
|
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
|
||||||
export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>;
|
export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>;
|
||||||
export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`;
|
export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`;
|
||||||
|
|
||||||
export type Margins = Record<"marginTop16" | "marginTop8" | "marginBottom8" | "marginTop20" | "marginBottom20", string>;
|
export type Margins = Record<"marginTop16" | "marginTop8" | "marginBottom8" | "marginTop20" | "marginBottom20", string>;
|
||||||
export type ButtonLooks = Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>;
|
|
||||||
|
|
||||||
export type TextProps = PropsWithChildren<HtmlHTMLAttributes<HTMLDivElement> & {
|
export type TextProps = PropsWithChildren<HtmlHTMLAttributes<HTMLDivElement> & {
|
||||||
variant?: TextVariant;
|
variant?: TextVariant;
|
||||||
|
@ -471,15 +469,9 @@ export type ScrollerThin = ComponentType<PropsWithChildren<{
|
||||||
onScroll?(): void;
|
onScroll?(): void;
|
||||||
}>>;
|
}>>;
|
||||||
|
|
||||||
export type Clickable = ComponentType<PropsWithChildren<{
|
export type Clickable = <T extends "a" | "div" | "span" | "li" = "div">(props: PropsWithChildren<ComponentPropsWithRef<T>> & {
|
||||||
className?: string;
|
tag?: T;
|
||||||
|
}) => ReactNode;
|
||||||
href?: string;
|
|
||||||
ignoreKeyPress?: boolean;
|
|
||||||
|
|
||||||
onClick?(): void;
|
|
||||||
onKeyPress?(): void;
|
|
||||||
}>>;
|
|
||||||
|
|
||||||
export type Avatar = ComponentType<PropsWithChildren<{
|
export type Avatar = ComponentType<PropsWithChildren<{
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -510,4 +502,3 @@ export type Icon = ComponentType<JSX.IntrinsicElements["svg"] & {
|
||||||
colorClass?: string;
|
colorClass?: string;
|
||||||
} & Record<string, any>>;
|
} & Record<string, any>>;
|
||||||
|
|
||||||
export type Icons = Record<IconNames, Icon>;
|
|
||||||
|
|
14
src/webpack/common/types/iconNames.d.ts
vendored
14
src/webpack/common/types/iconNames.d.ts
vendored
File diff suppressed because one or more lines are too long
6
src/webpack/common/types/utils.d.ts
vendored
6
src/webpack/common/types/utils.d.ts
vendored
|
@ -16,7 +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 { Channel, Guild, GuildMember, User } from "discord-types/general";
|
import { Channel, Guild, GuildMember, Message, User } from "discord-types/general";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { LiteralUnion } from "type-fest";
|
import { LiteralUnion } from "type-fest";
|
||||||
|
|
||||||
|
@ -133,6 +133,10 @@ export type Permissions = "CREATE_INSTANT_INVITE"
|
||||||
|
|
||||||
export type PermissionsBits = Record<Permissions, bigint>;
|
export type PermissionsBits = Record<Permissions, bigint>;
|
||||||
|
|
||||||
|
export interface MessageSnapshot {
|
||||||
|
message: Message;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Locale {
|
export interface Locale {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
|
|
@ -172,26 +172,36 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
|
||||||
|
|
||||||
if (!exports) return;
|
if (!exports) return;
|
||||||
|
|
||||||
// There are (at the time of writing) 11 modules exporting the window
|
|
||||||
// Make these non enumerable to improve webpack search performance
|
|
||||||
if (require.c) {
|
if (require.c) {
|
||||||
let foundWindow = false;
|
let shouldMakeNonEnumerable = false;
|
||||||
|
|
||||||
if (exports === window) {
|
nonEnumerableChecking: {
|
||||||
foundWindow = true;
|
// There are (at the time of writing) 11 modules exporting the window,
|
||||||
} else if (typeof exports === "object") {
|
// and also modules exporting DOMTokenList, which breaks webpack finding
|
||||||
if (exports?.default === window) {
|
// Make these non enumerable to improve search performance and avoid erros
|
||||||
foundWindow = true;
|
if (exports === window || exports[Symbol.toStringTag] === "DOMTokenList") {
|
||||||
} else {
|
shouldMakeNonEnumerable = true;
|
||||||
for (const nested in exports) if (nested.length <= 3) {
|
break nonEnumerableChecking;
|
||||||
if (exports[nested] === window) {
|
|
||||||
foundWindow = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof exports !== "object") {
|
||||||
|
break nonEnumerableChecking;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exports.default === window || exports.default?.[Symbol.toStringTag] === "DOMTokenList") {
|
||||||
|
shouldMakeNonEnumerable = true;
|
||||||
|
break nonEnumerableChecking;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const nested in exports) {
|
||||||
|
if (exports[nested] === window || exports[nested]?.[Symbol.toStringTag] === "DOMTokenList") {
|
||||||
|
shouldMakeNonEnumerable = true;
|
||||||
|
break nonEnumerableChecking;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundWindow) {
|
if (shouldMakeNonEnumerable) {
|
||||||
Object.defineProperty(require.c, id, {
|
Object.defineProperty(require.c, id, {
|
||||||
value: require.c[id],
|
value: require.c[id],
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
|
@ -221,7 +231,7 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
|
||||||
subscriptions.delete(filter);
|
subscriptions.delete(filter);
|
||||||
callback(exports.default, id);
|
callback(exports.default, id);
|
||||||
} else {
|
} else {
|
||||||
for (const nested in exports) if (nested.length <= 3) {
|
for (const nested in exports) {
|
||||||
if (exports[nested] && filter(exports[nested])) {
|
if (exports[nested] && filter(exports[nested])) {
|
||||||
subscriptions.delete(filter);
|
subscriptions.delete(filter);
|
||||||
callback(exports[nested], id);
|
callback(exports[nested], id);
|
||||||
|
|
|
@ -56,11 +56,14 @@ export const filters = {
|
||||||
: m => props.every(p => m[p] !== void 0),
|
: m => props.every(p => m[p] !== void 0),
|
||||||
|
|
||||||
byCode: (...code: CodeFilter): FilterFn => {
|
byCode: (...code: CodeFilter): FilterFn => {
|
||||||
code = code.map(canonicalizeMatch);
|
const parsedCode = code.map(canonicalizeMatch);
|
||||||
return m => {
|
const filter = m => {
|
||||||
if (typeof m !== "function") return false;
|
if (typeof m !== "function") return false;
|
||||||
return stringMatches(Function.prototype.toString.call(m), code);
|
return stringMatches(Function.prototype.toString.call(m), parsedCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
filter.$$vencordProps = [...code];
|
||||||
|
return filter;
|
||||||
},
|
},
|
||||||
byStoreName: (name: StoreNameFilter): FilterFn => m =>
|
byStoreName: (name: StoreNameFilter): FilterFn => m =>
|
||||||
m.constructor?.displayName === name,
|
m.constructor?.displayName === name,
|
||||||
|
@ -131,8 +134,7 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn
|
||||||
return isWaitFor ? [found, key] : found;
|
return isWaitFor ? [found, key] : found;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the length check makes search about 20% faster
|
for (const nestedMod in mod.exports) {
|
||||||
for (const nestedMod in mod.exports) if (nestedMod.length <= 3) {
|
|
||||||
const nested = mod.exports[nestedMod];
|
const nested = mod.exports[nestedMod];
|
||||||
if (nested && filter(nested)) {
|
if (nested && filter(nested)) {
|
||||||
return isWaitFor ? [nested, key] : nested;
|
return isWaitFor ? [nested, key] : nested;
|
||||||
|
@ -163,7 +165,7 @@ export function findAll(filter: FilterFn) {
|
||||||
|
|
||||||
if (mod.exports.default && filter(mod.exports.default))
|
if (mod.exports.default && filter(mod.exports.default))
|
||||||
ret.push(mod.exports.default);
|
ret.push(mod.exports.default);
|
||||||
else for (const nestedMod in mod.exports) if (nestedMod.length <= 3) {
|
else for (const nestedMod in mod.exports) {
|
||||||
const nested = mod.exports[nestedMod];
|
const nested = mod.exports[nestedMod];
|
||||||
if (nested && filter(nested)) ret.push(nested);
|
if (nested && filter(nested)) ret.push(nested);
|
||||||
}
|
}
|
||||||
|
@ -226,8 +228,7 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const nestedMod in mod.exports)
|
for (const nestedMod in mod.exports) {
|
||||||
if (nestedMod.length <= 3) {
|
|
||||||
const nested = mod.exports[nestedMod];
|
const nested = mod.exports[nestedMod];
|
||||||
if (nested && filter(nested)) {
|
if (nested && filter(nested)) {
|
||||||
results[j] = nested;
|
results[j] = nested;
|
||||||
|
|
Loading…
Reference in a new issue