Merge branch 'dev'
This commit is contained in:
commit
8a52ddde7a
99 changed files with 1280 additions and 1567 deletions
|
|
@ -10,7 +10,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
|
||||
### Extra included plugins
|
||||
<details>
|
||||
<summary>149 additional plugins</summary>
|
||||
<summary>148 additional plugins</summary>
|
||||
|
||||
### All Platforms
|
||||
- AllCallTimers by MaxHerbold & D3SOX
|
||||
|
|
@ -77,7 +77,6 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- ImagePreview by Creations
|
||||
- ImgToGif by zyqunix
|
||||
- InRole by nin0dev
|
||||
- IrcColors by Grzesiek11
|
||||
- IRememberYou by zoodogood
|
||||
- Jumpscare by Surgedevs
|
||||
- JumpToStart by Samwich
|
||||
|
|
@ -186,11 +185,10 @@ MacOS
|
|||
|
||||
Linux
|
||||
- [CLI](https://github.com/Equicord/Equilotl/releases/latest/download/EquilotlCli-Linux)
|
||||
- [AUR](https://aur.archlinux.org/packages?O=0&K=equibop)
|
||||
- [AUR](https://aur.archlinux.org/packages?O=0&K=equicord)
|
||||
```shell
|
||||
sh -c "$(curl -sS https://raw.githubusercontent.com/Equicord/Equicord/refs/heads/main/misc/install.sh)"
|
||||
```
|
||||
|
||||
## Installing Equicord Devbuild
|
||||
|
||||
### Dependencies
|
||||
|
|
|
|||
|
|
@ -103,7 +103,13 @@ export default tseslint.config(
|
|||
"no-invalid-regexp": "error",
|
||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||
"no-duplicate-imports": "error",
|
||||
"dot-notation": "error",
|
||||
"@typescript-eslint/dot-notation": [
|
||||
"error",
|
||||
{
|
||||
"allowPrivateClassPropertyAccess": true,
|
||||
"allowProtectedClassPropertyAccess": true
|
||||
}
|
||||
],
|
||||
"no-useless-escape": [
|
||||
"error",
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,55 +1,50 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Exit immediately if a command exits with a non-zero status
|
||||
set -e
|
||||
|
||||
# Check if the script is run as root
|
||||
# Constants
|
||||
INSTALLER_PATH="$HOME/.equilotl"
|
||||
GITHUB_URL="https://github.com/Equicord/Equilotl/releases/latest/download/EquilotlCli-Linux"
|
||||
|
||||
# Check for root
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
echo "Run this script as a normal user, not root!"
|
||||
echo "Run me as a normal user, not root!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Define variables
|
||||
installer_path="$HOME/.equilotl"
|
||||
github_url="https://github.com/Equicord/Equilotl/releases/latest/download/EquilotlCli-Linux"
|
||||
download_installer() {
|
||||
curl -sSL "$GITHUB_URL" --output "$INSTALLER_PATH"
|
||||
chmod +x "$INSTALLER_PATH"
|
||||
}
|
||||
|
||||
# Inform user about the update check
|
||||
echo "Checking if the installer needs updating..."
|
||||
|
||||
# Fetch the latest modified date from the server
|
||||
latest_modified=$(curl -sI "$github_url" | grep -i "last-modified" | cut -d' ' -f2-)
|
||||
if [ -f "$INSTALLER_PATH" ]; then
|
||||
latest_modified=$(curl -sI "$GITHUB_URL" | grep -i "last-modified" | cut -d' ' -f2-)
|
||||
local_modified=$(stat -c "%y" "$INSTALLER_PATH" | cut -d' ' -f1-2)
|
||||
|
||||
# Check if the installer exists locally
|
||||
if [ -f "$installer_path" ]; then
|
||||
# Get the local file's last modified date
|
||||
local_modified=$(stat -c "%y" "$installer_path" | cut -d' ' -f1-2)
|
||||
|
||||
# Compare the local file's date with the server's latest modified date
|
||||
if [ "$local_modified" = "$latest_modified" ]; then
|
||||
echo "The installer is up-to-date."
|
||||
else
|
||||
echo "The installer is outdated. Downloading the latest version..."
|
||||
curl -sSL "$github_url" --output "$installer_path"
|
||||
chmod +x "$installer_path"
|
||||
download_installer
|
||||
fi
|
||||
else
|
||||
echo "Installer not found. Downloading it..."
|
||||
curl -sSL "$github_url" --output "$installer_path"
|
||||
chmod +x "$installer_path"
|
||||
download_installer
|
||||
fi
|
||||
|
||||
# Check for sudo or doas availability to run the installer with elevated privileges
|
||||
# Try to run the installer with sudo or doas
|
||||
if command -v sudo >/dev/null; then
|
||||
echo "Running installer with sudo..."
|
||||
sudo "$installer_path"
|
||||
sudo "$INSTALLER_PATH"
|
||||
elif command -v doas >/dev/null; then
|
||||
echo "Running installer with doas..."
|
||||
doas "$installer_path"
|
||||
doas "$INSTALLER_PATH"
|
||||
else
|
||||
echo "Neither sudo nor doas were found. Please install one to proceed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Provide script attribution
|
||||
# Credits
|
||||
echo "Original script forked from Vencord"
|
||||
echo "Modified by PhoenixAceVFX & Crxaw for Equicord Updater"
|
||||
|
|
|
|||
|
|
@ -122,4 +122,4 @@
|
|||
"node": ">=18",
|
||||
"pnpm": ">=9"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -296,6 +296,7 @@ page.on("error", e => {
|
|||
page.on("pageerror", e => {
|
||||
if (e.message.includes("Sentry successfully disabled")) return;
|
||||
if (e.message.includes("the network is offline")) return;
|
||||
if (e.message.includes("Cannot read properties of undefined (reading 'includes')")) return;
|
||||
|
||||
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) {
|
||||
console.error("[Page Error]", e.message);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ const Badges = new Set<ProfileBadge>();
|
|||
* Register a new badge with the Badges API
|
||||
* @param badge The badge to register
|
||||
*/
|
||||
export function addBadge(badge: ProfileBadge) {
|
||||
export function addProfileBadge(badge: ProfileBadge) {
|
||||
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
||||
Badges.add(badge);
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ export function addBadge(badge: ProfileBadge) {
|
|||
* Unregister a badge from the Badges API
|
||||
* @param badge The badge to remove
|
||||
*/
|
||||
export function removeBadge(badge: ProfileBadge) {
|
||||
export function removeProfileBadge(badge: ProfileBadge) {
|
||||
return Badges.delete(badge);
|
||||
}
|
||||
|
||||
|
|
@ -102,20 +102,3 @@ export interface BadgeUserArgs {
|
|||
userId: 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
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 interface ChatBarButtonProps {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
* 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 { JSX } from "react";
|
||||
|
||||
|
|
@ -39,27 +40,32 @@ interface DecoratorProps {
|
|||
user: User;
|
||||
[key: string]: any;
|
||||
}
|
||||
export type Decorator = (props: DecoratorProps) => JSX.Element | null;
|
||||
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
|
||||
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) {
|
||||
decorators.set(identifier, { decorator, onlyIn });
|
||||
export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) {
|
||||
decorators.set(identifier, { render, onlyIn });
|
||||
}
|
||||
|
||||
export function removeDecorator(identifier: string) {
|
||||
export function removeMemberListDecorator(identifier: string) {
|
||||
decorators.delete(identifier);
|
||||
}
|
||||
|
||||
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
||||
const isInGuild = !!(props.guildId);
|
||||
return Array.from(decorators.values(), decoratorObj => {
|
||||
const { decorator, onlyIn } = decoratorObj;
|
||||
// this can most likely be done cleaner
|
||||
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
||||
return decorator(props);
|
||||
return Array.from(
|
||||
decorators.entries(),
|
||||
([key, { render: Decorator, onlyIn }]) => {
|
||||
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
|
||||
return null;
|
||||
|
||||
return (
|
||||
<ErrorBoundary noop key={key} message={`Failed to render ${key} Member List Decorator`}>
|
||||
<Decorator {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
);
|
||||
}
|
||||
|
|
@ -16,28 +16,29 @@
|
|||
* 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 Accessory = {
|
||||
callback: AccessoryCallback;
|
||||
export type MessageAccessoryFactory = (props: Record<string, any>) => ReactNode;
|
||||
export type MessageAccessory = {
|
||||
render: MessageAccessoryFactory;
|
||||
position?: number;
|
||||
};
|
||||
|
||||
export const accessories = new Map<String, Accessory>();
|
||||
export const accessories = new Map<string, MessageAccessory>();
|
||||
|
||||
export function addAccessory(
|
||||
export function addMessageAccessory(
|
||||
identifier: string,
|
||||
callback: AccessoryCallback,
|
||||
render: MessageAccessoryFactory,
|
||||
position?: number
|
||||
) {
|
||||
accessories.set(identifier, {
|
||||
callback,
|
||||
render,
|
||||
position,
|
||||
});
|
||||
}
|
||||
|
||||
export function removeAccessory(identifier: string) {
|
||||
export function removeMessageAccessory(identifier: string) {
|
||||
accessories.delete(identifier);
|
||||
}
|
||||
|
||||
|
|
@ -45,15 +46,12 @@ export function _modifyAccessories(
|
|||
elements: JSX.Element[],
|
||||
props: Record<string, any>
|
||||
) {
|
||||
for (const accessory of accessories.values()) {
|
||||
let accessories = accessory.callback(props);
|
||||
if (accessories == null)
|
||||
continue;
|
||||
|
||||
if (!Array.isArray(accessories))
|
||||
accessories = [accessories];
|
||||
else if (accessories.length === 0)
|
||||
continue;
|
||||
for (const [key, accessory] of accessories.entries()) {
|
||||
const res = (
|
||||
<ErrorBoundary message={`Failed to render ${key} Message Accessory`} key={key}>
|
||||
<accessory.render {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
elements.splice(
|
||||
accessory.position != null
|
||||
|
|
@ -62,7 +60,7 @@ export function _modifyAccessories(
|
|||
: accessory.position
|
||||
: elements.length,
|
||||
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/>.
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Channel, Message } from "discord-types/general/index.js";
|
||||
import { JSX } from "react";
|
||||
|
||||
interface DecorationProps {
|
||||
export interface MessageDecorationProps {
|
||||
author: {
|
||||
/**
|
||||
* Will be username if the user has no nickname
|
||||
|
|
@ -45,20 +46,25 @@ interface DecorationProps {
|
|||
message: Message;
|
||||
[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);
|
||||
}
|
||||
|
||||
export function removeDecoration(identifier: string) {
|
||||
export function removeMessageDecoration(identifier: string) {
|
||||
decorations.delete(identifier);
|
||||
}
|
||||
|
||||
export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] {
|
||||
return [...decorations.values()].map(decoration => {
|
||||
return decoration(props);
|
||||
});
|
||||
export function __addDecorationsToMessage(props: MessageDecorationProps): (JSX.Element | null)[] {
|
||||
return Array.from(
|
||||
decorations.entries(),
|
||||
([key, Decoration]) => (
|
||||
<ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}>
|
||||
<Decoration {...props} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -75,11 +75,11 @@ export interface MessageExtra {
|
|||
openWarningPopout: (props: any) => any;
|
||||
}
|
||||
|
||||
export type SendListener = (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 MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
||||
export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
||||
|
||||
const sendListeners = new Set<SendListener>();
|
||||
const editListeners = new Set<EditListener>();
|
||||
const sendListeners = new Set<MessageSendListener>();
|
||||
const editListeners = new Set<MessageEditListener>();
|
||||
|
||||
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
||||
extra.replyOptions = replyOptions;
|
||||
|
|
@ -113,29 +113,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.
|
||||
*/
|
||||
export function addPreSendListener(listener: SendListener) {
|
||||
export function addMessagePreSendListener(listener: MessageSendListener) {
|
||||
sendListeners.add(listener);
|
||||
return listener;
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
return listener;
|
||||
}
|
||||
export function removePreSendListener(listener: SendListener) {
|
||||
export function removeMessagePreSendListener(listener: MessageSendListener) {
|
||||
return sendListeners.delete(listener);
|
||||
}
|
||||
export function removePreEditListener(listener: EditListener) {
|
||||
export function removeMessagePreEditListener(listener: MessageEditListener) {
|
||||
return editListeners.delete(listener);
|
||||
}
|
||||
|
||||
|
||||
// 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) {
|
||||
// message object may be outdated, so (try to) fetch latest one
|
||||
|
|
@ -149,11 +149,11 @@ export function _handleClick(message: Message, channel: Channel, event: MouseEve
|
|||
}
|
||||
}
|
||||
|
||||
export function addClickListener(listener: ClickListener) {
|
||||
export function addMessageClickListener(listener: MessageClickListener) {
|
||||
listeners.add(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
export function removeClickListener(listener: ClickListener) {
|
||||
export function removeMessageClickListener(listener: MessageClickListener) {
|
||||
return listeners.delete(listener);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import type { ComponentType, MouseEventHandler } from "react";
|
|||
|
||||
const logger = new Logger("MessagePopover");
|
||||
|
||||
export interface ButtonItem {
|
||||
export interface MessagePopoverButtonItem {
|
||||
key?: string,
|
||||
label: string,
|
||||
icon: ComponentType<any>,
|
||||
|
|
@ -33,23 +33,23 @@ export interface ButtonItem {
|
|||
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,
|
||||
item: getButtonItem,
|
||||
item: MessagePopoverButtonFactory,
|
||||
) {
|
||||
buttons.set(identifier, item);
|
||||
}
|
||||
|
||||
export function removeButton(identifier: string) {
|
||||
export function removeMessagePopoverButton(identifier: string) {
|
||||
buttons.delete(identifier);
|
||||
}
|
||||
|
||||
export function _buildPopoverElements(
|
||||
Component: React.ComponentType<ButtonItem>,
|
||||
Component: React.ComponentType<MessagePopoverButtonItem>,
|
||||
message: Message
|
||||
) {
|
||||
const items: React.ReactNode[] = [];
|
||||
|
|
|
|||
|
|
@ -16,10 +16,8 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { JSX } from "react";
|
||||
|
||||
const logger = new Logger("ServerListAPI");
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { ComponentType } from "react";
|
||||
|
||||
export const enum ServerListRenderPosition {
|
||||
Above,
|
||||
|
|
@ -27,34 +25,36 @@ export const enum ServerListRenderPosition {
|
|||
Below,
|
||||
}
|
||||
|
||||
const renderFunctions = {
|
||||
[ServerListRenderPosition.Above]: new Set<Function>(),
|
||||
[ServerListRenderPosition.In]: new Set<Function>(),
|
||||
[ServerListRenderPosition.Below]: new Set<Function>(),
|
||||
};
|
||||
const componentsIn = new Set<ComponentType>();
|
||||
const componentsAbove = new Set<ComponentType>();
|
||||
const componentsBelow = new Set<ComponentType>();
|
||||
|
||||
function getRenderFunctions(position: ServerListRenderPosition) {
|
||||
return renderFunctions[position];
|
||||
switch (position) {
|
||||
case ServerListRenderPosition.Above:
|
||||
return componentsAbove;
|
||||
case ServerListRenderPosition.In:
|
||||
return componentsIn;
|
||||
case ServerListRenderPosition.Below:
|
||||
return componentsBelow;
|
||||
}
|
||||
}
|
||||
|
||||
export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
||||
export function addServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||
getRenderFunctions(position).add(renderFunction);
|
||||
}
|
||||
|
||||
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
||||
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||
getRenderFunctions(position).delete(renderFunction);
|
||||
}
|
||||
|
||||
export const renderAll = (position: ServerListRenderPosition) => {
|
||||
const ret: Array<JSX.Element> = [];
|
||||
|
||||
for (const renderFunction of getRenderFunctions(position)) {
|
||||
try {
|
||||
ret.unshift(renderFunction());
|
||||
} catch (e) {
|
||||
logger.error("Failed to render server list element:", e);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return Array.from(
|
||||
getRenderFunctions(position),
|
||||
(Component, i) => (
|
||||
<ErrorBoundary noop key={i}>
|
||||
<Component />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
|
@ -70,8 +70,7 @@ const ErrorBoundary = LazyComponent(() => {
|
|||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
||||
logger.error("A component threw an Error\n", error);
|
||||
logger.error("Component Stack", errorInfo.componentStack);
|
||||
logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -81,7 +81,8 @@ const Components: Record<OptionType, React.ComponentType<ISettingElementProps<an
|
|||
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
||||
[OptionType.SELECT]: SettingSelectComponent,
|
||||
[OptionType.SLIDER]: SettingSliderComponent,
|
||||
[OptionType.COMPONENT]: SettingCustomComponent
|
||||
[OptionType.COMPONENT]: SettingCustomComponent,
|
||||
[OptionType.CUSTOM]: () => null,
|
||||
};
|
||||
|
||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||
|
|
@ -129,7 +130,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
for (const [key, value] of Object.entries(tempSettings)) {
|
||||
const option = plugin.options[key];
|
||||
pluginSettings[key] = value;
|
||||
option?.onChange?.(value);
|
||||
|
||||
if (option.type === OptionType.CUSTOM) continue;
|
||||
if (option?.restartNeeded) restartNeeded = true;
|
||||
}
|
||||
if (plugin.afterSave) {
|
||||
|
|
@ -148,7 +150,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
||||
} else {
|
||||
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) {
|
||||
setTempSettings(s => ({ ...s, [key]: newValue }));
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { migratePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
|
@ -34,13 +34,13 @@ export default definePlugin({
|
|||
}
|
||||
],
|
||||
start() {
|
||||
this.preSend = addPreSendListener((_, msg) => {
|
||||
this.preSend = addMessagePreSendListener((_, msg) => {
|
||||
msg.content = msg.content.replace(/@someone/g, () => `<@${randomUser()}>`);
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removeMessagePreSendListener(this.preSend);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
|
|
@ -116,7 +116,7 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
start() {
|
||||
addButton("DecodeBase64", msg => {
|
||||
addMessagePopoverButton("DecodeBase64", msg => {
|
||||
const handleClick = () => {
|
||||
const base64Strings = findBase64Strings(msg.content);
|
||||
const decodedContent = decodeBase64Strings(base64Strings);
|
||||
|
|
@ -157,6 +157,6 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
stop() {
|
||||
removeButton("DecodeBase64");
|
||||
removeMessagePopoverButton("DecodeBase64");
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ import { classNameFactory } from "@api/Styles";
|
|||
import { getIntlMessage } from "@utils/discord";
|
||||
import { classes } from "@utils/misc";
|
||||
import { closeModal, openModal } from "@utils/modal";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Avatar, ChannelStore, ContextMenuApi, FluxDispatcher, GuildStore, Menu, ReadStateStore, ReadStateUtils, Text, Tooltip, useDrag, useDrop, useEffect, useRef, UserStore } from "@webpack/common";
|
||||
import { Avatar, ChannelStore, ContextMenuApi, FluxDispatcher, GuildStore, Icons, Menu, ReadStateStore, ReadStateUtils, Text, Tooltip, useDrag, useDrop, useEffect, useRef, UserStore } from "@webpack/common";
|
||||
|
||||
import { BasicChannelTabsProps, Bookmark, BookmarkFolder, BookmarkProps, CircleQuestionIcon, isBookmarkFolder, settings, switchChannel, useBookmarks } from "../util";
|
||||
import { NotificationDot } from "./ChannelTab";
|
||||
|
|
@ -17,7 +16,7 @@ import { BookmarkContextMenu, EditModal } from "./ContextMenus";
|
|||
|
||||
const cl = classNameFactory("vc-channeltabs-");
|
||||
|
||||
const { StarIcon } = findByPropsLazy("StarIcon");
|
||||
const { StarIcon } = Icons;
|
||||
|
||||
function FolderIcon({ fill }: { fill: string; }) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { classNameFactory } from "@api/Styles";
|
|||
import { getIntlMessage, getUniqueUsername } from "@utils/discord";
|
||||
import { classes } from "@utils/misc";
|
||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { Avatar, ChannelStore, ContextMenuApi, Dots, GuildStore, PresenceStore, ReadStateStore, Text, TypingStore, useDrag, useDrop, useRef, UserStore, useStateFromStores } from "@webpack/common";
|
||||
import { Avatar, ChannelStore, ContextMenuApi, Dots, GuildStore, Icons, PresenceStore, ReadStateStore, Text, TypingStore, useDrag, useDrop, useRef, UserStore, useStateFromStores } from "@webpack/common";
|
||||
import { Channel, Guild, User } from "discord-types/general";
|
||||
|
||||
import { ChannelTabsProps, CircleQuestionIcon, closeTab, isTabSelected, moveDraggedTabs, moveToTab, openedTabs, settings } from "../util";
|
||||
|
|
@ -17,7 +17,7 @@ import { TabContextMenu } from "./ContextMenus";
|
|||
const { getBadgeWidthForValue } = findByPropsLazy("getBadgeWidthForValue");
|
||||
const dotStyles = findByPropsLazy("numberBadge", "textBadge");
|
||||
|
||||
const { FriendsIcon } = findByPropsLazy("FriendsIcon");
|
||||
const { FriendsIcon } = Icons;
|
||||
const ChannelTypeIcon = findComponentByCodeLazy(".iconContainerWithGuildIcon,");
|
||||
|
||||
const cl = classNameFactory("vc-channeltabs-");
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@
|
|||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, ContextMenuApi, Flex, FluxDispatcher, Forms, useCallback, useEffect, useRef, UserStore, useState } from "@webpack/common";
|
||||
import { Button, ContextMenuApi, Flex, FluxDispatcher, Forms, Icons, useCallback, useEffect, useRef, UserStore, useState } from "@webpack/common";
|
||||
|
||||
import { BasicChannelTabsProps, ChannelTabsProps, createTab, handleChannelSwitch, openedTabs, openStartupTabs, saveTabs, settings, setUpdaterFunction, useGhostTabs } from "../util";
|
||||
import BookmarkContainer from "./BookmarkContainer";
|
||||
|
|
@ -16,7 +15,7 @@ import { BasicContextMenu } from "./ContextMenus";
|
|||
|
||||
type TabSet = Record<string, ChannelTabsProps[]>;
|
||||
|
||||
const { PlusSmallIcon } = findByPropsLazy("PlusSmallIcon");
|
||||
const { PlusSmallIcon } = Icons;
|
||||
const cl = classNameFactory("vc-channeltabs-");
|
||||
|
||||
export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Icons } from "@webpack/common";
|
||||
|
||||
import { ChannelTabsPreview } from "../components/ChannelTabsContainer";
|
||||
|
||||
|
|
@ -74,4 +74,4 @@ export const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
export const { CircleQuestionIcon } = findByPropsLazy("CircleQuestionIcon");
|
||||
export const { CircleQuestionIcon } = Icons;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPreSendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Alerts, ChannelStore, Forms } from "@webpack/common";
|
||||
|
|
@ -41,7 +41,7 @@ export default definePlugin({
|
|||
authors: [Devs.Samwich],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
start() {
|
||||
this.preSend = addPreSendListener(async (channelId, messageObj, extra) => {
|
||||
this.preSend = addMessagePreSendListener(async (channelId, messageObj, extra) => {
|
||||
|
||||
if (ChannelStore.getChannel(channelId.toString()).isDM()) return { cancel: false };
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addChatBarButton, ChatBarButton, ChatBarButtonFactory, removeChatBarButton } from "@api/ChatButtons";
|
||||
import {
|
||||
ApplicationCommandInputType,
|
||||
ApplicationCommandOptionType,
|
||||
sendBotMessage,
|
||||
} from "@api/Commands";
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { sleep } from "@utils/misc";
|
||||
import definePlugin from "@utils/types";
|
||||
|
|
@ -41,12 +41,12 @@ interface IMessageCreate {
|
|||
message: Message;
|
||||
}
|
||||
|
||||
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
[enabled, setEnabled] = useState(false);
|
||||
const [buttonDisabled, setButtonDisabled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const listener: SendListener = async (_, message) => {
|
||||
const listener: MessageSendListener = async (_, message) => {
|
||||
if (enabled) {
|
||||
const groupChannel = await DataStore.get("encryptcordChannelId");
|
||||
// @ts-expect-error typing issue
|
||||
|
|
@ -72,8 +72,8 @@ const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
|||
}
|
||||
};
|
||||
|
||||
addPreSendListener(listener);
|
||||
return () => void removePreSendListener(listener);
|
||||
addMessagePreSendListener(listener);
|
||||
return () => void removeMessagePreSendListener(listener);
|
||||
}, [enabled]);
|
||||
|
||||
if (!isMainChat) return null;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -72,7 +72,7 @@ export default definePlugin({
|
|||
authors: [Devs.newwares],
|
||||
start() {
|
||||
enableStyle(styles);
|
||||
addButton("vc-findreply", message => {
|
||||
addMessagePopoverButton("vc-findreply", message => {
|
||||
if (!message.id) return null;
|
||||
const replies = findReplies(message);
|
||||
if (Vencord.Settings.plugins.FindReply.hideButtonIfNoReply && !replies.length) return null;
|
||||
|
|
@ -117,7 +117,7 @@ export default definePlugin({
|
|||
});
|
||||
},
|
||||
stop() {
|
||||
removeButton("vc-findreply");
|
||||
removeMessagePopoverButton("vc-findreply");
|
||||
root && root.unmount();
|
||||
element?.remove();
|
||||
disableStyle(styles);
|
||||
|
|
|
|||
|
|
@ -148,10 +148,10 @@ export default definePlugin({
|
|||
description: "Adds badges showcasing how long you have been friends with a user for",
|
||||
authors: [Devs.Samwich],
|
||||
start() {
|
||||
getBadgesToApply().forEach(thing => Vencord.Api.Badges.addBadge(thing));
|
||||
getBadgesToApply().forEach(thing => Vencord.Api.Badges.addProfileBadge(thing));
|
||||
|
||||
},
|
||||
stop() {
|
||||
getBadgesToApply().forEach(thing => Vencord.Api.Badges.removeBadge(thing));
|
||||
getBadgesToApply().forEach(thing => Vencord.Api.Badges.removeProfileBadge(thing));
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addChatBarButton, ChatBarButton, ChatBarButtonFactory, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { ErrorCard } from "@components/ErrorCard";
|
||||
import { Link } from "@components/Link";
|
||||
|
|
@ -96,14 +96,14 @@ const furudosettings = definePluginSettings(
|
|||
|
||||
function messageSendListenerFuncs() {
|
||||
if (furudosettings.store.isEnabled) {
|
||||
addPreSendListener(presendObject);
|
||||
addMessagePreSendListener(presendObject);
|
||||
} else {
|
||||
removePreSendListener(presendObject);
|
||||
removeMessagePreSendListener(presendObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const FurudoSpeakChatToggle: ChatBarButton = ({ isMainChat }) => {
|
||||
const FurudoSpeakChatToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const { isEnabled, showIcon } = furudosettings.use(["isEnabled", "showIcon"]);
|
||||
const toggle = async () => {
|
||||
const done = await togglefunc();
|
||||
|
|
@ -160,7 +160,7 @@ const ChatBarContextCheckbox: NavContextMenuPatchCallback = children => {
|
|||
};
|
||||
|
||||
|
||||
const presendObject: SendListener = async (channelId, msg, extra) => {
|
||||
const presendObject: MessageSendListener = async (channelId, msg, extra) => {
|
||||
const messageRef = extra.replyOptions.messageReference;
|
||||
const repliedMessage = ((messageRef?.message_id && messageRef.channel_id) && MessageStore.getMessage(messageRef?.channel_id, messageRef?.message_id)) || undefined;
|
||||
const apikey = Settings.plugins.FurudoSpeak.apiKey || "";
|
||||
|
|
@ -243,6 +243,6 @@ export default definePlugin({
|
|||
|
||||
stop: () => {
|
||||
removeChatBarButton("FurudoSpeak");
|
||||
removePreSendListener(presendObject);
|
||||
removeMessagePreSendListener(presendObject);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges";
|
||||
import { addProfileBadge, BadgePosition, ProfileBadge, removeProfileBadge } from "@api/Badges";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
|
|
@ -137,8 +137,8 @@ export default definePlugin({
|
|||
description: "Adds global badges from other client mods",
|
||||
authors: [Devs.HypedDomi, EquicordDevs.Wolfie],
|
||||
|
||||
start: () => addBadge(Badge),
|
||||
stop: () => removeBadge(Badge),
|
||||
start: () => addProfileBadge(Badge),
|
||||
stop: () => removeProfileBadge(Badge),
|
||||
|
||||
options: {
|
||||
showPrefix: {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -77,13 +77,13 @@ export default definePlugin({
|
|||
}
|
||||
}),
|
||||
async start() {
|
||||
this.preSend = addPreSendListener((_channelId, msg) => {
|
||||
this.preSend = addMessagePreSendListener((_channelId, msg) => {
|
||||
const newContent = this.replaceBadVerbs(this.replaceBadNouns(msg.content));
|
||||
msg.content = newContent;
|
||||
});
|
||||
},
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removeMessagePreSendListener(this.preSend);
|
||||
},
|
||||
getEnabledBadNouns() {
|
||||
const thingToReturn: string[] = [];
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import "./styles.css";
|
|||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { get, set } from "@api/DataStore";
|
||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||
import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
|
|
@ -147,7 +147,7 @@ export default definePlugin({
|
|||
buildCss();
|
||||
}
|
||||
|
||||
addAccessory("vc-hide-message", ({ message }) => {
|
||||
addMessageAccessory("vc-hide-message", ({ message }) => {
|
||||
if (hiddenMessages.has(message.id) && settings.store.showNotice) return <HideMessageAccessory id={message.id} />;
|
||||
return null;
|
||||
});
|
||||
|
|
@ -156,7 +156,7 @@ export default definePlugin({
|
|||
async stop() {
|
||||
for (const id of hiddenMessages.keys()) revealMessage(id);
|
||||
|
||||
removeAccessory("vc-hide-message");
|
||||
removeMessageAccessory("vc-hide-message");
|
||||
|
||||
style.remove();
|
||||
hiddenMessages.clear();
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import "./style.css";
|
|||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { DataStore } from "@api/index";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { classes } from "@utils/misc";
|
||||
|
|
@ -114,7 +114,7 @@ export default definePlugin({
|
|||
if (await DataStore.keys(HolyNoteStore).then(keys => !keys.includes("Main"))) return noteHandler.newNoteBook("Main");
|
||||
if (!noteHandlerCache.has("Main")) await DataStoreToCache();
|
||||
|
||||
addButton("HolyNotes", message => {
|
||||
addMessagePopoverButton("HolyNotes", message => {
|
||||
return {
|
||||
label: "Save Note",
|
||||
icon: NoteButtonPopover,
|
||||
|
|
@ -127,6 +127,6 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
async stop() {
|
||||
removeButton("HolyNotes");
|
||||
removeMessagePopoverButton("HolyNotes");
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { classes } from "@utils/misc";
|
||||
|
|
@ -103,7 +103,7 @@ export default definePlugin({
|
|||
return id !== "" ? id : this.settings.store.emojiID;
|
||||
},
|
||||
async start() {
|
||||
addButton("Husk", msg => {
|
||||
addMessagePopoverButton("Husk", msg => {
|
||||
return {
|
||||
label: "Husk",
|
||||
icon: Husk,
|
||||
|
|
@ -121,7 +121,7 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
stop() {
|
||||
removeButton("Husk");
|
||||
removeMessagePopoverButton("Husk");
|
||||
},
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import "styles.css?managed";
|
||||
|
||||
import { DataStore } from "@api/index";
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { Heart } from "@components/Heart";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { openUserProfile } from "@utils/discord";
|
||||
|
|
@ -364,7 +364,7 @@ export default definePlugin({
|
|||
await data.initializeUsersCollection();
|
||||
data.writeGuildsOwnersToCollection();
|
||||
data.writeMembersFromUserGuildsToCollection();
|
||||
data._onMessagePreSend_preSend = addPreSendListener(
|
||||
data._onMessagePreSend_preSend = addMessagePreSendListener(
|
||||
data.onMessagePreSend.bind(data)
|
||||
);
|
||||
data.storageAutoSaveProtocol();
|
||||
|
|
@ -385,7 +385,7 @@ export default definePlugin({
|
|||
stop() {
|
||||
const dataManager = this.dataManager as Data;
|
||||
|
||||
removePreSendListener(dataManager._onMessagePreSend_preSend);
|
||||
removeMessagePreSendListener(dataManager._onMessagePreSend_preSend);
|
||||
clearInterval(dataManager._storageAutoSaveProtocol_interval);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -328,17 +328,16 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
find: ".guildFilter:null",
|
||||
replacement: {
|
||||
match: /function (\i)\(\i\){let{message:\i,gotoMessage/,
|
||||
replace: "$self.renderMsg = $1; $&"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".guildFilter:null",
|
||||
replacement: {
|
||||
match: /onClick:\(\)=>(\i\.\i\.deleteRecentMention\((\i)\.id\))/,
|
||||
replace: "onClick: () => $2._keyword ? $self.deleteKeyword($2.id) : $1"
|
||||
}
|
||||
replacement: [
|
||||
{
|
||||
match: /function (\i)\(\i\){let{message:\i,gotoMessage/,
|
||||
replace: "$self.renderMsg = $1; $&"
|
||||
},
|
||||
{
|
||||
match: /onClick:\(\)=>(\i\.\i\.deleteRecentMention\((\i)\.id\))/,
|
||||
replace: "onClick: () => $2._keyword ? $self.deleteKeyword($2.id) : $1"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { preload, unload } from "./images";
|
|||
import { cl } from "./ui";
|
||||
import openQrModal from "./ui/modals/QrModal";
|
||||
|
||||
const qrModalOpen = false;
|
||||
export default definePlugin({
|
||||
name: "LoginWithQR",
|
||||
description: "Allows you to login to another device by scanning a login QR code, just like on mobile!",
|
||||
|
|
@ -87,7 +88,7 @@ export default definePlugin({
|
|||
}
|
||||
],
|
||||
|
||||
qrModalOpen: false,
|
||||
qrModalOpen,
|
||||
|
||||
insertScanQrButton: (button: ReactElement) => (
|
||||
<div className={cl("settings-btns")}>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addChatBarButton, ChatBarButton, ChatBarButtonFactory, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getCurrentChannel, sendMessage } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
|
|
@ -14,7 +14,7 @@ async function handleButtonClick() {
|
|||
sendMessage(getCurrentChannel().id, { content: "meow" });
|
||||
}
|
||||
|
||||
const ChatBarIcon: ChatBarButton = () => {
|
||||
const ChatBarIcon: ChatBarButtonFactory = () => {
|
||||
return (
|
||||
<ChatBarButton tooltip="Meow" onClick={handleButtonClick}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 576 512"><path fill="currentColor" d="M320 192h17.1c22.1 38.3 63.5 64 110.9 64c11 0 21.8-1.4 32-4v228c0 17.7-14.3 32-32 32s-32-14.3-32-32V339.2L280 448h56c17.7 0 32 14.3 32 32s-14.3 32-32 32H192c-53 0-96-43-96-96V192.5c0-16.1-12-29.8-28-31.8l-7.9-1c-17.5-2.2-30-18.2-27.8-35.7S50.5 94 68 96.2l7.9 1c48 6 84.1 46.8 84.1 95.3v85.3c34.4-51.7 93.2-85.8 160-85.8m160 26.5c-10 3.5-20.8 5.5-32 5.5c-28.4 0-54-12.4-71.6-32c-3.7-4.1-7-8.5-9.9-13.2C357.3 164 352 146.6 352 128V10.7C352 4.8 356.7.1 362.6 0h.2c3.3 0 6.4 1.6 8.4 4.2v.1l12.8 17l27.2 36.3L416 64h64l4.8-6.4L512 21.3l12.8-17v-.1c2-2.6 5.1-4.2 8.4-4.2h.2c5.9.1 10.6 4.8 10.6 10.7V128c0 17.3-4.6 33.6-12.6 47.6c-11.3 19.8-29.6 35.2-51.4 42.9M432 128a16 16 0 1 0-32 0a16 16 0 1 0 32 0m48 16a16 16 0 1 0 0-32a16 16 0 1 0 0 32" /></svg>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export default definePlugin({
|
|||
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::STICKER_BUTTON_LABEL}",
|
||||
find: "ChannelStickerPickerButton",
|
||||
replacement: [{
|
||||
match: /(children:\(0,\i\.jsx\)\()([\w.]+?)(,{innerClassName.{10,30}\.stickerButton)/,
|
||||
replace: (_, head, button, tail) => {
|
||||
|
|
@ -56,7 +56,7 @@ export default definePlugin({
|
|||
{
|
||||
find: ".gifts)",
|
||||
replacement: {
|
||||
match: /\(null===\(\i=\i\.stickers\).*?(\i)\.push\((\(0,\i\.jsx\))\((.+?),{disabled:\w,type:(\i)},"sticker"\)\)/,
|
||||
match: /,.{0,5}\(null===\(\w=\w\.stickers\)\|\|void 0.*?(\w)\.push\((\(0,\w\.jsx\))\((.+?),{disabled:\w,type:(\w)},"sticker"\)\)\)/,
|
||||
replace: (m, _, jsx, compo, type) => {
|
||||
const c = "arguments[0].type";
|
||||
return `${m},${c}?.submit?.button&&${_}.push(${jsx}(${compo},{disabled:!${c}?.submit?.button,type:${type},stickersType:"stickers+"},"stickers+"))`;
|
||||
|
|
@ -64,7 +64,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: "role:\"tablist\",\"aria-label\":",
|
||||
find: `role:"tablist","aria-label":`,
|
||||
replacement: {
|
||||
match: /role:"tablist",.*?,?"aria-label":.+?\),children:(\[.*?\)\]}\)}\):null,)(.*?closePopout:\w.*?:null)/s,
|
||||
replace: m => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
export default definePlugin({
|
||||
|
|
@ -13,12 +13,12 @@ export default definePlugin({
|
|||
authors: [Devs.Samwich],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
start() {
|
||||
this.preSend = addPreSendListener((channelId, msg) => {
|
||||
this.preSend = addMessagePreSendListener((channelId, msg) => {
|
||||
msg.content = textProcessing(msg.content);
|
||||
});
|
||||
},
|
||||
stop() {
|
||||
this.preSend = removePreSendListener((channelId, msg) => {
|
||||
this.preSend = removeMessagePreSendListener((channelId, msg) => {
|
||||
msg.content = textProcessing(msg.content);
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
addPreSendListener,
|
||||
removePreSendListener,
|
||||
SendListener,
|
||||
addMessagePreSendListener,
|
||||
MessageSendListener,
|
||||
removeMessagePreSendListener,
|
||||
} from "@api/MessageEvents";
|
||||
import {
|
||||
definePluginSettings,
|
||||
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
const presendObject: SendListener = (channelId, msg) => {
|
||||
const presendObject: MessageSendListener = (channelId, msg) => {
|
||||
msg.content = textProcessing(msg.content);
|
||||
};
|
||||
|
||||
|
|
@ -38,10 +38,10 @@ export default definePlugin({
|
|||
authors: [Devs.Samwich],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
start() {
|
||||
addPreSendListener(presendObject);
|
||||
addMessagePreSendListener(presendObject);
|
||||
},
|
||||
stop() {
|
||||
removePreSendListener(presendObject);
|
||||
removeMessagePreSendListener(presendObject);
|
||||
},
|
||||
settings,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -36,12 +36,12 @@ export default definePlugin({
|
|||
settings,
|
||||
|
||||
start() {
|
||||
this.preSend = addPreSendListener((_, msg) => {
|
||||
this.preSend = addMessagePreSendListener((_, msg) => {
|
||||
msg.content = replaceQuestionMarks(msg.content);
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removeMessagePreSendListener(this.preSend);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPreSendListener, MessageExtra, MessageObject, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreSendListener, MessageExtra, MessageObject, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
|
|
@ -14,6 +14,6 @@ export default definePlugin({
|
|||
name: "RemixMe",
|
||||
description: "Turns every single message with attachment to have remix tag",
|
||||
authors: [EquicordDevs.kvba],
|
||||
start: () => addPreSendListener(handleMessage),
|
||||
stop: () => removePreSendListener(handleMessage)
|
||||
start: () => addMessagePreSendListener(handleMessage),
|
||||
stop: () => removeMessagePreSendListener(handleMessage)
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { sendMessage } from "@utils/discord";
|
||||
|
|
@ -103,7 +103,7 @@ export default definePlugin({
|
|||
"message": messageCtxPatch
|
||||
},
|
||||
start() {
|
||||
addButton("vc-repeat", message => {
|
||||
addMessagePopoverButton("vc-repeat", message => {
|
||||
if (!message.content && message.stickerItems.length === 0) return null;
|
||||
|
||||
return {
|
||||
|
|
@ -119,7 +119,7 @@ export default definePlugin({
|
|||
document.addEventListener("keydown", keydownListener);
|
||||
},
|
||||
stop() {
|
||||
removeButton("vc-repeat");
|
||||
removeMessagePopoverButton("vc-repeat");
|
||||
|
||||
document.removeEventListener("keyup", keyupListener);
|
||||
document.removeEventListener("keydown", keydownListener);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addChatBarButton, ChatBarButton, ChatBarButtonFactory, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { openModal } from "@utils/modal";
|
||||
|
|
@ -21,7 +21,7 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
const SekaiStickerChatButton: ChatBarButton = () => {
|
||||
const SekaiStickerChatButton: ChatBarButtonFactory = () => {
|
||||
return (
|
||||
<ChatBarButton onClick={() => openModal(props => <SekaiStickersModal modalProps={props} settings={settings} />)} tooltip="Sekai Stickers">
|
||||
{kanadeSvg()}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -12,7 +12,7 @@ import { MessageStore } from "@webpack/common";
|
|||
|
||||
import { transferMessage } from "./native";
|
||||
|
||||
const presendObject: SendListener = async (channelId, msg, extra) => {
|
||||
const presendObject: MessageSendListener = async (channelId, msg, extra) => {
|
||||
const messageRef = extra.replyOptions.messageReference;
|
||||
const repliedMessage = ((messageRef?.message_id && messageRef.channel_id) && MessageStore.getMessage(messageRef?.channel_id, messageRef?.message_id)) || undefined;
|
||||
msg.content = await transferMessage(msg, Settings.plugins.Shakespearean.model, repliedMessage);
|
||||
|
|
@ -32,9 +32,9 @@ export default definePlugin({
|
|||
}
|
||||
}),
|
||||
start() {
|
||||
addPreSendListener(presendObject);
|
||||
addMessagePreSendListener(presendObject);
|
||||
},
|
||||
stop() {
|
||||
removePreSendListener(presendObject);
|
||||
removeMessagePreSendListener(presendObject);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
|
||||
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { isEquicordPluginDev, isPluginDev } from "@utils/misc";
|
||||
import definePlugin from "@utils/types";
|
||||
|
|
@ -145,9 +145,9 @@ export default definePlugin({
|
|||
dependencies: ["MessageDecorationsAPI"],
|
||||
settings,
|
||||
start: () => {
|
||||
addDecoration("vc-show-badges-in-chat", props => props.message?.author ? <ChatBadges author={props.message.author} /> : null);
|
||||
addMessageDecoration("vc-show-badges-in-chat", props => props.message?.author ? <ChatBadges author={props.message.author} /> : null);
|
||||
},
|
||||
stop: () => {
|
||||
removeDecoration("vc-show-badges-in-chat");
|
||||
removeMessageDecoration("vc-show-badges-in-chat");
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addChatBarButton, ChatBarButton, ChatBarButtonFactory, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -40,7 +40,7 @@ const settings = definePluginSettings(
|
|||
},
|
||||
});
|
||||
|
||||
const SignatureToggle: ChatBarButton = ({ isMainChat }) => {
|
||||
const SignatureToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]);
|
||||
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
||||
|
||||
|
|
@ -104,12 +104,12 @@ export default definePlugin({
|
|||
start: () => {
|
||||
if (settings.store.isEnabled) true;
|
||||
addChatBarButton("Signature", SignatureToggle);
|
||||
addPreSendListener(handleMessage);
|
||||
addMessagePreSendListener(handleMessage);
|
||||
},
|
||||
stop: () => {
|
||||
if (settings.store.isEnabled) false;
|
||||
removeChatBarButton("Signature");
|
||||
removePreSendListener(handleMessage);
|
||||
removeMessagePreSendListener(handleMessage);
|
||||
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ChatBarButton } from "@api/ChatButtons";
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { classes } from "@utils/misc";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ export function IconWithTooltip({ text, icon, onClick }) {
|
|||
</Tooltip>;
|
||||
}
|
||||
|
||||
export const ChatBarIcon: ChatBarButton = () => {
|
||||
export const ChatBarIcon: ChatBarButtonFactory = () => {
|
||||
return (
|
||||
<ChatBarButton tooltip="Open SoundBoard Log"
|
||||
onClick={openSoundBoardLog}>
|
||||
|
|
|
|||
|
|
@ -16,15 +16,15 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
||||
import { addChatBarButton, ChatBarButton, ChatBarButtonFactory, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { React, useEffect, useState } from "@webpack/common";
|
||||
|
||||
let lastState = false;
|
||||
|
||||
const ReverseMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
||||
const ReverseMessageToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const [enabled, setEnabled] = useState(lastState);
|
||||
|
||||
function setEnabledValue(value: boolean) {
|
||||
|
|
@ -34,12 +34,12 @@ const ReverseMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
const listener: SendListener = async (_, message) => {
|
||||
const listener: MessageSendListener = async (_, message) => {
|
||||
if (enabled && message.content) message.content = message.content.split("").reverse().join("");
|
||||
};
|
||||
|
||||
addPreSendListener(listener);
|
||||
return () => void removePreSendListener(listener);
|
||||
addMessagePreSendListener(listener);
|
||||
return () => void removeMessagePreSendListener(listener);
|
||||
}, [enabled]);
|
||||
|
||||
if (!isMainChat) return null;
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
import "./style.css";
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories";
|
||||
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore, Menu } from "@webpack/common";
|
||||
|
|
@ -56,9 +56,9 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
start() {
|
||||
addAccessory("ec-translation", props => <Accessory message={props.message} />);
|
||||
addMessageAccessory("ec-translation", props => <Accessory message={props.message} />);
|
||||
|
||||
addButton("ec-translate", message => {
|
||||
addMessagePopoverButton("ec-translate", message => {
|
||||
if (!message.content) return null;
|
||||
|
||||
return {
|
||||
|
|
@ -71,7 +71,7 @@ export default definePlugin({
|
|||
});
|
||||
},
|
||||
stop() {
|
||||
removeButton("ec-translate");
|
||||
removeAccessory("ec-translation");
|
||||
removeMessagePopoverButton("ec-translate");
|
||||
removeMessageAccessory("ec-translation");
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
import "./style.css";
|
||||
|
||||
import { addAccessory } from "@api/MessageAccessories";
|
||||
import { addButton } from "@api/MessagePopover";
|
||||
import { addMessageAccessory } from "@api/MessageAccessories";
|
||||
import { addMessagePopoverButton } from "@api/MessagePopover";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -57,8 +57,8 @@ export default definePlugin({
|
|||
description: "Converts metric units to Imperal units and vice versa",
|
||||
authors: [Devs.sadan],
|
||||
start() {
|
||||
addAccessory("vc-converter", props => <ConverterAccessory message={props.message} />);
|
||||
addButton("vc-converter", message => {
|
||||
addMessageAccessory("vc-converter", props => <ConverterAccessory message={props.message} />);
|
||||
addMessagePopoverButton("vc-converter", message => {
|
||||
if (!message.content) return null;
|
||||
return {
|
||||
label: "Convert Units",
|
||||
|
|
|
|||
|
|
@ -23,18 +23,18 @@ const settings = definePluginSettings({
|
|||
{ label: "Nitro", value: true, default: true },
|
||||
],
|
||||
},
|
||||
urlForDB: {
|
||||
databaseToUse: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Which Database url to use to load avatars, KNOW WHAT YOUR DOING",
|
||||
options: [
|
||||
{
|
||||
label: "UserPFP Default DB",
|
||||
label: "UserPFP Main DB",
|
||||
value: "https://userpfp.github.io/UserPFP/source/data.json",
|
||||
default: true
|
||||
},
|
||||
{
|
||||
label: "UserPFP Backup DB",
|
||||
value: "https://userpfp.thororen.com/data.json"
|
||||
value: "https://userpfp.equicord.fyi/data.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ export default definePlugin({
|
|||
return data.avatars[user.id] ?? original(user, animated, size);
|
||||
},
|
||||
async start() {
|
||||
const res = await fetch(settings.store.urlForDB)
|
||||
await fetch(settings.store.databaseToUse)
|
||||
.then(async res => {
|
||||
if (res.ok) this.data = data = await res.json();
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import { findOption, RequiredMessageOption } from "@api/Commands";
|
||||
import { addPreEditListener, addPreSendListener, MessageObject, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreEditListener, addMessagePreSendListener, MessageObject, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -199,14 +199,14 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
start() {
|
||||
this.preSend = addPreSendListener((_, msg) => this.onSend(msg));
|
||||
this.preEdit = addPreEditListener((_cid, _mid, msg) =>
|
||||
this.preSend = addMessagePreSendListener((_, msg) => this.onSend(msg));
|
||||
this.preEdit = addMessagePreEditListener((_cid, _mid, msg) =>
|
||||
this.onSend(msg)
|
||||
);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removePreEditListener(this.preEdit);
|
||||
removeMessagePreSendListener(this.preSend);
|
||||
removeMessagePreEditListener(this.preEdit);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addChatBarButton, ChatBarButton, ChatBarButtonFactory, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getCurrentChannel, sendMessage } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
|
|
@ -14,7 +14,7 @@ async function handleButtonClick() {
|
|||
sendMessage(getCurrentChannel().id, { content: "woof" });
|
||||
}
|
||||
|
||||
const ChatBarIcon: ChatBarButton = () => {
|
||||
const ChatBarIcon: ChatBarButtonFactory = () => {
|
||||
return (
|
||||
<ChatBarButton tooltip="Woof" onClick={handleButtonClick}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 576 512"><path fill="currentColor" d="m309.6 158.5l23.1-138.7C334.6 8.4 344.5 0 356.1 0c7.5 0 14.5 3.5 19 9.5L392 32h52.1c12.7 0 24.9 5.1 33.9 14.1L496 64h56c13.3 0 24 10.7 24 24v24c0 44.2-35.8 80-80 80h-69.3l-5.1 30.5zM416 256.1V480c0 17.7-14.3 32-32 32h-32c-17.7 0-32-14.3-32-32V364.8c-24 12.3-51.2 19.2-80 19.2s-56-6.9-80-19.2V480c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V249.8c-28.8-10.9-51.4-35.3-59.2-66.5L1 167.8c-4.3-17.1 6.1-34.5 23.3-38.8s34.5 6.1 38.8 23.3l3.9 15.5C70.5 182 83.3 192 98 192h205.8zM464 80a16 16 0 1 0-32 0a16 16 0 1 0 32 0" /></svg>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -19,7 +19,7 @@ const settings = definePluginSettings(
|
|||
}
|
||||
);
|
||||
|
||||
const presendObject: SendListener = (_, msg) => {
|
||||
const presendObject: MessageSendListener = (_, msg) => {
|
||||
const sentences = msg.content.split(/(?<=\w\.)\s/);
|
||||
const blockedWordsArray: string[] = settings.store.blockedWords.split(", ");
|
||||
|
||||
|
|
@ -39,9 +39,9 @@ export default definePlugin({
|
|||
settings,
|
||||
|
||||
start() {
|
||||
addPreSendListener(presendObject);
|
||||
addMessagePreSendListener(presendObject);
|
||||
},
|
||||
stop() {
|
||||
removePreSendListener(presendObject);
|
||||
removeMessagePreSendListener(presendObject);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -118,8 +118,8 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
async start() {
|
||||
Vencord.Api.Badges.addBadge(ContributorBadge);
|
||||
Vencord.Api.Badges.addBadge(EquicordContributorBadge);
|
||||
Vencord.Api.Badges.addProfileBadge(ContributorBadge);
|
||||
Vencord.Api.Badges.addProfileBadge(EquicordContributorBadge);
|
||||
await loadAllBadges();
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addAccessory } from "@api/MessageAccessories";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
|
|
@ -143,7 +142,7 @@ export default definePlugin({
|
|||
required: true,
|
||||
description: "Helps us provide support to you",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"],
|
||||
dependencies: ["UserSettingsAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
|
|
@ -256,6 +255,85 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
|
||||
renderMessageAccessory(props) {
|
||||
const buttons = [] as JSX.Element[];
|
||||
|
||||
const shouldAddUpdateButton =
|
||||
!IS_UPDATER_DISABLED
|
||||
&& (
|
||||
(props.channel.id === KNOWN_ISSUES_CHANNEL_ID) ||
|
||||
(props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID)
|
||||
)
|
||||
&& props.message.content?.includes("update");
|
||||
|
||||
if (shouldAddUpdateButton) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="vc-update"
|
||||
color={Button.Colors.GREEN}
|
||||
onClick={async () => {
|
||||
try {
|
||||
if (await forceUpdate())
|
||||
showToast("Success! Restarting...", Toasts.Type.SUCCESS);
|
||||
else
|
||||
showToast("Already up to date!", Toasts.Type.MESSAGE);
|
||||
} catch (e) {
|
||||
new Logger(this.name).error("Error while updating:", e);
|
||||
showToast("Failed to update :(", Toasts.Type.FAILURE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Update Now
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.channel.id === SUPPORT_CHANNEL_ID) {
|
||||
if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="vc-dbg"
|
||||
onClick={async () => sendMessage(props.channel.id, { content: await generateDebugInfoMessage() })}
|
||||
>
|
||||
Run /vencord-debug
|
||||
</Button>,
|
||||
<Button
|
||||
key="vc-plg-list"
|
||||
onClick={async () => sendMessage(props.channel.id, { content: generatePluginList() })}
|
||||
>
|
||||
Run /vencord-plugins
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.message.author.id === VENBOT_USER_ID) {
|
||||
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
|
||||
if (match) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="vc-run-snippet"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await AsyncFunction(match[1])();
|
||||
showToast("Success!", Toasts.Type.SUCCESS);
|
||||
} catch (e) {
|
||||
new Logger(this.name).error("Error while running snippet:", e);
|
||||
showToast("Failed to run snippet :(", Toasts.Type.FAILURE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Run Snippet
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buttons.length
|
||||
? <Flex>{buttons}</Flex>
|
||||
: null;
|
||||
},
|
||||
|
||||
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
||||
const userId = channel.getRecipientId();
|
||||
if (!isPluginDev(userId) || !isEquicordPluginDev(userId)) return null;
|
||||
|
|
@ -270,85 +348,4 @@ export default definePlugin({
|
|||
</Card>
|
||||
);
|
||||
}, { noop: true }),
|
||||
|
||||
start() {
|
||||
addAccessory("equicord-debug", props => {
|
||||
const buttons = [] as JSX.Element[];
|
||||
|
||||
const shouldAddUpdateButton =
|
||||
!IS_UPDATER_DISABLED
|
||||
&& (
|
||||
(props.channel.id === KNOWN_ISSUES_CHANNEL_ID) ||
|
||||
(props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID)
|
||||
)
|
||||
&& props.message.content?.includes("update");
|
||||
|
||||
if (shouldAddUpdateButton) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="vc-update"
|
||||
color={Button.Colors.GREEN}
|
||||
onClick={async () => {
|
||||
try {
|
||||
if (await forceUpdate())
|
||||
showToast("Success! Restarting...", Toasts.Type.SUCCESS);
|
||||
else
|
||||
showToast("Already up to date!", Toasts.Type.MESSAGE);
|
||||
} catch (e) {
|
||||
new Logger(this.name).error("Error while updating:", e);
|
||||
showToast("Failed to update :(", Toasts.Type.FAILURE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Update Now
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.channel.id === SUPPORT_CHANNEL_ID) {
|
||||
if (props.message.content.includes("/equicord-debug") || props.message.content.includes("/equicord-plugins")) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="vc-dbg"
|
||||
onClick={async () => sendMessage(props.channel.id, { content: await generateDebugInfoMessage() })}
|
||||
>
|
||||
Run /equicord-debug
|
||||
</Button>,
|
||||
<Button
|
||||
key="vc-plg-list"
|
||||
onClick={async () => sendMessage(props.channel.id, { content: generatePluginList() })}
|
||||
>
|
||||
Run /equicord-plugins
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.message.author.id === VENBOT_USER_ID) {
|
||||
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
|
||||
if (match) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="vc-run-snippet"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await AsyncFunction(match[1])();
|
||||
showToast("Success!", Toasts.Type.SUCCESS);
|
||||
} catch (e) {
|
||||
new Logger(this.name).error("Error while running snippet:", e);
|
||||
showToast("Failed to run snippet :(", Toasts.Type.FAILURE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Run Snippet
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buttons.length
|
||||
? <Flex>{buttons}</Flex>
|
||||
: null;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cann
|
|||
const styles = findByPropsLazy("accountProfilePopoutWrapper");
|
||||
|
||||
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 { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export default definePlugin({
|
|||
if (!role) return;
|
||||
|
||||
if (role.colorString) {
|
||||
children.push(
|
||||
children.unshift(
|
||||
<Menu.MenuItem
|
||||
id="vc-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) {
|
||||
children.push(
|
||||
<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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ export default definePlugin({
|
|||
patches: [{
|
||||
find: "renderConnectionStatus(){",
|
||||
replacement: {
|
||||
match: /(?<=renderConnectionStatus\(\){.+\.channel,children:).+?}\):\i(?=}\))/,
|
||||
replace: "[$&, $self.renderTimer(this.props.channel.id)]"
|
||||
match: /(renderConnectionStatus\(\){.+\.channel,children:)(.+?}\):\i)(?=}\))/,
|
||||
replace: "$1[$2,$self.renderTimer(this.props.channel.id)]"
|
||||
}
|
||||
}],
|
||||
|
||||
|
|
|
|||
|
|
@ -17,11 +17,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
addPreEditListener,
|
||||
addPreSendListener,
|
||||
MessageObject,
|
||||
removePreEditListener,
|
||||
removePreSendListener
|
||||
MessageObject
|
||||
} from "@api/MessageEvents";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
|
@ -36,7 +32,18 @@ export default definePlugin({
|
|||
name: "ClearURLs",
|
||||
description: "Removes tracking garbage from URLs",
|
||||
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) {
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -95,10 +95,9 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: 'console.warn("[DEPRECATED] Please use `subscribeWithSelector` middleware");',
|
||||
all: true,
|
||||
find: '"AppCrashedFatalReport: getLastCrash not supported."',
|
||||
replacement: {
|
||||
match: /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\);/,
|
||||
match: /console\.log\("AppCrashedFatalReport: getLastCrash not supported\."\);/,
|
||||
replace: ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* 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, Settings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
||||
|
|
@ -858,7 +858,7 @@ export default definePlugin({
|
|||
});
|
||||
}
|
||||
|
||||
this.preSend = addPreSendListener(async (channelId, messageObj, extra) => {
|
||||
this.preSend = addMessagePreSendListener(async (channelId, messageObj, extra) => {
|
||||
const { guildId } = this;
|
||||
|
||||
let hasBypass = false;
|
||||
|
|
@ -949,7 +949,7 @@ export default definePlugin({
|
|||
return { cancel: false };
|
||||
});
|
||||
|
||||
this.preEdit = addPreEditListener(async (channelId, __, messageObj) => {
|
||||
this.preEdit = addMessagePreEditListener(async (channelId, __, messageObj) => {
|
||||
if (!s.enableEmojiBypass) return;
|
||||
|
||||
let hasBypass = false;
|
||||
|
|
@ -981,7 +981,7 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removePreEditListener(this.preEdit);
|
||||
removeMessagePreSendListener(this.preSend);
|
||||
removeMessagePreEditListener(this.preEdit);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@
|
|||
*/
|
||||
|
||||
import { get, set } from "@api/DataStore";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore } from "@webpack/common";
|
||||
import { MessageSnapshot } from "@webpack/types";
|
||||
|
||||
let style: HTMLStyleElement;
|
||||
|
||||
|
|
@ -38,7 +38,25 @@ export default definePlugin({
|
|||
name: "HideAttachments",
|
||||
description: "Hide attachments and Embeds for individual messages via hover button",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
|
||||
renderMessagePopoverButton(msg) {
|
||||
// @ts-ignore - discord-types lags behind discord.
|
||||
const hasAttachmentsInShapshots = msg.messageSnapshots.some(
|
||||
(snapshot: MessageSnapshot) => snapshot?.message.attachments.length
|
||||
);
|
||||
|
||||
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length && !hasAttachmentsInShapshots) return null;
|
||||
|
||||
const isHidden = hiddenMessages.has(msg.id);
|
||||
|
||||
return {
|
||||
label: isHidden ? "Show Attachments" : "Hide Attachments",
|
||||
icon: isHidden ? ImageVisible : ImageInvisible,
|
||||
message: msg,
|
||||
channel: ChannelStore.getChannel(msg.channel_id),
|
||||
onClick: () => this.toggleHide(msg.id)
|
||||
};
|
||||
},
|
||||
|
||||
async start() {
|
||||
style = document.createElement("style");
|
||||
|
|
@ -47,26 +65,11 @@ export default definePlugin({
|
|||
|
||||
await getHiddenMessages();
|
||||
await this.buildCss();
|
||||
|
||||
addButton("HideAttachments", msg => {
|
||||
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length) return null;
|
||||
|
||||
const isHidden = hiddenMessages.has(msg.id);
|
||||
|
||||
return {
|
||||
label: isHidden ? "Show Attachments" : "Hide Attachments",
|
||||
icon: isHidden ? ImageVisible : ImageInvisible,
|
||||
message: msg,
|
||||
channel: ChannelStore.getChannel(msg.channel_id),
|
||||
onClick: () => this.toggleHide(msg.id)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
style.remove();
|
||||
hiddenMessages.clear();
|
||||
removeButton("HideAttachments");
|
||||
},
|
||||
|
||||
async buildCss() {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
|
|
@ -62,7 +61,7 @@ const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(ac
|
|||
|
||||
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
||||
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)");
|
||||
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) {
|
||||
e.stopPropagation();
|
||||
|
||||
const ignoredActivityIndex = getIgnoredActivities().findIndex(act => act.id === activity.id);
|
||||
if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity);
|
||||
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
|
||||
|
||||
recalculateActivities();
|
||||
const ignoredActivityIndex = settings.store.ignoredActivities.findIndex(act => act.id === activity.id);
|
||||
if (ignoredActivityIndex === -1) settings.store.ignoredActivities.push(activity);
|
||||
else settings.store.ignoredActivities.splice(ignoredActivityIndex, 1);
|
||||
}
|
||||
|
||||
function recalculateActivities() {
|
||||
|
|
@ -172,7 +169,6 @@ const settings = definePluginSettings({
|
|||
idsList: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "",
|
||||
default: "",
|
||||
onChange(newValue: string) {
|
||||
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
|
||||
settings.store.idsList = Array.from(ids).join(", ");
|
||||
|
|
@ -209,14 +205,14 @@ const settings = definePluginSettings({
|
|||
description: "Ignore all competing activities (These are normally special game activities)",
|
||||
default: false,
|
||||
onChange: recalculateActivities
|
||||
},
|
||||
ignoredActivities: {
|
||||
type: OptionType.CUSTOM,
|
||||
default: [] as IgnoredActivity[],
|
||||
onChange: recalculateActivities,
|
||||
description: "",
|
||||
}
|
||||
}).withPrivateSettings<{
|
||||
ignoredActivities: IgnoredActivity[];
|
||||
}>();
|
||||
|
||||
function getIgnoredActivities() {
|
||||
return settings.store.ignoredActivities ??= [];
|
||||
}
|
||||
});
|
||||
|
||||
function isActivityTypeIgnored(type: number, id?: string) {
|
||||
if (id && settings.store.idsList.includes(id)) {
|
||||
|
|
@ -284,29 +280,14 @@ export default definePlugin({
|
|||
],
|
||||
|
||||
async start() {
|
||||
// Migrate allowedIds
|
||||
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) {
|
||||
if (settings.store.ignoredActivities.length !== 0) {
|
||||
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 (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
||||
getIgnoredActivities().splice(index, 1);
|
||||
settings.store.ignoredActivities.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -316,11 +297,11 @@ export default definePlugin({
|
|||
if (isActivityTypeIgnored(props.type, props.application_id)) return false;
|
||||
|
||||
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 {
|
||||
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.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"]);
|
||||
|
||||
children.push(
|
||||
|
|
|
|||
|
|
@ -16,12 +16,19 @@
|
|||
* 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 { 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 { 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 { FluxEvents } from "@webpack/types";
|
||||
|
||||
|
|
@ -83,6 +90,13 @@ function isReporterTestable(p: Plugin, part: ReporterTestable) {
|
|||
: (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
|
||||
//
|
||||
// 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;
|
||||
});
|
||||
|
||||
if (p.commands?.length) {
|
||||
Plugins.CommandsAPI.isDependency = true;
|
||||
settings.CommandsAPI.enabled = true;
|
||||
if (p.commands?.length) neededApiPlugins.add("CommandsAPI");
|
||||
if (p.onBeforeMessageEdit || p.onBeforeMessageSend || p.onMessageClick) neededApiPlugins.add("MessageEventsAPI");
|
||||
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) {
|
||||
if (p.settings) {
|
||||
p.settings.pluginName = p.name;
|
||||
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];
|
||||
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 (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) {
|
||||
for (const patch of p.patches) {
|
||||
|
|
@ -216,7 +254,11 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc
|
|||
}
|
||||
|
||||
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) {
|
||||
logger.info("Starting plugin", name);
|
||||
|
|
@ -250,7 +292,6 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
|||
subscribePluginFluxEvents(p, FluxDispatcher);
|
||||
}
|
||||
|
||||
|
||||
if (contextMenus) {
|
||||
logger.debug("Adding context menus patches of plugin", name);
|
||||
for (const navId in contextMenus) {
|
||||
|
|
@ -258,11 +299,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;
|
||||
}, p => `startPlugin ${p.name}`);
|
||||
|
||||
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) {
|
||||
logger.info("Stopping plugin", name);
|
||||
|
|
@ -301,5 +358,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;
|
||||
}, p => `stopPlugin ${p.name}`);
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { updateMessage } from "@api/MessageUpdater";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
|
|
@ -54,7 +53,7 @@ function Indicator() {
|
|||
aria-label="Hidden Message Indicator (InvisibleChat)"
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
src="https://github.com/SammCheese/invisible-chat/blob/1c1a1111d5aed4ddd04bb76a8f10d138944e1d5a/src/assets/lock.png"
|
||||
src="https://github.com/SammCheese/invisible-chat/raw/NewReplugged/src/assets/lock.png"
|
||||
width={20}
|
||||
height={20}
|
||||
style={{ transform: "translateY(4p)", paddingInline: 4 }}
|
||||
|
|
@ -66,17 +65,12 @@ function Indicator() {
|
|||
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
savedPasswords: {
|
||||
type: OptionType.STRING,
|
||||
default: "password, Password",
|
||||
description: "Saved Passwords (Seperated with a , )"
|
||||
}
|
||||
});
|
||||
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
if (!isMainChat) return null;
|
||||
|
||||
const generateChatButton: ChatBarButton = () => {
|
||||
return (
|
||||
<ChatBarButton tooltip="Encrypt Message"
|
||||
<ChatBarButton
|
||||
tooltip="Encrypt Message"
|
||||
onClick={() => buildEncModal()}
|
||||
|
||||
buttonProps={{
|
||||
|
|
@ -97,11 +91,19 @@ const generateChatButton: ChatBarButton = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const settings = definePluginSettings({
|
||||
savedPasswords: {
|
||||
type: OptionType.STRING,
|
||||
default: "password, Password",
|
||||
description: "Saved Passwords (Seperated with a , )"
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "InvisibleChat",
|
||||
description: "Encrypt your Messages in a non-suspicious way!",
|
||||
authors: [Devs.SammCheese],
|
||||
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI", "MessageUpdaterAPI"],
|
||||
dependencies: ["MessageUpdaterAPI"],
|
||||
reporterTestable: ReporterTestable.Patches,
|
||||
settings,
|
||||
|
||||
|
|
@ -124,32 +126,28 @@ export default definePlugin({
|
|||
async start() {
|
||||
const { default: StegCloak } = await getStegCloak();
|
||||
steggo = new StegCloak(true, false);
|
||||
},
|
||||
|
||||
addButton("invDecrypt", message => {
|
||||
return this.INV_REGEX.test(message?.content)
|
||||
? {
|
||||
label: "Decrypt Message",
|
||||
icon: this.popOverIcon,
|
||||
message: message,
|
||||
channel: ChannelStore.getChannel(message.channel_id),
|
||||
onClick: async () => {
|
||||
const res = await iteratePasswords(message);
|
||||
renderMessagePopoverButton(message) {
|
||||
return this.INV_REGEX.test(message?.content)
|
||||
? {
|
||||
label: "Decrypt Message",
|
||||
icon: this.popOverIcon,
|
||||
message: message,
|
||||
channel: ChannelStore.getChannel(message.channel_id),
|
||||
onClick: async () => {
|
||||
const res = await iteratePasswords(message);
|
||||
|
||||
if (res)
|
||||
this.buildEmbed(message, res);
|
||||
else
|
||||
buildDecModal({ message });
|
||||
}
|
||||
if (res)
|
||||
this.buildEmbed(message, res);
|
||||
else
|
||||
buildDecModal({ message });
|
||||
}
|
||||
: null;
|
||||
});
|
||||
addChatBarButton("invButton", generateChatButton);
|
||||
}
|
||||
: null;
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeButton("invDecrypt");
|
||||
removeChatBarButton("invButton");
|
||||
},
|
||||
renderChatBarButton: ChatBarIcon,
|
||||
|
||||
// Gets the Embed of a Link
|
||||
async getEmbed(url: URL): Promise<Object | {}> {
|
||||
|
|
@ -184,7 +182,6 @@ export default definePlugin({
|
|||
updateMessage(message.channel_id, message.id, { embeds: message.embeds });
|
||||
},
|
||||
|
||||
chatBarIcon: ErrorBoundary.wrap(generateChatButton, { noop: true }),
|
||||
popOverIcon: () => <PopOverIcon />,
|
||||
indicator: ErrorBoundary.wrap(Indicator, { noop: true })
|
||||
});
|
||||
|
|
|
|||
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
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
|
@ -17,39 +17,22 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { hash as h64 } from "@intrnl/xxhash64";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
// Compute a 64-bit FNV-1a hash of the passed data
|
||||
function hash(data: ArrayBuffer) {
|
||||
const fnvPrime = 1099511628211n;
|
||||
const offsetBasis = 14695981039346656037n;
|
||||
|
||||
let result = offsetBasis;
|
||||
for (const byte of new Uint8Array(data)) {
|
||||
result ^= BigInt(byte);
|
||||
result = (result * fnvPrime) % 2n ** 32n;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
import { useMemo } from "@webpack/common";
|
||||
|
||||
// Calculate a CSS color string based on the user ID
|
||||
function calculateNameColorForUser(id: bigint) {
|
||||
const idBuffer = new ArrayBuffer(16);
|
||||
{
|
||||
const idView = new DataView(idBuffer);
|
||||
idView.setBigUint64(0, id);
|
||||
}
|
||||
const idHash = hash(idBuffer);
|
||||
function calculateNameColorForUser(id: string) {
|
||||
const { lightness } = settings.use(["lightness"]);
|
||||
const idHash = useMemo(() => h64(id), [id]);
|
||||
|
||||
return `hsl(${idHash % 360n}, 100%, ${settings.store.lightness}%)`;
|
||||
return `hsl(${idHash % 360n}, 100%, ${lightness}%)`;
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
lightness: {
|
||||
description: "Lightness, in %. Change if the colors are too light or too dark.",
|
||||
restartNeeded: true,
|
||||
description: "Lightness, in %. Change if the colors are too light or too dark",
|
||||
type: OptionType.NUMBER,
|
||||
default: 70,
|
||||
},
|
||||
|
|
@ -57,36 +40,46 @@ const settings = definePluginSettings({
|
|||
description: "Replace role colors in the member list",
|
||||
restartNeeded: true,
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
},
|
||||
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\"",
|
||||
find: '="SYSTEM_TAG"',
|
||||
replacement: {
|
||||
match: /(?<=className:\i\.username,style:.{0,50}:void 0,)/,
|
||||
replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},",
|
||||
},
|
||||
replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".NameWithRole,{roleName:",
|
||||
find: "#{intl::GUILD_OWNER}),children:",
|
||||
replacement: {
|
||||
match: /(?<=color:)null!=.{0,50}?(?=,)/,
|
||||
replace: "$self.calculateNameColorForListContext(arguments[0])",
|
||||
match: /(?<=\.MEMBER_LIST}\),\[\]\),)(.+?color:)null!=.{0,50}?(?=,)/,
|
||||
replace: (_, rest) => `ircColor=$self.calculateNameColorForListContext(arguments[0]),${rest}ircColor`
|
||||
},
|
||||
predicate: () => settings.store.memberListColors,
|
||||
},
|
||||
predicate: () => settings.store.memberListColors
|
||||
}
|
||||
],
|
||||
settings,
|
||||
|
||||
calculateNameColorForMessageContext(context: any) {
|
||||
return calculateNameColorForUser(BigInt(context.message.author.id));
|
||||
const id = context?.message?.author?.id;
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
return calculateNameColorForUser(id);
|
||||
},
|
||||
calculateNameColorForListContext(context: any) {
|
||||
return calculateNameColorForUser(BigInt(context.user.id));
|
||||
},
|
||||
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 presenceStore = findByPropsLazy("getLocalPresence");
|
||||
const PresenceStore = findByPropsLazy("getLocalPresence");
|
||||
|
||||
async function getApplicationAsset(key: string): Promise<string> {
|
||||
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
||||
|
|
@ -124,6 +124,11 @@ const settings = definePluginSettings({
|
|||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
},
|
||||
hideWithActivity: {
|
||||
description: "Hide Last.fm presence if you have any other presence",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
},
|
||||
statusName: {
|
||||
description: "custom status text",
|
||||
type: OptionType.STRING,
|
||||
|
|
@ -274,12 +279,16 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
async getActivity(): Promise<Activity | null> {
|
||||
if (settings.store.hideWithActivity) {
|
||||
if (PresenceStore.getActivities().some(a => a.application_id !== applicationId)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.store.hideWithSpotify) {
|
||||
for (const activity of presenceStore.getActivities()) {
|
||||
if (activity.type === ActivityType.LISTENING && activity.application_id !== applicationId) {
|
||||
// there is already music status because of Spotify or richerCider (probably more)
|
||||
return null;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addClickListener, removeClickListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -57,66 +56,64 @@ export default definePlugin({
|
|||
name: "MessageClickActions",
|
||||
description: "Hold Backspace and click to delete, double click to edit/reply",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
start() {
|
||||
document.addEventListener("keydown", keydown);
|
||||
document.addEventListener("keyup", keyup);
|
||||
|
||||
this.onClick = addClickListener((msg: any, channel, event) => {
|
||||
const isMe = msg.author.id === UserStore.getCurrentUser().id;
|
||||
if (!isDeletePressed) {
|
||||
if (event.detail < 2) return;
|
||||
if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return;
|
||||
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return;
|
||||
if (msg.deleted === true) return;
|
||||
|
||||
if (isMe) {
|
||||
if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return;
|
||||
|
||||
MessageActions.startEditMessage(channel.id, msg.id, msg.content);
|
||||
event.preventDefault();
|
||||
} else {
|
||||
if (!settings.store.enableDoubleClickToReply) return;
|
||||
|
||||
const EPHEMERAL = 64;
|
||||
if (msg.hasFlag(EPHEMERAL)) return;
|
||||
|
||||
const isShiftPress = event.shiftKey && !settings.store.requireModifier;
|
||||
const NoReplyMention = Vencord.Plugins.plugins.NoReplyMention as any as typeof import("../noReplyMention").default;
|
||||
const shouldMention = Vencord.Plugins.isPluginEnabled("NoReplyMention")
|
||||
? NoReplyMention.shouldMention(msg, isShiftPress)
|
||||
: !isShiftPress;
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
type: "CREATE_PENDING_REPLY",
|
||||
channel,
|
||||
message: msg,
|
||||
shouldMention,
|
||||
showMentionToggle: channel.guild_id !== null
|
||||
});
|
||||
}
|
||||
} else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel))) {
|
||||
if (msg.deleted) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "MESSAGE_DELETE",
|
||||
channelId: channel.id,
|
||||
id: msg.id,
|
||||
mlDeleted: true
|
||||
});
|
||||
} else {
|
||||
MessageActions.deleteMessage(channel.id, msg.id);
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeClickListener(this.onClick);
|
||||
document.removeEventListener("keydown", keydown);
|
||||
document.removeEventListener("keyup", keyup);
|
||||
}
|
||||
},
|
||||
|
||||
onMessageClick(msg: any, channel, event) {
|
||||
const isMe = msg.author.id === UserStore.getCurrentUser().id;
|
||||
if (!isDeletePressed) {
|
||||
if (event.detail < 2) return;
|
||||
if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return;
|
||||
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return;
|
||||
if (msg.deleted === true) return;
|
||||
|
||||
if (isMe) {
|
||||
if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return;
|
||||
|
||||
MessageActions.startEditMessage(channel.id, msg.id, msg.content);
|
||||
event.preventDefault();
|
||||
} else {
|
||||
if (!settings.store.enableDoubleClickToReply) return;
|
||||
|
||||
const EPHEMERAL = 64;
|
||||
if (msg.hasFlag(EPHEMERAL)) return;
|
||||
|
||||
const isShiftPress = event.shiftKey && !settings.store.requireModifier;
|
||||
const NoReplyMention = Vencord.Plugins.plugins.NoReplyMention as any as typeof import("../noReplyMention").default;
|
||||
const shouldMention = Vencord.Plugins.isPluginEnabled("NoReplyMention")
|
||||
? NoReplyMention.shouldMention(msg, isShiftPress)
|
||||
: !isShiftPress;
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
type: "CREATE_PENDING_REPLY",
|
||||
channel,
|
||||
message: msg,
|
||||
shouldMention,
|
||||
showMentionToggle: channel.guild_id !== null
|
||||
});
|
||||
}
|
||||
} else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel))) {
|
||||
if (msg.deleted) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "MESSAGE_DELETE",
|
||||
channelId: channel.id,
|
||||
id: msg.id,
|
||||
mlDeleted: true
|
||||
});
|
||||
} else {
|
||||
MessageActions.deleteMessage(channel.id, msg.id);
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ export default definePlugin({
|
|||
</>
|
||||
}
|
||||
</Tooltip>;
|
||||
});
|
||||
}, { noop: true });
|
||||
},
|
||||
|
||||
Icon({ delta, fill, props }: {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* 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 { definePluginSettings } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
|
|
@ -373,7 +373,7 @@ export default definePlugin({
|
|||
settings,
|
||||
|
||||
start() {
|
||||
addAccessory("messageLinkEmbed", props => {
|
||||
addMessageAccessory("messageLinkEmbed", props => {
|
||||
if (!messageLinkRegex.test(props.message.content))
|
||||
return null;
|
||||
|
||||
|
|
@ -390,6 +390,6 @@ export default definePlugin({
|
|||
}, 4 /* just above rich embeds */);
|
||||
},
|
||||
stop() {
|
||||
removeAccessory("messageLinkEmbed");
|
||||
},
|
||||
removeMessageAccessory("messageLinkEmbed");
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -215,9 +215,9 @@ export default definePlugin({
|
|||
},
|
||||
collapseDeleted: {
|
||||
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,
|
||||
restartNeeded: true,
|
||||
},
|
||||
logEdits: {
|
||||
type: OptionType.BOOLEAN,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands";
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
|
|
@ -33,23 +33,23 @@ const author = {
|
|||
interface Tag {
|
||||
name: string;
|
||||
message: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const getTags = () => DataStore.get(DATA_KEY).then<Tag[]>(t => t ?? []);
|
||||
const getTag = (name: string) => DataStore.get(DATA_KEY).then<Tag | null>((t: Tag[]) => (t ?? []).find((tt: Tag) => tt.name === name) ?? null);
|
||||
const addTag = async (tag: Tag) => {
|
||||
const tags = await getTags();
|
||||
tags.push(tag);
|
||||
DataStore.set(DATA_KEY, tags);
|
||||
return tags;
|
||||
};
|
||||
const removeTag = async (name: string) => {
|
||||
let tags = await getTags();
|
||||
tags = await tags.filter((t: Tag) => t.name !== name);
|
||||
DataStore.set(DATA_KEY, tags);
|
||||
return tags;
|
||||
};
|
||||
function getTags() {
|
||||
return settings.store.tagsList;
|
||||
}
|
||||
|
||||
function getTag(name: string) {
|
||||
return settings.store.tagsList[name] ?? null;
|
||||
}
|
||||
|
||||
function addTag(tag: Tag) {
|
||||
settings.store.tagsList[tag.name] = tag;
|
||||
}
|
||||
|
||||
function removeTag(name: string) {
|
||||
delete settings.store.tagsList[name];
|
||||
}
|
||||
|
||||
function createTagCommand(tag: Tag) {
|
||||
registerCommand({
|
||||
|
|
@ -57,7 +57,7 @@ function createTagCommand(tag: Tag) {
|
|||
description: tag.name,
|
||||
inputType: ApplicationCommandInputType.BUILT_IN_TEXT,
|
||||
execute: async (_, ctx) => {
|
||||
if (!await getTag(tag.name)) {
|
||||
if (!getTag(tag.name)) {
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)`
|
||||
|
|
@ -65,8 +65,7 @@ function createTagCommand(tag: Tag) {
|
|||
return { content: `/${tag.name}` };
|
||||
}
|
||||
|
||||
if (Settings.plugins.MessageTags.clyde) sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
if (settings.store.clyde) sendBotMessage(ctx.channel.id, {
|
||||
content: `${EMOTE} The tag **${tag.name}** has been sent!`
|
||||
});
|
||||
return { content: tag.message.replaceAll("\\n", "\n") };
|
||||
|
|
@ -75,22 +74,38 @@ function createTagCommand(tag: Tag) {
|
|||
}, "CustomTags");
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
clyde: {
|
||||
description: "If enabled, clyde will send you an ephemeral message when a tag was used.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true
|
||||
},
|
||||
tagsList: {
|
||||
type: OptionType.CUSTOM,
|
||||
default: {} as Record<string, Tag>,
|
||||
description: "",
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "MessageTags",
|
||||
description: "Allows you to save messages and to use them with a simple command.",
|
||||
authors: [Devs.Luna],
|
||||
options: {
|
||||
clyde: {
|
||||
name: "Clyde message on send",
|
||||
description: "If enabled, clyde will send you an ephemeral message when a tag was used.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
settings,
|
||||
|
||||
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: [
|
||||
|
|
@ -159,7 +174,7 @@ export default definePlugin({
|
|||
const name: string = findOption(args[0].options, "tag-name", "");
|
||||
const message: string = findOption(args[0].options, "message", "");
|
||||
|
||||
if (await getTag(name))
|
||||
if (getTag(name))
|
||||
return sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
content: `${EMOTE} A Tag with the name **${name}** already exists!`
|
||||
|
|
@ -167,12 +182,11 @@ export default definePlugin({
|
|||
|
||||
const tag = {
|
||||
name: name,
|
||||
enabled: true,
|
||||
message: message
|
||||
};
|
||||
|
||||
createTagCommand(tag);
|
||||
await addTag(tag);
|
||||
addTag(tag);
|
||||
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
|
|
@ -183,14 +197,14 @@ export default definePlugin({
|
|||
case "delete": {
|
||||
const name: string = findOption(args[0].options, "tag-name", "");
|
||||
|
||||
if (!await getTag(name))
|
||||
if (!getTag(name))
|
||||
return sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
content: `${EMOTE} A Tag with the name **${name}** does not exist!`
|
||||
});
|
||||
|
||||
unregisterCommand(name);
|
||||
await removeTag(name);
|
||||
removeTag(name);
|
||||
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
|
|
@ -203,10 +217,8 @@ export default definePlugin({
|
|||
author,
|
||||
embeds: [
|
||||
{
|
||||
// @ts-ignore
|
||||
title: "All Tags:",
|
||||
// @ts-ignore
|
||||
description: (await getTags())
|
||||
description: Object.values(getTags())
|
||||
.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!`,
|
||||
// @ts-ignore
|
||||
|
|
@ -219,7 +231,7 @@ export default definePlugin({
|
|||
}
|
||||
case "preview": {
|
||||
const name: string = findOption(args[0].options, "tag-name", "");
|
||||
const tag = await getTag(name);
|
||||
const tag = getTag(name);
|
||||
|
||||
if (!tag)
|
||||
return sendBotMessage(ctx.channel.id, {
|
||||
|
|
|
|||
|
|
@ -1,454 +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 "./style.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Devs, EquicordDevs } 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, GuildMemberStore, 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;
|
||||
|
||||
// From https://gist.github.com/StevenBlack/960189
|
||||
export function getContrastYIQ(hexColor: string | undefined): "#000" | "#fff" | undefined {
|
||||
if (!hexColor) return;
|
||||
const r = parseInt(hexColor.substring(1, 3), 16);
|
||||
const g = parseInt(hexColor.substring(3, 5), 16);
|
||||
const b = parseInt(hexColor.substring(5, 7), 16);
|
||||
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
return yiq >= 128 ? "#000" : "#fff";
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
useRoleColors: {
|
||||
description: "Use the user's role color instead of the default color",
|
||||
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, EquicordDevs.OIRNOIR],
|
||||
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: [
|
||||
// Grab the colors from the params using the custom params
|
||||
{
|
||||
match: /type:\i=\i\.\i\.BOT/,
|
||||
replace: "$&,moreTags_bgColor:moreTags_bgColor,moreTags_fgColor:moreTags_fgColor"
|
||||
},
|
||||
// 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)
|
||||
// also set the colors
|
||||
{
|
||||
match: /.botText,children:(\i)}\)]/,
|
||||
replace: "$&,'data-tag':$1.toLowerCase(),style:{'background-color':moreTags_bgColor,'color':moreTags_fgColor},'data-moreTags-darkFg':moreTags_fgColor?.includes('0')"
|
||||
}
|
||||
],
|
||||
},
|
||||
// in messages
|
||||
{
|
||||
find: ".Types.ORIGINAL_POSTER",
|
||||
replacement: [
|
||||
// Pass the tag's colors and type to the next function
|
||||
{
|
||||
match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)\?null:\(0,(\i)\.jsx\)\((\i).(\i),{/,
|
||||
replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null?null:(0,$3.jsx)($4.$5,{...$self.getTagColors({...arguments[0],tagType:$2,location:'chat'}),"
|
||||
}
|
||||
]
|
||||
},
|
||||
// in the member list
|
||||
{
|
||||
find: "#{intl::GUILD_OWNER}),children:",
|
||||
replacement: [
|
||||
// Pass the tag's colors and type to the next function
|
||||
{
|
||||
match: /(?<type>\i)=\(null==.{0,100}\.BOT;return null!=(?<user>\i)&&\i\.bot\?\(0,(?<jsxf>\i)\.jsx\)\((?<module>\i)\.(?<subm>\i),{/,
|
||||
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }); return typeof $<type> === 'number'?(0,$<jsxf>.jsx)($<module>.$<subm>,{...$self.getTagColors({...arguments[0],tagType:$<type>,location:'not-chat'}),"
|
||||
}
|
||||
]
|
||||
},
|
||||
// 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,"
|
||||
},
|
||||
{
|
||||
// Get the tag type and tag colors so that they can be distributed
|
||||
match: /(,\i=\i\.isPomelo\(\)\|\|\i;)(.+?botType:(\i),botVerified:(\i),(?!discriminatorClass:)(?<=user:(\i).+?))/,
|
||||
replace: "$1let moreTags_tagType=$self.getTag({user:$5,channelId:moreTags_channelId,origType:$3,location:'not-chat'});let moreTags_tagColors=$self.getTagColors({user:$5,channelId:moreTags_channelId,tagType:moreTags_tagType,location:'not-chat'});$2"
|
||||
},
|
||||
{
|
||||
match: /,botType:(\i),botVerified:(\i),(?!discriminatorClass:)(?<=user:(\i).+?)/g,
|
||||
replace: ",botType:moreTags_tagType,...moreTags_tagColors,"
|
||||
}, {
|
||||
// Get the parameters from the function that were passed in previously
|
||||
// because Discord needs to make it hard on us
|
||||
match: /,botClass:\i,showStreamerModeTooltip:\i/,
|
||||
replace: "$&,moreTags_bgColor:moreTags_bgColor,moreTags_fgColor:moreTags_fgColor"
|
||||
}, {
|
||||
// Finally pass the color information into the renderer
|
||||
match: /\.jsx\)\(\i\.\i,{type:\i,/,
|
||||
replace: "$&moreTags_bgColor,moreTags_fgColor,"
|
||||
}
|
||||
]
|
||||
},
|
||||
// Colors need to be passed down through this random module.
|
||||
// If there's a way to bypass this module without modifying discord's random variables (We don't want to break stuff) leave a PR (or comment if an existing one is open)
|
||||
{
|
||||
find: ",invertBotTagColor:",
|
||||
replacement: [
|
||||
{
|
||||
match: /,invertBotTagColor:\i/,
|
||||
replace: "$&,moreTags_bgColor:moreTags_bgColor,moreTags_fgColor:moreTags_fgColor"
|
||||
}, {
|
||||
match: /verified:\i,useRemSizes:\i/,
|
||||
replace: "$&,moreTags_bgColor,moreTags_fgColor"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
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;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
getTagColors({
|
||||
user, channelId, channel, location, tagType
|
||||
}: {
|
||||
user: User,
|
||||
channel?: Channel,
|
||||
channelId?: string;
|
||||
location: "chat" | "not-chat";
|
||||
tagType: number;
|
||||
}): {
|
||||
moreTags_bgColor: string,
|
||||
moreTags_fgColor: string;
|
||||
} {
|
||||
const passedTagName = Object.keys(Tag.Types).find(k => Tag.Types[k] === tagType);
|
||||
const [tagName, variant] = passedTagName?.split("-") ?? [null, null];
|
||||
if (!settings.store.useRoleColors || !tagType || !tagName || (location === "chat" && !settings.store.tagSettings[tagName]?.showInChat) || (location === "not-chat" && !settings.store.tagSettings[tagName]?.showInNotChat) || (user.bot && settings.store.dontShowForBots)) {
|
||||
return { moreTags_bgColor: "", moreTags_fgColor: "" };
|
||||
}
|
||||
if (!channel && channelId) channel = ChannelStore.getChannel(channelId);
|
||||
const member = channel?.guild_id != null ? GuildMemberStore.getMember(channel?.guild_id, user.id) : null;
|
||||
const colorString = member?.colorString;
|
||||
const fgColorString = colorString != null ? getContrastYIQ(colorString) : null;
|
||||
return { moreTags_bgColor: colorString ?? "", moreTags_fgColor: fgColorString ?? "" };
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
|
@ -7,11 +7,10 @@
|
|||
import { classNameFactory } from "@api/Styles";
|
||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal";
|
||||
import { extractAndLoadChunksLazy, findComponentByCodeLazy, findExportedComponentLazy } 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 { categories, Category, createCategory, getCategory, updateCategory } from "../data";
|
||||
import { forceUpdate } from "../index";
|
||||
import { categoryLen, createCategory, getCategory } from "../data";
|
||||
|
||||
interface ColorPickerProps {
|
||||
color: number | null;
|
||||
|
|
@ -39,45 +38,45 @@ const cl = classNameFactory("vc-pindms-modal-");
|
|||
|
||||
interface Props {
|
||||
categoryId: string | null;
|
||||
initalChannelId: string | null;
|
||||
initialChannelId: string | null;
|
||||
modalProps: ModalProps;
|
||||
}
|
||||
|
||||
function useCategory(categoryId: string | null, initalChannelId: string | null) {
|
||||
const [category, setCategory] = useState<Category | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (categoryId)
|
||||
setCategory(getCategory(categoryId)!);
|
||||
else if (initalChannelId)
|
||||
setCategory({
|
||||
const category = useMemo(() => {
|
||||
if (categoryId) {
|
||||
return getCategory(categoryId);
|
||||
} else if (initalChannelId) {
|
||||
return {
|
||||
id: Toasts.genId(),
|
||||
name: `Pin Category ${categories.length + 1}`,
|
||||
name: `Pin Category ${categoryLen() + 1}`,
|
||||
color: DEFAULT_COLOR,
|
||||
collapsed: false,
|
||||
channels: [initalChannelId]
|
||||
});
|
||||
};
|
||||
}
|
||||
}, [categoryId, initalChannelId]);
|
||||
|
||||
return {
|
||||
category,
|
||||
setCategory
|
||||
};
|
||||
return category;
|
||||
}
|
||||
|
||||
export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Props) {
|
||||
const { category, setCategory } = useCategory(categoryId, initalChannelId);
|
||||
|
||||
export function NewCategoryModal({ categoryId, modalProps, initialChannelId }: Props) {
|
||||
const category = useCategory(categoryId, initialChannelId);
|
||||
if (!category) return null;
|
||||
|
||||
const onSave = async (e: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
if (!categoryId)
|
||||
await createCategory(category);
|
||||
else
|
||||
await updateCategory(category);
|
||||
const [name, setName] = useState(category.name);
|
||||
const [color, setColor] = useState(category.color);
|
||||
|
||||
const onSave = (e: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
|
||||
category.name = name;
|
||||
category.color = color;
|
||||
|
||||
if (!categoryId) {
|
||||
createCategory(category);
|
||||
}
|
||||
|
||||
forceUpdate();
|
||||
modalProps.onClose();
|
||||
};
|
||||
|
||||
|
|
@ -93,25 +92,25 @@ export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Pr
|
|||
<Forms.FormSection>
|
||||
<Forms.FormTitle>Name</Forms.FormTitle>
|
||||
<TextInput
|
||||
value={category.name}
|
||||
onChange={e => setCategory({ ...category, name: e })}
|
||||
value={name}
|
||||
onChange={e => setName(e)}
|
||||
/>
|
||||
</Forms.FormSection>
|
||||
<Forms.FormDivider />
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle>Color</Forms.FormTitle>
|
||||
<ColorPickerWithSwatches
|
||||
key={category.name}
|
||||
key={category.id}
|
||||
defaultColor={DEFAULT_COLOR}
|
||||
colors={SWATCHES}
|
||||
onChange={c => setCategory({ ...category, color: c! })}
|
||||
value={category.color}
|
||||
onChange={c => setColor(c!)}
|
||||
value={color}
|
||||
renderDefaultButton={() => null}
|
||||
renderCustomButton={() => (
|
||||
<ColorPicker
|
||||
color={category.color}
|
||||
onChange={c => setCategory({ ...category, color: c! })}
|
||||
key={category.name}
|
||||
color={color}
|
||||
onChange={c => setColor(c!)}
|
||||
key={category.id}
|
||||
showEyeDropper={false}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -119,7 +118,7 @@ export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Pr
|
|||
</Forms.FormSection>
|
||||
</ModalContent>
|
||||
<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>
|
||||
</form>
|
||||
</ModalRoot>
|
||||
|
|
@ -129,5 +128,5 @@ export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Pr
|
|||
export const openCategoryModal = (categoryId: string | null, channelId: string | null) =>
|
||||
openModalLazy(async () => {
|
||||
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 { Menu } from "@webpack/common";
|
||||
|
||||
import { addChannelToCategory, canMoveChannelInDirection, categories, isPinned, moveChannel, removeChannelFromCategory } from "../data";
|
||||
import { forceUpdate, PinOrder, settings } from "../index";
|
||||
import { addChannelToCategory, canMoveChannelInDirection, currentUserCategories, isPinned, moveChannel, removeChannelFromCategory } from "../data";
|
||||
import { PinOrder, settings } from "../index";
|
||||
import { openCategoryModal } from "./CreateCategoryModal";
|
||||
|
||||
function createPinMenuItem(channelId: string) {
|
||||
|
|
@ -31,12 +31,12 @@ function createPinMenuItem(channelId: string) {
|
|||
<Menu.MenuSeparator />
|
||||
|
||||
{
|
||||
categories.map(category => (
|
||||
currentUserCategories.map(category => (
|
||||
<Menu.MenuItem
|
||||
key={category.id}
|
||||
id={`pin-category-${category.id}`}
|
||||
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"
|
||||
label="Unpin DM"
|
||||
color="danger"
|
||||
action={() => removeChannelFromCategory(channelId).then(forceUpdate)}
|
||||
action={() => removeChannelFromCategory(channelId)}
|
||||
/>
|
||||
|
||||
{
|
||||
|
|
@ -57,7 +57,7 @@ function createPinMenuItem(channelId: string) {
|
|||
<Menu.MenuItem
|
||||
id="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
|
||||
id="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 { Settings } from "@api/Settings";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
||||
import { DEFAULT_COLOR } from "./constants";
|
||||
import { forceUpdate, PinOrder, PrivateChannelSortStore, settings } from "./index";
|
||||
import { PinOrder, PrivateChannelSortStore, settings } from "./index";
|
||||
|
||||
export interface Category {
|
||||
id: string;
|
||||
|
|
@ -24,104 +24,92 @@ const CATEGORY_MIGRATED_PINDMS_KEY = "PinDMsMigratedPinDMs";
|
|||
const CATEGORY_MIGRATED_KEY = "PinDMsMigratedOldCategories";
|
||||
const OLD_CATEGORY_KEY = "BetterPinDMsCategories-";
|
||||
|
||||
|
||||
export let categories: Category[] = [];
|
||||
|
||||
export async function saveCats(cats: Category[]) {
|
||||
const { id } = UserStore.getCurrentUser();
|
||||
await DataStore.set(CATEGORY_BASE_KEY + id, cats);
|
||||
}
|
||||
let forceUpdateDms: (() => void) | undefined = undefined;
|
||||
export let currentUserCategories: Category[] = [];
|
||||
|
||||
export async function init() {
|
||||
const id = UserStore.getCurrentUser()?.id;
|
||||
await initCategories(id);
|
||||
await migrateData(id);
|
||||
forceUpdate();
|
||||
await migrateData();
|
||||
|
||||
const userId = UserStore.getCurrentUser()?.id;
|
||||
if (userId == null) return;
|
||||
|
||||
currentUserCategories = settings.store.userBasedCategoryList[userId] ??= [];
|
||||
forceUpdateDms?.();
|
||||
}
|
||||
|
||||
export async function initCategories(userId: string) {
|
||||
categories = await DataStore.get<Category[]>(CATEGORY_BASE_KEY + userId) ?? [];
|
||||
export function usePinnedDms() {
|
||||
forceUpdateDms = useForceUpdater();
|
||||
settings.use(["pinOrder", "canCollapseDmSection", "dmSectionCollapsed", "userBasedCategoryList"]);
|
||||
}
|
||||
|
||||
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) {
|
||||
categories.push(category);
|
||||
await saveCats(categories);
|
||||
export function getCategoryByIndex(index: number) {
|
||||
return currentUserCategories[index];
|
||||
}
|
||||
|
||||
export async function updateCategory(category: Category) {
|
||||
const index = categories.findIndex(c => c.id === category.id);
|
||||
if (index === -1) return;
|
||||
|
||||
categories[index] = category;
|
||||
await saveCats(categories);
|
||||
export function createCategory(category: Category) {
|
||||
currentUserCategories.push(category);
|
||||
}
|
||||
|
||||
export async function addChannelToCategory(channelId: string, categoryId: string) {
|
||||
const category = categories.find(c => c.id === categoryId);
|
||||
if (!category) return;
|
||||
export function addChannelToCategory(channelId: string, categoryId: string) {
|
||||
const category = currentUserCategories.find(c => c.id === categoryId);
|
||||
if (category == null) return;
|
||||
|
||||
if (category.channels.includes(channelId)) return;
|
||||
|
||||
category.channels.push(channelId);
|
||||
await saveCats(categories);
|
||||
|
||||
}
|
||||
|
||||
export async function removeChannelFromCategory(channelId: string) {
|
||||
const category = categories.find(c => c.channels.includes(channelId));
|
||||
if (!category) return;
|
||||
export function removeChannelFromCategory(channelId: string) {
|
||||
const category = currentUserCategories.find(c => c.channels.includes(channelId));
|
||||
if (category == null) return;
|
||||
|
||||
category.channels = category.channels.filter(c => c !== channelId);
|
||||
await saveCats(categories);
|
||||
}
|
||||
|
||||
export async function removeCategory(categoryId: string) {
|
||||
const catagory = categories.find(c => c.id === categoryId);
|
||||
if (!catagory) return;
|
||||
export function removeCategory(categoryId: string) {
|
||||
const categoryIndex = currentUserCategories.findIndex(c => c.id === categoryId);
|
||||
if (categoryIndex === -1) return;
|
||||
|
||||
// catagory?.channels.forEach(c => removeChannelFromCategory(c));
|
||||
categories = categories.filter(c => c.id !== categoryId);
|
||||
await saveCats(categories);
|
||||
currentUserCategories.splice(categoryIndex, 1);
|
||||
}
|
||||
|
||||
export async function collapseCategory(id: string, value = true) {
|
||||
const category = categories.find(c => c.id === id);
|
||||
if (!category) return;
|
||||
export function collapseCategory(id: string, value = true) {
|
||||
const category = currentUserCategories.find(c => c.id === id);
|
||||
if (category == null) return;
|
||||
|
||||
category.collapsed = value;
|
||||
await saveCats(categories);
|
||||
}
|
||||
|
||||
// utils
|
||||
// Utils
|
||||
export function isPinned(id: string) {
|
||||
return categories.some(c => c.channels.includes(id));
|
||||
return currentUserCategories.some(c => c.channels.includes(id));
|
||||
}
|
||||
|
||||
export function categoryLen() {
|
||||
return categories.length;
|
||||
return currentUserCategories.length;
|
||||
}
|
||||
|
||||
export function getAllUncollapsedChannels() {
|
||||
if (settings.store.pinOrder === PinOrder.LastMessage) {
|
||||
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() {
|
||||
return categories.reduce((acc, category) => {
|
||||
return currentUserCategories.reduce((acc, category) => {
|
||||
acc.push(category.channels.length === 0 ? 1 : category.channels.length);
|
||||
return acc;
|
||||
}, [] as number[]);
|
||||
}
|
||||
|
||||
// move categories
|
||||
// Move categories
|
||||
export const canMoveArrayInDirection = (array: any[], index: number, direction: -1 | 1) => {
|
||||
const a = array[index];
|
||||
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) => {
|
||||
const index = categories.findIndex(m => m.id === id);
|
||||
return canMoveArrayInDirection(categories, index, direction);
|
||||
const categoryIndex = currentUserCategories.findIndex(m => m.id === id);
|
||||
return canMoveArrayInDirection(currentUserCategories, categoryIndex, direction);
|
||||
};
|
||||
|
||||
export const canMoveCategory = (id: string) => canMoveCategoryInDirection(id, -1) || canMoveCategoryInDirection(id, 1);
|
||||
|
||||
export const canMoveChannelInDirection = (channelId: string, direction: -1 | 1) => {
|
||||
const category = categories.find(c => c.channels.includes(channelId));
|
||||
if (!category) return false;
|
||||
const category = currentUserCategories.find(c => c.channels.includes(channelId));
|
||||
if (category == null) return false;
|
||||
|
||||
const index = category.channels.indexOf(channelId);
|
||||
return canMoveArrayInDirection(category.channels, index, direction);
|
||||
const channelIndex = category.channels.indexOf(channelId);
|
||||
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]];
|
||||
}
|
||||
|
||||
// stolen from PinDMs
|
||||
export async function moveCategory(id: string, direction: -1 | 1) {
|
||||
const a = categories.findIndex(m => m.id === id);
|
||||
export function moveCategory(id: string, direction: -1 | 1) {
|
||||
const a = currentUserCategories.findIndex(m => m.id === id);
|
||||
const b = a + direction;
|
||||
|
||||
swapElementsInArray(categories, a, b);
|
||||
|
||||
await saveCats(categories);
|
||||
swapElementsInArray(currentUserCategories, a, b);
|
||||
}
|
||||
|
||||
export async function moveChannel(channelId: string, direction: -1 | 1) {
|
||||
const category = categories.find(c => c.channels.includes(channelId));
|
||||
if (!category) return;
|
||||
export function moveChannel(channelId: string, direction: -1 | 1) {
|
||||
const category = currentUserCategories.find(c => c.channels.includes(channelId));
|
||||
if (category == null) return;
|
||||
|
||||
const a = category.channels.indexOf(channelId);
|
||||
const b = a + direction;
|
||||
|
||||
swapElementsInArray(category.channels, a, b);
|
||||
|
||||
await saveCats(categories);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// migrate data
|
||||
const getPinDMsPins = () => (Settings.plugins.PinDMs.pinnedDMs || void 0)?.split(",") as string[] | undefined;
|
||||
|
||||
async function migratePinDMs() {
|
||||
if (categories.some(m => m.id === "oldPins")) {
|
||||
return await DataStore.set(CATEGORY_MIGRATED_PINDMS_KEY, true);
|
||||
// TODO: Remove DataStore PinnedDms migration once enough time has passed
|
||||
async function migrateData() {
|
||||
if (Settings.plugins.PinDMs.dmSectioncollapsed != null) {
|
||||
settings.store.dmSectionCollapsed = Settings.plugins.PinDMs.dmSectioncollapsed;
|
||||
delete Settings.plugins.PinDMs.dmSectioncollapsed;
|
||||
}
|
||||
|
||||
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
|
||||
const difference = [...new Set(pindmspins)]?.filter(m => !categories.some(c => c.channels.includes(m)));
|
||||
if (difference?.length) {
|
||||
categories.push({
|
||||
id: "oldPins",
|
||||
name: "Pins",
|
||||
color: DEFAULT_COLOR,
|
||||
channels: difference
|
||||
});
|
||||
if (pinDmsKeys.length === 0) return;
|
||||
|
||||
for (const pinDmsKey of pinDmsKeys) {
|
||||
const categories = await DataStore.get<Category[]>(pinDmsKey);
|
||||
if (categories == null) continue;
|
||||
|
||||
const userId = pinDmsKey.replace(CATEGORY_BASE_KEY, "");
|
||||
settings.store.userBasedCategoryList[userId] = categories;
|
||||
|
||||
await DataStore.del(pinDmsKey);
|
||||
}
|
||||
|
||||
await DataStore.set(CATEGORY_MIGRATED_PINDMS_KEY, true);
|
||||
}
|
||||
|
||||
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);
|
||||
await Promise.all([DataStore.del(CATEGORY_MIGRATED_PINDMS_KEY), DataStore.del(CATEGORY_MIGRATED_KEY), DataStore.del(OLD_CATEGORY_KEY)]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ import { Devs } from "@utils/constants";
|
|||
import { classes } from "@utils/misc";
|
||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||
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 { contextMenus } from "./components/contextMenu";
|
||||
import { openCategoryModal, requireSettingsMenu } from "./components/CreateCategoryModal";
|
||||
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 {
|
||||
children: React.ReactNode,
|
||||
|
|
@ -26,13 +26,11 @@ interface ChannelComponentProps {
|
|||
selected: boolean;
|
||||
}
|
||||
|
||||
|
||||
const headerClasses = findByPropsLazy("privateChannelsHeaderContainer");
|
||||
|
||||
export const PrivateChannelSortStore = findStoreLazy("PrivateChannelSortStore") as { getPrivateChannelIds: () => string[]; };
|
||||
|
||||
export let instance: any;
|
||||
export const forceUpdate = () => instance?.props?._forceUpdate?.();
|
||||
|
||||
export const enum PinOrder {
|
||||
LastMessage,
|
||||
|
|
@ -46,21 +44,29 @@ export const settings = definePluginSettings({
|
|||
options: [
|
||||
{ label: "Most recent message", value: PinOrder.LastMessage, default: true },
|
||||
{ label: "Custom (right click channels to reorder)", value: PinOrder.Custom }
|
||||
],
|
||||
onChange: () => forceUpdate()
|
||||
]
|
||||
},
|
||||
|
||||
dmSectioncollapsed: {
|
||||
canCollapseDmSection: {
|
||||
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,
|
||||
onChange: () => forceUpdate()
|
||||
hidden: true
|
||||
},
|
||||
userBasedCategoryList: {
|
||||
type: OptionType.CUSTOM,
|
||||
default: {} as Record<string, Category[]>,
|
||||
description: "",
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
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],
|
||||
settings,
|
||||
contextMenus,
|
||||
|
|
@ -124,8 +130,8 @@ export default definePlugin({
|
|||
{
|
||||
find: ".FRIENDS},\"friends\"",
|
||||
replacement: {
|
||||
match: /let{showLibrary:\i,.+?showDMHeader:.+?,/,
|
||||
replace: "let forceUpdate = Vencord.Util.useForceUpdater();$&_forceUpdate:forceUpdate,"
|
||||
match: /let{showLibrary:\i,/,
|
||||
replace: "$self.usePinnedDms();$&"
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -149,6 +155,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
],
|
||||
|
||||
sections: null as number[] | null,
|
||||
|
||||
set _instance(i: any) {
|
||||
|
|
@ -162,6 +169,7 @@ export default definePlugin({
|
|||
CONNECTION_OPEN: init,
|
||||
},
|
||||
|
||||
usePinnedDms,
|
||||
isPinned,
|
||||
categoryLen,
|
||||
getSections,
|
||||
|
|
@ -186,11 +194,11 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
makeSpanProps() {
|
||||
return {
|
||||
return settings.store.canCollapseDmSection ? {
|
||||
onClick: () => this.collapseDMList(),
|
||||
role: "button",
|
||||
style: { cursor: "pointer" }
|
||||
};
|
||||
} : undefined;
|
||||
},
|
||||
|
||||
getChunkSize() {
|
||||
|
|
@ -210,30 +218,27 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
isChannelIndex(sectionIndex: number, channelIndex: number) {
|
||||
if (settings.store.dmSectioncollapsed && sectionIndex !== 0)
|
||||
if (settings.store.canCollapseDmSection && settings.store.dmSectionCollapsed && sectionIndex !== 0) {
|
||||
return true;
|
||||
const cat = categories[sectionIndex - 1];
|
||||
return this.isCategoryIndex(sectionIndex) && (cat?.channels?.length === 0 || cat?.channels[channelIndex]);
|
||||
},
|
||||
}
|
||||
|
||||
isDMSectioncollapsed() {
|
||||
return settings.store.dmSectioncollapsed;
|
||||
const category = getCategoryByIndex(sectionIndex - 1);
|
||||
return this.isCategoryIndex(sectionIndex) && (category?.channels?.length === 0 || category?.channels[channelIndex]);
|
||||
},
|
||||
|
||||
collapseDMList() {
|
||||
settings.store.dmSectioncollapsed = !settings.store.dmSectioncollapsed;
|
||||
forceUpdate();
|
||||
settings.store.dmSectionCollapsed = !settings.store.dmSectionCollapsed;
|
||||
},
|
||||
|
||||
isChannelHidden(categoryIndex: number, channelIndex: number) {
|
||||
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;
|
||||
|
||||
if (!this.instance || !this.isChannelIndex(categoryIndex, channelIndex)) return false;
|
||||
|
||||
const category = categories[categoryIndex - 1];
|
||||
const category = getCategoryByIndex(categoryIndex - 1);
|
||||
if (!category) return false;
|
||||
|
||||
return category.collapsed && this.instance.props.selectedChannelId !== this.getCategoryChannels(category)[channelIndex];
|
||||
|
|
@ -251,18 +256,12 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
renderCategory: ErrorBoundary.wrap(({ section }: { section: number; }) => {
|
||||
const category = categories[section - 1];
|
||||
|
||||
const category = getCategoryByIndex(section - 1);
|
||||
if (!category) return null;
|
||||
|
||||
return (
|
||||
<h2
|
||||
className={classes(headerClasses.privateChannelsHeaderContainer, "vc-pindms-section-container", category.collapsed ? "vc-pindms-collapsed" : "")}
|
||||
style={{ color: `#${category.color.toString(16).padStart(6, "0")}` }}
|
||||
onClick={async () => {
|
||||
await collapseCategory(category.id, !category.collapsed);
|
||||
forceUpdate();
|
||||
}}
|
||||
<Clickable
|
||||
onClick={() => collapseCategory(category.id, !category.collapsed)}
|
||||
onContextMenu={e => {
|
||||
ContextMenuApi.openContextMenu(e, () => (
|
||||
<Menu.Menu
|
||||
|
|
@ -284,14 +283,14 @@ export default definePlugin({
|
|||
canMoveCategoryInDirection(category.id, -1) && <Menu.MenuItem
|
||||
id="vc-pindms-move-category-up"
|
||||
label="Move Up"
|
||||
action={() => moveCategory(category.id, -1).then(() => forceUpdate())}
|
||||
action={() => moveCategory(category.id, -1)}
|
||||
/>
|
||||
}
|
||||
{
|
||||
canMoveCategoryInDirection(category.id, 1) && <Menu.MenuItem
|
||||
id="vc-pindms-move-category-down"
|
||||
label="Move Down"
|
||||
action={() => moveCategory(category.id, 1).then(() => forceUpdate())}
|
||||
action={() => moveCategory(category.id, 1)}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
|
|
@ -304,7 +303,7 @@ export default definePlugin({
|
|||
id="vc-pindms-delete-category"
|
||||
color="danger"
|
||||
label="Delete Category"
|
||||
action={() => removeCategory(category.id).then(() => forceUpdate())}
|
||||
action={() => removeCategory(category.id)}
|
||||
/>
|
||||
|
||||
|
||||
|
|
@ -312,13 +311,18 @@ export default definePlugin({
|
|||
));
|
||||
}}
|
||||
>
|
||||
<span className={headerClasses.headerText}>
|
||||
{category?.name ?? "uh oh"}
|
||||
</span>
|
||||
<svg className="vc-pindms-collapse-icon" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<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>
|
||||
</h2>
|
||||
<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}>
|
||||
{category?.name ?? "uh oh"}
|
||||
</span>
|
||||
<svg className="vc-pindms-collapse-icon" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<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>
|
||||
</h2>
|
||||
</Clickable>
|
||||
);
|
||||
}, { noop: true }),
|
||||
|
||||
|
|
@ -341,7 +345,7 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
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 };
|
||||
|
||||
const channelId = this.getCategoryChannels(category)[index];
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@
|
|||
|
||||
import "./style.css";
|
||||
|
||||
import { addBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeBadge } from "@api/Badges";
|
||||
import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
|
||||
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
|
||||
import { addProfileBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeProfileBadge } from "@api/Badges";
|
||||
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
|
||||
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
|
||||
import { Settings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
|
|
@ -175,26 +175,26 @@ const badge: ProfileBadge = {
|
|||
const indicatorLocations = {
|
||||
list: {
|
||||
description: "In the member list",
|
||||
onEnable: () => addDecorator("platform-indicator", props =>
|
||||
onEnable: () => addMemberListDecorator("platform-indicator", props =>
|
||||
<ErrorBoundary noop>
|
||||
<PlatformIndicator user={props.user} small={true} />
|
||||
</ErrorBoundary>
|
||||
),
|
||||
onDisable: () => removeDecorator("platform-indicator")
|
||||
onDisable: () => removeMemberListDecorator("platform-indicator")
|
||||
},
|
||||
badges: {
|
||||
description: "In user profiles, as badges",
|
||||
onEnable: () => addBadge(badge),
|
||||
onDisable: () => removeBadge(badge)
|
||||
onEnable: () => addProfileBadge(badge),
|
||||
onDisable: () => removeProfileBadge(badge)
|
||||
},
|
||||
messages: {
|
||||
description: "Inside messages",
|
||||
onEnable: () => addDecoration("platform-indicator", props =>
|
||||
onEnable: () => addMessageDecoration("platform-indicator", props =>
|
||||
<ErrorBoundary noop>
|
||||
<PlatformIndicator user={props.message?.author} wantTopMargin={true} />
|
||||
</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/>.
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { generateId, sendBotMessage } from "@api/Commands";
|
||||
import { Devs } from "@utils/constants";
|
||||
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 draft = useStateFromStores([DraftStore], () => getDraft(channelId));
|
||||
|
||||
|
|
@ -121,11 +121,9 @@ export default definePlugin({
|
|||
name: "PreviewMessage",
|
||||
description: "Lets you preview your message before sending it.",
|
||||
authors: [Devs.Aria],
|
||||
dependencies: ["ChatInputButtonAPI"],
|
||||
// start early to ensure we're the first plugin to add our button
|
||||
// This makes the popping in less awkward
|
||||
startAt: StartAt.Init,
|
||||
|
||||
start: () => addChatBarButton("previewMessage", PreviewButton),
|
||||
stop: () => removeChatBarButton("previewMessage"),
|
||||
renderChatBarButton: PreviewButton,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { insertTextIntoChatInputBox } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
|
|
@ -26,24 +25,18 @@ export default definePlugin({
|
|||
name: "QuickMention",
|
||||
authors: [Devs.kemo],
|
||||
description: "Adds a quick mention button to the message actions bar",
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
|
||||
start() {
|
||||
addButton("QuickMention", msg => {
|
||||
const channel = ChannelStore.getChannel(msg.channel_id);
|
||||
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return null;
|
||||
renderMessagePopoverButton(msg) {
|
||||
const channel = ChannelStore.getChannel(msg.channel_id);
|
||||
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return null;
|
||||
|
||||
return {
|
||||
label: "Quick Mention",
|
||||
icon: this.Icon,
|
||||
message: msg,
|
||||
channel,
|
||||
onClick: () => insertTextIntoChatInputBox(`<@${msg.author.id}> `)
|
||||
};
|
||||
});
|
||||
},
|
||||
stop() {
|
||||
removeButton("QuickMention");
|
||||
return {
|
||||
label: "Quick Mention",
|
||||
icon: this.Icon,
|
||||
message: msg,
|
||||
channel,
|
||||
onClick: () => insertTextIntoChatInputBox(`<@${msg.author.id}> `)
|
||||
};
|
||||
},
|
||||
|
||||
Icon: () => (
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@
|
|||
|
||||
import "./styles.css";
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
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;
|
||||
|
||||
return (
|
||||
|
|
@ -160,22 +159,14 @@ export default definePlugin({
|
|||
name: "SendTimestamps",
|
||||
description: "Send timestamps easily via chat box button & text shortcuts. Read the extended description!",
|
||||
authors: [Devs.Ven, Devs.Tyler, Devs.Grzesiek11],
|
||||
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
start() {
|
||||
addChatBarButton("SendTimestamps", ChatBarIcon);
|
||||
this.listener = addPreSendListener((_, msg) => {
|
||||
if (settings.store.replaceMessageContents) {
|
||||
msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime);
|
||||
}
|
||||
});
|
||||
},
|
||||
renderChatBarButton: ChatBarIcon,
|
||||
|
||||
stop() {
|
||||
removeChatBarButton("SendTimestamps");
|
||||
removePreSendListener(this.listener);
|
||||
onBeforeMessageSend(_, msg) {
|
||||
if (settings.store.replaceMessageContents) {
|
||||
msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime);
|
||||
}
|
||||
},
|
||||
|
||||
settingsAboutComponent() {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const enum Tabs {
|
|||
ServerInfo,
|
||||
Friends,
|
||||
BlockedUsers,
|
||||
IgnoredUsers,
|
||||
MutualMembers
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +48,8 @@ interface RelationshipProps extends GuildProps {
|
|||
|
||||
const fetched = {
|
||||
friends: false,
|
||||
blocked: false
|
||||
blocked: false,
|
||||
ignored: false
|
||||
};
|
||||
|
||||
function renderTimestamp(timestamp: number) {
|
||||
|
|
@ -59,11 +61,13 @@ function renderTimestamp(timestamp: number) {
|
|||
function GuildInfoModal({ guild }: GuildProps) {
|
||||
const [friendCount, setFriendCount] = useState<number>();
|
||||
const [blockedCount, setBlockedCount] = useState<number>();
|
||||
const [ignoredCount, setIgnoredCount] = useState<number>();
|
||||
const [mutualMembersCount, setMutualMembersCount] = useState<number>();
|
||||
|
||||
useEffect(() => {
|
||||
fetched.friends = false;
|
||||
fetched.blocked = false;
|
||||
fetched.ignored = false;
|
||||
}, []);
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
|
||||
|
|
@ -122,25 +126,55 @@ function GuildInfoModal({ guild }: GuildProps) {
|
|||
className={cl("tab", { selected: currentTab === Tabs.ServerInfo })}
|
||||
id={Tabs.ServerInfo}
|
||||
>
|
||||
Server Info
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<div>
|
||||
Server Info
|
||||
</div>
|
||||
</div>
|
||||
</TabBar.Item>
|
||||
<TabBar.Item
|
||||
className={cl("tab", { selected: currentTab === Tabs.Friends })}
|
||||
id={Tabs.Friends}
|
||||
>
|
||||
Friends{friendCount !== undefined ? ` (${friendCount})` : ""}
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<div>
|
||||
Friends
|
||||
</div>
|
||||
{friendCount !== undefined ? ` (${friendCount})` : ""}
|
||||
</div>
|
||||
</TabBar.Item>
|
||||
<TabBar.Item
|
||||
className={cl("tab", { selected: currentTab === Tabs.MutualMembers })}
|
||||
id={Tabs.MutualMembers}
|
||||
>
|
||||
Mutual Server Members{mutualMembersCount !== undefined ? ` (${mutualMembersCount})` : ""}
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<div>
|
||||
Mutual Members
|
||||
</div>{mutualMembersCount !== undefined ? ` (${mutualMembersCount})` : ""}
|
||||
</div>
|
||||
</TabBar.Item>
|
||||
<TabBar.Item
|
||||
className={cl("tab", { selected: currentTab === Tabs.BlockedUsers })}
|
||||
id={Tabs.BlockedUsers}
|
||||
>
|
||||
Blocked Users{blockedCount !== undefined ? ` (${blockedCount})` : ""}
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<div>
|
||||
Blocked Users
|
||||
</div>
|
||||
{blockedCount !== undefined ? ` (${blockedCount})` : ""}
|
||||
</div>
|
||||
</TabBar.Item>
|
||||
<TabBar.Item
|
||||
className={cl("tab", { selected: currentTab === Tabs.IgnoredUsers })}
|
||||
id={Tabs.IgnoredUsers}
|
||||
>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<div>
|
||||
Ignored Users
|
||||
</div>
|
||||
{ignoredCount !== undefined ? `(${ignoredCount})` : ""}
|
||||
|
||||
</div>
|
||||
</TabBar.Item>
|
||||
</TabBar>
|
||||
|
||||
|
|
@ -149,6 +183,7 @@ function GuildInfoModal({ guild }: GuildProps) {
|
|||
{currentTab === Tabs.Friends && <FriendsTab guild={guild} setCount={setFriendCount} />}
|
||||
{currentTab === Tabs.MutualMembers && <MutualMembersTab guild={guild} setCount={setMutualMembersCount} />}
|
||||
{currentTab === Tabs.BlockedUsers && <BlockedUsersTab guild={guild} setCount={setBlockedCount} />}
|
||||
{currentTab === Tabs.IgnoredUsers && <IgnoredUserTab guild={guild} setCount={setIgnoredCount} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -222,7 +257,13 @@ function BlockedUsersTab({ guild, setCount }: RelationshipProps) {
|
|||
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 members = [] as string[];
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export default definePlugin({
|
|||
},
|
||||
// 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}`
|
||||
},
|
||||
// Remove permission checking for getRenderLevel function
|
||||
|
|
@ -251,12 +251,12 @@ export default definePlugin({
|
|||
find: "Missing channel in Channel.renderHeaderToolbar",
|
||||
replacement: [
|
||||
{
|
||||
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;}`
|
||||
match: /"renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_TEXT:(?=.+?(\i\.push.{0,50}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
|
||||
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).+?)/,
|
||||
replace: (_, pushNotificationButtonExpression, channel, isLurking) => `if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
|
||||
match: /"renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_MEDIA:(?=.+?(\i\.push.{0,40}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
|
||||
replace: (m, pushNotificationButtonExpression, channel, isLurking) => `${m}if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
|
||||
},
|
||||
{
|
||||
match: /"renderMobileToolbar",\(\)=>{.+?case \i\.\i\.GUILD_DIRECTORY:(?<=let{channel:(\i).+?)/,
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
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);
|
||||
|
||||
function setEnabledValue(value: boolean) {
|
||||
|
|
@ -50,15 +50,15 @@ const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
const listener: SendListener = (_, message) => {
|
||||
const listener: MessageSendListener = (_, message) => {
|
||||
if (enabled) {
|
||||
if (settings.store.autoDisable) setEnabledValue(false);
|
||||
if (!message.content.startsWith("@silent ")) message.content = "@silent " + message.content;
|
||||
}
|
||||
};
|
||||
|
||||
addPreSendListener(listener);
|
||||
return () => void removePreSendListener(listener);
|
||||
addMessagePreSendListener(listener);
|
||||
return () => void removeMessagePreSendListener(listener);
|
||||
}, [enabled]);
|
||||
|
||||
if (!isMainChat) return null;
|
||||
|
|
@ -91,9 +91,7 @@ export default definePlugin({
|
|||
name: "SilentMessageToggle",
|
||||
authors: [Devs.Nuckyz, Devs.CatNoir],
|
||||
description: "Adds a button to the chat bar to toggle sending a silent message.",
|
||||
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
|
||||
settings,
|
||||
|
||||
start: () => addChatBarButton("SilentMessageToggle", SilentMessageToggle),
|
||||
stop: () => removeChatBarButton("SilentMessageToggle")
|
||||
renderChatBarButton: SilentMessageToggle,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* 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 { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
|
|
@ -85,7 +85,7 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
const SilentTypingToggle: ChatBarButton = ({ isMainChat }) => {
|
||||
const SilentTypingToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]);
|
||||
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
||||
|
||||
|
|
@ -137,11 +137,12 @@ export default definePlugin({
|
|||
name: "SilentTyping",
|
||||
authors: [Devs.Ven, Devs.Rini, Devs.ImBanana],
|
||||
description: "Hide that you are typing",
|
||||
dependencies: ["ChatInputButtonAPI"],
|
||||
settings,
|
||||
|
||||
contextMenus: {
|
||||
"textarea-context": ChatBarContextCheckbox
|
||||
},
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: '.dispatch({type:"TYPING_START_LOCAL"',
|
||||
|
|
@ -201,8 +202,5 @@ export default definePlugin({
|
|||
FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId });
|
||||
},
|
||||
|
||||
start: () => {
|
||||
addChatBarButton("SilentTyping", SilentTypingToggle);
|
||||
},
|
||||
stop: () => removeChatBarButton("SilentTyping"),
|
||||
renderChatBarButton: SilentTypingToggle,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,13 +17,11 @@
|
|||
*/
|
||||
|
||||
import { DataStore } from "@api/index";
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { DeleteIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, Forms, React, TextInput, useState } from "@webpack/common";
|
||||
|
||||
|
|
@ -35,8 +33,6 @@ type Rule = Record<"find" | "replace" | "onlyIfIncludes", string>;
|
|||
interface TextReplaceProps {
|
||||
title: string;
|
||||
rulesArray: Rule[];
|
||||
rulesKey: string;
|
||||
update: () => void;
|
||||
}
|
||||
|
||||
const makeEmptyRule: () => Rule = () => ({
|
||||
|
|
@ -46,34 +42,38 @@ const makeEmptyRule: () => Rule = () => ({
|
|||
});
|
||||
const makeEmptyRuleArray = () => [makeEmptyRule()];
|
||||
|
||||
let stringRules = makeEmptyRuleArray();
|
||||
let regexRules = makeEmptyRuleArray();
|
||||
|
||||
const settings = definePluginSettings({
|
||||
replace: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "",
|
||||
component: () => {
|
||||
const update = useForceUpdater();
|
||||
const { stringRules, regexRules } = settings.use(["stringRules", "regexRules"]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextReplace
|
||||
title="Using String"
|
||||
rulesArray={stringRules}
|
||||
rulesKey={STRING_RULES_KEY}
|
||||
update={update}
|
||||
/>
|
||||
<TextReplace
|
||||
title="Using Regex"
|
||||
rulesArray={regexRules}
|
||||
rulesKey={REGEX_RULES_KEY}
|
||||
update={update}
|
||||
/>
|
||||
<TextReplaceTesting />
|
||||
</>
|
||||
);
|
||||
}
|
||||
},
|
||||
stringRules: {
|
||||
type: OptionType.CUSTOM,
|
||||
default: makeEmptyRuleArray(),
|
||||
description: "",
|
||||
},
|
||||
regexRules: {
|
||||
type: OptionType.CUSTOM,
|
||||
default: makeEmptyRuleArray(),
|
||||
description: "",
|
||||
}
|
||||
});
|
||||
|
||||
function stringToRegex(str: string) {
|
||||
|
|
@ -120,28 +120,24 @@ function Input({ initialValue, onChange, placeholder }: {
|
|||
);
|
||||
}
|
||||
|
||||
function TextReplace({ title, rulesArray, rulesKey, update }: TextReplaceProps) {
|
||||
function TextReplace({ title, rulesArray }: TextReplaceProps) {
|
||||
const isRegexRules = title === "Using Regex";
|
||||
|
||||
async function onClickRemove(index: number) {
|
||||
if (index === rulesArray.length - 1) return;
|
||||
rulesArray.splice(index, 1);
|
||||
|
||||
await DataStore.set(rulesKey, rulesArray);
|
||||
update();
|
||||
}
|
||||
|
||||
async function onChange(e: string, index: number, key: string) {
|
||||
if (index === rulesArray.length - 1)
|
||||
if (index === rulesArray.length - 1) {
|
||||
rulesArray.push(makeEmptyRule());
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
await DataStore.set(rulesKey, rulesArray);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -208,29 +204,26 @@ function TextReplaceTesting() {
|
|||
}
|
||||
|
||||
function applyRules(content: string): string {
|
||||
if (content.length === 0)
|
||||
if (content.length === 0) {
|
||||
return content;
|
||||
|
||||
if (stringRules) {
|
||||
for (const rule of stringRules) {
|
||||
if (!rule.find) continue;
|
||||
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
||||
|
||||
content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, "");
|
||||
}
|
||||
}
|
||||
|
||||
if (regexRules) {
|
||||
for (const rule of regexRules) {
|
||||
if (!rule.find) continue;
|
||||
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
||||
for (const rule of settings.store.stringRules) {
|
||||
if (!rule.find) continue;
|
||||
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
||||
|
||||
try {
|
||||
const regex = stringToRegex(rule.find);
|
||||
content = content.replace(regex, rule.replace.replaceAll("\\n", "\n"));
|
||||
} catch (e) {
|
||||
new Logger("TextReplace").error(`Invalid regex: ${rule.find}`);
|
||||
}
|
||||
content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, "");
|
||||
}
|
||||
|
||||
for (const rule of settings.store.regexRules) {
|
||||
if (!rule.find) continue;
|
||||
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
||||
|
||||
try {
|
||||
const regex = stringToRegex(rule.find);
|
||||
content = content.replace(regex, rule.replace.replaceAll("\\n", "\n"));
|
||||
} catch (e) {
|
||||
new Logger("TextReplace").error(`Invalid regex: ${rule.find}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -244,22 +237,27 @@ export default definePlugin({
|
|||
name: "TextReplace",
|
||||
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],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
async start() {
|
||||
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
|
||||
if (channelId === TEXT_REPLACE_RULES_CHANNEL_ID) return;
|
||||
msg.content = applyRules(msg.content);
|
||||
});
|
||||
onBeforeMessageSend(channelId, msg) {
|
||||
// Channel used for sharing rules, applying rules here would be messy
|
||||
if (channelId === TEXT_REPLACE_RULES_CHANNEL_ID) return;
|
||||
msg.content = applyRules(msg.content);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
async start() {
|
||||
// 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/>.
|
||||
*/
|
||||
|
||||
import { ChatBarButton } from "@api/ChatButtons";
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { classes } from "@utils/misc";
|
||||
import { openModal } from "@utils/modal";
|
||||
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 const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||
export const TranslateChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const { autoTranslate, showChatBarButton } = settings.use(["autoTranslate", "showChatBarButton"]);
|
||||
|
||||
const [shouldShowTranslateEnabledTooltip, setter] = useState(false);
|
||||
|
|
|
|||
|
|
@ -18,11 +18,7 @@
|
|||
|
||||
import "./styles.css";
|
||||
|
||||
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
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 definePlugin from "@utils/types";
|
||||
import { ChannelStore, Menu } from "@webpack/common";
|
||||
|
|
@ -51,11 +47,12 @@ const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) =>
|
|||
));
|
||||
};
|
||||
|
||||
let tooltipTimeout: any;
|
||||
|
||||
export default definePlugin({
|
||||
name: "Translate",
|
||||
description: "Translate messages with Google Translate or DeepL",
|
||||
authors: [Devs.Ven, Devs.AshtonMemer],
|
||||
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"],
|
||||
settings,
|
||||
contextMenus: {
|
||||
"message": messageCtxPatch
|
||||
|
|
@ -63,45 +60,34 @@ export default definePlugin({
|
|||
// not used, just here in case some other plugin wants it or w/e
|
||||
translate,
|
||||
|
||||
start() {
|
||||
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
||||
renderMessageAccessory: props => <TranslationAccessory message={props.message} />,
|
||||
|
||||
addChatBarButton("vc-translate", TranslateChatBarIcon);
|
||||
renderChatBarButton: TranslateChatBarIcon,
|
||||
|
||||
addButton("vc-translate", message => {
|
||||
if (!message.content) return null;
|
||||
renderMessagePopoverButton(message) {
|
||||
if (!message.content) return null;
|
||||
|
||||
return {
|
||||
label: "Translate",
|
||||
icon: TranslateIcon,
|
||||
message,
|
||||
channel: ChannelStore.getChannel(message.channel_id),
|
||||
onClick: async () => {
|
||||
const trans = await translate("received", message.content);
|
||||
handleTranslate(message.id, trans);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
let tooltipTimeout: any;
|
||||
this.preSend = addPreSendListener(async (_, message) => {
|
||||
if (!settings.store.autoTranslate) return;
|
||||
if (!message.content) return;
|
||||
|
||||
setShouldShowTranslateEnabledTooltip?.(true);
|
||||
clearTimeout(tooltipTimeout);
|
||||
tooltipTimeout = setTimeout(() => setShouldShowTranslateEnabledTooltip?.(false), 2000);
|
||||
|
||||
const trans = await translate("sent", message.content);
|
||||
message.content = trans.text;
|
||||
|
||||
});
|
||||
return {
|
||||
label: "Translate",
|
||||
icon: TranslateIcon,
|
||||
message,
|
||||
channel: ChannelStore.getChannel(message.channel_id),
|
||||
onClick: async () => {
|
||||
const trans = await translate("received", message.content);
|
||||
handleTranslate(message.id, trans);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removeChatBarButton("vc-translate");
|
||||
removeButton("vc-translate");
|
||||
removeAccessory("vc-translation");
|
||||
},
|
||||
async onBeforeMessageSend(_, message) {
|
||||
if (!settings.store.autoTranslate) return;
|
||||
if (!message.content) return;
|
||||
|
||||
setShouldShowTranslateEnabledTooltip?.(true);
|
||||
clearTimeout(tooltipTimeout);
|
||||
tooltipTimeout = setTimeout(() => setShouldShowTranslateEnabledTooltip?.(false), 2000);
|
||||
|
||||
const trans = await translate("sent", message.content);
|
||||
message.content = trans.text;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* 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 definePlugin from "@utils/types";
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ export default definePlugin({
|
|||
name: "Unindent",
|
||||
description: "Trims leading indentation from codeblocks",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "inQuote:",
|
||||
|
|
@ -55,13 +55,11 @@ export default definePlugin({
|
|||
});
|
||||
},
|
||||
|
||||
start() {
|
||||
this.preSend = addPreSendListener((_, msg) => this.unindentMsg(msg));
|
||||
this.preEdit = addPreEditListener((_cid, _mid, msg) => this.unindentMsg(msg));
|
||||
onBeforeMessageSend(_, msg) {
|
||||
return this.unindentMsg(msg);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removePreEditListener(this.preEdit);
|
||||
onBeforeMessageEdit(_cid, _mid, msg) {
|
||||
return this.unindentMsg(msg);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,12 +21,18 @@ import { ImageInvisible, ImageVisible } from "@components/Icons";
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common";
|
||||
import { MessageSnapshot } from "@webpack/types";
|
||||
|
||||
|
||||
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;
|
||||
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);
|
||||
if (author.id === UserStore.getCurrentUser().id && !hasEmbedPerms) return;
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
import "./style.css";
|
||||
|
||||
import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
|
||||
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
|
||||
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
|
||||
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -96,16 +96,16 @@ export default definePlugin({
|
|||
|
||||
start() {
|
||||
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) {
|
||||
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() {
|
||||
removeDecorator("UserVoiceShow");
|
||||
removeDecoration("UserVoiceShow");
|
||||
removeMemberListDecorator("UserVoiceShow");
|
||||
removeMessageDecoration("UserVoiceShow");
|
||||
},
|
||||
|
||||
VoiceChannelIndicator
|
||||
|
|
|
|||
|
|
@ -17,18 +17,17 @@
|
|||
*/
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getIntlMessage } from "@utils/discord";
|
||||
import { getCurrentGuild, getIntlMessage } from "@utils/discord";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
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";
|
||||
|
||||
|
||||
|
|
@ -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) => {
|
||||
const value = props[name.toLowerCase()];
|
||||
if (!value) return;
|
||||
|
|
@ -145,58 +144,71 @@ 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({
|
||||
name: "ViewRaw",
|
||||
description: "Copy and view the raw content/data of any message, channel or guild",
|
||||
authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna],
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
settings,
|
||||
|
||||
contextMenus: {
|
||||
"guild-context": MakeContextCallback("Guild"),
|
||||
"guild-settings-role-context": MakeContextCallback("Role"),
|
||||
"channel-context": MakeContextCallback("Channel"),
|
||||
"thread-context": MakeContextCallback("Channel"),
|
||||
"gdm-context": MakeContextCallback("Channel"),
|
||||
"user-context": MakeContextCallback("User")
|
||||
"user-context": MakeContextCallback("User"),
|
||||
"dev-context": devContextCallback
|
||||
},
|
||||
|
||||
start() {
|
||||
addButton("ViewRaw", msg => {
|
||||
const handleClick = () => {
|
||||
if (settings.store.clickMethod === "Right") {
|
||||
copyWithToast(msg.content);
|
||||
} else {
|
||||
openViewRawModalMessage(msg);
|
||||
}
|
||||
};
|
||||
renderMessagePopoverButton(msg) {
|
||||
const handleClick = () => {
|
||||
if (settings.store.clickMethod === "Right") {
|
||||
copyWithToast(msg.content);
|
||||
} else {
|
||||
openViewRawModalMessage(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const handleContextMenu = e => {
|
||||
if (settings.store.clickMethod === "Left") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
copyWithToast(msg.content);
|
||||
} else {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openViewRawModalMessage(msg);
|
||||
}
|
||||
};
|
||||
const handleContextMenu = e => {
|
||||
if (settings.store.clickMethod === "Left") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
copyWithToast(msg.content);
|
||||
} else {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openViewRawModalMessage(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const label = settings.store.clickMethod === "Right"
|
||||
? "Copy Raw (Left Click) / View Raw (Right Click)"
|
||||
: "View Raw (Left Click) / Copy Raw (Right Click)";
|
||||
const label = settings.store.clickMethod === "Right"
|
||||
? "Copy Raw (Left Click) / View Raw (Right Click)"
|
||||
: "View Raw (Left Click) / Copy Raw (Right Click)";
|
||||
|
||||
return {
|
||||
label,
|
||||
icon: CopyIcon,
|
||||
message: msg,
|
||||
channel: ChannelStore.getChannel(msg.channel_id),
|
||||
onClick: handleClick,
|
||||
onContextMenu: handleContextMenu
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeButton("ViewRaw");
|
||||
return {
|
||||
label,
|
||||
icon: CopyIcon,
|
||||
message: msg,
|
||||
channel: ChannelStore.getChannel(msg.channel_id),
|
||||
onClick: handleClick,
|
||||
onContextMenu: handleContextMenu
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
|
||||
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
|
||||
type ResolvePropDeep<T, P> = P extends `${infer Pre}.${infer Suf}`
|
||||
? Pre extends keyof T
|
||||
|
|
@ -28,6 +31,11 @@ interface SettingsStoreOptions {
|
|||
// merges the SettingsStoreOptions type into the class
|
||||
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
|
||||
* 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> {
|
||||
private pathListeners = new Map<string, Set<(newData: any) => 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
|
||||
|
|
@ -51,39 +143,35 @@ export class SettingsStore<T extends object> {
|
|||
Object.assign(this, options);
|
||||
}
|
||||
|
||||
private makeProxy(object: any, root: T = object, path: string = "") {
|
||||
const self = this;
|
||||
|
||||
return new Proxy(object, {
|
||||
get(target, key: string) {
|
||||
let v = target[key];
|
||||
|
||||
if (!(key in target) && self.getDefaultValue) {
|
||||
v = self.getDefaultValue({
|
||||
target,
|
||||
key,
|
||||
root,
|
||||
path
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof v === "object" && v !== null && !Array.isArray(v))
|
||||
return self.makeProxy(v, root, `${path}${path && "."}${key}`);
|
||||
|
||||
return v;
|
||||
},
|
||||
set(target, key: string, value) {
|
||||
if (target[key] === value) return true;
|
||||
|
||||
Reflect.set(target, key, value);
|
||||
const setPath = `${path}${path && "."}${key}`;
|
||||
|
||||
self.globalListeners.forEach(cb => cb(value, setPath));
|
||||
self.pathListeners.get(setPath)?.forEach(cb => cb(value));
|
||||
|
||||
return true;
|
||||
}
|
||||
private makeProxy(object: any, root: T = object, path = "") {
|
||||
this.proxyContexts.set(object, {
|
||||
root,
|
||||
path
|
||||
});
|
||||
|
||||
return new Proxy(object, this.proxyHandler);
|
||||
}
|
||||
|
||||
private notifyListeners(pathStr: string, value: any, root: T) {
|
||||
const paths = pathStr.split(".");
|
||||
|
||||
// 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).
|
||||
// So, we need to extract the top-level setting path (plugins.pluginName.settingName),
|
||||
// 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);
|
||||
|
||||
this.globalListeners.forEach(cb => cb(root, settingPathStr));
|
||||
this.pathListeners.get(settingPathStr)?.forEach(cb => cb(settingValue));
|
||||
} else {
|
||||
this.globalListeners.forEach(cb => cb(root, pathStr));
|
||||
}
|
||||
|
||||
this.pathListeners.get(pathStr)?.forEach(cb => cb(value));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -16,8 +16,15 @@
|
|||
* 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 { 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 { JSX } from "react";
|
||||
import { Promisable } from "type-fest";
|
||||
|
|
@ -147,6 +154,20 @@ export interface PluginDef {
|
|||
toolboxActions?: Record<string, () => void>;
|
||||
|
||||
tags?: string[];
|
||||
|
||||
userProfileBadge?: ProfileBadge;
|
||||
|
||||
onMessageClick?: MessageClickListener;
|
||||
onBeforeMessageSend?: MessageSendListener;
|
||||
onBeforeMessageEdit?: MessageEditListener;
|
||||
|
||||
renderMessagePopoverButton?: MessagePopoverButtonFactory;
|
||||
renderMessageAccessory?: MessageAccessoryFactory;
|
||||
renderMessageDecoration?: MessageDecorationFactory;
|
||||
|
||||
renderMemberListDecorator?: MemberListDecoratorFactory;
|
||||
|
||||
renderChatBarButton?: ChatBarButtonFactory;
|
||||
}
|
||||
|
||||
export const enum StartAt {
|
||||
|
|
@ -173,6 +194,7 @@ export const enum OptionType {
|
|||
SELECT,
|
||||
SLIDER,
|
||||
COMPONENT,
|
||||
CUSTOM
|
||||
}
|
||||
|
||||
export type SettingsDefinition = Record<string, PluginSettingDef>;
|
||||
|
|
@ -181,7 +203,7 @@ export type SettingsChecks<D extends SettingsDefinition> = {
|
|||
(IsDisabled<DefinedSettings<D>> & IsValid<PluginSettingType<D[K]>, DefinedSettings<D>>);
|
||||
};
|
||||
|
||||
export type PluginSettingDef = (
|
||||
export type PluginSettingDef = (PluginSettingCommon & PluginSettingCustomDef & Pick<PluginSettingCommon, "onChange">) | ((
|
||||
| PluginSettingStringDef
|
||||
| PluginSettingNumberDef
|
||||
| PluginSettingBooleanDef
|
||||
|
|
@ -189,7 +211,7 @@ export type PluginSettingDef = (
|
|||
| PluginSettingSliderDef
|
||||
| PluginSettingComponentDef
|
||||
| PluginSettingBigIntDef
|
||||
) & PluginSettingCommon;
|
||||
) & PluginSettingCommon);
|
||||
|
||||
export interface PluginSettingCommon {
|
||||
description: string;
|
||||
|
|
@ -243,12 +265,18 @@ export interface PluginSettingSelectDef {
|
|||
type: OptionType.SELECT;
|
||||
options: readonly PluginSettingSelectOption[];
|
||||
}
|
||||
|
||||
export interface PluginSettingSelectOption {
|
||||
label: string;
|
||||
value: string | number | boolean;
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
export interface PluginSettingCustomDef {
|
||||
type: OptionType.CUSTOM;
|
||||
default?: any;
|
||||
}
|
||||
|
||||
export interface PluginSettingSliderDef {
|
||||
type: OptionType.SLIDER;
|
||||
/**
|
||||
|
|
@ -298,7 +326,9 @@ type PluginSettingType<O extends PluginSettingDef> = O extends PluginSettingStri
|
|||
O extends PluginSettingSelectDef ? O["options"][number]["value"] :
|
||||
O extends PluginSettingSliderDef ? number :
|
||||
O extends PluginSettingComponentDef ? any :
|
||||
O extends PluginSettingCustomDef ? O extends { default: infer Default; } ? Default : any :
|
||||
never;
|
||||
|
||||
type PluginSettingDefaultType<O extends PluginSettingDef> = O extends PluginSettingSelectDef ? (
|
||||
O["options"] extends { default?: boolean; }[] ? O["options"][number]["value"] : undefined
|
||||
) : O extends { default: infer T; } ? T : undefined;
|
||||
|
|
@ -350,13 +380,15 @@ export type PluginOptionsItem =
|
|||
| PluginOptionBoolean
|
||||
| PluginOptionSelect
|
||||
| PluginOptionSlider
|
||||
| PluginOptionComponent;
|
||||
| PluginOptionComponent
|
||||
| PluginOptionCustom;
|
||||
export type PluginOptionString = PluginSettingStringDef & PluginSettingCommon & IsDisabled & IsValid<string>;
|
||||
export type PluginOptionNumber = (PluginSettingNumberDef | PluginSettingBigIntDef) & PluginSettingCommon & IsDisabled & IsValid<number | BigInt>;
|
||||
export type PluginOptionBoolean = PluginSettingBooleanDef & PluginSettingCommon & IsDisabled & IsValid<boolean>;
|
||||
export type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid<PluginSettingSelectOption>;
|
||||
export type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid<number>;
|
||||
export type PluginOptionComponent = PluginSettingComponentDef & PluginSettingCommon;
|
||||
export type PluginOptionCustom = PluginSettingCustomDef & Pick<PluginSettingCommon, "onChange">;
|
||||
|
||||
export type PluginNative<PluginExports extends Record<string, (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any>> = {
|
||||
[key in keyof PluginExports]:
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export let GuildMemberStore: Stores.GuildMemberStore & t.FluxStore;
|
|||
export let RelationshipStore: Stores.RelationshipStore & t.FluxStore & {
|
||||
/** Get the date (as a string) that the relationship was created */
|
||||
getSince(userId: string): string;
|
||||
isIgnored(userId: string): boolean;
|
||||
};
|
||||
|
||||
export let EmojiStore: t.EmojiStore;
|
||||
|
|
|
|||
15
src/webpack/common/types/components.d.ts
vendored
15
src/webpack/common/types/components.d.ts
vendored
|
|
@ -16,8 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { Moment } from "moment";
|
||||
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";
|
||||
|
||||
|
|
@ -472,15 +471,9 @@ export type ScrollerThin = ComponentType<PropsWithChildren<{
|
|||
onScroll?(): void;
|
||||
}>>;
|
||||
|
||||
export type Clickable = ComponentType<PropsWithChildren<{
|
||||
className?: string;
|
||||
|
||||
href?: string;
|
||||
ignoreKeyPress?: boolean;
|
||||
|
||||
onClick?(e: React.MouseEvent<any>): void;
|
||||
onKeyPress?(): void;
|
||||
}>>;
|
||||
export type Clickable = <T extends "a" | "div" | "span" | "li" = "div">(props: PropsWithChildren<ComponentPropsWithRef<T>> & {
|
||||
tag?: T;
|
||||
}) => ReactNode;
|
||||
|
||||
export type Avatar = ComponentType<PropsWithChildren<{
|
||||
className?: string;
|
||||
|
|
|
|||
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/>.
|
||||
*/
|
||||
|
||||
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 { LiteralUnion } from "type-fest";
|
||||
|
||||
|
|
@ -135,6 +135,10 @@ export type Permissions = "CREATE_INSTANT_INVITE"
|
|||
|
||||
export type PermissionsBits = Record<Permissions, bigint>;
|
||||
|
||||
export interface MessageSnapshot {
|
||||
message: Message;
|
||||
}
|
||||
|
||||
export interface Locale {
|
||||
name: string;
|
||||
value: string;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue