Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord
This commit is contained in:
commit
9e89449383
64 changed files with 962 additions and 392 deletions
|
@ -62,7 +62,7 @@ function GM_fetch(url, opt) {
|
||||||
resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
|
resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
|
||||||
resp.text = () => blobTo("text", blob);
|
resp.text = () => blobTo("text", blob);
|
||||||
resp.json = async () => JSON.parse(await 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;
|
resp.ok = resp.status >= 200 && resp.status < 300;
|
||||||
resolve(resp);
|
resolve(resp);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.7.0",
|
"version": "1.7.2",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
@ -428,10 +428,11 @@ function runTime(token: string) {
|
||||||
|
|
||||||
if (searchType === "findComponent") method = "find";
|
if (searchType === "findComponent") method = "find";
|
||||||
if (searchType === "findExportedComponent") method = "findByProps";
|
if (searchType === "findExportedComponent") method = "findByProps";
|
||||||
if (searchType === "waitFor" || searchType === "waitForComponent" || searchType === "waitForStore") {
|
if (searchType === "waitFor" || searchType === "waitForComponent") {
|
||||||
if (typeof args[0] === "string") method = "findByProps";
|
if (typeof args[0] === "string") method = "findByProps";
|
||||||
else method = "find";
|
else method = "find";
|
||||||
}
|
}
|
||||||
|
if (searchType === "waitForStore") method = "findStore";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let result: any;
|
let result: any;
|
||||||
|
|
|
@ -17,22 +17,20 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
|
import { Menu, React } from "@webpack/common";
|
||||||
import type { ReactElement } from "react";
|
import type { ReactElement } from "react";
|
||||||
|
|
||||||
type ContextMenuPatchCallbackReturn = (() => void) | void;
|
|
||||||
/**
|
/**
|
||||||
* @param children The rendered context menu elements
|
* @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
|
* @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 navId The navId of the context menu being patched
|
||||||
* @param children The rendered context menu elements
|
* @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
|
* @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");
|
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 id The id of the child. If an array is specified, all ids will be tried
|
||||||
* @param children The context menu children
|
* @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) {
|
for (const child of children) {
|
||||||
if (child == null) continue;
|
if (child == null) continue;
|
||||||
|
|
||||||
|
if (Array.isArray(child)) {
|
||||||
|
const found = findGroupChildrenByChildId(id, child);
|
||||||
|
if (found !== null) return found;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(Array.isArray(id) && id.some(id => child.props?.id === id))
|
(Array.isArray(id) && id.some(id => child.props?.id === id))
|
||||||
|| child.props?.id === id
|
|| child.props?.id === id
|
||||||
) return _itemsArray ?? null;
|
) return children;
|
||||||
|
|
||||||
let nextChildren = child.props?.children;
|
let nextChildren = child.props?.children;
|
||||||
if (nextChildren) {
|
if (nextChildren) {
|
||||||
|
@ -109,7 +112,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
||||||
child.props.children = nextChildren;
|
child.props.children = nextChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
const found = findGroupChildrenByChildId(id, nextChildren, nextChildren);
|
const found = findGroupChildrenByChildId(id, nextChildren);
|
||||||
if (found !== null) return found;
|
if (found !== null) return found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,9 +129,12 @@ interface ContextMenuProps {
|
||||||
onClose: (callback: (...args: Array<any>) => any) => void;
|
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 ??= [];
|
props.contextMenuApiArguments ??= [];
|
||||||
const contextMenuPatches = navPatches.get(props.navId);
|
const contextMenuPatches = navPatches.get(props.navId);
|
||||||
|
|
||||||
|
@ -137,8 +143,7 @@ export function _patchContextMenu(props: ContextMenuProps) {
|
||||||
if (contextMenuPatches) {
|
if (contextMenuPatches) {
|
||||||
for (const patch of contextMenuPatches) {
|
for (const patch of contextMenuPatches) {
|
||||||
try {
|
try {
|
||||||
const callback = patch(props.children, ...props.contextMenuApiArguments);
|
patch(props.children, ...props.contextMenuApiArguments);
|
||||||
if (!patchedMenus.has(props)) callback?.();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
||||||
}
|
}
|
||||||
|
@ -147,12 +152,30 @@ export function _patchContextMenu(props: ContextMenuProps) {
|
||||||
|
|
||||||
for (const patch of globalPatches) {
|
for (const patch of globalPatches) {
|
||||||
try {
|
try {
|
||||||
const callback = patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
||||||
if (!patchedMenus.has(props)) callback?.();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error("Global patch errored,", 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,13 +223,13 @@ export const Settings = makeProxy(settings);
|
||||||
export function useSettings(paths?: UseSettings<Settings>[]) {
|
export function useSettings(paths?: UseSettings<Settings>[]) {
|
||||||
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
||||||
|
|
||||||
const onUpdate: SubscriptionCallback = paths
|
if (paths) {
|
||||||
? (value, path) => paths.includes(path as UseSettings<Settings>) && forceUpdate()
|
(forceUpdate as SubscriptionCallback)._paths = paths;
|
||||||
: forceUpdate;
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
subscriptions.add(onUpdate);
|
subscriptions.add(forceUpdate);
|
||||||
return () => void subscriptions.delete(onUpdate);
|
return () => void subscriptions.delete(forceUpdate);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return Settings;
|
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 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 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) {
|
export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) {
|
||||||
if (path)
|
if (path) {
|
||||||
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
|
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
|
||||||
|
}
|
||||||
|
|
||||||
subscriptions.add(onUpdate);
|
subscriptions.add(onUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,7 @@ function validateUrl(url: string) {
|
||||||
async function eraseAllData() {
|
async function eraseAllData() {
|
||||||
const res = await fetch(new URL("/v1/", getCloudUrl()), {
|
const res = await fetch(new URL("/v1/", getCloudUrl()), {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: new Headers({
|
headers: { Authorization: await getCloudAuth() }
|
||||||
Authorization: await getCloudAuth()
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { debounce } from "@utils/debounce";
|
||||||
import { IpcEvents } from "@utils/IpcEvents";
|
import { IpcEvents } from "@utils/IpcEvents";
|
||||||
import { Queue } from "@utils/Queue";
|
import { Queue } from "@utils/Queue";
|
||||||
import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron";
|
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 { open, readdir, readFile, writeFile } from "fs/promises";
|
||||||
import { join, normalize } from "path";
|
import { join, normalize } from "path";
|
||||||
|
|
||||||
|
@ -126,16 +126,23 @@ ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => {
|
||||||
|
|
||||||
|
|
||||||
export function initIpc(mainWindow: BrowserWindow) {
|
export function initIpc(mainWindow: BrowserWindow) {
|
||||||
|
let quickCssWatcher: FSWatcher | undefined;
|
||||||
|
|
||||||
open(QUICKCSS_PATH, "a+").then(fd => {
|
open(QUICKCSS_PATH, "a+").then(fd => {
|
||||||
fd.close();
|
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());
|
mainWindow.webContents.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss());
|
||||||
}, 50));
|
}, 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.webContents.postMessage(IpcEvents.THEME_UPDATE, void 0);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
mainWindow.once("closed", () => {
|
||||||
|
quickCssWatcher?.close();
|
||||||
|
themesWatcher.close();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
|
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
|
||||||
|
|
|
@ -49,9 +49,12 @@ async function getRepo() {
|
||||||
async function calculateGitChanges() {
|
async function calculateGitChanges() {
|
||||||
await git("fetch");
|
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();
|
const commits = res.stdout.trim();
|
||||||
return commits ? commits.split("\n").map(line => {
|
return commits ? commits.split("\n").map(line => {
|
||||||
|
|
|
@ -22,15 +22,15 @@ import definePlugin from "@utils/types";
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "ContextMenuAPI",
|
name: "ContextMenuAPI",
|
||||||
description: "API for adding/removing items to/from context menus.",
|
description: "API for adding/removing items to/from context menus.",
|
||||||
authors: [Devs.Nuckyz, Devs.Ven],
|
authors: [Devs.Nuckyz, Devs.Ven, Devs.Kyuuhachi],
|
||||||
required: true,
|
required: true,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "♫ (つ。◕‿‿◕。)つ ♪",
|
find: "♫ (つ。◕‿‿◕。)つ ♪",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /let{navId:/,
|
match: /(?=let{navId:)(?<=function \i\((\i)\).+?)/,
|
||||||
replace: "Vencord.Api.ContextMenu._patchContextMenu(arguments[0]);$&"
|
replace: "$1=Vencord.Api.ContextMenu._usePatchContextMenu($1);"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addContextMenuPatch } from "@api/ContextMenu";
|
import { findGroupChildrenByChildId } from "@api/ContextMenu";
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
@ -30,21 +30,21 @@ export default definePlugin({
|
||||||
authors: [Devs.Ven, Devs.Megu],
|
authors: [Devs.Ven, Devs.Megu],
|
||||||
required: true,
|
required: true,
|
||||||
|
|
||||||
start() {
|
contextMenus: {
|
||||||
// The settings shortcuts in the user settings cog context menu
|
// The settings shortcuts in the user settings cog context menu
|
||||||
// read the elements from a hardcoded map which for obvious reason
|
// read the elements from a hardcoded map which for obvious reason
|
||||||
// doesn't contain our sections. This patches the actions of our
|
// doesn't contain our sections. This patches the actions of our
|
||||||
// sections to manually use SettingsRouter (which only works on desktop
|
// sections to manually use SettingsRouter (which only works on desktop
|
||||||
// but the context menu is usually not available on mobile anyway)
|
// but the context menu is usually not available on mobile anyway)
|
||||||
addContextMenuPatch("user-settings-cog", children => () => {
|
"user-settings-cog"(children) {
|
||||||
const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any;
|
const section = findGroupChildrenByChildId("VencordSettings", children);
|
||||||
section?.forEach(c => {
|
section?.forEach(c => {
|
||||||
const id = c?.props?.id;
|
const id = c?.props?.id;
|
||||||
if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) {
|
if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) {
|
||||||
c.props.action = () => SettingsRouter.open(id);
|
c!.props.action = () => SettingsRouter.open(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
patches: [{
|
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)
|
||||||
|
|
81
src/plugins/betterRoleContext/index.tsx
Normal file
81
src/plugins/betterRoleContext/index.tsx
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* 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, getGuildRoles } 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();
|
||||||
|
if (!guild) return;
|
||||||
|
|
||||||
|
const role = getGuildRoles(guild.id)[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/>.
|
* 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 { ScreenshareIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { openImageModal } from "@utils/discord";
|
import { openImageModal } from "@utils/discord";
|
||||||
|
@ -60,7 +60,7 @@ export const handleViewPreview = async ({ guildId, channelId, ownerId }: Applica
|
||||||
openImageModal(previewUrl);
|
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);
|
const stream = ApplicationStreamingStore.getAnyStreamForUser(userId);
|
||||||
if (!stream) return;
|
if (!stream) return;
|
||||||
|
|
||||||
|
@ -89,12 +89,8 @@ export default definePlugin({
|
||||||
name: "BiggerStreamPreview",
|
name: "BiggerStreamPreview",
|
||||||
description: "This plugin allows you to enlarge stream previews",
|
description: "This plugin allows you to enlarge stream previews",
|
||||||
authors: [Devs.phil],
|
authors: [Devs.phil],
|
||||||
start: () => {
|
contextMenus: {
|
||||||
addContextMenuPatch("user-context", userContextPatch);
|
"user-context": userContextPatch,
|
||||||
addContextMenuPatch("stream-context", streamContextPatch);
|
"stream-context": streamContextPatch
|
||||||
},
|
|
||||||
stop: () => {
|
|
||||||
removeContextMenuPatch("user-context", userContextPatch);
|
|
||||||
removeContextMenuPatch("stream-context", streamContextPatch);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
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)");
|
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];
|
return (result === null) ? null : result[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapReject(arr, mapFunc, rejectFunc = _.isNull) {
|
function mapReject(arr, mapFunc) {
|
||||||
return _.reject(arr.map(mapFunc), rejectFunc);
|
return arr.map(mapFunc).filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateColorVars(color: string) {
|
function updateColorVars(color: string) {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { LinkIcon } from "@components/Icons";
|
import { LinkIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
@ -29,7 +29,7 @@ interface UserContextProps {
|
||||||
user: User;
|
user: User;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => () => {
|
const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
children.push(
|
children.push(
|
||||||
|
@ -46,12 +46,7 @@ export default definePlugin({
|
||||||
name: "CopyUserURLs",
|
name: "CopyUserURLs",
|
||||||
authors: [Devs.castdrian],
|
authors: [Devs.castdrian],
|
||||||
description: "Adds a 'Copy User URL' option to the user context menu.",
|
description: "Adds a 'Copy User URL' option to the user context menu.",
|
||||||
|
contextMenus: {
|
||||||
start() {
|
"user-context": UserContextMenuPatch
|
||||||
addContextMenuPatch("user-context", UserContextMenuPatch);
|
}
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeContextMenuPatch("user-context", UserContextMenuPatch);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -175,7 +175,7 @@ const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
startTime: {
|
startTime: {
|
||||||
type: OptionType.NUMBER,
|
type: OptionType.NUMBER,
|
||||||
description: "Start timestamp (only for custom timestamp mode)",
|
description: "Start timestamp in milisecond (only for custom timestamp mode)",
|
||||||
onChange: onChange,
|
onChange: onChange,
|
||||||
disabled: isTimestampDisabled,
|
disabled: isTimestampDisabled,
|
||||||
isValid: (value: number) => {
|
isValid: (value: number) => {
|
||||||
|
@ -185,7 +185,7 @@ const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
endTime: {
|
endTime: {
|
||||||
type: OptionType.NUMBER,
|
type: OptionType.NUMBER,
|
||||||
description: "End timestamp (only for custom timestamp mode)",
|
description: "End timestamp in milisecond (only for custom timestamp mode)",
|
||||||
onChange: onChange,
|
onChange: onChange,
|
||||||
disabled: isTimestampDisabled,
|
disabled: isTimestampDisabled,
|
||||||
isValid: (value: number) => {
|
isValid: (value: number) => {
|
||||||
|
@ -313,12 +313,12 @@ async function createActivity(): Promise<Activity | undefined> {
|
||||||
switch (settings.store.timestampMode) {
|
switch (settings.store.timestampMode) {
|
||||||
case TimestampMode.NOW:
|
case TimestampMode.NOW:
|
||||||
activity.timestamps = {
|
activity.timestamps = {
|
||||||
start: Math.floor(Date.now() / 1000)
|
start: Date.now()
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case TimestampMode.TIME:
|
case TimestampMode.TIME:
|
||||||
activity.timestamps = {
|
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;
|
break;
|
||||||
case TimestampMode.CUSTOM:
|
case TimestampMode.CUSTOM:
|
||||||
|
|
|
@ -131,9 +131,10 @@ export default definePlugin({
|
||||||
getDecorAvatarDecorationURL({ avatarDecoration, canAnimate }: { avatarDecoration: AvatarDecoration | null; canAnimate?: boolean; }) {
|
getDecorAvatarDecorationURL({ avatarDecoration, canAnimate }: { avatarDecoration: AvatarDecoration | null; canAnimate?: boolean; }) {
|
||||||
// Only Decor avatar decorations have this SKU ID
|
// Only Decor avatar decorations have this SKU ID
|
||||||
if (avatarDecoration?.skuId === SKU_ID) {
|
if (avatarDecoration?.skuId === SKU_ID) {
|
||||||
const url = new URL(`${CDN_URL}/${avatarDecoration.asset}.png`);
|
const parts = avatarDecoration.asset.split("_");
|
||||||
url.searchParams.set("animate", (!!canAnimate && isAnimatedAvatarDecoration(avatarDecoration.asset)).toString());
|
// Remove a_ prefix if it's animated and animation is disabled
|
||||||
return url.toString();
|
if (isAnimatedAvatarDecoration(avatarDecoration.asset) && !canAnimate) parts.shift();
|
||||||
|
return `${CDN_URL}/${parts.join("_")}.png`;
|
||||||
} else if (avatarDecoration?.skuId === RAW_SKU_ID) {
|
} else if (avatarDecoration?.skuId === RAW_SKU_ID) {
|
||||||
return avatarDecoration.asset;
|
return avatarDecoration.asset;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
|
@ -312,7 +312,7 @@ function isGifUrl(url: string) {
|
||||||
return new URL(url).pathname.endsWith(".gif");
|
return new URL(url).pathname.endsWith(".gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
|
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||||
const { favoriteableId, itemHref, itemSrc, favoriteableType } = props ?? {};
|
const { favoriteableId, itemHref, itemSrc, favoriteableType } = props ?? {};
|
||||||
|
|
||||||
if (!favoriteableId) return;
|
if (!favoriteableId) return;
|
||||||
|
@ -341,7 +341,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) =
|
||||||
findGroupChildrenByChildId("copy-link", children)?.push(menuItem);
|
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 ?? {};
|
const { id, name, type } = props?.target?.dataset ?? {};
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
|
@ -363,14 +363,8 @@ export default definePlugin({
|
||||||
description: "Allows you to clone Emotes & Stickers to your own server (right click them)",
|
description: "Allows you to clone Emotes & Stickers to your own server (right click them)",
|
||||||
tags: ["StickerCloner"],
|
tags: ["StickerCloner"],
|
||||||
authors: [Devs.Ven, Devs.Nuckyz],
|
authors: [Devs.Ven, Devs.Nuckyz],
|
||||||
|
contextMenus: {
|
||||||
start() {
|
"message": messageContextMenuPatch,
|
||||||
addContextMenuPatch("message", messageContextMenuPatch);
|
"expression-picker": expressionPickerPatch
|
||||||
addContextMenuPatch("expression-picker", expressionPickerPatch);
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeContextMenuPatch("message", messageContextMenuPatch);
|
|
||||||
removeContextMenuPatch("expression-picker", expressionPickerPatch);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -162,7 +162,7 @@ const settings = definePluginSettings({
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
hyperLinkText: {
|
hyperLinkText: {
|
||||||
description: "What text the hyperlink should use. {{NAME}} will be replaced with the emoji name.",
|
description: "What text the hyperlink should use. {{NAME}} will be replaced with the emoji/sticker name.",
|
||||||
type: OptionType.STRING,
|
type: OptionType.STRING,
|
||||||
default: "{{NAME}}"
|
default: "{{NAME}}"
|
||||||
}
|
}
|
||||||
|
@ -369,8 +369,8 @@ export default definePlugin({
|
||||||
predicate: () => settings.store.transformEmojis,
|
predicate: () => settings.store.transformEmojis,
|
||||||
replacement: {
|
replacement: {
|
||||||
// Add the fake nitro emoji notice
|
// Add the fake nitro emoji notice
|
||||||
match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.*?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/,
|
match: /(?<=emojiDescription:)(\i)(?<=\1=\i\((\i)\).+?)/,
|
||||||
replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)`
|
replace: (_, reactNode, props) => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!${props}?.fakeNitroNode?.fake)`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Allow using custom app icons
|
// Allow using custom app icons
|
||||||
|
@ -474,7 +474,7 @@ export default definePlugin({
|
||||||
if (typeof firstContent === "string") {
|
if (typeof firstContent === "string") {
|
||||||
content[0] = firstContent.trimStart();
|
content[0] = firstContent.trimStart();
|
||||||
content[0] || content.shift();
|
content[0] || content.shift();
|
||||||
} else if (firstContent?.type === "span") {
|
} else if (typeof firstContent?.props?.children === "string") {
|
||||||
firstContent.props.children = firstContent.props.children.trimStart();
|
firstContent.props.children = firstContent.props.children.trimStart();
|
||||||
firstContent.props.children || content.shift();
|
firstContent.props.children || content.shift();
|
||||||
}
|
}
|
||||||
|
@ -484,7 +484,7 @@ export default definePlugin({
|
||||||
if (typeof lastContent === "string") {
|
if (typeof lastContent === "string") {
|
||||||
content[lastIndex] = lastContent.trimEnd();
|
content[lastIndex] = lastContent.trimEnd();
|
||||||
content[lastIndex] || content.pop();
|
content[lastIndex] || content.pop();
|
||||||
} else if (lastContent?.type === "span") {
|
} else if (typeof lastContent?.props?.children === "string") {
|
||||||
lastContent.props.children = lastContent.props.children.trimEnd();
|
lastContent.props.children = lastContent.props.children.trimEnd();
|
||||||
lastContent.props.children || content.pop();
|
lastContent.props.children || content.pop();
|
||||||
}
|
}
|
||||||
|
@ -585,13 +585,15 @@ export default definePlugin({
|
||||||
for (const [index, child] of children.entries()) children[index] = modifyChild(child);
|
for (const [index, child] of children.entries()) children[index] = modifyChild(child);
|
||||||
|
|
||||||
children = this.clearEmptyArrayItems(children);
|
children = this.clearEmptyArrayItems(children);
|
||||||
this.trimContent(children);
|
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return modifyChildren(lodash.cloneDeep(content));
|
const newContent = modifyChildren(lodash.cloneDeep(content));
|
||||||
|
this.trimContent(newContent);
|
||||||
|
|
||||||
|
return newContent;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
new Logger("FakeNitro").error(err);
|
new Logger("FakeNitro").error(err);
|
||||||
return content;
|
return content;
|
||||||
|
@ -791,8 +793,8 @@ export default definePlugin({
|
||||||
title: "Hold on!",
|
title: "Hold on!",
|
||||||
body: <div>
|
body: <div>
|
||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
You are trying to send/edit a message that contains a FakeNitro emoji or sticker
|
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.
|
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.
|
Are you sure you want to send this message? Your FakeNitro items will appear as a link only.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>
|
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>
|
||||||
|
@ -864,7 +866,9 @@ export default definePlugin({
|
||||||
const url = new URL(link);
|
const url = new URL(link);
|
||||||
url.searchParams.set("name", sticker.name);
|
url.searchParams.set("name", sticker.name);
|
||||||
|
|
||||||
messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}${s.useHyperLinks ? `[${sticker.name}](${url})` : url}`;
|
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;
|
extra.stickers!.length = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
5
src/plugins/friendsSince/README.md
Normal file
5
src/plugins/friendsSince/README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# FriendsSince
|
||||||
|
|
||||||
|
Shows when you became friends with someone in the user popout
|
||||||
|
|
||||||
|
![](https://github.com/Vendicated/Vencord/assets/45497981/bb258188-ab48-4c4d-9858-1e90ba41e926)
|
60
src/plugins/friendsSince/index.tsx
Normal file
60
src/plugins/friendsSince/index.tsx
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { React, RelationshipStore } from "@webpack/common";
|
||||||
|
|
||||||
|
const { Heading, Text } = findByPropsLazy("Heading", "Text");
|
||||||
|
const container = findByPropsLazy("memberSinceContainer");
|
||||||
|
const { getCreatedAtDate } = findByPropsLazy("getCreatedAtDate");
|
||||||
|
const clydeMoreInfo = findByPropsLazy("clydeMoreInfo");
|
||||||
|
const locale = findByPropsLazy("getLocale");
|
||||||
|
const lastSection = findByPropsLazy("lastSection");
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "FriendsSince",
|
||||||
|
description: "Shows when you became friends with someone in the user popout",
|
||||||
|
authors: [Devs.Elvyra],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: ".AnalyticsSections.USER_PROFILE}",
|
||||||
|
replacement: {
|
||||||
|
match: /\i.default,\{userId:(\i.id).{0,30}}\)/,
|
||||||
|
replace: "$&,$self.friendsSince({ userId: $1 })"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".UserPopoutUpsellSource.PROFILE_PANEL,",
|
||||||
|
replacement: {
|
||||||
|
match: /\i.default,\{userId:(\i)}\)/,
|
||||||
|
replace: "$&,$self.friendsSince({ userId: $1 })"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
friendsSince: ErrorBoundary.wrap(({ userId }: { userId: string; }) => {
|
||||||
|
const friendsSince = RelationshipStore.getSince(userId);
|
||||||
|
if (!friendsSince) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={lastSection.section}>
|
||||||
|
<Heading variant="eyebrow" className={clydeMoreInfo.title}>
|
||||||
|
Friends Since
|
||||||
|
</Heading>
|
||||||
|
|
||||||
|
<div className={container.memberSinceContainer}>
|
||||||
|
<Text variant="text-sm/normal" className={clydeMoreInfo.body}>
|
||||||
|
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, { noop: true })
|
||||||
|
});
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
import * as DataStore from "@api/DataStore";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
import { Devs } from "@utils/constants";
|
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 { 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 {
|
const enum ActivitiesTypes {
|
||||||
Game,
|
Game,
|
||||||
|
@ -69,7 +71,113 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||||
StatusSettingsStores.ShowCurrentGame.updateSetting(old => old);
|
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[];
|
ignoredActivities: IgnoredActivity[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -77,10 +185,26 @@ function getIgnoredActivities() {
|
||||||
return settings.store.ignoredActivities ??= [];
|
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({
|
export default definePlugin({
|
||||||
name: "IgnoreActivities",
|
name: "IgnoreActivities",
|
||||||
authors: [Devs.Nuckyz],
|
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,
|
settings,
|
||||||
|
|
||||||
|
@ -141,13 +265,17 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) {
|
isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) {
|
||||||
if (props.type === 0 || props.type === 3) {
|
if (isActivityTypeIgnored(props.type, props.application_id)) return false;
|
||||||
if (props.application_id != null) return !getIgnoredActivities().some(activity => activity.id === props.application_id);
|
|
||||||
else {
|
if (props.application_id != null) {
|
||||||
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
return !getIgnoredActivities().some(activity => activity.id === props.application_id) || settings.store.allowedIds.includes(props.application_id);
|
||||||
if (exePath) return !getIgnoredActivities().some(activity => activity.id === exePath);
|
} else {
|
||||||
|
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
||||||
|
if (exePath) {
|
||||||
|
return !getIgnoredActivities().some(activity => activity.id === exePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -16,14 +16,14 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
import { makeRange } from "@components/PluginSettings/components";
|
import { makeRange } from "@components/PluginSettings/components";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
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 type { Root } from "react-dom/client";
|
||||||
|
|
||||||
import { Magnifier, MagnifierProps } from "./components/Magnifier";
|
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(
|
children.push(
|
||||||
<Menu.MenuGroup id="image-zoom">
|
<Menu.MenuGroup id="image-zoom">
|
||||||
<Menu.MenuCheckboxItem
|
<Menu.MenuCheckboxItem
|
||||||
id="vc-square"
|
id="vc-square"
|
||||||
label="Square Lens"
|
label="Square Lens"
|
||||||
checked={settings.store.square}
|
checked={square}
|
||||||
action={() => {
|
action={() => {
|
||||||
settings.store.square = !settings.store.square;
|
settings.store.square = !square;
|
||||||
ContextMenuApi.closeContextMenu();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Menu.MenuCheckboxItem
|
<Menu.MenuCheckboxItem
|
||||||
id="vc-nearest-neighbour"
|
id="vc-nearest-neighbour"
|
||||||
label="Nearest Neighbour"
|
label="Nearest Neighbour"
|
||||||
checked={settings.store.nearestNeighbour}
|
checked={nearestNeighbour}
|
||||||
action={() => {
|
action={() => {
|
||||||
settings.store.nearestNeighbour = !settings.store.nearestNeighbour;
|
settings.store.nearestNeighbour = !nearestNeighbour;
|
||||||
ContextMenuApi.closeContextMenu();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Menu.MenuControlItem
|
<Menu.MenuControlItem
|
||||||
|
@ -196,6 +196,9 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
contextMenus: {
|
||||||
|
"image-context": imageContextMenuPatch
|
||||||
|
},
|
||||||
|
|
||||||
// to stop from rendering twice /shrug
|
// to stop from rendering twice /shrug
|
||||||
currentMagnifierElement: null as React.FunctionComponentElement<MagnifierProps & JSX.IntrinsicAttributes> | null,
|
currentMagnifierElement: null as React.FunctionComponentElement<MagnifierProps & JSX.IntrinsicAttributes> | null,
|
||||||
|
@ -245,7 +248,6 @@ export default definePlugin({
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
enableStyle(styles);
|
enableStyle(styles);
|
||||||
addContextMenuPatch("image-context", imageContextMenuPatch);
|
|
||||||
this.element = document.createElement("div");
|
this.element = document.createElement("div");
|
||||||
this.element.classList.add("MagnifierContainer");
|
this.element.classList.add("MagnifierContainer");
|
||||||
document.body.appendChild(this.element);
|
document.body.appendChild(this.element);
|
||||||
|
@ -256,6 +258,5 @@ export default definePlugin({
|
||||||
// so componenetWillUnMount gets called if Magnifier component is still alive
|
// so componenetWillUnMount gets called if Magnifier component is still alive
|
||||||
this.root && this.root.unmount();
|
this.root && this.root.unmount();
|
||||||
this.element?.remove();
|
this.element?.remove();
|
||||||
removeContextMenuPatch("image-context", imageContextMenuPatch);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { registerCommand, unregisterCommand } from "@api/Commands";
|
import { registerCommand, unregisterCommand } from "@api/Commands";
|
||||||
|
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Patch, Plugin, StartAt } from "@utils/types";
|
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) {
|
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
|
||||||
const { name, commands, flux } = p;
|
const { name, commands, flux, contextMenus } = p;
|
||||||
|
|
||||||
if (p.start) {
|
if (p.start) {
|
||||||
logger.info("Starting plugin", name);
|
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;
|
return true;
|
||||||
}, p => `startPlugin ${p.name}`);
|
}, p => `startPlugin ${p.name}`);
|
||||||
|
|
||||||
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
||||||
const { name, commands, flux } = p;
|
const { name, commands, flux, contextMenus } = p;
|
||||||
if (p.stop) {
|
if (p.stop) {
|
||||||
logger.info("Stopping plugin", name);
|
logger.info("Stopping plugin", name);
|
||||||
if (!p.started) {
|
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;
|
return true;
|
||||||
}, p => `stopPlugin ${p.name}`);
|
}, p => `stopPlugin ${p.name}`);
|
||||||
|
|
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/>.
|
* 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 ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getCurrentChannel } from "@utils/discord";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
import { findStoreLazy } from "@webpack";
|
import { findStoreLazy } from "@webpack";
|
||||||
import { SelectedChannelStore, Tooltip, useStateFromStores } from "@webpack/common";
|
|
||||||
import { FluxStore } from "@webpack/types";
|
import { FluxStore } from "@webpack/types";
|
||||||
|
|
||||||
const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; };
|
import { MemberCount } from "./MemberCount";
|
||||||
const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
|
|
||||||
|
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; }[]; };
|
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 sharedIntlNumberFormat = new Intl.NumberFormat();
|
||||||
const numberFormat = (value: number) => sharedIntlNumberFormat.format(value);
|
export const numberFormat = (value: number) => sharedIntlNumberFormat.format(value);
|
||||||
|
export const cl = classNameFactory("vc-membercount-");
|
||||||
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 default definePlugin({
|
export default definePlugin({
|
||||||
name: "MemberCount",
|
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],
|
authors: [Devs.Ven, Devs.Commandtechno],
|
||||||
|
settings,
|
||||||
|
|
||||||
patches: [{
|
patches: [
|
||||||
find: "{isSidebarVisible:",
|
{
|
||||||
replacement: {
|
find: "{isSidebarVisible:",
|
||||||
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
replacement: {
|
||||||
replace: ":[$1?.startsWith('members')?$self.render():null,$2"
|
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;
|
||||||
|
}
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import "./messageLogger.css";
|
import "./messageLogger.css";
|
||||||
|
|
||||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
@ -45,7 +45,7 @@ function addDeleteStyle() {
|
||||||
|
|
||||||
const REMOVE_HISTORY_ID = "ml-remove-history";
|
const REMOVE_HISTORY_ID = "ml-remove-history";
|
||||||
const TOGGLE_DELETE_STYLE_ID = "ml-toggle-style";
|
const TOGGLE_DELETE_STYLE_ID = "ml-toggle-style";
|
||||||
const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) => () => {
|
const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) => {
|
||||||
const { message } = props;
|
const { message } = props;
|
||||||
const { deleted, editHistory, id, channel_id } = message;
|
const { deleted, editHistory, id, channel_id } = message;
|
||||||
|
|
||||||
|
@ -94,13 +94,12 @@ export default definePlugin({
|
||||||
description: "Temporarily logs deleted and edited messages.",
|
description: "Temporarily logs deleted and edited messages.",
|
||||||
authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN],
|
authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN],
|
||||||
|
|
||||||
start() {
|
contextMenus: {
|
||||||
addDeleteStyle();
|
"message": patchMessageContextMenu
|
||||||
addContextMenuPatch("message", patchMessageContextMenu);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
start() {
|
||||||
removeContextMenuPatch("message", patchMessageContextMenu);
|
addDeleteStyle();
|
||||||
},
|
},
|
||||||
|
|
||||||
renderEdit(edit: { timestamp: any, content: string; }) {
|
renderEdit(edit: { timestamp: any, content: string; }) {
|
||||||
|
|
|
@ -47,8 +47,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded
|
find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.MUTUAL_GUILDS\}\),)(?=(\i\.bot).{0,20}(\(0,\i\.jsx\)\(.{0,100}id:))/,
|
match: /(?<=\.tabBarItem.{0,50}MUTUAL_GUILDS.+?}\),)(?=.+?(\(0,\i\.jsxs?\)\(.{0,100}id:))/,
|
||||||
replace: '($1||arguments[0].isCurrentUser)?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),'
|
replace: '(arguments[0].user.bot||arguments[0].isCurrentUser)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,8 +27,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "_ensureAudio(){",
|
find: "_ensureAudio(){",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /onloadeddata=\(\)=>\{.\.volume=/,
|
match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/,
|
||||||
replace: "$&$self.settings.store.notificationVolume/100*"
|
replace: "$self.settings.store.notificationVolume/100*"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
|
import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
|
||||||
import { getUniqueUsername } from "@utils/discord";
|
import { getGuildRoles, getUniqueUsername } from "@utils/discord";
|
||||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { ContextMenuApi, FluxDispatcher, GuildMemberStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
|
import { ContextMenuApi, FluxDispatcher, GuildMemberStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
|
||||||
import type { Guild } from "discord-types/general";
|
import type { Guild } from "discord-types/general";
|
||||||
|
@ -78,6 +78,8 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
|
||||||
const [selectedItemIndex, selectItem] = useState(0);
|
const [selectedItemIndex, selectItem] = useState(0);
|
||||||
const selectedItem = permissions[selectedItemIndex];
|
const selectedItem = permissions[selectedItemIndex];
|
||||||
|
|
||||||
|
const roles = getGuildRoles(guild.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalRoot
|
<ModalRoot
|
||||||
{...modalProps}
|
{...modalProps}
|
||||||
|
@ -100,7 +102,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
|
||||||
<div className={cl("perms-list")}>
|
<div className={cl("perms-list")}>
|
||||||
{permissions.map((permission, index) => {
|
{permissions.map((permission, index) => {
|
||||||
const user = UserStore.getUser(permission.id ?? "");
|
const user = UserStore.getUser(permission.id ?? "");
|
||||||
const role = guild.roles[permission.id ?? ""];
|
const role = roles[permission.id ?? ""];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
@ -201,7 +203,7 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str
|
||||||
id="vc-pw-view-as-role"
|
id="vc-pw-view-as-role"
|
||||||
label="View As Role"
|
label="View As Role"
|
||||||
action={() => {
|
action={() => {
|
||||||
const role = guild.roles[roleId];
|
const role = getGuildRoles(guild.id)[roleId];
|
||||||
if (!role) return;
|
if (!role) return;
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
|
|
|
@ -18,9 +18,10 @@
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { getGuildRoles } from "@utils/discord";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { ChannelStore, GuildMemberStore, GuildStore, Menu, PermissionsBits, UserStore } from "@webpack/common";
|
import { ChannelStore, GuildMemberStore, GuildStore, Menu, PermissionsBits, UserStore } from "@webpack/common";
|
||||||
import type { Guild, GuildMember } from "discord-types/general";
|
import type { Guild, GuildMember } from "discord-types/general";
|
||||||
|
@ -107,7 +108,7 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
permissions = Object.values(guild.roles).map(role => ({
|
permissions = Object.values(getGuildRoles(guild.id)).map(role => ({
|
||||||
type: PermissionType.Role,
|
type: PermissionType.Role,
|
||||||
...role
|
...role
|
||||||
}));
|
}));
|
||||||
|
@ -125,10 +126,10 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback {
|
function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback {
|
||||||
return (children, props) => () => {
|
return (children, props) => {
|
||||||
if (!props) return;
|
if (!props) return;
|
||||||
if ((type === MenuItemParentType.User && !props.user) || (type === MenuItemParentType.Guild && !props.guild) || (type === MenuItemParentType.Channel && (!props.channel || !props.guild)))
|
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);
|
const group = findGroupChildrenByChildId(childId, children);
|
||||||
|
|
||||||
|
@ -173,19 +174,10 @@ export default definePlugin({
|
||||||
|
|
||||||
UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBoder: boolean) => !!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBoder} />,
|
UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBoder: boolean) => !!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBoder} />,
|
||||||
|
|
||||||
userContextMenuPatch: makeContextMenuPatch("roles", MenuItemParentType.User),
|
contextMenus: {
|
||||||
channelContextMenuPatch: makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel),
|
"user-context": makeContextMenuPatch("roles", MenuItemParentType.User),
|
||||||
guildContextMenuPatch: makeContextMenuPatch("privacy", MenuItemParentType.Guild),
|
"channel-context": makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel),
|
||||||
|
"guild-context": makeContextMenuPatch("privacy", MenuItemParentType.Guild),
|
||||||
start() {
|
"guild-header-popout": makeContextMenuPatch("privacy", MenuItemParentType.Guild)
|
||||||
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);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,8 +17,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
|
import { getGuildRoles } from "@utils/discord";
|
||||||
import { wordsToTitle } from "@utils/text";
|
import { wordsToTitle } from "@utils/text";
|
||||||
import { GuildStore, i18n, Parser } from "@webpack/common";
|
import { i18n, Parser } from "@webpack/common";
|
||||||
import { Guild, GuildMember, Role } from "discord-types/general";
|
import { Guild, GuildMember, Role } from "discord-types/general";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
@ -67,7 +68,9 @@ export function getPermissionDescription(permission: string): ReactNode {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSortedRoles({ roles, id }: Guild, member: GuildMember) {
|
export function getSortedRoles({ id }: Guild, member: GuildMember) {
|
||||||
|
const roles = getGuildRoles(id);
|
||||||
|
|
||||||
return [...member.roles, id]
|
return [...member.roles, id]
|
||||||
.map(id => roles[id])
|
.map(id => roles[id])
|
||||||
.sort((a, b) => b.position - a.position);
|
.sort((a, b) => b.position - a.position);
|
||||||
|
@ -85,13 +88,13 @@ export function sortUserRoles(roles: Role[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortPermissionOverwrites<T extends { id: string; type: number; }>(overwrites: T[], guildId: string) {
|
export function sortPermissionOverwrites<T extends { id: string; type: number; }>(overwrites: T[], guildId: string) {
|
||||||
const guild = GuildStore.getGuild(guildId);
|
const roles = getGuildRoles(guildId);
|
||||||
|
|
||||||
return overwrites.sort((a, b) => {
|
return overwrites.sort((a, b) => {
|
||||||
if (a.type !== PermissionType.Role || b.type !== PermissionType.Role) return 0;
|
if (a.type !== PermissionType.Role || b.type !== PermissionType.Role) return 0;
|
||||||
|
|
||||||
const roleA = guild.roles[a.id];
|
const roleA = roles[a.id];
|
||||||
const roleB = guild.roles[b.id];
|
const roleB = roles[b.id];
|
||||||
|
|
||||||
return roleB.position - roleA.position;
|
return roleB.position - roleA.position;
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { Menu } from "@webpack/common";
|
import { Menu } from "@webpack/common";
|
||||||
|
|
||||||
import { isPinned, movePin, PinOrder, settings, snapshotArray, togglePin } from "./settings";
|
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);
|
const container = findGroupChildrenByChildId("leave-channel", children);
|
||||||
if (container)
|
if (container)
|
||||||
container.unshift(PinMenuItem(props.channel.id));
|
container.unshift(PinMenuItem(props.channel.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserContext: NavContextMenuPatchCallback = (children, props) => () => {
|
const UserContext: NavContextMenuPatchCallback = (children, props) => {
|
||||||
const container = findGroupChildrenByChildId("close-dm", children);
|
const container = findGroupChildrenByChildId("close-dm", children);
|
||||||
if (container) {
|
if (container) {
|
||||||
const idx = container.findIndex(c => c?.props?.id === "close-dm");
|
const idx = container.findIndex(c => c?.props?.id === "close-dm");
|
||||||
|
@ -64,12 +64,7 @@ const UserContext: NavContextMenuPatchCallback = (children, props) => () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function addContextMenus() {
|
export const contextMenus = {
|
||||||
addContextMenuPatch("gdm-context", GroupDMContext);
|
"gdm-context": GroupDMContext,
|
||||||
addContextMenuPatch("user-context", UserContext);
|
"user-context": UserContext
|
||||||
}
|
};
|
||||||
|
|
||||||
export function removeContextMenus() {
|
|
||||||
removeContextMenuPatch("gdm-context", GroupDMContext);
|
|
||||||
removeContextMenuPatch("user-context", UserContext);
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Channel } from "discord-types/general";
|
import { Channel } from "discord-types/general";
|
||||||
|
|
||||||
import { addContextMenus, removeContextMenus } from "./contextMenus";
|
import { contextMenus } from "./contextMenus";
|
||||||
import { getPinAt, isPinned, settings, snapshotArray, sortedSnapshot, usePinnedDms } from "./settings";
|
import { getPinAt, isPinned, settings, snapshotArray, sortedSnapshot, usePinnedDms } from "./settings";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
|
@ -29,9 +29,7 @@ export default definePlugin({
|
||||||
authors: [Devs.Ven, Devs.Strencher],
|
authors: [Devs.Ven, Devs.Strencher],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
contextMenus,
|
||||||
start: addContextMenus,
|
|
||||||
stop: removeContextMenus,
|
|
||||||
|
|
||||||
usePinCount(channelIds: string[]) {
|
usePinCount(channelIds: string[]) {
|
||||||
const pinnedDms = usePinnedDms();
|
const pinnedDms = usePinnedDms();
|
||||||
|
|
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://github.com/Vendicated/Vencord/assets/61953774/98d5d667-bbb9-48b8-872d-c9b3980f6506)
|
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/>.
|
* 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 { Flex } from "@components/Flex";
|
||||||
import { OpenExternalIcon } from "@components/Icons";
|
import { OpenExternalIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
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;
|
if (props?.reverseImageSearchType !== "img") return;
|
||||||
|
|
||||||
const src = props.itemHref ?? props.itemSrc;
|
const src = props.itemHref ?? props.itemSrc;
|
||||||
|
@ -93,7 +93,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) =
|
||||||
group?.push(makeSearchItem(src));
|
group?.push(makeSearchItem(src));
|
||||||
};
|
};
|
||||||
|
|
||||||
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
|
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||||
if (!props?.src) return;
|
if (!props?.src) return;
|
||||||
|
|
||||||
const group = findGroupChildrenByChildId("copy-native-link", children) ?? children;
|
const group = findGroupChildrenByChildId("copy-native-link", children) ?? children;
|
||||||
|
@ -115,14 +115,8 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
contextMenus: {
|
||||||
start() {
|
"message": messageContextMenuPatch,
|
||||||
addContextMenuPatch("message", messageContextMenuPatch);
|
"image-context": imageContextMenuPatch
|
||||||
addContextMenuPatch("image-context", imageContextMenuPatch);
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeContextMenuPatch("message", messageContextMenuPatch);
|
|
||||||
removeContextMenuPatch("image-context", imageContextMenuPatch);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { LazyComponent, useAwaiter, useForceUpdater } from "@utils/react";
|
import { useAwaiter, useForceUpdater } from "@utils/react";
|
||||||
import { find, findByPropsLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common";
|
import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import { Auth, authorize } from "../auth";
|
import { Auth, authorize } from "../auth";
|
||||||
|
@ -31,7 +31,7 @@ import ReviewComponent from "./ReviewComponent";
|
||||||
const { Editor, Transforms } = findByPropsLazy("Editor", "Transforms");
|
const { Editor, Transforms } = findByPropsLazy("Editor", "Transforms");
|
||||||
const { ChatInputTypes } = findByPropsLazy("ChatInputTypes");
|
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");
|
const { createChannelRecordFromServer } = findByPropsLazy("createChannelRecordFromServer");
|
||||||
|
|
||||||
interface UserProps {
|
interface UserProps {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
|
|
||||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import ExpandableHeader from "@components/ExpandableHeader";
|
import ExpandableHeader from "@components/ExpandableHeader";
|
||||||
import { OpenExternalIcon } from "@components/Icons";
|
import { OpenExternalIcon } from "@components/Icons";
|
||||||
|
@ -36,7 +36,7 @@ import { getCurrentUserInfo, readNotification } from "./reviewDbApi";
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
import { showToast } from "./utils";
|
import { showToast } from "./utils";
|
||||||
|
|
||||||
const guildPopoutPatch: NavContextMenuPatchCallback = (children, props: { guild: Guild, onClose(): void; }) => () => {
|
const guildPopoutPatch: NavContextMenuPatchCallback = (children, props: { guild: Guild, onClose(): void; }) => {
|
||||||
children.push(
|
children.push(
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
label="View Reviews"
|
label="View Reviews"
|
||||||
|
@ -53,6 +53,9 @@ export default definePlugin({
|
||||||
authors: [Devs.mantikafasi, Devs.Ven],
|
authors: [Devs.mantikafasi, Devs.Ven],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
contextMenus: {
|
||||||
|
"guild-header-popout": guildPopoutPatch
|
||||||
|
},
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
@ -69,8 +72,6 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
addContextMenuPatch("guild-header-popout", guildPopoutPatch);
|
|
||||||
|
|
||||||
const s = settings.store;
|
const s = settings.store;
|
||||||
const { lastReviewId, notifyReviews } = s;
|
const { lastReviewId, notifyReviews } = s;
|
||||||
|
|
||||||
|
@ -127,10 +128,6 @@ export default definePlugin({
|
||||||
}, 4000);
|
}, 4000);
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeContextMenuPatch("guild-header-popout", guildPopoutPatch);
|
|
||||||
},
|
|
||||||
|
|
||||||
getReviewsComponent: ErrorBoundary.wrap((user: User) => {
|
getReviewsComponent: ErrorBoundary.wrap((user: User) => {
|
||||||
const [reviewCount, setReviewCount] = useState<number>();
|
const [reviewCount, setReviewCount] = useState<number>();
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { getGuildRoles } from "@utils/discord";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { ChannelStore, GuildMemberStore, GuildStore } from "@webpack/common";
|
import { ChannelStore, GuildMemberStore } from "@webpack/common";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
chatMentions: {
|
chatMentions: {
|
||||||
|
@ -112,9 +114,8 @@ export default definePlugin({
|
||||||
return colorString && parseInt(colorString.slice(1), 16);
|
return colorString && parseInt(colorString.slice(1), 16);
|
||||||
},
|
},
|
||||||
|
|
||||||
roleGroupColor({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) {
|
roleGroupColor: ErrorBoundary.wrap(({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) => {
|
||||||
const guild = GuildStore.getGuild(guildId);
|
const role = getGuildRoles(guildId)[id];
|
||||||
const role = guild?.roles[id];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span style={{
|
<span style={{
|
||||||
|
@ -125,7 +126,7 @@ export default definePlugin({
|
||||||
{title ?? label} — {count}
|
{title ?? label} — {count}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
}, { noop: true }),
|
||||||
|
|
||||||
getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) {
|
getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { ReplyIcon } from "@components/Icons";
|
import { ReplyIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
@ -27,7 +27,7 @@ import { Message } from "discord-types/general";
|
||||||
|
|
||||||
const messageUtils = findByPropsLazy("replyToMessage");
|
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
|
// make sure the message is in the selected channel
|
||||||
if (SelectedChannelStore.getChannelId() !== message.channel_id) return;
|
if (SelectedChannelStore.getChannelId() !== message.channel_id) return;
|
||||||
const channel = ChannelStore.getChannel(message?.channel_id);
|
const channel = ChannelStore.getChannel(message?.channel_id);
|
||||||
|
@ -38,7 +38,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
|
||||||
const dmGroup = findGroupChildrenByChildId("pin", children);
|
const dmGroup = findGroupChildrenByChildId("pin", children);
|
||||||
if (dmGroup && !dmGroup.some(child => child?.props?.id === "reply")) {
|
if (dmGroup && !dmGroup.some(child => child?.props?.id === "reply")) {
|
||||||
const pinIndex = dmGroup.findIndex(c => c?.props.id === "pin");
|
const pinIndex = dmGroup.findIndex(c => c?.props.id === "pin");
|
||||||
return dmGroup.splice(pinIndex + 1, 0, (
|
dmGroup.splice(pinIndex + 1, 0, (
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="reply"
|
id="reply"
|
||||||
label={i18n.Messages.MESSAGE_ACTION_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)}
|
action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// servers
|
// servers
|
||||||
const serverGroup = findGroupChildrenByChildId("mark-unread", children);
|
const serverGroup = findGroupChildrenByChildId("mark-unread", children);
|
||||||
if (serverGroup && !serverGroup.some(child => child?.props?.id === "reply")) {
|
if (serverGroup && !serverGroup.some(child => child?.props?.id === "reply")) {
|
||||||
return serverGroup.unshift((
|
serverGroup.unshift((
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="reply"
|
id="reply"
|
||||||
label={i18n.Messages.MESSAGE_ACTION_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)}
|
action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,12 +69,7 @@ export default definePlugin({
|
||||||
name: "SearchReply",
|
name: "SearchReply",
|
||||||
description: "Adds a reply button to search results",
|
description: "Adds a reply button to search results",
|
||||||
authors: [Devs.Aria],
|
authors: [Devs.Aria],
|
||||||
|
contextMenus: {
|
||||||
start() {
|
"message": messageContextMenuPatch
|
||||||
addContextMenuPatch("message", messageContextMenuPatch);
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeContextMenuPatch("message", messageContextMenuPatch);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { openImageModal, openUserProfile } from "@utils/discord";
|
import { getGuildRoles, openImageModal, openUserProfile } from "@utils/discord";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
|
@ -172,7 +172,7 @@ function ServerInfoTab({ guild }: GuildProps) {
|
||||||
"Verification Level": ["None", "Low", "Medium", "High", "Highest"][guild.verificationLevel] || "?",
|
"Verification Level": ["None", "Low", "Medium", "High", "Highest"][guild.verificationLevel] || "?",
|
||||||
"Nitro Boosts": `${guild.premiumSubscriberCount ?? 0} (Level ${guild.premiumTier ?? 0})`,
|
"Nitro Boosts": `${guild.premiumSubscriberCount ?? 0} (Level ${guild.premiumTier ?? 0})`,
|
||||||
"Channels": GuildChannelStore.getChannels(guild.id)?.count - 1 || "?", // - null category
|
"Channels": GuildChannelStore.getChannels(guild.id)?.count - 1 || "?", // - null category
|
||||||
"Roles": Object.keys(guild.roles).length - 1, // - @everyone
|
"Roles": Object.keys(getGuildRoles(guild.id)).length - 1, // - @everyone
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* 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 { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Menu } from "@webpack/common";
|
import { Menu } from "@webpack/common";
|
||||||
|
@ -12,7 +12,7 @@ import { Guild } from "discord-types/general";
|
||||||
|
|
||||||
import { openGuildProfileModal } from "./GuildProfileModal";
|
import { openGuildProfileModal } from "./GuildProfileModal";
|
||||||
|
|
||||||
const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => () => {
|
const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => {
|
||||||
const group = findGroupChildrenByChildId("privacy", children);
|
const group = findGroupChildrenByChildId("privacy", children);
|
||||||
|
|
||||||
group?.push(
|
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",
|
description: "Allows you to view info about a server by right clicking it in the server list",
|
||||||
authors: [Devs.Ven, Devs.Nuckyz],
|
authors: [Devs.Ven, Devs.Nuckyz],
|
||||||
tags: ["guild", "info"],
|
tags: ["guild", "info"],
|
||||||
|
contextMenus: {
|
||||||
start() {
|
"guild-context": Patch,
|
||||||
addContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
|
"guild-header-popout": Patch
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,8 +4,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-gp-banner {
|
.vc-gp-banner {
|
||||||
width: 100%;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
aspect-ratio: auto 240 / 135;
|
||||||
|
height: 334px;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
overflow: clip;
|
||||||
|
overflow-clip-margin: content-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-gp-header {
|
.vc-gp-header {
|
||||||
|
|
|
@ -305,27 +305,27 @@ export default definePlugin({
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".avatars),children",
|
find: '+1]})},"overflow"))',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Create a variable for the channel prop
|
// 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};`
|
replace: (m, props) => `${m}let{shcChannel}=${props};`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen
|
// Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen
|
||||||
match: /\i>0(?=&&.{0,60}renderPopout)/,
|
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
|
// 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)/,
|
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
|
// 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/,
|
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}`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
|
@ -32,7 +32,7 @@ import { TranslateChatBarIcon, TranslateIcon } from "./TranslateIcon";
|
||||||
import { handleTranslate, TranslationAccessory } from "./TranslationAccessory";
|
import { handleTranslate, TranslationAccessory } from "./TranslationAccessory";
|
||||||
import { translate } from "./utils";
|
import { translate } from "./utils";
|
||||||
|
|
||||||
const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => () => {
|
const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => {
|
||||||
if (!message.content) return;
|
if (!message.content) return;
|
||||||
|
|
||||||
const group = findGroupChildrenByChildId("copy-text", children);
|
const group = findGroupChildrenByChildId("copy-text", children);
|
||||||
|
@ -57,13 +57,15 @@ export default definePlugin({
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"],
|
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"],
|
||||||
settings,
|
settings,
|
||||||
|
contextMenus: {
|
||||||
|
"message": messageCtxPatch
|
||||||
|
},
|
||||||
// not used, just here in case some other plugin wants it or w/e
|
// not used, just here in case some other plugin wants it or w/e
|
||||||
translate,
|
translate,
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
||||||
|
|
||||||
addContextMenuPatch("message", messageCtxPatch);
|
|
||||||
addChatBarButton("vc-translate", TranslateChatBarIcon);
|
addChatBarButton("vc-translate", TranslateChatBarIcon);
|
||||||
|
|
||||||
addButton("vc-translate", message => {
|
addButton("vc-translate", message => {
|
||||||
|
@ -91,7 +93,6 @@ export default definePlugin({
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
removePreSendListener(this.preSend);
|
removePreSendListener(this.preSend);
|
||||||
removeContextMenuPatch("message", messageCtxPatch);
|
|
||||||
removeChatBarButton("vc-translate");
|
removeChatBarButton("vc-translate");
|
||||||
removeButton("vc-translate");
|
removeButton("vc-translate");
|
||||||
removeAccessory("vc-translation");
|
removeAccessory("vc-translation");
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
@ -24,7 +24,7 @@ import { Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@web
|
||||||
|
|
||||||
const EMBED_SUPPRESSED = 1 << 2;
|
const EMBED_SUPPRESSED = 1 << 2;
|
||||||
|
|
||||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => () => {
|
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => {
|
||||||
const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0;
|
const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0;
|
||||||
if (!isEmbedSuppressed && !embeds.length) return;
|
if (!isEmbedSuppressed && !embeds.length) return;
|
||||||
|
|
||||||
|
@ -56,12 +56,7 @@ export default definePlugin({
|
||||||
name: "UnsuppressEmbeds",
|
name: "UnsuppressEmbeds",
|
||||||
authors: [Devs.rad, Devs.HypedDomi],
|
authors: [Devs.rad, Devs.HypedDomi],
|
||||||
description: "Allows you to unsuppress embeds in messages",
|
description: "Allows you to unsuppress embeds in messages",
|
||||||
|
contextMenus: {
|
||||||
start() {
|
"message": messageContextMenuPatch
|
||||||
addContextMenuPatch("message", messageContextMenuPatch);
|
}
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeContextMenuPatch("message", messageContextMenuPatch);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings, useSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
@ -30,6 +30,8 @@ import type { ReactNode } from "react";
|
||||||
const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider");
|
const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider");
|
||||||
|
|
||||||
function VencordPopout(onClose: () => void) {
|
function VencordPopout(onClose: () => void) {
|
||||||
|
const { useQuickCss } = useSettings(["useQuickCss"]);
|
||||||
|
|
||||||
const pluginEntries = [] as ReactNode[];
|
const pluginEntries = [] as ReactNode[];
|
||||||
|
|
||||||
for (const plugin of Object.values(Vencord.Plugins.plugins)) {
|
for (const plugin of Object.values(Vencord.Plugins.plugins)) {
|
||||||
|
@ -68,11 +70,10 @@ function VencordPopout(onClose: () => void) {
|
||||||
/>
|
/>
|
||||||
<Menu.MenuCheckboxItem
|
<Menu.MenuCheckboxItem
|
||||||
id="vc-toolbox-quickcss-toggle"
|
id="vc-toolbox-quickcss-toggle"
|
||||||
checked={Settings.useQuickCss}
|
checked={useQuickCss}
|
||||||
label={"Enable QuickCSS"}
|
label={"Enable QuickCSS"}
|
||||||
action={() => {
|
action={() => {
|
||||||
Settings.useQuickCss = !Settings.useQuickCss;
|
Settings.useQuickCss = !useQuickCss;
|
||||||
onClose();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { ImageIcon } from "@components/Icons";
|
import { ImageIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
@ -80,7 +80,7 @@ function openImage(url: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => () => {
|
const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
const memberAvatar = GuildMemberStore.getMember(guildId!, user.id)?.avatar || null;
|
const memberAvatar = GuildMemberStore.getMember(guildId!, user.id)?.avatar || null;
|
||||||
|
|
||||||
|
@ -109,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;
|
if (!guild) return;
|
||||||
|
|
||||||
const { id, icon, banner } = guild;
|
const { id, icon, banner } = guild;
|
||||||
|
@ -155,14 +155,9 @@ export default definePlugin({
|
||||||
|
|
||||||
openImage,
|
openImage,
|
||||||
|
|
||||||
start() {
|
contextMenus: {
|
||||||
addContextMenuPatch("user-context", UserContext);
|
"user-context": UserContext,
|
||||||
addContextMenuPatch("guild-context", GuildContext);
|
"guild-context": GuildContext
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeContextMenuPatch("user-context", UserContext);
|
|
||||||
removeContextMenuPatch("guild-context", GuildContext);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { CodeBlock } from "@components/CodeBlock";
|
import { CodeBlock } from "@components/CodeBlock";
|
||||||
|
@ -117,8 +117,8 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function MakeContextCallback(name: "Guild" | "User" | "Channel") {
|
function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenuPatchCallback {
|
||||||
const callback: NavContextMenuPatchCallback = (children, props) => () => {
|
return (children, props) => {
|
||||||
const value = props[name.toLowerCase()];
|
const value = props[name.toLowerCase()];
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
if (props.label === i18n.Messages.CHANNEL_ACTIONS_MENU_LABEL) return; // random shit like notification settings
|
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({
|
export default definePlugin({
|
||||||
name: "ViewRaw",
|
name: "ViewRaw",
|
||||||
description: "Copy and view the raw content/data of any message, channel or guild",
|
description: "Copy and view the raw content/data of any message, channel or guild",
|
||||||
authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna],
|
authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna],
|
||||||
dependencies: ["MessagePopoverAPI"],
|
dependencies: ["MessagePopoverAPI"],
|
||||||
settings,
|
settings,
|
||||||
|
contextMenus: {
|
||||||
|
"guild-context": MakeContextCallback("Guild"),
|
||||||
|
"channel-context": MakeContextCallback("Channel"),
|
||||||
|
"user-context": MakeContextCallback("User")
|
||||||
|
},
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
addButton("ViewRaw", msg => {
|
addButton("ViewRaw", msg => {
|
||||||
|
@ -187,16 +190,9 @@ export default definePlugin({
|
||||||
onContextMenu: handleContextMenu
|
onContextMenu: handleContextMenu
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
addContextMenuPatch("guild-context", MakeContextCallback("Guild"));
|
|
||||||
addContextMenuPatch("channel-context", MakeContextCallback("Channel"));
|
|
||||||
addContextMenuPatch("user-context", MakeContextCallback("User"));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
removeButton("CopyRawMessage");
|
removeButton("ViewRaw");
|
||||||
removeContextMenuPatch("guild-context", MakeContextCallback("Guild"));
|
|
||||||
removeContextMenuPatch("channel-context", MakeContextCallback("Channel"));
|
|
||||||
removeContextMenuPatch("user-context", MakeContextCallback("User"));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { Microphone } from "@components/Icons";
|
import { Microphone } from "@components/Icons";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
@ -48,18 +48,30 @@ export type VoiceRecorder = ComponentType<{
|
||||||
|
|
||||||
const VoiceRecorder = IS_DISCORD_DESKTOP ? VoiceRecorderDesktop : VoiceRecorderWeb;
|
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({
|
export default definePlugin({
|
||||||
name: "VoiceMessages",
|
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",
|
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],
|
authors: [Devs.Ven, Devs.Vap, Devs.Nickyux],
|
||||||
settings,
|
settings,
|
||||||
|
contextMenus: {
|
||||||
start() {
|
"channel-attach": ctxMenuPatch
|
||||||
addContextMenuPatch("channel-attach", ctxMenuPatch);
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeContextMenuPatch("channel-attach", ctxMenuPatch);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -234,20 +246,3 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) {
|
||||||
</ModalRoot>
|
</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} />)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { makeRange } from "@components/PluginSettings/components";
|
import { makeRange } from "@components/PluginSettings/components";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { getGuildRoles } from "@utils/discord";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType, PluginNative } from "@utils/types";
|
import definePlugin, { OptionType, PluginNative } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
@ -196,7 +197,7 @@ export default definePlugin({
|
||||||
|
|
||||||
if (message.mention_roles.length > 0) {
|
if (message.mention_roles.length > 0) {
|
||||||
for (const roleId of message.mention_roles) {
|
for (const roleId of message.mention_roles) {
|
||||||
const role = GuildStore.getGuild(channel.guild_id).roles[roleId];
|
const role = getGuildRoles(channel.guild_id)[roleId];
|
||||||
if (!role) continue;
|
if (!role) continue;
|
||||||
const roleColor = role.colorString ?? `#${pingColor}`;
|
const roleColor = role.colorString ?? `#${pingColor}`;
|
||||||
finalMsg = finalMsg.replace(`<@&${roleId}>`, `<b><color=${roleColor}>@${role.name}</color></b>`);
|
finalMsg = finalMsg.replace(`<@&${roleId}>`, `<b><color=${roleColor}>@${role.name}</color></b>`);
|
||||||
|
|
|
@ -106,7 +106,7 @@ export async function authorizeCloud() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(location, {
|
const res = await fetch(location, {
|
||||||
headers: new Headers({ Accept: "application/json" })
|
headers: { Accept: "application/json" }
|
||||||
});
|
});
|
||||||
const { secret } = await res.json();
|
const { secret } = await res.json();
|
||||||
if (secret) {
|
if (secret) {
|
||||||
|
|
|
@ -418,6 +418,14 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
Av32000: {
|
Av32000: {
|
||||||
name: "Av32000",
|
name: "Av32000",
|
||||||
id: 593436735380127770n,
|
id: 593436735380127770n,
|
||||||
|
},
|
||||||
|
Kyuuhachi: {
|
||||||
|
name: "Kyuuhachi",
|
||||||
|
id: 236588665420251137n,
|
||||||
|
},
|
||||||
|
Elvyra: {
|
||||||
|
name: "Elvyra",
|
||||||
|
id: 708275751816003615n,
|
||||||
}
|
}
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { MessageObject } from "@api/MessageEvents";
|
import { MessageObject } from "@api/MessageEvents";
|
||||||
import { ChannelStore, ComponentDispatch, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
|
import { ChannelStore, ComponentDispatch, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
|
||||||
import { Guild, Message, User } from "discord-types/general";
|
import { Guild, Message, Role, User } from "discord-types/general";
|
||||||
|
|
||||||
import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal";
|
import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal";
|
||||||
|
|
||||||
|
@ -185,3 +185,11 @@ export async function fetchUserProfile(id: string, options?: FetchUserProfileOpt
|
||||||
export function getUniqueUsername(user: User) {
|
export function getUniqueUsername(user: User) {
|
||||||
return user.discriminator === "0" ? user.username : user.tag;
|
return user.discriminator === "0" ? user.username : user.tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: remove this once discord merges the role change into stable
|
||||||
|
export function getGuildRoles(guildId: string): Record<string, Role> {
|
||||||
|
if ("getRoles" in GuildStore)
|
||||||
|
return (GuildStore as any).getRoles(guildId);
|
||||||
|
|
||||||
|
return GuildStore.getGuild(guildId)?.roles ?? {};
|
||||||
|
}
|
||||||
|
|
|
@ -118,10 +118,10 @@ export async function putCloudSettings(manual?: boolean) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
|
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: new Headers({
|
headers: {
|
||||||
Authorization: await getCloudAuth(),
|
Authorization: await getCloudAuth(),
|
||||||
"Content-Type": "application/octet-stream"
|
"Content-Type": "application/octet-stream"
|
||||||
}),
|
},
|
||||||
body: deflateSync(new TextEncoder().encode(settings))
|
body: deflateSync(new TextEncoder().encode(settings))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -162,11 +162,11 @@ export async function getCloudSettings(shouldNotify = true, force = false) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
|
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: new Headers({
|
headers: {
|
||||||
Authorization: await getCloudAuth(),
|
Authorization: await getCloudAuth(),
|
||||||
Accept: "application/octet-stream",
|
Accept: "application/octet-stream",
|
||||||
"If-None-Match": Settings.cloud.settingsSyncVersion.toString()
|
"If-None-Match": Settings.cloud.settingsSyncVersion.toString()
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.status === 404) {
|
if (res.status === 404) {
|
||||||
|
@ -251,9 +251,7 @@ export async function deleteCloudSettings() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
|
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: new Headers({
|
headers: { Authorization: await getCloudAuth() },
|
||||||
Authorization: await getCloudAuth()
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Command } from "@api/Commands";
|
import { Command } from "@api/Commands";
|
||||||
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { FluxEvents } from "@webpack/types";
|
import { FluxEvents } from "@webpack/types";
|
||||||
import { Promisable } from "type-fest";
|
import { Promisable } from "type-fest";
|
||||||
|
|
||||||
|
@ -115,6 +116,10 @@ export interface PluginDef {
|
||||||
flux?: {
|
flux?: {
|
||||||
[E in FluxEvents]?: (event: any) => void;
|
[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.
|
* Allows you to add custom actions to the Vencord Toolbox.
|
||||||
* The key will be used as text for the button
|
* 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 */
|
/** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */
|
||||||
export let useToken: t.useToken;
|
export let useToken: t.useToken;
|
||||||
|
|
||||||
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 Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"));
|
||||||
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
|
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,10 @@
|
||||||
|
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
|
||||||
export const TextAndImagesSettingsStores = findByPropsLazy("MessageDisplayCompact");
|
import * as t from "./types/settingsStores";
|
||||||
export const StatusSettingsStores = findByPropsLazy("ShowCurrentGame");
|
|
||||||
|
|
||||||
|
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");
|
export const UserSettingsActionCreators = findByPropsLazy("PreloadedUserSettingsActionCreators");
|
||||||
|
|
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/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export * from "./classes";
|
||||||
export * from "./components";
|
export * from "./components";
|
||||||
export * from "./fluxEvents";
|
export * from "./fluxEvents";
|
||||||
|
export * from "./i18nMessages";
|
||||||
export * from "./menu";
|
export * from "./menu";
|
||||||
|
export * from "./settingsStores";
|
||||||
export * from "./stores";
|
export * from "./stores";
|
||||||
export * from "./utils";
|
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;
|
||||||
|
}
|
|
@ -60,6 +60,7 @@ export const filters = {
|
||||||
return m => {
|
return m => {
|
||||||
if (filter(m)) return true;
|
if (filter(m)) return true;
|
||||||
if (!m.$$typeof) return false;
|
if (!m.$$typeof) return false;
|
||||||
|
if (m.type && m.type.render) return filter(m.type.render); // memo + forwardRef
|
||||||
if (m.type) return filter(m.type); // memos
|
if (m.type) return filter(m.type); // memos
|
||||||
if (m.render) return filter(m.render); // forwardRefs
|
if (m.render) return filter(m.render); // forwardRefs
|
||||||
return false;
|
return false;
|
||||||
|
@ -83,8 +84,8 @@ export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let devToolsOpen = false;
|
||||||
if (IS_DEV && IS_DISCORD_DESKTOP) {
|
if (IS_DEV && IS_DISCORD_DESKTOP) {
|
||||||
var devToolsOpen = false;
|
|
||||||
// At this point in time, DiscordNative has not been exposed yet, so setImmediate is needed
|
// At this point in time, DiscordNative has not been exposed yet, so setImmediate is needed
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
DiscordNative/* just to make sure */?.window.setDevtoolsCallbacks(() => devToolsOpen = true, () => devToolsOpen = false);
|
DiscordNative/* just to make sure */?.window.setDevtoolsCallbacks(() => devToolsOpen = true, () => devToolsOpen = false);
|
||||||
|
@ -475,8 +476,10 @@ export function waitFor(filter: string | string[] | FilterFn, callback: Callback
|
||||||
else if (typeof filter !== "function")
|
else if (typeof filter !== "function")
|
||||||
throw new Error("filter must be a string, string[] or function, got " + typeof filter);
|
throw new Error("filter must be a string, string[] or function, got " + typeof filter);
|
||||||
|
|
||||||
const [existing, id] = find(filter!, { isIndirect: true, isWaitFor: true });
|
if (cache != null) {
|
||||||
if (existing) return void callback(existing, id);
|
const [existing, id] = find(filter, { isIndirect: true, isWaitFor: true });
|
||||||
|
if (existing) return void callback(existing, id);
|
||||||
|
}
|
||||||
|
|
||||||
subscriptions.set(filter, callback);
|
subscriptions.set(filter, callback);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue