mirror of
https://github.com/Equicord/Equicord.git
synced 2025-05-11 01:45:36 +02:00
Add All Those Plugins Back
This commit is contained in:
parent
0eafb62a6b
commit
1d99644de7
40 changed files with 8824 additions and 57 deletions
13
README.md
13
README.md
|
@ -11,7 +11,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
### Extra included plugins
|
||||
|
||||
<details>
|
||||
<summary>152 additional plugins</summary>
|
||||
<summary>163 additional plugins</summary>
|
||||
|
||||
### All Platforms
|
||||
|
||||
|
@ -23,6 +23,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- AtSomeone by Joona
|
||||
- BannersEverywhere by ImLvna & AutumnVN
|
||||
- BetterActivities by D3SOX, Arjix, AutumnVN
|
||||
- BetterAudioPlayer by creations
|
||||
- BetterBanReasons by Inbestigator
|
||||
- BetterBlockedUsers by TheArmagan & Elvyra
|
||||
- BetterInvites by iamme
|
||||
|
@ -42,6 +43,9 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- CustomSounds by TheKodeToad & SpikeHD
|
||||
- CustomTimestamps by Rini & nvhrr
|
||||
- CustomUserColors by mochienya
|
||||
- CuteAnimeBoys by ShadyGoat
|
||||
- CuteNekos by echo
|
||||
- CutePats by thororen
|
||||
- DecodeBase64 by ThePirateStoner
|
||||
- Demonstration by Samwich
|
||||
- DisableAnimations by S€th
|
||||
|
@ -64,10 +68,13 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- FriendshipRanks by Samwich
|
||||
- FriendTags by Samwich
|
||||
- FullVcPfp by mochie
|
||||
- GensokyoRadioRPC by RyanCaoDev & Prince527
|
||||
- GifCollections by Aria & Creations
|
||||
- GifRoulette by Samwich
|
||||
- GitHubRepos by talhakf
|
||||
- Glide by Samwich
|
||||
- GlobalBadges by HypedDomi & Hosted by Wolfie
|
||||
- GoogleThat by Samwich
|
||||
- HideChatButtons by iamme
|
||||
- HideServers by bepvte
|
||||
- HolyNotes by Wolfie
|
||||
|
@ -75,6 +82,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- HopOn by ImLvna
|
||||
- Husk by nin0dev
|
||||
- IconViewer by iamme
|
||||
- Identity by Samwich
|
||||
- IgnoreCalls by TheArmagan
|
||||
- IgnoreTerms by D3SOX
|
||||
- ImagePreview by Creations
|
||||
|
@ -124,6 +132,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- RPCEditor by Nyako & nin0dev
|
||||
- RPCStats by Samwich
|
||||
- SearchFix by Jaxx
|
||||
- SekaiStickers by MaiKokain
|
||||
- ServerSearch by camila314
|
||||
- ShowBadgesInChat by Inbestigator & KrystalSkull
|
||||
- SidebarChat by Joona
|
||||
|
@ -142,6 +151,8 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- Timezones by Aria
|
||||
- Title by Kyuuhachi
|
||||
- ToggleVideoBind by mochie
|
||||
- TosuRPC by AutumnVN
|
||||
- Translate+ by Prince527 & Ven
|
||||
- UnitConverter by sadan
|
||||
- UnlimitedAccounts by thororen
|
||||
- UnreadCountBadge by Joona
|
||||
|
|
|
@ -318,12 +318,6 @@ function ThemesTab() {
|
|||
<Forms.FormText>If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder.</Forms.FormText>
|
||||
</Card>
|
||||
|
||||
<Card className="vc-settings-card">
|
||||
<Forms.FormTitle tag="h5">External Resources</Forms.FormTitle>
|
||||
<Forms.FormText>For security reasons, loading resources (styles, fonts, images, ...) from most sites is blocked.</Forms.FormText>
|
||||
<Forms.FormText>Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts.</Forms.FormText>
|
||||
</Card>
|
||||
|
||||
<Forms.FormSection title="Local Themes">
|
||||
<QuickActionCard>
|
||||
<>
|
||||
|
|
312
src/equicordplugins/betterAudioPlayer/index.tsx
Normal file
312
src/equicordplugins/betterAudioPlayer/index.tsx
Normal file
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { showToast, Toasts } from "@webpack/common";
|
||||
|
||||
const fileSizeLimit = 12e6;
|
||||
|
||||
function parseFileSize(size: string) {
|
||||
const [value, unit] = size.split(" ");
|
||||
const multiplier = {
|
||||
B: 1,
|
||||
KB: 1024,
|
||||
MB: 1024 ** 2,
|
||||
GB: 1024 ** 3,
|
||||
TB: 1024 ** 4,
|
||||
}[unit];
|
||||
if (!multiplier) return;
|
||||
return parseFloat(value) * multiplier;
|
||||
}
|
||||
|
||||
function getMetadata(audioElement: HTMLElement) {
|
||||
const metadataElement = audioElement.querySelector("[class^='metadataContent_']");
|
||||
const nameElement = metadataElement?.querySelector("a");
|
||||
const sizeElement = audioElement.querySelector("[class^='metadataContent_'] [class^='metadataSize_']");
|
||||
const url = nameElement?.getAttribute("href");
|
||||
const audioElementLink = audioElement.querySelector("audio");
|
||||
|
||||
if (!sizeElement?.textContent || !nameElement?.textContent || !url || !audioElementLink) return false;
|
||||
|
||||
const name = nameElement.textContent;
|
||||
const size = parseFileSize(sizeElement.textContent);
|
||||
|
||||
if (size && size > fileSizeLimit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const elements = [metadataElement?.parentElement, audioElement.querySelector("[class^='audioControls_']")];
|
||||
|
||||
const computedStyle = getComputedStyle(audioElement);
|
||||
const parentBorderRadius = computedStyle.borderRadius;
|
||||
|
||||
if (settings.store.forceMoveBelow) {
|
||||
elements.forEach(element => {
|
||||
if (element) (element as HTMLElement).style.zIndex = "2";
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
size,
|
||||
url,
|
||||
audio: audioElementLink,
|
||||
parentBorderRadius: parentBorderRadius,
|
||||
};
|
||||
}
|
||||
|
||||
async function addListeners(audioElement: HTMLAudioElement, url: string, parentBorderRadius: string) {
|
||||
const madeURL = new URL(url);
|
||||
madeURL.searchParams.set("t", Date.now().toString());
|
||||
|
||||
const corsProxyUrl = "https://corsproxy.io?" + encodeURIComponent(madeURL.href);
|
||||
const response = await fetch(corsProxyUrl);
|
||||
const blob = await response.blob();
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
const audioContext = new AudioContext();
|
||||
const analyser = audioContext.createAnalyser();
|
||||
analyser.fftSize = 2048;
|
||||
const bufferLength = analyser.frequencyBinCount;
|
||||
const dataArray = new Uint8Array(bufferLength);
|
||||
const frequencyData = new Uint8Array(bufferLength);
|
||||
|
||||
const source = audioContext.createMediaElementSource(audioElement);
|
||||
source.connect(analyser);
|
||||
analyser.connect(audioContext.destination);
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const canvasContext = canvas.getContext("2d");
|
||||
if (!canvasContext) return;
|
||||
|
||||
canvas.classList.add("better-audio-visualizer");
|
||||
audioElement.parentElement?.appendChild(canvas);
|
||||
|
||||
if (parentBorderRadius) canvas.style.borderRadius = parentBorderRadius;
|
||||
|
||||
function drawVisualizer() {
|
||||
if (!audioElement.paused) {
|
||||
requestAnimationFrame(drawVisualizer);
|
||||
}
|
||||
|
||||
analyser.getByteTimeDomainData(dataArray);
|
||||
analyser.getByteFrequencyData(frequencyData);
|
||||
|
||||
if (!canvasContext) return;
|
||||
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (settings.store.oscilloscope) drawOscilloscope(canvasContext, canvas, dataArray, bufferLength);
|
||||
if (settings.store.spectrograph) drawSpectrograph(canvasContext, canvas, frequencyData, bufferLength);
|
||||
}
|
||||
|
||||
audioElement.src = blobUrl;
|
||||
audioElement.addEventListener("play", () => {
|
||||
if (audioContext.state === "suspended") {
|
||||
audioContext.resume();
|
||||
}
|
||||
drawVisualizer();
|
||||
});
|
||||
|
||||
audioElement.addEventListener("pause", () => {
|
||||
audioContext.suspend();
|
||||
});
|
||||
}
|
||||
|
||||
function drawOscilloscope(canvasContext, canvas, dataArray, bufferLength) {
|
||||
const sliceWidth = canvas.width / bufferLength;
|
||||
let x = 0;
|
||||
|
||||
const { oscilloscopeSolidColor, oscilloscopeColor } = settings.store;
|
||||
|
||||
const [r, g, b] = oscilloscopeColor.split(",").map(Number);
|
||||
|
||||
canvasContext.lineWidth = 2;
|
||||
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
|
||||
canvasContext.beginPath();
|
||||
|
||||
for (let i = 0; i < bufferLength; i++) {
|
||||
const v = dataArray[i] / 128.0;
|
||||
const y = (v * canvas.height) / 2;
|
||||
|
||||
if (oscilloscopeSolidColor) {
|
||||
canvasContext.strokeStyle = `rgb(${r}, ${g}, ${b})`;
|
||||
} else {
|
||||
const red = Math.min(r + (v * 100) + (i / bufferLength) * 155, 255);
|
||||
const green = Math.min(g + (v * 50) + (i / bufferLength) * 155, 255);
|
||||
const blue = Math.min(b + (v * 150) + (i / bufferLength) * 155, 255);
|
||||
|
||||
canvasContext.strokeStyle = `rgb(${red}, ${green}, ${blue})`;
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
canvasContext.moveTo(x, y);
|
||||
} else {
|
||||
canvasContext.lineTo(x, y);
|
||||
}
|
||||
|
||||
x += sliceWidth;
|
||||
}
|
||||
|
||||
canvasContext.stroke();
|
||||
}
|
||||
|
||||
function drawSpectrograph(canvasContext, canvas, frequencyData, bufferLength) {
|
||||
const { spectrographSolidColor, spectrographColor } = settings.store;
|
||||
const maxHeight = canvas.height;
|
||||
const barWidth = canvas.width / bufferLength;
|
||||
let x = 0;
|
||||
|
||||
const maxFrequencyValue = Math.max(...frequencyData);
|
||||
|
||||
if (maxFrequencyValue === 0 || !isFinite(maxFrequencyValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < bufferLength; i++) {
|
||||
const normalizedHeight = (frequencyData[i] / maxFrequencyValue) * maxHeight;
|
||||
|
||||
if (spectrographSolidColor) {
|
||||
canvasContext.fillStyle = `rgb(${spectrographColor})`;
|
||||
} else {
|
||||
const [r, g, b] = spectrographColor.split(",").map(Number);
|
||||
|
||||
const red = Math.min(r + (i / bufferLength) * 155, 255);
|
||||
const green = Math.min(g + (i / bufferLength) * 155, 255);
|
||||
const blue = Math.min(b + (i / bufferLength) * 155, 255);
|
||||
|
||||
const gradient = canvasContext.createLinearGradient(x, canvas.height - normalizedHeight, x, canvas.height);
|
||||
gradient.addColorStop(0, `rgb(${red}, ${green}, ${blue})`);
|
||||
|
||||
const darkerColor = `rgb(${Math.max(red - 50, 0)},${Math.max(green - 50, 0)},${Math.max(blue - 50, 0)})`;
|
||||
|
||||
gradient.addColorStop(1, darkerColor);
|
||||
canvasContext.fillStyle = gradient;
|
||||
}
|
||||
|
||||
canvasContext.fillRect(x, canvas.height - normalizedHeight, barWidth, normalizedHeight);
|
||||
x += barWidth + 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
function scanForAudioElements(element: HTMLElement) {
|
||||
element.querySelectorAll("[class^='wrapperAudio_']:not([data-better-audio-processed])").forEach(audioElement => {
|
||||
(audioElement as HTMLElement).dataset.betterAudioProcessed = "true";
|
||||
const metadata = getMetadata(audioElement as HTMLElement);
|
||||
|
||||
if (!metadata) return;
|
||||
|
||||
addListeners(metadata.audio, metadata.url, metadata.parentBorderRadius);
|
||||
});
|
||||
}
|
||||
|
||||
function createObserver(targetNode: HTMLElement) {
|
||||
const observer = new MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
if (mutation.type === "childList") {
|
||||
mutation.addedNodes.forEach(addedNode => {
|
||||
if (addedNode instanceof HTMLElement) {
|
||||
scanForAudioElements(addedNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(targetNode, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
|
||||
function tryHexToRgb(hex) {
|
||||
if (hex.startsWith("#")) {
|
||||
const hexMatch = hex.match(/\w\w/g);
|
||||
if (hexMatch) {
|
||||
const [r, g, b] = hexMatch.map(x => parseInt(x, 16));
|
||||
return `${r}, ${g}, ${b}`;
|
||||
}
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
function handleColorChange(value, settingKey, defaultValue) {
|
||||
const rgbPattern = /^(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})$/;
|
||||
|
||||
if (!value.match(rgbPattern)) {
|
||||
const rgb = tryHexToRgb(value);
|
||||
|
||||
if (rgb.match(rgbPattern)) {
|
||||
settings.store[settingKey] = rgb;
|
||||
} else {
|
||||
showToast(`Invalid color format for ${settingKey}, make sure it's in the format 'R, G, B' or '#RRGGBB'`, Toasts.Type.FAILURE);
|
||||
settings.store[settingKey] = defaultValue;
|
||||
}
|
||||
} else {
|
||||
settings.store[settingKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
oscilloscope: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable oscilloscope visualizer",
|
||||
default: true,
|
||||
},
|
||||
spectrograph: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable spectrograph visualizer",
|
||||
default: true,
|
||||
},
|
||||
oscilloscopeSolidColor: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Use solid color for oscilloscope",
|
||||
default: false,
|
||||
},
|
||||
oscilloscopeColor: {
|
||||
type: OptionType.STRING,
|
||||
description: "Color for oscilloscope",
|
||||
default: "255, 255, 255",
|
||||
onChange: value => handleColorChange(value, "oscilloscopeColor", "255, 255, 255"),
|
||||
},
|
||||
spectrographSolidColor: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Use solid color for spectrograph",
|
||||
default: false,
|
||||
},
|
||||
spectrographColor: {
|
||||
type: OptionType.STRING,
|
||||
description: "Color for spectrograph",
|
||||
default: "33, 150, 243",
|
||||
onChange: value => handleColorChange(value, "spectrographColor", "33, 150, 243"),
|
||||
},
|
||||
forceMoveBelow: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Force the visualizer below the audio player",
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "BetterAudioPlayer",
|
||||
description: "Adds a spectrograph and oscilloscope visualizer to audio attachment players",
|
||||
authors: [EquicordDevs.creations],
|
||||
settings,
|
||||
start() {
|
||||
const waitForContent = () => {
|
||||
const targetNode = document.querySelector("[class^='content_']");
|
||||
if (targetNode) {
|
||||
scanForAudioElements(targetNode as HTMLElement);
|
||||
createObserver(targetNode as HTMLElement);
|
||||
} else {
|
||||
requestAnimationFrame(waitForContent);
|
||||
}
|
||||
};
|
||||
waitForContent();
|
||||
},
|
||||
});
|
10
src/equicordplugins/betterAudioPlayer/style.css
Normal file
10
src/equicordplugins/betterAudioPlayer/style.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
.better-audio-visualizer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
border: none;
|
||||
}
|
60
src/equicordplugins/cuteAnimeBoys/index.ts
Normal file
60
src/equicordplugins/cuteAnimeBoys/index.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
|
||||
import { ApplicationCommandOptionType } from "../../api/Commands";
|
||||
import definePlugin from "../../utils/types";
|
||||
|
||||
function rand(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
async function fetchReddit(sub: string) {
|
||||
const res = await fetch(`https://www.reddit.com/r/${sub}/top.json?limit=100&t=all`);
|
||||
const resp = await res.json();
|
||||
try {
|
||||
const { children } = resp.data;
|
||||
const r = rand(0, children.length - 1);
|
||||
return children[r].data.url;
|
||||
} catch (err) {
|
||||
console.error(resp);
|
||||
console.error(err);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "CuteAnimeBoys",
|
||||
authors: [EquicordDevs.ShadyGoat],
|
||||
description: "Add a command to send cute anime boys in the chat",
|
||||
commands: [{
|
||||
name: "anime-boys",
|
||||
description: "Send cute anime boys",
|
||||
options: [
|
||||
{
|
||||
name: "cat",
|
||||
description: "If set, this will send exclusively cute anime cat boys",
|
||||
type: ApplicationCommandOptionType.BOOLEAN,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
|
||||
async execute(args) {
|
||||
let sub = "cuteanimeboys";
|
||||
if (args.length > 0) {
|
||||
const v = args[0].value as any as boolean;
|
||||
if (v) {
|
||||
sub = "animecatboys";
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: await fetchReddit(sub),
|
||||
};
|
||||
},
|
||||
}]
|
||||
});
|
29
src/equicordplugins/cuteNekos/index.ts
Normal file
29
src/equicordplugins/cuteNekos/index.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
async function getcuteneko(): Promise<string> {
|
||||
const res = await fetch("https://nekos.best/api/v2/neko");
|
||||
const url = (await res.json()).results[0].url as string;
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default definePlugin({
|
||||
name: "CuteNekos",
|
||||
authors: [Devs.echo],
|
||||
description: "Send Nekos to others",
|
||||
commands: [{
|
||||
name: "nekos",
|
||||
description: "Send Neko",
|
||||
execute: async opts => ({
|
||||
content: await getcuteneko()
|
||||
})
|
||||
}]
|
||||
});
|
29
src/equicordplugins/cutePats/index.ts
Normal file
29
src/equicordplugins/cutePats/index.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
async function getcutepats(): Promise<string> {
|
||||
const res = await fetch("https://api.waifu.pics/sfw/pat");
|
||||
const url = (await res.json()).url as string;
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default definePlugin({
|
||||
name: "CutePats",
|
||||
authors: [EquicordDevs.thororen],
|
||||
description: "Sending Head Pats",
|
||||
commands: [{
|
||||
name: "pat",
|
||||
description: "Sends a headpat gif",
|
||||
execute: async opts => ({
|
||||
content: await getcutepats()
|
||||
})
|
||||
}]
|
||||
});
|
102
src/equicordplugins/gensokyoRadioRPC/index.tsx
Normal file
102
src/equicordplugins/gensokyoRadioRPC/index.tsx
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
|
||||
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
|
||||
|
||||
import { Activity, ActivityFlag, ActivityType } from "./types";
|
||||
|
||||
const Native = VencordNative.pluginHelpers.GensokyoRadioRPC as PluginNative<typeof import("./native")>;
|
||||
|
||||
const applicationId = "1253772057926303804";
|
||||
|
||||
function setActivity(activity: Activity | null) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "LOCAL_ACTIVITY_UPDATE",
|
||||
activity,
|
||||
socketId: "GensokyoRadio",
|
||||
});
|
||||
}
|
||||
|
||||
function getImageAsset(data: string) {
|
||||
return ApplicationAssetUtils.fetchAssetIds(applicationId, [data]).then(ids => ids[0]);
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
refreshInterval: {
|
||||
type: OptionType.SLIDER,
|
||||
description: "The interval between activity refreshes (seconds)",
|
||||
markers: [1, 2, 2.5, 3, 5, 10, 15],
|
||||
default: 15,
|
||||
restartNeeded: true,
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "GensokyoRadioRPC",
|
||||
description: "Discord rich presence for Gensokyo Radio!",
|
||||
authors: [Devs.RyanCaoDev, EquicordDevs.Prince527],
|
||||
reporterTestable: ReporterTestable.None,
|
||||
|
||||
settingsAboutComponent() {
|
||||
return <>
|
||||
<Forms.FormText>
|
||||
Discord rich presence for Gensokyo Radio!
|
||||
</Forms.FormText>
|
||||
</>;
|
||||
},
|
||||
|
||||
settings,
|
||||
|
||||
start() {
|
||||
this.updatePresence();
|
||||
this.updateInterval = setInterval(() => { this.updatePresence(); }, settings.store.refreshInterval * 1000);
|
||||
},
|
||||
|
||||
stop() {
|
||||
clearInterval(this.updateInterval);
|
||||
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null });
|
||||
},
|
||||
|
||||
updatePresence() {
|
||||
this.getActivity().then(activity => { setActivity(activity); });
|
||||
},
|
||||
|
||||
async getActivity(): Promise<Activity | null> {
|
||||
const trackData = await Native.fetchTrackData();
|
||||
if (!trackData) return null;
|
||||
|
||||
return {
|
||||
application_id: applicationId,
|
||||
|
||||
name: "Gensokyo Radio",
|
||||
details: trackData.title,
|
||||
state: trackData.artist,
|
||||
|
||||
timestamps: {
|
||||
// start: Date.now() - (trackData.position * 1000),
|
||||
start: trackData.position * 1000,
|
||||
// end: Date.now() - (trackData.position * 1000) + (trackData.duration * 1000),
|
||||
end: trackData.duration * 1000,
|
||||
},
|
||||
|
||||
assets: {
|
||||
large_image: await getImageAsset(trackData.artwork),
|
||||
large_text: trackData.album,
|
||||
small_image: await getImageAsset("logo"),
|
||||
small_text: "Gensokyo Radio"
|
||||
},
|
||||
|
||||
buttons: undefined,
|
||||
metadata: { button_urls: undefined },
|
||||
|
||||
type: ActivityType.LISTENING,
|
||||
flags: ActivityFlag.INSTANCE,
|
||||
};
|
||||
}
|
||||
});
|
20
src/equicordplugins/gensokyoRadioRPC/native.ts
Normal file
20
src/equicordplugins/gensokyoRadioRPC/native.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { TrackData } from "./types";
|
||||
|
||||
export async function fetchTrackData(): Promise<TrackData | null> {
|
||||
const song = await (await fetch("https://gensokyoradio.net/api/station/playing/")).json();
|
||||
|
||||
return {
|
||||
title: song.SONGINFO.TITLE,
|
||||
album: song.SONGINFO.ALBUM,
|
||||
artist: song.SONGINFO.ARTIST,
|
||||
position: song.SONGTIMES.SONGSTART,
|
||||
duration: song.SONGTIMES.SONGEND,
|
||||
artwork: song.MISC.ALBUMART ? `https://gensokyoradio.net/images/albums/500/${song.MISC.ALBUMART}` : "undefined",
|
||||
};
|
||||
}
|
57
src/equicordplugins/gensokyoRadioRPC/types.ts
Normal file
57
src/equicordplugins/gensokyoRadioRPC/types.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface ActivityAssets {
|
||||
large_image?: string;
|
||||
large_text?: string;
|
||||
small_image?: string;
|
||||
small_text?: string;
|
||||
}
|
||||
|
||||
export interface Activity {
|
||||
state: string;
|
||||
details?: string;
|
||||
timestamps?: {
|
||||
start?: number;
|
||||
end?: number;
|
||||
};
|
||||
assets?: ActivityAssets;
|
||||
buttons?: Array<string>;
|
||||
name: string;
|
||||
application_id: string;
|
||||
metadata?: {
|
||||
button_urls?: Array<string>;
|
||||
};
|
||||
type: number;
|
||||
flags: number;
|
||||
}
|
||||
|
||||
export interface ActivityAssets {
|
||||
large_image?: string;
|
||||
large_text?: string;
|
||||
small_image?: string;
|
||||
small_text?: string;
|
||||
}
|
||||
|
||||
export const enum ActivityType {
|
||||
PLAYING = 0,
|
||||
LISTENING = 2,
|
||||
}
|
||||
|
||||
export const enum ActivityFlag {
|
||||
INSTANCE = 1 << 0
|
||||
}
|
||||
|
||||
export interface TrackData {
|
||||
title: string;
|
||||
album: string;
|
||||
artist: string;
|
||||
|
||||
artwork: string;
|
||||
|
||||
position: number;
|
||||
duration: number;
|
||||
}
|
BIN
src/equicordplugins/glide/crosshair.png
Normal file
BIN
src/equicordplugins/glide/crosshair.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 108 B |
30
src/equicordplugins/glide/generateTheme.tsx
Normal file
30
src/equicordplugins/glide/generateTheme.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export function generateRandomColorHex(): string {
|
||||
const r = Math.floor(Math.random() * 90);
|
||||
const g = Math.floor(Math.random() * 90);
|
||||
const b = Math.floor(Math.random() * 90);
|
||||
|
||||
return `${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
export function darkenColorHex(color: string): string {
|
||||
const hex = color.replace(/^#/, "");
|
||||
const bigint = parseInt(hex, 16);
|
||||
let r = (bigint >> 16) & 255;
|
||||
let g = (bigint >> 8) & 255;
|
||||
let b = bigint & 255;
|
||||
r = Math.max(r - 5, 0);
|
||||
g = Math.max(g - 5, 0);
|
||||
b = Math.max(b - 5, 0);
|
||||
return `${((r << 16) + (g << 8) + b).toString(16).padStart(6, "0")}`;
|
||||
}
|
||||
|
||||
export function saturateColorHex(color: string): string {
|
||||
// i should really do something with this at some point :P
|
||||
return color;
|
||||
}
|
801
src/equicordplugins/glide/index.tsx
Normal file
801
src/equicordplugins/glide/index.tsx
Normal file
|
@ -0,0 +1,801 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, Clipboard, Forms, TextInput, Toasts, useState } from "@webpack/common";
|
||||
|
||||
import { darkenColorHex, generateRandomColorHex, saturateColorHex } from "./generateTheme";
|
||||
import { themes } from "./themeDefinitions";
|
||||
|
||||
export interface ThemePreset {
|
||||
bgcol: string;
|
||||
accentcol: string;
|
||||
textcol: string;
|
||||
brand: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
let setPreset;
|
||||
|
||||
|
||||
function LoadPreset(preset?: ThemePreset) {
|
||||
if (setPreset === settings.store.ColorPreset) { return; }
|
||||
const theme: ThemePreset = preset == null ? themes[settings.store.ColorPreset] : preset;
|
||||
setPreset = settings.store.ColorPreset;
|
||||
settings.store.Primary = theme.bgcol;
|
||||
settings.store.Accent = theme.accentcol;
|
||||
settings.store.Text = theme.textcol;
|
||||
settings.store.Brand = theme.brand;
|
||||
injectCSS();
|
||||
}
|
||||
|
||||
function mute(hex, amount) {
|
||||
hex = hex.replace(/^#/, "");
|
||||
const bigint = parseInt(hex, 16);
|
||||
let r = (bigint >> 16) & 255;
|
||||
let g = (bigint >> 8) & 255;
|
||||
let b = bigint & 255;
|
||||
r = Math.max(r - amount, 0);
|
||||
g = Math.max(g - amount, 0);
|
||||
b = Math.max(b - amount, 0);
|
||||
return "#" + ((r << 16) + (g << 8) + b).toString(16).padStart(6, "0");
|
||||
}
|
||||
|
||||
function copyPreset(name: string) {
|
||||
const template =
|
||||
`
|
||||
{
|
||||
bgcol: "${settings.store.Primary}",
|
||||
accentcol: "${settings.store.Accent}",
|
||||
textcol: "${settings.store.Text}",
|
||||
brand: "${settings.store.Brand}",
|
||||
name: "${name}"
|
||||
}
|
||||
`;
|
||||
if (Clipboard.SUPPORTS_COPY) {
|
||||
Clipboard.copy(template);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function CopyPresetComponent() {
|
||||
|
||||
const [inputtedName, setInputtedName] = useState("");
|
||||
return (
|
||||
<>
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle>{"Preset name"}</Forms.FormTitle>
|
||||
<TextInput
|
||||
type="text"
|
||||
value={inputtedName}
|
||||
onChange={setInputtedName}
|
||||
placeholder={"Enter a name"}
|
||||
/>
|
||||
</Forms.FormSection>
|
||||
<Button onClick={() => {
|
||||
copyPreset(inputtedName);
|
||||
}}>Copy preset</Button>
|
||||
<Button onClick={() => {
|
||||
generateAndApplyProceduralTheme();
|
||||
}}>Generate Random</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
|
||||
|
||||
export function generateAndApplyProceduralTheme() {
|
||||
|
||||
const randomBackgroundColor = generateRandomColorHex();
|
||||
const accentColor = darkenColorHex(randomBackgroundColor);
|
||||
const textColor = "ddd0d0";
|
||||
const brandColor = saturateColorHex(randomBackgroundColor);
|
||||
|
||||
settings.store.Primary = randomBackgroundColor;
|
||||
settings.store.Accent = accentColor;
|
||||
settings.store.Text = textColor;
|
||||
settings.store.Brand = brandColor;
|
||||
|
||||
injectCSS();
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
serverListAnim: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Toggles if the server list hides when not hovered",
|
||||
default: false,
|
||||
onChange: () => injectCSS()
|
||||
},
|
||||
memberListAnim: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Toggles if the member list hides when not hovered",
|
||||
default: true,
|
||||
onChange: () => injectCSS()
|
||||
},
|
||||
privacyBlur: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Blurs potentially sensitive information when not tabbed in",
|
||||
default: false,
|
||||
onChange: () => injectCSS()
|
||||
},
|
||||
tooltips: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "If tooltips are displayed in the client",
|
||||
default: false,
|
||||
onChange: () => injectCSS()
|
||||
},
|
||||
customFont: {
|
||||
type: OptionType.STRING,
|
||||
description: "The google fonts @import for a custom font (blank to disable)",
|
||||
default: "@import url('https://fonts.googleapis.com/css2?family=Poppins&wght@500&display=swap');",
|
||||
onChange: injectCSS
|
||||
},
|
||||
animationSpeed: {
|
||||
type: OptionType.STRING,
|
||||
description: "The speed of animations",
|
||||
default: "0.2",
|
||||
onChange: injectCSS
|
||||
},
|
||||
colorsEnabled: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Whether or not to enable theming",
|
||||
onChange: () => injectCSS()
|
||||
},
|
||||
ColorPreset: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Some pre-made color presets (more soon hopefully)",
|
||||
options: themes.map(theme => ({ label: theme.name, value: themes.indexOf(theme), default: themes.indexOf(theme) === 0 })),
|
||||
onChange: () => { LoadPreset(); }
|
||||
},
|
||||
Primary: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "",
|
||||
default: "000000",
|
||||
component: () => <ColorPick propertyname="Primary" />
|
||||
},
|
||||
Accent: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "",
|
||||
default: "313338",
|
||||
component: () => <ColorPick propertyname="Accent" />
|
||||
},
|
||||
Text: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "",
|
||||
default: "ffffff",
|
||||
component: () => <ColorPick propertyname="Text" />
|
||||
},
|
||||
Brand: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "",
|
||||
default: "ffffff",
|
||||
component: () => <ColorPick propertyname="Brand" />
|
||||
},
|
||||
pastelStatuses: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Changes the status colors to be more pastel (fits with the catppuccin presets)",
|
||||
default: true,
|
||||
onChange: () => injectCSS()
|
||||
},
|
||||
DevTools:
|
||||
{
|
||||
type: OptionType.COMPONENT,
|
||||
description: "meow",
|
||||
default: "",
|
||||
component: () => <CopyPresetComponent />
|
||||
},
|
||||
ExportTheme:
|
||||
{
|
||||
type: OptionType.COMPONENT,
|
||||
description: "",
|
||||
default: "",
|
||||
component: () => <Button onClick={() => {
|
||||
copyCSS();
|
||||
Toasts.show({
|
||||
id: Toasts.genId(),
|
||||
message: "Successfully copied theme!",
|
||||
type: Toasts.Type.SUCCESS
|
||||
});
|
||||
}} >Copy The CSS for your current configuration.</Button>
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export function ColorPick({ propertyname }: { propertyname: string; }) {
|
||||
return (
|
||||
|
||||
<div className="color-options-container">
|
||||
<Forms.FormTitle tag="h3">{propertyname}</Forms.FormTitle>
|
||||
|
||||
<ColorPicker
|
||||
color={parseInt(settings.store[propertyname], 16)}
|
||||
onChange={color => {
|
||||
const hexColor = color.toString(16).padStart(6, "0");
|
||||
settings.store[propertyname] = hexColor;
|
||||
injectCSS();
|
||||
}
|
||||
}
|
||||
showEyeDropper={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function copyCSS() {
|
||||
if (Clipboard.SUPPORTS_COPY) {
|
||||
Clipboard.copy(getCSS(parseFontContent()));
|
||||
}
|
||||
}
|
||||
|
||||
function parseFontContent() {
|
||||
const fontRegex = /family=([^&;,:]+)/;
|
||||
const customFontString: string = Settings.plugins.Glide.customFont;
|
||||
if (customFontString == null) { return; }
|
||||
const fontNameMatch: RegExpExecArray | null = fontRegex.exec(customFontString);
|
||||
const fontName = fontNameMatch ? fontNameMatch[1].replace(/[^a-zA-Z0-9]+/g, " ") : "";
|
||||
return fontName;
|
||||
}
|
||||
function injectCSS() {
|
||||
if (Settings.plugins.Glide.enabled) {
|
||||
|
||||
const fontName = parseFontContent();
|
||||
const theCSS = getCSS(fontName);
|
||||
|
||||
const elementToRemove = document.getElementById("GlideStyleInjection");
|
||||
if (elementToRemove) {
|
||||
elementToRemove.remove();
|
||||
}
|
||||
const styleElement = document.createElement("style");
|
||||
styleElement.id = "GlideStyleInjection";
|
||||
styleElement.textContent = theCSS;
|
||||
document.documentElement.appendChild(styleElement);
|
||||
}
|
||||
}
|
||||
|
||||
function getCSS(fontName) {
|
||||
return `
|
||||
/* IMPORTS */
|
||||
|
||||
/* Fonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Nunito&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Fira+Code&display=swap');
|
||||
${Settings.plugins.Glide.customFont}
|
||||
|
||||
/*Settings things*/
|
||||
/*Server list animation*/
|
||||
${Settings.plugins.Glide.serverListAnim ? `
|
||||
.guilds_a4d4d9 {
|
||||
width: 10px;
|
||||
transition: width var(--animspeed) ease 0.1s, opacity var(--animspeed) ease 0.1s;
|
||||
opacity: 0;
|
||||
}
|
||||
.guilds_a4d4d9:hover {
|
||||
width: 65px;
|
||||
opacity: 100;
|
||||
}
|
||||
` : ""}
|
||||
/*Member list anim toggle*/
|
||||
${Settings.plugins.Glide.memberListAnim ? `
|
||||
.container_cbd271
|
||||
{
|
||||
width: 60px;
|
||||
opacity: 0.2;
|
||||
transition: width var(--animspeed) ease 0.1s, opacity var(--animspeed) ease 0.1s;
|
||||
|
||||
}
|
||||
.container_cbd271:hover
|
||||
{
|
||||
width: 250px;
|
||||
opacity: 1;
|
||||
}
|
||||
` : ""}
|
||||
/*Privacy blur*/
|
||||
${Settings.plugins.Glide.privacyBlur ? `
|
||||
.header_f9f2ca,
|
||||
.container_ee69e0,
|
||||
.title_a7d72e,
|
||||
.layout_ec8679,
|
||||
[aria-label="Members"] {
|
||||
filter: blur(0);
|
||||
transition: filter 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
body:not(:hover) .header_f9f2ca,
|
||||
body:not(:hover) .container_ee69e0,
|
||||
body:not(:hover) .title_a7d72e,
|
||||
body:not(:hover) [aria-label="Members"],
|
||||
body:not(:hover) .layout_ec8679 {
|
||||
filter: blur(5px);
|
||||
}
|
||||
` : ""}
|
||||
/*Tooltips*/
|
||||
[class*="tooltip"]
|
||||
{
|
||||
${!Settings.plugins.Glide.tooltips ? "display: none !important;" : ""}
|
||||
}
|
||||
/*Root configs*/
|
||||
:root
|
||||
{
|
||||
--animspeed: ${Settings.plugins.Glide.animationSpeed + "s"};
|
||||
--font-primary: ${(fontName.length > 0 ? fontName : "Nunito")};
|
||||
${Settings.plugins.Glide.colorsEnabled ? `
|
||||
--accent: #${Settings.plugins.Glide.Accent};
|
||||
--bgcol: #${Settings.plugins.Glide.Primary};
|
||||
--text: #${Settings.plugins.Glide.Text};
|
||||
--brand: #${Settings.plugins.Glide.Brand};
|
||||
--mutedtext: ${mute(Settings.plugins.Glide.Text, 20)};
|
||||
--mutedbrand: ${mute(Settings.plugins.Glide.Brand, 10)};
|
||||
--mutedaccent: ${mute(Settings.plugins.Glide.Accent, 10)};
|
||||
` : ""}
|
||||
}
|
||||
:root
|
||||
{
|
||||
|
||||
/*VARIABLES*/
|
||||
|
||||
/*editable variables. Feel free to mess around with these to your hearts content, i recommend not editing the logic variables unless you have an understanding of css*/
|
||||
--glowcol: rgba(0, 0, 0, 0);
|
||||
--mentioncol: rgb(0, 0, 0);
|
||||
--mentionhighlightcol: rgb(0, 0, 0);
|
||||
--linkcol: rgb(95, 231, 255);
|
||||
--highlightcol: rgb(95, 231, 255);
|
||||
|
||||
|
||||
|
||||
/*COLOR ASSIGNING (most of these probably effect more than whats commented)*/
|
||||
${Settings.plugins.Glide.colorsEnabled ? `
|
||||
/*accent based*/
|
||||
|
||||
/*buttons*/
|
||||
--button-secondary-background: var(--accent);
|
||||
|
||||
/*also buttons*/
|
||||
--brand-experiment: var(--brand);
|
||||
--brand-experiment-560: var(--brand);
|
||||
--brand-500: var(--brand);
|
||||
|
||||
/*message bar*/
|
||||
--channeltextarea-background: var(--accent);
|
||||
|
||||
/*selected dm background*/
|
||||
--background-modifier-selected: var(--accent);
|
||||
|
||||
/*emoji autofill*/
|
||||
--primary-630: var(--accent);
|
||||
|
||||
/*plugin grid square and nitro shop*/
|
||||
--background-secondary-alt: var(--accent);
|
||||
|
||||
/*modal background, self explanatory*/
|
||||
--modal-background: var(--accent);
|
||||
|
||||
/*color of the background of mention text*/
|
||||
--mention-background: var(--accent);
|
||||
--input-background: var(--accent);
|
||||
|
||||
/*the side profile thingy*/
|
||||
--profile-body-background-color: var(--accent);
|
||||
|
||||
/*the weird hover thing idk*/
|
||||
--background-modifier-hover: var(--mutedaccent) !important;
|
||||
|
||||
|
||||
/*background based*/
|
||||
|
||||
/*primary color, self explanatory*/
|
||||
--background-primary: var(--bgcol);
|
||||
|
||||
/*dm list*/
|
||||
--background-secondary: var(--bgcol);
|
||||
|
||||
/*outer frame and search background*/
|
||||
--background-tertiary: var(--bgcol);
|
||||
|
||||
|
||||
/*friends header bar*/
|
||||
--bg-overlay-2: var(--bgcol);
|
||||
|
||||
/*user panel*/
|
||||
--bg-overlay-1: var(--bgcol);
|
||||
|
||||
/*call thingy*/
|
||||
--bg-overlay-app-frame: var(--bgcol);
|
||||
|
||||
/*shop*/
|
||||
--background-mentioned-hover: var(--bgcol) !important;
|
||||
--background-mentioned: var(--bgcol) !important;
|
||||
|
||||
|
||||
|
||||
|
||||
/*other*/
|
||||
|
||||
/*mention side line color color*/
|
||||
--info-warning-foreground: var(--mentionhighlightcol);
|
||||
|
||||
/*text color of mention text*/
|
||||
--mention-foreground: white;
|
||||
|
||||
/*Link color*/
|
||||
--text-link: var(--linkcol);
|
||||
--header-primary: var(--text);
|
||||
--header-secondary: var(--text);
|
||||
--font-display: var(--text);
|
||||
--text-normal: var(--text);
|
||||
--text-muted: var(--mutedtext);
|
||||
--channels-default: var(--mutedtext);
|
||||
--interactive-normal: var(--text) !important;
|
||||
--white-500: var(--text);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*EXTRA COLORS*/
|
||||
|
||||
[class*="tooltipPrimary__"]
|
||||
{
|
||||
background-color: var(--mutedaccent) !important;
|
||||
}
|
||||
[class*="tooltipPointer_"]
|
||||
{
|
||||
border-top-color: var(--mutedaccent) !important;
|
||||
}
|
||||
/*sorry, forgot to document what these are when i was adding them*/
|
||||
.inspector_c3120f, .scroller_d53d65, .unicodeShortcut_dfa278
|
||||
{
|
||||
background-color: var(--bgcol);
|
||||
}
|
||||
.inner_effbe2
|
||||
{
|
||||
background-color: var(--accent);
|
||||
}
|
||||
/*recolor embeds*/
|
||||
[class^="embedWrap"]
|
||||
{
|
||||
border-color: var(--accent) !important;
|
||||
background: var(--accent);
|
||||
}
|
||||
/*emoji menu recolor*/
|
||||
.contentWrapper_af5dbb, .header_a3bc57
|
||||
{
|
||||
background-color: var(--bgcol);
|
||||
}
|
||||
/*vc background recolor*/
|
||||
.root_dd069c
|
||||
{
|
||||
background-color: var(--bgcol);
|
||||
}
|
||||
|
||||
/*Fix the forum page*/
|
||||
/*Set the bg color*/
|
||||
.container_a6d69a
|
||||
{
|
||||
background-color: var(--bgcol);
|
||||
}
|
||||
/*Recolor the posts to the accent*/
|
||||
.container_d331f1
|
||||
{
|
||||
background-color: var(--accent);
|
||||
}
|
||||
|
||||
/*Recolor the background of stickers in the sticker picker that dont take up the full 1:1 ratio*/
|
||||
[id^="sticker-picker-grid"]
|
||||
{
|
||||
background-color: var(--bgcol);
|
||||
}
|
||||
/* profile sidebar*/
|
||||
[class="none_eed6a8 scrollerBase_eed6a8"]
|
||||
{
|
||||
background-color: var(--bgcol) !important;
|
||||
}
|
||||
/*Recolor the emoji, gif, and sticker picker selected button*/
|
||||
.navButtonActive_af5dbb, .stickerCategoryGenericSelected_a7a485, .categoryItemDefaultCategorySelected_dfa278
|
||||
{
|
||||
background-color: var(--accent) !important;
|
||||
}
|
||||
|
||||
/*side profile bar*/
|
||||
[class="none_c49869 scrollerBase_c49869"]
|
||||
{
|
||||
background-color: var(--bgcol) !important;
|
||||
}
|
||||
.userPanelOverlayBackground_a2b6ae, .badgeList_ab525a
|
||||
{
|
||||
background-color: var(--accent) !important;
|
||||
border-radius: 15px !important;
|
||||
}
|
||||
/*uhhhhhhhhhhhhhhh*/
|
||||
.headerText_c47fa9
|
||||
{
|
||||
color: var(--text) !important;
|
||||
}
|
||||
/*message bar placeholder*/
|
||||
.placeholder_a552a6
|
||||
{
|
||||
color: var(--mutedtext) !important
|
||||
}
|
||||
.menu_d90b3d
|
||||
{
|
||||
background: var(--accent) !important;
|
||||
}
|
||||
.messageGroupWrapper_ac90a2, .header_ac90a2
|
||||
{
|
||||
background-color: var(--primary);
|
||||
}
|
||||
${settings.store.pastelStatuses ?
|
||||
`
|
||||
/*Pastel statuses*/
|
||||
rect[fill='#23a55a'], svg[fill='#23a55a'] {
|
||||
fill: #80c968 !important;
|
||||
}
|
||||
rect[fill='#f0b232'], svg[fill='#f0b232'] {
|
||||
fill: #e7ca45 !important;
|
||||
}
|
||||
rect[fill='#f23f43'], svg[fill='#f23f43'] {
|
||||
fill: #e0526c !important;
|
||||
}
|
||||
rect[fill='#80848e'], svg[fill='#80848e'] {
|
||||
fill: #696e88 !important;
|
||||
}
|
||||
rect[fill='#593695'], svg[fill='#593695'] {
|
||||
fill: #ac7de6 important;
|
||||
}
|
||||
` : ""}
|
||||
.name_d8bfb3
|
||||
{
|
||||
color: var(--text) !important;
|
||||
}
|
||||
.unread_d8bfb3
|
||||
{
|
||||
background-color: var(--text) !important;
|
||||
}` : ""}
|
||||
|
||||
/*ROUNDING (rounding)*/
|
||||
|
||||
/*round message bar, some buttons, dm list button, new messages notif bar, channel buttons, emoji menu search bar, context menus, account connections(in that order)*/
|
||||
.scrollableContainer_bdf0de, .button_dd4f85, .interactive_f5eb4b, .newMessagesBar_cf58b5, .link_d8bfb3, .searchBar_c6ee36, .menu_d90b3d, .connectedAccountContainer_ab12c6
|
||||
{
|
||||
border-radius: 25px;
|
||||
}
|
||||
/*round emojis seperately (and spotify activity icons)*/
|
||||
[data-type="emoji"], [class*="Spotify"]
|
||||
{
|
||||
border-radius: 5px;
|
||||
}
|
||||
/*round gifs and stickers (and maybe images idk lmao), and embeds*/
|
||||
[class^="imageWr"], [data-type="sticker"], [class^="embed"]
|
||||
{
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.item_d90b3d
|
||||
{
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*slightly move messages right when hovered*/
|
||||
.cozyMessage_d5deea
|
||||
{
|
||||
left: 0px;
|
||||
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
.cozyMessage_d5deea:hover
|
||||
{
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
|
||||
/*CONTENT (Typically changing values or hiding elements)*/
|
||||
|
||||
/*remove status text in user thing*/
|
||||
.panelSubtextContainer_b2ca13
|
||||
{
|
||||
display: none !important;
|
||||
}
|
||||
/*Hide most of the ugly useless scrollbars*/
|
||||
::-webkit-scrollbar
|
||||
{
|
||||
display:none;
|
||||
}
|
||||
|
||||
|
||||
/*Hide user profile button, the dm favourite, dm close, support, gift buttons, the now playing column, and the channel + favourite icons*/
|
||||
[aria-label="Hide User Profile"], .favoriteIcon_c91bad, .closeButton_c91bad, [href="https://support.discord.com"], .nowPlayingColumn_c2739c, button[aria-label="Send a gift"], .icon_d8bfb3, .iconContainer_d8bfb3
|
||||
{
|
||||
display :none;
|
||||
}
|
||||
|
||||
/*yeet the shitty nitro and family link tabs that no one likes*/
|
||||
.channel_c91bad[aria-posinset="2"],
|
||||
.familyCenterLinkButton_f0963d
|
||||
{
|
||||
display: none;
|
||||
|
||||
}
|
||||
/*Remove the buttons at the bottom of the user pop out (seriously, who wanted this?)*/
|
||||
.addFriendSection__413d3
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*No more useless spotify activity header*/
|
||||
.headerContainer_c1d9fd
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
/*hide sidebar connections*/
|
||||
.profilePanelConnections_b433b4
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
/*pad the message bar right slightly. Not sure what caused the buttons to flow out of it, might be something in the theme :shrug:*/
|
||||
.inner_bdf0de
|
||||
{
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
/*Yeet hypesquad badges (who cares)*/
|
||||
[aria-label*="HypeSquad"]
|
||||
{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*Hide icon on file uploading status*/
|
||||
.icon_f46c86
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*hide the play button while a soundmoji is playing*/
|
||||
.playing_bf9443 [viewBox="0 0 24 24"]
|
||||
{
|
||||
display:none;
|
||||
}
|
||||
/*hide the public servers button on member list*/
|
||||
[aria-label="Explore Discoverable Servers"]
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
/*fix context menu being not symmetrical*/
|
||||
.scroller_d90b3d
|
||||
{
|
||||
padding: 6px 8px !important;
|
||||
}
|
||||
/*Hide the icon that displays what platform the user is listening with on spotify status*/
|
||||
.platformIcon_c1d9fd
|
||||
{
|
||||
display: none !important;
|
||||
}
|
||||
/*hide the album name on spotify statuses (who cares)*/
|
||||
[class="state_c1d9fd ellipsis_c1d9fd textRow_c1d9fd"]
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
/*space the connections a bit better*/
|
||||
.userInfoSection_a24910
|
||||
{
|
||||
margin-bottom: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
/*Space channels*/
|
||||
.containerDefault_f6f816
|
||||
{
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
/*round banners in profile popout*/
|
||||
.banner_d5fdb1:not(.panelBanner_c3e427)
|
||||
{
|
||||
border-radius: 20px;
|
||||
}
|
||||
/*round the user popout*/
|
||||
.userPopoutOuter_c69a7b
|
||||
{
|
||||
border-radius: 25px;
|
||||
}
|
||||
/*round the inner profile popout*/
|
||||
[class="userPopoutInner_c69a7b userProfileInner_c69a7b userProfileInnerThemedWithBanner_c69a7b"]::before
|
||||
{
|
||||
border-radius: 20px;
|
||||
}
|
||||
.footer_be6801
|
||||
{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*STYLING (Modification of content to fit the theme)*/
|
||||
|
||||
/*Round and scale down the users banner*/
|
||||
.panelBanner_c3e427
|
||||
{
|
||||
border-radius: 20px;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
/*add a soft glow to message bar contents, user panel, dms, channel names (in that order)*/
|
||||
.inner_bdf0de .layout_ec8679, .name_d8bfb3
|
||||
{
|
||||
filter: drop-shadow(0px 0px 3px var(--glowcol));
|
||||
}
|
||||
[type="button"]
|
||||
{
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
[type="button"]:hover
|
||||
{
|
||||
filter: drop-shadow(0px 0px 3px var(--glowcol));
|
||||
}
|
||||
|
||||
/*Change the font*/
|
||||
:root
|
||||
{
|
||||
--font-code: "Fira Code";
|
||||
}
|
||||
|
||||
/*Round all status symbols. basically does what that one plugin does but easier (disabled because of a bug)
|
||||
.pointerEvents_c51b4e
|
||||
{
|
||||
mask: url(#svg-mask-status-online);
|
||||
}
|
||||
*/
|
||||
|
||||
/*pfp uploader crosshair*/
|
||||
.overlayAvatar_ba5b9e
|
||||
{
|
||||
background-image: url(https://raw.githubusercontent.com/Equicord/Equicord/main/src/equicordplugins/glide/crosshair.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position-x: 50%;
|
||||
background-position-y: 50%;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
/*change highlighted text color*/
|
||||
::selection
|
||||
{
|
||||
color: inherit;
|
||||
background-color: transparent;
|
||||
text-shadow: 0px 0px 2px var(--highlightcol);
|
||||
}
|
||||
/*hide the line between connections and note*/
|
||||
[class="connectedAccounts_ab12c6 userInfoSection_a24910"]
|
||||
{
|
||||
border-top: transparent !important;
|
||||
}
|
||||
.container_cebd1c:not(.checked_cebd1c)
|
||||
{
|
||||
background-color: var(--mutedbrand) !important;
|
||||
}
|
||||
.checked_cebd1c
|
||||
{
|
||||
background-color: var(--brand) !important;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "Glide",
|
||||
description: "A sleek, rounded theme for discord.",
|
||||
authors: [Devs.Samwich],
|
||||
settings,
|
||||
start() {
|
||||
injectCSS();
|
||||
},
|
||||
stop() {
|
||||
const injectedStyle = document.getElementById("GlideStyleInjection");
|
||||
if (injectedStyle) {
|
||||
injectedStyle.remove();
|
||||
}
|
||||
},
|
||||
startAt: StartAt.DOMContentLoaded,
|
||||
// preview thing, kinda low effort but eh
|
||||
settingsAboutComponent: () => <img src="https://cdn.nest.rip/uploads/97fdf6c1-764c-4445-9422-d3d52af7434c.webp" style={{ width: "568px", borderRadius: "30px" }} alt=""></img>
|
||||
});
|
164
src/equicordplugins/glide/themeDefinitions.tsx
Normal file
164
src/equicordplugins/glide/themeDefinitions.tsx
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ThemePreset } from ".";
|
||||
|
||||
export const themes: ThemePreset[] = [
|
||||
{
|
||||
bgcol: "000000",
|
||||
accentcol: "020202",
|
||||
textcol: "c0d5e4",
|
||||
brand: "070707",
|
||||
name: "Amoled"
|
||||
},
|
||||
{
|
||||
bgcol: "0e2936",
|
||||
accentcol: "0c2430",
|
||||
textcol: "99b0bd",
|
||||
brand: "124057",
|
||||
name: "Solar"
|
||||
},
|
||||
{
|
||||
bgcol: "0e0e36",
|
||||
accentcol: "0e0c30",
|
||||
textcol: "bdbfd8",
|
||||
brand: "171750",
|
||||
name: "Indigo"
|
||||
},
|
||||
{
|
||||
bgcol: "8a2b5f",
|
||||
accentcol: "812658",
|
||||
textcol: "ffedfb",
|
||||
brand: "b23982",
|
||||
name: "Grapefruit"
|
||||
},
|
||||
{
|
||||
bgcol: "410b05",
|
||||
accentcol: "360803",
|
||||
textcol: "f8e6e6",
|
||||
brand: "681109",
|
||||
name: "Crimson"
|
||||
},
|
||||
{
|
||||
bgcol: "184e66",
|
||||
accentcol: "215a72",
|
||||
textcol: "d0efff",
|
||||
brand: "2d718f",
|
||||
name: "Azure"
|
||||
},
|
||||
{
|
||||
bgcol: "1d091a",
|
||||
accentcol: "240d21",
|
||||
textcol: "f3e1f0",
|
||||
brand: "411837",
|
||||
name: "Blackberry"
|
||||
},
|
||||
{
|
||||
bgcol: "1f073b",
|
||||
accentcol: "250b44",
|
||||
textcol: "dfd7e9",
|
||||
brand: "340d63",
|
||||
name: "Porple"
|
||||
},
|
||||
{
|
||||
bgcol: "0a0a0a",
|
||||
accentcol: "0f0f0f",
|
||||
textcol: "c9c9c9",
|
||||
brand: "0a0a0a",
|
||||
name: "Charcoal"
|
||||
},
|
||||
{
|
||||
bgcol: "00345b",
|
||||
accentcol: "002f53",
|
||||
textcol: "e7d8df",
|
||||
brand: "944068",
|
||||
name: "Lofi Pop"
|
||||
},
|
||||
{
|
||||
bgcol: "471b05",
|
||||
accentcol: "4e2009",
|
||||
textcol: "ffffff",
|
||||
brand: "903e14",
|
||||
name: "Oaken"
|
||||
},
|
||||
{
|
||||
bgcol: "040b2b",
|
||||
accentcol: "000626",
|
||||
textcol: "ddd0d0",
|
||||
brand: "040b2b",
|
||||
name: "Deep Blue"
|
||||
},
|
||||
{
|
||||
bgcol: "32464a",
|
||||
accentcol: "2d4145",
|
||||
textcol: "ddd0d0",
|
||||
brand: "32464a",
|
||||
name: "Steel Blue"
|
||||
},
|
||||
{
|
||||
bgcol: "31031f",
|
||||
accentcol: "2c001a",
|
||||
textcol: "ddd0d0",
|
||||
brand: "31031f",
|
||||
name: "Velvet"
|
||||
},
|
||||
{
|
||||
bgcol: "22111f",
|
||||
accentcol: "1d0c1a",
|
||||
textcol: "ddd0d0",
|
||||
brand: "22111f",
|
||||
name: "Really Oddly Depressed Purple"
|
||||
},
|
||||
{
|
||||
bgcol: "2b3959",
|
||||
accentcol: "263454",
|
||||
textcol: "ddd0d0",
|
||||
brand: "2b3959",
|
||||
name: "Light Sky"
|
||||
},
|
||||
{
|
||||
bgcol: "06403d",
|
||||
accentcol: "013b38",
|
||||
textcol: "ddd0d0",
|
||||
brand: "06403d",
|
||||
name: "Tealish"
|
||||
},
|
||||
{
|
||||
bgcol: "273b0b",
|
||||
accentcol: "223606",
|
||||
textcol: "ddd0d0",
|
||||
brand: "273b0b",
|
||||
name: "Leaf (or a tree perhaps)"
|
||||
},
|
||||
{
|
||||
bgcol: "1a2022",
|
||||
accentcol: "151b1d",
|
||||
textcol: "ddd0d0",
|
||||
brand: "1a2022",
|
||||
name: "Steel"
|
||||
},
|
||||
{
|
||||
bgcol: "1e1e2e",
|
||||
accentcol: "181825",
|
||||
textcol: "cdd6f4",
|
||||
brand: "45475a",
|
||||
name: "Catppuccin Mocha"
|
||||
},
|
||||
{
|
||||
bgcol: "303446",
|
||||
accentcol: "292c3c",
|
||||
textcol: "c6d0f5",
|
||||
brand: "414559",
|
||||
name: "Catppuccin Frappé"
|
||||
},
|
||||
{
|
||||
bgcol: "6b422e",
|
||||
accentcol: "754b36",
|
||||
textcol: "ead9c9",
|
||||
brand: "8b5032",
|
||||
name: "Relax"
|
||||
}
|
||||
];
|
77
src/equicordplugins/googleThat/index.ts
Normal file
77
src/equicordplugins/googleThat/index.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ApplicationCommandOptionType, findOption } from "@api/Commands";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
function getMessage(opts) {
|
||||
const inputOption = findOption(opts, "input", "");
|
||||
|
||||
const queryURL = "" + searchEngines[settings.store.defaultEngine] + encodeURIComponent(inputOption);
|
||||
|
||||
if (settings.store.hyperlink) {
|
||||
return `[${inputOption}](${queryURL})`;
|
||||
}
|
||||
else {
|
||||
return queryURL;
|
||||
}
|
||||
}
|
||||
|
||||
const searchEngines = {
|
||||
"Google": "https://www.google.com/search?q=",
|
||||
"Bing": "https://www.bing.com/search?q=",
|
||||
"Yahoo": "https://search.yahoo.com/search?p=",
|
||||
"DuckDuckGo": "https://duckduckgo.com/?q=",
|
||||
"Baidu": "https://www.baidu.com/s?wd=",
|
||||
"Yandex": "https://yandex.com/search/?text=",
|
||||
"Ecosia": "https://www.ecosia.org/search?q=",
|
||||
"Ask": "https://www.ask.com/web?q=",
|
||||
"LetMeGoogleThatForYou": "https://letmegooglethat.com/?q="
|
||||
};
|
||||
|
||||
const settings = definePluginSettings({
|
||||
hyperlink: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "If the sent link should hyperlink with the query as the label",
|
||||
default: true
|
||||
},
|
||||
defaultEngine:
|
||||
{
|
||||
type: OptionType.SELECT,
|
||||
description: "The search engine to use",
|
||||
options: Object.keys(searchEngines).map((key, index) => ({
|
||||
label: key,
|
||||
value: key,
|
||||
default: index === 0
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "GoogleThat",
|
||||
description: "Adds a command to send a google search link to a query",
|
||||
authors: [Devs.Samwich],
|
||||
tags: ["search", "google", "query", "duckduckgo", "command"],
|
||||
settings,
|
||||
commands: [
|
||||
{
|
||||
name: "googlethat",
|
||||
description: "send a search engine link to a query",
|
||||
options: [
|
||||
{
|
||||
name: "input",
|
||||
description: "The search query",
|
||||
type: ApplicationCommandOptionType.STRING,
|
||||
required: true,
|
||||
}
|
||||
],
|
||||
execute: opts => ({
|
||||
content: getMessage(opts)
|
||||
}),
|
||||
}
|
||||
]
|
||||
});
|
148
src/equicordplugins/identity/index.tsx
Normal file
148
src/equicordplugins/identity/index.tsx
Normal file
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { DataStore } from "@api/index";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { PluginNative } from "@utils/types";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { Alerts, Button, FluxDispatcher, Forms, Toasts, UserProfileStore, UserStore } from "@webpack/common";
|
||||
const native = VencordNative.pluginHelpers.Identity as PluginNative<typeof import("./native")>;
|
||||
|
||||
const CustomizationSection = findComponentByCodeLazy(".customizationSectionBackground");
|
||||
|
||||
async function SetNewData() {
|
||||
const PersonData = JSON.parse(await native.RequestRandomUser());
|
||||
console.log(PersonData);
|
||||
|
||||
const pfpBase64 = JSON.parse(await native.ToBase64ImageUrl({ imgUrl: PersonData.picture.large })).data;
|
||||
|
||||
// holy moly
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR", avatar: pfpBase64 });
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_GLOBAL_NAME", globalName: `${PersonData.name.first} ${PersonData.name.last}` });
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_PRONOUNS", pronouns: `${PersonData.gender === "male" ? "he/him" : PersonData.gender === "female" ? "she/her" : ""}` });
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_BANNER", banner: null });
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_ACCENT_COLOR", color: null });
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_THEME_COLORS", themeColors: [null, null] });
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_BIO", bio: `Hello! I am ${PersonData.name.first} ${PersonData.name.last}` });
|
||||
}
|
||||
|
||||
async function SaveData() {
|
||||
const userData = UserProfileStore.getUserProfile(UserStore.getCurrentUser().id);
|
||||
|
||||
// the getUserProfile function doesn't return all the information we need, so we append the standard user object data to the end
|
||||
const extraUserObject: any = { extraUserObject: UserStore.getCurrentUser() };
|
||||
|
||||
const pfp = JSON.parse(await native.ToBase64ImageUrl({ imgUrl: `https://cdn.discordapp.com/avatars/${userData.userId}/${extraUserObject.extraUserObject.avatar}.webp?size=4096` })).data;
|
||||
const banner = JSON.parse(await native.ToBase64ImageUrl({ imgUrl: `https://cdn.discordapp.com/banners/${userData.userId}/${userData.banner}.webp?size=4096` })).data;
|
||||
|
||||
const fetchedBase64Data =
|
||||
{
|
||||
pfpBase64: pfp,
|
||||
bannerBase64: banner
|
||||
};
|
||||
|
||||
DataStore.set("identity-saved-base", JSON.stringify({ ...userData, ...extraUserObject, ...{ fetchedBase64Data: fetchedBase64Data } }));
|
||||
}
|
||||
|
||||
async function LoadData() {
|
||||
const userDataMaybeNull = await DataStore.get("identity-saved-base");
|
||||
if (!userDataMaybeNull) {
|
||||
Toasts.show({ message: "No saved base! Save one first.", id: Toasts.genId(), type: Toasts.Type.FAILURE });
|
||||
return;
|
||||
}
|
||||
|
||||
const userData = JSON.parse(userDataMaybeNull);
|
||||
|
||||
console.log(userData);
|
||||
|
||||
const { pfpBase64, bannerBase64 } = userData.fetchedBase64Data;
|
||||
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR", avatar: pfpBase64 });
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_GLOBAL_NAME", globalName: userData.extraUserObject.globalName });
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_PRONOUNS", pronouns: userData.pronouns });
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_BANNER", banner: bannerBase64 });
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_ACCENT_COLOR", color: userData.accentColor });
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_THEME_COLORS", themeColors: userData.themeColors });
|
||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SET_PENDING_BIO", bio: userData.bio });
|
||||
}
|
||||
|
||||
function ResetCard() {
|
||||
return (
|
||||
<CustomizationSection
|
||||
title={"Identity"}
|
||||
hasBackground={true}
|
||||
hideDivider={false}
|
||||
>
|
||||
<Flex>
|
||||
<Button
|
||||
onClick={() => {
|
||||
Alerts.show({
|
||||
title: "Hold on!",
|
||||
body: <div>
|
||||
<Forms.FormText>
|
||||
Saving your base profile will allow you to have a backup of your actual profile
|
||||
</Forms.FormText>
|
||||
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>
|
||||
If you save, it will overwrite your previous data.
|
||||
</Forms.FormText>
|
||||
</div>,
|
||||
confirmText: "Save Anyway",
|
||||
cancelText: "Cancel",
|
||||
onConfirm: SaveData
|
||||
});
|
||||
}}
|
||||
size={Button.Sizes.MEDIUM}
|
||||
>
|
||||
Save Base
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
Alerts.show({
|
||||
title: "Hold on!",
|
||||
body: <div>
|
||||
<Forms.FormText>
|
||||
Loading your base profile will restore your actual profile settings
|
||||
</Forms.FormText>
|
||||
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>
|
||||
If you load, it will overwrite your current profile configuration.
|
||||
</Forms.FormText>
|
||||
</div>,
|
||||
confirmText: "Load Anyway",
|
||||
cancelText: "Cancel",
|
||||
onConfirm: LoadData
|
||||
});
|
||||
}}
|
||||
size={Button.Sizes.MEDIUM}
|
||||
>
|
||||
Load Base
|
||||
</Button>
|
||||
<Button
|
||||
onClick={SetNewData}
|
||||
size={Button.Sizes.MEDIUM}
|
||||
>
|
||||
Randomise
|
||||
</Button>
|
||||
</Flex>
|
||||
</CustomizationSection>
|
||||
);
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "Identity",
|
||||
description: "Allows you to edit your profile to a random fake person with the click of a button",
|
||||
authors: [Devs.Samwich, EquicordDevs.port22exposed],
|
||||
ResetCard: ResetCard,
|
||||
patches: [
|
||||
{
|
||||
find: "DefaultCustomizationSections",
|
||||
replacement: {
|
||||
match: /(?<=#{intl::USER_SETTINGS_AVATAR_DECORATION}\)},"decoration"\),)/,
|
||||
replace: "$self.ResetCard(),"
|
||||
}
|
||||
},
|
||||
]
|
||||
});
|
29
src/equicordplugins/identity/native.ts
Normal file
29
src/equicordplugins/identity/native.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export async function RequestRandomUser() {
|
||||
const data = await fetch("https://randomuser.me/api").then(e => e.json());
|
||||
|
||||
return JSON.stringify(data.results[0]);
|
||||
}
|
||||
|
||||
export async function ToBase64ImageUrl(_, data) {
|
||||
const { imgUrl } = data;
|
||||
|
||||
try {
|
||||
const fetchImageUrl = await fetch(imgUrl);
|
||||
const responseArrBuffer = await fetchImageUrl.arrayBuffer();
|
||||
|
||||
const toBase64 =
|
||||
`data:${fetchImageUrl.headers.get("Content-Type") || "image/png"};base64,${Buffer.from(responseArrBuffer).toString("base64")}`;
|
||||
|
||||
return JSON.stringify({ data: toBase64 });
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error converting image to Base64:", error);
|
||||
return JSON.stringify({ error: "Failed to convert image to Base64" });
|
||||
}
|
||||
}
|
|
@ -5,12 +5,13 @@
|
|||
*/
|
||||
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Button, Forms, React, TabBar, Text, TextArea, Toasts } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import { convert as convertLineEP, isLineEmojiPackHtml, parseHtml as getLineEPFromHtml } from "../lineEmojis";
|
||||
import { convert as convertLineSP, isLineStickerPackHtml, parseHtml as getLineSPFromHtml } from "../lineStickers";
|
||||
import { convert as convertLineEP, getIdFromUrl as getLineEmojiPackIdFromUrl, getStickerPackById as getLineEmojiPackById, isLineEmojiPackHtml, parseHtml as getLineEPFromHtml } from "../lineEmojis";
|
||||
import { convert as convertLineSP, getIdFromUrl as getLineStickerPackIdFromUrl, getStickerPackById as getLineStickerPackById, isLineStickerPackHtml, parseHtml as getLineSPFromHtml } from "../lineStickers";
|
||||
import { isV1, migrate } from "../migrate-v1";
|
||||
import { deleteStickerPack, getStickerPack, getStickerPackMetas, saveStickerPack } from "../stickers";
|
||||
import { SettingsTabsKey, Sticker, StickerPack, StickerPackMeta } from "../types";
|
||||
|
@ -88,8 +89,9 @@ const StickerPackMetadata = ({ meta, hoveredStickerPackId, setHoveredStickerPack
|
|||
|
||||
export const Settings = () => {
|
||||
const [stickerPackMetas, setstickerPackMetas] = React.useState<StickerPackMeta[]>([]);
|
||||
const [addStickerUrl, setAddStickerUrl] = React.useState<string>("");
|
||||
const [addStickerHtml, setAddStickerHtml] = React.useState<string>("");
|
||||
const [tab, setTab] = React.useState<SettingsTabsKey>(SettingsTabsKey.ADD_STICKER_PACK_HTML);
|
||||
const [tab, setTab] = React.useState<SettingsTabsKey>(SettingsTabsKey.ADD_STICKER_PACK_URL);
|
||||
const [hoveredStickerPackId, setHoveredStickerPackId] = React.useState<string | null>(null);
|
||||
const [_isV1, setV1] = React.useState<boolean>(false);
|
||||
|
||||
|
@ -121,6 +123,114 @@ export const Settings = () => {
|
|||
}
|
||||
</TabBar>
|
||||
|
||||
{tab === SettingsTabsKey.ADD_STICKER_PACK_URL &&
|
||||
<div className="section">
|
||||
<Forms.FormTitle tag="h5">Add Sticker Pack from URL</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
<p>
|
||||
Currently LINE stickers/emojis supported only. <br />
|
||||
|
||||
Get Telegram stickers with <a href="#" onClick={() => VencordNative.native.openExternal("https://github.com/lekoOwO/MoreStickersConverter")}> MoreStickersConverter</a>.
|
||||
</p>
|
||||
</Forms.FormText>
|
||||
<Flex flexDirection="row" style={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}} >
|
||||
<span style={{
|
||||
flexGrow: 1
|
||||
}}>
|
||||
<CheckedTextInput
|
||||
value={addStickerUrl}
|
||||
onChange={setAddStickerUrl}
|
||||
validate={(v: string) => {
|
||||
try {
|
||||
getLineStickerPackIdFromUrl(v);
|
||||
return true;
|
||||
} catch (e: any) { }
|
||||
try {
|
||||
getLineEmojiPackIdFromUrl(v);
|
||||
return true;
|
||||
} catch (e: any) { }
|
||||
|
||||
return "Invalid URL";
|
||||
}}
|
||||
placeholder="Sticker Pack URL"
|
||||
/>
|
||||
</span>
|
||||
<Button
|
||||
size={Button.Sizes.SMALL}
|
||||
onClick={async e => {
|
||||
e.preventDefault();
|
||||
|
||||
let type: string = "";
|
||||
try {
|
||||
getLineStickerPackIdFromUrl(addStickerUrl);
|
||||
type = "LineStickerPack";
|
||||
} catch (e: any) { }
|
||||
|
||||
try {
|
||||
getLineEmojiPackIdFromUrl(addStickerUrl);
|
||||
type = "LineEmojiPack";
|
||||
} catch (e: any) { }
|
||||
|
||||
let errorMessage = "";
|
||||
switch (type) {
|
||||
case "LineStickerPack": {
|
||||
try {
|
||||
const id = getLineStickerPackIdFromUrl(addStickerUrl);
|
||||
const lineSP = await getLineStickerPackById(id);
|
||||
const stickerPack = convertLineSP(lineSP);
|
||||
await saveStickerPack(stickerPack);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
errorMessage = e.message;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "LineEmojiPack": {
|
||||
try {
|
||||
const id = getLineEmojiPackIdFromUrl(addStickerUrl);
|
||||
const lineEP = await getLineEmojiPackById(id);
|
||||
const stickerPack = convertLineEP(lineEP);
|
||||
await saveStickerPack(stickerPack);
|
||||
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
errorMessage = e.message;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setAddStickerUrl("");
|
||||
refreshStickerPackMetas();
|
||||
|
||||
if (errorMessage) {
|
||||
Toasts.show({
|
||||
message: errorMessage,
|
||||
type: Toasts.Type.FAILURE,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
duration: 1000
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Toasts.show({
|
||||
message: "Sticker Pack added",
|
||||
type: Toasts.Type.SUCCESS,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
duration: 1000
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}}
|
||||
>Insert</Button>
|
||||
</Flex>
|
||||
</div>
|
||||
}
|
||||
{tab === SettingsTabsKey.ADD_STICKER_PACK_HTML &&
|
||||
<div className="section">
|
||||
<Forms.FormTitle tag="h5">Add Sticker Pack from HTML</Forms.FormTitle>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { LineEmoji, LineEmojiPack, Sticker, StickerPack } from "./types";
|
||||
import { corsFetch } from "./utils";
|
||||
|
||||
export interface StickerCategory {
|
||||
title: string;
|
||||
|
@ -122,3 +123,16 @@ export function parseHtml(html: string): LineEmojiPack {
|
|||
export function isLineEmojiPackHtml(html: string): boolean {
|
||||
return html.includes("data-test=\"emoji-name-title\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stickers from LINE
|
||||
*
|
||||
* @param {string} id The id of the sticker pack.
|
||||
* @return {Promise<LineEmojiPack>} The sticker pack.
|
||||
*/
|
||||
export async function getStickerPackById(id: string, region = "en"): Promise<LineEmojiPack> {
|
||||
const res = await corsFetch(`https://store.line.me/emojishop/product/${id}/${region}`);
|
||||
const html = await res.text();
|
||||
|
||||
return parseHtml(html);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { LineSticker, LineStickerPack, Sticker, StickerPack } from "./types";
|
||||
import { corsFetch } from "./utils";
|
||||
|
||||
export interface StickerCategory {
|
||||
title: string;
|
||||
|
@ -122,3 +123,16 @@ export function parseHtml(html: string): LineStickerPack {
|
|||
export function isLineStickerPackHtml(html: string): boolean {
|
||||
return html.includes("data-test=\"sticker-name-title\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stickers from LINE
|
||||
*
|
||||
* @param {string} id The id of the sticker pack.
|
||||
* @return {Promise<LineStickerPack>} The sticker pack.
|
||||
*/
|
||||
export async function getStickerPackById(id: string, region = "en"): Promise<LineStickerPack> {
|
||||
const res = await corsFetch(`https://store.line.me/stickershop/product/${id}/${region}`);
|
||||
const html = await res.text();
|
||||
|
||||
return parseHtml(html);
|
||||
}
|
||||
|
|
77
src/equicordplugins/moreStickers/testdata.ts
Normal file
77
src/equicordplugins/moreStickers/testdata.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { setRecentStickers } from "./components";
|
||||
import {
|
||||
convert,
|
||||
getStickerPackById
|
||||
} from "./lineStickers";
|
||||
import {
|
||||
deleteStickerPack,
|
||||
getStickerPackMetas,
|
||||
saveStickerPack
|
||||
} from "./stickers";
|
||||
import { StickerPack } from "./types";
|
||||
|
||||
export async function initTest() {
|
||||
console.log("initTest.");
|
||||
|
||||
console.log("Clearing recent stickers.");
|
||||
setRecentStickers([]);
|
||||
|
||||
// Clear all sticker packs
|
||||
console.log("Clearing all sticker packs.");
|
||||
const stickerPackMetas = await getStickerPackMetas();
|
||||
for (const meta of stickerPackMetas) {
|
||||
await deleteStickerPack(meta.id);
|
||||
}
|
||||
|
||||
// Add test sticker packs
|
||||
console.log("Adding test sticker packs.");
|
||||
const lineStickerPackIds = [
|
||||
"22814489", // LV.47
|
||||
"22567773", // LV.46
|
||||
"22256215", // LV.45
|
||||
"21936635", // LV.44
|
||||
"21836565", // LV.43
|
||||
];
|
||||
const ps: Promise<StickerPack | null>[] = [];
|
||||
for (const id of lineStickerPackIds) {
|
||||
ps.push((async () => {
|
||||
try {
|
||||
const lsp = await getStickerPackById(id);
|
||||
const sp = convert(lsp);
|
||||
return sp;
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch sticker pack: " + id);
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
})());
|
||||
}
|
||||
const stickerPacks = (await Promise.all(ps)).filter(sp => sp !== null) as StickerPack[];
|
||||
|
||||
console.log("Saving test sticker packs.");
|
||||
for (const sp of stickerPacks) {
|
||||
await saveStickerPack(sp);
|
||||
}
|
||||
|
||||
console.log(await getStickerPackMetas());
|
||||
}
|
||||
|
||||
export async function clearTest() {
|
||||
console.log("clearTest.");
|
||||
|
||||
console.log("Clearing recent stickers.");
|
||||
setRecentStickers([]);
|
||||
|
||||
// Clear all sticker packs
|
||||
console.log("Clearing all sticker packs.");
|
||||
const stickerPackMetas = await getStickerPackMetas();
|
||||
for (const meta of stickerPackMetas) {
|
||||
await deleteStickerPack(meta.id);
|
||||
}
|
||||
}
|
|
@ -97,6 +97,7 @@ export interface PickerContentRowGrid {
|
|||
}
|
||||
|
||||
export enum SettingsTabsKey {
|
||||
ADD_STICKER_PACK_URL = "Add from URL",
|
||||
ADD_STICKER_PACK_HTML = "Add from HTML",
|
||||
ADD_STICKER_PACK_FILE = "Add from File",
|
||||
MISC = "Misc",
|
||||
|
|
|
@ -14,6 +14,16 @@ import { FFmpegState } from "./types";
|
|||
export const cl = classNameFactory("vc-more-stickers-");
|
||||
export const clPicker = (className: string, ...args: any[]) => cl("picker-" + className, ...args);
|
||||
|
||||
const CORS_PROXY = "https://corsproxy.io?";
|
||||
|
||||
function corsUrl(url: string | URL) {
|
||||
return CORS_PROXY + encodeURIComponent(url.toString());
|
||||
}
|
||||
|
||||
export function corsFetch(url: string | URL, init?: RequestInit | undefined) {
|
||||
return fetch(corsUrl(url), init);
|
||||
}
|
||||
|
||||
export class Mutex {
|
||||
current = Promise.resolve();
|
||||
lock() {
|
||||
|
|
27
src/equicordplugins/sekaiStickers/Components/Canvas.tsx
Normal file
27
src/equicordplugins/sekaiStickers/Components/Canvas.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from "@webpack/common";
|
||||
|
||||
const Canvas = props => {
|
||||
|
||||
const { draw, ...rest } = props;
|
||||
const canvasRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const canvas = canvasRef.current;
|
||||
// @ts-ignore
|
||||
const context = canvas.getContext("2d");
|
||||
|
||||
draw(context);
|
||||
|
||||
}, [draw]);
|
||||
|
||||
return <canvas ref={canvasRef} {...rest} />;
|
||||
};
|
||||
|
||||
export default Canvas;
|
51
src/equicordplugins/sekaiStickers/Components/Picker.tsx
Normal file
51
src/equicordplugins/sekaiStickers/Components/Picker.tsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Flex } from "@components/Flex";
|
||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
|
||||
import { React, ScrollerThin, Text, TextInput } from "@webpack/common";
|
||||
|
||||
import { characters } from "../characters.json";
|
||||
|
||||
export default function CharSelectModal({ modalProps, setCharacter }: { modalProps: ModalProps; setCharacter?: any; }) {
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
|
||||
const memoedSearchChar = React.useMemo(() => {
|
||||
const s = search.toLowerCase();
|
||||
return characters.map((c, index) => {
|
||||
if (
|
||||
s === c.id ||
|
||||
c.name.toLowerCase().includes(s) ||
|
||||
c.character.toLowerCase().includes(s)
|
||||
) {
|
||||
return (
|
||||
<img key={index} onClick={() => { modalProps.onClose(); setCharacter(index); }} src={`https://st.ayaka.one/img/${c.img}`} srcSet={`https://st.ayaka.one/img/${c.img}`} loading="lazy" />
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}, [search, characters]);
|
||||
return (
|
||||
<ModalRoot {...modalProps} size={ModalSize.DYNAMIC}>
|
||||
<ModalHeader>
|
||||
<Text variant="heading-lg/bold" style={{ flexGrow: 1 }}>Select character menu</Text>
|
||||
<ModalCloseButton onClick={modalProps.onClose} ></ModalCloseButton>
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<Flex flexDirection="column" style={{ paddingTop: 12 }}>
|
||||
<TextInput content="mafuyu" placeholder="Mafuyu" onChange={(e: string) => setSearch(e)} />
|
||||
<ScrollerThin style={{ height: 520 }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 330px)", rowGap: 6, columnGap: 5, gridTemplateRows: "repeat(3, 256px)" }}>
|
||||
{memoedSearchChar}
|
||||
</div>
|
||||
</ScrollerThin>
|
||||
</Flex>
|
||||
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Flex } from "@components/Flex";
|
||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { Button, ChannelStore, Forms, React, SelectedChannelStore, Slider, Switch, Text, TextArea, UploadHandler } from "@webpack/common";
|
||||
|
||||
import { characters } from "../characters.json";
|
||||
import Canvas from "./Canvas";
|
||||
import CharSelectModal from "./Picker";
|
||||
|
||||
export default function SekaiStickersModal({ modalProps, settings }: { modalProps: ModalProps; settings: any; }) {
|
||||
const [text, setText] = React.useState<string>("奏でーかわいい");
|
||||
const [character, setChracter] = React.useState<number>(49);
|
||||
const [fontSize, setFontSize] = React.useState<number>(characters[character].defaultText.s);
|
||||
const [rotate, setRotate] = React.useState<number>(characters[character].defaultText.r);
|
||||
const [curve, setCurve] = React.useState<boolean>(false);
|
||||
const [isImgLoaded, setImgLoaded] = React.useState<boolean>(false);
|
||||
const [position, setPosition] = React.useState<{ x: number, y: number; }>({ x: characters[character].defaultText.x, y: characters[character].defaultText.y });
|
||||
const [spaceSize, setSpaceSize] = React.useState<number>(1);
|
||||
let canvast!: HTMLCanvasElement;
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
img.src = "https://st.ayaka.one/img/" + characters[character].img;
|
||||
|
||||
React.useEffect(() => {
|
||||
setPosition({
|
||||
x: characters[character].defaultText.x,
|
||||
y: characters[character].defaultText.y
|
||||
});
|
||||
setFontSize(characters[character].defaultText.s);
|
||||
setRotate(characters[character].defaultText.r);
|
||||
setImgLoaded(false);
|
||||
}, [character]);
|
||||
|
||||
img.onload = () => { setImgLoaded(true); };
|
||||
const angle = (Math.PI * text.length) / 7;
|
||||
|
||||
const draw = ctx => {
|
||||
ctx.canvas.width = 296;
|
||||
ctx.canvas.height = 256;
|
||||
|
||||
if (isImgLoaded && document.fonts.check("12px YurukaStd")) {
|
||||
const hRatio = ctx.canvas.width / img.width;
|
||||
const vRatio = ctx.canvas.height / img.height;
|
||||
const ratio = Math.min(hRatio, vRatio);
|
||||
const centerShiftX = (ctx.canvas.width - img.width * ratio) / 2;
|
||||
const centerShiftY = (ctx.canvas.height - img.height * ratio) / 2;
|
||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
ctx.drawImage(
|
||||
img,
|
||||
0,
|
||||
0,
|
||||
img.width,
|
||||
img.height,
|
||||
centerShiftX,
|
||||
centerShiftY,
|
||||
img.width * ratio,
|
||||
img.height * ratio
|
||||
);
|
||||
ctx.font = `${fontSize}px YurukaStd, SSFangTangTi`;
|
||||
ctx.lineWidth = 9;
|
||||
ctx.save();
|
||||
|
||||
ctx.translate(position.x, position.y);
|
||||
ctx.rotate(rotate / 10);
|
||||
ctx.textAlign = "center";
|
||||
ctx.strokeStyle = "white";
|
||||
ctx.fillStyle = characters[character].color;
|
||||
const lines = text.split("\n");
|
||||
if (curve) {
|
||||
for (const line of lines) {
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
ctx.rotate(angle / line.length / 2.5);
|
||||
ctx.save();
|
||||
ctx.translate(0, -1 * fontSize * 3.5);
|
||||
ctx.strokeText(line[i], 0, -1 * spaceSize);
|
||||
ctx.fillText(line[i], 0, -1 * spaceSize);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0, k = 0; i < lines.length; i++) {
|
||||
ctx.strokeText(lines[i], 0, k);
|
||||
ctx.fillText(lines[i], 0, k);
|
||||
k += spaceSize;
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
canvast = ctx.canvas;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ModalRoot {...modalProps} size={ModalSize.DYNAMIC}>
|
||||
<ModalHeader>
|
||||
<Text variant="heading-lg/bold" style={{ flexGrow: 1 }}>Sekai Stickers</Text>
|
||||
<ModalCloseButton onClick={modalProps.onClose} ></ModalCloseButton>
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<Flex flexDirection="row" style={{ paddingTop: 12 }}>
|
||||
<div style={{ marginRight: 30 }}>
|
||||
<Canvas draw={draw} id="SekaiCard_Canvas" />
|
||||
<Forms.FormTitle>Text Y Pos</Forms.FormTitle>
|
||||
<Slider minValue={0} maxValue={256} asValueChanges={va => { va = Math.round(va); setPosition({ x: position.x, y: curve ? 256 + fontSize * 3 - va : 256 - va }); }} initialValue={curve ? 256 - position.y + fontSize * 3 : 256 - position.y} orientation={"vertical"} onValueRender={va => String(Math.round(va))} />
|
||||
<Forms.FormTitle>Text XZ Pos</Forms.FormTitle>
|
||||
<Slider minValue={0} maxValue={296} asValueChanges={va => { va = Math.round(va); setPosition({ y: position.y, x: va }); }} initialValue={position.x} orientation={"horizontal"} onValueRender={(v: number) => String(Math.round(v))} />
|
||||
</div>
|
||||
<div style={{ marginRight: 10, width: "30vw" }}>
|
||||
<Forms.FormTitle>Text</Forms.FormTitle>
|
||||
<TextArea onChange={setText} placeholder={text} rows={4} spellCheck={false} />
|
||||
<Forms.FormTitle>Rotation</Forms.FormTitle>
|
||||
<Slider markers={[-10, -5, 0, 5, 10]} stickToMarkers={false} minValue={-10} maxValue={10} asValueChanges={val => setRotate(val)} initialValue={rotate} keyboardStep={0.2} orientation={"horizontal"} onValueRender={(v: number) => String(v.toFixed(2))} />
|
||||
<Forms.FormTitle>Font Size</Forms.FormTitle>
|
||||
<Slider minValue={10} asValueChanges={val => setFontSize(Math.round(val))} maxValue={100} initialValue={fontSize} keyboardStep={1} orientation={"horizontal"} onValueRender={(v: number) => String(Math.round(v))} />
|
||||
<Forms.FormTitle>Spacing</Forms.FormTitle>
|
||||
<Slider markers={[18, 36, 72, 100]} stickToMarkers={false} minValue={18} maxValue={100} initialValue={spaceSize} asValueChanges={e => setSpaceSize(e)} onValueRender={e => String(Math.round(e))} />
|
||||
<Switch value={curve} onChange={val => setCurve(val)}>Enable curve</Switch>
|
||||
<Button onClick={() => openModal(props => <CharSelectModal modalProps={props} setCharacter={setChracter} />)}>Switch Character</Button>
|
||||
</div>
|
||||
</Flex>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<Flex flexDirection="row" style={{ gap: 12 }}>
|
||||
<Button onClick={() => {
|
||||
if (settings.store.AutoCloseModal) modalProps.onClose();
|
||||
canvast.toBlob(blob => {
|
||||
const file = new File([blob as Blob], `${characters[character].character}-sekai_cards.png`, { type: "image/png" });
|
||||
UploadHandler.promptToUpload([file], ChannelStore.getChannel(SelectedChannelStore.getChannelId()), 0);
|
||||
});
|
||||
}}>Upload as Attachment</Button>
|
||||
</Flex>
|
||||
</ModalFooter>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
5035
src/equicordplugins/sekaiStickers/characters.json.ts
Normal file
5035
src/equicordplugins/sekaiStickers/characters.json.ts
Normal file
File diff suppressed because it is too large
Load diff
53
src/equicordplugins/sekaiStickers/index.tsx
Normal file
53
src/equicordplugins/sekaiStickers/index.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, ChatBarButtonFactory, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
import SekaiStickersModal from "./Components/SekaiStickersModal";
|
||||
import { kanadeSvg } from "./kanade.svg";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
AutoCloseModal: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Auto close modal when done",
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
const SekaiStickerChatButton: ChatBarButtonFactory = () => {
|
||||
return (
|
||||
<ChatBarButton onClick={() => openModal(props => <SekaiStickersModal modalProps={props} settings={settings} />)} tooltip="Sekai Stickers">
|
||||
{kanadeSvg()}
|
||||
</ChatBarButton>
|
||||
);
|
||||
};
|
||||
|
||||
let IS_FONTS_LOADED = false;
|
||||
export default definePlugin({
|
||||
name: "SekaiStickers",
|
||||
description: "Sekai Stickers built in discord originally from github.com/TheOriginalAyaka",
|
||||
authors: [Devs.MaiKokain],
|
||||
dependencies: ["ChatInputButtonAPI"],
|
||||
settings,
|
||||
start: async () => {
|
||||
const fonts = [{ name: "YurukaStd", url: "https://raw.githubusercontent.com/TheOriginalAyaka/sekai-stickers/47a2ca33b8cb35f59800e8faad48980e4ce5ea71/src/fonts/YurukaStd.woff2" }, { name: "SSFangTangTi", url: "https://raw.githubusercontent.com/TheOriginalAyaka/sekai-stickers/main/src/fonts/ShangShouFangTangTi.woff2" }];
|
||||
if (!IS_FONTS_LOADED) {
|
||||
fonts.map(n => {
|
||||
new FontFace(n.name, `url(${n.url})`).load().then(
|
||||
font => { document.fonts.add(font); },
|
||||
err => { console.log(err); }
|
||||
);
|
||||
});
|
||||
IS_FONTS_LOADED = true;
|
||||
}
|
||||
addChatBarButton("SekaiStickers", SekaiStickerChatButton);
|
||||
},
|
||||
stop: () => removeChatBarButton("SekaiStickers")
|
||||
});
|
21
src/equicordplugins/sekaiStickers/kanade.svg.tsx
Normal file
21
src/equicordplugins/sekaiStickers/kanade.svg.tsx
Normal file
File diff suppressed because one or more lines are too long
219
src/equicordplugins/tosuRPC/index.ts
Normal file
219
src/equicordplugins/tosuRPC/index.ts
Normal file
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ApplicationAssetUtils, FluxDispatcher } from "@webpack/common";
|
||||
|
||||
import { Activity, ActivityType, BanchoStatusEnum, GameState, Modes, TosuApi, UserLoginStatus } from "./type";
|
||||
|
||||
const socketId = "tosu";
|
||||
const OSU_APP_ID = "367827983903490050";
|
||||
const OSU_LARGE_IMAGE = "373344233077211136";
|
||||
const OSU_STARDARD_SMALL_IMAGE = "373370493127884800";
|
||||
const OSU_MANIA_SMALL_IMAGE = "373370588703621136";
|
||||
const OSU_TAIKO_SMALL_IMAGE = "373370519891738624";
|
||||
const OSU_CATCH_SMALL_IMAGE = "373370543161999361";
|
||||
|
||||
const throttledOnMessage = throttle(onMessage, 3000, () => FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null, socketId: "tosu" }));
|
||||
|
||||
let ws: WebSocket;
|
||||
let wsReconnect: NodeJS.Timeout;
|
||||
|
||||
export default definePlugin({
|
||||
name: "TosuRPC",
|
||||
description: "osu! RPC with data from tosu",
|
||||
authors: [Devs.AutumnVN],
|
||||
start() {
|
||||
(function connect() {
|
||||
ws = new WebSocket("ws://localhost:24050/websocket/v2");
|
||||
ws.addEventListener("error", () => ws.close());
|
||||
ws.addEventListener("close", () => wsReconnect = setTimeout(connect, 5000));
|
||||
ws.addEventListener("message", ({ data }) => throttledOnMessage(data));
|
||||
})();
|
||||
},
|
||||
stop() {
|
||||
ws.close();
|
||||
clearTimeout(wsReconnect);
|
||||
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null, socketId: "tosu" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
async function onMessage(data: string) {
|
||||
const json: TosuApi = JSON.parse(data);
|
||||
// @ts-ignore
|
||||
if (json.error) return FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null, socketId: "tosu" });
|
||||
|
||||
const { state, session, profile, beatmap, play, resultsScreen } = json;
|
||||
|
||||
const activity: Activity = {
|
||||
application_id: OSU_APP_ID,
|
||||
name: "osu!",
|
||||
type: ActivityType.PLAYING,
|
||||
assets: {
|
||||
large_image: OSU_LARGE_IMAGE,
|
||||
large_text: profile.userStatus.number === UserLoginStatus.Connected ? `${profile.name} | #${profile.globalRank} | ${Math.round(profile.pp)}pp` : undefined,
|
||||
},
|
||||
timestamps: {
|
||||
start: Date.now() - session.playTime
|
||||
},
|
||||
flags: 1 << 0,
|
||||
};
|
||||
|
||||
switch (profile.mode.number) {
|
||||
case Modes.Osu:
|
||||
activity.assets.small_image = OSU_STARDARD_SMALL_IMAGE;
|
||||
activity.assets.small_text = "osu!";
|
||||
break;
|
||||
case Modes.Mania:
|
||||
activity.assets.small_image = OSU_MANIA_SMALL_IMAGE;
|
||||
activity.assets.small_text = "osu!mania";
|
||||
break;
|
||||
case Modes.Taiko:
|
||||
activity.assets.small_image = OSU_TAIKO_SMALL_IMAGE;
|
||||
activity.assets.small_text = "osu!taiko";
|
||||
break;
|
||||
case Modes.Fruits:
|
||||
activity.assets.small_image = OSU_CATCH_SMALL_IMAGE;
|
||||
activity.assets.small_text = "osu!catch";
|
||||
break;
|
||||
}
|
||||
|
||||
let player = "";
|
||||
let mods = "";
|
||||
let fc = "";
|
||||
let combo = "";
|
||||
let h100 = "";
|
||||
let h50 = "";
|
||||
let h0 = "";
|
||||
let sb = "";
|
||||
let pp = "";
|
||||
switch (state.number) {
|
||||
case GameState.Play:
|
||||
activity.type = profile.banchoStatus.number === BanchoStatusEnum.Playing ? ActivityType.PLAYING : ActivityType.WATCHING;
|
||||
|
||||
player = profile.banchoStatus.number === BanchoStatusEnum.Playing ? "" : `${play.playerName} | `;
|
||||
mods = play.mods.name ? `+${play.mods.name} ` : "";
|
||||
activity.name = `${player}${beatmap.artist} - ${beatmap.title} [${beatmap.version}] ${mods}(${beatmap.mapper}, ${beatmap.stats.stars.total.toFixed(2)}*)`;
|
||||
|
||||
combo = play.hits[0] === 0 && play.hits.sliderBreaks === 0
|
||||
? `${play.combo.current}x`
|
||||
: `${play.combo.current}x/${play.combo.max}x`;
|
||||
pp = play.hits[0] === 0 && play.hits.sliderBreaks === 0
|
||||
? `${Math.round(play.pp.current)}pp`
|
||||
: `${Math.round(play.pp.current)}pp/${Math.round(play.pp.fc)}pp`;
|
||||
activity.details = `${play.accuracy.toFixed(2)}% | ${combo} | ${pp}`;
|
||||
|
||||
h100 = play.hits[100] > 0 ? `${play.hits[100]}x100` : "";
|
||||
h50 = play.hits[50] > 0 ? `${play.hits[50]}x50` : "";
|
||||
h0 = play.hits[0] > 0 ? `${play.hits[0]}xMiss` : "";
|
||||
sb = play.hits.sliderBreaks > 0 ? `${play.hits.sliderBreaks}xSB` : "";
|
||||
activity.state = [h100, h50, h0, sb].filter(Boolean).join(" | ");
|
||||
|
||||
const playRank = await getAsset(`https://raw.githubusercontent.com/AutumnVN/gosu-rich-presence/main/grade/${play.rank.current.toLowerCase().replace("x", "ss")}.png`);
|
||||
activity.assets.small_image = playRank;
|
||||
activity.assets.small_text = undefined;
|
||||
break;
|
||||
case GameState.ResultScreen:
|
||||
activity.type = ActivityType.WATCHING;
|
||||
|
||||
mods = resultsScreen.mods.name ? `+${resultsScreen.mods.name} ` : "";
|
||||
activity.name = `${resultsScreen.playerName} | ${beatmap.artist} - ${beatmap.title} [${beatmap.version}] ${mods}(${beatmap.mapper}, ${beatmap.stats.stars.total.toFixed(2)}*)`;
|
||||
|
||||
fc = resultsScreen.maxCombo === beatmap.stats.maxCombo ? "FC" : `| ${resultsScreen.maxCombo}x/${beatmap.stats.maxCombo}x`;
|
||||
pp = !resultsScreen.pp.current ? ""
|
||||
: Math.round(resultsScreen.pp.current) === Math.round(resultsScreen.pp.fc)
|
||||
? `| ${Math.round(resultsScreen.pp.current)}pp`
|
||||
: `| ${Math.round(resultsScreen.pp.current)}pp/${Math.round(resultsScreen.pp.fc)}pp`;
|
||||
activity.details = `${resultsScreen.accuracy.toFixed(2)}% ${fc} ${pp}`;
|
||||
|
||||
h100 = resultsScreen.hits[100] > 0 ? `${resultsScreen.hits[100]}x100` : "";
|
||||
h50 = resultsScreen.hits[50] > 0 ? `${resultsScreen.hits[50]}x50` : "";
|
||||
h0 = resultsScreen.hits[0] > 0 ? `${resultsScreen.hits[0]}xMiss` : "";
|
||||
sb = play.hits.sliderBreaks > 0 ? `${play.hits.sliderBreaks}xSB` : "";
|
||||
activity.state = [h100, h50, h0].filter(Boolean).join(" | ");
|
||||
|
||||
const resultRank = await getAsset(`https://raw.githubusercontent.com/AutumnVN/gosu-rich-presence/main/grade/${resultsScreen.rank.toLowerCase().replace("x", "ss")}.png`);
|
||||
activity.assets.small_image = resultRank;
|
||||
activity.assets.small_text = undefined;
|
||||
break;
|
||||
default:
|
||||
activity.type = ActivityType.LISTENING;
|
||||
mods = play.mods.name ? `+${play.mods.name} ` : "";
|
||||
activity.name = `${beatmap.artist} - ${beatmap.title} [${beatmap.version}] ${mods}(${beatmap.mapper}, ${beatmap.stats.stars.total.toFixed(2)}*)`;
|
||||
|
||||
switch (state.number) {
|
||||
case GameState.Menu: activity.details = "Main Menu"; break;
|
||||
case GameState.Edit: activity.details = "Edit"; break;
|
||||
case GameState.SelectEdit: activity.details = "Song Select (Edit)"; break;
|
||||
case GameState.SelectPlay: activity.details = "Song Select (Play)"; break;
|
||||
case GameState.SelectDrawings: activity.details = "Select Drawings"; break;
|
||||
case GameState.Update: activity.details = "Update"; break;
|
||||
case GameState.Busy: activity.details = "Busy"; break;
|
||||
case GameState.Lobby: activity.details = "Lobby"; break;
|
||||
case GameState.MatchSetup: activity.details = "Match Setup"; break;
|
||||
case GameState.SelectMulti: activity.details = "Select Multi"; break;
|
||||
case GameState.RankingVs: activity.details = "Ranking Vs"; break;
|
||||
case GameState.OnlineSelection: activity.details = "Online Selection"; break;
|
||||
case GameState.OptionsOffsetWizard: activity.details = "Options Offset Wizard"; break;
|
||||
case GameState.RankingTagCoop: activity.details = "Ranking Tag Coop"; break;
|
||||
case GameState.RankingTeam: activity.details = "Ranking Team"; break;
|
||||
case GameState.BeatmapImport: activity.details = "Beatmap Import"; break;
|
||||
case GameState.PackageUpdater: activity.details = "Package Updater"; break;
|
||||
case GameState.Benchmark: activity.details = "Benchmark"; break;
|
||||
case GameState.Tourney: activity.details = "Tourney"; break;
|
||||
case GameState.Charts: activity.details = "Charts"; break;
|
||||
}
|
||||
|
||||
switch (profile.banchoStatus.number) {
|
||||
case BanchoStatusEnum.Idle: activity.state = "Idle"; break;
|
||||
case BanchoStatusEnum.Afk: activity.state = "AFK"; break;
|
||||
case BanchoStatusEnum.Playing: activity.state = "Playing"; break;
|
||||
case BanchoStatusEnum.Editing: activity.state = "Editing"; break;
|
||||
case BanchoStatusEnum.Modding: activity.state = "Modding"; break;
|
||||
case BanchoStatusEnum.Multiplayer: activity.state = "Multiplayer"; break;
|
||||
case BanchoStatusEnum.Watching: activity.state = "Watching"; break;
|
||||
case BanchoStatusEnum.Testing: activity.state = "Testing"; break;
|
||||
case BanchoStatusEnum.Submitting: activity.state = "Submitting"; break;
|
||||
case BanchoStatusEnum.Paused: activity.state = "Paused"; break;
|
||||
case BanchoStatusEnum.Lobby: activity.state = "Lobby"; break;
|
||||
case BanchoStatusEnum.Multiplaying: activity.state = "Multiplaying"; break;
|
||||
case BanchoStatusEnum.OsuDirect: activity.state = "osu!direct"; break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (beatmap.set > 0) {
|
||||
const mapBg = await getAsset(`https://assets.ppy.sh/beatmaps/${beatmap.set}/covers/list@2x.jpg`);
|
||||
const res = await fetch(mapBg.replace(/^mp:/, "https://media.discordapp.net/"), { method: "HEAD" });
|
||||
if (res.ok) activity.assets.large_image = mapBg;
|
||||
}
|
||||
|
||||
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity, socketId: "tosu" });
|
||||
}
|
||||
|
||||
function throttle<T extends Function>(func: T, limit: number, timedOutCallback?: () => void): T {
|
||||
let inThrottle: boolean;
|
||||
let callbackTimeout: NodeJS.Timeout;
|
||||
return function (this: any, ...args: any[]) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
if (timedOutCallback) {
|
||||
clearTimeout(callbackTimeout);
|
||||
callbackTimeout = setTimeout(timedOutCallback, limit * 2);
|
||||
}
|
||||
}
|
||||
} as any;
|
||||
}
|
||||
|
||||
async function getAsset(key: string): Promise<string> {
|
||||
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
|
||||
return (await ApplicationAssetUtils.fetchAssetIds(OSU_APP_ID, [key]))[0];
|
||||
}
|
652
src/equicordplugins/tosuRPC/type.ts
Normal file
652
src/equicordplugins/tosuRPC/type.ts
Normal file
|
@ -0,0 +1,652 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface ActivityAssets {
|
||||
large_image?: string;
|
||||
large_text?: string;
|
||||
small_image?: string;
|
||||
small_text?: string;
|
||||
}
|
||||
|
||||
export interface Activity {
|
||||
state?: string;
|
||||
details?: string;
|
||||
timestamps?: {
|
||||
start?: number;
|
||||
end?: number;
|
||||
};
|
||||
assets: ActivityAssets;
|
||||
buttons?: Array<string>;
|
||||
name: string;
|
||||
application_id: string;
|
||||
metadata?: {
|
||||
button_urls?: Array<string>;
|
||||
};
|
||||
type: ActivityType;
|
||||
url?: string;
|
||||
flags: number;
|
||||
}
|
||||
|
||||
export enum ActivityType {
|
||||
PLAYING = 0,
|
||||
STREAMING = 1,
|
||||
LISTENING = 2,
|
||||
WATCHING = 3,
|
||||
COMPETING = 5
|
||||
}
|
||||
|
||||
export enum BeatmapStatuses {
|
||||
Unknown,
|
||||
NotSubmitted = 1,
|
||||
Pending = 2,
|
||||
Ranked = 4,
|
||||
Approved = 5,
|
||||
Qualified = 6,
|
||||
Loved = 7
|
||||
}
|
||||
|
||||
export enum Modes {
|
||||
Osu = 0,
|
||||
Taiko = 1,
|
||||
Fruits = 2,
|
||||
Mania = 3
|
||||
}
|
||||
|
||||
export enum BanchoStatusEnum {
|
||||
Idle,
|
||||
Afk,
|
||||
Playing,
|
||||
Editing,
|
||||
Modding,
|
||||
Multiplayer,
|
||||
Watching,
|
||||
Unknown,
|
||||
Testing,
|
||||
Submitting,
|
||||
Paused,
|
||||
Lobby,
|
||||
Multiplaying,
|
||||
OsuDirect
|
||||
}
|
||||
|
||||
export enum UserLoginStatus {
|
||||
Reconnecting = 0,
|
||||
Guest = 256,
|
||||
Recieving_data = 257,
|
||||
Disconnected = 65537,
|
||||
Connected = 65793
|
||||
}
|
||||
|
||||
export enum ReleaseStream {
|
||||
CuttingEdge,
|
||||
Stable,
|
||||
Beta,
|
||||
Fallback
|
||||
}
|
||||
|
||||
export enum ScoreMeterType {
|
||||
None,
|
||||
Colour,
|
||||
Error
|
||||
}
|
||||
|
||||
export enum LeaderboardType {
|
||||
Local,
|
||||
Global,
|
||||
Selectedmods,
|
||||
Friends,
|
||||
Country
|
||||
}
|
||||
|
||||
export enum GroupType {
|
||||
None,
|
||||
Artist,
|
||||
BPM,
|
||||
Creator,
|
||||
Date,
|
||||
Difficulty,
|
||||
Length,
|
||||
Rank,
|
||||
MyMaps,
|
||||
Search = 12,
|
||||
Show_All = 12,
|
||||
Title,
|
||||
LastPlayed,
|
||||
OnlineFavourites,
|
||||
ManiaKeys,
|
||||
Mode,
|
||||
Collection,
|
||||
RankedStatus
|
||||
}
|
||||
|
||||
export enum SortType {
|
||||
Artist,
|
||||
BPM,
|
||||
Creator,
|
||||
Date,
|
||||
Difficulty,
|
||||
Length,
|
||||
Rank,
|
||||
Title
|
||||
}
|
||||
|
||||
export enum ChatStatus {
|
||||
Hidden,
|
||||
Visible,
|
||||
VisibleWithFriendsList
|
||||
}
|
||||
|
||||
export enum ProgressBarType {
|
||||
Off,
|
||||
Pie,
|
||||
TopRight,
|
||||
BottomRight,
|
||||
Bottom
|
||||
}
|
||||
|
||||
export enum GameState {
|
||||
Menu,
|
||||
Edit,
|
||||
Play,
|
||||
Exit,
|
||||
SelectEdit,
|
||||
SelectPlay,
|
||||
SelectDrawings,
|
||||
ResultScreen,
|
||||
Update,
|
||||
Busy,
|
||||
Unknown,
|
||||
Lobby,
|
||||
MatchSetup,
|
||||
SelectMulti,
|
||||
RankingVs,
|
||||
OnlineSelection,
|
||||
OptionsOffsetWizard,
|
||||
RankingTagCoop,
|
||||
RankingTeam,
|
||||
BeatmapImport,
|
||||
PackageUpdater,
|
||||
Benchmark,
|
||||
Tourney,
|
||||
Charts
|
||||
}
|
||||
|
||||
export type ApiAnswer = TosuApi | { error?: string; };
|
||||
export type ApiAnswerPrecise = TosuPreciseAnswer | { error?: string; };
|
||||
|
||||
export interface TosuApi {
|
||||
state: NumberName;
|
||||
session: Session;
|
||||
settings: Settings;
|
||||
profile: Profile;
|
||||
beatmap: Beatmap;
|
||||
play: Play;
|
||||
leaderboard: Leaderboard[];
|
||||
performance: Performance;
|
||||
resultsScreen: ResultsScreen;
|
||||
folders: Folders;
|
||||
files: Files;
|
||||
directPath: DirectPath;
|
||||
tourney: Tourney | undefined;
|
||||
}
|
||||
|
||||
export interface BeatmapTime {
|
||||
live: number;
|
||||
firstObject: number;
|
||||
lastObject: number;
|
||||
mp3Length: number;
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
playTime: number;
|
||||
playCount: number;
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
interfaceVisible: boolean;
|
||||
replayUIVisible: boolean;
|
||||
chatVisibilityStatus: NumberName;
|
||||
leaderboard: SettingsLeaderboard;
|
||||
|
||||
progressBar: NumberName;
|
||||
bassDensity: number;
|
||||
|
||||
resolution: Resolution;
|
||||
client: Client;
|
||||
|
||||
scoreMeter: ScoreMeter;
|
||||
cursor: Cursor;
|
||||
mouse: Mouse;
|
||||
mania: Mania;
|
||||
|
||||
sort: NumberName;
|
||||
group: NumberName;
|
||||
|
||||
skin: Skin;
|
||||
mode: NumberName;
|
||||
audio: Audio;
|
||||
background: Background;
|
||||
|
||||
keybinds: Keybinds;
|
||||
}
|
||||
|
||||
export interface Keybinds {
|
||||
osu: KeybindsOsu;
|
||||
fruits: KeybindsFruits;
|
||||
taiko: KeybindsTaiko;
|
||||
quickRetry: string;
|
||||
}
|
||||
|
||||
export interface KeybindsOsu {
|
||||
k1: string;
|
||||
k2: string;
|
||||
smokeKey: string;
|
||||
}
|
||||
|
||||
export interface KeybindsFruits {
|
||||
k1: string;
|
||||
k2: string;
|
||||
Dash: string;
|
||||
}
|
||||
|
||||
export interface KeybindsTaiko {
|
||||
innerLeft: string;
|
||||
innerRight: string;
|
||||
outerLeft: string;
|
||||
outerRight: string;
|
||||
}
|
||||
|
||||
export interface Volume {
|
||||
master: number;
|
||||
music: number;
|
||||
effect: number;
|
||||
}
|
||||
|
||||
export interface Audio {
|
||||
ignoreBeatmapSounds: boolean;
|
||||
useSkinSamples: boolean;
|
||||
volume: Volume;
|
||||
offset: Offset;
|
||||
}
|
||||
|
||||
export interface Background {
|
||||
storyboard: boolean;
|
||||
video: boolean;
|
||||
dim: number;
|
||||
}
|
||||
|
||||
export interface Client {
|
||||
updateAvailable: boolean;
|
||||
branch: number;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface Resolution {
|
||||
fullscreen: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
widthFullscreen: number;
|
||||
heightFullscreen: number;
|
||||
}
|
||||
|
||||
export interface Offset {
|
||||
universal: number;
|
||||
}
|
||||
|
||||
export interface Cursor {
|
||||
useSkinCursor: boolean;
|
||||
autoSize: boolean;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface Mouse {
|
||||
disableButtons: boolean;
|
||||
disableWheel: boolean;
|
||||
rawInput: boolean;
|
||||
sensitivity: number;
|
||||
}
|
||||
|
||||
export interface Mania {
|
||||
speedBPMScale: boolean;
|
||||
usePerBeatmapSpeedScale: boolean;
|
||||
}
|
||||
|
||||
export interface Skin {
|
||||
useDefaultSkinInEditor: boolean;
|
||||
ignoreBeatmapSkins: boolean;
|
||||
tintSliderBall: boolean;
|
||||
useTaikoSkin: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface SettingsLeaderboard {
|
||||
visible: boolean;
|
||||
type: NumberName;
|
||||
}
|
||||
|
||||
export interface ScoreMeter {
|
||||
type: NumberName;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface Volume {
|
||||
master: number;
|
||||
music: number;
|
||||
effect: number;
|
||||
}
|
||||
|
||||
export interface NumberName {
|
||||
number: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Profile {
|
||||
userStatus: NumberName;
|
||||
banchoStatus: NumberName;
|
||||
id: number;
|
||||
name: string;
|
||||
mode: NumberName;
|
||||
rankedScore: number;
|
||||
level: number;
|
||||
accuracy: number;
|
||||
pp: number;
|
||||
playCount: number;
|
||||
globalRank: number;
|
||||
countryCode: NumberName;
|
||||
backgroundColour: string;
|
||||
}
|
||||
|
||||
export interface Beatmap {
|
||||
time: BeatmapTime;
|
||||
status: NumberName;
|
||||
checksum: string;
|
||||
id: number;
|
||||
set: number;
|
||||
mode: NumberName;
|
||||
artist: string;
|
||||
artistUnicode: string;
|
||||
title: string;
|
||||
titleUnicode: string;
|
||||
mapper: string;
|
||||
version: string;
|
||||
stats: Stats;
|
||||
}
|
||||
|
||||
export interface Stats {
|
||||
stars: Stars;
|
||||
ar: Ar;
|
||||
cs: Cs;
|
||||
od: Od;
|
||||
hp: Hp;
|
||||
bpm: Bpm;
|
||||
objects: Objects;
|
||||
maxCombo: number;
|
||||
}
|
||||
|
||||
export interface Stars {
|
||||
live: number;
|
||||
aim: number | undefined;
|
||||
speed: number | undefined;
|
||||
flashlight: number | undefined;
|
||||
sliderFactor: number | undefined;
|
||||
stamina: number | undefined;
|
||||
rhythm: number | undefined;
|
||||
color: number | undefined;
|
||||
peak: number | undefined;
|
||||
hitWindow: number | undefined;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface Ar {
|
||||
original: number;
|
||||
converted: number;
|
||||
}
|
||||
|
||||
export interface Cs {
|
||||
original: number;
|
||||
converted: number;
|
||||
}
|
||||
|
||||
export interface Od {
|
||||
original: number;
|
||||
converted: number;
|
||||
}
|
||||
|
||||
export interface Hp {
|
||||
original: number;
|
||||
converted: number;
|
||||
}
|
||||
|
||||
export interface Bpm {
|
||||
common: number;
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface Objects {
|
||||
circles: number;
|
||||
sliders: number;
|
||||
spinners: number;
|
||||
holds: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface Play {
|
||||
playerName: string;
|
||||
mode: NumberName;
|
||||
score: number;
|
||||
accuracy: number;
|
||||
healthBar: HealthBar;
|
||||
hits: Hits;
|
||||
hitErrorArray: any[];
|
||||
combo: Combo;
|
||||
mods: NumberName;
|
||||
rank: Rank;
|
||||
pp: Pp;
|
||||
unstableRate: number;
|
||||
}
|
||||
|
||||
export interface HealthBar {
|
||||
normal: number;
|
||||
smooth: number;
|
||||
}
|
||||
|
||||
export interface Hits {
|
||||
"0": number;
|
||||
"50": number;
|
||||
"100": number;
|
||||
"300": number;
|
||||
geki: number;
|
||||
katu: number;
|
||||
sliderBreaks: number;
|
||||
}
|
||||
|
||||
export interface Combo {
|
||||
current: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface Rank {
|
||||
current: string;
|
||||
maxThisPlay: string;
|
||||
}
|
||||
|
||||
export interface Pp {
|
||||
current: number;
|
||||
fc: number;
|
||||
maxAchievedThisPlay: number;
|
||||
}
|
||||
export interface Pp2 {
|
||||
current: number;
|
||||
fc: number;
|
||||
}
|
||||
|
||||
export interface Leaderboard {
|
||||
isFailed: boolean;
|
||||
position: number;
|
||||
team: number;
|
||||
name: string;
|
||||
score: number;
|
||||
accuracy: number;
|
||||
hits: Hits2;
|
||||
combo: Combo2;
|
||||
mods: NumberName;
|
||||
rank: string;
|
||||
}
|
||||
|
||||
export interface Hits2 {
|
||||
"0": number;
|
||||
"50": number;
|
||||
"100": number;
|
||||
"300": number;
|
||||
geki: number;
|
||||
katu: number;
|
||||
}
|
||||
|
||||
export interface Combo2 {
|
||||
current: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface TosuPreciseAnswer {
|
||||
currentTime: number;
|
||||
keys: KeyOverlay;
|
||||
hitErrors: number[];
|
||||
tourney: PreciseTourney[];
|
||||
}
|
||||
|
||||
export interface PreciseTourney {
|
||||
ipcId: number;
|
||||
keys: KeyOverlay;
|
||||
hitErrors: number[];
|
||||
}
|
||||
|
||||
interface KeyOverlay {
|
||||
k1: KeyOverlayButton;
|
||||
k2: KeyOverlayButton;
|
||||
m1: KeyOverlayButton;
|
||||
m2: KeyOverlayButton;
|
||||
}
|
||||
|
||||
interface KeyOverlayButton {
|
||||
isPressed: boolean;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface Performance {
|
||||
accuracy: Accuracy;
|
||||
graph: Graph;
|
||||
}
|
||||
|
||||
export interface Accuracy {
|
||||
"95": number;
|
||||
"96": number;
|
||||
"97": number;
|
||||
"98": number;
|
||||
"99": number;
|
||||
"100": number;
|
||||
}
|
||||
|
||||
export interface Graph {
|
||||
series: Series[];
|
||||
xaxis: number[];
|
||||
}
|
||||
|
||||
export interface Series {
|
||||
name: string;
|
||||
data: number[];
|
||||
}
|
||||
|
||||
export interface ResultsScreen {
|
||||
playerName: string;
|
||||
mode: NumberName;
|
||||
score: number;
|
||||
accuracy: number;
|
||||
name: string;
|
||||
hits: Hits3;
|
||||
mods: NumberName;
|
||||
maxCombo: number;
|
||||
rank: string;
|
||||
pp: Pp2;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Hits3 {
|
||||
"0": number;
|
||||
"50": number;
|
||||
"100": number;
|
||||
"300": number;
|
||||
geki: number;
|
||||
katu: number;
|
||||
}
|
||||
|
||||
export interface Folders {
|
||||
game: string;
|
||||
skin: string;
|
||||
songs: string;
|
||||
beatmap: string;
|
||||
}
|
||||
|
||||
export interface Files {
|
||||
beatmap: string;
|
||||
background: string;
|
||||
audio: string;
|
||||
}
|
||||
|
||||
export interface DirectPath {
|
||||
beatmapFile: string;
|
||||
beatmapBackground: string;
|
||||
beatmapAudio: string;
|
||||
beatmapFolder: string;
|
||||
skinFolder: string;
|
||||
}
|
||||
|
||||
export interface Tourney {
|
||||
scoreVisible: boolean;
|
||||
starsVisible: boolean;
|
||||
|
||||
ipcState: number;
|
||||
bestOF: number;
|
||||
|
||||
team: {
|
||||
left: string;
|
||||
right: string;
|
||||
};
|
||||
points: {
|
||||
left: number;
|
||||
right: number;
|
||||
};
|
||||
totalScore: {
|
||||
left: number;
|
||||
right: number;
|
||||
};
|
||||
|
||||
chat: TourneyChatMessages[];
|
||||
clients: TourneyClients[];
|
||||
}
|
||||
|
||||
export interface TourneyChatMessages {
|
||||
team: string;
|
||||
name: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface TourneyClients {
|
||||
ipcId: number;
|
||||
team: "left" | "right";
|
||||
user: {
|
||||
id: number;
|
||||
name: string;
|
||||
country: string;
|
||||
accuracy: number;
|
||||
rankedScore: number;
|
||||
playCount: number;
|
||||
globalRank: number;
|
||||
totalPP: number;
|
||||
};
|
||||
play: Play;
|
||||
}
|
77
src/equicordplugins/translatePlus/index.tsx
Normal file
77
src/equicordplugins/translatePlus/index.tsx
Normal 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 "./style.css";
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories";
|
||||
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore, Menu } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import { Accessory, handleTranslate } from "./utils/accessory";
|
||||
import { Icon } from "./utils/icon";
|
||||
|
||||
const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => {
|
||||
if (!message.content) return;
|
||||
|
||||
const group = findGroupChildrenByChildId("copy-text", children);
|
||||
if (!group) return;
|
||||
|
||||
group.splice(group.findIndex(c => c?.props?.id === "copy-text") + 1, 0, (
|
||||
<Menu.MenuItem
|
||||
id="ec-trans"
|
||||
label="Translate"
|
||||
icon={Icon}
|
||||
action={() => handleTranslate(message)}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "Translate+",
|
||||
description: "Vencord's translate plugin but with support for artistic languages!",
|
||||
dependencies: ["MessageAccessoriesAPI"],
|
||||
authors: [Devs.Ven, EquicordDevs.Prince527],
|
||||
settings,
|
||||
contextMenus: {
|
||||
"message": messageCtxPatch
|
||||
},
|
||||
|
||||
start() {
|
||||
addMessageAccessory("ec-translation", props => <Accessory message={props.message} />);
|
||||
|
||||
addMessagePopoverButton("ec-translate", message => {
|
||||
if (!message.content) return null;
|
||||
|
||||
return {
|
||||
label: "Translate",
|
||||
icon: Icon,
|
||||
message: message,
|
||||
channel: ChannelStore.getChannel(message.channel_id),
|
||||
onClick: () => handleTranslate(message),
|
||||
};
|
||||
});
|
||||
},
|
||||
stop() {
|
||||
removeMessagePopoverButton("ec-translate");
|
||||
removeMessageAccessory("ec-translation");
|
||||
}
|
||||
});
|
173
src/equicordplugins/translatePlus/misc/languages.ts
Normal file
173
src/equicordplugins/translatePlus/misc/languages.ts
Normal file
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
To generate:
|
||||
- Visit https://translate.google.com/?sl=auto&tl=en&op=translate
|
||||
- Open Language dropdown
|
||||
- Open Devtools and use the element picker to pick the root of the language picker
|
||||
- Right click on the element in devtools and click "Store as global variable"
|
||||
|
||||
copy(Object.fromEntries(
|
||||
Array.from(
|
||||
temp1.querySelectorAll("[data-language-code]"),
|
||||
e => [e.dataset.languageCode, e.children[1].textContent]
|
||||
).sort((a, b) => a[1] === "Detect language" ? -1 : b[1] === "Detect language" ? 1 : a[1].localeCompare(b[1]))
|
||||
))
|
||||
*/
|
||||
|
||||
export type languages = keyof typeof languages;
|
||||
|
||||
export const languages = {
|
||||
"auto": "Detect language",
|
||||
"af": "Afrikaans",
|
||||
"sq": "Albanian",
|
||||
"am": "Amharic",
|
||||
"ar": "Arabic",
|
||||
"hy": "Armenian",
|
||||
"as": "Assamese",
|
||||
"ay": "Aymara",
|
||||
"az": "Azerbaijani",
|
||||
"bm": "Bambara",
|
||||
"eu": "Basque",
|
||||
"be": "Belarusian",
|
||||
"bn": "Bengali",
|
||||
"bho": "Bhojpuri",
|
||||
"bs": "Bosnian",
|
||||
"bg": "Bulgarian",
|
||||
"ca": "Catalan",
|
||||
"ceb": "Cebuano",
|
||||
"ny": "Chichewa",
|
||||
"zh-CN": "Chinese (Simplified)",
|
||||
"zh-TW": "Chinese (Traditional)",
|
||||
"co": "Corsican",
|
||||
"hr": "Croatian",
|
||||
"cs": "Czech",
|
||||
"da": "Danish",
|
||||
"dv": "Dhivehi",
|
||||
"doi": "Dogri",
|
||||
"nl": "Dutch",
|
||||
"en": "English",
|
||||
"eo": "Esperanto",
|
||||
"et": "Estonian",
|
||||
"ee": "Ewe",
|
||||
"tl": "Filipino",
|
||||
"tp": "Toki Pona",
|
||||
"sh": "Shavian",
|
||||
"fi": "Finnish",
|
||||
"fr": "French",
|
||||
"fy": "Frisian",
|
||||
"gl": "Galician",
|
||||
"ka": "Georgian",
|
||||
"de": "German",
|
||||
"el": "Greek",
|
||||
"gn": "Guarani",
|
||||
"gu": "Gujarati",
|
||||
"ht": "Haitian Creole",
|
||||
"ha": "Hausa",
|
||||
"haw": "Hawaiian",
|
||||
"iw": "Hebrew",
|
||||
"hi": "Hindi",
|
||||
"hmn": "Hmong",
|
||||
"hu": "Hungarian",
|
||||
"is": "Icelandic",
|
||||
"ig": "Igbo",
|
||||
"ilo": "Ilocano",
|
||||
"id": "Indonesian",
|
||||
"ga": "Irish",
|
||||
"it": "Italian",
|
||||
"ja": "Japanese",
|
||||
"jw": "Javanese",
|
||||
"kn": "Kannada",
|
||||
"kk": "Kazakh",
|
||||
"km": "Khmer",
|
||||
"rw": "Kinyarwanda",
|
||||
"gom": "Konkani",
|
||||
"ko": "Korean",
|
||||
"kri": "Krio",
|
||||
"ku": "Kurdish (Kurmanji)",
|
||||
"ckb": "Kurdish (Sorani)",
|
||||
"ky": "Kyrgyz",
|
||||
"lo": "Lao",
|
||||
"la": "Latin",
|
||||
"lv": "Latvian",
|
||||
"ln": "Lingala",
|
||||
"lt": "Lithuanian",
|
||||
"lg": "Luganda",
|
||||
"lb": "Luxembourgish",
|
||||
"mk": "Macedonian",
|
||||
"mai": "Maithili",
|
||||
"mg": "Malagasy",
|
||||
"ms": "Malay",
|
||||
"ml": "Malayalam",
|
||||
"mt": "Maltese",
|
||||
"mi": "Maori",
|
||||
"mr": "Marathi",
|
||||
"mni-Mtei": "Meiteilon (Manipuri)",
|
||||
"lus": "Mizo",
|
||||
"mn": "Mongolian",
|
||||
"my": "Myanmar (Burmese)",
|
||||
"ne": "Nepali",
|
||||
"no": "Norwegian",
|
||||
"or": "Odia (Oriya)",
|
||||
"om": "Oromo",
|
||||
"ps": "Pashto",
|
||||
"fa": "Persian",
|
||||
"pl": "Polish",
|
||||
"pt": "Portuguese",
|
||||
"pa": "Punjabi",
|
||||
"qu": "Quechua",
|
||||
"ro": "Romanian",
|
||||
"ru": "Russian",
|
||||
"sm": "Samoan",
|
||||
"sa": "Sanskrit",
|
||||
"gd": "Scots Gaelic",
|
||||
"nso": "Sepedi",
|
||||
"sr": "Serbian",
|
||||
"st": "Sesotho",
|
||||
"sn": "Shona",
|
||||
"sd": "Sindhi",
|
||||
"si": "Sinhala",
|
||||
"sk": "Slovak",
|
||||
"sl": "Slovenian",
|
||||
"so": "Somali",
|
||||
"es": "Spanish",
|
||||
"su": "Sundanese",
|
||||
"sw": "Swahili",
|
||||
"sv": "Swedish",
|
||||
"tg": "Tajik",
|
||||
"ta": "Tamil",
|
||||
"tt": "Tatar",
|
||||
"te": "Telugu",
|
||||
"th": "Thai",
|
||||
"ti": "Tigrinya",
|
||||
"ts": "Tsonga",
|
||||
"tr": "Turkish",
|
||||
"tk": "Turkmen",
|
||||
"ak": "Twi",
|
||||
"uk": "Ukrainian",
|
||||
"ur": "Urdu",
|
||||
"ug": "Uyghur",
|
||||
"uz": "Uzbek",
|
||||
"vi": "Vietnamese",
|
||||
"cy": "Welsh",
|
||||
"xh": "Xhosa",
|
||||
"yi": "Yiddish",
|
||||
"yo": "Yoruba",
|
||||
"zu": "Zulu"
|
||||
} as const;
|
19
src/equicordplugins/translatePlus/misc/types.ts
Normal file
19
src/equicordplugins/translatePlus/misc/types.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
|
||||
export const cl = classNameFactory("eq-trans-");
|
||||
|
||||
export interface Translation {
|
||||
text: string;
|
||||
src: string;
|
||||
}
|
||||
|
||||
export type IconProps = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
35
src/equicordplugins/translatePlus/settings.ts
Normal file
35
src/equicordplugins/translatePlus/settings.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { OptionType } from "@utils/types";
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
target: {
|
||||
type: OptionType.STRING,
|
||||
description: "Target language",
|
||||
default: "en",
|
||||
restartNeeded: true
|
||||
},
|
||||
toki: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Toki Pona",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
},
|
||||
sitelen: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Sitelen Pona",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
},
|
||||
shavian: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Shavian",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
}
|
||||
});
|
20
src/equicordplugins/translatePlus/style.css
Normal file
20
src/equicordplugins/translatePlus/style.css
Normal file
|
@ -0,0 +1,20 @@
|
|||
.eq-trans-accessory {
|
||||
color: var(--text-muted);
|
||||
margin-top: 0.5em;
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.eq-trans-accessory svg {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.eq-trans-dismiss {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
color: var(--text-link);
|
||||
}
|
||||
|
||||
.eq-trans-dismiss:is(:hover, :focus) {
|
||||
text-decoration: underline;
|
||||
}
|
42
src/equicordplugins/translatePlus/utils/accessory.tsx
Normal file
42
src/equicordplugins/translatePlus/utils/accessory.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Parser, useEffect, useState } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
import { languages } from "../misc/languages";
|
||||
import { cl, Translation } from "../misc/types";
|
||||
import { Icon } from "./icon";
|
||||
import { translate } from "./translator";
|
||||
|
||||
const setters = new Map();
|
||||
|
||||
export function Accessory({ message }: { message: Message; }) {
|
||||
const [translation, setTranslation] = useState<Translation | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if ((message as any).vencordEmbeddedBy) return;
|
||||
|
||||
setters.set(message.id, setTranslation);
|
||||
|
||||
return () => void setters.delete(message.id);
|
||||
}, []);
|
||||
|
||||
if (!translation) return null;
|
||||
|
||||
return (
|
||||
<span className={cl("accessory")}>
|
||||
<Icon width={16} height={16} />
|
||||
{Parser.parse(translation.text)}
|
||||
{" "}
|
||||
(translated from {languages[translation.src] ?? translation.src} - <button onClick={() => setTranslation(undefined)} className={cl("dismiss")}>Dismiss</button>)
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export async function handleTranslate(message: Message) {
|
||||
setters.get(message.id)!(await translate(message.content));
|
||||
}
|
17
src/equicordplugins/translatePlus/utils/icon.tsx
Normal file
17
src/equicordplugins/translatePlus/utils/icon.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { cl, IconProps } from "../misc/types";
|
||||
|
||||
export function Icon({ width = 24, height = 24 }: IconProps) {
|
||||
return (
|
||||
<svg viewBox="0 96 960 960" height={width} width={height} className={cl("icon")}>
|
||||
<path fill="currentColor" d="m475 976 181-480h82l186 480h-87l-41-126H604l-47 126h-82Zm151-196h142l-70-194h-2l-70 194Zm-466 76-55-55 204-204q-38-44-67.5-88.5T190 416h87q17 33 37.5 62.5T361 539q45-47 75-97.5T487 336H40v-80h280v-80h80v80h280v80H567q-22 69-58.5 135.5T419 598l98 99-30 81-127-122-200 200Z" />
|
||||
<path fill="currentColor" d="m 830.17456,136.43701 c -11.54729,0 -20.84473,8.71252 -20.84473,19.53369 v 66.21826 h -66.21826 c -10.82122,0 -19.53369,9.29373 -19.53369,20.84107 0,11.54734 8.71247,20.84473 19.53369,20.84473 h 66.21826 v 66.21826 c 0,10.8212 9.29742,19.53369 20.84473,19.53369 11.54731,0 20.84106,-8.71249 20.84106,-19.53369 v -66.21826 h 66.21827 c 10.82124,0 19.53369,-9.29737 19.53369,-20.84473 0,-11.54736 -8.71245,-20.84107 -19.53369,-20.84107 H 851.01562 V 155.9707 c 0,-10.82117 -9.29377,-19.53369 -20.84106,-19.53369 z" />
|
||||
<rect fill="currentColor" width="0.42110577" height="2.1055288" x="848.52814" y="112.42313" ry="0.2105529" />
|
||||
</svg>
|
||||
);
|
||||
}
|
135
src/equicordplugins/translatePlus/utils/translator.ts
Normal file
135
src/equicordplugins/translatePlus/utils/translator.ts
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { settings } from "../settings";
|
||||
|
||||
function isTokiPona(text: string) {
|
||||
const dictionary = /\b(?:leko|weka|pan|lete|linja|lipu|suli|nimi|akesi|misikeke|selo|ike|sijelo|sona|lili|pimeja|ante|jo|loje|telo|walo|kijetesantakalu|kasi|waso|wile|utala|lukin|sina|lape|ma|pilin|jasima|la|olin|pipi|meso|lawa|pi|pakala|oko|tan|ken|jaki|unpa|esun|seme|sitelen|len|kule|soko|open|ala|tenpo|lon|sinpin|pini|kokosila|mama|musi|monsi|mewika|taso|ona|mun|kiwen|tomo|mute|mi|nena|palisa|meli|laso|wawa|ale|kipisi|kulupu|ilo|lupa|nanpa|en|mu|jelo|kili|tonsi|moku|ni|kama|pu|poki|monsuta|sin|lasina|poka|soweli|sewi|elena|epiku|moli|pona|lanpan|alasa|anu|kute|uta|luka|suno|sama|awen|namako|suwi|noka|seli|mije|sike|jan|pali|tawa|inli|nasa|mani|wan|insa|nijon|nasin|kalama|ijo|toki|anpa|kala|kepeken|ko|kon|pana|tu|supa|kin|usawi|yupekosi)\b/gm;
|
||||
|
||||
return (text.match(dictionary) || []).length >= text.split(/\s+/).length * 0.5;
|
||||
}
|
||||
|
||||
function isSitelen(text: string) {
|
||||
const dictionary = /(?:||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||)/gm;
|
||||
|
||||
return dictionary.test(text);
|
||||
}
|
||||
|
||||
function isShavian(text: string) {
|
||||
const shavianRegex = /[\u{10450}-\u{1047F}]+/u;
|
||||
|
||||
return shavianRegex.test(text);
|
||||
}
|
||||
|
||||
async function translateShavian(message: string) {
|
||||
const dictionary = await (await fetch("https://raw.githubusercontent.com/ForkPrince/TranslatePlus/322199d5fdb1a9506591c9f4a2826338b5d67e38/shavian.json")).json();
|
||||
|
||||
const punctuationMap = {
|
||||
'"': "\"",
|
||||
"«": "\"",
|
||||
"»": "\"",
|
||||
",": ",",
|
||||
"!": "!",
|
||||
"?": "?",
|
||||
".": ".",
|
||||
"(": "(",
|
||||
")": ")",
|
||||
"/": "/",
|
||||
";": ";",
|
||||
":": ":"
|
||||
};
|
||||
|
||||
let translated = "";
|
||||
const words = message.split(/\s+/);
|
||||
|
||||
for (let word of words) {
|
||||
let punctuationBefore = "", punctuationAfter = "";
|
||||
|
||||
if (word[0] in punctuationMap) {
|
||||
punctuationBefore = punctuationMap[word[0]];
|
||||
word = word.slice(1);
|
||||
}
|
||||
|
||||
if (word[word.length - 1] in punctuationMap) {
|
||||
punctuationAfter = punctuationMap[word[word.length - 1]];
|
||||
word = word.slice(0, -1);
|
||||
}
|
||||
|
||||
translated += punctuationBefore;
|
||||
|
||||
if (word in dictionary) translated += dictionary[word];
|
||||
else translated += word;
|
||||
|
||||
translated += punctuationAfter + " ";
|
||||
}
|
||||
|
||||
return translated.trim();
|
||||
}
|
||||
|
||||
async function translateSitelen(message: string) {
|
||||
message = Array.from(message).join(" ");
|
||||
|
||||
const dictionary = await (await fetch("https://raw.githubusercontent.com/ForkPrince/TranslatePlus/5ca152b134ea11433971f21b2ef8d556d4306717/sitelen-pona.json")).json();
|
||||
|
||||
const sorted = Object.keys(dictionary).sort((a, b) => b.length - a.length);
|
||||
|
||||
const pattern = new RegExp(`(${sorted.join("|")})`, "g");
|
||||
|
||||
const translate = message.replace(pattern, match => dictionary[match]);
|
||||
|
||||
return translate;
|
||||
}
|
||||
|
||||
async function google(target: string, text: string) {
|
||||
const translate = await (await fetch(`https://translate.googleapis.com/translate_a/single?${new URLSearchParams({ client: "gtx", sl: "auto", tl: target, dt: "t", dj: "1", source: "input", q: text })}`)).json();
|
||||
|
||||
return {
|
||||
src: translate.src,
|
||||
text: translate.sentences.map(s => s.trans).filter(Boolean).join("")
|
||||
};
|
||||
}
|
||||
|
||||
export async function translate(text: string): Promise<any> {
|
||||
const { target, toki, sitelen, shavian } = settings.store;
|
||||
|
||||
const output = { src: "", text: "" };
|
||||
|
||||
if ((isTokiPona(text) || isSitelen(text)) && (toki || sitelen)) {
|
||||
if (isSitelen(text) && sitelen) text = await translateSitelen(text);
|
||||
|
||||
console.log(text);
|
||||
|
||||
const translate = await (await fetch("https://aiapi.serversmp.xyz/toki", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text: text,
|
||||
src: "tl",
|
||||
target: "en"
|
||||
})
|
||||
})).json();
|
||||
|
||||
console.log(translate);
|
||||
|
||||
output.src = "tp";
|
||||
output.text = target === "en" ? translate.translation[0] : (await google(target, translate.translation[0])).text;
|
||||
} else if (isShavian(text) && shavian) {
|
||||
const translate = await translateShavian(text);
|
||||
|
||||
output.src = "sh";
|
||||
output.text = target === "en" ? translate : (await google(target, translate)).text;
|
||||
} else {
|
||||
const translate = await google(target, text);
|
||||
|
||||
output.src = translate.src;
|
||||
output.text = translate.text;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
|
@ -18,53 +18,7 @@ const MediaScriptsAndCssSrc = [...MediaAndCssSrc, "script-src", "worker-src"];
|
|||
// script and just adding to it. But generally, you should just edit this file instead
|
||||
|
||||
export const CspPolicies: PolicyMap = {
|
||||
"*.github.io": MediaAndCssSrc, // github pages, used by most themes
|
||||
"raw.githubusercontent.com": MediaAndCssSrc, // github raw, used by some themes
|
||||
"*.gitlab.io": MediaAndCssSrc, // gitlab pages, used by some themes
|
||||
"gitlab.com": MediaAndCssSrc, // gitlab raw, used by some themes
|
||||
"*.codeberg.page": MediaAndCssSrc, // codeberg pages, used by some themes
|
||||
"codeberg.org": MediaAndCssSrc, // codeberg raw, used by some themes
|
||||
|
||||
"*.githack.com": MediaAndCssSrc, // githack (namely raw.githack.com), used by some themes
|
||||
"jsdelivr.net": MediaAndCssSrc, // jsdeliver, used by very few themes
|
||||
|
||||
"fonts.googleapis.com": CssSrc, // google fonts, used by many themes
|
||||
|
||||
"i.imgur.com": MediaSrc, // imgur, used by some themes
|
||||
"i.ibb.co": MediaSrc, // imgbb, used by some themes
|
||||
|
||||
"cdn.discordapp.com": MediaAndCssSrc, // Discord CDN, used by Vencord and some themes to load media
|
||||
"media.discordapp.net": MediaSrc, // Discord media CDN, possible alternative to Discord CDN
|
||||
|
||||
// CDNs used for some things by Vencord.
|
||||
// FIXME: we really should not be using CDNs anymore
|
||||
"cdnjs.cloudflare.com": MediaScriptsAndCssSrc,
|
||||
"cdn.jsdelivr.net": MediaScriptsAndCssSrc,
|
||||
|
||||
// Function Specific
|
||||
"api.github.com": ConnectSrc, // used for updating Vencord itself
|
||||
"ws.audioscrobbler.com": ConnectSrc, // last.fm API
|
||||
"translate.googleapis.com": ConnectSrc, // Google Translate API
|
||||
"*.vencord.dev": MediaSrc, // VenCloud (api.vencord.dev) and Badges (badges.vencord.dev)
|
||||
"manti.vendicated.dev": MediaSrc, // ReviewDB API
|
||||
"decor.fieryflames.dev": ConnectSrc, // Decor API
|
||||
"ugc.decor.fieryflames.dev": MediaSrc, // Decor CDN
|
||||
"sponsor.ajay.app": ConnectSrc, // Dearrow API
|
||||
"dearrow-thumb.ajay.app": MediaSrc, // Dearrow Thumbnail CDN
|
||||
"usrbg.is-hardly.online": MediaSrc, // USRBG API
|
||||
"icons.duckduckgo.com": MediaSrc, // DuckDuckGo Favicon API (Reverse Image Search)
|
||||
|
||||
// Equicord
|
||||
"cdn.nest.rip": MediaSrc, // Nest CDN
|
||||
"equicord.org": MediaSrc, // Equicord CDN
|
||||
"*.equicord.org": MediaSrc, // Equicord CDN
|
||||
"discord-themes.com": MediaAndCssSrc, // Discord Themes CDN
|
||||
"fonts.google.com": ConnectSrc,
|
||||
"lrclib.net": ConnectSrc, // Lrclib API
|
||||
"spotify-lyrics-api-pi.vercel.app": ConnectSrc, // Spotify Lyrics API
|
||||
"stats.fm": MediaSrc, // Stats.fm API
|
||||
"discord.com": MediaScriptsAndCssSrc, // Discord
|
||||
"*.discord.com": MediaScriptsAndCssSrc // Discord
|
||||
"*": MediaScriptsAndCssSrc
|
||||
};
|
||||
|
||||
const findHeader = (headers: PolicyMap, headerName: Lowercase<string>) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue