Merge branch 'main' into customFolderIcons

This commit is contained in:
sadan4 2024-07-30 23:13:40 -04:00 committed by GitHub
commit 8a0b8b60b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 194 additions and 141 deletions

View file

@ -35,7 +35,8 @@ export const ALLOWED_PROTOCOLS = [
"steam:", "steam:",
"spotify:", "spotify:",
"com.epicgames.launcher:", "com.epicgames.launcher:",
"tidal:" "tidal:",
"itunes:",
]; ];
export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla"); export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla");

View file

@ -27,12 +27,8 @@ export default definePlugin({
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL", find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
replacement: { replacement: {
// foo && !bar ? createElement(reactionStuffs)... createElement(blah,...makeElement(reply-other)) // foo && !bar ? createElement(reactionStuffs)... createElement(blah,...makeElement(reply-other))
match: /\i&&!\i\?\(0,\i\.jsxs?\)\(.{0,200}renderEmojiPicker:.{0,500}\?(\i)\(\{key:"reply-other"/, match: /\i&&!\i\?\(0,\i\.jsxs?\)\(.{0,200}renderEmojiPicker:.{0,500}\?(\i)\(\{key:"reply-other"(?<=message:(\i).+?)/,
replace: (m, makeElement) => { replace: (m, makeElement, msg) => `...Vencord.Api.MessagePopover._buildPopoverElements(${msg},${makeElement}),${m}`
const msg = m.match(/message:(.{1,3}),/)?.[1];
if (!msg) throw new Error("Could not find message variable");
return `...Vencord.Api.MessagePopover._buildPopoverElements(${msg},${makeElement}),${m}`;
}
} }
}], }],
}); });

View file

@ -25,11 +25,9 @@ export default definePlugin({
description: "Upload with a single click, open menu with right click", description: "Upload with a single click, open menu with right click",
patches: [ patches: [
{ {
find: "Messages.CHAT_ATTACH_UPLOAD_OR_INVITE", find: '"ChannelAttachButton"',
replacement: { replacement: {
// Discord merges multiple props here with Object.assign() match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
// This patch passes a third object to it with which we override onClick and onContextMenu
match: /CHAT_ATTACH_UPLOAD_OR_INVITE,onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
replace: "$&onClick:$1,onContextMenu:$2.onClick,", replace: "$&onClick:$1,onContextMenu:$2.onClick,",
}, },
}, },

View file

@ -0,0 +1,11 @@
# OpenInApp
Open links in their respective apps instead of your browser
## Currently supports:
- Spotify
- Steam
- EpicGames
- Tidal
- Apple Music (iTunes)

View file

@ -18,46 +18,70 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType, PluginNative } from "@utils/types"; import definePlugin, { OptionType, PluginNative, SettingsDefinition } from "@utils/types";
import { showToast, Toasts } from "@webpack/common"; import { showToast, Toasts } from "@webpack/common";
import type { MouseEvent } from "react"; import type { MouseEvent } from "react";
const ShortUrlMatcher = /^https:\/\/(spotify\.link|s\.team)\/.+$/; interface URLReplacementRule {
const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/; match: RegExp;
const SteamMatcher = /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/; replace: (...matches: string[]) => string;
const EpicMatcher = /^https:\/\/store\.epicgames\.com\/(.+)$/; description: string;
const TidalMatcher = /^https:\/\/tidal\.com\/browse\/(track|album|artist|playlist|user|video|mix)\/(.+)(?:\?.+?)?$/; shortlinkMatch?: RegExp;
accountViewReplace?: (userId: string) => string;
}
const settings = definePluginSettings({ // Do not forget to add protocols to the ALLOWED_PROTOCOLS constant
const UrlReplacementRules: Record<string, URLReplacementRule> = {
spotify: { spotify: {
type: OptionType.BOOLEAN, match: /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/,
replace: (_, type, id) => `spotify://${type}/${id}`,
description: "Open Spotify links in the Spotify app", description: "Open Spotify links in the Spotify app",
default: true, shortlinkMatch: /^https:\/\/spotify\.link\/.+$/,
accountViewReplace: userId => `spotify:user:${userId}`,
}, },
steam: { steam: {
type: OptionType.BOOLEAN, match: /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/,
replace: match => `steam://openurl/${match}`,
description: "Open Steam links in the Steam app", description: "Open Steam links in the Steam app",
default: true, shortlinkMatch: /^https:\/\/s.team\/.+$/,
accountViewReplace: userId => `steam://openurl/https://steamcommunity.com/profiles/${userId}`,
}, },
epic: { epic: {
type: OptionType.BOOLEAN, match: /^https:\/\/store\.epicgames\.com\/(.+)$/,
replace: (_, id) => `com.epicgames.launcher://store/${id}`,
description: "Open Epic Games links in the Epic Games Launcher", description: "Open Epic Games links in the Epic Games Launcher",
default: true,
}, },
tidal: { tidal: {
type: OptionType.BOOLEAN, match: /^https:\/\/tidal\.com\/browse\/(track|album|artist|playlist|user|video|mix)\/(.+)(?:\?.+?)?$/,
replace: (_, type, id) => `tidal://${type}/${id}`,
description: "Open Tidal links in the Tidal app", description: "Open Tidal links in the Tidal app",
default: true, },
} itunes: {
}); match: /^https:\/\/music\.apple\.com\/([a-z]{2}\/)?(album|artist|playlist|song|curator)\/([^/?#]+)\/?([^/?#]+)?(?:\?.*)?(?:#.*)?$/,
replace: (_, lang, type, name, id) => id ? `itunes://music.apple.com/us/${type}/${name}/${id}` : `itunes://music.apple.com/us/${type}/${name}`,
description: "Open Apple Music links in the iTunes app"
},
};
const pluginSettings = definePluginSettings(
Object.entries(UrlReplacementRules).reduce((acc, [key, rule]) => {
acc[key] = {
type: OptionType.BOOLEAN,
description: rule.description,
default: true,
};
return acc;
}, {} as SettingsDefinition)
);
const Native = VencordNative.pluginHelpers.OpenInApp as PluginNative<typeof import("./native")>; const Native = VencordNative.pluginHelpers.OpenInApp as PluginNative<typeof import("./native")>;
export default definePlugin({ export default definePlugin({
name: "OpenInApp", name: "OpenInApp",
description: "Open Spotify, Tidal, Steam and Epic Games URLs in their respective apps instead of your browser", description: "Open links in their respective apps instead of your browser",
authors: [Devs.Ven], authors: [Devs.Ven, Devs.surgedevs],
settings, settings: pluginSettings,
patches: [ patches: [
{ {
@ -70,7 +94,7 @@ export default definePlugin({
// Make Spotify profile activity links open in app on web // Make Spotify profile activity links open in app on web
{ {
find: "WEB_OPEN(", find: "WEB_OPEN(",
predicate: () => !IS_DISCORD_DESKTOP && settings.store.spotify, predicate: () => !IS_DISCORD_DESKTOP && pluginSettings.store.spotify,
replacement: { replacement: {
match: /\i\.\i\.isProtocolRegistered\(\)(.{0,100})window.open/g, match: /\i\.\i\.isProtocolRegistered\(\)(.{0,100})window.open/g,
replace: "true$1VencordNative.native.openExternal" replace: "true$1VencordNative.native.openExternal"
@ -79,8 +103,8 @@ export default definePlugin({
{ {
find: ".CONNECTED_ACCOUNT_VIEWED,", find: ".CONNECTED_ACCOUNT_VIEWED,",
replacement: { replacement: {
match: /(?<=href:\i,onClick:\i=>\{)(?=.{0,10}\i=(\i)\.type,.{0,100}CONNECTED_ACCOUNT_VIEWED)/, match: /(?<=href:\i,onClick:(\i)=>\{)(?=.{0,10}\i=(\i)\.type,.{0,100}CONNECTED_ACCOUNT_VIEWED)/,
replace: "$self.handleAccountView(arguments[0],$1.type,$1.id);" replace: "if($self.handleAccountView($1,$2.type,$2.id)) return;"
} }
} }
], ],
@ -89,61 +113,25 @@ export default definePlugin({
if (!data) return false; if (!data) return false;
let url = data.href; let url = data.href;
if (!IS_WEB && ShortUrlMatcher.test(url)) { if (!url) return false;
event?.preventDefault();
// CORS jumpscare
url = await Native.resolveRedirect(url);
}
spotify: { for (const [key, rule] of Object.entries(UrlReplacementRules)) {
if (!settings.store.spotify) break spotify; if (!pluginSettings.store[key]) continue;
const match = SpotifyMatcher.exec(url); if (rule.shortlinkMatch?.test(url)) {
if (!match) break spotify; event?.preventDefault();
url = await Native.resolveRedirect(url);
}
const [, type, id] = match; if (rule.match.test(url)) {
VencordNative.native.openExternal(`spotify:${type}:${id}`); showToast("Opened link in native app", Toasts.Type.SUCCESS);
event?.preventDefault(); const newUrl = url.replace(rule.match, rule.replace);
return true; VencordNative.native.openExternal(newUrl);
}
steam: { event?.preventDefault();
if (!settings.store.steam) break steam; return true;
}
if (!SteamMatcher.test(url)) break steam;
VencordNative.native.openExternal(`steam://openurl/${url}`);
event?.preventDefault();
// Steam does not focus itself so show a toast so it's slightly less confusing
showToast("Opened link in Steam", Toasts.Type.SUCCESS);
return true;
}
epic: {
if (!settings.store.epic) break epic;
const match = EpicMatcher.exec(url);
if (!match) break epic;
VencordNative.native.openExternal(`com.epicgames.launcher://store/${match[1]}`);
event?.preventDefault();
return true;
}
tidal: {
if (!settings.store.tidal) break tidal;
const match = TidalMatcher.exec(url);
if (!match) break tidal;
const [, type, id] = match;
VencordNative.native.openExternal(`tidal://${type}/${id}`);
event?.preventDefault();
return true;
} }
// in case short url didn't end up being something we can handle // in case short url didn't end up being something we can handle
@ -155,14 +143,12 @@ export default definePlugin({
return false; return false;
}, },
handleAccountView(event: { preventDefault(): void; }, platformType: string, userId: string) { handleAccountView(e: MouseEvent, platformType: string, userId: string) {
if (platformType === "spotify" && settings.store.spotify) { const rule = UrlReplacementRules[platformType];
VencordNative.native.openExternal(`spotify:user:${userId}`); if (rule?.accountViewReplace && pluginSettings.store[platformType]) {
event.preventDefault(); VencordNative.native.openExternal(rule.accountViewReplace(userId));
} else if (platformType === "steam" && settings.store.steam) { e.preventDefault();
VencordNative.native.openExternal(`steam://openurl/https://steamcommunity.com/profiles/${userId}`); return true;
showToast("Opened link in Steam", Toasts.Type.SUCCESS);
event.preventDefault();
} }
} }
}); });

View file

@ -211,9 +211,9 @@ export default definePlugin({
} }
}, },
{ {
find: /\.BITE_SIZE,onOpenProfile:\i,usernameIcon:/, find: '"BiteSizeProfileBody"',
replacement: { replacement: {
match: /currentUser:\i,guild:\i,onOpenProfile:.+?}\)(?=])(?<=user:(\i),bio:null==(\i)\?.+?)/, match: /currentUser:\i,guild:\i}\)(?<=user:(\i),bio:null==(\i)\?.+?)/,
replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2, simplified: true })" replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2, simplified: true })"
} }
} }

View file

@ -307,7 +307,7 @@ export default definePlugin({
] ]
}, },
{ {
find: '+1]})},"overflow"))', find: '})},"overflow"))',
replacement: [ replacement: [
{ {
// Create a variable for the channel prop // Create a variable for the channel prop

View file

@ -66,6 +66,15 @@ export default definePlugin({
replace: "return true", replace: "return true",
} }
}, },
// fixes a bug where Members page must be loaded to see highest role, why is Discord depending on MemberSafetyStore.getEnhancedMember for something that can be obtained here?
{
find: "Messages.GUILD_MEMBER_MOD_VIEW_PERMISSION_GRANTED_BY_ARIA_LABEL,tooltipContentClassName",
predicate: () => settings.store.showModView,
replacement: {
match: /(role:)\i(?=,guildId.{0,100}role:(\i\[))/,
replace: "$1$2arguments[0].member.highestRoleId]",
}
},
{ {
find: "prod_discoverable_guilds", find: "prod_discoverable_guilds",
predicate: () => settings.store.disableDiscoveryFilters, predicate: () => settings.store.disableDiscoveryFilters,

View file

@ -1,15 +0,0 @@
# XSOverlay Notifier
Sends Discord messages to [XSOverlay](https://store.steampowered.com/app/1173510/XSOverlay/) for easier viewing while using VR.
## Preview
![](https://github.com/Vendicated/Vencord/assets/24845294/205d2055-bb4a-44e4-b7e3-265391bccd40)
![](https://github.com/Vendicated/Vencord/assets/24845294/f15eff61-2d52-4620-bcab-808ecb1606d2)
## Usage
- Enable this plugin
- Set plugin settings as desired
- Open XSOverlay
- get ping spammed

View file

@ -1,16 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { createSocket, Socket } from "dgram";
let xsoSocket: Socket;
export function sendToOverlay(_, data: any) {
data.icon = Buffer.from(data.icon).toString("base64");
const json = JSON.stringify(data);
xsoSocket ??= createSocket("udp4");
xsoSocket.send(json, 42069, "127.0.0.1");
}

View file

@ -0,0 +1,14 @@
# XSOverlay Notifier
Sends Discord messages to [XSOverlay](https://store.steampowered.com/app/1173510/XSOverlay/) for easier viewing while using VR.
## Preview
![Resulting notification inside XSOverlay](https://github.com/Vendicated/Vencord/assets/24845294/205d2055-bb4a-44e4-b7e3-265391bccd40)
![Test notification inside XSOverlay](https://github.com/user-attachments/assets/d3b0c387-1d67-4697-a470-d4a927e228f4)
## Usage
- Enable this plugin
- Set port and plugin settings as desired (defaults should work fine)
- Open SteamVR and XSOverlay

View file

@ -8,9 +8,9 @@ import { definePluginSettings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components"; import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { findByCodeLazy, findLazy } from "@webpack"; import { findByCodeLazy, findLazy } from "@webpack";
import { ChannelStore, GuildStore, UserStore } from "@webpack/common"; import { Button, ChannelStore, GuildStore, UserStore } from "@webpack/common";
import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general"; import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general";
const ChannelTypes = findLazy(m => m.ANNOUNCEMENT_THREAD === 10); const ChannelTypes = findLazy(m => m.ANNOUNCEMENT_THREAD === 10);
@ -68,10 +68,40 @@ interface Call {
ringing: string[]; ringing: string[];
} }
interface ApiObject {
sender: string,
target: string,
command: string,
jsonData: string,
rawData: string | null,
}
interface NotificationObject {
type: number;
timeout: number;
height: number;
opacity: number;
volume: number;
audioPath: string;
title: string;
content: string;
useBase64Icon: boolean;
icon: ArrayBuffer | string;
sourceApp: string;
}
const notificationsShouldNotify = findByCodeLazy(".SUPPRESS_NOTIFICATIONS))return!1"); const notificationsShouldNotify = findByCodeLazy(".SUPPRESS_NOTIFICATIONS))return!1");
const XSLog = new Logger("XSOverlay"); const logger = new Logger("XSOverlay");
const settings = definePluginSettings({ const settings = definePluginSettings({
webSocketPort: {
type: OptionType.NUMBER,
description: "Websocket port",
default: 42070,
async onChange() {
await start();
}
},
botNotifications: { botNotifications: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Allow bot notifications", description: "Allow bot notifications",
@ -136,7 +166,17 @@ const settings = definePluginSettings({
}, },
}); });
const Native = VencordNative.pluginHelpers.XSOverlay as PluginNative<typeof import("./native")>; let socket: WebSocket;
async function start() {
if (socket) socket.close();
socket = new WebSocket(`ws://127.0.0.1:${settings.store.webSocketPort ?? 42070}/?client=Vencord`);
return new Promise((resolve, reject) => {
socket.onopen = resolve;
socket.onerror = reject;
setTimeout(reject, 3000);
});
}
export default definePlugin({ export default definePlugin({
name: "XSOverlay", name: "XSOverlay",
@ -248,7 +288,21 @@ export default definePlugin({
if (shouldIgnoreForChannelType(channel)) return; if (shouldIgnoreForChannelType(channel)) return;
sendMsgNotif(titleString, finalMsg, message); sendMsgNotif(titleString, finalMsg, message);
} }
} },
start,
stop() {
socket.close();
},
settingsAboutComponent: () => (
<>
<Button onClick={() => sendOtherNotif("This is a test notification! explode", "Hello from Vendor!")}>
Send test notification
</Button>
</>
)
}); });
function shouldIgnoreForChannelType(channel: Channel) { function shouldIgnoreForChannelType(channel: Channel) {
@ -259,9 +313,8 @@ function shouldIgnoreForChannelType(channel: Channel) {
function sendMsgNotif(titleString: string, content: string, message: Message) { function sendMsgNotif(titleString: string, content: string, message: Message) {
fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => { fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => {
const msgData = { const msgData: NotificationObject = {
messageType: 1, type: 1,
index: 0,
timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout, timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout,
height: calculateHeight(content), height: calculateHeight(content),
opacity: settings.store.opacity, opacity: settings.store.opacity,
@ -270,17 +323,17 @@ function sendMsgNotif(titleString: string, content: string, message: Message) {
title: titleString, title: titleString,
content: content, content: content,
useBase64Icon: true, useBase64Icon: true,
icon: result, icon: new TextDecoder().decode(result),
sourceApp: "Vencord" sourceApp: "Vencord"
}; };
Native.sendToOverlay(msgData);
sendToOverlay(msgData);
}); });
} }
function sendOtherNotif(content: string, titleString: string) { function sendOtherNotif(content: string, titleString: string) {
const msgData = { const msgData: NotificationObject = {
messageType: 1, type: 1,
index: 0,
timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout, timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout,
height: calculateHeight(content), height: calculateHeight(content),
opacity: settings.store.opacity, opacity: settings.store.opacity,
@ -289,10 +342,22 @@ function sendOtherNotif(content: string, titleString: string) {
title: titleString, title: titleString,
content: content, content: content,
useBase64Icon: false, useBase64Icon: false,
icon: null, icon: "default",
sourceApp: "Vencord" sourceApp: "Vencord"
}; };
Native.sendToOverlay(msgData); sendToOverlay(msgData);
}
async function sendToOverlay(notif: NotificationObject) {
const apiObject: ApiObject = {
sender: "Vencord",
target: "xsoverlay",
command: "SendNotification",
jsonData: JSON.stringify(notif),
rawData: null
};
if (socket.readyState !== socket.OPEN) await start();
socket.send(JSON.stringify(apiObject));
} }
function shouldNotify(message: Message, channel: string) { function shouldNotify(message: Message, channel: string) {

View file

@ -542,6 +542,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "Joona", name: "Joona",
id: 297410829589020673n id: 297410829589020673n
}, },
surgedevs: {
name: "Chloe",
id: 1084592643784331324n
}
} satisfies Record<string, Dev>); } satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly // iife so #__PURE__ works correctly