Compare commits

..

No commits in common. "c320c0ff206d6fd2bb41fce332b6e7e48241b3d1" and "a8ed09f6392a45db135fdf338300cd9943c65bb0" have entirely different histories.

39 changed files with 1281 additions and 862 deletions

View file

@ -5,9 +5,15 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) # READ THIS BEFORE OPENING AN ISSUE
GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
DO NOT USE THIS FORM, unless
- you are a vencord contributor
- you were given explicit permission to use this form by a moderator in our support server
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
- type: textarea - type: textarea
id: content id: content

View file

@ -7,9 +7,24 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) # READ THIS BEFORE OPENING AN ISSUE
GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
DO NOT USE THIS FORM, unless
- you are a vencord contributor
- you were given explicit permission to use this form by a moderator in our support server
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
- type: input
id: discord
attributes:
label: Discord Account
description: Who on Discord is making this request? Not required but encouraged for easier follow-up
placeholder: username#0000
validations:
required: false
- type: textarea - type: textarea
id: bug-description id: bug-description
@ -62,5 +77,5 @@ body:
options: options:
- label: I am using Discord Stable or tried on Stable and this bug happens there as well - label: I am using Discord Stable or tried on Stable and this bug happens there as well
required: true required: true
- label: I am a Vencord Developer - label: I have read the requirements for opening an issue above
required: true required: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View file

@ -24,7 +24,7 @@
"dev": "pnpm watch", "dev": "pnpm watch",
"watchWeb": "pnpm buildWeb --watch", "watchWeb": "pnpm buildWeb --watch",
"generatePluginJson": "tsx scripts/generatePluginList.ts", "generatePluginJson": "tsx scripts/generatePluginList.ts",
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types --allowJs false", "generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
"inject": "node scripts/runInstaller.mjs", "inject": "node scripts/runInstaller.mjs",
"uninject": "node scripts/runInstaller.mjs", "uninject": "node scripts/runInstaller.mjs",
"lint": "eslint", "lint": "eslint",
@ -45,31 +45,31 @@
"virtual-merge": "^1.0.1" "virtual-merge": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@stylistic/eslint-plugin": "^4.0.0", "@stylistic/eslint-plugin": "^2.12.1",
"@types/chrome": "^0.0.304", "@types/chrome": "^0.0.287",
"@types/diff": "^7.0.1", "@types/diff": "^6.0.0",
"@types/lodash": "^4.17.14", "@types/lodash": "^4.17.14",
"@types/node": "^22.10.5", "@types/node": "^22.10.5",
"@types/react": "^19.0.10", "@types/react": "^19.0.2",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.0.2",
"@types/yazl": "^2.4.5", "@types/yazl": "^2.4.5",
"diff": "^7.0.0", "diff": "^7.0.0",
"discord-types": "^1.3.26", "discord-types": "^1.3.26",
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"eslint": "^9.20.1", "eslint": "^9.17.0",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "2.1.0", "eslint-plugin-path-alias": "2.1.0",
"eslint-plugin-react": "^7.37.3", "eslint-plugin-react": "^7.37.3",
"eslint-plugin-simple-header": "^1.2.1", "eslint-plugin-simple-header": "^1.2.1",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^4.1.4",
"highlight.js": "11.11.1", "highlight.js": "11.7.0",
"html-minifier-terser": "^7.2.0", "html-minifier-terser": "^7.2.0",
"moment": "^2.22.2", "moment": "^2.22.2",
"puppeteer-core": "^24.2.1", "puppeteer-core": "^23.11.1",
"standalone-electron-types": "^34.2.0", "standalone-electron-types": "^1.0.0",
"stylelint": "^16.12.0", "stylelint": "^16.12.0",
"stylelint-config-standard": "^37.0.0", "stylelint-config-standard": "^36.0.1",
"ts-patch": "^3.3.0", "ts-patch": "^3.3.0",
"ts-pattern": "^5.6.0", "ts-pattern": "^5.6.0",
"tsx": "^4.19.2", "tsx": "^4.19.2",
@ -79,10 +79,10 @@
"typescript-transform-paths": "^3.5.3", "typescript-transform-paths": "^3.5.3",
"zip-local": "^0.3.5" "zip-local": "^0.3.5"
}, },
"packageManager": "pnpm@10.4.1", "packageManager": "pnpm@9.1.0",
"pnpm": { "pnpm": {
"patchedDependencies": { "patchedDependencies": {
"eslint@9.20.1": "patches/eslint@9.20.1.patch", "eslint@9.17.0": "patches/eslint@9.17.0.patch",
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch" "eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
}, },
"peerDependencyRules": { "peerDependencyRules": {
@ -95,14 +95,18 @@
"source-map-resolve": "*", "source-map-resolve": "*",
"resolve-url": "*", "resolve-url": "*",
"source-map-url": "*", "source-map-url": "*",
"urix": "*", "urix": "*"
"q": "*" }
}, },
"onlyBuiltDependencies": [ "webExt": {
"esbuild" "artifactsDir": "./dist",
] "build": {
"overwriteDest": true
},
"sourceDir": "./dist/firefox-unpacked"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18",
"pnpm": ">=9"
} }
} }

View file

@ -1,7 +1,7 @@
{ {
"name": "@vencord/types", "name": "@vencord/types",
"private": false, "private": false,
"version": "1.11.5", "version": "0.1.3",
"description": "", "description": "",
"types": "index.d.ts", "types": "index.d.ts",
"scripts": { "scripts": {
@ -13,16 +13,16 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"fs-extra": "^11.3.0", "fs-extra": "^11.2.0",
"tsx": "^4.19.2" "tsx": "^3.12.6"
}, },
"dependencies": { "dependencies": {
"@types/lodash": "4.17.15", "@types/lodash": "^4.14.191",
"@types/node": "^22.13.4", "@types/node": "^18.11.18",
"@types/react": "18.3.1", "@types/react": "^18.2.0",
"@types/react-dom": "18.3.1", "@types/react-dom": "^18.0.10",
"discord-types": "^1.3.26", "discord-types": "^1.3.26",
"standalone-electron-types": "^34.2.0", "standalone-electron-types": "^1.0.0",
"type-fest": "^4.35.0" "type-fest": "^3.5.3"
} }
} }

1598
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -4,11 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import type { Settings } from "@api/Settings";
import { PluginIpcMappings } from "@main/ipcPlugins"; import { PluginIpcMappings } from "@main/ipcPlugins";
import type { UserThemeHeader } from "@main/themes"; import type { UserThemeHeader } from "@main/themes";
import { IpcEvents } from "@shared/IpcEvents"; import { IpcEvents } from "@shared/IpcEvents";
import { IpcRes } from "@utils/types"; import { IpcRes } from "@utils/types";
import type { Settings } from "api/Settings";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
function invoke<T = any>(event: IpcEvents, ...args: any[]) { function invoke<T = any>(event: IpcEvents, ...args: any[]) {

View file

@ -68,7 +68,7 @@ export async function loadLazyChunks() {
const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
.then(r => r.text()) .then(r => r.text())
.then(t => /importScripts\(|self\.postMessage/.test(t)); .then(t => t.includes("importScripts("));
if (isWorkerAsset) { if (isWorkerAsset) {
invalidChunks.add(id); invalidChunks.add(id);
@ -174,7 +174,7 @@ export async function loadLazyChunks() {
await Promise.all(chunksLeft.map(async id => { await Promise.all(chunksLeft.map(async id => {
const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
.then(r => r.text()) .then(r => r.text())
.then(t => /importScripts\(|self\.postMessage/.test(t)); .then(t => t.includes("importScripts("));
// Loads the chunk. Currently this only happens with the language packs which are loaded differently // Loads the chunk. Currently this only happens with the language packs which are loaded differently
if (!isWorkerAsset) { if (!isWorkerAsset) {

View file

@ -16,8 +16,8 @@ export default definePlugin({
{ {
find: "SCALE_DOWN:", find: "SCALE_DOWN:",
replacement: { replacement: {
match: /(?<="IMAGE"===\i\?)\i(?=\?)/, match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/,
replace: "true" replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))`
} }
} }
] ]

View file

@ -85,7 +85,7 @@ export default definePlugin({
replace: "$&onRequestClose:$self.onPopoutClose," replace: "$&onRequestClose:$self.onPopoutClose,"
}, },
{ {
match: /(?<=#{intl::SET_STATUS}\),)/, match: /(?<=\.avatarWrapper,)/,
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu," replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
} }
] ]

View file

@ -26,18 +26,10 @@ export default definePlugin({
patches: [ patches: [
{ {
find: '"ChannelAttachButton"', find: '"ChannelAttachButton"',
replacement: [ replacement: {
{
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/, match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/,
replace: "$&onClick:$1,onContextMenu:$2.onClick,", replace: "$&onClick:$1,onContextMenu:$2.onClick,",
noWarn: true
}, },
{
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/,
replace: "$&,onClick:$1,onContextMenu:$2.onClick,",
},
]
}, },
], ],
}); });

View file

@ -50,24 +50,12 @@ export default definePlugin({
find: ".decorationGridItem,", find: ".decorationGridItem,",
replacement: [ replacement: [
{ {
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert match: /(?<==)\i=>{let{children.{20,100}decorationGridItem/,
match: /(?<==)\i=>{let{children.{20,200}decorationGridItem/, replace: "$self.DecorationGridItem=$&"
replace: "$self.DecorationGridItem=$&",
noWarn: true
}, },
{ {
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
match: /(?<==)\i=>{let{user:\i,avatarDecoration/, match: /(?<==)\i=>{let{user:\i,avatarDecoration/,
replace: "$self.DecorationGridDecoration=$&", replace: "$self.DecorationGridDecoration=$&"
noWarn: true
},
{
match: /(?<==)\i=>{var{children.{20,200}decorationGridItem/,
replace: "$self.DecorationGridItem=$&",
},
{
match: /(?<==)\i=>{var{user:\i,avatarDecoration/,
replace: "$self.DecorationGridDecoration=$&",
}, },
// Remove NEW label from decor avatar decorations // Remove NEW label from decor avatar decorations
{ {

View file

@ -5,7 +5,7 @@
*/ */
import { CopyIcon, DeleteIcon } from "@components/Icons"; import { CopyIcon, DeleteIcon } from "@components/Icons";
import { Alerts, Clipboard, ContextMenuApi, Menu, UserStore } from "@webpack/common"; import { Alerts, Clipboard, ContextMenuApi, Menu, UserStore } from "webpack/common";
import { Decoration } from "../../lib/api"; import { Decoration } from "../../lib/api";
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore"; import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";

View file

@ -9,7 +9,7 @@ import { app } from "electron";
app.on("browser-window-created", (_, win) => { app.on("browser-window-created", (_, win) => {
win.webContents.on("frame-created", (_, { frame }) => { win.webContents.on("frame-created", (_, { frame }) => {
frame?.once("dom-ready", () => { frame.once("dom-ready", () => {
if (frame.url.startsWith("https://open.spotify.com/embed/")) { if (frame.url.startsWith("https://open.spotify.com/embed/")) {
const settings = RendererSettings.store.plugins?.FixSpotifyEmbeds; const settings = RendererSettings.store.plugins?.FixSpotifyEmbeds;
if (!settings?.enabled) return; if (!settings?.enabled) return;

View file

@ -9,7 +9,7 @@ import { app } from "electron";
app.on("browser-window-created", (_, win) => { app.on("browser-window-created", (_, win) => {
win.webContents.on("frame-created", (_, { frame }) => { win.webContents.on("frame-created", (_, { frame }) => {
frame?.once("dom-ready", () => { frame.once("dom-ready", () => {
if (frame.url.startsWith("https://www.youtube.com/")) { if (frame.url.startsWith("https://www.youtube.com/")) {
const settings = RendererSettings.store.plugins?.FixYoutubeEmbeds; const settings = RendererSettings.store.plugins?.FixYoutubeEmbeds;
if (!settings?.enabled) return; if (!settings?.enabled) return;

View file

@ -12,7 +12,7 @@ import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findStoreLazy } from "@webpack"; import { findStoreLazy } from "@webpack";
import { Button, Forms, showToast, TextInput, Toasts, Tooltip, useEffect, useState } from "@webpack/common"; import { Button, Forms, showToast, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common";
const enum ActivitiesTypes { const enum ActivitiesTypes {
Game, Game,

View file

@ -34,7 +34,7 @@ export default definePlugin({
{ {
find: "#{intl::FRIENDS_ALL_HEADER}", find: "#{intl::FRIENDS_ALL_HEADER}",
replacement: { replacement: {
match: /toString\(\)\}\);case (\i\.\i)\.PENDING/, match: /toString\(\)\}\);case (\i\.\i)\.BLOCKED/,
replace: 'toString()});case $1.IMPLICIT:return "Implicit — "+arguments[1];case $1.BLOCKED' replace: 'toString()});case $1.IMPLICIT:return "Implicit — "+arguments[1];case $1.BLOCKED'
}, },
}, },
@ -50,7 +50,7 @@ export default definePlugin({
{ {
find: "#{intl::FRIENDS_SECTION_ONLINE}", find: "#{intl::FRIENDS_SECTION_ONLINE}",
replacement: { replacement: {
match: /,{id:(\i\.\i)\.PENDING,show:.+?className:(\i\.item)/, match: /,{id:(\i\.\i)\.BLOCKED,show:.+?className:(\i\.item)/,
replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}` replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}`
} }
}, },
@ -58,7 +58,7 @@ export default definePlugin({
{ {
find: '"FriendsStore"', find: '"FriendsStore"',
replacement: { replacement: {
match: /(?<=case (\i\.\i)\.SUGGESTIONS:return \d+===(\i)\.type)/, match: /(?<=case (\i\.\i)\.BLOCKED:return (\i)\.type===\i\.\i\.BLOCKED)/,
replace: ";case $1.IMPLICIT:return $2.type===5" replace: ";case $1.IMPLICIT:return $2.type===5"
}, },
}, },

View file

@ -65,18 +65,10 @@ export default definePlugin({
patches: [ patches: [
{ {
find: "{isSidebarVisible:", find: "{isSidebarVisible:",
replacement: [ replacement: {
{
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
replace: ":[$1?.startsWith('members')?$self.render():null,$2", replace: ":[$1?.startsWith('members')?$self.render():null,$2"
noWarn: true
}, },
{
match: /(?<=var\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
replace: ":[$1?.startsWith('members')?$self.render():null,$2",
},
],
predicate: () => settings.store.memberList predicate: () => settings.store.memberList
}, },
{ {

View file

@ -84,14 +84,8 @@ export default definePlugin({
find: ".USER_MENTION)", find: ".USER_MENTION)",
replacement: [ replacement: [
{ {
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/, match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/,
replace: "$&,color:$self.getColorInt($1?.id,$2?.id)", replace: "$&,color:$self.getColorInt($1?.id,$2?.id)"
noWarn: true
},
{
match: /(?<=onContextMenu:\i,color:)\i(?=\},\i\),\{children)(?<=user:(\i),channel:(\i).{0,500}?)/,
replace: "$self.getColorInt($1?.id,$2?.id)",
} }
], ],
predicate: () => settings.store.chatMentions predicate: () => settings.store.chatMentions

View file

@ -96,6 +96,6 @@
.vc-shiki-root .vc-shiki-table-cell:last-child { .vc-shiki-root .vc-shiki-table-cell:last-child {
padding-left: 8px; padding-left: 8px;
overflow-wrap: break-word; word-break: break-word;
width: 100%; width: 100%;
} }

View file

@ -173,7 +173,7 @@ export default definePlugin({
replacement: [ replacement: [
// Make the channel appear as muted if it's hidden // Make the channel appear as muted if it's hidden
{ {
match: /\.subtitle,.+?;(?=return\(0,\i\.jsxs?\))(?<={channel:(\i),name:\i,muted:(\i).+?;)/, match: /{channel:(\i),name:\i,muted:(\i).+?;/,
replace: (m, channel, muted) => `${m}${muted}=$self.isHiddenChannel(${channel})?true:${muted};` replace: (m, channel, muted) => `${m}${muted}=$self.isHiddenChannel(${channel})?true:${muted};`
}, },
// Add the hidden eye icon if the channel is hidden // Add the hidden eye icon if the channel is hidden
@ -204,7 +204,7 @@ export default definePlugin({
{ {
// Hide unreads // Hide unreads
predicate: () => settings.store.hideUnreads === true, predicate: () => settings.store.hideUnreads === true,
match: /\.subtitle,.+?;(?=return\(0,\i\.jsxs?\))(?<={channel:(\i),name:\i,.+?unread:(\i).+?)/, match: /{channel:(\i),name:\i,.+?unread:(\i).+?;/,
replace: (m, channel, unread) => `${m}${unread}=$self.isHiddenChannel(${channel})?false:${unread};` replace: (m, channel, unread) => `${m}${unread}=$self.isHiddenChannel(${channel})?false:${unread};`
} }
] ]
@ -485,7 +485,7 @@ export default definePlugin({
} }
}, },
{ {
find: '"NowPlayingViewStore"', find: '="NowPlayingViewStore",',
replacement: { replacement: {
// Make active now voice states on hidden channels // Make active now voice states on hidden channels
match: /(getVoiceStateForUser.{0,150}?)&&\i\.\i\.canWithPartialContext.{0,20}VIEW_CHANNEL.+?}\)(?=\?)/, match: /(getVoiceStateForUser.{0,150}?)&&\i\.\i\.canWithPartialContext.{0,20}VIEW_CHANNEL.+?}\)(?=\?)/,

View file

@ -77,7 +77,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
class SpotifyStore extends Store { class SpotifyStore extends Store {
public mPosition = 0; public mPosition = 0;
public _start = 0; private start = 0;
public track: Track | null = null; public track: Track | null = null;
public device: Device | null = null; public device: Device | null = null;
@ -100,26 +100,26 @@ export const SpotifyStore = proxyLazyWebpack(() => {
public get position(): number { public get position(): number {
let pos = this.mPosition; let pos = this.mPosition;
if (this.isPlaying) { if (this.isPlaying) {
pos += Date.now() - this._start; pos += Date.now() - this.start;
} }
return pos; return pos;
} }
public set position(p: number) { public set position(p: number) {
this.mPosition = p; this.mPosition = p;
this._start = Date.now(); this.start = Date.now();
} }
prev() { prev() {
this._req("post", "/previous"); this.req("post", "/previous");
} }
next() { next() {
this._req("post", "/next"); this.req("post", "/next");
} }
setVolume(percent: number) { setVolume(percent: number) {
this._req("put", "/volume", { this.req("put", "/volume", {
query: { query: {
volume_percent: Math.round(percent) volume_percent: Math.round(percent)
} }
@ -131,17 +131,17 @@ export const SpotifyStore = proxyLazyWebpack(() => {
} }
setPlaying(playing: boolean) { setPlaying(playing: boolean) {
this._req("put", playing ? "/play" : "/pause"); this.req("put", playing ? "/play" : "/pause");
} }
setRepeat(state: Repeat) { setRepeat(state: Repeat) {
this._req("put", "/repeat", { this.req("put", "/repeat", {
query: { state } query: { state }
}); });
} }
setShuffle(state: boolean) { setShuffle(state: boolean) {
this._req("put", "/shuffle", { this.req("put", "/shuffle", {
query: { state } query: { state }
}).then(() => { }).then(() => {
this.shuffle = state; this.shuffle = state;
@ -154,7 +154,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
this.isSettingPosition = true; this.isSettingPosition = true;
return this._req("put", "/seek", { return this.req("put", "/seek", {
query: { query: {
position_ms: Math.round(ms) position_ms: Math.round(ms)
} }
@ -164,7 +164,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
}); });
} }
_req(method: "post" | "get" | "put", route: string, data: any = {}) { private req(method: "post" | "get" | "put", route: string, data: any = {}) {
if (this.device?.is_active) if (this.device?.is_active)
(data.query ??= {}).device_id = this.device.id; (data.query ??= {}).device_id = this.device.id;

View file

@ -27,22 +27,12 @@ export default definePlugin({
authors: [Devs.Megu], authors: [Devs.Megu],
patches: [{ patches: [{
find: "#{intl::ACTIVITY_SETTINGS}", find: "#{intl::ACTIVITY_SETTINGS}",
replacement: [ replacement: {
{
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/, match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/,
replace: (_, commaOrSemi, settings, elements) => "" + replace: (_, commaOrSemi, settings, elements) => "" +
`${commaOrSemi}${settings}?.[0]==="CHANGELOG"` + `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
`&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`, `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`
noWarn: true }
},
{
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+\)\)\}\))(?=\)\})/,
replace: (_, commaOrSemi, settings, elements) => "" +
`${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
`&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`,
},
]
}], }],
StartupTimingPage StartupTimingPage
}); });

View file

@ -41,20 +41,11 @@ export default definePlugin({
}, },
{ {
find: '="SYSTEM_TAG"', find: '="SYSTEM_TAG"',
replacement: [ replacement: {
{
// Add next to username (compact mode) // Add next to username (compact mode)
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g, match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g,
replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),", replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),"
noWarn: true }
},
{
// Add next to username (compact mode)
match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\)\),(?=\i)/g,
replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),",
},
]
} }
], ],

View file

@ -128,7 +128,7 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) {
); );
} }
export interface VoiceChannelIndicatorProps { interface VoiceChannelIndicatorProps {
userId: string; userId: string;
isActionButton?: boolean; isActionButton?: boolean;
shouldHighlight?: boolean; shouldHighlight?: boolean;

View file

@ -193,18 +193,10 @@ export default definePlugin({
// Avatar component used in User DMs "User Profile" popup in the right and Profiles Modal pfp // Avatar component used in User DMs "User Profile" popup in the right and Profiles Modal pfp
{ {
find: ".overlay:void 0,status:", find: ".overlay:void 0,status:",
replacement: [ replacement: {
{
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/, match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/,
replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},", replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},"
noWarn: true
}, },
{
match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",.{0,100}className:\i,/,
replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},",
}
],
all: true all: true
}, },
// Banners // Banners

View file

@ -10,7 +10,7 @@ import adguard from "file://adguard.js?minify";
app.on("browser-window-created", (_, win) => { app.on("browser-window-created", (_, win) => {
win.webContents.on("frame-created", (_, { frame }) => { win.webContents.on("frame-created", (_, { frame }) => {
frame?.once("dom-ready", () => { frame.once("dom-ready", () => {
if (!RendererSettings.store.plugins?.YoutubeAdblock?.enabled) return; if (!RendererSettings.store.plugins?.YoutubeAdblock?.enabled) return;
if (frame.url.includes("youtube.com/embed/") || (frame.url.includes("discordsays") && frame.url.includes("youtube.com"))) { if (frame.url.includes("youtube.com/embed/") || (frame.url.includes("discordsays") && frame.url.includes("youtube.com"))) {

View file

@ -17,6 +17,7 @@
@media(width <= 485px) { @media(width <= 485px) {
.vc-image-modal { .vc-image-modal {
display: relative;
overflow: visible; overflow: visible;
overflow: initial; overflow: initial;
} }

View file

@ -20,9 +20,9 @@ export function makeLazy<T>(factory: () => T, attempts = 5): () => T {
let tries = 0; let tries = 0;
let cache: T; let cache: T;
return () => { return () => {
if (cache === undefined && attempts > tries++) { if (!cache && attempts > tries++) {
cache = factory(); cache = factory();
if (cache === undefined && attempts === tries) if (!cache && attempts === tries)
console.error("Lazy factory failed:", factory); console.error("Lazy factory failed:", factory);
} }
return cache; return cache;

View file

@ -41,12 +41,7 @@ export interface PatchReplacement {
match: string | RegExp; match: string | RegExp;
/** The replacement string or function which returns the string for the patch replacement */ /** The replacement string or function which returns the string for the patch replacement */
replace: string | ReplaceFn; replace: string | ReplaceFn;
/** Do not warn if this replacement did no changes */ /** A function which returns whether this patch replacement should be applied */
noWarn?: boolean;
/**
* A function which returns whether this patch replacement should be applied.
* This is ran before patches are registered, so if this returns false, the patch will never be registered.
*/
predicate?(): boolean; predicate?(): boolean;
/** The minimum build number for this patch to be applied */ /** The minimum build number for this patch to be applied */
fromBuild?: number; fromBuild?: number;
@ -66,10 +61,7 @@ export interface Patch {
noWarn?: boolean; noWarn?: boolean;
/** Only apply this set of replacements if all of them succeed. Use this if your replacements depend on each other */ /** Only apply this set of replacements if all of them succeed. Use this if your replacements depend on each other */
group?: boolean; group?: boolean;
/** /** A function which returns whether this patch should be applied */
* A function which returns whether this patch replacement should be applied.
* This is ran before patches are registered, so if this returns false, the patch will never be registered.
*/
predicate?(): boolean; predicate?(): boolean;
/** The minimum build number for this patch to be applied */ /** The minimum build number for this patch to be applied */
fromBuild?: number; fromBuild?: number;

View file

@ -41,7 +41,7 @@ export const Switch = waitForComponent<t.Switch>("Switch", filters.componentByCo
const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", { const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", {
Tooltip: filters.componentByCode("this.renderTooltip()]"), Tooltip: filters.componentByCode("this.renderTooltip()]"),
TooltipContainer: filters.componentByCode('="div"') TooltipContainer: filters.componentByCode('="div",')
}) as { }) as {
Tooltip: t.Tooltip, Tooltip: t.Tooltip,
TooltipContainer: t.TooltipContainer; TooltipContainer: t.TooltipContainer;

View file

@ -29,7 +29,7 @@ export type GenericStore = t.FluxStore & Record<string, any>;
export const DraftType = findByPropsLazy("ChannelMessage", "SlashCommand"); export const DraftType = findByPropsLazy("ChannelMessage", "SlashCommand");
export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & GenericStore & { export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & {
getMessages(chanId: string): any; getMessages(chanId: string): any;
}; };

View file

@ -496,7 +496,7 @@ export type Avatar = ComponentType<PropsWithChildren<{
}>>; }>>;
type FocusLock = ComponentType<PropsWithChildren<{ type FocusLock = ComponentType<PropsWithChildren<{
containerRef: Ref<HTMLElement>; containerRef: RefObject<HTMLElement>;
}>>; }>>;
export type Icon = ComponentType<JSX.IntrinsicElements["svg"] & { export type Icon = ComponentType<JSX.IntrinsicElements["svg"] & {

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { DraftType } from "@webpack/common";
import { Channel, Guild, Role } from "discord-types/general"; import { Channel, Guild, Role } from "discord-types/general";
import { FluxDispatcher, FluxEvents } from "./utils"; import { FluxDispatcher, FluxEvents } from "./utils";
@ -228,7 +229,7 @@ export class ThemeStore extends FluxStore {
} }
export type useStateFromStores = <T>( export type useStateFromStores = <T>(
stores: any[], stores: t.FluxStore[],
mapper: () => T, mapper: () => T,
dependencies?: any, dependencies?: any,
isEqual?: (old: T, newer: T) => boolean isEqual?: (old: T, newer: T) => boolean

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { runtimeHashMessageKey } from "@utils/intlHash";
import type { Channel } from "discord-types/general"; import type { Channel } from "discord-types/general";
// eslint-disable-next-line path-alias/no-relative // eslint-disable-next-line path-alias/no-relative
@ -57,8 +58,8 @@ export const { match, P }: Pick<typeof import("ts-pattern"), "match" | "P"> = ma
export const lodash: typeof import("lodash") = findByPropsLazy("debounce", "cloneDeep"); export const lodash: typeof import("lodash") = findByPropsLazy("debounce", "cloneDeep");
export const i18n = mapMangledModuleLazy('defaultLocale:"en-US"', { export const i18n = mapMangledModuleLazy('defaultLocale:"en-US"', {
t: m => m?.[Symbol.toStringTag] === "IntlMessagesProxy", t: filters.byProps(runtimeHashMessageKey("DISCORD")),
intl: m => m != null && Object.getPrototypeOf(m)?.withFormatters != null intl: filters.byProps("string", "format"),
}, true); }, true);
export let SnowflakeUtils: t.SnowflakeUtils; export let SnowflakeUtils: t.SnowflakeUtils;

View file

@ -12,8 +12,8 @@ import { canonicalizeReplacement } from "@utils/patches";
import { Patch, PatchReplacement } from "@utils/types"; import { Patch, PatchReplacement } from "@utils/types";
import { traceFunctionWithResults } from "../debug/Tracer"; import { traceFunctionWithResults } from "../debug/Tracer";
import { _blacklistBadModules, _initWebpack, factoryListeners, findModuleFactory, moduleListeners, waitForSubscriptions, wreq } from "./webpack"; import { _blacklistBadModules, _initWebpack, factoryListeners, findModuleId, moduleListeners, waitForSubscriptions, wreq } from "./webpack";
import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, PatchedModuleFactory, WebpackRequire } from "./wreq.d"; import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, ModuleExports, PatchedModuleFactory, WebpackRequire } from "./wreq.d";
export const patches = [] as Patch[]; export const patches = [] as Patch[];
@ -27,26 +27,28 @@ export const patchTimings = [] as Array<[plugin: string, moduleId: PropertyKey,
export const getBuildNumber = makeLazy(() => { export const getBuildNumber = makeLazy(() => {
try { try {
function matchBuildNumber(factoryStr: string) { try {
const buildNumberMatch = factoryStr.match(/.concat\("(\d+?)"\)/); if (wreq.m[128014]?.toString().includes("Trying to open a changelog for an invalid build number")) {
if (buildNumberMatch == null) { const hardcodedGetBuildNumber = wreq(128014).b as () => number;
if (typeof hardcodedGetBuildNumber === "function" && typeof hardcodedGetBuildNumber() === "number") {
return hardcodedGetBuildNumber();
}
}
} catch { }
const moduleId = findModuleId("Trying to open a changelog for an invalid build number");
if (moduleId == null) {
return -1; return -1;
} }
return Number(buildNumberMatch[1]); const exports = Object.values<ModuleExports>(wreq(moduleId));
if (exports.length !== 1 || typeof exports[0] !== "function") {
return -1;
} }
const hardcodedFactoryStr = String(wreq.m[128014]); const buildNumber = exports[0]();
if (hardcodedFactoryStr.includes("Trying to open a changelog for an invalid build number")) { return typeof buildNumber === "number" ? buildNumber : -1;
const hardcodedBuildNumber = matchBuildNumber(hardcodedFactoryStr);
if (hardcodedBuildNumber !== -1) {
return hardcodedBuildNumber;
}
}
const moduleFactory = findModuleFactory("Trying to open a changelog for an invalid build number");
return matchBuildNumber(String(moduleFactory));
} catch { } catch {
return -1; return -1;
} }
@ -120,12 +122,12 @@ define(Function.prototype, "m", {
return; return;
} }
patchThisInstance();
if (wreq == null && this.c != null) { if (wreq == null && this.c != null) {
logger.info("Main WebpackInstance found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire"); logger.info("Main WebpackInstance found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
_initWebpack(this as WebpackRequire); _initWebpack(this as WebpackRequire);
} }
patchThisInstance();
} }
}); });
@ -191,13 +193,13 @@ define(Function.prototype, "m", {
// Overwrite Webpack's defineExports function to define the export descriptors configurable. // Overwrite Webpack's defineExports function to define the export descriptors configurable.
// This is needed so we can later blacklist specific exports from Webpack search by making them non-enumerable // This is needed so we can later blacklist specific exports from Webpack search by making them non-enumerable
this.d = function (exports, definition) { this.d = function (exports: object, getters: object) {
for (const key in definition) { for (const key in getters) {
if (Object.hasOwn(definition, key) && !Object.hasOwn(exports, key)) { if (Object.hasOwn(getters, key) && !Object.hasOwn(exports, key)) {
Object.defineProperty(exports, key, { Object.defineProperty(exports, key, {
enumerable: true, enumerable: true,
configurable: true, configurable: true,
get: definition[key], get: getters[key],
}); });
} }
} }
@ -295,6 +297,11 @@ function updateExistingFactory(moduleFactories: AnyWebpackRequire["m"], moduleId
} }
if (existingFactory != null) { if (existingFactory != null) {
// Sanity check to make sure these factories are equal
if (String(newFactory) !== String(existingFactory)) {
return false;
}
// If existingFactory exists in any of the Webpack instances we track, it's either wrapped in our proxy, or it has already been required. // If existingFactory exists in any of the Webpack instances we track, it's either wrapped in our proxy, or it has already been required.
// In the case it is wrapped in our proxy, and the instance we are setting does not already have it, we need to make sure the instance contains our proxy too. // In the case it is wrapped in our proxy, and the instance we are setting does not already have it, we need to make sure the instance contains our proxy too.
if (moduleFactoriesWithFactory !== moduleFactories && existingFactory[SYM_IS_PROXIED_FACTORY]) { if (moduleFactoriesWithFactory !== moduleFactories && existingFactory[SYM_IS_PROXIED_FACTORY]) {
@ -410,6 +417,9 @@ function runFactoryWithWrap(patchedFactory: PatchedModuleFactory, thisArg: unkno
} }
exports = module.exports; exports = module.exports;
if (exports == null) {
return factoryReturn;
}
if (typeof require === "function" && require.c) { if (typeof require === "function" && require.c) {
if (_blacklistBadModules(require.c, exports, module.id)) { if (_blacklistBadModules(require.c, exports, module.id)) {
@ -417,10 +427,6 @@ function runFactoryWithWrap(patchedFactory: PatchedModuleFactory, thisArg: unkno
} }
} }
if (exports == null) {
return factoryReturn;
}
for (const callback of moduleListeners) { for (const callback of moduleListeners) {
try { try {
callback(exports, module.id); callback(exports, module.id);
@ -476,23 +482,23 @@ function patchFactory(moduleId: PropertyKey, originalFactory: AnyModuleFactory):
for (let i = 0; i < patches.length; i++) { for (let i = 0; i < patches.length; i++) {
const patch = patches[i]; const patch = patches[i];
const buildNumber = getBuildNumber(); const moduleMatches = typeof patch.find === "string"
const shouldCheckBuildNumber = buildNumber !== -1; ? code.includes(patch.find)
: (patch.find.global && (patch.find.lastIndex = 0), patch.find.test(code));
if (!moduleMatches) {
continue;
}
// Eager patches cannot retrieve the build number because this code runs before the module for it is loaded
const buildNumber = Settings.eagerPatches ? -1 : getBuildNumber();
const shouldCheckBuildNumber = !Settings.eagerPatches && buildNumber !== -1;
if ( if (
shouldCheckBuildNumber && shouldCheckBuildNumber &&
(patch.fromBuild != null && buildNumber < patch.fromBuild) || (patch.fromBuild != null && buildNumber < patch.fromBuild) ||
(patch.toBuild != null && buildNumber > patch.toBuild) (patch.toBuild != null && buildNumber > patch.toBuild)
) { ) {
patches.splice(i--, 1);
continue;
}
const moduleMatches = typeof patch.find === "string"
? code.includes(patch.find)
: (patch.find.global && (patch.find.lastIndex = 0), patch.find.test(code));
if (!moduleMatches) {
continue; continue;
} }
@ -534,7 +540,7 @@ function patchFactory(moduleId: PropertyKey, originalFactory: AnyModuleFactory):
} }
if (newCode === code) { if (newCode === code) {
if (!(patch.noWarn || replacement.noWarn)) { if (!patch.noWarn) {
logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${String(moduleId)}): ${replacement.match}`); logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${String(moduleId)}): ${replacement.match}`);
if (IS_DEV) { if (IS_DEV) {
logger.debug("Function Source:\n", code); logger.debug("Function Source:\n", code);
@ -556,13 +562,8 @@ function patchFactory(moduleId: PropertyKey, originalFactory: AnyModuleFactory):
continue; continue;
} }
const pluginsList = [...patchedBy];
if (!patchedBy.has(patch.plugin)) {
pluginsList.push(patch.plugin);
}
code = newCode; code = newCode;
patchedSource = `// Webpack Module ${String(moduleId)} - Patched by ${pluginsList.join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${String(moduleId)}`; patchedSource = `// Webpack Module ${String(moduleId)} - Patched by ${[...patchedBy, patch.plugin].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${String(moduleId)}`;
patchedFactory = (0, eval)(patchedSource); patchedFactory = (0, eval)(patchedSource);
if (!patchedBy.has(patch.plugin)) { if (!patchedBy.has(patch.plugin)) {

View file

@ -131,7 +131,7 @@ function shouldIgnoreValue(value: any) {
return false; return false;
} }
function makePropertyNonEnumerable(target: Record<PropertyKey, any>, key: PropertyKey) { function makePropertyNonEnumerable(target: Object, key: PropertyKey) {
const descriptor = Object.getOwnPropertyDescriptor(target, key); const descriptor = Object.getOwnPropertyDescriptor(target, key);
if (descriptor == null) return; if (descriptor == null) return;
@ -499,12 +499,12 @@ export function findExportedComponentLazy<T extends object = any>(...props: Prop
}); });
} }
function getAllPropertyNames(object: Record<PropertyKey, any>, includeNonEnumerable: boolean) { function getAllPropertyNames(object: Object, includeNonEnumerable: boolean) {
const names = new Set<PropertyKey>(); const names = new Set<PropertyKey>();
const getKeys = includeNonEnumerable ? Object.getOwnPropertyNames : Object.keys; const getKeys = includeNonEnumerable ? Object.getOwnPropertyNames : Object.keys;
do { do {
getKeys(object).forEach(name => name !== "__esModule" && names.add(name)); getKeys(object).forEach(name => names.add(name));
object = Object.getPrototypeOf(object); object = Object.getPrototypeOf(object);
} while (object != null); } while (object != null);

13
src/webpack/wreq.d.ts vendored
View file

@ -17,11 +17,14 @@ export type Module = {
/** exports can be anything, however initially it is always an empty object */ /** exports can be anything, however initially it is always an empty object */
export type ModuleFactory = (this: ModuleExports, module: Module, exports: ModuleExports, require: WebpackRequire) => void; export type ModuleFactory = (this: ModuleExports, module: Module, exports: ModuleExports, require: WebpackRequire) => void;
/** Keys here can be symbols too, but we can't properly type them */ export type WebpackQueues = unique symbol | "__webpack_queues__";
export type WebpackExports = unique symbol | "__webpack_exports__";
export type WebpackError = unique symbol | "__webpack_error__";
export type AsyncModulePromise = Promise<ModuleExports> & { export type AsyncModulePromise = Promise<ModuleExports> & {
"__webpack_queues__": (fnQueue: ((queue: any[]) => any)) => any; [WebpackQueues]: (fnQueue: ((queue: any[]) => any)) => any;
"__webpack_exports__": ModuleExports; [WebpackExports]: ModuleExports;
"__webpack_error__"?: any; [WebpackError]?: any;
}; };
export type AsyncModuleBody = ( export type AsyncModuleBody = (
@ -149,7 +152,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & {
* } * }
* // exports is now { exportName: someExportedValue } (but each value is actually a getter) * // exports is now { exportName: someExportedValue } (but each value is actually a getter)
*/ */
d: (this: WebpackRequire, exports: Record<PropertyKey, any>, definiton: Record<PropertyKey, () => ModuleExports>) => void; d: (this: WebpackRequire, exports: AnyRecord, definiton: AnyRecord) => void;
/** The ensure chunk handlers, which are used to ensure the files of the chunks are loaded, or load if necessary */ /** The ensure chunk handlers, which are used to ensure the files of the chunks are loaded, or load if necessary */
f: EnsureChunkHandlers; f: EnsureChunkHandlers;
/** /**