Compare commits

...

31 Commits

Author SHA1 Message Date
V
7e8397a4da Bump to 1.2.8 2023-06-16 19:47:01 +02:00
V
555cf64080 BiggerStreamPreview: Hide element if no stream is found 2023-06-16 19:46:39 +02:00
V
2039e10fd5 Codeberg Mirror: Hopefully fix race condition 2023-06-16 19:45:01 +02:00
Phil
e8d90d2b45 feat(plugin): BiggerStreamPreview (#1222)
Co-authored-by: V <vendicated@riseup.net>
2023-06-16 19:35:50 +02:00
kb
55af40ee74 MessageLogger: Add user ignore list (#1275)
Co-authored-by: V <vendicated@riseup.net>
2023-06-16 19:35:35 +02:00
V
a1fabcdf0a UnsuppressEmbeds: Support dms 2023-06-16 19:28:30 +02:00
V
eaeb60308e [skip ci] Add version to /vencord-debug 2023-06-16 19:07:22 +02:00
Amia
662c0227eb New Plugin: MutualGroupDMs (#1239)
Co-authored-by: V <vendicated@riseup.net>
2023-06-15 03:39:15 +02:00
Dominik
543fdf4943 New Plugin: UnsuppressEmbeds (#1262)
Co-authored-by: V <vendicated@riseup.net>
2023-06-15 03:18:34 +02:00
Nuckyz
1225383723 Fix broken SHC patch (#1290) 2023-06-15 01:05:15 +00:00
V
07a9adbce2 🧹🧹 2023-06-13 03:45:05 +02:00
V
42d8211871 Fix disabling plugins whose stop() errors 2023-06-13 02:29:29 +02:00
V
ab3e993274 ViewRaw: Remove less properties from author 2023-06-13 02:23:06 +02:00
rad
386dfe363a ViewRaw: Add setting for swapping left/right click (#1263)
Co-authored-by: V <vendicated@riseup.net>
2023-06-13 01:05:04 +02:00
AAGaming
a4191c9f6c Settings: add custom sections support (#1270)
Co-authored-by: V <vendicated@riseup.net>
2023-06-11 23:11:56 +02:00
V
f1349a2787 Remove Cloud Sync notification 2023-06-11 22:57:07 +02:00
V
3680c26f72 Bump to 1.2.7 2023-06-09 22:18:32 +02:00
Axu
683c92f904 fix: do not highlight new member leaf in red in message logger (#1071)
Co-authored-by: V <vendicated@riseup.net>
2023-06-09 22:17:44 +02:00
Rendundakat
3410ed024f QuickReply: respect users in userList of NoReplyMention (#1191)
Co-authored-by: V <vendicated@riseup.net>
2023-06-09 22:17:32 +02:00
Luna
dbad10984a [skip ci] Don't run ci on forks; Fix interaction nicks in SMYN (#4)
The following actions are disabled unless run on the main repo: codeberg, publish, build/upload, reportBrokenPlugins

Reviewed-on: https://codeberg.org/Ven/cord/pulls/4
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-06-09 19:48:18 +00:00
whqwert
55543d8640 Fix(RevealAllSpoilers): New spoiler class name (#1229) 2023-06-04 17:22:21 -03:00
Manti
263fbc377e Fix ReviewDB auth (#1227) 2023-06-03 20:07:04 +00:00
AutumnVN
c9c0ab5aca Fix cloud auth [object%20Object] (#1226) 2023-06-03 16:57:31 -03:00
V
7b2bf08b8f [skip ci] ci: fix inconsistent formatting 2023-06-01 22:49:15 +02:00
TheKodeToad
43011825af Add NoProfileThemes plugin (#1193)
Co-authored-by: V <vendicated@riseup.net>
2023-05-31 19:14:18 +02:00
fawn
4abcea61f8 feat(MessageClickAction): delete message logger history (#1204) 2023-05-31 19:14:17 +02:00
V
cba810cab5 Improve settings selectable text style 2023-05-31 19:14:17 +02:00
Amia
5938c7d67c Plugin: NoPendingCount (#1216)
Co-authored-by: V <vendicated@riseup.net>
2023-05-31 19:14:17 +02:00
V
99d8b8b75f VcNarrator: Fix always saying 'someone' 2023-05-31 19:14:14 +02:00
Luna
503d49d295 Add codeberg to gitlens and add build tasks (#3)
It needed to be added inorder for gitlens to recognise the remote as a gitea instance
without, it wouldnt be able to open branches/specific commits correctly

and build tasks are just for ease of development

(teeny weeny pr bc im not gonna do anything else with this branch)

Co-authored-by: Luna R <imlvnaa@gmail.com>
Reviewed-on: https://codeberg.org/Ven/cord/pulls/3
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-05-31 17:12:37 +00:00
V
137b79d95b README: Add codeberg mirror 2023-05-30 16:27:59 +02:00
79 changed files with 1078 additions and 321 deletions

View File

@ -42,37 +42,39 @@ jobs:
- name: Clean up obsolete files
run: |
rm -rf dist/*-unpacked Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
rm -rf dist/*-unpacked Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
- name: Get some values needed for the release
id: release_values
run: |
echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Upload DevBuild as release
if: github.repository == 'Vendicated/Vencord'
run: |
gh release upload devbuild --clobber dist/*
gh release edit devbuild --title "DevBuild $RELEASE_TAG"
gh release upload devbuild --clobber dist/*
gh release edit devbuild --title "DevBuild $RELEASE_TAG"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ env.release_tag }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ env.release_tag }}
- name: Upload DevBuild to builds repo
if: github.repository == 'Vendicated/Vencord'
run: |
git config --global user.name "$USERNAME"
git config --global user.email actions@github.com
git config --global user.name "$USERNAME"
git config --global user.email actions@github.com
git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git upload
cd upload
git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git upload
cd upload
GLOBIGNORE=.git:.gitignore:README.md:LICENSE
rm -rf *
cp -r ../dist/* .
GLOBIGNORE=.git:.gitignore:README.md:LICENSE
rm -rf *
cp -r ../dist/* .
git add -A
git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA"
git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git
git add -A
git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA"
git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git
env:
API_TOKEN: ${{ secrets.BUILDS_TOKEN }}
GH_REPO: Vencord/builds
USERNAME: GitHub-Actions
API_TOKEN: ${{ secrets.BUILDS_TOKEN }}
GH_REPO: Vencord/builds
USERNAME: GitHub-Actions

View File

@ -1,4 +1,7 @@
name: Sync to Codeberg
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
on:
push:
workflow_dispatch:
@ -7,6 +10,7 @@ on:
jobs:
codeberg:
if: github.repository == 'Vendicated/Vencord'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

View File

@ -6,6 +6,7 @@ on:
jobs:
Publish:
if: github.repository == 'Vendicated/Vencord'
runs-on: ubuntu-latest
steps:
@ -13,11 +14,11 @@ jobs:
- name: check that tag matches package.json version
run: |
pkg_version="v$(jq -r .version < package.json)"
if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then
echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2
exit 1
fi
pkg_version="v$(jq -r .version < package.json)"
if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then
echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2
exit 1
fi
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
@ -35,26 +36,26 @@ jobs:
- name: Publish extension
run: |
# Do not fail so that even if chrome fails, firefox gets a shot. But also store exit code to fail workflow later
EXIT_CODE=0
# Do not fail so that even if chrome fails, firefox gets a shot. But also store exit code to fail workflow later
EXIT_CODE=0
# Chrome
cd dist/chromium-unpacked
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish || EXIT_CODE=$?
# Chrome
cd dist/chromium-unpacked
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish || EXIT_CODE=$?
# Firefox
cd ../firefox-unpacked
npm i -g web-ext@7.4.0 web-ext-submit@7.4.0
web-ext-submit || EXIT_CODE=$?
# Firefox
cd ../firefox-unpacked
npm i -g web-ext@7.4.0 web-ext-submit@7.4.0
web-ext-submit || EXIT_CODE=$?
exit $EXIT_CODE
exit $EXIT_CODE
env:
# Chrome
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
# Chrome
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
# Firefox
WEB_EXT_API_KEY: ${{ secrets.WEBEXT_USER }}
WEB_EXT_API_SECRET: ${{ secrets.WEBEXT_SECRET }}
# Firefox
WEB_EXT_API_KEY: ${{ secrets.WEBEXT_USER }}
WEB_EXT_API_SECRET: ${{ secrets.WEBEXT_SECRET }}

View File

@ -2,11 +2,12 @@ name: Test Patches
on:
workflow_dispatch:
schedule:
# Every day at midnight
- cron: 0 0 * * *
# Every day at midnight
- cron: 0 0 * * *
jobs:
TestPlugins:
if: github.repository == 'Vendicated/Vencord'
runs-on: ubuntu-latest
steps:
@ -22,10 +23,10 @@ jobs:
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
pnpm add puppeteer
pnpm install --frozen-lockfile
pnpm add puppeteer
sudo apt-get install -y chromium-browser
sudo apt-get install -y chromium-browser
- name: Build web
run: pnpm buildWeb --standalone
@ -33,25 +34,25 @@ jobs:
- name: Create Report
timeout-minutes: 10
run: |
export PATH="$PWD/node_modules/.bin:$PATH"
export CHROMIUM_BIN=$(which chromium-browser)
export PATH="$PWD/node_modules/.bin:$PATH"
export CHROMIUM_BIN=$(which chromium-browser)
esbuild scripts/generateReport.ts > dist/report.mjs
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
esbuild scripts/generateReport.ts > dist/report.mjs
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
- name: Create Report (Canary)
timeout-minutes: 10
if: success() || failure() # even run if previous one failed
run: |
export PATH="$PWD/node_modules/.bin:$PATH"
export CHROMIUM_BIN=$(which chromium-browser)
export USE_CANARY=true
export PATH="$PWD/node_modules/.bin:$PATH"
export CHROMIUM_BIN=$(which chromium-browser)
export USE_CANARY=true
esbuild scripts/generateReport.ts > dist/report.mjs
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
esbuild scripts/generateReport.ts > dist/report.mjs
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}

View File

@ -12,5 +12,12 @@
"javascript.format.semicolons": "insert",
"typescript.format.semicolons": "insert",
"typescript.preferences.quoteStyle": "double",
"javascript.preferences.quoteStyle": "double"
"javascript.preferences.quoteStyle": "double",
"gitlens.remotes": [
{
"domain": "codeberg.org",
"type": "Gitea"
}
]
}

25
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,25 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build",
"type": "shell",
"command": "pnpm build",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "Watch",
"type": "shell",
"command": "pnpm watch",
"problemMatcher": [],
"group": {
"kind": "build"
}
}
]
}

View File

@ -1,7 +1,8 @@
# Vencord
The cutest Discord client mod
[![Codeberg Mirror](https://img.shields.io/static/v1?style=for-the-badge&label=Codeberg%20Mirror&message=codeberg.org/Ven/cord&color=2185D0&logo=data:image/png;base64,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)](https://codeberg.org/Ven/cord)
The cutest Discord client mod
![](https://user-images.githubusercontent.com/45497981/235015332-0453d3eb-1da6-4601-963e-ef5e454123a1.png)
*A screenshot of Vencord featuring the [ClearVision-v6 theme](https://github.com/ClearVision/ClearVision-v6) (Vencord does not come with it pre-installed, it is only an example)*

View File

@ -41,12 +41,5 @@
"path": "modifyResponseHeaders.json"
}
]
},
"browser_specific_settings": {
"gecko": {
"id": "vencord-firefox@vendicated.dev",
"strict_min_version": "109.0"
}
}
}

View File

@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
"version": "1.2.6",
"version": "1.2.8",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@ -24,7 +24,7 @@
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins",
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
"lint:fix": "pnpm lint --fix",
"test": "pnpm build && pnpm lint && pnpm lint-styles && pnpm testTsc",
"test": "pnpm build && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
"testTsc": "tsc --noEmit",
"uninject": "node scripts/runInstaller.mjs",

View File

@ -19,11 +19,13 @@
import esbuild from "esbuild";
import { commonOpts, globPlugins, isStandalone, watch } from "./common.mjs";
import { commonOpts, globPlugins, isStandalone, VERSION, watch } from "./common.mjs";
const defines = {
IS_STANDALONE: isStandalone,
IS_DEV: JSON.stringify(watch)
IS_DEV: JSON.stringify(watch),
VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP: Date.now(),
};
if (defines.IS_STANDALONE === "false")
// If this is a local build (not standalone), optimise

View File

@ -24,9 +24,7 @@ import { readFileSync } from "fs";
import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
import { join } from "path";
// wtf is this assert syntax
import PackageJSON from "../../package.json" assert { type: "json" };
import { commonOpts, globPlugins, watch } from "./common.mjs";
import { commonOpts, globPlugins, VERSION, watch } from "./common.mjs";
/**
* @type {esbuild.BuildOptions}
@ -47,7 +45,9 @@ const commonOptions = {
IS_STANDALONE: "true",
IS_DEV: JSON.stringify(watch),
IS_DISCORD_DESKTOP: "false",
IS_VENCORD_DESKTOP: "false"
IS_VENCORD_DESKTOP: "false",
VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP: Date.now(),
}
};
@ -67,7 +67,7 @@ await Promise.all(
},
outfile: "dist/Vencord.user.js",
banner: {
js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${PackageJSON.version}.${new Date().getTime()}`)
js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${VERSION}.${new Date().getTime()}`)
},
footer: {
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
@ -88,7 +88,7 @@ async function buildPluginZip(target, files, shouldZip) {
let content = await readFile(join("browser", f));
if (f.startsWith("manifest")) {
const json = JSON.parse(content.toString("utf-8"));
json.version = PackageJSON.version;
json.version = VERSION;
content = new TextEncoder().encode(JSON.stringify(json));
}

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "../suppressExperimentalWarnings.js";
import "../checkNodeVersion.js";
import { exec, execSync } from "child_process";
@ -24,6 +25,11 @@ import { readdir, readFile } from "fs/promises";
import { join, relative } from "path";
import { promisify } from "util";
// wtf is this assert syntax
import PackageJSON from "../../package.json" assert { type: "json" };
export const VERSION = PackageJSON.version;
export const BUILD_TIMESTAMP = Date.now();
export const watch = process.argv.includes("--watch");
export const isStandalone = JSON.stringify(process.argv.includes("--standalone"));
export const gitHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
@ -64,7 +70,7 @@ export const globPlugins = kind => ({
});
build.onLoad({ filter, namespace: "import-plugins" }, async () => {
const pluginDirs = ["plugins", "userplugins"];
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
let code = "";
let plugins = "\n";
let i = 0;
@ -72,8 +78,9 @@ export const globPlugins = kind => ({
if (!existsSync(`./src/${dir}`)) continue;
const files = await readdir(`./src/${dir}`);
for (const file of files) {
if (file.startsWith(".")) continue;
if (file.startsWith("_") || file.startsWith(".")) continue;
if (file === "index.ts") continue;
const fileBits = file.split(".");
if (fileBits.length > 2 && ["ts", "tsx"].includes(fileBits.at(-1))) {
const mod = fileBits.at(-2);

View File

@ -171,8 +171,8 @@ async function parseFile(fileName: string) {
throw fail("no default export called 'definePlugin' found");
}
async function getEntryPoint(dirent: Dirent) {
const base = join("./src/plugins", dirent.name);
async function getEntryPoint(dir: string, dirent: Dirent) {
const base = join(dir, dirent.name);
if (!dirent.isDirectory()) return base;
for (const name of ["index.ts", "index.tsx"]) {
@ -186,13 +186,23 @@ async function getEntryPoint(dirent: Dirent) {
throw new Error(`${dirent.name}: Couldn't find entry point`);
}
function isPluginFile({ name }: { name: string; }) {
if (name === "index.ts") return false;
return !name.startsWith("_") && !name.startsWith(".");
}
(async () => {
parseDevs();
const plugins = readdirSync("./src/plugins", { withFileTypes: true }).filter(d => d.name !== "index.ts");
const promises = plugins.map(async dirent => parseFile(await getEntryPoint(dirent)));
const plugins = ["src/plugins", "src/plugins/_core"].flatMap(dir =>
readdirSync(dir, { withFileTypes: true })
.filter(isPluginFile)
.map(async dirent =>
parseFile(await getEntryPoint(dir, dirent))
)
);
const data = JSON.stringify(await Promise.all(promises));
const data = JSON.stringify(await Promise.all(plugins));
if (process.argv.length > 2) {
writeFileSync(process.argv[2], data);

View File

@ -22,7 +22,7 @@ import { ComponentType, HTMLProps } from "react";
import Plugins from "~plugins";
export enum BadgePosition {
export const enum BadgePosition {
START,
END
}
@ -79,7 +79,7 @@ export function _getBadges(args: BadgeUserArgs) {
: badges.push({ ...badge, ...args });
}
}
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/apiBadges").default).getDonorBadges(args.user.id);
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.user.id);
if (donorBadges) badges.unshift(...donorBadges);
return badges;

View File

@ -24,7 +24,7 @@ export interface CommandContext {
guild?: Guild;
}
export enum ApplicationCommandOptionType {
export const enum ApplicationCommandOptionType {
SUB_COMMAND = 1,
SUB_COMMAND_GROUP = 2,
STRING = 3,
@ -38,7 +38,7 @@ export enum ApplicationCommandOptionType {
ATTACHMENT = 11,
}
export enum ApplicationCommandInputType {
export const enum ApplicationCommandInputType {
BUILT_IN = 0,
BUILT_IN_TEXT = 1,
BUILT_IN_INTEGRATION = 2,
@ -64,7 +64,7 @@ export interface ChoicesOption {
displayName?: string;
}
export enum ApplicationCommandType {
export const enum ApplicationCommandType {
CHAT_INPUT = 1,
USER = 2,
MESSAGE = 3,

View File

@ -20,7 +20,7 @@ import { Logger } from "@utils/Logger";
const logger = new Logger("ServerListAPI");
export enum ServerListRenderPosition {
export const enum ServerListRenderPosition {
Above,
In,
}

View File

@ -146,3 +146,47 @@ export function OwnerCrownIcon(props: IconProps) {
</Icon>
);
}
/**
* Discord's screenshare icon, as seen in the connection panel
*/
export function ScreenshareIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-screenshare-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
d="M2 4.5C2 3.397 2.897 2.5 4 2.5H20C21.103 2.5 22 3.397 22 4.5V15.5C22 16.604 21.103 17.5 20 17.5H13V19.5H17V21.5H7V19.5H11V17.5H4C2.897 17.5 2 16.604 2 15.5V4.5ZM13.2 14.3375V11.6C9.864 11.6 7.668 12.6625 6 15C6.672 11.6625 8.532 8.3375 13.2 7.6625V5L18 9.6625L13.2 14.3375Z"
/>
</Icon>
);
}
export function ImageVisible(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-image-visible")}
viewBox="0 0 24 24"
>
<path fill="currentColor" d="M5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h14q.825 0 1.413.587Q21 4.175 21 5v14q0 .825-.587 1.413Q19.825 21 19 21Zm0-2h14V5H5v14Zm1-2h12l-3.75-5-3 4L9 13Zm-1 2V5v14Z" />
</Icon>
);
}
export function ImageInvisible(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-image-invisible")}
viewBox="0 0 24 24"
>
<path fill="currentColor" d="m21 18.15-2-2V5H7.85l-2-2H19q.825 0 1.413.587Q21 4.175 21 5Zm-1.2 4.45L18.2 21H5q-.825 0-1.413-.587Q3 19.825 3 19V5.8L1.4 4.2l1.4-1.4 18.4 18.4ZM6 17l3-4 2.25 3 .825-1.1L5 7.825V19h11.175l-2-2Zm7.425-6.425ZM10.6 13.4Z" />
</Icon>
);
}

View File

@ -138,11 +138,13 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
}
const result = wasEnabled ? stopPlugin(plugin) : startPlugin(plugin);
const action = wasEnabled ? "stop" : "start";
if (!result) {
logger.error(`Failed to ${action} plugin ${plugin.name}`);
showErrorToast(`Failed to ${action} plugin: ${plugin.name}`);
settings.enabled = false;
const msg = `Error while ${wasEnabled ? "stopping" : "starting"} plugin ${plugin.name}`;
logger.error(msg);
showErrorToast(msg);
return;
}
@ -171,7 +173,7 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
);
}
enum SearchStatus {
const enum SearchStatus {
ALL,
ENABLED,
DISABLED

View File

@ -57,7 +57,7 @@
}
.vc-text-selectable,
.vc-text-selectable :not(a, button, a *, button *, input, input *) {
.vc-text-selectable :where([class*="text" i], [class*="title" i]) {
/* make text selectable, silly discord makes the entirety of settings not selectable */
user-select: text;

2
src/globals.d.ts vendored
View File

@ -37,6 +37,8 @@ declare global {
export var IS_STANDALONE: boolean;
export var IS_DISCORD_DESKTOP: boolean;
export var IS_VENCORD_DESKTOP: boolean;
export var VERSION: string;
export var BUILD_TIMESTAMP: number;
export var VencordNative: typeof import("./VencordNative").default;
export var Vencord: typeof import("./Vencord");

View File

@ -82,6 +82,8 @@ export default definePlugin({
}
}],
customSections: [] as ((ID: Record<string, unknown>) => any)[],
makeSettingsCategories({ ID }: { ID: Record<string, unknown>; }) {
return [
{
@ -123,11 +125,13 @@ export default definePlugin({
label: "Patch Helper",
element: require("@components/VencordSettings/PatchHelperTab").default,
},
// TODO: make this use customSections
IS_VENCORD_DESKTOP && {
section: "VencordDesktop",
label: "Desktop Settings",
element: VencordDesktop.Components.Settings,
},
...this.customSections.map(func => func(ID)),
{
section: ID.DIVIDER
}

View File

@ -20,7 +20,7 @@ import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
enum Methods {
const enum Methods {
Random,
Consistent,
Timestamp,

View File

@ -104,6 +104,6 @@ export default definePlugin({
stop() {
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null }); // clear status
ws.close(); // close WebSocket
ws?.close(); // close WebSocket
}
});

View File

@ -0,0 +1,100 @@
/*
* 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 { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { ScreenshareIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import { openImageModal } from "@utils/discord";
import definePlugin from "@utils/types";
import { Menu } from "@webpack/common";
import { Channel, User } from "discord-types/general";
import { ApplicationStreamingStore, ApplicationStreamPreviewStore } from "./webpack/stores";
import { ApplicationStream, Stream } from "./webpack/types/stores";
export interface UserContextProps {
channel: Channel,
channelSelected: boolean,
className: string,
config: { context: string; };
context: string,
onHeightUpdate: Function,
position: string,
target: HTMLElement,
theme: string,
user: User;
}
export interface StreamContextProps {
appContext: string,
className: string,
config: { context: string; };
context: string,
exitFullscreen: Function,
onHeightUpdate: Function,
position: string,
target: HTMLElement,
stream: Stream,
theme: string,
}
export const handleViewPreview = async ({ guildId, channelId, ownerId }: ApplicationStream | Stream) => {
const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId);
if (!previewUrl) return;
openImageModal(previewUrl);
};
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => () => {
const stream = ApplicationStreamingStore.getAnyStreamForUser(userId);
if (!stream) return;
const streamPreviewItem = (
<Menu.MenuItem
label="View Stream Preview"
id="view-stream-preview"
icon={ScreenshareIcon}
action={() => stream && handleViewPreview(stream)}
disabled={!stream}
/>
);
children.push(<Menu.MenuSeparator />, streamPreviewItem);
};
export const streamContextPatch: NavContextMenuPatchCallback = (children, { stream }: StreamContextProps) => {
return addViewStreamContext(children, { userId: stream.ownerId });
};
export const userContextPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => {
return addViewStreamContext(children, { userId: user.id });
};
export default definePlugin({
name: "BiggerStreamPreview",
description: "This plugin allows you to enlarge stream previews",
authors: [Devs.phil],
start: () => {
addContextMenuPatch("user-context", userContextPatch);
addContextMenuPatch("stream-context", streamContextPatch);
},
stop: () => {
removeContextMenuPatch("user-context", userContextPatch);
removeContextMenuPatch("stream-context", streamContextPatch);
}
});

View File

@ -0,0 +1,25 @@
/*
* 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 { findStoreLazy } from "@webpack";
import * as t from "./types/stores";
export const ApplicationStreamPreviewStore: t.ApplicationStreamPreviewStore = findStoreLazy("ApplicationStreamPreviewStore");
export const ApplicationStreamingStore: t.ApplicationStreamingStore = findStoreLazy("ApplicationStreamingStore");

View File

@ -0,0 +1,77 @@
/*
* 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 { FluxStore } from "@webpack/types";
export interface ApplicationStreamPreviewStore extends FluxStore {
getIsPreviewLoading: (guildId: string | bigint | null, channelId: string | bigint, ownerId: string | bigint) => boolean;
getPreviewURL: (guildId: string | bigint | null, channelId: string | bigint, ownerId: string | bigint) => Promise<string | null>;
getPreviewURLForStreamKey: (streamKey: string) => ReturnType<ApplicationStreamPreviewStore["getPreviewURL"]>;
}
export interface ApplicationStream {
streamType: string;
guildId: string | null;
channelId: string;
ownerId: string;
}
export interface Stream extends ApplicationStream {
state: string;
}
export interface RTCStream {
region: string,
streamKey: string,
viewerIds: string[];
}
export interface StreamMetadata {
id: string | null,
pid: number | null,
sourceName: string | null;
}
export interface StreamingStoreState {
activeStreams: [string, Stream][];
rtcStreams: { [key: string]: RTCStream; };
streamerActiveStreamMetadatas: { [key: string]: StreamMetadata | null; };
streamsByUserAndGuild: { [key: string]: { [key: string]: ApplicationStream; }; };
}
/**
* example how a stream key could look like: `call(type of connection):1116549917987192913(channelId):305238513941667851(ownerId)`
*/
export interface ApplicationStreamingStore extends FluxStore {
getActiveStreamForApplicationStream: (stream: ApplicationStream) => Stream | null;
getActiveStreamForStreamKey: (streamKey: string) => Stream | null;
getActiveStreamForUser: (userId: string | bigint, guildId?: string | bigint | null) => Stream | null;
getAllActiveStreams: () => Stream[];
getAllApplicationStreams: () => ApplicationStream[];
getAllApplicationStreamsForChannel: (channelId: string | bigint) => ApplicationStream[];
getAllActiveStreamsForChannel: (channelId: string | bigint) => Stream[];
getAnyStreamForUser: (userId: string | bigint) => Stream | ApplicationStream | null;
getStreamForUser: (userId: string | bigint, guildId?: string | bigint | null) => Stream | null;
getCurrentUserActiveStream: () => Stream | null;
getLastActiveStream: () => Stream | null;
getState: () => StreamingStoreState;
getRTCStream: (streamKey: string) => RTCStream | null;
getStreamerActiveStreamMetadata: () => StreamMetadata;
getViewerIds: (stream: ApplicationStream) => string[];
isSelfStreamHidden: (channelId: string | bigint | null) => boolean;
}

View File

@ -74,7 +74,7 @@ interface Activity {
flags: number;
}
enum ActivityType {
const enum ActivityType {
PLAYING = 0,
LISTENING = 2,
WATCHING = 3,

View File

@ -1,61 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { ApplicationCommandOptionType } from "@api/Commands";
import { Settings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
export default definePlugin({
name: "Fart2",
authors: [Devs.Animal],
description: "Enable farting v2, a slash command that allows you to perform or request that someone perform a little toot.",
dependencies: ["CommandsAPI"],
commands: [{
name: "fart",
description: "A simple command in which you may either request that a user do a little toot for you, or conduct one yourself.",
options: [
{
type: ApplicationCommandOptionType.USER,
name: "user",
description: "A Discord™ user of which you would humbly request a toot from.",
required: false
}
],
execute(args) {
const fart = new Audio("https://raw.githubusercontent.com/ItzOnlyAnimal/AliuPlugins/main/fart.mp3");
fart.volume = Settings.plugins.Fart2.volume;
fart.play();
return {
content: (args[0]) ? `<@${args[0].value}> fart` : "fart"
};
},
}],
options: {
volume: {
description: "how loud you wanna fart (aka volume)",
type: OptionType.SLIDER,
markers: makeRange(0, 1, 0.1),
default: 0.5,
stickToMarkers: false,
}
}
});

View File

@ -18,6 +18,7 @@
import { get, set } from "@api/DataStore";
import { addButton, removeButton } from "@api/MessagePopover";
import { ImageInvisible, ImageVisible } from "@components/Icons";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { ChannelStore } from "@webpack/common";
@ -26,17 +27,6 @@ let style: HTMLStyleElement;
const KEY = "HideAttachments_HiddenIds";
const ImageVisible = () => (
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h14q.825 0 1.413.587Q21 4.175 21 5v14q0 .825-.587 1.413Q19.825 21 19 21Zm0-2h14V5H5v14Zm1-2h12l-3.75-5-3 4L9 13Zm-1 2V5v14Z" />
</svg>
);
const ImageInvisible = () => (
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="m21 18.15-2-2V5H7.85l-2-2H19q.825 0 1.413.587Q21 4.175 21 5Zm-1.2 4.45L18.2 21H5q-.825 0-1.413-.587Q3 19.825 3 19V5.8L1.4 4.2l1.4-1.4 18.4 18.4ZM6 17l3-4 2.25 3 .825-1.1L5 7.825V19h11.175l-2-2Zm7.425-6.425ZM10.6 13.4Z" />
</svg>
);
let hiddenMessages: Set<string> = new Set();
const getHiddenMessages = () => get(KEY).then(set => {
hiddenMessages = set ?? new Set<string>();

View File

@ -24,7 +24,7 @@ import definePlugin from "@utils/types";
import { findByPropsLazy, findStoreLazy } from "@webpack";
import { Tooltip } from "webpack/common";
enum ActivitiesTypes {
const enum ActivitiesTypes {
Game,
Embedded
}

View File

@ -63,12 +63,12 @@ interface TrackData {
}
// only relevant enum values
enum ActivityType {
const enum ActivityType {
PLAYING = 0,
LISTENING = 2,
}
enum ActivityFlag {
const enum ActivityFlag {
INSTANCE = 1 << 0,
}

View File

@ -67,7 +67,7 @@ export default definePlugin({
document.addEventListener("keydown", keydown);
document.addEventListener("keyup", keyup);
this.onClick = addClickListener((msg, channel, event) => {
this.onClick = addClickListener((msg: any, channel, event) => {
const isMe = msg.author.id === UserStore.getCurrentUser().id;
if (!isDeletePressed) {
if (event.detail < 2) return;
@ -90,7 +90,16 @@ export default definePlugin({
});
}
} else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(MANAGE_CHANNELS, channel))) {
MessageActions.deleteMessage(channel.id, msg.id);
if (msg.deleted) {
FluxDispatcher.dispatch({
type: "MESSAGE_DELETE",
channelId: channel.id,
id: msg.id,
mlDeleted: true
});
} else {
MessageActions.deleteMessage(channel.id, msg.id);
}
event.preventDefault();
}
});

View File

@ -1,4 +1,10 @@
.messagelogger-deleted :is(div, h1, h2, h3, p) {
/* Message content highlighting */
.messagelogger-deleted [class*="contents-"] > :is(div, h1, h2, h3, p) {
color: #f04747 !important;
}
/* Embed highlighting */
.messagelogger-deleted article :is(div, span, h1, h2, h3, p) {
color: #f04747 !important;
}

View File

@ -147,6 +147,11 @@ export default definePlugin({
type: OptionType.BOOLEAN,
description: "Whether to ignore messages by yourself",
default: false
},
ignoreUsers: {
type: OptionType.STRING,
description: "Comma-separated list of user IDs to ignore",
default: ""
}
},
@ -154,7 +159,7 @@ export default definePlugin({
try {
if (cache == null || (!isBulk && !cache.has(data.id))) return cache;
const { ignoreBots, ignoreSelf } = Settings.plugins.MessageLogger;
const { ignoreBots, ignoreSelf, ignoreUsers } = Settings.plugins.MessageLogger;
const myId = UserStore.getCurrentUser().id;
function mutate(id: string) {
@ -165,7 +170,8 @@ export default definePlugin({
const shouldIgnore = data.mlDeleted ||
(msg.flags & EPHEMERAL) === EPHEMERAL ||
ignoreBots && msg.author?.bot ||
ignoreSelf && msg.author?.id === myId;
ignoreSelf && msg.author?.id === myId ||
ignoreUsers.includes(msg.author?.id);
if (shouldIgnore) {
cache = cache.remove(id);

View File

@ -2,16 +2,29 @@
display: none;
}
.messagelogger-deleted :is(video, .emoji, [data-type="sticker"]),
.messagelogger-deleted .messagelogger-deleted-attachment,
.messagelogger-deleted div iframe {
.messagelogger-deleted
:is(
video,
.emoji,
[data-type="sticker"],
iframe,
.messagelogger-deleted-attachment,
[class|="inlineMediaEmbed"]
) {
filter: grayscale(1) !important;
transition: 150ms filter ease-in-out;
}
.messagelogger-deleted:hover :is(video, .emoji, [data-type="sticker"]),
.messagelogger-deleted .messagelogger-deleted-attachment:hover,
.messagelogger-deleted iframe:hover {
.messagelogger-deleted
:is(
video,
.emoji,
[data-type="sticker"],
iframe,
.messagelogger-deleted-attachment,
[class|="inlineMediaEmbed"]
):hover,
.messagelogger-deleted {
filter: grayscale(0) !important;
}

View File

@ -18,37 +18,8 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { ModalContent, ModalFooter, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { findByProps, findStoreLazy } from "@webpack";
import { Button, Text } from "@webpack/common";
const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore");
function NoDMNotificationsModal({ modalProps }: { modalProps: ModalProps; }) {
return (
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<ModalContent>
<div style={{ display: "flex", flexDirection: "column", justifyContent: "center", "alignItems": "center", textAlign: "center", height: "100%", padding: "8px 0", gap: "16px" }}>
<Text variant="text-lg/semibold">You seem to have been affected by a bug that caused DM notifications to be muted and break if you used the MuteNewGuild plugin.</Text>
<Text variant="text-lg/semibold">If you haven't received any notifications for private messages, this is why. This issue is now fixed, so they should work again. Please verify, and in case they are still broken, ask for help in the Vencord support channel!</Text>
<Text variant="text-lg/semibold">We're very sorry for any inconvenience caused by this issue :(</Text>
</div>
</ModalContent>
<ModalFooter>
<div style={{ display: "flex", justifyContent: "center", width: "100%" }}>
<Button
onClick={modalProps.onClose}
size={Button.Sizes.MEDIUM}
color={Button.Colors.BRAND}
>
Understood!
</Button>
</div>
</ModalFooter>
</ModalRoot>
);
}
import { findByProps } from "@webpack";
const settings = definePluginSettings({
guild: {
@ -92,15 +63,5 @@ export default definePlugin({
suppress_roles: settings.store.role
}
);
},
start() {
const [isMuted, isEveryoneSupressed, isRolesSupressed] = [UserGuildSettingsStore.isMuted(null), UserGuildSettingsStore.isSuppressEveryoneEnabled(null), UserGuildSettingsStore.isSuppressRolesEnabled(null)];
if (isMuted || isEveryoneSupressed || isRolesSupressed) {
findByProps("updateGuildNotificationSettings").updateGuildNotificationSettings(null, { muted: false, suppress_everyone: false, suppress_roles: false });
openModal(modalProps => <NoDMNotificationsModal modalProps={modalProps} />);
}
}
});

View File

@ -0,0 +1,104 @@
/*
* 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 { Devs } from "@utils/constants";
import { isNonNullish } from "@utils/guards";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Avatar, ChannelStore, Clickable, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common";
import { Channel, User } from "discord-types/general";
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
const AvatarUtils = findByPropsLazy("getChannelIconURL");
const UserUtils = findByPropsLazy("getGlobalName");
const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds");
const GuildLabelClasses = findByPropsLazy("guildNick", "guildAvatarWithoutIcon");
function getGroupDMName(channel: Channel) {
return channel.name ||
channel.recipients
.map(UserStore.getUser)
.filter(isNonNullish)
.map(c => RelationshipStore.getNickname(c.id) || UserUtils.getName(c))
.join(", ");
}
export default definePlugin({
name: "MutualGroupDMs",
description: "Shows mutual group dms in profiles",
authors: [Devs.amia],
patches: [
{
find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded
replacement: [
{
match: /(?<=\.MUTUAL_GUILDS\}\),)(?=(\i\.bot).{0,20}(\(0,\i\.jsx\)\(.{0,100}id:))/,
replace: '$1?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),'
},
{
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs($1,$2);"
}
]
}
],
renderMutualGDMs(user: User, onClose: () => void) {
const entries = ChannelStore.getSortedPrivateChannels().filter(c => c.isGroupDM() && c.recipients.includes(user.id)).map(c => (
<Clickable
className={ProfileListClasses.listRow}
onClick={() => {
onClose();
SelectedChannelActionCreators.selectPrivateChannel(c.id);
}}
>
<Avatar
src={AvatarUtils.getChannelIconURL({ id: c.id, icon: c.icon, size: 32 })}
size="SIZE_40"
className={ProfileListClasses.listAvatar}
>
</Avatar>
<div className={ProfileListClasses.listRowContent}>
<div className={ProfileListClasses.listName}>{getGroupDMName(c)}</div>
<div className={GuildLabelClasses.guildNick}>{c.recipients.length} Members</div>
</div>
</Clickable>
));
return (
<ScrollerThin
className={ProfileListClasses.listScroller}
fade={true}
onClose={onClose}
>
{entries.length > 0
? entries
: (
<div className={ProfileListClasses.empty}>
<div className={ProfileListClasses.emptyIconFriends}></div>
<div className={ProfileListClasses.emptyText}>No group dms in common</div>
</div>
)
}
</ScrollerThin>
);
}
});

View File

@ -0,0 +1,97 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
const MessageRequestStore = findByPropsLazy("getMessageRequestsCount");
const settings = definePluginSettings({
hideFriendRequestsCount: {
type: OptionType.BOOLEAN,
description: "Hide incoming friend requests count",
default: true,
restartNeeded: true
},
hideMessageRequestsCount: {
type: OptionType.BOOLEAN,
description: "Hide message requests count",
default: true,
restartNeeded: true
},
hidePremiumOffersCount: {
type: OptionType.BOOLEAN,
description: "Hide nitro offers count",
default: true,
restartNeeded: true
}
});
export default definePlugin({
name: "NoPendingCount",
description: "Removes the ping count of incoming friend requests, message requests, and nitro offers.",
authors: [Devs.amia],
settings: settings,
// Functions used to determine the top left count indicator can be found in the single module that calls getUnacknowledgedOffers(...)
// or by searching for "showProgressBadge:"
patches: [
{
find: ".getPendingCount=",
predicate: () => settings.store.hideFriendRequestsCount,
replacement: {
match: /(?<=\.getPendingCount=function\(\)\{)/,
replace: "return 0;"
}
},
{
find: ".getMessageRequestsCount=",
predicate: () => settings.store.hideMessageRequestsCount,
replacement: {
match: /(?<=\.getMessageRequestsCount=function\(\)\{)/,
replace: "return 0;"
}
},
// This prevents the Message Requests tab from always hiding due to the previous patch (and is compatible with spam requests)
// In short, only the red badge is hidden. Button visibility behavior isn't changed.
{
find: ".getSpamChannelsCount(),",
predicate: () => settings.store.hideMessageRequestsCount,
replacement: {
match: /(?<=getSpamChannelsCount\(\),\i=)\i\.getMessageRequestsCount\(\)/,
replace: "$self.getRealMessageRequestCount()"
}
},
{
find: "showProgressBadge:",
predicate: () => settings.store.hidePremiumOffersCount,
replacement: {
match: /\(function\(\){return \i\.\i\.getUnacknowledgedOffers\(\i\)\.length}\)/,
replace: "(function(){return 0})"
}
}
],
getRealMessageRequestCount() {
return MessageRequestStore.getMessageRequestChannelIds().size;
}
});

View File

@ -0,0 +1,53 @@
/*
* 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 { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "NoProfileThemes",
description: "Completely removes Nitro profile themes",
authors: [Devs.TheKodeToad],
patches: [
{
find: ".NITRO_BANNER,",
replacement: {
// = isPremiumAtLeast(user.premiumType, TIER_2)
match: /=(?=\i\.\i\.isPremiumAtLeast\(null==(\i))/,
// = user.banner && isPremiumAtLeast(user.premiumType, TIER_2)
replace: "=$1?.banner&&"
}
},
{
find: "().avatarPositionPremiumNoBanner,default:",
replacement: {
// premiumUserWithoutBanner: foo().avatarPositionPremiumNoBanner, default: foo().avatarPositionNormal
match: /\.avatarPositionPremiumNoBanner(?=,default:\i\(\)\.(\i))/,
// premiumUserWithoutBanner: foo().avatarPositionNormal...
replace: ".$1"
}
},
{
find: ".hasThemeColors=function(){",
replacement: {
match: /(?<=key:"canUsePremiumProfileCustomization",get:function\(\){return)/,
replace: " false;"
}
}
]
});

View File

@ -159,9 +159,12 @@ function getNextMessage(isUp: boolean, isReply: boolean) {
return i === - 1 ? undefined : messages[messages.length - i - 1];
}
function shouldMention() {
function shouldMention(message) {
const { enabled, userList, shouldPingListed } = Settings.plugins.NoReplyMention;
const shouldPing = !enabled || (shouldPingListed === userList.includes(message.author.id));
switch (settings.store.shouldMention) {
case MentionOptions.NO_REPLY_MENTION_PLUGIN: return !Settings.plugins.NoReplyMention.enabled;
case MentionOptions.NO_REPLY_MENTION_PLUGIN: return shouldPing;
case MentionOptions.DISABLED: return false;
default: return true;
}
@ -184,7 +187,7 @@ function nextReply(isUp: boolean) {
type: "CREATE_PENDING_REPLY",
channel,
message,
shouldMention: shouldMention(),
shouldMention: shouldMention(message),
showMentionToggle: channel.guild_id !== null && message.author.id !== meId,
_isQuickReply: true
});

View File

@ -20,7 +20,7 @@ import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
const SpoilerClasses = findByPropsLazy("spoilerText");
const SpoilerClasses = findByPropsLazy("spoilerContent");
const MessagesClasses = findByPropsLazy("messagesWrapper", "messages");
export default definePlugin({
@ -43,14 +43,14 @@ export default definePlugin({
if (!ctrlKey) { return; }
const { spoilerText, hidden } = SpoilerClasses;
const { spoilerContent, hidden } = SpoilerClasses;
const { messagesWrapper } = MessagesClasses;
const parent = shiftKey
? document.querySelector(`div.${messagesWrapper}`)
: (target as HTMLSpanElement).parentElement;
for (const spoiler of parent!.querySelectorAll(`span.${spoilerText}.${hidden}`)) {
for (const spoiler of parent!.querySelectorAll(`span.${spoilerContent}.${hidden}`)) {
(spoiler as HTMLSpanElement).click();
}
}

View File

@ -79,8 +79,8 @@ export default definePlugin({
addContextMenuPatch("guild-header-popout", guildPopoutPatch);
if (user.banInfo) {
const endDate = new Date(user.banInfo.banEndDate).getTime();
if (endDate > Date.now() && (s.user?.banInfo?.banEndDate ?? 0) < endDate) {
const endDate = new Date(user.banInfo.banEndDate);
if (endDate.getTime() > Date.now() && (s.user?.banInfo?.banEndDate ?? 0) < endDate.getTime()) {
Alerts.show({
title: "You have been banned from ReviewDB",
body: (

View File

@ -50,9 +50,9 @@ export function authorize(callback?: any) {
permissions={0n}
clientId="915703782174752809"
cancelCompletesFlow={false}
callback={async (u: string) => {
callback={async (response: any) => {
try {
const url = new URL(u);
const url = new URL(response.location);
url.searchParams.append("clientMod", "vencord");
const res = await fetch(url, {
headers: new Headers({ Accept: "application/json" })

View File

@ -24,7 +24,7 @@ import { useForceUpdater } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { GuildStore, PresenceStore, RelationshipStore } from "@webpack/common";
enum IndicatorType {
const enum IndicatorType {
SERVER = 1 << 0,
FRIEND = 1 << 1,
BOTH = SERVER | FRIEND,

View File

@ -46,18 +46,18 @@ export type ShikiSpec = {
}) => Promise<IThemedToken[][]>;
};
export enum StyleSheets {
export const enum StyleSheets {
Main = "MAIN",
DevIcons = "DEVICONS",
}
export enum HljsSetting {
export const enum HljsSetting {
Never = "NEVER",
Secondary = "SECONDARY",
Primary = "PRIMARY",
Always = "ALWAYS",
}
export enum DeviconSetting {
export const enum DeviconSetting {
Disabled = "DISABLED",
Greyscale = "GREYSCALE",
Color = "COLOR"

View File

@ -29,12 +29,12 @@ import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission
import { sortPermissionOverwrites } from "../../permissionsViewer/utils";
import { settings, VIEW_CHANNEL } from "..";
enum SortOrderTypes {
const enum SortOrderTypes {
LATEST_ACTIVITY = 0,
CREATION_DATE = 1
}
enum ForumLayoutTypes {
const enum ForumLayoutTypes {
DEFAULT = 0,
LIST = 1,
GRID = 2
@ -61,7 +61,7 @@ interface ExtendedChannel extends Channel {
availableTags?: Array<Tag>;
}
enum ChannelTypes {
const enum ChannelTypes {
GUILD_TEXT = 0,
GUILD_VOICE = 2,
GUILD_ANNOUNCEMENT = 5,
@ -69,12 +69,12 @@ enum ChannelTypes {
GUILD_FORUM = 15
}
enum VideoQualityModes {
const enum VideoQualityModes {
AUTO = 1,
FULL = 2
}
enum ChannelFlags {
const enum ChannelFlags {
PINNED = 1 << 1,
REQUIRE_TAG = 1 << 4
}

View File

@ -34,7 +34,7 @@ const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted
export const VIEW_CHANNEL = 1n << 10n;
const CONNECT = 1n << 20n;
enum ShowMode {
const enum ShowMode {
LockIcon,
HiddenIconWithMutedStyle
}
@ -107,8 +107,8 @@ export default definePlugin({
},
{
// Prevent Discord from trying to connect to hidden channels
match: /if\(!\i&&!\i(?=.{0,50}?selectVoiceChannel\((\i)\.id\))/,
replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})`
match: /(?=\|\|\i\.default\.selectVoiceChannel\((\i)\.id\))/,
replace: (_, channel) => `||$self.isHiddenChannel(${channel})`
},
{
// Make Discord show inside the channel if clicking on a hidden or locked channel

View File

@ -63,6 +63,7 @@ export default definePlugin({
settings,
renderUsername: ({ author, message, isRepliedMessage, withMentionPrefix }: UsernameProps) => {
if (message.interaction) return author?.nick;
try {
const { username } = message.author;
const { nick } = author;

View File

@ -27,7 +27,7 @@ import { Alerts, Forms, UserStore } from "@webpack/common";
import gitHash from "~git-hash";
import plugins from "~plugins";
import settings from "./settings";
import settings from "./_core/settings";
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
@ -55,7 +55,10 @@ export default definePlugin({
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
if (IS_VENCORD_DESKTOP) return `Vencord Desktop v${VencordDesktopNative.app.getVersion()}`;
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
return `Web (${navigator.userAgent})`;
// @ts-expect-error
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
return `${name} (${navigator.userAgent})`;
})();
const isApiPlugin = (plugin: string) => plugin.endsWith("API") || plugins[plugin].required;
@ -63,14 +66,18 @@ export default definePlugin({
const enabledPlugins = Object.keys(plugins).filter(p => Vencord.Plugins.isPluginEnabled(p) && !isApiPlugin(p));
const enabledApiPlugins = Object.keys(plugins).filter(p => Vencord.Plugins.isPluginEnabled(p) && isApiPlugin(p));
const info = {
Vencord: `v${VERSION}${gitHash}${settings.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
"Discord Branch": RELEASE_CHANNEL,
Client: client,
Platform: window.navigator.platform,
Outdated: isOutdated,
OpenAsar: "openasar" in window,
};
const debugInfo = `
**Vencord Debug Info**
>>> Discord Branch: ${RELEASE_CHANNEL}
Client: ${client}
Platform: ${window.navigator.platform}
Vencord: ${gitHash}${settings.additionalInfo}
Outdated: ${isOutdated}
OpenAsar: ${"openasar" in window}
>>> ${Object.entries(info).map(([k, v]) => `${k}: ${v}`).join("\n")}
Enabled Plugins (${enabledPlugins.length + enabledApiPlugins.length}):
${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", "))}

View File

@ -0,0 +1,67 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { ImageInvisible, ImageVisible } from "@components/Icons";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common";
const EMBED_SUPPRESSED = 1 << 2;
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => () => {
const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0;
if (!isEmbedSuppressed && !embeds.length) return;
const hasEmbedPerms = channel.isPrivate() || !!(PermissionStore.getChannelPermissions({ id: channel.id }) & PermissionsBits.EMBED_LINKS);
if (author.id === UserStore.getCurrentUser().id && !hasEmbedPerms) return;
const menuGroup = findGroupChildrenByChildId("delete", children);
const deleteIndex = menuGroup?.findIndex(i => i?.props?.id === "delete");
if (!deleteIndex || !menuGroup) return;
menuGroup.splice(deleteIndex - 1, 0, (
<Menu.MenuItem
id="unsuppress-embeds"
key="unsuppress-embeds"
label={isEmbedSuppressed ? "Unsuppress Embeds" : "Suppress Embeds"}
color={isEmbedSuppressed ? undefined : "danger"}
icon={isEmbedSuppressed ? ImageVisible : ImageInvisible}
action={() =>
RestAPI.patch({
url: `/channels/${channel.id}/messages/${messageId}`,
body: { flags: isEmbedSuppressed ? flags & ~EMBED_SUPPRESSED : flags | EMBED_SUPPRESSED }
})
}
/>
));
};
export default definePlugin({
name: "UnsuppressEmbeds",
authors: [Devs.rad, Devs.HypedDomi],
description: "Allows you to unsuppress embeds in messages",
start() {
addContextMenuPatch("message", messageContextMenuPatch);
},
stop() {
removeContextMenuPatch("message", messageContextMenuPatch);
},
});

View File

@ -71,7 +71,7 @@ function clean(str: string) {
function formatText(str: string, user: string, channel: string) {
return str
.replaceAll("{{USER}}", clean(user) || user ? "Someone" : "")
.replaceAll("{{USER}}", clean(user) || (user ? "Someone" : ""))
.replaceAll("{{CHANNEL}}", clean(channel) || "channel");
}

View File

@ -20,15 +20,12 @@ import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatc
import { definePluginSettings } from "@api/Settings";
import { ImageIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import { ModalRoot, ModalSize, openModal } from "@utils/modal";
import { LazyComponent } from "@utils/react";
import { openImageModal } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { find, findByCode, findByPropsLazy } from "@webpack";
import { findByPropsLazy } from "@webpack";
import { GuildMemberStore, Menu } from "@webpack/common";
import type { Channel, Guild, User } from "discord-types/general";
const ImageModal = LazyComponent(() => findByCode(".MEDIA_MODAL_CLOSE,"));
const MaskedLink = LazyComponent(() => find(m => m.type?.toString().includes("MASKED_LINK)")));
const BannerStore = findByPropsLazy("getGuildBannerURL");
interface UserContextProps {
@ -60,26 +57,29 @@ const settings = definePluginSettings({
value: "jpg",
}
]
},
imgSize: {
type: OptionType.SELECT,
description: "The image size to use",
options: ["128", "256", "512", "1024", "2048", "4096"].map(n => ({ label: n, value: n, default: n === "1024" }))
}
});
function openImage(url: string) {
const format = url.startsWith("/") ? "png" : settings.store.format;
const u = new URL(url, window.location.href);
u.searchParams.set("size", "512");
u.searchParams.set("size", settings.store.imgSize);
u.pathname = u.pathname.replace(/\.(png|jpe?g|webp)$/, `.${format}`);
url = u.toString();
openModal(modalProps => (
<ModalRoot size={ModalSize.DYNAMIC} {...modalProps}>
<ImageModal
shouldAnimate={true}
original={url}
src={url}
renderLinkComponent={MaskedLink}
/>
</ModalRoot>
));
u.searchParams.set("size", "4096");
const originalUrl = u.toString();
openImageModal(url, {
original: originalUrl,
height: 256
});
}
const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => () => {
@ -90,7 +90,7 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
<Menu.MenuItem
id="view-avatar"
label="View Avatar"
action={() => openImage(BannerStore.getUserAvatarURL(user, true, 512))}
action={() => openImage(BannerStore.getUserAvatarURL(user, true))}
icon={ImageIcon}
/>
{memberAvatar && (
@ -122,7 +122,6 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild: { id, icon
openImage(BannerStore.getGuildIconURL({
id,
icon,
size: 512,
canAnimate: true
}))
}

View File

@ -17,13 +17,14 @@
*/
import { addButton, removeButton } from "@api/MessagePopover";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import { copyWithToast } from "@utils/misc";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin from "@utils/types";
import definePlugin, { OptionType } from "@utils/types";
import { Button, ChannelStore, Forms, Parser, Text } from "@webpack/common";
import { Message } from "discord-types/general";
@ -41,22 +42,12 @@ function sortObject<T extends object>(obj: T): T {
function cleanMessage(msg: Message) {
const clone = sortObject(JSON.parse(JSON.stringify(msg)));
for (const key in clone.author) {
switch (key) {
case "id":
case "username":
case "usernameNormalized":
case "discriminator":
case "avatar":
case "bot":
case "system":
case "publicFlags":
break;
default:
// phone number, email, etc
delete clone.author[key];
}
}
for (const key of [
"email",
"phone",
"mfaEnabled",
"personalConnectionId"
]) delete clone.author[key];
// message logger added properties
const cloneAny = clone as any;
@ -116,26 +107,58 @@ function openViewRawModal(msg: Message) {
));
}
const settings = definePluginSettings({
clickMethod: {
description: "Change the button to view the raw content/data of any message.",
type: OptionType.SELECT,
options: [
{ label: "Left Click to view the raw content.", value: "Left", default: true },
{ label: "Right click to view the raw content.", value: "Right" }
]
}
});
export default definePlugin({
name: "ViewRaw",
description: "Copy and view the raw content/data of any message.",
authors: [Devs.KingFish, Devs.Ven],
authors: [Devs.KingFish, Devs.Ven, Devs.rad],
dependencies: ["MessagePopoverAPI"],
settings,
start() {
addButton("ViewRaw", msg => {
return {
label: "View Raw (Left Click) / Copy Raw (Right Click)",
icon: CopyIcon,
message: msg,
channel: ChannelStore.getChannel(msg.channel_id),
onClick: () => openViewRawModal(msg),
onContextMenu: e => {
const handleClick = () => {
if (settings.store.clickMethod === "Right") {
copyWithToast(msg.content);
} else {
openViewRawModal(msg);
}
};
const handleContextMenu = e => {
if (settings.store.clickMethod === "Left") {
e.preventDefault();
e.stopPropagation();
copyWithToast(msg.content);
} else {
e.preventDefault();
e.stopPropagation();
openViewRawModal(msg);
}
};
const label = settings.store.clickMethod === "Right"
? "Copy Raw (Left Click) / View Raw (Right Click)"
: "View Raw (Left Click) / Copy Raw (Right Click)";
return {
label,
icon: CopyIcon,
message: msg,
channel: ChannelStore.getChannel(msg.channel_id),
onClick: handleClick,
onContextMenu: handleContextMenu
};
});
},

View File

@ -77,14 +77,14 @@ export async function authorizeCloud() {
permissions={0n}
clientId={clientId}
cancelCompletesFlow={false}
callback={async (callbackUrl: string) => {
if (!callbackUrl) {
callback={async ({ location }: any) => {
if (!location) {
Settings.cloud.authenticated = false;
return;
}
try {
const res = await fetch(callbackUrl, {
const res = await fetch(location, {
headers: new Headers({ Accept: "application/json" })
});
const { secret } = await res.json();

View File

@ -315,6 +315,26 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "UwU",
id: 691413039156690994n,
},
amia: {
name: "amia",
id: 142007603549962240n
},
phil: {
name: "phil",
id: 305288513941667851n
},
ImLvna: {
name: "Luna <3",
id: 174200708818665472n
},
rad: {
name: "rad",
id: 113027285765885952n
},
HypedDomi: {
name: "HypedDomi",
id: 354191516979429376n
}
} satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly

View File

@ -37,7 +37,7 @@ export const importApngJs = makeLazy(async () => {
});
// https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
export enum ApngDisposeOp {
export const enum ApngDisposeOp {
/**
* no disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
*/
@ -53,7 +53,7 @@ export enum ApngDisposeOp {
}
// TODO: Might need to somehow implement this
export enum ApngBlendOp {
export const enum ApngBlendOp {
SOURCE,
OVER
}

View File

@ -18,9 +18,11 @@
import { MessageObject } from "@api/MessageEvents";
import { findByPropsLazy, findLazy } from "@webpack";
import { ChannelStore, ComponentDispatch, GuildStore, PrivateChannelsStore, SelectedChannelStore } from "@webpack/common";
import { ChannelStore, ComponentDispatch, GuildStore, MaskedLink, ModalImageClasses, PrivateChannelsStore, SelectedChannelStore } from "@webpack/common";
import { Guild, Message } from "discord-types/general";
import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal";
const PreloadedUserSettings = findLazy(m => m.ProtoClass?.typeName.endsWith("PreloadedUserSettings"));
const MessageActions = findByPropsLazy("editMessage", "sendMessage");
@ -77,3 +79,23 @@ export function sendMessage(
return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, extra);
}
export function openImageModal(url: string, props?: Partial<React.ComponentProps<ImageModal>>): string {
return openModal(modalProps => (
<ModalRoot
{...modalProps}
className={ModalImageClasses.modal}
size={ModalSize.DYNAMIC}>
<ImageModal
className={ModalImageClasses.image}
original={url}
placeholder={url}
src={url}
renderLinkComponent={props => <MaskedLink {...props} />}
shouldHideMediaOptions={false}
shouldAnimate
{...props}
/>
</ModalRoot>
));
}

View File

@ -16,19 +16,19 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { filters, mapMangledModuleLazy } from "@webpack";
import { filters, findByCode, mapMangledModuleLazy } from "@webpack";
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
import { LazyComponent } from "./react";
export enum ModalSize {
export const enum ModalSize {
SMALL = "small",
MEDIUM = "medium",
LARGE = "large",
DYNAMIC = "dynamic",
}
enum ModalTransitionState {
const enum ModalTransitionState {
ENTERING,
ENTERED,
EXITING,
@ -107,6 +107,25 @@ export const Modals = mapMangledModuleLazy(".closeWithCircleBackground", {
}>;
};
export type ImageModal = ComponentType<{
className?: string;
src: string;
placeholder: string;
original: string;
width?: number;
height?: number;
animated?: boolean;
responsive?: boolean;
renderLinkComponent(props: any): ReactNode;
maxWidth?: number;
maxHeight?: number;
shouldAnimate?: boolean;
onClose?(): void;
shouldHideMediaOptions?: boolean;
}>;
export const ImageModal = LazyComponent(() => findByCode(".renderLinkComponent", ".responsive") as ImageModal);
export const ModalRoot = LazyComponent(() => Modals.ModalRoot);
export const ModalHeader = LazyComponent(() => Modals.ModalHeader);
export const ModalContent = LazyComponent(() => Modals.ModalContent);

View File

@ -149,12 +149,6 @@ export async function putCloudSettings() {
VencordNative.settings.set(JSON.stringify(PlainSettings, null, 4));
cloudSettingsLogger.info("Settings uploaded to cloud successfully");
showNotification({
title: "Cloud Settings",
body: "Synchronized your settings to the cloud!",
color: "var(--green-360)",
noPersist: true
});
} catch (e: any) {
cloudSettingsLogger.error("Failed to sync up", e);
showNotification({

View File

@ -117,7 +117,7 @@ export interface PluginDef {
tags?: string[];
}
export enum OptionType {
export const enum OptionType {
STRING,
NUMBER,
BIGINT,

View File

@ -0,0 +1,24 @@
/*
* 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 { findByPropsLazy } from "@webpack";
import * as t from "./types/classes";
export const ModalImageClasses: t.ImageModalClasses = findByPropsLazy("image", "modal");
export const ButtonWrapperClasses: t.ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent");

View File

@ -17,7 +17,7 @@
*/
// eslint-disable-next-line path-alias/no-relative
import { filters, findByPropsLazy, waitFor } from "@webpack";
import { filters, waitFor } from "@webpack";
import { waitForComponent } from "./internal";
import * as t from "./types/components";
@ -44,16 +44,18 @@ export let Popout: t.Popout;
export let Dialog: t.Dialog;
export let TabBar: any;
export let Paginator: t.Paginator;
export let ScrollerThin: t.ScrollerThin;
export let Clickable: t.Clickable;
export let Avatar: t.Avatar;
// token lagger real
/** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */
export let useToken: t.useToken;
export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", m => m?.type?.toString().includes("MASKED_LINK)"));
export const 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 ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>;
waitFor("FormItem", m => {
({ useToken, Card, Button, FormSwitch: Switch, Tooltip, TextInput, TextArea, Text, Select, SearchableSelect, Slider, ButtonLooks, TabBar, Popout, Dialog, Paginator } = m);
({ useToken, Card, Button, FormSwitch: Switch, Tooltip, TextInput, TextArea, Text, Select, SearchableSelect, Slider, ButtonLooks, TabBar, Popout, Dialog, Paginator, ScrollerThin, Clickable, Avatar } = m);
Forms = m;
});

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./classes";
export * from "./components";
export * from "./menu";
export * from "./react";
@ -24,4 +25,3 @@ export * as ComponentTypes from "./types/components.d";
export * as MenuTypes from "./types/menu.d";
export * as UtilTypes from "./types/utils.d";
export * from "./utils";

40
src/webpack/common/types/classes.d.ts vendored Normal file
View File

@ -0,0 +1,40 @@
/*
* 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/>.
*/
export interface ImageModalClasses {
image: string,
modal: string,
responsiveWidthMobile: string;
}
export interface ButtonWrapperClasses {
hoverScale: string;
buttonWrapper: string;
button: string;
iconMask: string;
buttonContent: string;
icon: string;
pulseIcon: string;
pulseButton: string;
notificationDot: string;
sparkleContainer: string;
sparkleStar: string;
sparklePlus: string;
sparkle: string;
active: string;
}

View File

@ -397,3 +397,53 @@ export type Paginator = ComponentType<{
onPageChange?(page: number): void;
hideMaxPage?: boolean;
}>;
export type MaskedLink = ComponentType<{
onClick(): void;
trusted: boolean;
title: string,
href: string;
}>;
export type ScrollerThin = ComponentType<PropsWithChildren<{
className?: string;
style?: CSSProperties;
dir?: "ltr";
orientation?: "horizontal" | "vertical";
paddingFix?: boolean;
fade?: boolean;
onClose?(): void;
onScroll?(): void;
}>>;
export type Clickable = ComponentType<PropsWithChildren<{
className?: string;
href?: string;
ignoreKeyPress?: boolean;
onClick?(): void;
onKeyPress?(): void;
}>>;
export type Avatar = ComponentType<PropsWithChildren<{
className?: string;
src?: string;
size?: "SIZE_16" | "SIZE_20" | "SIZE_24" | "SIZE_32" | "SIZE_40" | "SIZE_48" | "SIZE_56" | "SIZE_80" | "SIZE_120";
statusColor?: string;
statusTooltip?: string;
statusBackdropColor?: string;
isMobile?: boolean;
isTyping?: boolean;
isSpeaking?: boolean;
typingIndicatorRef?: unknown;
"aria-hidden"?: boolean;
"aria-label"?: string;
}>>;

View File

@ -44,6 +44,7 @@ export interface Menu {
onChildrenScroll?: Function;
childRowHeight?: number;
listClassName?: string;
disabled?: boolean;
}>;
MenuCheckboxItem: RC<{
id: string;