Skip to content

Commit

Permalink
Merge branch 'Davilarek:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
SashaXser authored Oct 13, 2024
2 parents f2aef26 + 11a04b3 commit 461bd0c
Show file tree
Hide file tree
Showing 77 changed files with 1,098 additions and 783 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
"version": "1.10.1",
"version": "1.10.4",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
Expand Down
11 changes: 6 additions & 5 deletions src/api/ContextMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,20 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
* A helper function for finding the children array of a group nested inside a context menu based on the id(s) of its children
* @param id The id of the child. If an array is specified, all ids will be tried
* @param children The context menu children
* @param matchSubstring Whether to check if the id is a substring of the child id
*/
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null>): Array<ReactElement | null> | null {
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null | undefined>, matchSubstring = false): Array<ReactElement | null | undefined> | null {
for (const child of children) {
if (child == null) continue;

if (Array.isArray(child)) {
const found = findGroupChildrenByChildId(id, child);
const found = findGroupChildrenByChildId(id, child, matchSubstring);
if (found !== null) return found;
}

if (
(Array.isArray(id) && id.some(id => child.props?.id === id))
|| child.props?.id === id
(Array.isArray(id) && id.some(id => matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id))
|| (matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id)
) return children;

let nextChildren = child.props?.children;
Expand All @@ -112,7 +113,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
child.props.children = nextChildren;
}

const found = findGroupChildrenByChildId(id, nextChildren);
const found = findGroupChildrenByChildId(id, nextChildren, matchSubstring);
if (found !== null) return found;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { OptionType, PluginOptionNumber } from "@utils/types";
import { Forms, React, TextInput } from "@webpack/common";

Expand Down Expand Up @@ -54,7 +56,8 @@ export function SettingNumericComponent({ option, pluginSettings, definedSetting

return (
<Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle>
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
<TextInput
type="number"
pattern="-?[0-9]+"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { PluginOptionSelect } from "@utils/types";
import { Forms, React, Select } from "@webpack/common";

Expand Down Expand Up @@ -44,7 +46,8 @@ export function SettingSelectComponent({ option, pluginSettings, definedSettings

return (
<Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle>
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom16} type="description">{option.description}</Forms.FormText>
<Select
isDisabled={option.disabled?.call(definedSettings) ?? false}
options={option.options}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { PluginOptionSlider } from "@utils/types";
import { Forms, React, Slider } from "@webpack/common";

Expand Down Expand Up @@ -50,7 +52,8 @@ export function SettingSliderComponent({ option, pluginSettings, definedSettings

return (
<Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle>
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
<Slider
disabled={option.disabled?.call(definedSettings) ?? false}
markers={option.markers}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { PluginOptionString } from "@utils/types";
import { Forms, React, TextInput } from "@webpack/common";

Expand All @@ -41,7 +43,8 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings,

return (
<Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle>
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
<TextInput
type="text"
value={state}
Expand Down
6 changes: 3 additions & 3 deletions src/components/PluginSettings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
const settings = Settings.plugins[plugin.name];

const isEnabled = () => settings.enabled ?? false;
const isEnabled = () => Vencord.Plugins.isPluginEnabled(plugin.name);

function toggleEnabled() {
const wasEnabled = isEnabled();
Expand Down Expand Up @@ -292,10 +292,10 @@ export default function PluginSettings() {

if (!pluginFilter(p)) continue;

const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
const isRequired = p.required || p.isDependency || depMap[p.name]?.some(d => settings.plugins[d].enabled);

if (isRequired) {
const tooltipText = p.required
const tooltipText = p.required || !depMap[p.name]
? "This plugin is required for Vencord to function."
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));

Expand Down
2 changes: 1 addition & 1 deletion src/plugins/_core/supportHelper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export default definePlugin({
required: true,
description: "Helps us provide support to you",
authors: [Devs.Ven],
dependencies: ["CommandsAPI", "UserSettingsAPI", "MessageAccessoriesAPI"],
dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"],

settings,

Expand Down
2 changes: 1 addition & 1 deletion src/plugins/anonymiseFileNames/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export default definePlugin({
description: "Anonymise uploaded file names",
patches: [
{
find: "instantBatchUpload:function",
find: "instantBatchUpload:",
replacement: {
match: /uploadFiles:(\i),/,
replace:
Expand Down
32 changes: 17 additions & 15 deletions src/plugins/appleMusic.desktop/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ interface ActivityButton {
}

interface Activity {
state: string;
state?: string;
details?: string;
timestamps?: {
start?: number;
Expand Down Expand Up @@ -52,17 +52,17 @@ const enum ActivityFlag {

export interface TrackData {
name: string;
album: string;
artist: string;
album?: string;
artist?: string;

appleMusicLink?: string;
songLink?: string;

albumArtwork?: string;
artistArtwork?: string;

playerPosition: number;
duration: number;
playerPosition?: number;
duration?: number;
}

const enum AssetImageType {
Expand Down Expand Up @@ -120,7 +120,7 @@ const settings = definePluginSettings({
stateString: {
type: OptionType.STRING,
description: "Activity state format string",
default: "{artist}"
default: "{artist} · {album}"
},
largeImageType: {
type: OptionType.SELECT,
Expand Down Expand Up @@ -155,8 +155,8 @@ const settings = definePluginSettings({
function customFormat(formatStr: string, data: TrackData) {
return formatStr
.replaceAll("{name}", data.name)
.replaceAll("{album}", data.album)
.replaceAll("{artist}", data.artist);
.replaceAll("{album}", data.album ?? "")
.replaceAll("{artist}", data.artist ?? "");
}

function getImageAsset(type: AssetImageType, data: TrackData) {
Expand Down Expand Up @@ -212,14 +212,16 @@ export default definePlugin({

const assets: ActivityAssets = {};

const isRadio = Number.isNaN(trackData.duration) && (trackData.playerPosition === 0);

if (settings.store.largeImageType !== AssetImageType.Disabled) {
assets.large_image = largeImageAsset;
assets.large_text = customFormat(settings.store.largeTextString, trackData);
if (!isRadio) assets.large_text = customFormat(settings.store.largeTextString, trackData);
}

if (settings.store.smallImageType !== AssetImageType.Disabled) {
assets.small_image = smallImageAsset;
assets.small_text = customFormat(settings.store.smallTextString, trackData);
if (!isRadio) assets.small_text = customFormat(settings.store.smallTextString, trackData);
}

const buttons: ActivityButton[] = [];
Expand All @@ -243,17 +245,17 @@ export default definePlugin({

name: customFormat(settings.store.nameString, trackData),
details: customFormat(settings.store.detailsString, trackData),
state: customFormat(settings.store.stateString, trackData),
state: isRadio ? undefined : customFormat(settings.store.stateString, trackData),

timestamps: (settings.store.enableTimestamps ? {
timestamps: (trackData.playerPosition && trackData.duration && settings.store.enableTimestamps) ? {
start: Date.now() - (trackData.playerPosition * 1000),
end: Date.now() - (trackData.playerPosition * 1000) + (trackData.duration * 1000),
} : undefined),
} : undefined,

assets,

buttons: buttons.length ? buttons.map(v => v.label) : undefined,
metadata: { button_urls: buttons.map(v => v.url) || undefined, },
buttons: !isRadio && buttons.length ? buttons.map(v => v.label) : undefined,
metadata: !isRadio && buttons.length ? { button_urls: buttons.map(v => v.url) } : undefined,

type: settings.store.activityType,
flags: ActivityFlag.INSTANCE,
Expand Down
84 changes: 47 additions & 37 deletions src/plugins/appleMusic.desktop/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,11 @@ import type { TrackData } from ".";

const exec = promisify(execFile);

// function exec(file: string, args: string[] = []) {
// return new Promise<{ code: number | null, stdout: string | null, stderr: string | null; }>((resolve, reject) => {
// const process = spawn(file, args, { stdio: [null, "pipe", "pipe"] });

// let stdout: string | null = null;
// process.stdout.on("data", (chunk: string) => { stdout ??= ""; stdout += chunk; });
// let stderr: string | null = null;
// process.stderr.on("data", (chunk: string) => { stdout ??= ""; stderr += chunk; });

// process.on("exit", code => { resolve({ code, stdout, stderr }); });
// process.on("error", err => reject(err));
// });
// }

async function applescript(cmds: string[]) {
const { stdout } = await exec("osascript", cmds.map(c => ["-e", c]).flat());
return stdout;
}

function makeSearchUrl(type: string, query: string) {
const url = new URL("https://tools.applemediaservices.com/api/apple-media/music/US/search.json");
url.searchParams.set("types", type);
url.searchParams.set("limit", "1");
url.searchParams.set("term", query);
return url;
}

const requestOptions: RequestInit = {
headers: { "user-agent": "Mozilla/5.0 (Windows NT 10.0; rv:125.0) Gecko/20100101 Firefox/125.0" },
};

interface RemoteData {
appleMusicLink?: string,
songLink?: string,
Expand All @@ -51,28 +25,64 @@ interface RemoteData {

let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;

const APPLE_MUSIC_BUNDLE_REGEX = /<script type="module" crossorigin src="([a-zA-Z0-9.\-/]+)"><\/script>/;
const APPLE_MUSIC_TOKEN_REGEX = /\w+="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)",\w+="x-apple-jingle-correlation-key"/;

let cachedToken: string | undefined = undefined;

const getToken = async () => {
if (cachedToken) return cachedToken;

const html = await fetch("https://music.apple.com/").then(r => r.text());
const bundleUrl = new URL(html.match(APPLE_MUSIC_BUNDLE_REGEX)![1], "https://music.apple.com/");

const bundle = await fetch(bundleUrl).then(r => r.text());
const token = bundle.match(APPLE_MUSIC_TOKEN_REGEX)![1];

cachedToken = token;
return token;
};

async function fetchRemoteData({ id, name, artist, album }: { id: string, name: string, artist: string, album: string; }) {
if (id === cachedRemoteData?.id) {
if ("data" in cachedRemoteData) return cachedRemoteData.data;
if ("failures" in cachedRemoteData && cachedRemoteData.failures >= 5) return null;
}

try {
const [songData, artistData] = await Promise.all([
fetch(makeSearchUrl("songs", artist + " " + album + " " + name), requestOptions).then(r => r.json()),
fetch(makeSearchUrl("artists", artist.split(/ *[,&] */)[0]), requestOptions).then(r => r.json())
]);

const appleMusicLink = songData?.songs?.data[0]?.attributes.url;
const songLink = songData?.songs?.data[0]?.id ? `https://song.link/i/${songData?.songs?.data[0]?.id}` : undefined;

const albumArtwork = songData?.songs?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512");
const artistArtwork = artistData?.artists?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512");
const dataUrl = new URL("https://amp-api-edge.music.apple.com/v1/catalog/us/search");
dataUrl.searchParams.set("platform", "web");
dataUrl.searchParams.set("l", "en-US");
dataUrl.searchParams.set("limit", "1");
dataUrl.searchParams.set("with", "serverBubbles");
dataUrl.searchParams.set("types", "songs");
dataUrl.searchParams.set("term", `${name} ${artist} ${album}`);
dataUrl.searchParams.set("include[songs]", "artists");

const token = await getToken();

const songData = await fetch(dataUrl, {
headers: {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9",
"authorization": `Bearer ${token}`,
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
"origin": "https://music.apple.com",
},
})
.then(r => r.json())
.then(data => data.results.song.data[0]);

cachedRemoteData = {
id,
data: { appleMusicLink, songLink, albumArtwork, artistArtwork }
data: {
appleMusicLink: songData.attributes.url,
songLink: `https://song.link/i/${songData.id}`,
albumArtwork: songData.attributes.artwork.url.replace("{w}x{h}", "512x512"),
artistArtwork: songData.relationships.artists.data[0].attributes.artwork.url.replace("{w}x{h}", "512x512"),
}
};

return cachedRemoteData.data;
} catch (e) {
console.error("[AppleMusicRichPresence] Failed to fetch remote data:", e);
Expand Down
11 changes: 11 additions & 0 deletions src/plugins/betterFolders/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Better Folders

Better Folders offers a variety of options to improve your folder experience

Always show the folder icon, regardless of if the folder is open or not

Only have one folder open at a time

Open folders in a sidebar:

![A folder open in a separate sidebar](https://github.com/user-attachments/assets/432d3146-8091-4bae-9c1e-c19046c72947)
Loading

0 comments on commit 461bd0c

Please sign in to comment.