Merge branch 'dev'
This commit is contained in:
commit
ddff504228
32 changed files with 1245 additions and 442 deletions
4
.github/ISSUE_TEMPLATE/blank.yml
vendored
4
.github/ISSUE_TEMPLATE/blank.yml
vendored
|
|
@ -5,9 +5,9 @@ body:
|
|||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||

|
||||

|
||||
|
||||
GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer.
|
||||
GitHub Issues are for development, not support! Please use our [support server](https://equicord.org/discord) unless you are a Vencord Developer.
|
||||
|
||||
- type: textarea
|
||||
id: content
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
Equicord is a fork of [Vencord](https://github.com/Vendicated/Vencord), with over 300+ plugins.
|
||||
|
||||
You can join our [discord server](https://equicord.org/discord) for commits, changes, chat or even support.
|
||||
You can join our [Discord server](https://equicord.org/discord) for commits, changes, chatting, or even support.
|
||||
|
||||
### Included Plugins
|
||||
|
||||
Our included plugins can be found [here](https://equicord.org/plugins)
|
||||
Our included plugins can be found [here](https://equicord.org/plugins).
|
||||
|
||||
## Installing / Uninstalling
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ Thank you to [Vendicated](https://github.com/Vendicated) for creating [Vencord](
|
|||
|
||||
## Disclaimer
|
||||
|
||||
Discord is trademark of Discord Inc. and solely mentioned for the sake of descriptivity.
|
||||
Discord is trademark of Discord Inc., and solely mentioned for the sake of descriptivity.
|
||||
Mentioning it does not imply any affiliation with or endorsement by Discord Inc.
|
||||
Vencord is not connected to Equicord and as such, all donation links go to Vendicated's donation link.
|
||||
|
||||
|
|
@ -101,8 +101,8 @@ Client modifications are against Discord’s Terms of Service.
|
|||
|
||||
However, Discord is pretty indifferent about them and there are no known cases of users getting banned for using client mods! So you should generally be fine if you don’t use plugins that implement abusive behaviour. But no worries, all inbuilt plugins are safe to use!
|
||||
|
||||
Regardless, if your account is essential to you and getting disabled would be a disaster for you, you should probably not use any client mods (not exclusive to Equicord), just to be safe
|
||||
Regardless, if your account is essential to you and getting disabled would be a disaster for you, you should probably not use any client mods (not exclusive to Equicord), just to be safe.
|
||||
|
||||
Additionally, make sure not to post screenshots with Equicord in a server where you might get banned for it
|
||||
Additionally, make sure not to post screenshots with Equicord in a server where you might get banned for it.
|
||||
|
||||
</details>
|
||||
|
|
|
|||
18
package.json
18
package.json
|
|
@ -41,7 +41,7 @@
|
|||
"@ffmpeg/ffmpeg": "^0.12.10",
|
||||
"@ffmpeg/util": "^0.12.1",
|
||||
"@intrnl/xxhash64": "^0.1.2",
|
||||
"@types/less": "^3.0.6",
|
||||
"@types/less": "^3.0.8",
|
||||
"@types/stylus": "^0.48.42",
|
||||
"@types/tinycolor2": "^1.4.6",
|
||||
"@vap/core": "0.0.12",
|
||||
|
|
@ -56,8 +56,9 @@
|
|||
"virtual-merge": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/asar": "^3.2.10",
|
||||
"@electron/asar": "^4.0.1",
|
||||
"@stylistic/eslint-plugin": "^4.2.0",
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/chrome": "^0.0.312",
|
||||
"@types/diff": "^7.0.2",
|
||||
"@types/lodash": "^4.17.14",
|
||||
|
|
@ -66,6 +67,7 @@
|
|||
"@types/react-dom": "^19.0.4",
|
||||
"@types/yazl": "^2.4.5",
|
||||
"@vencord/discord-types": "link:packages/discord-types",
|
||||
"adm-zip": "^0.5.16",
|
||||
"diff": "^7.0.0",
|
||||
"esbuild": "^0.25.1",
|
||||
"eslint": "9.20.1",
|
||||
|
|
@ -77,7 +79,7 @@
|
|||
"highlight.js": "11.11.1",
|
||||
"html-minifier-terser": "^7.2.0",
|
||||
"moment": "^2.22.2",
|
||||
"puppeteer-core": "^24.4.0",
|
||||
"puppeteer-core": "^24.19.0",
|
||||
"standalone-electron-types": "^34.2.0",
|
||||
"stylelint": "^16.17.0",
|
||||
"stylelint-config-standard": "^37.0.0",
|
||||
|
|
@ -87,14 +89,13 @@
|
|||
"type-fest": "^4.38.0",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.28.0",
|
||||
"typescript-transform-paths": "^3.5.5",
|
||||
"zip-local": "^0.3.5"
|
||||
"typescript-transform-paths": "^3.5.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.14.0",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"eslint@9.20.1": "patches/eslint@9.20.1.patch",
|
||||
"@types/less@3.0.6": "patches/@types__less@3.0.6.patch"
|
||||
"@types/less@3.0.8": "patches/@types__less@3.0.8.patch"
|
||||
},
|
||||
"peerDependencyRules": {
|
||||
"ignoreMissing": [
|
||||
|
|
@ -111,7 +112,10 @@
|
|||
},
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild"
|
||||
]
|
||||
],
|
||||
"overrides": {
|
||||
"@eslint/plugin-kit@<0.3.4": ">=0.3.4"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
|
|
|||
577
pnpm-lock.yaml
generated
577
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -21,10 +21,10 @@
|
|||
|
||||
import { readFileSync } from "fs";
|
||||
import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import Zip from "zip-local";
|
||||
import path, { join } from "path";
|
||||
|
||||
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, IS_COMPANION_TEST, VERSION, commonRendererPlugins, buildOrWatchAll, stringifyValues } from "./common.mjs";
|
||||
import AdmZip from "adm-zip";
|
||||
|
||||
/**
|
||||
* @type {import("esbuild").BuildOptions}
|
||||
|
|
@ -203,14 +203,15 @@ if (!process.argv.includes("--skip-extension")) {
|
|||
buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
|
||||
]);
|
||||
|
||||
Zip.zip("dist/browser/chromium-unpacked", (_err, zip) => {
|
||||
zip.compress().save("dist/extension-chrome.zip");
|
||||
console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
|
||||
});
|
||||
Zip.zip("dist/browser/firefox-unpacked", (_err, zip) => {
|
||||
zip.compress().save("dist/extension-firefox.zip");
|
||||
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
|
||||
});
|
||||
const chromiumZip = new AdmZip();
|
||||
chromiumZip.addLocalFolder("dist/browser/chromium-unpacked");
|
||||
chromiumZip.writeZip(path.resolve("dist/extension-chrome.zip"));
|
||||
console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
|
||||
|
||||
const firefoxZip = new AdmZip();
|
||||
firefoxZip.addLocalFolder("dist/browser/firefox-unpacked");
|
||||
firefoxZip.writeZip(path.resolve("dist/extension-firefox.zip"));
|
||||
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
|
||||
} else {
|
||||
await appendCssRuntime;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ async function init() {
|
|||
|
||||
syncSettings();
|
||||
|
||||
if (!IS_WEB && !IS_UPDATER_DISABLED) {
|
||||
if (!IS_WEB && !IS_UPDATER_DISABLED || !IS_DEV) {
|
||||
runUpdateCheck();
|
||||
|
||||
// this tends to get really annoying, so only do this if the user has auto-update without notification enabled
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ import { CheckedTextInput } from "@components/CheckedTextInput";
|
|||
import { Grid } from "@components/Grid";
|
||||
import { Link } from "@components/Link";
|
||||
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
|
||||
import { authorizeCloud, checkCloudUrlCsp, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
|
||||
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync";
|
||||
import { Alerts, Button, Forms, Switch, Tooltip } from "@webpack/common";
|
||||
import { Alerts, Button, Forms, Switch, Tooltip, useState } from "@webpack/common";
|
||||
|
||||
function validateUrl(url: string) {
|
||||
try {
|
||||
|
|
@ -37,8 +37,6 @@ function validateUrl(url: string) {
|
|||
}
|
||||
|
||||
async function eraseAllData() {
|
||||
if (!await checkCloudUrlCsp()) return;
|
||||
|
||||
const res = await fetch(new URL("/v1/", getCloudUrl()), {
|
||||
method: "DELETE",
|
||||
headers: { Authorization: await getCloudAuth() }
|
||||
|
|
@ -119,15 +117,31 @@ function SettingsSyncSection() {
|
|||
|
||||
function CloudTab() {
|
||||
const settings = useSettings(["cloud.authenticated", "cloud.url"]);
|
||||
const [inputKey, setInputKey] = useState(0);
|
||||
|
||||
async function changeUrl(url: string) {
|
||||
settings.cloud.url = url;
|
||||
settings.cloud.authenticated = false;
|
||||
|
||||
await deauthorizeCloud();
|
||||
await authorizeCloud();
|
||||
|
||||
setInputKey(prev => prev + 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsTab title="Equicord Cloud">
|
||||
<Forms.FormSection title="Cloud Settings" className={Margins.top16}>
|
||||
<Forms.FormText variant="text-md/normal" className={Margins.bottom20}>
|
||||
Equicord comes with a cloud integration that adds goodies like settings sync across devices.
|
||||
It <Link href="https://vencord.dev/cloud/privacy">respects your privacy</Link>, and
|
||||
the <Link href="https://github.com/Vencord/Backend">source code</Link> is AGPL 3.0 licensed so you
|
||||
can host it yourself.
|
||||
Equicord comes with a cloud integration allowing settings to be synced across apps and devices.
|
||||
<br />
|
||||
We use Vencord's <Link href="https://github.com/Vencord/Backend">cloud source code</Link> to provide our cloud instance which has a 32MB backup cap.
|
||||
<br />
|
||||
Our <Link href="https://vencord.dev/cloud/privacy">privacy policy</Link> allows you to see what information we store, how we use it, and data retention.
|
||||
<br />
|
||||
The Vencord Cloud repository is AGPL 3.0 licensed so you can host it yourself it you would like.
|
||||
<br />
|
||||
You can swap between Equicord and Vencord's different cloud instances below if needed.
|
||||
</Forms.FormText>
|
||||
<Switch
|
||||
key="backend"
|
||||
|
|
@ -147,12 +161,12 @@ function CloudTab() {
|
|||
Which backend to use when using cloud integrations.
|
||||
</Forms.FormText>
|
||||
<CheckedTextInput
|
||||
key="backendUrl"
|
||||
key={`backendUrl-${inputKey}`}
|
||||
value={settings.cloud.url}
|
||||
onChange={async v => {
|
||||
settings.cloud.url = v;
|
||||
settings.cloud.authenticated = false;
|
||||
deauthorizeCloud();
|
||||
await deauthorizeCloud();
|
||||
}}
|
||||
validate={validateUrl}
|
||||
/>
|
||||
|
|
@ -162,8 +176,8 @@ function CloudTab() {
|
|||
size={Button.Sizes.MEDIUM}
|
||||
disabled={!settings.cloud.authenticated}
|
||||
onClick={async () => {
|
||||
await deauthorizeCloud();
|
||||
settings.cloud.authenticated = false;
|
||||
await deauthorizeCloud();
|
||||
await authorizeCloud();
|
||||
}}
|
||||
>
|
||||
|
|
@ -184,6 +198,12 @@ function CloudTab() {
|
|||
>
|
||||
Erase All Data
|
||||
</Button>
|
||||
<Button size={Button.Sizes.MEDIUM} onClick={() => changeUrl("https://cloud.equicord.org/")}>
|
||||
Use Equicord's Cloud
|
||||
</Button>
|
||||
<Button size={Button.Sizes.MEDIUM} onClick={() => changeUrl("https://api.vencord.dev/")}>
|
||||
Use Vencord's Cloud
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Forms.FormDivider className={Margins.top16} />
|
||||
|
|
|
|||
80
src/equicordplugins/autoJump/index.tsx
Normal file
80
src/equicordplugins/autoJump/index.tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu, ChannelStore, NavigationRouter, FluxDispatcher, MessageActions, MessageStore } from "@webpack/common";
|
||||
|
||||
interface ChannelSelectEvent {
|
||||
type: "CHANNEL_SELECT";
|
||||
channelId: string | null;
|
||||
guildId: string | null;
|
||||
}
|
||||
|
||||
let lastChan = "0";
|
||||
|
||||
function jumpToPresent(channelId, { limit }: { limit?: number } = {}) {
|
||||
MessageActions.trackJump(channelId, null, "Present");
|
||||
|
||||
const jump = { present: true };
|
||||
|
||||
if (MessageStore.hasPresent(channelId)) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "LOAD_MESSAGES_SUCCESS_CACHED",
|
||||
jump,
|
||||
channelId,
|
||||
limit
|
||||
});
|
||||
} else {
|
||||
MessageActions.fetchMessages({
|
||||
channelId,
|
||||
limit,
|
||||
jump
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function autoJump(channel: any) {
|
||||
const guildId = channel.guild_id ?? "@me";
|
||||
const channelId = channel.id;
|
||||
|
||||
if (channelId === lastChan) return;
|
||||
|
||||
NavigationRouter.transitionTo(`/channels/${guildId}/${channelId}`);
|
||||
lastChan = channelId;
|
||||
|
||||
jumpToPresent(channelId, { limit: 100 });
|
||||
}
|
||||
|
||||
const MenuPatch: NavContextMenuPatchCallback = (children, { channel }) => {
|
||||
children.push(
|
||||
<Menu.MenuItem
|
||||
id="auto-jump"
|
||||
label="Jump to Last Message"
|
||||
action={() => autoJump(channel)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "AutoJump",
|
||||
description: "Automatically jumps to the last message in selected channel & adds a context-menu option to jump to the last message.",
|
||||
authors: [EquicordDevs.omaw],
|
||||
contextMenus: {
|
||||
"channel-context": MenuPatch,
|
||||
"user-context": MenuPatch,
|
||||
"thread-context": MenuPatch
|
||||
},
|
||||
flux: {
|
||||
async CHANNEL_SELECT({ guildId, channelId }: ChannelSelectEvent) {
|
||||
if (!guildId || !channelId) return;
|
||||
const channel = ChannelStore.getChannel(channelId);
|
||||
if (!channel || lastChan === channel.id) return;
|
||||
autoJump(channel);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -4,9 +4,11 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { FluxDispatcher, UserStore } from "@webpack/common";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
autoFillArguments: {
|
||||
|
|
@ -16,6 +18,13 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
function fetchIndex(target: object) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "APPLICATION_COMMAND_INDEX_FETCH_REQUEST",
|
||||
target
|
||||
});
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "BetterCommands",
|
||||
description: "Enhances the command system with miscellaneous improvements.",
|
||||
|
|
@ -32,5 +41,52 @@ export default definePlugin({
|
|||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
commands: [
|
||||
{
|
||||
name: "refresh",
|
||||
description: "Refreshes Discord application commands locally",
|
||||
options: [
|
||||
{
|
||||
name: "user",
|
||||
description: "specific user to try and refresh",
|
||||
type: ApplicationCommandOptionType.USER,
|
||||
}
|
||||
],
|
||||
inputType: ApplicationCommandInputType.BOT,
|
||||
execute: async (opts, ctx) => {
|
||||
try {
|
||||
const channelId = ctx.channel.id;
|
||||
const guildId = ctx.guild?.id;
|
||||
const user = findOption(opts, "user") as string;
|
||||
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
content: "Refreshing application commands...",
|
||||
});
|
||||
|
||||
fetchIndex({ type: "channel", channelId });
|
||||
|
||||
if (guildId) fetchIndex({ type: "guild", guildId });
|
||||
|
||||
if (user) {
|
||||
const target = await UserStore.getUser(user);
|
||||
fetchIndex(
|
||||
target.bot
|
||||
? { type: "application", applicationId: target.id }
|
||||
: { type: "user" }
|
||||
);
|
||||
}
|
||||
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
content: "Commands refreshed successfully!",
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[Refresh Command] Error refreshing commands:", err);
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
content: "Failed to refresh commands. Check the console for details.",
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import { colors, DATASTORE_KEY } from "./index";
|
|||
|
||||
const cl = classNameFactory("vc-customColors-");
|
||||
|
||||
export function SetColorModal({ userId, modalProps }: { userId: string, modalProps: ModalProps; }) {
|
||||
const initialColor = parseInt(colors[userId], 16) || 372735;
|
||||
export function SetColorModal({ id, modalProps }: { id: string, modalProps: ModalProps; }) {
|
||||
const initialColor = parseInt(colors[id], 16) || 372735;
|
||||
// color picker default to current color set for user (if null it's 0x05afff :3 )
|
||||
|
||||
const [colorPickerColor, setColorPickerColor] = useState(initialColor);
|
||||
|
|
@ -32,13 +32,13 @@ export function SetColorModal({ userId, modalProps }: { userId: string, modalPro
|
|||
}
|
||||
|
||||
async function saveUserColor() {
|
||||
colors[userId] = colorPickerColor.toString(16).padStart(6, "0");
|
||||
colors[id] = colorPickerColor.toString(16).padStart(6, "0");
|
||||
await set(DATASTORE_KEY, colors);
|
||||
modalProps.onClose();
|
||||
}
|
||||
|
||||
async function deleteUserColor() {
|
||||
delete colors[userId];
|
||||
delete colors[id];
|
||||
await set(DATASTORE_KEY, colors);
|
||||
modalProps.onClose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ import { definePluginSettings, Settings } from "@api/Settings";
|
|||
import { EquicordDevs } from "@utils/constants";
|
||||
import { openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { User } from "@vencord/discord-types";
|
||||
import { Channel, User } from "@vencord/discord-types";
|
||||
import { extractAndLoadChunksLazy } from "@webpack";
|
||||
import { Menu } from "@webpack/common";
|
||||
import { ChannelStore, Menu, SelectedChannelStore } from "@webpack/common";
|
||||
|
||||
import { SetColorModal } from "./SetColorModal";
|
||||
|
||||
|
|
@ -52,20 +52,37 @@ const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: {
|
|||
icon={ColorIcon}
|
||||
action={async () => {
|
||||
await requireSettingsMenu();
|
||||
openModal(modalProps => <SetColorModal userId={user.id} modalProps={modalProps} />);
|
||||
openModal(modalProps => <SetColorModal id={user.id} modalProps={modalProps} />);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
children.push(<Menu.MenuSeparator />, setCustomColorItem);
|
||||
|
||||
};
|
||||
|
||||
export function getCustomColorString(userId: string | undefined, withHash?: boolean): string | undefined {
|
||||
if (!userId) return;
|
||||
if (!colors[userId] || !Settings.plugins.CustomUserColors.enabled) return;
|
||||
if (withHash) return `#${colors[userId]}`;
|
||||
return colors[userId];
|
||||
const channelContextMenuPatch: NavContextMenuPatchCallback = (children, { channel }: { channel: Channel; }) => {
|
||||
if (channel?.id == null) return;
|
||||
|
||||
const setCustomColorItem = (
|
||||
<Menu.MenuItem
|
||||
label="Set Color"
|
||||
id="set-color"
|
||||
icon={ColorIcon}
|
||||
action={async () => {
|
||||
await requireSettingsMenu();
|
||||
openModal(modalProps => <SetColorModal id={channel.id} modalProps={modalProps} />);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
children.push(<Menu.MenuSeparator />, setCustomColorItem);
|
||||
};
|
||||
|
||||
export function getCustomColorString(id: string | undefined, withHash?: boolean): string | undefined {
|
||||
if (!id) return;
|
||||
if (!colors[id] || !Settings.plugins.CustomUserColors.enabled) return;
|
||||
if (withHash) return `#${colors[id]}`;
|
||||
return colors[id];
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
|
|
@ -85,7 +102,10 @@ export default definePlugin({
|
|||
name: "CustomUserColors",
|
||||
description: "Lets you add a custom color to any user, anywhere! Highly recommend to use with typingTweaks and roleColorEverywhere",
|
||||
authors: [EquicordDevs.mochienya],
|
||||
contextMenus: { "user-context": userContextMenuPatch },
|
||||
contextMenus: {
|
||||
"user-context": userContextMenuPatch,
|
||||
"gdm-context": channelContextMenuPatch,
|
||||
},
|
||||
settings,
|
||||
requireSettingsMenu,
|
||||
getCustomColorString,
|
||||
|
|
@ -135,10 +155,12 @@ export default definePlugin({
|
|||
|
||||
wrapMessageColorProps(colorProps: { colorString: string, colorStrings?: Record<"primaryColor" | "secondaryColor" | "tertiaryColor", string>; }, context: any) {
|
||||
try {
|
||||
const channelId = SelectedChannelStore.getChannelId();
|
||||
const channel = ChannelStore.getChannel(channelId);
|
||||
const isDM = channel.isDM() || channel.isMultiUserDM();
|
||||
const colorString = this.colorIfServer(context);
|
||||
if (colorString === colorProps.colorString) {
|
||||
return colorProps;
|
||||
}
|
||||
if (colorString === colorProps.colorString) return colorProps;
|
||||
if (!settings.store.colorInServers && !isDM) return colorProps;
|
||||
|
||||
return {
|
||||
...colorProps,
|
||||
|
|
@ -156,8 +178,9 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
colorDMList(context: any): string | undefined {
|
||||
const userId = context?.user?.id;
|
||||
const colorString = getCustomColorString(userId, true);
|
||||
const id = context?.user?.id ?? context?.channel?.id;
|
||||
const colorString = getCustomColorString(id, true);
|
||||
|
||||
return colorString ?? "inherit";
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -7,15 +7,14 @@
|
|||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||
import { Forms, SearchableSelect, useEffect, useState } from "@webpack/common";
|
||||
import { findByCodeLazy } from "@webpack";
|
||||
import { Forms, MediaEngineStore, SearchableSelect, useEffect, useState } from "@webpack/common";
|
||||
|
||||
interface PickerProps {
|
||||
streamMediaSelection: any[];
|
||||
streamMedia: any[];
|
||||
}
|
||||
|
||||
const mediaEngine = findByPropsLazy("getMediaEngine");
|
||||
const getDesktopSources = findByCodeLazy("desktop sources");
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
|
|
@ -26,7 +25,7 @@ export const settings = definePluginSettings({
|
|||
});
|
||||
|
||||
export async function getCurrentMedia() {
|
||||
const media = mediaEngine.getMediaEngine();
|
||||
const media = MediaEngineStore.getMediaEngine();
|
||||
const sources = [
|
||||
...(await getDesktopSources(media, ["screen"], null) ?? []),
|
||||
...(await getDesktopSources(media, ["window", "application"], null) ?? [])
|
||||
|
|
@ -63,7 +62,7 @@ function StreamSimplePicker({ streamMediaSelection, streamMedia }: PickerProps)
|
|||
|
||||
function ScreenSetting() {
|
||||
const { streamMedia } = settings.use(["streamMedia"]);
|
||||
const media = mediaEngine.getMediaEngine();
|
||||
const media = MediaEngineStore.getMediaEngine();
|
||||
const [streamMediaSelection, setStreamMediaSelection] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
|
|
|
|||
20
src/equicordplugins/newPluginsManager/NewPluginsModal.css
Normal file
20
src/equicordplugins/newPluginsManager/NewPluginsModal.css
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
.vc-newPluginsManager-restart-tooltip {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.vc-newPluginsManager-restart-list {
|
||||
padding-left: 1.25em;
|
||||
}
|
||||
|
||||
.vc-newPluginsManager-restart-list li {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.vc-newPluginsManager-close > div {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vc-newPluginsManager-disable-wrapper {
|
||||
display: flex;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
|
@ -4,47 +4,36 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { useSettings } from "@api/Settings";
|
||||
import "./NewPluginsModal.css";
|
||||
|
||||
import { Settings, useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { PluginCard } from "@components/settings/tabs/plugins/PluginCard";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { PluginCard } from "@components/settings/tabs/plugins";
|
||||
import { ChangeList } from "@utils/ChangeList";
|
||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { Alerts, Button, Flex, Forms, Parser, React, Text, Tooltip, useMemo } from "@webpack/common";
|
||||
import { classes, Margins } from "@utils/index";
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, Flex, Forms, React, Text, Tooltip, useMemo } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import Plugins from "~plugins";
|
||||
|
||||
import { getNewPlugins, writeKnownPlugins } from "./knownPlugins";
|
||||
import { getNewPlugins, getNewSettings, KnownPluginSettingsMap, writeKnownSettings } from "./knownSettings";
|
||||
|
||||
const cl = classNameFactory("vc-plugins-");
|
||||
|
||||
const Checkbox = findComponentByCodeLazy(".checkboxWrapperDisabled:");
|
||||
|
||||
let hasSeen = false;
|
||||
|
||||
// Most of this was stolen from PluginSettings directly.
|
||||
|
||||
export function NewPluginsModal({ modalProps, newPlugins }: { modalProps: ModalProps; newPlugins: Set<string>; }) {
|
||||
export function NewPluginsModal({ modalProps, newPlugins, newSettings }: { modalProps: ModalProps; newPlugins: Set<string>; newSettings: KnownPluginSettingsMap; }) {
|
||||
const settings = useSettings();
|
||||
const changes = React.useMemo(() => new ChangeList<string>(), []);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => void (changes.hasChanges && Alerts.show({
|
||||
title: "Restart required",
|
||||
body: (
|
||||
<>
|
||||
<p>The following plugins require a restart:</p>
|
||||
<div>{changes.map((s, i) => (
|
||||
<>
|
||||
{i > 0 && ", "}
|
||||
{Parser.parse("`" + s + "`")}
|
||||
</>
|
||||
))}</div>
|
||||
</>
|
||||
),
|
||||
confirmText: "Restart now",
|
||||
cancelText: "Later!",
|
||||
onConfirm: () => location.reload()
|
||||
}));
|
||||
}, []);
|
||||
let updateContinueButton = () => { };
|
||||
|
||||
const depMap = React.useMemo(() => {
|
||||
const o = {} as Record<string, string[]>;
|
||||
|
|
@ -60,8 +49,13 @@ export function NewPluginsModal({ modalProps, newPlugins }: { modalProps: ModalP
|
|||
return o;
|
||||
}, []);
|
||||
|
||||
const sortedPlugins = useMemo(() => [...newPlugins].map(pn => Plugins[pn])
|
||||
.sort((a, b) => a.name.localeCompare(b.name)), []);
|
||||
const mapPlugins = (array: string[]) => array.map(pn => Plugins[pn])
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const sortedPlugins = useMemo(() => [
|
||||
...mapPlugins([...newPlugins]),
|
||||
...mapPlugins([...newSettings.keys()].filter(p => !newPlugins.has(p)))
|
||||
], []);
|
||||
|
||||
const plugins = [] as JSX.Element[];
|
||||
const requiredPlugins = [] as JSX.Element[];
|
||||
|
|
@ -83,10 +77,14 @@ export function NewPluginsModal({ modalProps, newPlugins }: { modalProps: ModalP
|
|||
<PluginCard
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onRestartNeeded={name => changes.handleChange(name)}
|
||||
onRestartNeeded={name => {
|
||||
changes.handleChange(name);
|
||||
updateContinueButton();
|
||||
}}
|
||||
disabled={true}
|
||||
plugin={p}
|
||||
key={p.name}
|
||||
isNew={newPlugins.has(p.name)}
|
||||
/>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
|
@ -94,10 +92,14 @@ export function NewPluginsModal({ modalProps, newPlugins }: { modalProps: ModalP
|
|||
} else {
|
||||
plugins.push(
|
||||
<PluginCard
|
||||
onRestartNeeded={name => changes.handleChange(name)}
|
||||
onRestartNeeded={name => {
|
||||
changes.handleChange(name);
|
||||
updateContinueButton();
|
||||
}}
|
||||
disabled={false}
|
||||
plugin={p}
|
||||
key={p.name}
|
||||
isNew={newPlugins.has(p.name)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -106,7 +108,17 @@ export function NewPluginsModal({ modalProps, newPlugins }: { modalProps: ModalP
|
|||
|
||||
return <ModalRoot {...modalProps} size={ModalSize.MEDIUM} >
|
||||
<ModalHeader>
|
||||
<Text variant="heading-lg/semibold">New Plugins ({[...plugins, ...requiredPlugins].length})</Text>
|
||||
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>New Plugins and Settings ({[...plugins, ...requiredPlugins].length})</Text>
|
||||
<Tooltip text="Dismiss for this session">
|
||||
{tooltipProps =>
|
||||
<div {...tooltipProps}>
|
||||
<ModalCloseButton
|
||||
onClick={modalProps.onClose}
|
||||
className={classes(cl("close"), "vc-newPluginsManager-close")}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</Tooltip>
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<div className={cl("grid")}>
|
||||
|
|
@ -115,39 +127,79 @@ export function NewPluginsModal({ modalProps, newPlugins }: { modalProps: ModalP
|
|||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<Flex direction={Flex.Direction.HORIZONTAL_REVERSE}>
|
||||
<Button
|
||||
color={Button.Colors.GREEN}
|
||||
onClick={async () => {
|
||||
await writeKnownPlugins();
|
||||
modalProps.onClose();
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
<ContinueButton
|
||||
close={modalProps.onClose}
|
||||
changes={changes}
|
||||
callback={(v: () => void) => updateContinueButton = v}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex direction={Flex.Direction.HORIZONTAL}>
|
||||
<div className="vc-newPluginsManager-disable-wrapper">
|
||||
<Checkbox
|
||||
type="inverted"
|
||||
value={!settings?.plugins?.NewPluginsManager?.enabled}
|
||||
onChange={() => {
|
||||
Settings.plugins.NewPluginsManager.enabled = !settings?.plugins?.NewPluginsManager?.enabled;
|
||||
}}
|
||||
>
|
||||
<Text>Don't show this again</Text>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</Flex>
|
||||
</ModalFooter>
|
||||
</ModalRoot>;
|
||||
}
|
||||
|
||||
function ContinueButton(props: { callback: (update: () => void) => void; changes: ChangeList<string>; close: () => any; }) {
|
||||
const update = useForceUpdater();
|
||||
props.callback(update);
|
||||
return <Tooltip
|
||||
tooltipClassName="vc-newPluginsManager-restart-tooltip"
|
||||
text={<>
|
||||
The following plugins require a restart:
|
||||
<div className={Margins.bottom8} />
|
||||
<ul className="vc-newPluginsManager-restart-list">
|
||||
{props.changes.map(p => <li key={p}>{p}</li>)}
|
||||
</ul>
|
||||
</>}
|
||||
shouldShow={props.changes.hasChanges}
|
||||
>
|
||||
{tooltipProps =>
|
||||
<Button
|
||||
{...tooltipProps}
|
||||
color={Button.Colors.GREEN}
|
||||
onClick={async () => {
|
||||
await writeKnownSettings();
|
||||
props.changes.hasChanges ? location.reload() : props.close();
|
||||
}}
|
||||
>
|
||||
{props.changes.hasChanges ? "Restart" : "Continue"}
|
||||
</Button>
|
||||
}
|
||||
</Tooltip>;
|
||||
}
|
||||
|
||||
|
||||
function makeDependencyList(deps: string[]) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Forms.FormText>This plugin is required by:</Forms.FormText>
|
||||
{deps.map((dep: string) => <Forms.FormText key={cl("dep-text")} className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
||||
{deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export async function openNewPluginsModal() {
|
||||
const newPlugins = await getNewPlugins();
|
||||
if (newPlugins.size && !hasSeen) {
|
||||
const newSettings = await getNewSettings();
|
||||
if ((newPlugins.size || newSettings.size) && !hasSeen) {
|
||||
hasSeen = true;
|
||||
openModal(modalProps => (
|
||||
const modalKey = openModal(modalProps => <ErrorBoundary noop onError={() => { closeModal(modalKey); }}>
|
||||
<NewPluginsModal
|
||||
modalProps={modalProps}
|
||||
newPlugins={newPlugins}
|
||||
newSettings={newSettings}
|
||||
/>
|
||||
));
|
||||
</ErrorBoundary>);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,18 +7,22 @@
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
import { KNOWN_PLUGINS_DATA_KEY } from "./knownPlugins";
|
||||
import * as KnownSettings from "./knownSettings";
|
||||
import { KNOWN_PLUGINS_LEGACY_DATA_KEY, KNOWN_SETTINGS_DATA_KEY } from "./knownSettings";
|
||||
import { openNewPluginsModal } from "./NewPluginsModal";
|
||||
|
||||
export default definePlugin({
|
||||
name: "NewPluginsManager",
|
||||
description: "Utility that notifies you when new plugins are added to Equicord",
|
||||
description: "Utility that notifies you when new plugins are added to Vencord",
|
||||
authors: [Devs.Sqaaakoi],
|
||||
enabledByDefault: true,
|
||||
flux: {
|
||||
async POST_CONNECTION_OPEN() {
|
||||
openNewPluginsModal();
|
||||
}
|
||||
},
|
||||
openNewPluginsModal,
|
||||
KNOWN_PLUGINS_DATA_KEY
|
||||
KNOWN_PLUGINS_LEGACY_DATA_KEY,
|
||||
KNOWN_SETTINGS_DATA_KEY,
|
||||
KnownSettings
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { DataStore } from "@api/index";
|
||||
|
||||
import plugins from "~plugins";
|
||||
|
||||
export const KNOWN_PLUGINS_DATA_KEY = "NewPluginsManager_KnownPlugins";
|
||||
|
||||
export async function getKnownPlugins(): Promise<Set<string>> {
|
||||
let knownPlugins = await DataStore.get(KNOWN_PLUGINS_DATA_KEY) as string[];
|
||||
if (knownPlugins === undefined) {
|
||||
knownPlugins = Object.keys(plugins);
|
||||
DataStore.set(KNOWN_PLUGINS_DATA_KEY, knownPlugins);
|
||||
}
|
||||
return new Set(knownPlugins);
|
||||
}
|
||||
|
||||
export async function getNewPlugins(): Promise<Set<string>> {
|
||||
const currentPlugins = Object.keys(plugins);
|
||||
const knownPlugins = await getKnownPlugins();
|
||||
return new Set(currentPlugins.filter(p => !knownPlugins.has(p)));
|
||||
}
|
||||
|
||||
export async function writeKnownPlugins(): Promise<void> {
|
||||
const currentPlugins = Object.keys(plugins);
|
||||
const knownPlugins = await getKnownPlugins();
|
||||
DataStore.set(KNOWN_PLUGINS_DATA_KEY, [...new Set([...currentPlugins, ...knownPlugins])]);
|
||||
}
|
||||
91
src/equicordplugins/newPluginsManager/knownSettings.ts
Normal file
91
src/equicordplugins/newPluginsManager/knownSettings.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { DataStore } from "@api/index";
|
||||
|
||||
import plugins from "~plugins";
|
||||
|
||||
export type KnownPluginSettingsMap = Map<string, Set<string>>;
|
||||
|
||||
export const KNOWN_PLUGINS_LEGACY_DATA_KEY = "NewPluginsManager_KnownPlugins";
|
||||
export const KNOWN_SETTINGS_DATA_KEY = "NewPluginsManager_KnownSettings";
|
||||
|
||||
function getSettingsSetForPlugin(plugin: string): Set<string> {
|
||||
const settings = plugins[plugin]?.settings?.def || {};
|
||||
return new Set(Object.keys(settings).filter(setting => setting !== "enabled"));
|
||||
}
|
||||
|
||||
function getCurrentSettings(pluginList: string[]): KnownPluginSettingsMap {
|
||||
return new Map(pluginList.map(name => [
|
||||
name,
|
||||
getSettingsSetForPlugin(name)
|
||||
]));
|
||||
}
|
||||
|
||||
export async function getKnownSettings(): Promise<KnownPluginSettingsMap> {
|
||||
let map = await DataStore.get(KNOWN_SETTINGS_DATA_KEY) as KnownPluginSettingsMap;
|
||||
if (map === undefined) {
|
||||
const knownPlugins = await DataStore.get(KNOWN_PLUGINS_LEGACY_DATA_KEY) ?? [] as string[];
|
||||
const Plugins = [...Object.keys(plugins), ...knownPlugins];
|
||||
map = getCurrentSettings(Plugins);
|
||||
DataStore.set(KNOWN_SETTINGS_DATA_KEY, map);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export async function getNewSettings(): Promise<KnownPluginSettingsMap> {
|
||||
const map = getCurrentSettings(Object.keys(plugins));
|
||||
const knownSettings = await getKnownSettings();
|
||||
map.forEach((settings, plugin) => {
|
||||
const filteredSettings = [...settings].filter(setting => !knownSettings.get(plugin)?.has(setting));
|
||||
if (!filteredSettings.length) return map.delete(plugin);
|
||||
map.set(plugin, new Set(filteredSettings));
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
export async function getKnownPlugins(): Promise<Set<string>> {
|
||||
const knownSettings = await getKnownSettings();
|
||||
return new Set(knownSettings.keys());
|
||||
}
|
||||
|
||||
export async function getNewPlugins(): Promise<Set<string>> {
|
||||
const currentPlugins = Object.keys(plugins);
|
||||
const knownPlugins = await getKnownPlugins();
|
||||
return new Set(currentPlugins.filter(p => !knownPlugins.has(p)));
|
||||
}
|
||||
|
||||
export async function writeKnownSettings() {
|
||||
const currentSettings = getCurrentSettings(Object.keys(plugins));
|
||||
const knownSettings = await getKnownSettings();
|
||||
const allSettings = new Map();
|
||||
new Set([...currentSettings.keys(), ...knownSettings.keys()]).forEach(plugin => {
|
||||
allSettings.set(plugin, new Set([
|
||||
...(currentSettings.get(plugin) || []),
|
||||
...(knownSettings.get(plugin) || [])
|
||||
]));
|
||||
});
|
||||
await DataStore.set(KNOWN_SETTINGS_DATA_KEY, allSettings);
|
||||
}
|
||||
|
||||
export async function debugWipeSomeData() {
|
||||
const settings = await getKnownSettings();
|
||||
settings.forEach((value, key) => {
|
||||
if (Math.random() > 0.8) {
|
||||
if (Math.random() > 0.5) return settings.set(key, new Set([...value].filter(() => Math.random() > 0.5)));
|
||||
return settings.delete(key);
|
||||
}
|
||||
});
|
||||
await DataStore.set(KNOWN_SETTINGS_DATA_KEY, settings);
|
||||
}
|
||||
|
||||
export async function editRawData(patcher: (data: KnownPluginSettingsMap) => (Promise<any> | any)) {
|
||||
if (!patcher) return;
|
||||
const map = await DataStore.get(KNOWN_SETTINGS_DATA_KEY) as KnownPluginSettingsMap;
|
||||
const newMap = new Map(map);
|
||||
await patcher(newMap);
|
||||
await DataStore.set(KNOWN_SETTINGS_DATA_KEY, newMap ?? map);
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ export const GuildlessServerListItemComponent = findComponentByCodeLazy("tooltip
|
|||
export const GuildedServerListItemPillComponent = findComponentByCodeLazy('"pill":"empty"');
|
||||
export const ServerListItemLowerBadgeComponent = findComponentByCodeLazy("STATUS_DANGER,disableColor:");
|
||||
export const ServerListItemUpperBadgeComponent = findComponentByCodeLazy("STATUS_DANGER,shape:", "iconBadge,");
|
||||
export const RadioGroup = findComponentByCodeLazy("radioItemIconClassName,");
|
||||
export const RadioGroup = findComponentByCodeLazy("\"label\",\"description\"]);");
|
||||
export const QuestTile = findComponentByCodeLazy(".rowIndex,trackGuildAndChannelMetadata");
|
||||
|
||||
export enum QuestStatus {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
|||
import { debounce } from "@shared/debounce";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { makeRange, OptionType } from "@utils/types";
|
||||
import { findByCode, findByProps, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { ChannelRouter, ChannelStore, ContextMenuApi, GuildStore, MediaEngineStore, Menu, PermissionsBits, PermissionStore, React, SelectedChannelStore, Toasts, UserStore, VoiceActions, VoiceStateStore } from "@webpack/common";
|
||||
|
||||
import style from "./styles.css?managed";
|
||||
|
|
@ -18,6 +18,8 @@ import style from "./styles.css?managed";
|
|||
const ChatVoiceIcon = findComponentByCodeLazy("0l1.8-1.8c.17");
|
||||
const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON");
|
||||
const ChannelActions = findByPropsLazy("selectChannel", "selectVoiceChannel");
|
||||
const startStream = findByCodeLazy('type:"STREAM_START"');
|
||||
const getDesktopSources = findByCodeLazy("desktop sources");
|
||||
|
||||
const valueOperation = [
|
||||
{ label: "More than", value: "<", default: false },
|
||||
|
|
@ -770,9 +772,7 @@ function JoinVc(channelID) {
|
|||
}
|
||||
|
||||
async function autoStream() {
|
||||
const startStream = findByCode('type:"STREAM_START"');
|
||||
const mediaEngine = findByProps("getMediaEngine").getMediaEngine();
|
||||
const getDesktopSources = findByCode("desktop sources");
|
||||
const mediaEngine = MediaEngineStore.getMediaEngine();
|
||||
const selected = SelectedChannelStore.getVoiceChannelId();
|
||||
if (!selected) return;
|
||||
const channel = ChannelStore.getChannel(selected);
|
||||
|
|
|
|||
34
src/equicordplugins/toneIndicators/ToneIndicator.tsx
Normal file
34
src/equicordplugins/toneIndicators/ToneIndicator.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Tooltip } from "@webpack/common";
|
||||
import type { ReactElement } from "react";
|
||||
|
||||
export interface ToneIndicatorProps {
|
||||
indicator: string;
|
||||
desc: string;
|
||||
}
|
||||
|
||||
export default function ToneIndicator({
|
||||
indicator,
|
||||
desc,
|
||||
}: ToneIndicatorProps): ReactElement {
|
||||
return (
|
||||
<Tooltip text={desc}>
|
||||
{tooltipProps => (
|
||||
<span
|
||||
{...tooltipProps}
|
||||
style={{
|
||||
color: "var(--text-normal)",
|
||||
userSelect: "text",
|
||||
}}
|
||||
>
|
||||
/{indicator}
|
||||
</span>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
142
src/equicordplugins/toneIndicators/index.tsx
Normal file
142
src/equicordplugins/toneIndicators/index.tsx
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { React } from "@webpack/common";
|
||||
import { type ReactNode } from "react";
|
||||
|
||||
import indicatorsDefault from "./indicators";
|
||||
import ToneIndicator from "./ToneIndicator";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
customIndicators: {
|
||||
type: OptionType.STRING,
|
||||
description: "Custom tone indicators (format: jk=Joking; srs=Serious)",
|
||||
default: "",
|
||||
},
|
||||
maxIndicators: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "Maximum number of tone indicators to show per message",
|
||||
default: 5,
|
||||
},
|
||||
});
|
||||
|
||||
function getCustomIndicators(): Record<string, string> {
|
||||
const raw = settings.store.customIndicators || "";
|
||||
const result: Record<string, string> = {};
|
||||
|
||||
raw.split("; ").forEach(entry => {
|
||||
const [key, ...rest] = entry.split("=");
|
||||
if (key && rest.length > 0) {
|
||||
result[key.trim().toLowerCase()] = rest.join("=").trim();
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getIndicator(text: string): string | null {
|
||||
text = text.toLowerCase();
|
||||
const customIndicators = getCustomIndicators();
|
||||
|
||||
return (
|
||||
customIndicators[text] ||
|
||||
customIndicators[`_${text}`] ||
|
||||
indicatorsDefault.get(text) ||
|
||||
indicatorsDefault.get(`_${text}`) ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
function splitTextWithIndicators(text: string): ReactNode[] {
|
||||
const nodes: ReactNode[] = [];
|
||||
let lastIndex = 0;
|
||||
let count = 0;
|
||||
const regex = /\/([a-z]{1,20})(?=\s|$|[^\w])/gi;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = regex.exec(text)) && count < settings.store.maxIndicators) {
|
||||
const indicator = match[1];
|
||||
const desc = getIndicator(indicator);
|
||||
if (!desc) continue;
|
||||
|
||||
if (match.index > lastIndex) {
|
||||
nodes.push(text.slice(lastIndex, match.index));
|
||||
}
|
||||
|
||||
nodes.push(
|
||||
<ToneIndicator
|
||||
key={`ti-${match.index}`}
|
||||
indicator={indicator}
|
||||
desc={desc}
|
||||
/>,
|
||||
);
|
||||
|
||||
lastIndex = regex.lastIndex;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function patchChildrenTree(children: any): any {
|
||||
const transform = (node: any): any => {
|
||||
if (node == null) return node;
|
||||
|
||||
if (typeof node === "string") {
|
||||
if (!/\/[a-z]+/i.test(node)) return node;
|
||||
const parts = splitTextWithIndicators(node);
|
||||
return parts.length === 1 ? parts[0] : parts;
|
||||
}
|
||||
|
||||
if (node?.props?.children != null) {
|
||||
const c = node.props.children;
|
||||
if (Array.isArray(c)) {
|
||||
node.props.children = c.map(transform).flat();
|
||||
} else {
|
||||
node.props.children = transform(c);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
if (Array.isArray(children)) return children.map(transform).flat();
|
||||
return transform(children);
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "ToneIndicators",
|
||||
description: "Show tooltips for tone indicators like /srs, /gen, etc. in sent messages.",
|
||||
authors: [EquicordDevs.justjxke],
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: '["strong","em","u","text","inlineCode","s","spoiler"]',
|
||||
replacement: [
|
||||
{
|
||||
match: /(?=return\{hasSpoilerEmbeds:\i,content:(\i)\})/,
|
||||
replace: (_: any, content: string) =>
|
||||
`${content}=$self.patchToneIndicators(${content});`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
patchToneIndicators(content: any): any {
|
||||
try {
|
||||
return patchChildrenTree(content);
|
||||
} catch {
|
||||
return content;
|
||||
}
|
||||
},
|
||||
});
|
||||
62
src/equicordplugins/toneIndicators/indicators.ts
Normal file
62
src/equicordplugins/toneIndicators/indicators.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
const defaultIndicators = new Map(
|
||||
Object.entries({
|
||||
j: "Joking",
|
||||
hj: "Half Joking",
|
||||
s: "Sarcastic",
|
||||
_sarc: "Sarcastic", // Alias for s
|
||||
gen: "Genuine",
|
||||
_g: "Genuine", // Alias for gen,
|
||||
genq: "Genuine Question",
|
||||
_gq: "Genuine Question", // Alias for genq
|
||||
srs: "Serious",
|
||||
nsrs: "Non-Serious",
|
||||
pos: "Positive Connotation",
|
||||
_pc: "Positive Connotation", // Alias for pos
|
||||
neu: "Neutral Connotation",
|
||||
neg: "Negative Connotation",
|
||||
_nc: "Negative Connotation", // Alias for neg
|
||||
p: "Platonic",
|
||||
r: "Romantic",
|
||||
a: "Alterous",
|
||||
c: "Copypasta",
|
||||
l: "Lyrics",
|
||||
_ly: "Lyrics", // Alias for l
|
||||
lh: "Light-Hearted",
|
||||
nm: "Not Mad",
|
||||
lu: "A Little Upset",
|
||||
nbh: "Not Directed At Anybody Here",
|
||||
nay: "Not Directed At You",
|
||||
ay: "Directed At You",
|
||||
nsb: "Not Subtweeting",
|
||||
x: "Sexual Intent",
|
||||
_sx: "Sexual Intent", // Alias for x
|
||||
nx: "Non-Sexual Intent",
|
||||
_nsx: "Non-Sexual Intent", // Alias for nx
|
||||
rh: "Rhetorical Question",
|
||||
_rt: "Rhetorical Question", // Alias for rh
|
||||
t: "Teasing",
|
||||
ij: "Inside Joke",
|
||||
m: "Metaphorically",
|
||||
li: "Literally",
|
||||
hyp: "Hyperbole",
|
||||
ex: "Exaggeration",
|
||||
f: "Fake",
|
||||
q: "Quote",
|
||||
th: "Threat",
|
||||
cb: "Clickbait",
|
||||
ref: "Reference",
|
||||
nf: "Not Forced",
|
||||
nbr: "Not Being Rude",
|
||||
ot: "Off-Topic",
|
||||
nj: "Not Joking",
|
||||
naj: "Not A Joke",
|
||||
}),
|
||||
);
|
||||
|
||||
export default defaultIndicators;
|
||||
|
|
@ -142,10 +142,10 @@ export default definePlugin({
|
|||
]
|
||||
},
|
||||
{
|
||||
find: "profileCardUsernameRow,children",
|
||||
find: "profileCardUsernameRow,children:",
|
||||
replacement: {
|
||||
match: /(?<=accountProfileCard.{0,50}displayProfile:(\i).*?badges:)(\i)/,
|
||||
replace: "[...$self.getBadges($1),...$2]"
|
||||
match: /badges:(\i)(?<=displayProfile:(\i).+?)/,
|
||||
replace: "badges:[...$self.getBadges($2),...$1]"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -284,6 +284,8 @@ export default definePlugin({
|
|||
// Allow users to use custom client themes
|
||||
{
|
||||
find: "customUserThemeSettings:{",
|
||||
// Discord has two separate modules for treatments 1 and 2
|
||||
all: true,
|
||||
replacement: {
|
||||
match: /(?<=\i=)\(0,\i\.\i\)\(\i\.\i\.TIER_2\)(?=,|;)/g,
|
||||
replace: "true"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.messagelogger-deleted:is(.messagelogger-deleted-attachment,
|
||||
.messagelogger-deleted :is(.messagelogger-deleted-attachment,
|
||||
.emoji,
|
||||
[data-type="sticker"],
|
||||
[class*="embedIframe"],
|
||||
|
|
@ -95,4 +95,4 @@
|
|||
.messagelogger-diff-disabled .messagelogger-diff-removed [class*="emojiContainer"]::after,
|
||||
.messagelogger-diff-disabled .messagelogger-diff-removed img.emoji::after {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,119 +17,184 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings, migratePluginSetting } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { runtimeHashMessageKey } from "@utils/intlHash";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Message } from "@vencord/discord-types";
|
||||
import { i18n, MessageStore, RelationshipStore } from "@webpack/common";
|
||||
import { MessageStore, RelationshipStore } from "@webpack/common";
|
||||
|
||||
interface MessageDeleteProps {
|
||||
// Internal intl message for BLOCKED_MESSAGE_COUNT
|
||||
collapsedReason: () => any;
|
||||
interface ChannelStreamDividerProps {
|
||||
type: "DIVIDER",
|
||||
content?: string,
|
||||
contentKey?: string,
|
||||
unreadId?: string,
|
||||
}
|
||||
|
||||
// Remove this migration once enough time has passed
|
||||
migratePluginSetting("NoBlockedMessages", "ignoreMessages", "ignoreBlockedMessages");
|
||||
interface ChannelStreamMessageProps {
|
||||
type: "MESSAGE",
|
||||
content: Message,
|
||||
}
|
||||
|
||||
interface ChannelStreamGroupProps {
|
||||
type: "MESSAGE_GROUP_BLOCKED" | "MESSAGE_GROUP_IGNORED",
|
||||
content: ChannelStreamMessageProps[] | any,
|
||||
}
|
||||
|
||||
migratePluginSetting("NoBlockedMessages", "disableNotifications", "ignoreMessages");
|
||||
migratePluginSetting("NoBlockedMessages", "alsoHideIgnoredUsers", "applyToIgnoredUsers");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
ignoreMessages: {
|
||||
description: "Completely ignores incoming messages from blocked and ignored (if enabled) users",
|
||||
alsoHideIgnoredUsers: {
|
||||
description: "Also hide messages from ignored users.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
restartNeeded: false
|
||||
},
|
||||
disableNotifications: {
|
||||
description: "Hide new message notifications for blocked/ignored users. Always true if \"Default Hide Users\" is enabled below and the user triggering the notification is not exempted in \"Override Users\".",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
restartNeeded: true
|
||||
restartNeeded: false
|
||||
},
|
||||
hideRepliesToBlockedMessages: {
|
||||
description: "Hides replies to blocked messages.",
|
||||
hideBlockedUserReplies: {
|
||||
description: "Hide replies to blocked/ignored users.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
restartNeeded: false,
|
||||
},
|
||||
applyToIgnoredUsers: {
|
||||
description: "Additionally apply to 'ignored' users",
|
||||
defaultHideUsers: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "If enabled, messages from blocked/ignored users will be completely hidden and any messages from user IDs in the override list will be collapsed (default Discord behavior) instead. If disabled, messages from blocked/ignored users will be collapsed and any messages from user IDs in the override list will be completely hidden instead.",
|
||||
default: true,
|
||||
restartNeeded: false
|
||||
}
|
||||
restartNeeded: false,
|
||||
},
|
||||
overrideUsers: {
|
||||
type: OptionType.STRING,
|
||||
description: "Comma separated list of user IDs which will be hidden or collapsed instead of the default behavior selected above.",
|
||||
restartNeeded: false,
|
||||
default: ""
|
||||
},
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "NoBlockedMessages",
|
||||
description: "Hides all blocked/ignored messages from chat completely",
|
||||
authors: [Devs.rushii, Devs.Samu, Devs.jamesbt365],
|
||||
description: "Hide all blocked/ignored messages from chat completely.",
|
||||
authors: [Devs.rushii, Devs.Samu, Devs.jamesbt365, Devs.Elvyra, EquicordDevs.Etorix],
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: ".__invalid_blocked,",
|
||||
replacement: [
|
||||
{
|
||||
match: /let{expanded:\i,[^}]*?collapsedReason[^}]*}/,
|
||||
replace: "if($self.shouldHide(arguments[0]))return null;$&"
|
||||
}
|
||||
]
|
||||
},
|
||||
...[
|
||||
'"MessageStore"',
|
||||
'"ReadStateStore"'
|
||||
].map(find => ({
|
||||
find,
|
||||
predicate: () => settings.store.ignoreMessages,
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=function (\i)\((\i)\){)(?=.*MESSAGE_CREATE:\1)/,
|
||||
replace: (_, _funcName, props) => `if($self.shouldIgnoreMessage(${props}.message)||$self.isReplyToBlocked(${props}.message))return;`
|
||||
replace: (_, _funcName, props) => `if($self.disableNotification(${props}.message)){return;};`
|
||||
}
|
||||
]
|
||||
})),
|
||||
{
|
||||
find: "referencedUsernameProfile,referencedAvatarProfile",
|
||||
find: '"forum-post-action-bar-"',
|
||||
replacement: [
|
||||
{
|
||||
match: /(?=\(0,\i.jsx\)\(\i.\i,\{offset)/,
|
||||
replace: "$&!$self.isReplyToBlocked(arguments[0].message)&&",
|
||||
match: /(\i).map\(/,
|
||||
replace: "$self.filterStream($1).map("
|
||||
}
|
||||
],
|
||||
]
|
||||
},
|
||||
],
|
||||
|
||||
shouldIgnoreMessage(message: Message) {
|
||||
hideBlockedMessage(userId: string) {
|
||||
const overrideUsers = settings.store.overrideUsers.split(",").map(id => id.trim()).filter(id => id.length > 0);
|
||||
|
||||
if (settings.store.defaultHideUsers) {
|
||||
// If hidden by default, overriding shows messages as collapsed.
|
||||
return overrideUsers.includes(userId);
|
||||
} else {
|
||||
// If collapsed by default, overriding hides messages.
|
||||
return !overrideUsers.includes(userId);
|
||||
}
|
||||
},
|
||||
|
||||
shouldKeepMessage(message: Message) {
|
||||
const blocked = this.isBlocked(message);
|
||||
const replyToBlocked = this.isReplyToBlocked(message);
|
||||
|
||||
if (blocked) return [this.hideBlockedMessage(message.author.id), true];
|
||||
if (replyToBlocked) return [this.hideBlockedMessage(replyToBlocked.author.id), true];
|
||||
|
||||
// [Message Visible, Author Blocked/Ignored]
|
||||
return [true, false];
|
||||
},
|
||||
|
||||
disableNotification(message: Message) {
|
||||
if (!message) return false;
|
||||
const messageFilteredData = this.shouldKeepMessage(message);
|
||||
const messageHidden = !messageFilteredData[0];
|
||||
const messageBlocked = messageFilteredData[1];
|
||||
// Always disable notifications for completely hidden messages as not doing so will
|
||||
// cause the client to scroll up into the loaded messages in search of an
|
||||
// unread message which it can't find because it was filtered.
|
||||
return messageHidden || (messageBlocked && settings.store.disableNotifications);
|
||||
},
|
||||
|
||||
filterStream(channelStream: [ChannelStreamGroupProps | ChannelStreamMessageProps | ChannelStreamDividerProps]) {
|
||||
const { alsoHideIgnoredUsers, disableNotifications, hideBlockedUserReplies, defaultHideUsers, overrideUsers } = settings.use();
|
||||
|
||||
const newChannelStream = channelStream.map(item => {
|
||||
if (item.type === "MESSAGE_GROUP_BLOCKED" || (item.type === "MESSAGE_GROUP_IGNORED" && alsoHideIgnoredUsers)) {
|
||||
const groupItem = item as ChannelStreamGroupProps;
|
||||
|
||||
const filteredContent = groupItem.content.filter((item: ChannelStreamMessageProps) => {
|
||||
return item.type !== "MESSAGE" || this.shouldKeepMessage(item.content)[0];
|
||||
});
|
||||
|
||||
return filteredContent.some(item => item.type === "MESSAGE") ? { ...groupItem, content: filteredContent } : null;
|
||||
}
|
||||
|
||||
return (item.type !== "MESSAGE" || this.shouldKeepMessage((item as ChannelStreamMessageProps).content)[0]) ? item : null;
|
||||
}).filter(item => item !== null);
|
||||
|
||||
let lastItem = newChannelStream[newChannelStream.length - 1];
|
||||
|
||||
// Remove the NEW Message divider if it is the last item,
|
||||
// implying the messages it was announcing got filtered.
|
||||
while (lastItem && lastItem.type === "DIVIDER" && lastItem.unreadId !== undefined) {
|
||||
newChannelStream.pop();
|
||||
lastItem = newChannelStream[newChannelStream.length - 1];
|
||||
}
|
||||
|
||||
return newChannelStream;
|
||||
},
|
||||
|
||||
isReplyToBlocked(message: Message) {
|
||||
if (!settings.store.hideBlockedUserReplies) return false;
|
||||
|
||||
try {
|
||||
// Messages received from the non-focused channel may have a referenced_message property.
|
||||
let repliedMessage: Message | null = (message as any).referenced_message || null;
|
||||
|
||||
if (!repliedMessage) {
|
||||
const messageReference = message.messageReference || (message as any).message_reference;
|
||||
repliedMessage = messageReference ? MessageStore.getMessage(messageReference.channel_id, messageReference.message_id) : null;
|
||||
}
|
||||
|
||||
return repliedMessage && this.isBlocked(repliedMessage) ? repliedMessage : false;
|
||||
} catch (e) {
|
||||
new Logger("NoBlockedMessages").error("Failed to check if referenced message is blocked or ignored:", e);
|
||||
}
|
||||
},
|
||||
|
||||
isBlocked(message: Message) {
|
||||
try {
|
||||
if (RelationshipStore.isBlocked(message.author.id)) {
|
||||
return true;
|
||||
}
|
||||
return settings.store.applyToIgnoredUsers && RelationshipStore.isIgnored(message.author.id);
|
||||
|
||||
return settings.store.alsoHideIgnoredUsers && RelationshipStore.isIgnored(message.author.id);
|
||||
} catch (e) {
|
||||
new Logger("NoBlockedMessages").error("Failed to check if user is blocked or ignored:", e);
|
||||
return false;
|
||||
new Logger("NoBlockedMessages").error("Failed to check if message is blocked or ignored:", e);
|
||||
}
|
||||
},
|
||||
|
||||
shouldHide(props: MessageDeleteProps): boolean {
|
||||
try {
|
||||
const collapsedReason = props.collapsedReason();
|
||||
const blockedReason = i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")]();
|
||||
const ignoredReason = settings.store.applyToIgnoredUsers
|
||||
? i18n.t[runtimeHashMessageKey("IGNORED_MESSAGE_COUNT")]()
|
||||
: null;
|
||||
|
||||
return collapsedReason === blockedReason || collapsedReason === ignoredReason;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
isReplyToBlocked(message: Message) {
|
||||
if (!settings.store.hideRepliesToBlockedMessages) return false;
|
||||
try {
|
||||
const { messageReference } = message;
|
||||
if (!messageReference) return false;
|
||||
|
||||
const replyMessage = MessageStore.getMessage(messageReference.channel_id, messageReference.message_id);
|
||||
|
||||
return replyMessage ? this.shouldIgnoreMessage(replyMessage) : false;
|
||||
} catch (e) {
|
||||
new Logger("NoBlockedMessages").error("Failed to check if referenced message is blocked:", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export const settings = definePluginSettings({
|
|||
},
|
||||
disableCreateDMButton: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Disabled the create dm button",
|
||||
description: "Disables the create dm button",
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
|
@ -160,9 +160,9 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: ".createDMButtonContainer",
|
||||
find: ".createDMButtonContainer,",
|
||||
replacement: {
|
||||
match: /"full-width"===\i/,
|
||||
match: /"create-dm"\)/,
|
||||
replace: "$&&&false"
|
||||
},
|
||||
predicate: () => settings.store.disableCreateDMButton
|
||||
|
|
|
|||
|
|
@ -153,7 +153,11 @@ export default definePlugin({
|
|||
const myId = useStateFromStores([AuthenticationStore], () => AuthenticationStore.getId());
|
||||
|
||||
return Object.keys(typingUsers)
|
||||
.filter(id => id && id !== myId && !RelationshipStore.isBlockedOrIgnored(id))
|
||||
.filter(id => {
|
||||
if (!id || RelationshipStore.isBlockedOrIgnored(id)) return false;
|
||||
if (id === myId) return Settings.plugins.AmITyping?.enabled;
|
||||
return true;
|
||||
})
|
||||
.map(id => UserStore.getUser(id))
|
||||
.filter(isNonNullish);
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -19,41 +19,16 @@
|
|||
import * as DataStore from "@api/DataStore";
|
||||
import { showNotification } from "@api/Notifications";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { Alerts, OAuth2AuthorizeModal, UserStore } from "@webpack/common";
|
||||
import { OAuth2AuthorizeModal, UserStore } from "@webpack/common";
|
||||
|
||||
import { Logger } from "./Logger";
|
||||
import { openModal } from "./modal";
|
||||
import { relaunch } from "./native";
|
||||
|
||||
export const cloudLogger = new Logger("Cloud", "#39b7e0");
|
||||
|
||||
export const getCloudUrl = () => new URL(Settings.cloud.url);
|
||||
const getCloudUrlOrigin = () => getCloudUrl().origin;
|
||||
|
||||
export async function checkCloudUrlCsp() {
|
||||
if (IS_WEB) return true;
|
||||
|
||||
const { host } = getCloudUrl();
|
||||
if (host === "api.vencord.dev") return true;
|
||||
if (host === "cloud.equicord.org") return true;
|
||||
|
||||
if (await VencordNative.csp.isDomainAllowed(Settings.cloud.url, ["connect-src"])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const res = await VencordNative.csp.requestAddOverride(Settings.cloud.url, ["connect-src"], "Cloud Sync");
|
||||
if (res === "ok") {
|
||||
Alerts.show({
|
||||
title: "Cloud Integration enabled",
|
||||
body: `${host} has been added to the whitelist. Please restart the app for the changes to take effect.`,
|
||||
confirmText: "Restart now",
|
||||
cancelText: "Later!",
|
||||
onConfirm: relaunch
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const getUserId = () => {
|
||||
const id = UserStore.getCurrentUser()?.id;
|
||||
if (!id) throw new Error("User not yet logged in");
|
||||
|
|
@ -104,8 +79,6 @@ export async function authorizeCloud() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!await checkCloudUrlCsp()) return;
|
||||
|
||||
try {
|
||||
const oauthConfiguration = await fetch(new URL("/v1/oauth/settings", getCloudUrl()));
|
||||
var { clientId, redirectUri } = await oauthConfiguration.json();
|
||||
|
|
|
|||
|
|
@ -1145,6 +1145,10 @@ export const EquicordDevs = Object.freeze({
|
|||
name: "Mocha",
|
||||
id: 808802000224518264n
|
||||
},
|
||||
justjxke: {
|
||||
name: "justjxke",
|
||||
id: 852558183087472640n
|
||||
}
|
||||
} satisfies Record<string, Dev>);
|
||||
|
||||
// iife so #__PURE__ works correctly
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { PlainSettings, Settings } from "@api/Settings";
|
|||
import { moment, SettingsRouter, Toasts } from "@webpack/common";
|
||||
import { deflateSync, inflateSync } from "fflate";
|
||||
|
||||
import { checkCloudUrlCsp, getCloudAuth, getCloudUrl } from "./cloud";
|
||||
import { getCloudAuth, getCloudUrl } from "./cloud";
|
||||
import { Logger } from "./Logger";
|
||||
import { relaunch } from "./native";
|
||||
import { chooseFile, saveFile } from "./web";
|
||||
|
|
@ -118,8 +118,6 @@ const cloudSettingsLogger = new Logger("Cloud:Settings", "#39b7e0");
|
|||
export async function putCloudSettings(manual?: boolean) {
|
||||
const settings = await exportSettings({ minify: true });
|
||||
|
||||
if (!await checkCloudUrlCsp()) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
|
||||
method: "PUT",
|
||||
|
|
@ -164,8 +162,6 @@ export async function putCloudSettings(manual?: boolean) {
|
|||
}
|
||||
|
||||
export async function getCloudSettings(shouldNotify = true, force = false) {
|
||||
if (!await checkCloudUrlCsp()) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
|
||||
method: "GET",
|
||||
|
|
@ -268,8 +264,6 @@ export async function getCloudSettings(shouldNotify = true, force = false) {
|
|||
}
|
||||
|
||||
export async function deleteCloudSettings() {
|
||||
if (!await checkCloudUrlCsp()) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
|
||||
method: "DELETE",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue