mirror of
https://github.com/Vendicated/Vencord.git
synced 2024-09-20 06:30:35 +00:00
Merge branch 'Vendicated:main' into main
This commit is contained in:
commit
57d22f9b6e
102 changed files with 1728 additions and 963 deletions
|
@ -62,7 +62,7 @@ function GM_fetch(url, opt) {
|
|||
resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
|
||||
resp.text = () => blobTo("text", blob);
|
||||
resp.json = async () => JSON.parse(await blobTo("text", blob));
|
||||
resp.headers = new Headers(parseHeaders(resp.responseHeaders));
|
||||
resp.headers = parseHeaders(resp.responseHeaders);
|
||||
resp.ok = resp.status >= 200 && resp.status < 300;
|
||||
resolve(resp);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.6.7",
|
||||
"version": "1.7.1",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
|
|
|
@ -428,10 +428,11 @@ function runTime(token: string) {
|
|||
|
||||
if (searchType === "findComponent") method = "find";
|
||||
if (searchType === "findExportedComponent") method = "findByProps";
|
||||
if (searchType === "waitFor" || searchType === "waitForComponent" || searchType === "waitForStore") {
|
||||
if (searchType === "waitFor" || searchType === "waitForComponent") {
|
||||
if (typeof args[0] === "string") method = "findByProps";
|
||||
else method = "find";
|
||||
}
|
||||
if (searchType === "waitForStore") method = "findStore";
|
||||
|
||||
try {
|
||||
let result: any;
|
||||
|
|
|
@ -118,11 +118,15 @@ const installerBin = await ensureBinary();
|
|||
|
||||
console.log("Now running Installer...");
|
||||
|
||||
execFileSync(installerBin, {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
VENCORD_USER_DATA_DIR: BASE_DIR,
|
||||
VENCORD_DEV_INSTALL: "1"
|
||||
}
|
||||
});
|
||||
try {
|
||||
execFileSync(installerBin, {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
VENCORD_USER_DATA_DIR: BASE_DIR,
|
||||
VENCORD_DEV_INSTALL: "1"
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
console.error("Something went wrong. Please check the logs above.");
|
||||
}
|
||||
|
|
4
src/api/ChatButton.css
Normal file
4
src/api/ChatButton.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.vc-chatbar-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
128
src/api/ChatButtons.tsx
Normal file
128
src/api/ChatButtons.tsx
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./ChatButton.css";
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { waitFor } from "@webpack";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||
import { Channel } from "discord-types/general";
|
||||
import { HTMLProps, MouseEventHandler, ReactNode } from "react";
|
||||
|
||||
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
||||
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
|
||||
|
||||
export interface ChatBarProps {
|
||||
channel: Channel;
|
||||
disabled: boolean;
|
||||
isEmpty: boolean;
|
||||
type: {
|
||||
analyticsName: string;
|
||||
attachments: boolean;
|
||||
autocomplete: {
|
||||
addReactionShortcut: boolean,
|
||||
forceChatLayer: boolean,
|
||||
reactions: boolean;
|
||||
},
|
||||
commands: {
|
||||
enabled: boolean;
|
||||
},
|
||||
drafts: {
|
||||
type: number,
|
||||
commandType: number,
|
||||
autoSave: boolean;
|
||||
},
|
||||
emojis: {
|
||||
button: boolean;
|
||||
},
|
||||
gifs: {
|
||||
button: boolean,
|
||||
allowSending: boolean;
|
||||
},
|
||||
gifts: {
|
||||
button: boolean;
|
||||
},
|
||||
permissions: {
|
||||
requireSendMessages: boolean;
|
||||
},
|
||||
showThreadPromptOnReply: boolean,
|
||||
stickers: {
|
||||
button: boolean,
|
||||
allowSending: boolean,
|
||||
autoSuggest: boolean;
|
||||
},
|
||||
users: {
|
||||
allowMentioning: boolean;
|
||||
},
|
||||
submit: {
|
||||
button: boolean,
|
||||
ignorePreference: boolean,
|
||||
disableEnterToSubmit: boolean,
|
||||
clearOnSubmit: boolean,
|
||||
useDisabledStylesOnSubmit: boolean;
|
||||
},
|
||||
uploadLongMessages: boolean,
|
||||
upsellLongMessages: {
|
||||
iconOnly: boolean;
|
||||
},
|
||||
showCharacterCount: boolean,
|
||||
sedReplace: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
||||
|
||||
const buttonFactories = new Map<string, ChatBarButton>();
|
||||
const logger = new Logger("ChatButtons");
|
||||
|
||||
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||
if (props.disabled) return;
|
||||
|
||||
for (const [key, Button] of buttonFactories) {
|
||||
buttons.push(
|
||||
<ErrorBoundary noop key={key} onError={e => logger.error(`Failed to render ${key}`, e.error)}>
|
||||
<Button {...props} isMainChat={props.type.analyticsName === "normal"} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button);
|
||||
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
||||
|
||||
export interface ChatBarButtonProps {
|
||||
children: ReactNode;
|
||||
tooltip: string;
|
||||
onClick: MouseEventHandler<HTMLButtonElement>;
|
||||
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu">;
|
||||
}
|
||||
export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
||||
return (
|
||||
<Tooltip text={props.tooltip}>
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
<div className={`expression-picker-chat-input-button ${ChannelTextAreaClasses?.buttonContainer ?? ""} vc-chatbar-button`}>
|
||||
<Button
|
||||
aria-label={props.tooltip}
|
||||
size=""
|
||||
look={ButtonLooks.BLANK}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
||||
onClick={props.onClick}
|
||||
onContextMenu={props.onContextMenu}
|
||||
{...props.buttonProps}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
{props.children}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
}, { noop: true });
|
|
@ -17,22 +17,20 @@
|
|||
*/
|
||||
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { Menu, React } from "@webpack/common";
|
||||
import type { ReactElement } from "react";
|
||||
|
||||
type ContextMenuPatchCallbackReturn = (() => void) | void;
|
||||
/**
|
||||
* @param children The rendered context menu elements
|
||||
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||
* @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates)
|
||||
*/
|
||||
export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => ContextMenuPatchCallbackReturn;
|
||||
export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => void;
|
||||
/**
|
||||
* @param navId The navId of the context menu being patched
|
||||
* @param children The rendered context menu elements
|
||||
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||
* @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates)
|
||||
*/
|
||||
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => ContextMenuPatchCallbackReturn;
|
||||
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => void;
|
||||
|
||||
const ContextMenuLogger = new Logger("ContextMenu");
|
||||
|
||||
|
@ -93,14 +91,19 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
|
|||
* @param id The id of the child. If an array is specified, all ids will be tried
|
||||
* @param children The context menu children
|
||||
*/
|
||||
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null>, _itemsArray?: Array<ReactElement | null>): Array<ReactElement | null> | null {
|
||||
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null>): Array<ReactElement | null> | null {
|
||||
for (const child of children) {
|
||||
if (child == null) continue;
|
||||
|
||||
if (Array.isArray(child)) {
|
||||
const found = findGroupChildrenByChildId(id, child);
|
||||
if (found !== null) return found;
|
||||
}
|
||||
|
||||
if (
|
||||
(Array.isArray(id) && id.some(id => child.props?.id === id))
|
||||
|| child.props?.id === id
|
||||
) return _itemsArray ?? null;
|
||||
) return children;
|
||||
|
||||
let nextChildren = child.props?.children;
|
||||
if (nextChildren) {
|
||||
|
@ -109,7 +112,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
|||
child.props.children = nextChildren;
|
||||
}
|
||||
|
||||
const found = findGroupChildrenByChildId(id, nextChildren, nextChildren);
|
||||
const found = findGroupChildrenByChildId(id, nextChildren);
|
||||
if (found !== null) return found;
|
||||
}
|
||||
}
|
||||
|
@ -126,9 +129,12 @@ interface ContextMenuProps {
|
|||
onClose: (callback: (...args: Array<any>) => any) => void;
|
||||
}
|
||||
|
||||
const patchedMenus = new WeakSet();
|
||||
export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||
props = {
|
||||
...props,
|
||||
children: cloneMenuChildren(props.children),
|
||||
};
|
||||
|
||||
export function _patchContextMenu(props: ContextMenuProps) {
|
||||
props.contextMenuApiArguments ??= [];
|
||||
const contextMenuPatches = navPatches.get(props.navId);
|
||||
|
||||
|
@ -137,8 +143,7 @@ export function _patchContextMenu(props: ContextMenuProps) {
|
|||
if (contextMenuPatches) {
|
||||
for (const patch of contextMenuPatches) {
|
||||
try {
|
||||
const callback = patch(props.children, ...props.contextMenuApiArguments);
|
||||
if (!patchedMenus.has(props)) callback?.();
|
||||
patch(props.children, ...props.contextMenuApiArguments);
|
||||
} catch (err) {
|
||||
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
||||
}
|
||||
|
@ -147,12 +152,30 @@ export function _patchContextMenu(props: ContextMenuProps) {
|
|||
|
||||
for (const patch of globalPatches) {
|
||||
try {
|
||||
const callback = patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
||||
if (!patchedMenus.has(props)) callback?.();
|
||||
patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
||||
} catch (err) {
|
||||
ContextMenuLogger.error("Global patch errored,", err);
|
||||
}
|
||||
}
|
||||
|
||||
patchedMenus.add(props);
|
||||
return props;
|
||||
}
|
||||
|
||||
function cloneMenuChildren(obj: ReactElement | Array<ReactElement | null> | null) {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(cloneMenuChildren);
|
||||
}
|
||||
|
||||
if (React.isValidElement(obj)) {
|
||||
obj = React.cloneElement(obj);
|
||||
|
||||
if (
|
||||
obj?.props?.children &&
|
||||
(obj.type !== Menu.MenuControlItem || obj.type === Menu.MenuControlItem && obj.props.control != null)
|
||||
) {
|
||||
obj.props.children = cloneMenuChildren(obj.props.children);
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ export interface MessageExtra {
|
|||
}
|
||||
|
||||
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
||||
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void>;
|
||||
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
||||
|
||||
const sendListeners = new Set<SendListener>();
|
||||
const editListeners = new Set<EditListener>();
|
||||
|
@ -84,7 +84,7 @@ export async function _handlePreSend(channelId: string, messageObj: MessageObjec
|
|||
for (const listener of sendListeners) {
|
||||
try {
|
||||
const result = await listener(channelId, messageObj, extra);
|
||||
if (result && result.cancel === true) {
|
||||
if (result?.cancel) {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -97,11 +97,15 @@ export async function _handlePreSend(channelId: string, messageObj: MessageObjec
|
|||
export async function _handlePreEdit(channelId: string, messageId: string, messageObj: MessageObject) {
|
||||
for (const listener of editListeners) {
|
||||
try {
|
||||
await listener(channelId, messageId, messageObj);
|
||||
const result = await listener(channelId, messageId, messageObj);
|
||||
if (result?.cancel) {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
MessageEventsLogger.error("MessageEditHandler: Listener encountered an unknown error\n", e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,7 +21,7 @@ import { Settings } from "@api/Settings";
|
|||
import { classNameFactory } from "@api/Styles";
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import { Alerts, Button, Forms, moment, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
||||
import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
||||
import { nanoid } from "nanoid";
|
||||
import type { DispatchWithoutAction } from "react";
|
||||
|
||||
|
@ -129,7 +129,7 @@ function NotificationEntry({ data }: { data: PersistentNotificationData; }) {
|
|||
richBody={
|
||||
<div className={cl("body")}>
|
||||
{data.body}
|
||||
<Timestamp timestamp={moment(data.timestamp)} className={cl("timestamp")} />
|
||||
<Timestamp timestamp={new Date(data.timestamp)} className={cl("timestamp")} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -223,13 +223,13 @@ export const Settings = makeProxy(settings);
|
|||
export function useSettings(paths?: UseSettings<Settings>[]) {
|
||||
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
||||
|
||||
const onUpdate: SubscriptionCallback = paths
|
||||
? (value, path) => paths.includes(path as UseSettings<Settings>) && forceUpdate()
|
||||
: forceUpdate;
|
||||
if (paths) {
|
||||
(forceUpdate as SubscriptionCallback)._paths = paths;
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
subscriptions.add(onUpdate);
|
||||
return () => void subscriptions.delete(onUpdate);
|
||||
subscriptions.add(forceUpdate);
|
||||
return () => void subscriptions.delete(forceUpdate);
|
||||
}, []);
|
||||
|
||||
return Settings;
|
||||
|
@ -253,8 +253,10 @@ type ResolvePropDeep<T, P> = P extends "" ? T :
|
|||
export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void): void;
|
||||
export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void): void;
|
||||
export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) {
|
||||
if (path)
|
||||
if (path) {
|
||||
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
|
||||
}
|
||||
|
||||
subscriptions.add(onUpdate);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
import * as $Badges from "./Badges";
|
||||
import * as $ChatButtons from "./ChatButtons";
|
||||
import * as $Commands from "./Commands";
|
||||
import * as $ContextMenu from "./ContextMenu";
|
||||
import * as $DataStore from "./DataStore";
|
||||
|
@ -104,3 +105,8 @@ export const Notifications = $Notifications;
|
|||
* An api allowing you to patch and add/remove items to/from context menus
|
||||
*/
|
||||
export const ContextMenu = $ContextMenu;
|
||||
|
||||
/**
|
||||
* An API allowing you to add buttons to the chat input
|
||||
*/
|
||||
export const ChatButtons = $ChatButtons;
|
||||
|
|
|
@ -39,9 +39,7 @@ function validateUrl(url: string) {
|
|||
async function eraseAllData() {
|
||||
const res = await fetch(new URL("/v1/", getCloudUrl()), {
|
||||
method: "DELETE",
|
||||
headers: new Headers({
|
||||
Authorization: await getCloudAuth()
|
||||
})
|
||||
headers: { Authorization: await getCloudAuth() }
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
|
|
|
@ -23,7 +23,7 @@ import { debounce } from "@utils/debounce";
|
|||
import { IpcEvents } from "@utils/IpcEvents";
|
||||
import { Queue } from "@utils/Queue";
|
||||
import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron";
|
||||
import { mkdirSync, readFileSync, watch } from "fs";
|
||||
import { FSWatcher, mkdirSync, readFileSync, watch } from "fs";
|
||||
import { open, readdir, readFile, writeFile } from "fs/promises";
|
||||
import { join, normalize } from "path";
|
||||
|
||||
|
@ -126,16 +126,23 @@ ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => {
|
|||
|
||||
|
||||
export function initIpc(mainWindow: BrowserWindow) {
|
||||
let quickCssWatcher: FSWatcher | undefined;
|
||||
|
||||
open(QUICKCSS_PATH, "a+").then(fd => {
|
||||
fd.close();
|
||||
watch(QUICKCSS_PATH, { persistent: false }, debounce(async () => {
|
||||
quickCssWatcher = watch(QUICKCSS_PATH, { persistent: false }, debounce(async () => {
|
||||
mainWindow.webContents.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss());
|
||||
}, 50));
|
||||
});
|
||||
}).catch(() => { });
|
||||
|
||||
watch(THEMES_DIR, { persistent: false }, debounce(() => {
|
||||
const themesWatcher = watch(THEMES_DIR, { persistent: false }, debounce(() => {
|
||||
mainWindow.webContents.postMessage(IpcEvents.THEME_UPDATE, void 0);
|
||||
}));
|
||||
|
||||
mainWindow.once("closed", () => {
|
||||
quickCssWatcher?.close();
|
||||
themesWatcher.close();
|
||||
});
|
||||
}
|
||||
|
||||
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
|
||||
|
|
|
@ -129,6 +129,15 @@ if (!IS_VANILLA) {
|
|||
});
|
||||
|
||||
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
||||
|
||||
// Monkey patch commandLine to disable WidgetLayering: Fix DevTools context menus https://github.com/electron/electron/issues/38790
|
||||
const originalAppend = app.commandLine.appendSwitch;
|
||||
app.commandLine.appendSwitch = function (...args) {
|
||||
if (args[0] === "disable-features" && !args[1]?.includes("WidgetLayering")) {
|
||||
args[1] += ",WidgetLayering";
|
||||
}
|
||||
return originalAppend.apply(this, args);
|
||||
};
|
||||
} else {
|
||||
console.log("[Vencord] Running in vanilla mode. Not loading Vencord");
|
||||
}
|
||||
|
|
|
@ -49,9 +49,12 @@ async function getRepo() {
|
|||
async function calculateGitChanges() {
|
||||
await git("fetch");
|
||||
|
||||
const branch = await git("branch", "--show-current");
|
||||
const branch = (await git("branch", "--show-current")).stdout.trim();
|
||||
|
||||
const res = await git("log", `HEAD...origin/${branch.stdout.trim()}`, "--pretty=format:%an/%h/%s");
|
||||
const existsOnOrigin = (await git("ls-remote", "origin", branch)).stdout.length > 0;
|
||||
if (!existsOnOrigin) return [];
|
||||
|
||||
const res = await git("log", `HEAD...origin/${branch}`, "--pretty=format:%an/%h/%s");
|
||||
|
||||
const commits = res.stdout.trim();
|
||||
return commits ? commits.split("\n").map(line => {
|
||||
|
|
22
src/plugins/_api/chatButtons.ts
Normal file
22
src/plugins/_api/chatButtons.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "ChatInputButtonAPI",
|
||||
description: "API to add buttons to the chat input",
|
||||
authors: [Devs.Ven],
|
||||
|
||||
patches: [{
|
||||
find: 'location:"ChannelTextAreaButtons"',
|
||||
replacement: {
|
||||
match: /if\(!\i\.isMobile\)\{(?=.+?&&(\i)\.push\(.{0,50}"gift")/,
|
||||
replace: "$&Vencord.Api.ChatButtons._injectButtons($1,arguments[0]);"
|
||||
}
|
||||
}]
|
||||
});
|
|
@ -22,15 +22,15 @@ import definePlugin from "@utils/types";
|
|||
export default definePlugin({
|
||||
name: "ContextMenuAPI",
|
||||
description: "API for adding/removing items to/from context menus.",
|
||||
authors: [Devs.Nuckyz, Devs.Ven],
|
||||
authors: [Devs.Nuckyz, Devs.Ven, Devs.Kyuuhachi],
|
||||
required: true,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "♫ (つ。◕‿‿◕。)つ ♪",
|
||||
replacement: {
|
||||
match: /let{navId:/,
|
||||
replace: "Vencord.Api.ContextMenu._patchContextMenu(arguments[0]);$&"
|
||||
match: /(?=let{navId:)(?<=function \i\((\i)\).+?)/,
|
||||
replace: "$1=Vencord.Api.ContextMenu._usePatchContextMenu($1);"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -25,10 +25,13 @@ export default definePlugin({
|
|||
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
|
||||
patches: [
|
||||
{
|
||||
find: '"MessageActionCreators"',
|
||||
find: ".Messages.EDIT_TEXTAREA_HELP",
|
||||
replacement: {
|
||||
match: /async editMessage\(.+?\)\{/,
|
||||
replace: "$&await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
|
||||
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
|
||||
replace: (match, args) => "" +
|
||||
`async ${match}` +
|
||||
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
|
||||
"return Promise.resolve({shoudClear:true,shouldRefocus:true});"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch } from "@api/ContextMenu";
|
||||
import { findGroupChildrenByChildId } from "@api/ContextMenu";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
@ -30,21 +30,21 @@ export default definePlugin({
|
|||
authors: [Devs.Ven, Devs.Megu],
|
||||
required: true,
|
||||
|
||||
start() {
|
||||
contextMenus: {
|
||||
// The settings shortcuts in the user settings cog context menu
|
||||
// read the elements from a hardcoded map which for obvious reason
|
||||
// doesn't contain our sections. This patches the actions of our
|
||||
// sections to manually use SettingsRouter (which only works on desktop
|
||||
// but the context menu is usually not available on mobile anyway)
|
||||
addContextMenuPatch("user-settings-cog", children => () => {
|
||||
const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any;
|
||||
"user-settings-cog"(children) {
|
||||
const section = findGroupChildrenByChildId("VencordSettings", children);
|
||||
section?.forEach(c => {
|
||||
const id = c?.props?.id;
|
||||
if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) {
|
||||
c.props.action = () => SettingsRouter.open(id);
|
||||
c!.props.action = () => SettingsRouter.open(id);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
patches: [{
|
||||
|
|
6
src/plugins/betterRoleContext/README.md
Normal file
6
src/plugins/betterRoleContext/README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# BetterRoleContext
|
||||
|
||||
Adds options to copy role color and edit role when right clicking roles in the user profile
|
||||
|
||||
![](https://github.com/Vendicated/Vencord/assets/45497981/d1765e9e-7db2-4a3c-b110-139c59235326)
|
||||
|
79
src/plugins/betterRoleContext/index.tsx
Normal file
79
src/plugins/betterRoleContext/index.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getCurrentGuild } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Clipboard, Menu, PermissionStore, TextAndImagesSettingsStores } from "@webpack/common";
|
||||
|
||||
const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild");
|
||||
|
||||
function PencilIcon() {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
width="18"
|
||||
height="18"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path fill="currentColor" d="m13.96 5.46 4.58 4.58a1 1 0 0 0 1.42 0l1.38-1.38a2 2 0 0 0 0-2.82l-3.18-3.18a2 2 0 0 0-2.82 0l-1.38 1.38a1 1 0 0 0 0 1.42ZM2.11 20.16l.73-4.22a3 3 0 0 1 .83-1.61l7.87-7.87a1 1 0 0 1 1.42 0l4.58 4.58a1 1 0 0 1 0 1.42l-7.87 7.87a3 3 0 0 1-1.6.83l-4.23.73a1.5 1.5 0 0 1-1.73-1.73Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function AppearanceIcon() {
|
||||
return (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M 12,0 C 5.3733333,0 0,5.3733333 0,12 c 0,6.626667 5.3733333,12 12,12 1.106667,0 2,-0.893333 2,-2 0,-0.52 -0.2,-0.986667 -0.52,-1.346667 -0.306667,-0.346666 -0.506667,-0.813333 -0.506667,-1.32 0,-1.106666 0.893334,-2 2,-2 h 2.36 C 21.013333,17.333333 24,14.346667 24,10.666667 24,4.7733333 18.626667,0 12,0 Z M 4.6666667,12 c -1.1066667,0 -2,-0.893333 -2,-2 0,-1.1066667 0.8933333,-2 2,-2 1.1066666,0 2,0.8933333 2,2 0,1.106667 -0.8933334,2 -2,2 z M 8.666667,6.6666667 c -1.106667,0 -2.0000003,-0.8933334 -2.0000003,-2 0,-1.1066667 0.8933333,-2 2.0000003,-2 1.106666,0 2,0.8933333 2,2 0,1.1066666 -0.893334,2 -2,2 z m 6.666666,0 c -1.106666,0 -2,-0.8933334 -2,-2 0,-1.1066667 0.893334,-2 2,-2 1.106667,0 2,0.8933333 2,2 0,1.1066666 -0.893333,2 -2,2 z m 4,5.3333333 c -1.106666,0 -2,-0.893333 -2,-2 0,-1.1066667 0.893334,-2 2,-2 1.106667,0 2,0.8933333 2,2 0,1.106667 -0.893333,2 -2,2 z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "BetterRoleContext",
|
||||
description: "Adds options to copy role color / edit role when right clicking roles in the user profile",
|
||||
authors: [Devs.Ven],
|
||||
|
||||
start() {
|
||||
// DeveloperMode needs to be enabled for the context menu to be shown
|
||||
TextAndImagesSettingsStores.DeveloperMode.updateSetting(true);
|
||||
},
|
||||
|
||||
contextMenus: {
|
||||
"dev-context"(children, { id }: { id: string; }) {
|
||||
const guild = getCurrentGuild();
|
||||
const role = guild?.roles[id];
|
||||
if (!role) return;
|
||||
|
||||
if (role.colorString) {
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
id="vc-copy-role-color"
|
||||
label="Copy Role Color"
|
||||
action={() => Clipboard.copy(role.colorString!)}
|
||||
icon={AppearanceIcon}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { ScreenshareIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { openImageModal } from "@utils/discord";
|
||||
|
@ -60,7 +60,7 @@ export const handleViewPreview = async ({ guildId, channelId, ownerId }: Applica
|
|||
openImageModal(previewUrl);
|
||||
};
|
||||
|
||||
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => () => {
|
||||
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => {
|
||||
const stream = ApplicationStreamingStore.getAnyStreamForUser(userId);
|
||||
if (!stream) return;
|
||||
|
||||
|
@ -89,12 +89,8 @@ export default definePlugin({
|
|||
name: "BiggerStreamPreview",
|
||||
description: "This plugin allows you to enlarge stream previews",
|
||||
authors: [Devs.phil],
|
||||
start: () => {
|
||||
addContextMenuPatch("user-context", userContextPatch);
|
||||
addContextMenuPatch("stream-context", streamContextPatch);
|
||||
},
|
||||
stop: () => {
|
||||
removeContextMenuPatch("user-context", userContextPatch);
|
||||
removeContextMenuPatch("stream-context", streamContextPatch);
|
||||
contextMenus: {
|
||||
"user-context": userContextPatch,
|
||||
"stream-context": streamContextPatch
|
||||
}
|
||||
});
|
||||
|
|
|
@ -140,11 +140,11 @@ export const defaultRules = [
|
|||
"tt_content",
|
||||
"lr@yandex.*",
|
||||
"redircnt@yandex.*",
|
||||
"feature@youtube.com",
|
||||
"kw@youtube.com",
|
||||
"si@youtube.com",
|
||||
"pp@youtube.com",
|
||||
"si@youtu.be",
|
||||
"feature@*.youtube.com",
|
||||
"kw@*.youtube.com",
|
||||
"si@*.youtube.com",
|
||||
"pp@*.youtube.com",
|
||||
"si@*.youtu.be",
|
||||
"wt_zmc",
|
||||
"utm_source",
|
||||
"utm_content",
|
||||
|
|
|
@ -12,7 +12,7 @@ import { Margins } from "@utils/margins";
|
|||
import { classes } from "@utils/misc";
|
||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||
import { Button, Forms, lodash as _, useStateFromStores } from "@webpack/common";
|
||||
import { Button, Forms, useStateFromStores } from "@webpack/common";
|
||||
|
||||
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
|
||||
|
||||
|
@ -200,8 +200,8 @@ function captureOne(str, regex) {
|
|||
return (result === null) ? null : result[1];
|
||||
}
|
||||
|
||||
function mapReject(arr, mapFunc, rejectFunc = _.isNull) {
|
||||
return _.reject(arr.map(mapFunc), rejectFunc);
|
||||
function mapReject(arr, mapFunc) {
|
||||
return arr.map(mapFunc).filter(Boolean);
|
||||
}
|
||||
|
||||
function updateColorVars(color: string) {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { LinkIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
@ -29,7 +29,7 @@ interface UserContextProps {
|
|||
user: User;
|
||||
}
|
||||
|
||||
const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => () => {
|
||||
const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => {
|
||||
if (!user) return;
|
||||
|
||||
children.push(
|
||||
|
@ -46,12 +46,7 @@ export default definePlugin({
|
|||
name: "CopyUserURLs",
|
||||
authors: [Devs.castdrian],
|
||||
description: "Adds a 'Copy User URL' option to the user context menu.",
|
||||
|
||||
start() {
|
||||
addContextMenuPatch("user-context", UserContextMenuPatch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch("user-context", UserContextMenuPatch);
|
||||
},
|
||||
contextMenus: {
|
||||
"user-context": UserContextMenuPatch
|
||||
}
|
||||
});
|
||||
|
|
|
@ -25,7 +25,6 @@ import definePlugin, { OptionType } from "@utils/types";
|
|||
import { maybePromptToUpdate } from "@utils/updater";
|
||||
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
|
||||
import { FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
|
||||
import type { ReactElement } from "react";
|
||||
|
||||
const CrashHandlerLogger = new Logger("CrashHandler");
|
||||
const { ModalStack, DraftManager, DraftType, closeExpressionPicker } = proxyLazyWebpack(() => {
|
||||
|
@ -57,13 +56,13 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
let crashCount: number = 0;
|
||||
let lastCrashTimestamp: number = 0;
|
||||
let shouldAttemptNextHandle = false;
|
||||
let hasCrashedOnce = false;
|
||||
let isRecovering = false;
|
||||
let shouldAttemptRecover = true;
|
||||
|
||||
export default definePlugin({
|
||||
name: "CrashHandler",
|
||||
description: "Utility plugin for handling and possibly recovering from Crashes without a restart",
|
||||
description: "Utility plugin for handling and possibly recovering from crashes without a restart",
|
||||
authors: [Devs.Nuckyz],
|
||||
enabledByDefault: true,
|
||||
|
||||
|
@ -73,61 +72,67 @@ export default definePlugin({
|
|||
{
|
||||
find: ".Messages.ERRORS_UNEXPECTED_CRASH",
|
||||
replacement: {
|
||||
match: /(?=this\.setState\()/,
|
||||
replace: "$self.handleCrash(this)||"
|
||||
match: /this\.setState\((.+?)\)/,
|
||||
replace: "$self.handleCrash(this,$1);"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
handleCrash(_this: ReactElement & { forceUpdate: () => void; }) {
|
||||
if (Date.now() - lastCrashTimestamp <= 1_000 && !shouldAttemptNextHandle) return true;
|
||||
handleCrash(_this: any, errorState: any) {
|
||||
_this.setState(errorState);
|
||||
|
||||
shouldAttemptNextHandle = false;
|
||||
// Already recovering, prevent error which happens more than once too fast to trigger another recover
|
||||
if (isRecovering) return;
|
||||
isRecovering = true;
|
||||
|
||||
if (++crashCount > 5) {
|
||||
// 1 ms timeout to avoid react breaking when re-rendering
|
||||
setTimeout(() => {
|
||||
try {
|
||||
showNotification({
|
||||
color: "#eed202",
|
||||
title: "Discord has crashed!",
|
||||
body: "Awn :( Discord has crashed more than five times, not attempting to recover.",
|
||||
noPersist: true,
|
||||
});
|
||||
// Prevent a crash loop with an error that could not be handled
|
||||
if (!shouldAttemptRecover) {
|
||||
try {
|
||||
showNotification({
|
||||
color: "#eed202",
|
||||
title: "Discord has crashed!",
|
||||
body: "Awn :( Discord has crashed two times rapidly, not attempting to recover.",
|
||||
noPersist: true
|
||||
});
|
||||
} catch { }
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
shouldAttemptRecover = false;
|
||||
// This is enough to avoid a crash loop
|
||||
setTimeout(() => shouldAttemptRecover = true, 500);
|
||||
} catch { }
|
||||
|
||||
lastCrashTimestamp = Date.now();
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (!hasCrashedOnce) {
|
||||
hasCrashedOnce = true;
|
||||
maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true);
|
||||
}
|
||||
} catch { }
|
||||
|
||||
setTimeout(() => crashCount--, 60_000);
|
||||
|
||||
try {
|
||||
if (crashCount === 1) maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true);
|
||||
|
||||
if (settings.store.attemptToPreventCrashes) {
|
||||
this.handlePreventCrash(_this);
|
||||
return true;
|
||||
try {
|
||||
if (settings.store.attemptToPreventCrashes) {
|
||||
this.handlePreventCrash(_this);
|
||||
}
|
||||
} catch (err) {
|
||||
CrashHandlerLogger.error("Failed to handle crash", err);
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (err) {
|
||||
CrashHandlerLogger.error("Failed to handle crash", err);
|
||||
return false;
|
||||
} finally {
|
||||
lastCrashTimestamp = Date.now();
|
||||
}
|
||||
}, 1);
|
||||
},
|
||||
|
||||
handlePreventCrash(_this: ReactElement & { forceUpdate: () => void; }) {
|
||||
if (Date.now() - lastCrashTimestamp >= 1_000) {
|
||||
try {
|
||||
showNotification({
|
||||
color: "#eed202",
|
||||
title: "Discord has crashed!",
|
||||
body: "Attempting to recover...",
|
||||
noPersist: true,
|
||||
});
|
||||
} catch { }
|
||||
}
|
||||
handlePreventCrash(_this: any) {
|
||||
try {
|
||||
showNotification({
|
||||
color: "#eed202",
|
||||
title: "Discord has crashed!",
|
||||
body: "Attempting to recover...",
|
||||
noPersist: true
|
||||
});
|
||||
} catch { }
|
||||
|
||||
try {
|
||||
const channelId = SelectedChannelStore.getChannelId();
|
||||
|
@ -176,9 +181,12 @@ export default definePlugin({
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Set isRecovering to false before setting the state to allow us to handle the next crash error correcty, in case it happens
|
||||
setImmediate(() => isRecovering = false);
|
||||
|
||||
try {
|
||||
shouldAttemptNextHandle = true;
|
||||
_this.forceUpdate();
|
||||
_this.setState({ error: null, info: null });
|
||||
} catch (err) {
|
||||
CrashHandlerLogger.debug("Failed to update crash handler component.", err);
|
||||
}
|
||||
|
|
|
@ -175,7 +175,7 @@ const settings = definePluginSettings({
|
|||
},
|
||||
startTime: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "Start timestamp (only for custom timestamp mode)",
|
||||
description: "Start timestamp in milisecond (only for custom timestamp mode)",
|
||||
onChange: onChange,
|
||||
disabled: isTimestampDisabled,
|
||||
isValid: (value: number) => {
|
||||
|
@ -185,7 +185,7 @@ const settings = definePluginSettings({
|
|||
},
|
||||
endTime: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "End timestamp (only for custom timestamp mode)",
|
||||
description: "End timestamp in milisecond (only for custom timestamp mode)",
|
||||
onChange: onChange,
|
||||
disabled: isTimestampDisabled,
|
||||
isValid: (value: number) => {
|
||||
|
@ -313,12 +313,12 @@ async function createActivity(): Promise<Activity | undefined> {
|
|||
switch (settings.store.timestampMode) {
|
||||
case TimestampMode.NOW:
|
||||
activity.timestamps = {
|
||||
start: Math.floor(Date.now() / 1000)
|
||||
start: Date.now()
|
||||
};
|
||||
break;
|
||||
case TimestampMode.TIME:
|
||||
activity.timestamps = {
|
||||
start: Math.floor(Date.now() / 1000) - (new Date().getHours() * 3600) - (new Date().getMinutes() * 60) - new Date().getSeconds()
|
||||
start: Date.now() - (new Date().getHours() * 3600 + new Date().getMinutes() * 60 + new Date().getSeconds()) * 1000
|
||||
};
|
||||
break;
|
||||
case TimestampMode.CUSTOM:
|
||||
|
|
|
@ -72,7 +72,7 @@ export default definePlugin({
|
|||
replacement: [
|
||||
// Add Decor avatar decoration hook to avatar decoration hook
|
||||
{
|
||||
match: /(?<=TryItOut:\i}\),)(?<=user:(\i).+?)/,
|
||||
match: /(?<=TryItOut:\i,guildId:\i}\),)(?<=user:(\i).+?)/,
|
||||
replace: "vcDecorAvatarDecoration=$self.useUserDecorAvatarDecoration($1),"
|
||||
},
|
||||
// Use added hook
|
||||
|
|
|
@ -16,18 +16,27 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { migratePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
migratePluginSettings("DisableCallIdle", "DisableDMCallIdle");
|
||||
export default definePlugin({
|
||||
name: "DisableDMCallIdle",
|
||||
description: "Disables automatically getting kicked from a DM voice call after 3 minutes.",
|
||||
name: "DisableCallIdle",
|
||||
description: "Disables automatically getting kicked from a DM voice call after 3 minutes and being moved to an AFK voice channel.",
|
||||
authors: [Devs.Nuckyz],
|
||||
patches: [
|
||||
{
|
||||
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
|
||||
replacement: {
|
||||
match: /(?<=function \i\(\){)(?=.{1,120}\.Messages\.BOT_CALL_IDLE_DISCONNECT)/,
|
||||
match: /,?(?=this\.idleTimeout=new \i\.Timeout)/,
|
||||
replace: ";return;"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "handleIdleUpdate(){",
|
||||
replacement: {
|
||||
match: /(?<=_initialize\(\){)/,
|
||||
replace: "return;"
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
|
@ -312,7 +312,7 @@ function isGifUrl(url: string) {
|
|||
return new URL(url).pathname.endsWith(".gif");
|
||||
}
|
||||
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||
const { favoriteableId, itemHref, itemSrc, favoriteableType } = props ?? {};
|
||||
|
||||
if (!favoriteableId) return;
|
||||
|
@ -341,7 +341,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) =
|
|||
findGroupChildrenByChildId("copy-link", children)?.push(menuItem);
|
||||
};
|
||||
|
||||
const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => () => {
|
||||
const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => {
|
||||
const { id, name, type } = props?.target?.dataset ?? {};
|
||||
if (!id) return;
|
||||
|
||||
|
@ -363,14 +363,8 @@ export default definePlugin({
|
|||
description: "Allows you to clone Emotes & Stickers to your own server (right click them)",
|
||||
tags: ["StickerCloner"],
|
||||
authors: [Devs.Ven, Devs.Nuckyz],
|
||||
|
||||
start() {
|
||||
addContextMenuPatch("message", messageContextMenuPatch);
|
||||
addContextMenuPatch("expression-picker", expressionPickerPatch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch("message", messageContextMenuPatch);
|
||||
removeContextMenuPatch("expression-picker", expressionPickerPatch);
|
||||
contextMenus: {
|
||||
"message": messageContextMenuPatch,
|
||||
"expression-picker": expressionPickerPatch
|
||||
}
|
||||
});
|
||||
|
|
|
@ -17,14 +17,14 @@
|
|||
*/
|
||||
|
||||
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
||||
import { getCurrentGuild } from "@utils/discord";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
|
||||
import { ChannelStore, EmojiStore, FluxDispatcher, lodash, Parser, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
|
||||
import { Alerts, ChannelStore, EmojiStore, FluxDispatcher, Forms, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
|
||||
import type { Message } from "discord-types/general";
|
||||
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
|
@ -51,8 +51,6 @@ const PreloadedUserSettingsActionCreators = proxyLazyWebpack(() => UserSettingsA
|
|||
const AppearanceSettingsActionCreators = proxyLazyWebpack(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass));
|
||||
const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators));
|
||||
|
||||
const USE_EXTERNAL_EMOJIS = 1n << 18n;
|
||||
const USE_EXTERNAL_STICKERS = 1n << 37n;
|
||||
|
||||
const enum EmojiIntentions {
|
||||
REACTION = 0,
|
||||
|
@ -108,6 +106,7 @@ const enum FakeNoticeType {
|
|||
const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/;
|
||||
const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./;
|
||||
const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/;
|
||||
const hyperLinkRegex = /\[.+?\]\((https?:\/\/.+?)\)/;
|
||||
|
||||
const settings = definePluginSettings({
|
||||
enableEmojiBypass: {
|
||||
|
@ -156,8 +155,33 @@ const settings = definePluginSettings({
|
|||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
},
|
||||
useHyperLinks: {
|
||||
description: "Whether to use hyperlinks when sending fake emojis and stickers",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true
|
||||
},
|
||||
hyperLinkText: {
|
||||
description: "What text the hyperlink should use. {{NAME}} will be replaced with the emoji/sticker name.",
|
||||
type: OptionType.STRING,
|
||||
default: "{{NAME}}"
|
||||
}
|
||||
});
|
||||
}).withPrivateSettings<{
|
||||
disableEmbedPermissionCheck: boolean;
|
||||
}>();
|
||||
|
||||
function hasPermission(channelId: string, permission: bigint) {
|
||||
const channel = ChannelStore.getChannel(channelId);
|
||||
|
||||
if (!channel || channel.isPrivate()) return true;
|
||||
|
||||
return PermissionStore.can(permission, channel);
|
||||
}
|
||||
|
||||
const hasExternalEmojiPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.USE_EXTERNAL_EMOJIS);
|
||||
const hasExternalStickerPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.USE_EXTERNAL_STICKERS);
|
||||
const hasEmbedPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.EMBED_LINKS);
|
||||
const hasAttachmentPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.ATTACH_FILES);
|
||||
|
||||
export default definePlugin({
|
||||
name: "FakeNitro",
|
||||
|
@ -345,8 +369,8 @@ export default definePlugin({
|
|||
predicate: () => settings.store.transformEmojis,
|
||||
replacement: {
|
||||
// Add the fake nitro emoji notice
|
||||
match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.+?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/,
|
||||
replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)`
|
||||
match: /(?<=emojiDescription:)(\i)(?<=\1=\i\((\i)\).+?)/,
|
||||
replace: (_, reactNode, props) => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!${props}?.fakeNitroNode?.fake)`
|
||||
}
|
||||
},
|
||||
// Allow using custom app icons
|
||||
|
@ -447,13 +471,23 @@ export default definePlugin({
|
|||
|
||||
trimContent(content: Array<any>) {
|
||||
const firstContent = content[0];
|
||||
if (typeof firstContent === "string") content[0] = firstContent.trimStart();
|
||||
if (content[0] === "") content.shift();
|
||||
if (typeof firstContent === "string") {
|
||||
content[0] = firstContent.trimStart();
|
||||
content[0] || content.shift();
|
||||
} else if (typeof firstContent?.props?.children === "string") {
|
||||
firstContent.props.children = firstContent.props.children.trimStart();
|
||||
firstContent.props.children || content.shift();
|
||||
}
|
||||
|
||||
const lastIndex = content.length - 1;
|
||||
const lastContent = content[lastIndex];
|
||||
if (typeof lastContent === "string") content[lastIndex] = lastContent.trimEnd();
|
||||
if (content[lastIndex] === "") content.pop();
|
||||
if (typeof lastContent === "string") {
|
||||
content[lastIndex] = lastContent.trimEnd();
|
||||
content[lastIndex] || content.pop();
|
||||
} else if (typeof lastContent?.props?.children === "string") {
|
||||
lastContent.props.children = lastContent.props.children.trimEnd();
|
||||
lastContent.props.children || content.pop();
|
||||
}
|
||||
},
|
||||
|
||||
clearEmptyArrayItems(array: Array<any>) {
|
||||
|
@ -465,7 +499,7 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
patchFakeNitroEmojisOrRemoveStickersLinks(content: Array<any>, inline: boolean) {
|
||||
// If content has more than one child or it's a single ReactElement like a header or list
|
||||
// If content has more than one child or it's a single ReactElement like a header, list or span
|
||||
if ((content.length > 1 || typeof content[0]?.type === "string") && !settings.store.transformCompoundSentence) return content;
|
||||
|
||||
let nextIndex = content.length;
|
||||
|
@ -551,13 +585,15 @@ export default definePlugin({
|
|||
for (const [index, child] of children.entries()) children[index] = modifyChild(child);
|
||||
|
||||
children = this.clearEmptyArrayItems(children);
|
||||
this.trimContent(children);
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
try {
|
||||
return modifyChildren(lodash.cloneDeep(content));
|
||||
const newContent = modifyChildren(lodash.cloneDeep(content));
|
||||
this.trimContent(newContent);
|
||||
|
||||
return newContent;
|
||||
} catch (err) {
|
||||
new Logger("FakeNitro").error(err);
|
||||
return content;
|
||||
|
@ -574,7 +610,7 @@ export default definePlugin({
|
|||
itemsToMaybePush.push(...message.attachments.filter(attachment => attachment.content_type === "image/gif").map(attachment => attachment.url));
|
||||
|
||||
for (const item of itemsToMaybePush) {
|
||||
if (!settings.store.transformCompoundSentence && !item.startsWith("http")) continue;
|
||||
if (!settings.store.transformCompoundSentence && !item.startsWith("http") && !hyperLinkRegex.test(item)) continue;
|
||||
|
||||
const imgMatch = item.match(fakeNitroStickerRegex);
|
||||
if (imgMatch) {
|
||||
|
@ -619,8 +655,7 @@ export default definePlugin({
|
|||
case "image": {
|
||||
if (
|
||||
!settings.store.transformCompoundSentence
|
||||
&& !contentItems.includes(embed.url!)
|
||||
&& !contentItems.includes(embed.image?.proxyURL!)
|
||||
&& !contentItems.some(item => item === embed.url! || item.match(hyperLinkRegex)?.[1] === embed.url!)
|
||||
) return false;
|
||||
|
||||
if (settings.store.transformEmojis) {
|
||||
|
@ -681,24 +716,8 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
|
||||
hasPermissionToUseExternalEmojis(channelId: string): boolean {
|
||||
const channel = ChannelStore.getChannel(channelId);
|
||||
|
||||
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true;
|
||||
|
||||
return PermissionStore.can(USE_EXTERNAL_EMOJIS, channel);
|
||||
},
|
||||
|
||||
hasPermissionToUseExternalStickers(channelId: string) {
|
||||
const channel = ChannelStore.getChannel(channelId);
|
||||
|
||||
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true;
|
||||
|
||||
return PermissionStore.can(USE_EXTERNAL_STICKERS, channel);
|
||||
},
|
||||
|
||||
getStickerLink(stickerId: string) {
|
||||
return `https://media.discordapp.net/stickers/${stickerId}.png?size=${Settings.plugins.FakeNitro.stickerSize}`;
|
||||
return `https://media.discordapp.net/stickers/${stickerId}.png?size=${settings.store.stickerSize}`;
|
||||
},
|
||||
|
||||
async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) {
|
||||
|
@ -707,7 +726,7 @@ export default definePlugin({
|
|||
const { frames, width, height } = await parseURL(stickerLink);
|
||||
|
||||
const gif = GIFEncoder();
|
||||
const resolution = Settings.plugins.FakeNitro.stickerSize;
|
||||
const resolution = settings.store.stickerSize;
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = resolution;
|
||||
|
@ -768,9 +787,38 @@ export default definePlugin({
|
|||
return (!origStr[offset] || /\s/.test(origStr[offset])) ? "" : " ";
|
||||
}
|
||||
|
||||
this.preSend = addPreSendListener((channelId, messageObj, extra) => {
|
||||
function cannotEmbedNotice() {
|
||||
return new Promise<boolean>(resolve => {
|
||||
Alerts.show({
|
||||
title: "Hold on!",
|
||||
body: <div>
|
||||
<Forms.FormText>
|
||||
You are trying to send/edit a message that contains a FakeNitro emoji or sticker,
|
||||
however you do not have permissions to embed links in the current channel.
|
||||
Are you sure you want to send this message? Your FakeNitro items will appear as a link only.
|
||||
</Forms.FormText>
|
||||
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>
|
||||
You can disable this notice in the plugin settings.
|
||||
</Forms.FormText>
|
||||
</div>,
|
||||
confirmText: "Send Anyway",
|
||||
cancelText: "Cancel",
|
||||
secondaryConfirmText: "Do not show again",
|
||||
onConfirm: () => resolve(true),
|
||||
onCloseCallback: () => setImmediate(() => resolve(false)),
|
||||
onConfirmSecondary() {
|
||||
settings.store.disableEmbedPermissionCheck = true;
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.preSend = addPreSendListener(async (channelId, messageObj, extra) => {
|
||||
const { guildId } = this;
|
||||
|
||||
let hasBypass = false;
|
||||
|
||||
stickerBypass: {
|
||||
if (!s.enableStickerBypass)
|
||||
break stickerBypass;
|
||||
|
@ -783,7 +831,7 @@ export default definePlugin({
|
|||
if ("pack_id" in sticker)
|
||||
break stickerBypass;
|
||||
|
||||
const canUseStickers = this.canUseStickers && this.hasPermissionToUseExternalStickers(channelId);
|
||||
const canUseStickers = this.canUseStickers && hasExternalStickerPerms(channelId);
|
||||
if (sticker.available !== false && (canUseStickers || sticker.guild_id === guildId))
|
||||
break stickerBypass;
|
||||
|
||||
|
@ -795,44 +843,78 @@ export default definePlugin({
|
|||
if (sticker.format_type === StickerType.GIF && link.includes(".png")) {
|
||||
link = link.replace(".png", ".gif");
|
||||
}
|
||||
|
||||
if (sticker.format_type === StickerType.APNG) {
|
||||
this.sendAnimatedSticker(link, sticker.id, channelId);
|
||||
if (!hasAttachmentPerms(channelId)) {
|
||||
Alerts.show({
|
||||
title: "Hold on!",
|
||||
body: <div>
|
||||
<Forms.FormText>
|
||||
You cannot send this message because it contains an animated FakeNitro sticker,
|
||||
and you do not have permissions to attach files in the current channel. Please remove the sticker to proceed.
|
||||
</Forms.FormText>
|
||||
</div>
|
||||
});
|
||||
} else {
|
||||
this.sendAnimatedSticker(link, sticker.id, channelId);
|
||||
}
|
||||
|
||||
return { cancel: true };
|
||||
} else {
|
||||
hasBypass = true;
|
||||
|
||||
const url = new URL(link);
|
||||
url.searchParams.set("name", sticker.name);
|
||||
|
||||
const linkText = s.hyperLinkText.replaceAll("{{NAME}}", sticker.name);
|
||||
|
||||
messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}${s.useHyperLinks ? `[${linkText}](${url})` : url}`;
|
||||
extra.stickers!.length = 0;
|
||||
messageObj.content += ` ${link}&name=${encodeURIComponent(sticker.name)}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.enableEmojiBypass) {
|
||||
const canUseEmotes = this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId);
|
||||
const canUseEmotes = this.canUseEmotes && hasExternalEmojiPerms(channelId);
|
||||
|
||||
for (const emoji of messageObj.validNonShortcutEmojis) {
|
||||
if (!emoji.require_colons) continue;
|
||||
if (emoji.available !== false && canUseEmotes) continue;
|
||||
if (emoji.guildId === guildId && !emoji.animated) continue;
|
||||
|
||||
hasBypass = true;
|
||||
|
||||
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
|
||||
const url = emoji.url.replace(/\?size=\d+/, "?" + new URLSearchParams({
|
||||
size: Settings.plugins.FakeNitro.emojiSize,
|
||||
name: encodeURIComponent(emoji.name)
|
||||
}));
|
||||
|
||||
const url = new URL(emoji.url);
|
||||
url.searchParams.set("size", s.emojiSize.toString());
|
||||
url.searchParams.set("name", emoji.name);
|
||||
|
||||
const linkText = s.hyperLinkText.replaceAll("{{NAME}}", emoji.name);
|
||||
|
||||
messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => {
|
||||
return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`;
|
||||
return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${linkText}](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (hasBypass && !s.disableEmbedPermissionCheck && !hasEmbedPerms(channelId)) {
|
||||
if (!await cannotEmbedNotice()) {
|
||||
return { cancel: true };
|
||||
}
|
||||
}
|
||||
|
||||
return { cancel: false };
|
||||
});
|
||||
|
||||
this.preEdit = addPreEditListener((channelId, __, messageObj) => {
|
||||
this.preEdit = addPreEditListener(async (channelId, __, messageObj) => {
|
||||
if (!s.enableEmojiBypass) return;
|
||||
|
||||
const canUseEmotes = this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId);
|
||||
|
||||
const { guildId } = this;
|
||||
|
||||
let hasBypass = false;
|
||||
|
||||
const canUseEmotes = this.canUseEmotes && hasExternalEmojiPerms(channelId);
|
||||
|
||||
messageObj.content = messageObj.content.replace(/(?<!\\)<a?:(?:\w+):(\d+)>/ig, (emojiStr, emojiId, offset, origStr) => {
|
||||
const emoji = EmojiStore.getCustomEmojiById(emojiId);
|
||||
if (emoji == null) return emojiStr;
|
||||
|
@ -840,12 +922,24 @@ export default definePlugin({
|
|||
if (emoji.available !== false && canUseEmotes) return emojiStr;
|
||||
if (emoji.guildId === guildId && !emoji.animated) return emojiStr;
|
||||
|
||||
const url = emoji.url.replace(/\?size=\d+/, "?" + new URLSearchParams({
|
||||
size: Settings.plugins.FakeNitro.emojiSize,
|
||||
name: encodeURIComponent(emoji.name)
|
||||
}));
|
||||
return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + emojiStr.length)}`;
|
||||
hasBypass = true;
|
||||
|
||||
const url = new URL(emoji.url);
|
||||
url.searchParams.set("size", s.emojiSize.toString());
|
||||
url.searchParams.set("name", emoji.name);
|
||||
|
||||
const linkText = s.hyperLinkText.replaceAll("{{NAME}}", emoji.name);
|
||||
|
||||
return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${linkText}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`;
|
||||
});
|
||||
|
||||
if (hasBypass && !s.disableEmbedPermissionCheck && !hasEmbedPerms(channelId)) {
|
||||
if (!await cannotEmbedNotice()) {
|
||||
return { cancel: true };
|
||||
}
|
||||
}
|
||||
|
||||
return { cancel: false };
|
||||
});
|
||||
},
|
||||
|
|
@ -16,8 +16,9 @@ app.on("browser-window-created", (_, win) => {
|
|||
|
||||
frame.executeJavaScript(`
|
||||
new MutationObserver(() => {
|
||||
let err = document.querySelector(".ytp-error-content-wrap-subreason span")?.textContent;
|
||||
if (err && err.includes("blocked it from display")) window.location.reload()
|
||||
if(
|
||||
document.querySelector('div.ytp-error-content-wrap-subreason a[href*="www.youtube.com/watch?v="]')
|
||||
) location.reload()
|
||||
}).observe(document.body, { childList: true, subtree:true });
|
||||
`);
|
||||
}
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
*/
|
||||
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Margins } from "@utils/margins";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { StatusSettingsStores, Tooltip } from "webpack/common";
|
||||
import { Button, Forms, showToast, StatusSettingsStores, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common";
|
||||
|
||||
const enum ActivitiesTypes {
|
||||
Game,
|
||||
|
@ -69,7 +71,113 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
|||
StatusSettingsStores.ShowCurrentGame.updateSetting(old => old);
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({}).withPrivateSettings<{
|
||||
function ImportCustomRPCComponent() {
|
||||
return (
|
||||
<Flex flexDirection="column">
|
||||
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>Import the application id of the CustomRPC plugin to the allowed list</Forms.FormText>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const id = Settings.plugins.CustomRPC?.appID as string | undefined;
|
||||
if (!id) {
|
||||
return showToast("CustomRPC application ID is not set.", Toasts.Type.FAILURE);
|
||||
}
|
||||
|
||||
const isAlreadyAdded = allowedIdsPushID?.(id);
|
||||
if (isAlreadyAdded) {
|
||||
showToast("CustomRPC application ID is already added.", Toasts.Type.FAILURE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Import CustomRPC ID
|
||||
</Button>
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
let allowedIdsPushID: ((id: string) => boolean) | null = null;
|
||||
|
||||
function AllowedIdsComponent(props: { setValue: (value: string) => void; }) {
|
||||
const [allowedIds, setAllowedIds] = useState<string>(settings.store.allowedIds ?? "");
|
||||
|
||||
allowedIdsPushID = (id: string) => {
|
||||
const currentIds = new Set(allowedIds.split(",").map(id => id.trim()).filter(Boolean));
|
||||
|
||||
const isAlreadyAdded = currentIds.has(id) || (currentIds.add(id), false);
|
||||
|
||||
const ids = Array.from(currentIds).join(", ");
|
||||
setAllowedIds(ids);
|
||||
props.setValue(ids);
|
||||
|
||||
return isAlreadyAdded;
|
||||
};
|
||||
|
||||
useEffect(() => () => {
|
||||
allowedIdsPushID = null;
|
||||
}, []);
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
setAllowedIds(newValue);
|
||||
props.setValue(newValue);
|
||||
}
|
||||
|
||||
return (
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle tag="h3">Allowed List</Forms.FormTitle>
|
||||
<Forms.FormText className={Margins.bottom8} type={Forms.FormText.Types.DESCRIPTION}>Comma separated list of activity IDs to allow (Useful for allowing RPC activities and CustomRPC)</Forms.FormText>
|
||||
<TextInput
|
||||
type="text"
|
||||
value={allowedIds}
|
||||
onChange={handleChange}
|
||||
placeholder="235834946571337729, 343383572805058560"
|
||||
/>
|
||||
</Forms.FormSection>
|
||||
);
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
importCustomRPC: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "",
|
||||
component: () => <ImportCustomRPCComponent />
|
||||
},
|
||||
allowedIds: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "",
|
||||
default: "",
|
||||
onChange(newValue: string) {
|
||||
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
|
||||
settings.store.allowedIds = Array.from(ids).join(", ");
|
||||
},
|
||||
component: props => <AllowedIdsComponent setValue={props.setValue} />
|
||||
},
|
||||
ignorePlaying: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Ignore all playing activities (These are usually game and RPC activities)",
|
||||
default: false
|
||||
},
|
||||
ignoreStreaming: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Ignore all streaming activities",
|
||||
default: false
|
||||
},
|
||||
ignoreListening: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Ignore all listening activities (These are usually spotify activities)",
|
||||
default: false
|
||||
},
|
||||
ignoreWatching: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Ignore all watching activities",
|
||||
default: false
|
||||
},
|
||||
ignoreCompeting: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Ignore all competing activities (These are normally special game activities)",
|
||||
default: false
|
||||
}
|
||||
}).withPrivateSettings<{
|
||||
ignoredActivities: IgnoredActivity[];
|
||||
}>();
|
||||
|
||||
|
@ -77,10 +185,26 @@ function getIgnoredActivities() {
|
|||
return settings.store.ignoredActivities ??= [];
|
||||
}
|
||||
|
||||
function isActivityTypeIgnored(type: number, id?: string) {
|
||||
if (id && settings.store.allowedIds.includes(id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 0: return settings.store.ignorePlaying;
|
||||
case 1: return settings.store.ignoreStreaming;
|
||||
case 2: return settings.store.ignoreListening;
|
||||
case 3: return settings.store.ignoreWatching;
|
||||
case 5: return settings.store.ignoreCompeting;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "IgnoreActivities",
|
||||
authors: [Devs.Nuckyz],
|
||||
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are ignored from the Registered Games and Activities tabs.",
|
||||
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.",
|
||||
|
||||
settings,
|
||||
|
||||
|
@ -141,13 +265,17 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) {
|
||||
if (props.type === 0 || props.type === 3) {
|
||||
if (props.application_id != null) return !getIgnoredActivities().some(activity => activity.id === props.application_id);
|
||||
else {
|
||||
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
||||
if (exePath) return !getIgnoredActivities().some(activity => activity.id === exePath);
|
||||
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.allowedIds.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 true;
|
||||
},
|
||||
|
||||
|
|
|
@ -123,14 +123,13 @@ export const Magnifier: React.FC<MagnifierProps> = ({ instance, size: initialSiz
|
|||
waitFor(() => instance.state.readyState === "READY", () => {
|
||||
const elem = document.getElementById(ELEMENT_ID) as HTMLDivElement;
|
||||
element.current = elem;
|
||||
elem.firstElementChild!.setAttribute("draggable", "false");
|
||||
elem.querySelector("img,video")?.setAttribute("draggable", "false");
|
||||
if (instance.props.animated) {
|
||||
originalVideoElementRef.current = elem!.querySelector("video")!;
|
||||
originalVideoElementRef.current.addEventListener("timeupdate", syncVideos);
|
||||
setReady(true);
|
||||
} else {
|
||||
setReady(true);
|
||||
}
|
||||
|
||||
setReady(true);
|
||||
});
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
document.addEventListener("keyup", onKeyUp);
|
||||
|
@ -155,7 +154,9 @@ export const Magnifier: React.FC<MagnifierProps> = ({ instance, size: initialSiz
|
|||
|
||||
if (!ready) return null;
|
||||
|
||||
const box = element.current!.getBoundingClientRect();
|
||||
const box = element.current?.getBoundingClientRect();
|
||||
|
||||
if (!box) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { makeRange } from "@components/PluginSettings/components";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { debounce } from "@utils/debounce";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { ContextMenuApi, Menu, React, ReactDOM } from "@webpack/common";
|
||||
import { Menu, React, ReactDOM } from "@webpack/common";
|
||||
import type { Root } from "react-dom/client";
|
||||
|
||||
import { Magnifier, MagnifierProps } from "./components/Magnifier";
|
||||
|
@ -80,25 +80,25 @@ export const settings = definePluginSettings({
|
|||
});
|
||||
|
||||
|
||||
const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
|
||||
const imageContextMenuPatch: NavContextMenuPatchCallback = children => {
|
||||
const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]);
|
||||
|
||||
children.push(
|
||||
<Menu.MenuGroup id="image-zoom">
|
||||
<Menu.MenuCheckboxItem
|
||||
id="vc-square"
|
||||
label="Square Lens"
|
||||
checked={settings.store.square}
|
||||
checked={square}
|
||||
action={() => {
|
||||
settings.store.square = !settings.store.square;
|
||||
ContextMenuApi.closeContextMenu();
|
||||
settings.store.square = !square;
|
||||
}}
|
||||
/>
|
||||
<Menu.MenuCheckboxItem
|
||||
id="vc-nearest-neighbour"
|
||||
label="Nearest Neighbour"
|
||||
checked={settings.store.nearestNeighbour}
|
||||
checked={nearestNeighbour}
|
||||
action={() => {
|
||||
settings.store.nearestNeighbour = !settings.store.nearestNeighbour;
|
||||
ContextMenuApi.closeContextMenu();
|
||||
settings.store.nearestNeighbour = !nearestNeighbour;
|
||||
}}
|
||||
/>
|
||||
<Menu.MenuControlItem
|
||||
|
@ -171,7 +171,7 @@ export default definePlugin({
|
|||
find: "handleImageLoad=",
|
||||
replacement: [
|
||||
{
|
||||
match: /showThumbhashPlaceholder:\i,/,
|
||||
match: /placeholderVersion:\i,/,
|
||||
replace: "...$self.makeProps(this),$&"
|
||||
},
|
||||
|
||||
|
@ -196,6 +196,9 @@ export default definePlugin({
|
|||
],
|
||||
|
||||
settings,
|
||||
contextMenus: {
|
||||
"image-context": imageContextMenuPatch
|
||||
},
|
||||
|
||||
// to stop from rendering twice /shrug
|
||||
currentMagnifierElement: null as React.FunctionComponentElement<MagnifierProps & JSX.IntrinsicAttributes> | null,
|
||||
|
@ -245,7 +248,6 @@ export default definePlugin({
|
|||
|
||||
start() {
|
||||
enableStyle(styles);
|
||||
addContextMenuPatch("image-context", imageContextMenuPatch);
|
||||
this.element = document.createElement("div");
|
||||
this.element.classList.add("MagnifierContainer");
|
||||
document.body.appendChild(this.element);
|
||||
|
@ -256,6 +258,5 @@ export default definePlugin({
|
|||
// so componenetWillUnMount gets called if Magnifier component is still alive
|
||||
this.root && this.root.unmount();
|
||||
this.element?.remove();
|
||||
removeContextMenuPatch("image-context", imageContextMenuPatch);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
box-shadow: inset 0 0 10px 2px grey;
|
||||
filter: drop-shadow(0 0 2px grey);
|
||||
pointer-events: none;
|
||||
|
||||
/* negate the border offsetting the lens */
|
||||
margin: -2px;
|
||||
}
|
||||
|
||||
.vc-imgzoom-square {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
import { registerCommand, unregisterCommand } from "@api/Commands";
|
||||
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { Patch, Plugin, StartAt } from "@utils/types";
|
||||
|
@ -119,7 +120,7 @@ export function startDependenciesRecursive(p: Plugin) {
|
|||
}
|
||||
|
||||
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
|
||||
const { name, commands, flux } = p;
|
||||
const { name, commands, flux, contextMenus } = p;
|
||||
|
||||
if (p.start) {
|
||||
logger.info("Starting plugin", name);
|
||||
|
@ -154,11 +155,17 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
|||
}
|
||||
}
|
||||
|
||||
if (contextMenus) {
|
||||
for (const navId in contextMenus) {
|
||||
addContextMenuPatch(navId, contextMenus[navId]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}, p => `startPlugin ${p.name}`);
|
||||
|
||||
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
||||
const { name, commands, flux } = p;
|
||||
const { name, commands, flux, contextMenus } = p;
|
||||
if (p.stop) {
|
||||
logger.info("Stopping plugin", name);
|
||||
if (!p.started) {
|
||||
|
@ -192,5 +199,11 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
|
|||
}
|
||||
}
|
||||
|
||||
if (contextMenus) {
|
||||
for (const navId in contextMenus) {
|
||||
removeContextMenuPatch(navId, contextMenus[navId]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}, p => `stopPlugin ${p.name}`);
|
||||
|
|
|
@ -16,13 +16,14 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton } from "@api/ChatButtons";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getStegCloak } from "@utils/dependencies";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common";
|
||||
import { ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
import { buildDecModal } from "./components/DecryptionModal";
|
||||
|
@ -64,54 +65,31 @@ function Indicator() {
|
|||
|
||||
}
|
||||
|
||||
function ChatBarIcon(chatBoxProps: {
|
||||
type: {
|
||||
analyticsName: string;
|
||||
};
|
||||
}) {
|
||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||
if (!isMainChat) return null;
|
||||
|
||||
return (
|
||||
<Tooltip text="Encrypt Message">
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
// size="" = Button.Sizes.NONE
|
||||
/*
|
||||
many themes set "> button" to display: none, as the gift button is
|
||||
the only directly descending button (all the other elements are divs.)
|
||||
Thus, wrap in a div here to avoid getting hidden by that.
|
||||
flex is for some reason necessary as otherwise the button goes flying off
|
||||
*/
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
aria-haspopup="dialog"
|
||||
aria-label="Encrypt Message"
|
||||
size=""
|
||||
look={ButtonLooks.BLANK}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
innerClassName={ButtonWrapperClasses.button}
|
||||
onClick={() => buildEncModal()}
|
||||
style={{ padding: "0 2px", scale: "0.9" }}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
<svg
|
||||
aria-hidden
|
||||
role="img"
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox={"0 0 64 64"}
|
||||
style={{ scale: "1.1" }}
|
||||
>
|
||||
<path fill="currentColor" d="M 32 9 C 24.832 9 19 14.832 19 22 L 19 27.347656 C 16.670659 28.171862 15 30.388126 15 33 L 15 49 C 15 52.314 17.686 55 21 55 L 43 55 C 46.314 55 49 52.314 49 49 L 49 33 C 49 30.388126 47.329341 28.171862 45 27.347656 L 45 22 C 45 14.832 39.168 9 32 9 z M 32 13 C 36.963 13 41 17.038 41 22 L 41 27 L 23 27 L 23 22 C 23 17.038 27.037 13 32 13 z" />
|
||||
</svg>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Tooltip >
|
||||
<ChatBarButton
|
||||
tooltip="Encrypt Message"
|
||||
onClick={() => buildEncModal()}
|
||||
|
||||
buttonProps={{
|
||||
"aria-haspopup": "dialog",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
aria-hidden
|
||||
role="img"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox={"0 0 64 64"}
|
||||
style={{ scale: "1.39", translate: "0 -1px" }}
|
||||
>
|
||||
<path fill="currentColor" d="M 32 9 C 24.832 9 19 14.832 19 22 L 19 27.347656 C 16.670659 28.171862 15 30.388126 15 33 L 15 49 C 15 52.314 17.686 55 21 55 L 43 55 C 46.314 55 49 52.314 49 49 L 49 33 C 49 30.388126 47.329341 28.171862 45 27.347656 L 45 22 C 45 14.832 39.168 9 32 9 z M 32 13 C 36.963 13 41 17.038 41 22 L 41 27 L 23 27 L 23 22 C 23 17.038 27.037 13 32 13 z" />
|
||||
</svg>
|
||||
</ChatBarButton>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const settings = definePluginSettings({
|
||||
savedPasswords: {
|
||||
|
@ -125,7 +103,7 @@ export default definePlugin({
|
|||
name: "InvisibleChat",
|
||||
description: "Encrypt your Messages in a non-suspicious way!",
|
||||
authors: [Devs.SammCheese],
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI"],
|
||||
patches: [
|
||||
{
|
||||
// Indicator
|
||||
|
@ -135,13 +113,6 @@ export default definePlugin({
|
|||
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "ChannelTextAreaButtons",
|
||||
replacement: {
|
||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
EMBED_API_URL: "https://embed.sammcheese.net",
|
||||
|
@ -151,10 +122,7 @@ export default definePlugin({
|
|||
),
|
||||
settings,
|
||||
async start() {
|
||||
const { default: StegCloak } = await getStegCloak();
|
||||
steggo = new StegCloak(true, false);
|
||||
|
||||
addButton("invDecrypt", message => {
|
||||
addButton("InvisibleChat", message => {
|
||||
return this.INV_REGEX.test(message?.content)
|
||||
? {
|
||||
label: "Decrypt Message",
|
||||
|
@ -170,10 +138,16 @@ export default definePlugin({
|
|||
}
|
||||
: null;
|
||||
});
|
||||
|
||||
addChatBarButton("InvisibleChat", ChatBarIcon);
|
||||
|
||||
const { default: StegCloak } = await getStegCloak();
|
||||
steggo = new StegCloak(true, false);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeButton("invDecrypt");
|
||||
removeButton("InvisibleChat");
|
||||
removeButton("InvisibleChat");
|
||||
},
|
||||
|
||||
// Gets the Embed of a Link
|
||||
|
@ -216,7 +190,6 @@ export default definePlugin({
|
|||
});
|
||||
},
|
||||
|
||||
chatBarIcon: ErrorBoundary.wrap(ChatBarIcon, { noop: true }),
|
||||
popOverIcon: () => <PopOverIcon />,
|
||||
indicator: ErrorBoundary.wrap(Indicator, { noop: true })
|
||||
});
|
||||
|
|
|
@ -170,6 +170,11 @@ const settings = definePluginSettings({
|
|||
}
|
||||
],
|
||||
},
|
||||
showLastFmLogo: {
|
||||
description: "show the Last.fm logo by the album cover",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
|
@ -276,8 +281,10 @@ export default definePlugin({
|
|||
{
|
||||
large_image: await getApplicationAsset(largeImage),
|
||||
large_text: trackData.album || undefined,
|
||||
small_image: await getApplicationAsset("lastfm-small"),
|
||||
small_text: "Last.fm",
|
||||
...(settings.store.showLastFmLogo && {
|
||||
small_image: await getApplicationAsset("lastfm-small"),
|
||||
small_text: "Last.fm"
|
||||
}),
|
||||
} : {
|
||||
large_image: await getApplicationAsset("lastfm-large"),
|
||||
large_text: trackData.album || undefined,
|
||||
|
|
66
src/plugins/memberCount/MemberCount.tsx
Normal file
66
src/plugins/memberCount/MemberCount.tsx
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { getCurrentChannel } from "@utils/discord";
|
||||
import { SelectedChannelStore, Tooltip, useEffect, useStateFromStores } from "@webpack/common";
|
||||
|
||||
import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat } from ".";
|
||||
import { OnlineMemberCountStore } from "./OnlineMemberCountStore";
|
||||
|
||||
export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) {
|
||||
const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
|
||||
|
||||
const guildId = isTooltip ? tooltipGuildId! : currentChannel.guild_id;
|
||||
|
||||
const totalCount = useStateFromStores(
|
||||
[GuildMemberCountStore],
|
||||
() => GuildMemberCountStore.getMemberCount(guildId)
|
||||
);
|
||||
|
||||
let onlineCount = useStateFromStores(
|
||||
[OnlineMemberCountStore],
|
||||
() => OnlineMemberCountStore.getCount(guildId)
|
||||
);
|
||||
|
||||
const { groups } = useStateFromStores(
|
||||
[ChannelMemberStore],
|
||||
() => ChannelMemberStore.getProps(guildId, currentChannel.id)
|
||||
);
|
||||
|
||||
if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) {
|
||||
onlineCount = groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
OnlineMemberCountStore.ensureCount(guildId);
|
||||
}, [guildId]);
|
||||
|
||||
if (totalCount == null)
|
||||
return null;
|
||||
|
||||
const formattedOnlineCount = onlineCount != null ? numberFormat(onlineCount) : "?";
|
||||
|
||||
return (
|
||||
<div className={cl("widget", { tooltip: isTooltip, "member-list": !isTooltip })}>
|
||||
<Tooltip text={`${formattedOnlineCount} online in this channel`} position="bottom">
|
||||
{props => (
|
||||
<div {...props}>
|
||||
<span className={cl("online-dot")} />
|
||||
<span className={cl("online")}>{formattedOnlineCount}</span>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
<Tooltip text={`${numberFormat(totalCount)} total server members`} position="bottom">
|
||||
{props => (
|
||||
<div {...props}>
|
||||
<span className={cl("total-dot")} />
|
||||
<span className={cl("total")}>{numberFormat(totalCount)}</span>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
52
src/plugins/memberCount/OnlineMemberCountStore.ts
Normal file
52
src/plugins/memberCount/OnlineMemberCountStore.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { sleep } from "@utils/misc";
|
||||
import { Queue } from "@utils/Queue";
|
||||
import { Flux, FluxDispatcher, GuildChannelStore, PrivateChannelsStore } from "@webpack/common";
|
||||
|
||||
export const OnlineMemberCountStore = proxyLazy(() => {
|
||||
const preloadQueue = new Queue();
|
||||
|
||||
const onlineMemberMap = new Map<string, number>();
|
||||
|
||||
class OnlineMemberCountStore extends Flux.Store {
|
||||
getCount(guildId: string) {
|
||||
return onlineMemberMap.get(guildId);
|
||||
}
|
||||
|
||||
async _ensureCount(guildId: string) {
|
||||
if (onlineMemberMap.has(guildId)) return;
|
||||
|
||||
await PrivateChannelsStore.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id);
|
||||
}
|
||||
|
||||
ensureCount(guildId: string) {
|
||||
if (onlineMemberMap.has(guildId)) return;
|
||||
|
||||
preloadQueue.push(() =>
|
||||
this._ensureCount(guildId)
|
||||
.then(
|
||||
() => sleep(200),
|
||||
() => sleep(200)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new OnlineMemberCountStore(FluxDispatcher, {
|
||||
GUILD_MEMBER_LIST_UPDATE({ guildId, groups }: { guildId: string, groups: { count: number; id: string; }[]; }) {
|
||||
onlineMemberMap.set(
|
||||
guildId,
|
||||
groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0)
|
||||
);
|
||||
},
|
||||
ONLINE_GUILD_MEMBER_COUNT_UPDATE({ guildId, count }) {
|
||||
onlineMemberMap.set(guildId, count);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -16,101 +16,66 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getCurrentChannel } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { SelectedChannelStore, Tooltip, useStateFromStores } from "@webpack/common";
|
||||
import { FluxStore } from "@webpack/types";
|
||||
|
||||
const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; };
|
||||
const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
|
||||
import { MemberCount } from "./MemberCount";
|
||||
|
||||
export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; };
|
||||
export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
|
||||
getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; };
|
||||
};
|
||||
|
||||
const settings = definePluginSettings({
|
||||
toolTip: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "If the member count should be displayed on the server tooltip",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
},
|
||||
memberList: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "If the member count should be displayed on the member list",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
}
|
||||
});
|
||||
|
||||
const sharedIntlNumberFormat = new Intl.NumberFormat();
|
||||
const numberFormat = (value: number) => sharedIntlNumberFormat.format(value);
|
||||
|
||||
function MemberCount() {
|
||||
const { id: channelId, guild_id: guildId } = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
|
||||
const { groups } = useStateFromStores(
|
||||
[ChannelMemberStore],
|
||||
() => ChannelMemberStore.getProps(guildId, channelId)
|
||||
);
|
||||
const total = useStateFromStores(
|
||||
[GuildMemberCountStore],
|
||||
() => GuildMemberCountStore.getMemberCount(guildId)
|
||||
);
|
||||
|
||||
if (total == null)
|
||||
return null;
|
||||
|
||||
const online =
|
||||
(groups.length === 1 && groups[0].id === "unknown")
|
||||
? 0
|
||||
: groups.reduce((count, curr) => count + (curr.id === "offline" ? 0 : curr.count), 0);
|
||||
|
||||
return (
|
||||
<Flex id="vc-membercount" style={{
|
||||
marginTop: "1em",
|
||||
paddingInline: "1em",
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
gap: 0
|
||||
}}>
|
||||
<Tooltip text={`${numberFormat(online)} online in this channel`} position="bottom">
|
||||
{props => (
|
||||
<div {...props}>
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: "var(--green-360)",
|
||||
width: "12px",
|
||||
height: "12px",
|
||||
borderRadius: "50%",
|
||||
display: "inline-block",
|
||||
marginRight: "0.5em"
|
||||
}}
|
||||
/>
|
||||
<span style={{ color: "var(--green-360)" }}>{numberFormat(online)}</span>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
<Tooltip text={`${numberFormat(total)} total server members`} position="bottom">
|
||||
{props => (
|
||||
<div {...props}>
|
||||
<span
|
||||
style={{
|
||||
width: "6px",
|
||||
height: "6px",
|
||||
borderRadius: "50%",
|
||||
border: "3px solid var(--primary-400)",
|
||||
display: "inline-block",
|
||||
marginRight: "0.5em",
|
||||
marginLeft: "1em"
|
||||
}}
|
||||
/>
|
||||
<span style={{ color: "var(--primary-400)" }}>{numberFormat(total)}</span>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
export const numberFormat = (value: number) => sharedIntlNumberFormat.format(value);
|
||||
export const cl = classNameFactory("vc-membercount-");
|
||||
|
||||
export default definePlugin({
|
||||
name: "MemberCount",
|
||||
description: "Shows the amount of online & total members in the server member list",
|
||||
description: "Shows the amount of online & total members in the server member list and tooltip",
|
||||
authors: [Devs.Ven, Devs.Commandtechno],
|
||||
settings,
|
||||
|
||||
patches: [{
|
||||
find: "{isSidebarVisible:",
|
||||
replacement: {
|
||||
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
||||
replace: ":[$1?.startsWith('members')?$self.render():null,$2"
|
||||
patches: [
|
||||
{
|
||||
find: "{isSidebarVisible:",
|
||||
replacement: {
|
||||
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
||||
replace: ":[$1?.startsWith('members')?$self.render():null,$2"
|
||||
},
|
||||
predicate: () => settings.store.memberList
|
||||
},
|
||||
{
|
||||
find: ".invitesDisabledTooltip",
|
||||
replacement: {
|
||||
match: /(?<=\.VIEW_AS_ROLES_MENTIONS_WARNING.{0,100})]/,
|
||||
replace: ",$self.renderTooltip(arguments[0].guild)]"
|
||||
},
|
||||
predicate: () => settings.store.toolTip
|
||||
}
|
||||
}],
|
||||
|
||||
render: ErrorBoundary.wrap(MemberCount, { noop: true })
|
||||
],
|
||||
render: ErrorBoundary.wrap(MemberCount, { noop: true }),
|
||||
renderTooltip: ErrorBoundary.wrap(guild => <MemberCount isTooltip tooltipGuildId={guild.id} />, { noop: true })
|
||||
});
|
||||
|
|
44
src/plugins/memberCount/style.css
Normal file
44
src/plugins/memberCount/style.css
Normal file
|
@ -0,0 +1,44 @@
|
|||
.vc-membercount-widget {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
|
||||
--color-online: var(--green-360);
|
||||
--color-total: var(--primary-400);
|
||||
}
|
||||
|
||||
.vc-membercount-tooltip {
|
||||
margin-top: 0.25em;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.vc-membercount-member-list {
|
||||
justify-content: center;
|
||||
margin-top: 1em;
|
||||
padding-inline: 1em;
|
||||
}
|
||||
|
||||
.vc-membercount-online {
|
||||
color: var(--color-online);
|
||||
}
|
||||
|
||||
.vc-membercount-total {
|
||||
color: var(--color-total);
|
||||
}
|
||||
|
||||
.vc-membercount-online-dot {
|
||||
background-color: var(--color-online);
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.vc-membercount-total-dot {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid var(--color-total);
|
||||
margin: 0 0.5em 0 1em;
|
||||
}
|
|
@ -29,15 +29,17 @@ import {
|
|||
ChannelStore,
|
||||
FluxDispatcher,
|
||||
GuildStore,
|
||||
IconUtils,
|
||||
MessageStore,
|
||||
Parser,
|
||||
PermissionsBits,
|
||||
PermissionStore,
|
||||
RestAPI,
|
||||
Text,
|
||||
TextAndImagesSettingsStores,
|
||||
UserStore
|
||||
} from "@webpack/common";
|
||||
import { Channel, Guild, Message } from "discord-types/general";
|
||||
import { Channel, Message } from "discord-types/general";
|
||||
|
||||
const messageCache = new Map<string, {
|
||||
message?: Message;
|
||||
|
@ -49,8 +51,9 @@ const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageCo
|
|||
const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)");
|
||||
|
||||
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
||||
const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor");
|
||||
|
||||
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
|
||||
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(?:\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
|
||||
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
|
||||
|
||||
interface Attachment {
|
||||
|
@ -63,7 +66,6 @@ interface Attachment {
|
|||
interface MessageEmbedProps {
|
||||
message: Message;
|
||||
channel: Channel;
|
||||
guildID: string;
|
||||
}
|
||||
|
||||
const messageFetchQueue = new Queue();
|
||||
|
@ -226,19 +228,19 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
|||
|
||||
let match = null as RegExpMatchArray | null;
|
||||
while ((match = messageLinkRegex.exec(message.content!)) !== null) {
|
||||
const [_, guildID, channelID, messageID] = match;
|
||||
const [_, channelID, messageID] = match;
|
||||
if (embeddedBy.includes(messageID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const linkedChannel = ChannelStore.getChannel(channelID);
|
||||
if (!linkedChannel || (guildID !== "@me" && !PermissionStore.can(1024n /* view channel */, linkedChannel))) {
|
||||
if (!linkedChannel || (!linkedChannel.isPrivate() && !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, linkedChannel))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { listMode, idList } = settings.store;
|
||||
|
||||
const isListed = [guildID, channelID, message.author.id].some(id => id && idList.includes(id));
|
||||
const isListed = [linkedChannel.guild_id, channelID, message.author.id].some(id => id && idList.includes(id));
|
||||
|
||||
if (listMode === "blacklist" && isListed) continue;
|
||||
if (listMode === "whitelist" && !isListed) continue;
|
||||
|
@ -265,8 +267,7 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
|||
|
||||
const messageProps: MessageEmbedProps = {
|
||||
message: withEmbeddedBy(linkedMessage, [...embeddedBy, message.id]),
|
||||
channel: linkedChannel,
|
||||
guildID
|
||||
channel: linkedChannel
|
||||
};
|
||||
|
||||
const type = settings.store.automodEmbeds;
|
||||
|
@ -280,59 +281,64 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
|||
return accessories.length ? <>{accessories}</> : null;
|
||||
}
|
||||
|
||||
function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbedProps): JSX.Element | null {
|
||||
const isDM = guildID === "@me";
|
||||
function getChannelLabelAndIconUrl(channel: Channel) {
|
||||
if (channel.isDM()) return ["Direct Message", IconUtils.getUserAvatarURL(UserStore.getUser(channel.recipients[0]))];
|
||||
if (channel.isGroupDM()) return ["Group DM", IconUtils.getChannelIconURL(channel)];
|
||||
return ["Server", IconUtils.getGuildIconURL(GuildStore.getGuild(channel.guild_id))];
|
||||
}
|
||||
|
||||
const guild = !isDM && GuildStore.getGuild(channel.guild_id);
|
||||
function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null {
|
||||
const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]);
|
||||
|
||||
const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel);
|
||||
|
||||
return <Embed
|
||||
embed={{
|
||||
rawDescription: "",
|
||||
color: "var(--background-secondary)",
|
||||
author: {
|
||||
name: <Text variant="text-xs/medium" tag="span">
|
||||
<span>{isDM ? "Direct Message - " : (guild as Guild).name + " - "}</span>
|
||||
{isDM
|
||||
? Parser.parse(`<@${dmReceiver.id}>`)
|
||||
: Parser.parse(`<#${channel.id}>`)
|
||||
}
|
||||
</Text>,
|
||||
iconProxyURL: guild
|
||||
? `https://${window.GLOBAL_ENV.CDN_HOST}/icons/${guild.id}/${guild.icon}.png`
|
||||
: `https://${window.GLOBAL_ENV.CDN_HOST}/avatars/${dmReceiver.id}/${dmReceiver.avatar}`
|
||||
}
|
||||
}}
|
||||
renderDescription={() => (
|
||||
<div key={message.id} className={classes(SearchResultClasses.message, settings.store.messageBackgroundColor && SearchResultClasses.searchResult)}>
|
||||
<ChannelMessage
|
||||
id={`message-link-embeds-${message.id}`}
|
||||
message={message}
|
||||
channel={channel}
|
||||
subscribeToComponentDispatch={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>;
|
||||
return (
|
||||
<Embed
|
||||
embed={{
|
||||
rawDescription: "",
|
||||
color: "var(--background-secondary)",
|
||||
author: {
|
||||
name: <Text variant="text-xs/medium" tag="span">
|
||||
<span>{channelLabel} - </span>
|
||||
{Parser.parse(channel.isDM() ? `<@${dmReceiver.id}>` : `<#${channel.id}>`)}
|
||||
</Text>,
|
||||
iconProxyURL: iconUrl
|
||||
}
|
||||
}}
|
||||
renderDescription={() => (
|
||||
<div key={message.id} className={classes(SearchResultClasses.message, settings.store.messageBackgroundColor && SearchResultClasses.searchResult)}>
|
||||
<ChannelMessage
|
||||
id={`message-link-embeds-${message.id}`}
|
||||
message={message}
|
||||
channel={channel}
|
||||
subscribeToComponentDispatch={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
||||
const { message, channel, guildID } = props;
|
||||
const { message, channel } = props;
|
||||
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting();
|
||||
const isDM = guildID === "@me";
|
||||
const images = getImages(message);
|
||||
const { parse } = Parser;
|
||||
|
||||
const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel);
|
||||
|
||||
return <AutoModEmbed
|
||||
channel={channel}
|
||||
childrenAccessories={
|
||||
<Text color="text-muted" variant="text-xs/medium" tag="span">
|
||||
{isDM
|
||||
? parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`)
|
||||
: parse(`<#${channel.id}>`)
|
||||
}
|
||||
<span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span>
|
||||
<Text color="text-muted" variant="text-xs/medium" tag="span" className={`${EmbedClasses.embedAuthor} ${EmbedClasses.embedMargin}`}>
|
||||
{iconUrl && <img src={iconUrl} className={EmbedClasses.embedAuthorIcon} alt="" />}
|
||||
<span>
|
||||
<span>{channelLabel} - </span>
|
||||
{channel.isDM()
|
||||
? Parser.parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`)
|
||||
: Parser.parse(`<#${channel.id}>`)
|
||||
}
|
||||
</span>
|
||||
</Text>
|
||||
}
|
||||
compact={compact}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import "./messageLogger.css";
|
||||
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
|
@ -26,7 +26,7 @@ import { Devs } from "@utils/constants";
|
|||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { ChannelStore, FluxDispatcher, i18n, Menu, moment, Parser, Timestamp, UserStore } from "@webpack/common";
|
||||
import { ChannelStore, FluxDispatcher, i18n, Menu, Parser, Timestamp, UserStore } from "@webpack/common";
|
||||
|
||||
import overlayStyle from "./deleteStyleOverlay.css?managed";
|
||||
import textStyle from "./deleteStyleText.css?managed";
|
||||
|
@ -45,7 +45,7 @@ function addDeleteStyle() {
|
|||
|
||||
const REMOVE_HISTORY_ID = "ml-remove-history";
|
||||
const TOGGLE_DELETE_STYLE_ID = "ml-toggle-style";
|
||||
const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) => () => {
|
||||
const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) => {
|
||||
const { message } = props;
|
||||
const { deleted, editHistory, id, channel_id } = message;
|
||||
|
||||
|
@ -94,13 +94,12 @@ export default definePlugin({
|
|||
description: "Temporarily logs deleted and edited messages.",
|
||||
authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN],
|
||||
|
||||
start() {
|
||||
addDeleteStyle();
|
||||
addContextMenuPatch("message", patchMessageContextMenu);
|
||||
contextMenus: {
|
||||
"message": patchMessageContextMenu
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch("message", patchMessageContextMenu);
|
||||
start() {
|
||||
addDeleteStyle();
|
||||
},
|
||||
|
||||
renderEdit(edit: { timestamp: any, content: string; }) {
|
||||
|
@ -122,7 +121,7 @@ export default definePlugin({
|
|||
|
||||
makeEdit(newMessage: any, oldMessage: any): any {
|
||||
return {
|
||||
timestamp: moment?.call(newMessage.edited_timestamp),
|
||||
timestamp: new Date(newMessage.edited_timestamp),
|
||||
content: oldMessage.content
|
||||
};
|
||||
},
|
||||
|
|
|
@ -198,7 +198,7 @@ export default definePlugin({
|
|||
replacement: [
|
||||
// make the tag show the right text
|
||||
{
|
||||
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(\i\.\i\.Messages)\.BOT_TAG_BOT/,
|
||||
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=.{0,40}(\i\.\i\.Messages)\.BOT_TAG_BOT/,
|
||||
replace: (_, origSwitch, variant, tags, displayedText, strings) =>
|
||||
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}`
|
||||
},
|
||||
|
|
|
@ -20,11 +20,10 @@ import { Devs } from "@utils/constants";
|
|||
import { isNonNullish } from "@utils/guards";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Avatar, ChannelStore, Clickable, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common";
|
||||
import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common";
|
||||
import { Channel, User } from "discord-types/general";
|
||||
|
||||
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
|
||||
const AvatarUtils = findByPropsLazy("getChannelIconURL");
|
||||
const UserUtils = findByPropsLazy("getGlobalName");
|
||||
|
||||
const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds");
|
||||
|
@ -48,8 +47,8 @@ export default definePlugin({
|
|||
{
|
||||
find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded
|
||||
replacement: {
|
||||
match: /(?<=\.MUTUAL_GUILDS\}\),)(?=(\i\.bot).{0,20}(\(0,\i\.jsx\)\(.{0,100}id:))/,
|
||||
replace: '($1||arguments[0].isCurrentUser)?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),'
|
||||
match: /(?<=\.tabBarItem.{0,50}MUTUAL_GUILDS.+?}\),)(?=.+?(\(0,\i\.jsxs?\)\(.{0,100}id:))/,
|
||||
replace: '(arguments[0].user.bot||arguments[0].isCurrentUser)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -71,7 +70,7 @@ export default definePlugin({
|
|||
}}
|
||||
>
|
||||
<Avatar
|
||||
src={AvatarUtils.getChannelIconURL({ id: c.id, icon: c.icon, size: 32 })}
|
||||
src={IconUtils.getChannelIconURL({ id: c.id, icon: c.icon, size: 32 })}
|
||||
size="SIZE_40"
|
||||
className={ProfileListClasses.listAvatar}
|
||||
>
|
||||
|
|
|
@ -16,16 +16,18 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { definePluginSettings,migratePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
|
||||
const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings");
|
||||
const { toggleShowAllChannels } = findByPropsLazy("toggleShowAllChannels");
|
||||
const { isOptInEnabledForGuild } = findByPropsLazy("isOptInEnabledForGuild");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
guild: {
|
||||
description: "Mute Guild",
|
||||
description: "Mute Guild automatically",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true
|
||||
},
|
||||
|
@ -38,13 +40,20 @@ const settings = definePluginSettings({
|
|||
description: "Suppress All Role @mentions",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true
|
||||
},
|
||||
showAllChannels: {
|
||||
description: "Show all channels automatically",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
migratePluginSettings("NewGuildSettings", "MuteNewGuild");
|
||||
export default definePlugin({
|
||||
name: "MuteNewGuild",
|
||||
description: "Mutes newly joined guilds",
|
||||
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince],
|
||||
name: "NewGuildSettings",
|
||||
description: "Automatically mute new servers and change various other settings upon joining",
|
||||
tags: ["MuteNewGuild", "mute", "server"],
|
||||
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince, Devs.Mopi],
|
||||
patches: [
|
||||
{
|
||||
find: ",acceptInvite(",
|
||||
|
@ -70,7 +79,9 @@ export default definePlugin({
|
|||
muted: settings.store.guild,
|
||||
suppress_everyone: settings.store.everyone,
|
||||
suppress_roles: settings.store.role
|
||||
}
|
||||
);
|
||||
});
|
||||
if (settings.store.showAllChannels && isOptInEnabledForGuild(guildId)) {
|
||||
toggleShowAllChannels(guildId);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -27,8 +27,8 @@ export default definePlugin({
|
|||
{
|
||||
find: "_ensureAudio(){",
|
||||
replacement: {
|
||||
match: /onloadeddata=\(\)=>\{.\.volume=/,
|
||||
replace: "$&$self.settings.store.notificationVolume/100*"
|
||||
match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/,
|
||||
replace: "$self.settings.store.notificationVolume/100*"
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -104,6 +104,7 @@ function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: G
|
|||
guildMember.nick || UserStore.getUser(guildMember.userId).username
|
||||
)
|
||||
}
|
||||
onDropDownClick={state => settings.store.defaultPermissionsDropdownState = !state}
|
||||
defaultState={settings.store.defaultPermissionsDropdownState}
|
||||
buttons={[
|
||||
(<Tooltip text={`Sorting by ${stns.permissionsSortOrder === PermissionsSortOrder.HighestRole ? "Highest Role" : "Lowest Role"}`}>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import "./styles.css";
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
@ -125,10 +125,10 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
|
|||
}
|
||||
|
||||
function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback {
|
||||
return (children, props) => () => {
|
||||
return (children, props) => {
|
||||
if (!props) return;
|
||||
if ((type === MenuItemParentType.User && !props.user) || (type === MenuItemParentType.Guild && !props.guild) || (type === MenuItemParentType.Channel && (!props.channel || !props.guild)))
|
||||
return children;
|
||||
return;
|
||||
|
||||
const group = findGroupChildrenByChildId(childId, children);
|
||||
|
||||
|
@ -173,19 +173,10 @@ export default definePlugin({
|
|||
|
||||
UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBoder: boolean) => !!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBoder} />,
|
||||
|
||||
userContextMenuPatch: makeContextMenuPatch("roles", MenuItemParentType.User),
|
||||
channelContextMenuPatch: makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel),
|
||||
guildContextMenuPatch: makeContextMenuPatch("privacy", MenuItemParentType.Guild),
|
||||
|
||||
start() {
|
||||
addContextMenuPatch("user-context", this.userContextMenuPatch);
|
||||
addContextMenuPatch("channel-context", this.channelContextMenuPatch);
|
||||
addContextMenuPatch(["guild-context", "guild-header-popout"], this.guildContextMenuPatch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch("user-context", this.userContextMenuPatch);
|
||||
removeContextMenuPatch("channel-context", this.channelContextMenuPatch);
|
||||
removeContextMenuPatch(["guild-context", "guild-header-popout"], this.guildContextMenuPatch);
|
||||
},
|
||||
contextMenus: {
|
||||
"user-context": makeContextMenuPatch("roles", MenuItemParentType.User),
|
||||
"channel-context": makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel),
|
||||
"guild-context": makeContextMenuPatch("privacy", MenuItemParentType.Guild),
|
||||
"guild-header-popout": makeContextMenuPatch("privacy", MenuItemParentType.Guild)
|
||||
}
|
||||
});
|
||||
|
|
|
@ -24,7 +24,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "PictureInPicture",
|
||||
description: "Adds picture in picture to videos (next to the Download button)",
|
||||
authors: [Devs.Lumap],
|
||||
authors: [Devs.Nobody],
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Menu } from "@webpack/common";
|
||||
|
||||
import { isPinned, movePin, PinOrder, settings, snapshotArray, togglePin } from "./settings";
|
||||
|
@ -50,13 +50,13 @@ function PinMenuItem(channelId: string) {
|
|||
);
|
||||
}
|
||||
|
||||
const GroupDMContext: NavContextMenuPatchCallback = (children, props) => () => {
|
||||
const GroupDMContext: NavContextMenuPatchCallback = (children, props) => {
|
||||
const container = findGroupChildrenByChildId("leave-channel", children);
|
||||
if (container)
|
||||
container.unshift(PinMenuItem(props.channel.id));
|
||||
};
|
||||
|
||||
const UserContext: NavContextMenuPatchCallback = (children, props) => () => {
|
||||
const UserContext: NavContextMenuPatchCallback = (children, props) => {
|
||||
const container = findGroupChildrenByChildId("close-dm", children);
|
||||
if (container) {
|
||||
const idx = container.findIndex(c => c?.props?.id === "close-dm");
|
||||
|
@ -64,12 +64,7 @@ const UserContext: NavContextMenuPatchCallback = (children, props) => () => {
|
|||
}
|
||||
};
|
||||
|
||||
export function addContextMenus() {
|
||||
addContextMenuPatch("gdm-context", GroupDMContext);
|
||||
addContextMenuPatch("user-context", UserContext);
|
||||
}
|
||||
|
||||
export function removeContextMenus() {
|
||||
removeContextMenuPatch("gdm-context", GroupDMContext);
|
||||
removeContextMenuPatch("user-context", UserContext);
|
||||
}
|
||||
export const contextMenus = {
|
||||
"gdm-context": GroupDMContext,
|
||||
"user-context": UserContext
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ import { Devs } from "@utils/constants";
|
|||
import definePlugin from "@utils/types";
|
||||
import { Channel } from "discord-types/general";
|
||||
|
||||
import { addContextMenus, removeContextMenus } from "./contextMenus";
|
||||
import { contextMenus } from "./contextMenus";
|
||||
import { getPinAt, isPinned, settings, snapshotArray, sortedSnapshot, usePinnedDms } from "./settings";
|
||||
|
||||
export default definePlugin({
|
||||
|
@ -29,9 +29,7 @@ export default definePlugin({
|
|||
authors: [Devs.Ven, Devs.Strencher],
|
||||
|
||||
settings,
|
||||
|
||||
start: addContextMenus,
|
||||
stop: removeContextMenus,
|
||||
contextMenus,
|
||||
|
||||
usePinCount(channelIds: string[]) {
|
||||
const pinnedDms = usePinnedDms();
|
||||
|
|
|
@ -16,22 +16,14 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { generateId, sendBotMessage } from "@api/Commands";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import definePlugin, { StartAt } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
|
||||
import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common";
|
||||
import { MessageAttachment } from "discord-types/general";
|
||||
|
||||
interface Props {
|
||||
type: {
|
||||
analyticsName: string;
|
||||
isEmpty: boolean;
|
||||
attachments: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const UploadStore = findByPropsLazy("getUploads");
|
||||
|
||||
const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage);
|
||||
|
@ -81,13 +73,11 @@ const getAttachments = async (channelId: string) =>
|
|||
);
|
||||
|
||||
|
||||
export function PreviewButton(chatBoxProps: Props) {
|
||||
const { isEmpty, attachments } = chatBoxProps.type;
|
||||
|
||||
const PreviewButton: ChatBarButton = ({ isMainChat, isEmpty, type: { attachments } }) => {
|
||||
const channelId = SelectedChannelStore.getChannelId();
|
||||
const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
|
||||
|
||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||
if (!isMainChat) return null;
|
||||
|
||||
const hasAttachments = attachments && UploadStore.getUploads(channelId, DraftType.ChannelMessage).length > 0;
|
||||
const hasContent = !isEmpty && draft?.length > 0;
|
||||
|
@ -95,47 +85,47 @@ export function PreviewButton(chatBoxProps: Props) {
|
|||
if (!hasContent && !hasAttachments) return null;
|
||||
|
||||
return (
|
||||
<Tooltip text="Preview Message">
|
||||
{tooltipProps => (
|
||||
<Button
|
||||
{...tooltipProps}
|
||||
onClick={async () =>
|
||||
sendBotMessage(
|
||||
channelId,
|
||||
{
|
||||
content: getDraft(channelId),
|
||||
author: UserStore.getCurrentUser(),
|
||||
attachments: hasAttachments ? await getAttachments(channelId) : undefined,
|
||||
}
|
||||
)}
|
||||
size=""
|
||||
look={ButtonLooks.BLANK}
|
||||
innerClassName={ButtonWrapperClasses.button}
|
||||
style={{ padding: "0 2px", height: "100%" }}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
<img width={24} height={24} src="https://discord.com/assets/4c5a77a89716352686f590a6f014770c.svg" />
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
<ChatBarButton
|
||||
tooltip="Preview Message"
|
||||
onClick={async () =>
|
||||
sendBotMessage(
|
||||
channelId,
|
||||
{
|
||||
content: getDraft(channelId),
|
||||
author: UserStore.getCurrentUser(),
|
||||
attachments: hasAttachments ? await getAttachments(channelId) : undefined,
|
||||
}
|
||||
)}
|
||||
buttonProps={{
|
||||
style: {
|
||||
translate: "0 2px"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
style={{ scale: "1.096", translate: "0 -1px" }}
|
||||
>
|
||||
<path d="M22.89 11.7c.07.2.07.4 0 .6C22.27 13.9 19.1 21 12 21c-7.11 0-10.27-7.11-10.89-8.7a.83.83 0 0 1 0-.6C1.73 10.1 4.9 3 12 3c7.11 0 10.27 7.11 10.89 8.7Zm-4.5-3.62A15.11 15.11 0 0 1 20.85 12c-.38.88-1.18 2.47-2.46 3.92C16.87 17.62 14.8 19 12 19c-2.8 0-4.87-1.38-6.39-3.08A15.11 15.11 0 0 1 3.15 12c.38-.88 1.18-2.47 2.46-3.92C7.13 6.38 9.2 5 12 5c2.8 0 4.87 1.38 6.39 3.08ZM15.56 11.77c.2-.1.44.02.44.23a4 4 0 1 1-4-4c.21 0 .33.25.23.44a2.5 2.5 0 0 0 3.32 3.32Z" />
|
||||
</svg>
|
||||
</ChatBarButton>
|
||||
);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "PreviewMessage",
|
||||
description: "Lets you preview your message before sending it.",
|
||||
authors: [Devs.Aria],
|
||||
patches: [
|
||||
{
|
||||
find: "ChannelTextAreaButtons",
|
||||
replacement: {
|
||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||
}
|
||||
},
|
||||
],
|
||||
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,
|
||||
|
||||
chatBarIcon: ErrorBoundary.wrap(PreviewButton, { noop: true }),
|
||||
start: () => addChatBarButton("previewMessage", PreviewButton),
|
||||
stop: () => removeChatBarButton("previewMessage"),
|
||||
});
|
||||
|
|
|
@ -25,16 +25,17 @@ function onClick() {
|
|||
const channels: Array<any> = [];
|
||||
|
||||
Object.values(GuildStore.getGuilds()).forEach(guild => {
|
||||
GuildChannelStore.getChannels(guild.id).SELECTABLE.forEach((c: { channel: { id: string; }; }) => {
|
||||
if (!ReadStateStore.hasUnread(c.channel.id)) return;
|
||||
GuildChannelStore.getChannels(guild.id).SELECTABLE
|
||||
.concat(GuildChannelStore.getChannels(guild.id).VOCAL)
|
||||
.forEach((c: { channel: { id: string; }; }) => {
|
||||
if (!ReadStateStore.hasUnread(c.channel.id)) return;
|
||||
|
||||
channels.push({
|
||||
channelId: c.channel.id,
|
||||
// messageId: c.channel?.lastMessageId,
|
||||
messageId: ReadStateStore.lastMessageId(c.channel.id),
|
||||
readStateType: 0
|
||||
channels.push({
|
||||
channelId: c.channel.id,
|
||||
messageId: ReadStateStore.lastMessageId(c.channel.id),
|
||||
readStateType: 0
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
|
|
5
src/plugins/resurrectHome/README.md
Normal file
5
src/plugins/resurrectHome/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# ResurrectHome
|
||||
|
||||
Brings back the phased out [Server Home](https://support.discord.com/hc/en-us/articles/6156116949911-Server-Home-Beta) feature!
|
||||
|
||||
![](https://private-user-images.githubusercontent.com/47677887/309572891-a9ee7354-9e5e-4b81-8faf-304d9c44f512.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk0OTE5MTIsIm5iZiI6MTcwOTQ5MTYxMiwicGF0aCI6Ii80NzY3Nzg4Ny8zMDk1NzI4OTEtYTllZTczNTQtOWU1ZS00YjgxLThmYWYtMzA0ZDljNDRmNTEyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDMlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzAzVDE4NDY1MlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTBhYzUxMWY1MzQxNTA4NDE1MWU0YjAxNzM1NzI1YWJkMTNiZmNkNjRmYTRkZDg1ZDE5NzdkMjM0MGVjMDA0OWQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.TPYWPRWHTJstfviT9HOaBWFkbBhokyxiDC-gOVL2dqs)
|
119
src/plugins/resurrectHome/index.tsx
Normal file
119
src/plugins/resurrectHome/index.tsx
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { findGroupChildrenByChildId } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Menu } from "@webpack/common";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
forceServerHome: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Force the Server Guide to be the Server Home tab when it is enabled.",
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
function useForceServerHome() {
|
||||
const { forceServerHome } = settings.use(["forceServerHome"]);
|
||||
|
||||
return forceServerHome;
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "ResurrectHome",
|
||||
description: "Re-enables the Server Home tab when there isn't a Server Guide. Also has an option to force the Server Home over the Server Guide, which is accessible through right-clicking the Server Guide.",
|
||||
authors: [Devs.Dolfies, Devs.Nuckyz],
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
// Force home deprecation override
|
||||
{
|
||||
find: "GuildFeatures.GUILD_HOME_DEPRECATION_OVERRIDE",
|
||||
all: true,
|
||||
replacement: [
|
||||
{
|
||||
match: /\i\.hasFeature\(\i\.GuildFeatures\.GUILD_HOME_DEPRECATION_OVERRIDE\)/g,
|
||||
replace: "true"
|
||||
}
|
||||
],
|
||||
},
|
||||
// Disable feedback prompts
|
||||
{
|
||||
find: "GuildHomeFeedbackExperiment.definition.id",
|
||||
replacement: [
|
||||
{
|
||||
match: /return{showFeedback:\i,setOnDismissedFeedback:(\i)}/,
|
||||
replace: "return{showFeedback:false,setOnDismissedFeedback:$1}"
|
||||
}
|
||||
]
|
||||
},
|
||||
// This feature was never finished, so the patch is disabled
|
||||
|
||||
// Enable guild feed render mode selector
|
||||
// {
|
||||
// find: "2022-01_home_feed_toggle",
|
||||
// replacement: [
|
||||
// {
|
||||
// match: /showSelector:!1/,
|
||||
// replace: "showSelector:true"
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
|
||||
// Fix focusMessage clearing previously cached messages and causing a loop when fetching messages around home messages
|
||||
{
|
||||
find: '"MessageActionCreators"',
|
||||
replacement: {
|
||||
match: /(?<=focusMessage\(\i\){.+?)(?=focus:{messageId:(\i)})/,
|
||||
replace: "before:$1,"
|
||||
}
|
||||
},
|
||||
// Force Server Home instead of Server Guide
|
||||
{
|
||||
find: "61eef9_2",
|
||||
replacement: {
|
||||
match: /(?<=getMutableGuildChannelsForGuild\(\i\)\);)(?=if\(null==\i\|\|)/,
|
||||
replace: "if($self.useForceServerHome())return false;"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
useForceServerHome,
|
||||
|
||||
contextMenus: {
|
||||
"guild-context"(children, props) {
|
||||
const forceServerHome = useForceServerHome();
|
||||
|
||||
if (!props?.guild) return;
|
||||
|
||||
const group = findGroupChildrenByChildId("hide-muted-channels", children);
|
||||
|
||||
group?.unshift(
|
||||
<Menu.MenuCheckboxItem
|
||||
key="force-server-home"
|
||||
id="force-server-home"
|
||||
label="Force Server Home"
|
||||
checked={forceServerHome}
|
||||
action={() => settings.store.forceServerHome = !forceServerHome}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { OpenExternalIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
|
@ -84,7 +84,7 @@ function makeSearchItem(src: string) {
|
|||
);
|
||||
}
|
||||
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||
if (props?.reverseImageSearchType !== "img") return;
|
||||
|
||||
const src = props.itemHref ?? props.itemSrc;
|
||||
|
@ -93,7 +93,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) =
|
|||
group?.push(makeSearchItem(src));
|
||||
};
|
||||
|
||||
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
|
||||
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||
if (!props?.src) return;
|
||||
|
||||
const group = findGroupChildrenByChildId("copy-native-link", children) ?? children;
|
||||
|
@ -115,14 +115,8 @@ export default definePlugin({
|
|||
}
|
||||
}
|
||||
],
|
||||
|
||||
start() {
|
||||
addContextMenuPatch("message", messageContextMenuPatch);
|
||||
addContextMenuPatch("image-context", imageContextMenuPatch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch("message", messageContextMenuPatch);
|
||||
removeContextMenuPatch("image-context", imageContextMenuPatch);
|
||||
contextMenus: {
|
||||
"message": messageContextMenuPatch,
|
||||
"image-context": imageContextMenuPatch
|
||||
}
|
||||
});
|
||||
|
|
|
@ -59,7 +59,7 @@ export function authorize(callback?: any) {
|
|||
const url = new URL(response.location);
|
||||
url.searchParams.append("clientMod", "vencord");
|
||||
const res = await fetch(url, {
|
||||
headers: new Headers({ Accept: "application/json" })
|
||||
headers: { Accept: "application/json" }
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
|
|
|
@ -20,7 +20,7 @@ import { openUserProfile } from "@utils/discord";
|
|||
import { classes } from "@utils/misc";
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { filters, findBulk } from "@webpack";
|
||||
import { Alerts, moment, Parser, Timestamp, useState } from "@webpack/common";
|
||||
import { Alerts, Parser, Timestamp, useState } from "@webpack/common";
|
||||
|
||||
import { Auth, getToken } from "../auth";
|
||||
import { Review, ReviewType } from "../entities";
|
||||
|
@ -163,7 +163,7 @@ export default LazyComponent(() => {
|
|||
|
||||
{
|
||||
!settings.store.hideTimestamps && review.type !== ReviewType.System && (
|
||||
<Timestamp timestamp={moment(review.timestamp * 1000)} >
|
||||
<Timestamp timestamp={new Date(review.timestamp * 1000)} >
|
||||
{dateFormat.format(review.timestamp * 1000)}
|
||||
</Timestamp>)
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { LazyComponent, useAwaiter, useForceUpdater } from "@utils/react";
|
||||
import { find, findByPropsLazy } from "@webpack";
|
||||
import { useAwaiter, useForceUpdater } from "@utils/react";
|
||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common";
|
||||
|
||||
import { Auth, authorize } from "../auth";
|
||||
|
@ -31,7 +31,8 @@ import ReviewComponent from "./ReviewComponent";
|
|||
const { Editor, Transforms } = findByPropsLazy("Editor", "Transforms");
|
||||
const { ChatInputTypes } = findByPropsLazy("ChatInputTypes");
|
||||
|
||||
const InputComponent = LazyComponent(() => find(m => m.default?.type?.render?.toString().includes("default.CHANNEL_TEXT_AREA")).default);
|
||||
const InputComponent = findComponentByCodeLazy("default.CHANNEL_TEXT_AREA");
|
||||
const { createChannelRecordFromServer } = findByPropsLazy("createChannelRecordFromServer");
|
||||
|
||||
interface UserProps {
|
||||
discordId: string;
|
||||
|
@ -125,19 +126,7 @@ export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: {
|
|||
const inputType = ChatInputTypes.FORM;
|
||||
inputType.disableAutoFocus = true;
|
||||
|
||||
const channel = {
|
||||
flags_: 256,
|
||||
guild_id_: null,
|
||||
id: "0",
|
||||
getGuildId: () => null,
|
||||
isPrivate: () => true,
|
||||
isActiveThread: () => false,
|
||||
isArchivedLockedThread: () => false,
|
||||
isDM: () => true,
|
||||
roles: { "0": { permissions: 0n } },
|
||||
getRecipientId: () => "0",
|
||||
hasFlag: () => false,
|
||||
};
|
||||
const channel = createChannelRecordFromServer({ id: "0", type: 1 });
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import "./style.css";
|
||||
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import ExpandableHeader from "@components/ExpandableHeader";
|
||||
import { OpenExternalIcon } from "@components/Icons";
|
||||
|
@ -36,7 +36,7 @@ import { getCurrentUserInfo, readNotification } from "./reviewDbApi";
|
|||
import { settings } from "./settings";
|
||||
import { showToast } from "./utils";
|
||||
|
||||
const guildPopoutPatch: NavContextMenuPatchCallback = (children, props: { guild: Guild, onClose(): void; }) => () => {
|
||||
const guildPopoutPatch: NavContextMenuPatchCallback = (children, props: { guild: Guild, onClose(): void; }) => {
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
label="View Reviews"
|
||||
|
@ -53,6 +53,9 @@ export default definePlugin({
|
|||
authors: [Devs.mantikafasi, Devs.Ven],
|
||||
|
||||
settings,
|
||||
contextMenus: {
|
||||
"guild-header-popout": guildPopoutPatch
|
||||
},
|
||||
|
||||
patches: [
|
||||
{
|
||||
|
@ -69,8 +72,6 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
async start() {
|
||||
addContextMenuPatch("guild-header-popout", guildPopoutPatch);
|
||||
|
||||
const s = settings.store;
|
||||
const { lastReviewId, notifyReviews } = s;
|
||||
|
||||
|
@ -127,10 +128,6 @@ export default definePlugin({
|
|||
}, 4000);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch("guild-header-popout", guildPopoutPatch);
|
||||
},
|
||||
|
||||
getReviewsComponent: ErrorBoundary.wrap((user: User) => {
|
||||
const [reviewCount, setReviewCount] = useState<number>();
|
||||
|
||||
|
|
|
@ -118,10 +118,10 @@ export async function addReview(review: any): Promise<Response | null> {
|
|||
export async function deleteReview(id: number): Promise<Response | null> {
|
||||
return await rdbRequest(`/users/${id}/reviews`, {
|
||||
method: "DELETE",
|
||||
headers: new Headers({
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
reviewid: id
|
||||
})
|
||||
|
@ -135,10 +135,10 @@ export async function deleteReview(id: number): Promise<Response | null> {
|
|||
export async function reportReview(id: number) {
|
||||
const res = await rdbRequest("/reports", {
|
||||
method: "PUT",
|
||||
headers: new Headers({
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
reviewid: id,
|
||||
})
|
||||
|
@ -150,10 +150,10 @@ export async function reportReview(id: number) {
|
|||
async function patchBlock(action: "block" | "unblock", userId: string) {
|
||||
const res = await rdbRequest("/blocks", {
|
||||
method: "PATCH",
|
||||
headers: new Headers({
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: action,
|
||||
discordId: userId
|
||||
|
@ -180,9 +180,9 @@ export const unblockUser = (userId: string) => patchBlock("unblock", userId);
|
|||
export async function fetchBlocks(): Promise<ReviewDBUser[]> {
|
||||
const res = await rdbRequest("/blocks", {
|
||||
method: "GET",
|
||||
headers: new Headers({
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error(`${res.status}: ${res.statusText}`);
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { ReplyIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
@ -27,7 +27,7 @@ import { Message } from "discord-types/general";
|
|||
|
||||
const messageUtils = findByPropsLazy("replyToMessage");
|
||||
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => () => {
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => {
|
||||
// make sure the message is in the selected channel
|
||||
if (SelectedChannelStore.getChannelId() !== message.channel_id) return;
|
||||
const channel = ChannelStore.getChannel(message?.channel_id);
|
||||
|
@ -38,7 +38,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
|
|||
const dmGroup = findGroupChildrenByChildId("pin", children);
|
||||
if (dmGroup && !dmGroup.some(child => child?.props?.id === "reply")) {
|
||||
const pinIndex = dmGroup.findIndex(c => c?.props.id === "pin");
|
||||
return dmGroup.splice(pinIndex + 1, 0, (
|
||||
dmGroup.splice(pinIndex + 1, 0, (
|
||||
<Menu.MenuItem
|
||||
id="reply"
|
||||
label={i18n.Messages.MESSAGE_ACTION_REPLY}
|
||||
|
@ -46,12 +46,13 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
|
|||
action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)}
|
||||
/>
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// servers
|
||||
const serverGroup = findGroupChildrenByChildId("mark-unread", children);
|
||||
if (serverGroup && !serverGroup.some(child => child?.props?.id === "reply")) {
|
||||
return serverGroup.unshift((
|
||||
serverGroup.unshift((
|
||||
<Menu.MenuItem
|
||||
id="reply"
|
||||
label={i18n.Messages.MESSAGE_ACTION_REPLY}
|
||||
|
@ -59,6 +60,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
|
|||
action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)}
|
||||
/>
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -67,12 +69,7 @@ export default definePlugin({
|
|||
name: "SearchReply",
|
||||
description: "Adds a reply button to search results",
|
||||
authors: [Devs.Aria],
|
||||
|
||||
start() {
|
||||
addContextMenuPatch("message", messageContextMenuPatch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch("message", messageContextMenuPatch);
|
||||
contextMenus: {
|
||||
"message": messageContextMenuPatch
|
||||
}
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
import "./styles.css";
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
|
@ -26,7 +27,7 @@ import { getTheme, insertTextIntoChatInputBox, Theme } from "@utils/discord";
|
|||
import { Margins } from "@utils/margins";
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, Forms, Parser, Select, Tooltip, useMemo, useState } from "@webpack/common";
|
||||
import { Button, Forms, Parser, Select, useMemo, useState } from "@webpack/common";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
replaceMessageContents: {
|
||||
|
@ -122,25 +123,49 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi
|
|||
);
|
||||
}
|
||||
|
||||
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||
if (!isMainChat) return null;
|
||||
|
||||
return (
|
||||
<ChatBarButton
|
||||
tooltip="Insert Timestamp"
|
||||
onClick={() => {
|
||||
const key = openModal(props => (
|
||||
<PickerModal
|
||||
rootProps={props}
|
||||
close={() => closeModal(key)}
|
||||
/>
|
||||
));
|
||||
}}
|
||||
buttonProps={{ "aria-haspopup": "dialog" }}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
style={{ scale: "1.2" }}
|
||||
>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z" />
|
||||
<rect width="24" height="24" />
|
||||
</g>
|
||||
</svg>
|
||||
</ChatBarButton>
|
||||
);
|
||||
};
|
||||
|
||||
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"],
|
||||
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
|
||||
|
||||
settings: settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "ChannelTextAreaButtons",
|
||||
replacement: {
|
||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||
}
|
||||
},
|
||||
],
|
||||
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);
|
||||
|
@ -149,56 +174,10 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
stop() {
|
||||
removeChatBarButton("SendTimestamps");
|
||||
removePreSendListener(this.listener);
|
||||
},
|
||||
|
||||
chatBarIcon(chatBoxProps: { type: { analyticsName: string; }; }) {
|
||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||
|
||||
return (
|
||||
<Tooltip text="Insert Timestamp">
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
aria-haspopup="dialog"
|
||||
aria-label="Insert Timestamp"
|
||||
size=""
|
||||
look={ButtonLooks.BLANK}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
innerClassName={ButtonWrapperClasses.button}
|
||||
onClick={() => {
|
||||
const key = openModal(props => (
|
||||
<PickerModal
|
||||
rootProps={props}
|
||||
close={() => closeModal(key)}
|
||||
/>
|
||||
));
|
||||
}}
|
||||
className={cl("button")}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z" />
|
||||
<rect width="24" height="24" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Tooltip >
|
||||
);
|
||||
},
|
||||
|
||||
settingsAboutComponent() {
|
||||
const samples = [
|
||||
"12:00",
|
||||
|
|
|
@ -42,10 +42,6 @@
|
|||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.vc-st-button {
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.vc-st-button svg {
|
||||
transform: scale(1.1) translateY(1px);
|
||||
}
|
||||
|
|
|
@ -12,10 +12,9 @@ import { classes } from "@utils/misc";
|
|||
import { ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import { findByPropsLazy, findExportedComponentLazy } from "@webpack";
|
||||
import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common";
|
||||
import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common";
|
||||
import { Guild, User } from "discord-types/general";
|
||||
|
||||
const IconUtils = findByPropsLazy("getGuildBannerURL");
|
||||
const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper");
|
||||
const FriendRow = findExportedComponentLazy("FriendRow");
|
||||
|
||||
|
@ -50,7 +49,7 @@ const fetched = {
|
|||
|
||||
function renderTimestamp(timestamp: number) {
|
||||
return (
|
||||
<Timestamp timestamp={moment(timestamp)} />
|
||||
<Timestamp timestamp={new Date(timestamp)} />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -65,10 +64,7 @@ function GuildProfileModal({ guild }: GuildProps) {
|
|||
|
||||
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
|
||||
|
||||
const bannerUrl = guild.banner && IconUtils.getGuildBannerURL({
|
||||
id: guild.id,
|
||||
banner: guild.banner
|
||||
}, true).replace(/\?size=\d+$/, "?size=1024");
|
||||
const bannerUrl = guild.banner && IconUtils.getGuildBannerURL(guild, true)!.replace(/\?size=\d+$/, "?size=1024");
|
||||
|
||||
const iconUrl = guild.icon && IconUtils.getGuildIconURL({
|
||||
id: guild.id,
|
||||
|
@ -89,7 +85,7 @@ function GuildProfileModal({ guild }: GuildProps) {
|
|||
)}
|
||||
|
||||
<div className={cl("header")}>
|
||||
{guild.icon
|
||||
{iconUrl
|
||||
? <img
|
||||
src={iconUrl}
|
||||
alt=""
|
||||
|
@ -150,7 +146,7 @@ function Owner(guildId: string, owner: User) {
|
|||
avatar: guildAvatar,
|
||||
guildId,
|
||||
canAnimate: true
|
||||
}, true)
|
||||
})
|
||||
: IconUtils.getUserAvatarURL(owner, true);
|
||||
|
||||
return (
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu } from "@webpack/common";
|
||||
|
@ -12,7 +12,7 @@ import { Guild } from "discord-types/general";
|
|||
|
||||
import { openGuildProfileModal } from "./GuildProfileModal";
|
||||
|
||||
const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => () => {
|
||||
const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => {
|
||||
const group = findGroupChildrenByChildId("privacy", children);
|
||||
|
||||
group?.push(
|
||||
|
@ -29,12 +29,8 @@ export default definePlugin({
|
|||
description: "Allows you to view info about a server by right clicking it in the server list",
|
||||
authors: [Devs.Ven, Devs.Nuckyz],
|
||||
tags: ["guild", "info"],
|
||||
|
||||
start() {
|
||||
addContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
|
||||
contextMenus: {
|
||||
"guild-context": Patch,
|
||||
"guild-header-popout": Patch
|
||||
}
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ import { Settings } from "@api/Settings";
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { formatDuration } from "@utils/text";
|
||||
import { findByPropsLazy, findComponentByCodeLazy, findComponentLazy } from "@webpack";
|
||||
import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common";
|
||||
import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common";
|
||||
import type { Channel } from "discord-types/general";
|
||||
|
||||
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions";
|
||||
|
@ -120,7 +120,7 @@ const VideoQualityModesToNames = {
|
|||
const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg";
|
||||
|
||||
function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
||||
const [viewAllowedUsersAndRoles, setViewAllowedUsersAndRoles] = useState(settings.store.defaultAllowedUsersAndRolesDropdownState);
|
||||
const { defaultAllowedUsersAndRolesDropdownState } = settings.use(["defaultAllowedUsersAndRolesDropdownState"]);
|
||||
const [permissions, setPermissions] = useState<RoleOrUserPermission[]>([]);
|
||||
|
||||
const {
|
||||
|
@ -216,12 +216,12 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
|||
{lastMessageId &&
|
||||
<Text variant="text-md/normal">
|
||||
Last {channel.isForumChannel() ? "post" : "message"} created:
|
||||
<Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(lastMessageId))} />
|
||||
<Timestamp timestamp={new Date(SnowflakeUtils.extractTimestamp(lastMessageId))} />
|
||||
</Text>
|
||||
}
|
||||
|
||||
{lastPinTimestamp &&
|
||||
<Text variant="text-md/normal">Last message pin: <Timestamp timestamp={moment(lastPinTimestamp)} /></Text>
|
||||
<Text variant="text-md/normal">Last message pin: <Timestamp timestamp={new Date(lastPinTimestamp)} /></Text>
|
||||
}
|
||||
{(rateLimitPerUser ?? 0) > 0 &&
|
||||
<Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser!, "seconds")}</Text>
|
||||
|
@ -301,19 +301,19 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
|||
</Tooltip>
|
||||
)}
|
||||
<Text variant="text-lg/bold">Allowed users and roles:</Text>
|
||||
<Tooltip text={viewAllowedUsersAndRoles ? "Hide Allowed Users and Roles" : "View Allowed Users and Roles"}>
|
||||
<Tooltip text={defaultAllowedUsersAndRolesDropdownState ? "Hide Allowed Users and Roles" : "View Allowed Users and Roles"}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<button
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
className="shc-lock-screen-allowed-users-and-roles-container-toggle-btn"
|
||||
onClick={() => setViewAllowedUsersAndRoles(v => !v)}
|
||||
onClick={() => settings.store.defaultAllowedUsersAndRolesDropdownState = !defaultAllowedUsersAndRolesDropdownState}
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
transform={viewAllowedUsersAndRoles ? "scale(1 -1)" : "scale(1 1)"}
|
||||
transform={defaultAllowedUsersAndRolesDropdownState ? "scale(1 -1)" : "scale(1 1)"}
|
||||
>
|
||||
<path fill="currentColor" d="M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z" />
|
||||
</svg>
|
||||
|
@ -321,7 +321,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
|||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
{viewAllowedUsersAndRoles && <ChannelBeginHeader channel={channel} />}
|
||||
{defaultAllowedUsersAndRolesDropdownState && <ChannelBeginHeader channel={channel} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,7 +29,7 @@ import type { Channel, Role } from "discord-types/general";
|
|||
|
||||
import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen";
|
||||
|
||||
const ChannelListClasses = findByPropsLazy("channelEmoji", "unread", "icon");
|
||||
const ChannelListClasses = findByPropsLazy("modeMuted", "modeSelected", "unread", "icon");
|
||||
|
||||
const enum ShowMode {
|
||||
LockIcon,
|
||||
|
@ -162,7 +162,7 @@ export default definePlugin({
|
|||
},
|
||||
// Add the hidden eye icon if the channel is hidden
|
||||
{
|
||||
match: /\i\.children.+?:null(?<=,channel:(\i).+?)/,
|
||||
match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/,
|
||||
replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null`
|
||||
},
|
||||
// Make voice channels also appear as muted if they are muted
|
||||
|
@ -305,27 +305,27 @@ export default definePlugin({
|
|||
]
|
||||
},
|
||||
{
|
||||
find: ".avatars),children",
|
||||
find: '+1]})},"overflow"))',
|
||||
replacement: [
|
||||
{
|
||||
// Create a variable for the channel prop
|
||||
match: /maxUsers:\i,users:\i.+?=(\i).+?;/,
|
||||
match: /maxUsers:\i,users:\i.+?}=(\i).*?;/,
|
||||
replace: (m, props) => `${m}let{shcChannel}=${props};`
|
||||
},
|
||||
{
|
||||
// Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen
|
||||
match: /\i>0(?=&&.{0,60}renderPopout)/,
|
||||
replace: m => `($self.isHiddenChannel(shcChannel,true)?true:${m})`
|
||||
replace: m => `($self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)?true:${m})`
|
||||
},
|
||||
{
|
||||
// Prevent Discord from overwriting the last children with the plus button if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen
|
||||
match: /(?<=\.value\(\),(\i)=.+?length-)1(?=\]=.{0,60}renderPopout)/,
|
||||
replace: (_, amount) => `($self.isHiddenChannel(shcChannel,true)&&${amount}<=0?0:1)`
|
||||
replace: (_, amount) => `($self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)&&${amount}<=0?0:1)`
|
||||
},
|
||||
{
|
||||
// Show only the plus text without overflowed children amount if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen
|
||||
match: /(?<="\+",)(\i)\+1/,
|
||||
replace: (m, amount) => `$self.isHiddenChannel(shcChannel,true)&&${amount}<=0?"":${m}`
|
||||
replace: (m, amount) => `$self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)&&${amount}<=0?"":${m}`
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -16,12 +16,12 @@
|
|||
* 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 { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, React, Tooltip } from "@webpack/common";
|
||||
import { React, useEffect, useState } from "@webpack/common";
|
||||
|
||||
let lastState = false;
|
||||
|
||||
|
@ -41,19 +41,15 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
function SilentMessageToggle(chatBoxProps: {
|
||||
type: {
|
||||
analyticsName: string;
|
||||
};
|
||||
}) {
|
||||
const [enabled, setEnabled] = React.useState(lastState);
|
||||
const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
||||
const [enabled, setEnabled] = useState(lastState);
|
||||
|
||||
function setEnabledValue(value: boolean) {
|
||||
if (settings.store.persistState) lastState = value;
|
||||
setEnabled(value);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
const listener: SendListener = (_, message) => {
|
||||
if (enabled) {
|
||||
if (settings.store.autoDisable) setEnabledValue(false);
|
||||
|
@ -65,55 +61,39 @@ function SilentMessageToggle(chatBoxProps: {
|
|||
return () => void removePreSendListener(listener);
|
||||
}, [enabled]);
|
||||
|
||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||
if (!isMainChat) return null;
|
||||
|
||||
return (
|
||||
<Tooltip text={enabled ? "Disable Silent Message" : "Enable Silent Message"}>
|
||||
{tooltipProps => (
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
{...tooltipProps}
|
||||
onClick={() => setEnabledValue(!enabled)}
|
||||
size=""
|
||||
look={ButtonLooks.BLANK}
|
||||
innerClassName={ButtonWrapperClasses.button}
|
||||
style={{ padding: "0 6px" }}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" mask="url(#_)" d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4c0-.30736.0198-.6101.0582-.907C12.7147 3.03189 12.3611 3 12 3 8.686 3 6 5.686 6 9v5c0 1.657-1.344 3-3 3v1h18v-1c-1.656 0-3-1.343-3-3v-3.2899ZM8.55493 19c.693 1.19 1.96897 2 3.44497 2s2.752-.81 3.445-2H8.55493ZM18.2624 5.50209 21 2.5V1h-4.9651v1.49791h2.4411L16 5.61088V7h5V5.50209h-2.7376Z" />
|
||||
{!enabled && <>
|
||||
<mask id="_">
|
||||
<path fill="#fff" d="M0 0h24v24H0Z" />
|
||||
<path stroke="#000" stroke-width="5.99068" d="M0 24 24 0" />
|
||||
</mask>
|
||||
<path fill="var(--status-danger)" d="m21.178 1.70703 1.414 1.414L4.12103 21.593l-1.414-1.415L21.178 1.70703Z" />
|
||||
</>}
|
||||
</svg>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
<ChatBarButton
|
||||
tooltip={enabled ? "Disable Silent Message" : "Enable Silent Message"}
|
||||
onClick={() => setEnabledValue(!enabled)}
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
style={{ scale: "1.2" }}
|
||||
>
|
||||
<path fill="currentColor" mask="url(#_)" d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4c0-.30736.0198-.6101.0582-.907C12.7147 3.03189 12.3611 3 12 3 8.686 3 6 5.686 6 9v5c0 1.657-1.344 3-3 3v1h18v-1c-1.656 0-3-1.343-3-3v-3.2899ZM8.55493 19c.693 1.19 1.96897 2 3.44497 2s2.752-.81 3.445-2H8.55493ZM18.2624 5.50209 21 2.5V1h-4.9651v1.49791h2.4411L16 5.61088V7h5V5.50209h-2.7376Z" />
|
||||
{!enabled && <>
|
||||
<mask id="_">
|
||||
<path fill="#fff" d="M0 0h24v24H0Z" />
|
||||
<path stroke="#000" stroke-width="5.99068" d="M0 24 24 0" />
|
||||
</mask>
|
||||
<path fill="var(--status-danger)" d="m21.178 1.70703 1.414 1.414L4.12103 21.593l-1.414-1.415L21.178 1.70703Z" />
|
||||
</>}
|
||||
</svg>
|
||||
</ChatBarButton>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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"],
|
||||
|
||||
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
find: "ChannelTextAreaButtons",
|
||||
replacement: {
|
||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
chatBarIcon: ErrorBoundary.wrap(SilentMessageToggle, { noop: true }),
|
||||
start: () => addChatBarButton("SilentMessageToggle", SilentMessageToggle),
|
||||
stop: () => removeChatBarButton("SilentMessageToggle")
|
||||
});
|
||||
|
|
|
@ -16,12 +16,12 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, FluxDispatcher, React, Tooltip } from "@webpack/common";
|
||||
import { FluxDispatcher, React } from "@webpack/common";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
showIcon: {
|
||||
|
@ -37,45 +37,32 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
function SilentTypingToggle(chatBoxProps: {
|
||||
type: {
|
||||
analyticsName: string;
|
||||
};
|
||||
}) {
|
||||
const { isEnabled } = settings.use(["isEnabled"]);
|
||||
const SilentTypingToggle: ChatBarButton = ({ isMainChat }) => {
|
||||
const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]);
|
||||
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
||||
|
||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||
if (!isMainChat || !showIcon) return null;
|
||||
|
||||
return (
|
||||
<Tooltip text={isEnabled ? "Disable Silent Typing" : "Enable Silent Typing"}>
|
||||
{(tooltipProps: any) => (
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
{...tooltipProps}
|
||||
onClick={toggle}
|
||||
size=""
|
||||
look={ButtonLooks.BLANK}
|
||||
innerClassName={ButtonWrapperClasses.button}
|
||||
style={{ padding: "0 6px" }}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
|
||||
<path fill="currentColor" d="M528 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h480c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM128 180v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm288 0v-40c0-6.627-5.373-12-12-12H172c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h232c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12z" />
|
||||
{isEnabled && <path d="M13 432L590 48" stroke="var(--red-500)" stroke-width="72" stroke-linecap="round" />}
|
||||
</svg>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
<ChatBarButton
|
||||
tooltip={isEnabled ? "Disable Silent Typing" : "Enable Silent Typing"}
|
||||
onClick={toggle}
|
||||
>
|
||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
|
||||
<path fill="currentColor" d="M528 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h480c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM128 180v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm288 0v-40c0-6.627-5.373-12-12-12H172c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h232c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12z" />
|
||||
{isEnabled && <path d="M13 432L590 48" stroke="var(--red-500)" stroke-width="72" stroke-linecap="round" />}
|
||||
</svg>
|
||||
</ChatBarButton>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "SilentTyping",
|
||||
authors: [Devs.Ven, Devs.Rini],
|
||||
description: "Hide that you are typing",
|
||||
dependencies: ["CommandsAPI", "ChatInputButtonAPI"],
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: '.dispatch({type:"TYPING_START_LOCAL"',
|
||||
|
@ -84,17 +71,8 @@ export default definePlugin({
|
|||
replace: "startTyping:$self.startTyping,stop"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "ChannelTextAreaButtons",
|
||||
predicate: () => settings.store.showIcon,
|
||||
replacement: {
|
||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||
}
|
||||
},
|
||||
],
|
||||
dependencies: ["CommandsAPI"],
|
||||
settings,
|
||||
|
||||
commands: [{
|
||||
name: "silenttype",
|
||||
description: "Toggle whether you're hiding that you're typing or not.",
|
||||
|
@ -120,5 +98,6 @@ export default definePlugin({
|
|||
FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId });
|
||||
},
|
||||
|
||||
chatBarIcon: ErrorBoundary.wrap(SilentTypingToggle, { noop: true }),
|
||||
start: () => addChatBarButton("SilentTyping", SilentTypingToggle),
|
||||
stop: () => removeChatBarButton("SilentTyping"),
|
||||
});
|
||||
|
|
|
@ -371,6 +371,10 @@ export function Player() {
|
|||
if (!track || !device?.is_active || shouldHide)
|
||||
return null;
|
||||
|
||||
const exportTrackImageStyle = {
|
||||
"--vc-spotify-track-image": `url(${track?.album?.image?.url || ""})`,
|
||||
} as React.CSSProperties;
|
||||
|
||||
return (
|
||||
<ErrorBoundary fallback={() => (
|
||||
<div className="vc-spotify-fallback">
|
||||
|
@ -378,7 +382,7 @@ export function Player() {
|
|||
<p >Check the console for errors</p>
|
||||
</div>
|
||||
)}>
|
||||
<div id={cl("player")}>
|
||||
<div id={cl("player")} style={exportTrackImageStyle}>
|
||||
<Info track={track} />
|
||||
<SeekBar />
|
||||
<Controls />
|
||||
|
|
|
@ -31,7 +31,7 @@ function toggleHoverControls(value: boolean) {
|
|||
export default definePlugin({
|
||||
name: "SpotifyControls",
|
||||
description: "Adds a Spotify player above the account panel",
|
||||
authors: [Devs.Ven, Devs.afn, Devs.KraXen72],
|
||||
authors: [Devs.Ven, Devs.afn, Devs.KraXen72, Devs.Av32000],
|
||||
options: {
|
||||
hoverControls: {
|
||||
description: "Show controls on hover",
|
||||
|
|
|
@ -170,9 +170,16 @@
|
|||
/* these importants are necessary, it applies a width and height through inline styles */
|
||||
height: 10px !important;
|
||||
width: 10px !important;
|
||||
margin-top: 4px;
|
||||
background-color: var(--interactive-normal);
|
||||
border-color: var(--interactive-normal);
|
||||
color: var(--interactive-normal);
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s;
|
||||
}
|
||||
|
||||
#vc-spotify-progress-bar:hover > [class^="slider"] [class^="grabber"] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#vc-spotify-progress-text {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
superReactByDefault: {
|
||||
|
@ -49,7 +50,7 @@ export default definePlugin({
|
|||
find: ".trackEmojiSearchEmpty,200",
|
||||
replacement: {
|
||||
match: /(\.trackEmojiSearchEmpty,200(?=.+?isBurstReaction:(\i).+?(\i===\i\.EmojiIntention.REACTION)).+?\[\2,\i\]=\i\.useState\().+?\)/,
|
||||
replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.settings.store.superReactByDefault&&${isReactionIntention})`
|
||||
replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.shouldSuperReactByDefault&&${isReactionIntention})`
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -59,5 +60,9 @@ export default definePlugin({
|
|||
if (settings.store.unlimitedSuperReactionPlaying) return true;
|
||||
if (playingCount <= settings.store.superReactionPlayingLimit) return true;
|
||||
return false;
|
||||
},
|
||||
|
||||
get shouldSuperReactByDefault() {
|
||||
return settings.store.superReactByDefault && UserStore.getCurrentUser().premiumType != null;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -16,9 +16,11 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ChatBarButton } from "@api/ChatButtons";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
import { openModal } from "@utils/modal";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||
import { Alerts, Forms } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import { TranslateModal } from "./TranslateModal";
|
||||
|
@ -37,42 +39,49 @@ export function TranslateIcon({ height = 24, width = 24, className }: { height?:
|
|||
);
|
||||
}
|
||||
|
||||
export function TranslateChatBarIcon({ slateProps }: { slateProps: { type: { analyticsName: string; }; }; }) {
|
||||
export const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||
const { autoTranslate } = settings.use(["autoTranslate"]);
|
||||
|
||||
if (slateProps.type.analyticsName !== "normal")
|
||||
return null;
|
||||
if (!isMainChat) return null;
|
||||
|
||||
const toggle = () => settings.store.autoTranslate = !autoTranslate;
|
||||
const toggle = () => {
|
||||
const newState = !autoTranslate;
|
||||
settings.store.autoTranslate = newState;
|
||||
if (newState && settings.store.showAutoTranslateAlert !== false)
|
||||
Alerts.show({
|
||||
title: "Vencord Auto-Translate Enabled",
|
||||
body: <>
|
||||
<Forms.FormText>
|
||||
You just enabled auto translate (by right clicking the Translate icon). Any message you send will automatically be translated before being sent.
|
||||
</Forms.FormText>
|
||||
<Forms.FormText className={Margins.top16}>
|
||||
If this was an accident, disable it again, or it will change your message content before sending.
|
||||
</Forms.FormText>
|
||||
</>,
|
||||
cancelText: "Disable Auto-Translate",
|
||||
confirmText: "Got it",
|
||||
secondaryConfirmText: "Don't show again",
|
||||
onConfirmSecondary: () => settings.store.showAutoTranslateAlert = false,
|
||||
onCancel: () => settings.store.autoTranslate = false
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip text="Open Translate Modal">
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
aria-haspopup="dialog"
|
||||
aria-label="Open Translate Modal"
|
||||
size=""
|
||||
look={ButtonLooks.BLANK}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
innerClassName={ButtonWrapperClasses.button}
|
||||
onClick={e => {
|
||||
if (e.shiftKey) return toggle();
|
||||
<ChatBarButton
|
||||
tooltip="Open Translate Modal"
|
||||
onClick={e => {
|
||||
if (e.shiftKey) return toggle();
|
||||
|
||||
openModal(props => (
|
||||
<TranslateModal rootProps={props} />
|
||||
));
|
||||
}}
|
||||
onContextMenu={() => toggle()}
|
||||
style={{ padding: "0 4px" }}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
<TranslateIcon className={cl({ "auto-translate": autoTranslate })} />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
openModal(props => (
|
||||
<TranslateModal rootProps={props} />
|
||||
));
|
||||
}}
|
||||
onContextMenu={() => toggle()}
|
||||
buttonProps={{
|
||||
"aria-haspopup": "dialog"
|
||||
}}
|
||||
>
|
||||
<TranslateIcon className={cl({ "auto-translate": autoTranslate, "chat-button": true })} />
|
||||
</ChatBarButton>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
|
||||
import "./styles.css";
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
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 ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore, Menu } from "@webpack/common";
|
||||
|
@ -32,7 +32,7 @@ import { TranslateChatBarIcon, TranslateIcon } from "./TranslateIcon";
|
|||
import { handleTranslate, TranslationAccessory } from "./TranslationAccessory";
|
||||
import { translate } from "./utils";
|
||||
|
||||
const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => () => {
|
||||
const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => {
|
||||
if (!message.content) return;
|
||||
|
||||
const group = findGroupChildrenByChildId("copy-text", children);
|
||||
|
@ -55,25 +55,18 @@ export default definePlugin({
|
|||
name: "Translate",
|
||||
description: "Translate messages with Google Translate",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI"],
|
||||
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"],
|
||||
settings,
|
||||
contextMenus: {
|
||||
"message": messageCtxPatch
|
||||
},
|
||||
// not used, just here in case some other plugin wants it or w/e
|
||||
translate,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "ChannelTextAreaButtons",
|
||||
replacement: {
|
||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
start() {
|
||||
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
||||
|
||||
addContextMenuPatch("message", messageCtxPatch);
|
||||
addChatBarButton("vc-translate", TranslateChatBarIcon);
|
||||
|
||||
addButton("vc-translate", message => {
|
||||
if (!message.content) return null;
|
||||
|
@ -100,14 +93,8 @@ export default definePlugin({
|
|||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removeContextMenuPatch("message", messageCtxPatch);
|
||||
removeChatBarButton("vc-translate");
|
||||
removeButton("vc-translate");
|
||||
removeAccessory("vc-translation");
|
||||
},
|
||||
|
||||
chatBarIcon: (slateProps: any) => (
|
||||
<ErrorBoundary noop>
|
||||
<TranslateChatBarIcon slateProps={slateProps} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
});
|
||||
|
|
|
@ -49,4 +49,6 @@ export const settings = definePluginSettings({
|
|||
description: "Automatically translate your messages before sending. You can also shift/right click the translate button to toggle this",
|
||||
default: false
|
||||
}
|
||||
});
|
||||
}).withPrivateSettings<{
|
||||
showAutoTranslateAlert: boolean;
|
||||
}>();
|
||||
|
|
|
@ -35,3 +35,7 @@
|
|||
.vc-trans-auto-translate {
|
||||
color: var(--green-360);
|
||||
}
|
||||
|
||||
.vc-trans-chat-button {
|
||||
scale: 1.085;
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ export default definePlugin({
|
|||
{
|
||||
find: "UNREAD_IMPORTANT:",
|
||||
replacement: {
|
||||
match: /channel:(\i).{0,100}?channelEmoji,.{0,250}?\.children.{0,50}?:null/,
|
||||
match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/,
|
||||
replace: "$&,$self.TypingIndicator($1.id)"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
@ -24,7 +24,7 @@ import { Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@web
|
|||
|
||||
const EMBED_SUPPRESSED = 1 << 2;
|
||||
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => () => {
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => {
|
||||
const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0;
|
||||
if (!isEmbedSuppressed && !embeds.length) return;
|
||||
|
||||
|
@ -56,12 +56,7 @@ export default definePlugin({
|
|||
name: "UnsuppressEmbeds",
|
||||
authors: [Devs.rad, Devs.HypedDomi],
|
||||
description: "Allows you to unsuppress embeds in messages",
|
||||
|
||||
start() {
|
||||
addContextMenuPatch("message", messageContextMenuPatch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch("message", messageContextMenuPatch);
|
||||
},
|
||||
contextMenus: {
|
||||
"message": messageContextMenuPatch
|
||||
}
|
||||
});
|
||||
|
|
|
@ -96,7 +96,7 @@ export default definePlugin({
|
|||
patches: [
|
||||
// above message box
|
||||
{
|
||||
find: ".lastEditedByContainer",
|
||||
find: ".popularApplicationCommandIds,",
|
||||
replacement: {
|
||||
match: /\(0,\i\.jsx\)\(\i\.\i,{user:\i,setNote/,
|
||||
replace: "$self.patchPopout(arguments[0]),$&",
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
import "./index.css";
|
||||
|
||||
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { Settings, useSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
@ -30,6 +30,8 @@ import type { ReactNode } from "react";
|
|||
const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider");
|
||||
|
||||
function VencordPopout(onClose: () => void) {
|
||||
const { useQuickCss } = useSettings(["useQuickCss"]);
|
||||
|
||||
const pluginEntries = [] as ReactNode[];
|
||||
|
||||
for (const plugin of Object.values(Vencord.Plugins.plugins)) {
|
||||
|
@ -68,11 +70,10 @@ function VencordPopout(onClose: () => void) {
|
|||
/>
|
||||
<Menu.MenuCheckboxItem
|
||||
id="vc-toolbox-quickcss-toggle"
|
||||
checked={Settings.useQuickCss}
|
||||
checked={useQuickCss}
|
||||
label={"Enable QuickCSS"}
|
||||
action={() => {
|
||||
Settings.useQuickCss = !Settings.useQuickCss;
|
||||
onClose();
|
||||
Settings.useQuickCss = !useQuickCss;
|
||||
}}
|
||||
/>
|
||||
<Menu.MenuItem
|
||||
|
|
|
@ -16,17 +16,15 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { ImageIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { openImageModal } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { GuildMemberStore, Menu } from "@webpack/common";
|
||||
import { GuildMemberStore, IconUtils, Menu } from "@webpack/common";
|
||||
import type { Channel, Guild, User } from "discord-types/general";
|
||||
|
||||
const BannerStore = findByPropsLazy("getGuildBannerURL");
|
||||
|
||||
interface UserContextProps {
|
||||
channel: Channel;
|
||||
|
@ -82,7 +80,7 @@ function openImage(url: string) {
|
|||
});
|
||||
}
|
||||
|
||||
const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => () => {
|
||||
const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => {
|
||||
if (!user) return;
|
||||
const memberAvatar = GuildMemberStore.getMember(guildId!, user.id)?.avatar || null;
|
||||
|
||||
|
@ -91,19 +89,19 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
|
|||
<Menu.MenuItem
|
||||
id="view-avatar"
|
||||
label="View Avatar"
|
||||
action={() => openImage(BannerStore.getUserAvatarURL(user, true))}
|
||||
action={() => openImage(IconUtils.getUserAvatarURL(user, true))}
|
||||
icon={ImageIcon}
|
||||
/>
|
||||
{memberAvatar && (
|
||||
<Menu.MenuItem
|
||||
id="view-server-avatar"
|
||||
label="View Server Avatar"
|
||||
action={() => openImage(BannerStore.getGuildMemberAvatarURLSimple({
|
||||
action={() => openImage(IconUtils.getGuildMemberAvatarURLSimple({
|
||||
userId: user.id,
|
||||
avatar: memberAvatar,
|
||||
guildId,
|
||||
guildId: guildId!,
|
||||
canAnimate: true
|
||||
}, true))}
|
||||
}))}
|
||||
icon={ImageIcon}
|
||||
/>
|
||||
)}
|
||||
|
@ -111,7 +109,7 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
|
|||
));
|
||||
};
|
||||
|
||||
const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildContextProps) => () => {
|
||||
const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildContextProps) => {
|
||||
if (!guild) return;
|
||||
|
||||
const { id, icon, banner } = guild;
|
||||
|
@ -124,11 +122,11 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
|
|||
id="view-icon"
|
||||
label="View Icon"
|
||||
action={() =>
|
||||
openImage(BannerStore.getGuildIconURL({
|
||||
openImage(IconUtils.getGuildIconURL({
|
||||
id,
|
||||
icon,
|
||||
canAnimate: true
|
||||
}))
|
||||
})!)
|
||||
}
|
||||
icon={ImageIcon}
|
||||
/>
|
||||
|
@ -138,10 +136,7 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
|
|||
id="view-banner"
|
||||
label="View Banner"
|
||||
action={() =>
|
||||
openImage(BannerStore.getGuildBannerURL({
|
||||
id,
|
||||
banner,
|
||||
}, true))
|
||||
openImage(IconUtils.getGuildBannerURL(guild, true)!)
|
||||
}
|
||||
icon={ImageIcon}
|
||||
/>
|
||||
|
@ -160,14 +155,9 @@ export default definePlugin({
|
|||
|
||||
openImage,
|
||||
|
||||
start() {
|
||||
addContextMenuPatch("user-context", UserContext);
|
||||
addContextMenuPatch("guild-context", GuildContext);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch("user-context", UserContext);
|
||||
removeContextMenuPatch("guild-context", GuildContext);
|
||||
contextMenus: {
|
||||
"user-context": UserContext,
|
||||
"guild-context": GuildContext
|
||||
},
|
||||
|
||||
patches: [
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
|
@ -117,8 +117,8 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
function MakeContextCallback(name: "Guild" | "User" | "Channel") {
|
||||
const callback: NavContextMenuPatchCallback = (children, props) => () => {
|
||||
function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenuPatchCallback {
|
||||
return (children, props) => {
|
||||
const value = props[name.toLowerCase()];
|
||||
if (!value) return;
|
||||
if (props.label === i18n.Messages.CHANNEL_ACTIONS_MENU_LABEL) return; // random shit like notification settings
|
||||
|
@ -141,16 +141,19 @@ function MakeContextCallback(name: "Guild" | "User" | "Channel") {
|
|||
/>
|
||||
);
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
|
||||
|
||||
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"),
|
||||
"channel-context": MakeContextCallback("Channel"),
|
||||
"user-context": MakeContextCallback("User")
|
||||
},
|
||||
|
||||
start() {
|
||||
addButton("ViewRaw", msg => {
|
||||
|
@ -187,16 +190,9 @@ export default definePlugin({
|
|||
onContextMenu: handleContextMenu
|
||||
};
|
||||
});
|
||||
|
||||
addContextMenuPatch("guild-context", MakeContextCallback("Guild"));
|
||||
addContextMenuPatch("channel-context", MakeContextCallback("Channel"));
|
||||
addContextMenuPatch("user-context", MakeContextCallback("User"));
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeButton("CopyRawMessage");
|
||||
removeContextMenuPatch("guild-context", MakeContextCallback("Guild"));
|
||||
removeContextMenuPatch("channel-context", MakeContextCallback("Channel"));
|
||||
removeContextMenuPatch("user-context", MakeContextCallback("User"));
|
||||
removeButton("ViewRaw");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -18,15 +18,17 @@
|
|||
|
||||
import "./styles.css";
|
||||
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Microphone } from "@components/Icons";
|
||||
import { Link } from "@components/Link";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import definePlugin from "@utils/types";
|
||||
import { chooseFile } from "@utils/web";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { Button, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
|
||||
import { Button, Card, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
|
||||
import { ComponentType } from "react";
|
||||
|
||||
import { VoiceRecorderDesktop } from "./DesktopRecorder";
|
||||
|
@ -46,18 +48,30 @@ export type VoiceRecorder = ComponentType<{
|
|||
|
||||
const VoiceRecorder = IS_DISCORD_DESKTOP ? VoiceRecorderDesktop : VoiceRecorderWeb;
|
||||
|
||||
const ctxMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||
if (props.channel.guild_id && !(PermissionStore.can(PermissionsBits.SEND_VOICE_MESSAGES, props.channel) && PermissionStore.can(PermissionsBits.SEND_MESSAGES, props.channel))) return;
|
||||
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
id="vc-send-vmsg"
|
||||
label={
|
||||
<div className={OptionClasses.optionLabel}>
|
||||
<Microphone className={OptionClasses.optionIcon} height={24} width={24} />
|
||||
<div className={OptionClasses.optionName}>Send voice message</div>
|
||||
</div>
|
||||
}
|
||||
action={() => openModal(modalProps => <Modal modalProps={modalProps} />)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "VoiceMessages",
|
||||
description: "Allows you to send voice messages like on mobile. To do so, right click the upload button and click Send Voice Message",
|
||||
authors: [Devs.Ven, Devs.Vap, Devs.Nickyux],
|
||||
settings,
|
||||
|
||||
start() {
|
||||
addContextMenuPatch("channel-attach", ctxMenuPatch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch("channel-attach", ctxMenuPatch);
|
||||
contextMenus: {
|
||||
"channel-attach": ctxMenuPatch
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -164,6 +178,11 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) {
|
|||
fallbackValue: EMPTY_META,
|
||||
});
|
||||
|
||||
const isUnsupportedFormat = blob && (
|
||||
!blob.type.startsWith("audio/ogg")
|
||||
|| blob.type.includes("codecs") && !blob.type.includes("opus")
|
||||
);
|
||||
|
||||
return (
|
||||
<ModalRoot {...modalProps}>
|
||||
<ModalHeader>
|
||||
|
@ -200,6 +219,16 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) {
|
|||
recording={isRecording}
|
||||
/>
|
||||
|
||||
{isUnsupportedFormat && (
|
||||
<Card className={`vc-plugins-restart-card ${Margins.top16}`}>
|
||||
<Forms.FormText>Voice Messages have to be OggOpus to be playable on iOS. This file is <code>{blob.type}</code> so it will not be playable on iOS.</Forms.FormText>
|
||||
|
||||
<Forms.FormText className={Margins.top8}>
|
||||
To fix it, first convert it to OggOpus, for example using the <Link href="https://convertio.co/mp3-opus/">convertio web converter</Link>
|
||||
</Forms.FormText>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
</ModalContent>
|
||||
|
||||
<ModalFooter>
|
||||
|
@ -217,20 +246,3 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) {
|
|||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
||||
const ctxMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
|
||||
if (props.channel.guild_id && !(PermissionStore.can(PermissionsBits.SEND_VOICE_MESSAGES, props.channel) && PermissionStore.can(PermissionsBits.SEND_MESSAGES, props.channel))) return;
|
||||
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
id="vc-send-vmsg"
|
||||
label={
|
||||
<div className={OptionClasses.optionLabel}>
|
||||
<Microphone className={OptionClasses.optionIcon} height={24} width={24} />
|
||||
<div className={OptionClasses.optionName}>Send voice message</div>
|
||||
</div>
|
||||
}
|
||||
action={() => openModal(modalProps => <Modal modalProps={modalProps} />)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -47,18 +47,23 @@ const settings = definePluginSettings({
|
|||
});
|
||||
|
||||
const MEDIA_PROXY_URL = "https://media.discordapp.net";
|
||||
const CDN_URL = "https://cdn.discordapp.com";
|
||||
const CDN_URL = "cdn.discordapp.com";
|
||||
|
||||
function fixImageUrl(urlString: string, explodeWebp: boolean) {
|
||||
function fixImageUrl(urlString: string) {
|
||||
const url = new URL(urlString);
|
||||
if (url.origin === CDN_URL) return urlString;
|
||||
if (url.origin === MEDIA_PROXY_URL) return CDN_URL + url.pathname;
|
||||
if (url.host === CDN_URL) return urlString;
|
||||
|
||||
url.searchParams.delete("width");
|
||||
url.searchParams.delete("height");
|
||||
url.searchParams.set("quality", "lossless");
|
||||
if (explodeWebp && url.searchParams.get("format") === "webp")
|
||||
url.searchParams.set("format", "png");
|
||||
|
||||
if (url.origin === MEDIA_PROXY_URL) {
|
||||
url.host = CDN_URL;
|
||||
url.searchParams.delete("size");
|
||||
url.searchParams.delete("quality");
|
||||
url.searchParams.delete("format");
|
||||
} else {
|
||||
url.searchParams.set("quality", "lossless");
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
@ -199,7 +204,7 @@ export default definePlugin({
|
|||
],
|
||||
|
||||
async copyImage(url: string) {
|
||||
url = fixImageUrl(url, true);
|
||||
url = fixImageUrl(url);
|
||||
|
||||
let imageData = await fetch(url).then(r => r.blob());
|
||||
if (imageData.type !== "image/png") {
|
||||
|
@ -231,7 +236,7 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
async saveImage(url: string) {
|
||||
url = fixImageUrl(url, false);
|
||||
url = fixImageUrl(url);
|
||||
|
||||
const data = await fetchImage(url);
|
||||
if (!data) return;
|
||||
|
|
|
@ -69,14 +69,14 @@ function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) {
|
|||
function makeRenderMoreUsers(users: User[]) {
|
||||
return function renderMoreUsers(_label: string, _count: number) {
|
||||
return (
|
||||
<Tooltip text={users.slice(5).map(u => u.username).join(", ")} >
|
||||
<Tooltip text={users.slice(4).map(u => u.username).join(", ")} >
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
<div
|
||||
className={AvatarStyles.moreUsers}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
+{users.length - 5}
|
||||
+{users.length - 4}
|
||||
</div>
|
||||
)}
|
||||
</Tooltip >
|
||||
|
|
|
@ -106,7 +106,7 @@ export async function authorizeCloud() {
|
|||
|
||||
try {
|
||||
const res = await fetch(location, {
|
||||
headers: new Headers({ Accept: "application/json" })
|
||||
headers: { Accept: "application/json" }
|
||||
});
|
||||
const { secret } = await res.json();
|
||||
if (secret) {
|
||||
|
|
|
@ -42,6 +42,10 @@ export interface Dev {
|
|||
* If you are fine with attribution but don't want the badge, add badge: false
|
||||
*/
|
||||
export const Devs = /* #__PURE__*/ Object.freeze({
|
||||
Nobody: {
|
||||
name: "Nobody",
|
||||
id: 0n,
|
||||
},
|
||||
Ven: {
|
||||
name: "Vendicated",
|
||||
id: 343383572805058560n
|
||||
|
@ -359,10 +363,6 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
name: "bb010g",
|
||||
id: 72791153467990016n,
|
||||
},
|
||||
Lumap: {
|
||||
name: "lumap",
|
||||
id: 635383782576357407n
|
||||
},
|
||||
Dolfies: {
|
||||
name: "Dolfies",
|
||||
id: 852892297661906993n,
|
||||
|
@ -403,6 +403,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
name: "maisy",
|
||||
id: 257109471589957632n,
|
||||
},
|
||||
Mopi: {
|
||||
name: "Mopi",
|
||||
id: 1022189106614243350n
|
||||
},
|
||||
Grzesiek11: {
|
||||
name: "Grzesiek11",
|
||||
id: 368475654662127616n,
|
||||
|
@ -414,6 +418,14 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
coolelectronics: {
|
||||
name: "coolelectronics",
|
||||
id: 696392247205298207n,
|
||||
},
|
||||
Av32000: {
|
||||
name: "Av32000",
|
||||
id: 593436735380127770n,
|
||||
},
|
||||
Kyuuhachi: {
|
||||
name: "Kyuuhachi",
|
||||
id: 236588665420251137n,
|
||||
}
|
||||
} satisfies Record<string, Dev>);
|
||||
|
||||
|
|
|
@ -118,10 +118,10 @@ export async function putCloudSettings(manual?: boolean) {
|
|||
try {
|
||||
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
|
||||
method: "PUT",
|
||||
headers: new Headers({
|
||||
headers: {
|
||||
Authorization: await getCloudAuth(),
|
||||
"Content-Type": "application/octet-stream"
|
||||
}),
|
||||
},
|
||||
body: deflateSync(new TextEncoder().encode(settings))
|
||||
});
|
||||
|
||||
|
@ -162,11 +162,11 @@ export async function getCloudSettings(shouldNotify = true, force = false) {
|
|||
try {
|
||||
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
|
||||
method: "GET",
|
||||
headers: new Headers({
|
||||
headers: {
|
||||
Authorization: await getCloudAuth(),
|
||||
Accept: "application/octet-stream",
|
||||
"If-None-Match": Settings.cloud.settingsSyncVersion.toString()
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status === 404) {
|
||||
|
@ -251,9 +251,7 @@ export async function deleteCloudSettings() {
|
|||
try {
|
||||
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
|
||||
method: "DELETE",
|
||||
headers: new Headers({
|
||||
Authorization: await getCloudAuth()
|
||||
}),
|
||||
headers: { Authorization: await getCloudAuth() },
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
import { Command } from "@api/Commands";
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { FluxEvents } from "@webpack/types";
|
||||
import { Promisable } from "type-fest";
|
||||
|
||||
|
@ -115,6 +116,10 @@ export interface PluginDef {
|
|||
flux?: {
|
||||
[E in FluxEvents]?: (event: any) => void;
|
||||
};
|
||||
/**
|
||||
* Allows you to manipulate context menus
|
||||
*/
|
||||
contextMenus?: Record<string, NavContextMenuPatchCallback>;
|
||||
/**
|
||||
* Allows you to add custom actions to the Vencord Toolbox.
|
||||
* The key will be used as text for the button
|
||||
|
|
|
@ -51,7 +51,7 @@ export let Avatar: t.Avatar;
|
|||
/** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */
|
||||
export let useToken: t.useToken;
|
||||
|
||||
export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", m => m?.type?.toString().includes("MASKED_LINK)"));
|
||||
export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", filters.componentByCode("MASKED_LINK)"));
|
||||
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"));
|
||||
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
|
||||
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
|
||||
export const TextAndImagesSettingsStores = findByPropsLazy("MessageDisplayCompact");
|
||||
export const StatusSettingsStores = findByPropsLazy("ShowCurrentGame");
|
||||
import * as t from "./types/settingsStores";
|
||||
|
||||
|
||||
export const TextAndImagesSettingsStores = findByPropsLazy("MessageDisplayCompact") as Record<string, t.SettingsStore>;
|
||||
export const StatusSettingsStores = findByPropsLazy("ShowCurrentGame") as Record<string, t.SettingsStore>;
|
||||
|
||||
export const UserSettingsActionCreators = findByPropsLazy("PreloadedUserSettingsActionCreators");
|
||||
|
|
3
src/webpack/common/types/components.d.ts
vendored
3
src/webpack/common/types/components.d.ts
vendored
|
@ -16,7 +16,6 @@
|
|||
* 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";
|
||||
|
||||
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
|
||||
|
@ -154,7 +153,7 @@ export type Switch = ComponentType<PropsWithChildren<{
|
|||
}>>;
|
||||
|
||||
export type Timestamp = ComponentType<PropsWithChildren<{
|
||||
timestamp: Moment;
|
||||
timestamp: Date;
|
||||
isEdited?: boolean;
|
||||
|
||||
className?: string;
|
||||
|
|
4
src/webpack/common/types/index.d.ts
vendored
4
src/webpack/common/types/index.d.ts
vendored
|
@ -16,9 +16,11 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export * from "./classes";
|
||||
export * from "./components";
|
||||
export * from "./fluxEvents";
|
||||
export * from "./i18nMessages";
|
||||
export * from "./menu";
|
||||
export * from "./settingsStores";
|
||||
export * from "./stores";
|
||||
export * from "./utils";
|
||||
|
||||
|
|
11
src/webpack/common/types/settingsStores.ts
Normal file
11
src/webpack/common/types/settingsStores.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface SettingsStore<T = any> {
|
||||
getSetting(): T;
|
||||
updateSetting(value: T): void;
|
||||
useSetting(): T;
|
||||
}
|
46
src/webpack/common/types/utils.d.ts
vendored
46
src/webpack/common/types/utils.d.ts
vendored
|
@ -16,6 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Guild, GuildMember } from "discord-types/general";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import type { FluxEvents } from "./fluxEvents";
|
||||
|
@ -58,6 +59,7 @@ export interface Alerts {
|
|||
onCancel?(): void;
|
||||
onConfirm?(): void;
|
||||
onConfirmSecondary?(): void;
|
||||
onCloseCallback?(): void;
|
||||
}): void;
|
||||
/** This is a noop, it does nothing. */
|
||||
close(): void;
|
||||
|
@ -182,3 +184,47 @@ export interface NavigationRouter {
|
|||
getLastRouteChangeSource(): any;
|
||||
getLastRouteChangeSourceLocationStack(): any;
|
||||
}
|
||||
|
||||
export interface IconUtils {
|
||||
getUserAvatarURL(user: User, canAnimate?: boolean, size?: number, format?: string): string;
|
||||
getDefaultAvatarURL(id: string, discriminator?: string): string;
|
||||
getUserBannerURL(data: { id: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined;
|
||||
getAvatarDecorationURL(dara: { avatarDecoration: string, size: number; canCanimate?: boolean; }): string | undefined;
|
||||
|
||||
getGuildMemberAvatarURL(member: GuildMember, canAnimate?: string): string | null;
|
||||
getGuildMemberAvatarURLSimple(data: { guildId: string, userId: string, avatar: string, canAnimate?: boolean; size?: number; }): string;
|
||||
getGuildMemberBannerURL(data: { id: string, guildId: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined;
|
||||
|
||||
getGuildIconURL(data: { id: string, icon?: string, size?: number, canAnimate?: boolean; }): string | undefined;
|
||||
getGuildBannerURL(guild: Guild, canAnimate?: boolean): string | null;
|
||||
|
||||
getChannelIconURL(data: { id: string; icon?: string; applicationId?: string; size?: number; }): string | undefined;
|
||||
getEmojiURL(data: { id: string, animated: boolean, size: number, forcePNG?: boolean; }): string;
|
||||
|
||||
hasAnimatedGuildIcon(guild: Guild): boolean;
|
||||
isAnimatedIconHash(hash: string): boolean;
|
||||
|
||||
getGuildSplashURL: any;
|
||||
getGuildDiscoverySplashURL: any;
|
||||
getGuildHomeHeaderURL: any;
|
||||
getResourceChannelIconURL: any;
|
||||
getNewMemberActionIconURL: any;
|
||||
getGuildTemplateIconURL: any;
|
||||
getApplicationIconURL: any;
|
||||
getGameAssetURL: any;
|
||||
getVideoFilterAssetURL: any;
|
||||
|
||||
getGuildMemberAvatarSource: any;
|
||||
getUserAvatarSource: any;
|
||||
getGuildSplashSource: any;
|
||||
getGuildDiscoverySplashSource: any;
|
||||
makeSource: any;
|
||||
getGameAssetSource: any;
|
||||
getGuildIconSource: any;
|
||||
getGuildTemplateIconSource: any;
|
||||
getGuildBannerSource: any;
|
||||
getGuildHomeHeaderSource: any;
|
||||
getChannelIconSource: any;
|
||||
getApplicationIconSource: any;
|
||||
getAnimatableSourceWithFallback: any;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue