mirror of
https://github.com/Equicord/Equicord.git
synced 2025-02-20 15:18:50 -05:00
Im done adding stuff
This commit is contained in:
parent
167612920b
commit
ee6a447c6f
57 changed files with 78068 additions and 4 deletions
|
@ -27,6 +27,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
|||
- AltKrispSwitch by newwares
|
||||
- AmITyping by MrDiamond
|
||||
- Anammox by Kyuuhachi
|
||||
- DecodeBase64 by ThePirateStoner
|
||||
- BetterActivities by D3SOX, Arjix, AutumnVN
|
||||
- BetterBanReasons by Inbestigator
|
||||
- BetterQuickReact by Ven and Sqaaakoi
|
||||
|
@ -37,6 +38,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
|||
- CommandPalette by Ethan
|
||||
- CopyUserMention by Cortex and castdrian
|
||||
- CustomAppIcons by Happy Enderman and SerStars
|
||||
- CustomSounds by ScattrdBlade
|
||||
- DeadMembers by Kyuuhachi
|
||||
- DiscordColorways by DaBluLite
|
||||
- DNDWhilePlaying by thororen
|
||||
|
@ -46,10 +48,13 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
|||
- Encryptcord by Inbestigator
|
||||
- EquicordCSS by FoxStorm1 and thororen (and all respective css developers)
|
||||
- ExportContacts by dat_insanity
|
||||
- FakeProfile by Sampath
|
||||
- FindReply by newwares
|
||||
- FriendshipRanks by Samwich
|
||||
- GensokyoRadioRPC by RyanCaoDev and Prince527
|
||||
- GlobalBadges by HypedDomi and Hosted by Wolfie
|
||||
- GodMode by Tolgchu
|
||||
- GrammarFix by unstream
|
||||
- HideMessage by Hanzy
|
||||
- HolyNotes by Wolfie
|
||||
- Hop On by ImLvna
|
||||
|
@ -62,6 +67,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
|||
- MessageLinkTooltip by Kyuuhachi
|
||||
- MessageLoggerEnhanced by Aria
|
||||
- ModalFade by Kyuuhachi
|
||||
- NewPluginsManager by Sqaaakoi
|
||||
- noAppsAllowed by kvba
|
||||
- NoModalAnimation by AutumnVN
|
||||
- NoNitroUpsell by thororen
|
||||
|
@ -76,6 +82,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
|||
- Quest Completer by HappyEnderman, SerStars, thororen
|
||||
- QuestionMarkReplacement by nyx
|
||||
- RepeatMessage by Tolgchu
|
||||
- ReplaceActivityTypes by Nyako
|
||||
- ReplyPingControl by ant0n and MrDiamond
|
||||
- ScreenRecorder by AutumnVN
|
||||
- SearchFix by Jaxx
|
||||
|
@ -89,6 +96,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
|||
- ThemeLibrary by Fafa
|
||||
- Title by Kyuuhachi
|
||||
- TosuRPC by AutumnVN
|
||||
- Translate+ by Prince527 (Using Translate by Ven)
|
||||
- UnlimitedAccounts by Balaclava and thororen
|
||||
- UserPFP by nexpid and thororen
|
||||
- VCSupport by thororen
|
||||
|
|
162
src/equicordplugins/baseDecoder/index.tsx
Normal file
162
src/equicordplugins/baseDecoder/index.tsx
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, ChannelStore, Forms, Text } from "@webpack/common";
|
||||
|
||||
const DecodeIcon = () => {
|
||||
return (
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 9.50026H14.0385C15.4063 9.50026 16.0902 9.50026 16.5859 9.82073C16.8235 9.97438 17.0259 10.1767 17.1795 10.4144C17.5 10.91 17.5 11.5939 17.5 12.9618C17.5 14.3297 17.5 15.0136 17.1795 15.5092C17.0259 15.7469 16.8235 15.9492 16.5859 16.1029C16.0902 16.4233 15.4063 16.4233 14.0385 16.4233H9.5M6.5 9.50026L8.75 7.42334M6.5 9.50026L8.75 11.5772" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C21.5093 4.43821 21.8356 5.80655 21.9449 8" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
function isValidUtf8String(str) {
|
||||
try {
|
||||
new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(str.split("").map(char => char.charCodeAt(0))));
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function findBase64Strings(content) {
|
||||
const base64Regex = /\b[A-Za-z0-9+/]{4,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\b/g;
|
||||
const matches = content.match(base64Regex);
|
||||
return matches || [];
|
||||
}
|
||||
|
||||
function decodeBase64Strings(base64Strings) {
|
||||
return base64Strings.map(base64 => {
|
||||
try {
|
||||
const decoded = atob(base64);
|
||||
return isValidUtf8String(decoded) ? decoded : null;
|
||||
} catch (e) {
|
||||
console.error("Failed to decode base64 content:", e);
|
||||
return null;
|
||||
}
|
||||
}).filter(decoded => decoded !== null);
|
||||
}
|
||||
|
||||
|
||||
function openDecodedBase64Modal(decodedContent) {
|
||||
const key = openModal(props => (
|
||||
<ErrorBoundary>
|
||||
<ModalRoot {...props} size={ModalSize.LARGE}>
|
||||
<ModalHeader>
|
||||
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Decoded Base64 Content</Text>
|
||||
<ModalCloseButton onClick={() => closeModal(key)} />
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<Forms.FormTitle tag="h5">Decoded Content</Forms.FormTitle>
|
||||
{decodedContent.map((content, index) => (
|
||||
<CodeBlock key={index} content={content} lang="" />
|
||||
))}
|
||||
</div>
|
||||
</ModalContent >
|
||||
<ModalFooter>
|
||||
<Flex cellSpacing={10}>
|
||||
{decodedContent.map((content, index) => (
|
||||
<Button key={index} onClick={() => copyWithToast(content, "Decoded content copied to clipboard!")}>
|
||||
Copy Decoded Content {index + 1}
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
</ModalFooter>
|
||||
</ModalRoot >
|
||||
</ErrorBoundary >
|
||||
));
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
clickMethod: {
|
||||
description: "Change the button to decode base64 content of any message.",
|
||||
type: OptionType.SELECT,
|
||||
options: [
|
||||
{ label: "Left Click to decode the base64 content.", value: "Left", default: true },
|
||||
{ label: "Right click to decode the base64 content.", value: "Right" }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "DecodeBase64",
|
||||
description: "Decode base64 content of any message and copy the decoded content.",
|
||||
authors: [EquicordDevs.ThePirateStoner],
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
settings,
|
||||
contextMenus: {
|
||||
},
|
||||
|
||||
start() {
|
||||
addButton("DecodeBase64", msg => {
|
||||
const handleClick = () => {
|
||||
const base64Strings = findBase64Strings(msg.content);
|
||||
const decodedContent = decodeBase64Strings(base64Strings);
|
||||
if (settings.store.clickMethod === "Right") {
|
||||
decodedContent.forEach(content => copyWithToast(content));
|
||||
} else {
|
||||
openDecodedBase64Modal(decodedContent);
|
||||
}
|
||||
};
|
||||
|
||||
const handleContextMenu = e => {
|
||||
const base64Strings = findBase64Strings(msg.content);
|
||||
const decodedContent = decodeBase64Strings(base64Strings);
|
||||
if (settings.store.clickMethod === "Left") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
decodedContent.forEach(content => copyWithToast(content));
|
||||
} else {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openDecodedBase64Modal(decodedContent);
|
||||
}
|
||||
};
|
||||
|
||||
const label = settings.store.clickMethod === "Right"
|
||||
? "Copy Decoded (Left Click) / Decode Base64 (Right Click)"
|
||||
: "Decode Base64 (Left Click) / Copy Decoded (Right Click)";
|
||||
|
||||
return {
|
||||
label,
|
||||
icon: DecodeIcon,
|
||||
message: msg,
|
||||
channel: ChannelStore.getChannel(msg.channel_id),
|
||||
onClick: handleClick,
|
||||
onContextMenu: handleContextMenu
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeButton("DecodeBase64");
|
||||
}
|
||||
});
|
|
@ -22,7 +22,7 @@ import { ModalSize, openModalLazy } from "@utils/modal";
|
|||
import { Button, Card, Forms, React, Select, Slider, TextInput, useEffect, useState } from "@webpack/common";
|
||||
import { SelectOption } from "@webpack/types";
|
||||
|
||||
import { MicrophoneSettingsModal } from "../../betterMicrophone/components";
|
||||
import { MicrophoneSettingsModal } from "../../betterMicrophone.desktop/components";
|
||||
import {
|
||||
MediaEngineStore,
|
||||
openURL,
|
|
@ -21,7 +21,7 @@ import {
|
|||
MicrophoneProfile as ScreenshareAudioProfile,
|
||||
MicrophoneStore as ScreenshareAudioStore,
|
||||
microphoneStoreDefault as screenshareAudioStoreDefault
|
||||
} from "../../betterMicrophone/stores";
|
||||
} from "../../betterMicrophone.desktop/stores";
|
||||
import { createPluginStore, ProfilableStore, profileable } from "../../philsPluginLibrary";
|
||||
import { PluginInfo } from "../constants";
|
||||
|
402
src/equicordplugins/customSounds/index.ts
Normal file
402
src/equicordplugins/customSounds/index.ts
Normal file
|
@ -0,0 +1,402 @@
|
|||
/*
|
||||
* 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 } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
const soundFileMapping: { [key: string]: string[]; } = {
|
||||
discodoDetuneURL: ["c9bfe03395cf2616891f.mp3", "9b8b7e8c94287d5491a8.mp3"],
|
||||
activitiesRocketTimeURL: ["cd402df20cddf4d85b4b.mp3"],
|
||||
activityEndURL: ["37530244cdcd5141095b.mp3"],
|
||||
activityLaunchURL: ["267f72c6f838aac3be94.mp3"],
|
||||
activityUserJoinURL: ["74c606872cea9803e310.mp3"],
|
||||
activityUserLeftURL: ["99bd2585703114d2df64.mp3"],
|
||||
asmrMessage1URL: ["d04d1ee13ab2d7d04e97.mp3"],
|
||||
bitMessage1URL: ["fd9f21c60424f7bbe603.mp3"],
|
||||
bopMessage1URL: ["f9b3c218d2bac00a50a5.mp3"],
|
||||
callCallingURL: ["11b68eb8f243b5f6c8d7.mp3", "ec09898a0bd65dfaa768.mp3"],
|
||||
callRingingURL: ["986703daecf955ce3ce3.mp3", "6345bccfecdfa67fdb97.mp3"],
|
||||
callRingingBeatURL: ["3b3a2f5f29b9cb656efb.mp3"],
|
||||
callRingingHalloweenURL: ["feb12b25f1200b97c4eb.mp3"],
|
||||
callRingingSnowHalationURL: ["99b1d8a6fe0b95e99827.mp3"],
|
||||
callRingingSnowsgivingURL: ["54527e70cf0ddaeff76f.mp3"],
|
||||
clipErrorURL: ["4185e05ac87668c95db7.mp3"],
|
||||
clipSaveURL: ["f96b272b4140be6ce8a9.mp3"],
|
||||
ddrDownURL: ["60b2fa578027733f07b2.mp3"],
|
||||
ddrLeftURL: ["6a2283291b8468b5dcbc.mp3"],
|
||||
ddrRightURL: ["ede3b86253bb4aa1615b.mp3"],
|
||||
ddrUpURL: ["89547833e1e1ebb138a4.mp3"],
|
||||
deafenURL: ["763976e8bc7c745f1bbb.mp3", "1e63dc2f54bef5c5003c.mp3"],
|
||||
disconnectDetuneURL: ["752b32be8f41b5ef55c4.mp3"],
|
||||
duckyMessage1URL: ["7732a4c7760789c64269.mp3"],
|
||||
hangStatusSelectURL: ["6f82a1ced41ffba7e474.mp3"],
|
||||
highfiveClapURL: ["b6765c41e5305ed3ccbf.mp3"],
|
||||
highfiveWhistleURL: ["ea499ca7cb948b4e89f3.mp3"],
|
||||
humanManURL: ["ba335c8e058cf0edef0c.mp3"],
|
||||
lofiMessage1URL: ["8fd01840800c5b8d5d40.mp3"],
|
||||
mention1URL: ["72d6195de7af6f6c522f.mp3"],
|
||||
mention2URL: ["5746c97822ddd998ecaa.mp3"],
|
||||
mention3URL: ["90268f54ea2962dd9a9d.mp3"],
|
||||
message1DetuneURL: ["70ae9d5bc54e4e0954f9.mp3", "ed256a76f3fe748177de.mp3"],
|
||||
message2URL: ["94d97da9d3ca65ca5c48.mp3"],
|
||||
message3URL: ["647a0cfe7a004fa8b20d.mp3"],
|
||||
muteDetuneURL: ["ad1365b07daf20cf62d5.mp3", "e8ffe6892e47d655e796.mp3"],
|
||||
overlayunlockURL: ["cf5424f20c2a9c65b6bc.mp3"],
|
||||
poggermodeAchievementUnlockURL: ["156fff4a60f8bd215dd9.mp3"],
|
||||
poggermodeApplauseURL: ["07ff9c560f9ba1c99cc8.mp3"],
|
||||
poggermodeEnabledURL: ["73eaa6460d4bdb9dcd4d.mp3"],
|
||||
poggermodeMessageSendURL: ["7e01b5cb50d9d1941f16.mp3"],
|
||||
pttStartDetuneURL: ["369f4aaf35687b9f986d.mp3", "3d1f7316482d3b37e947.mp3"],
|
||||
pttStopDetuneURL: ["b843a42563e5f3144483.mp3", "1e1af3535e94d2143b69.mp3"],
|
||||
reconnectURL: ["da2dc3239ecd9ab74be1.mp3"],
|
||||
robotManURL: ["76ff191944dd58f4835f.mp3"],
|
||||
stageWaitingURL: ["e23b4d3cf753989097f4.mp3"],
|
||||
streamEndedDetuneURL: ["9b12ba39365fff66dd95.mp3", "8f4bc39481c815b02e4c.mp3"],
|
||||
streamStartedDetuneURL: ["d954d7bfa51d58a25610.mp3", "7868133f107297d09719.mp3"],
|
||||
streamUserJoinedDetuneURL: ["e9fa25653b507623acbd.mp3", "5320b9eae726f7c7b3f5.mp3"],
|
||||
streamUserLeftDetuneURL: ["0fdb31ebfdaf7e86e7ff.mp3", "b45e91a78a4377b853b8.mp3"],
|
||||
successURL: ["1cc608397adb2bb151de.mp3"],
|
||||
undeafenDetuneURL: ["2f7089b0d3e66e7d6d67.mp3", "c87a93fd4b6918f21b8d.mp3"],
|
||||
unmuteDetuneURL: ["80f50a0752b5cd6028ac.mp3", "1bf3e7ad8588f290f1d8.mp3"],
|
||||
userJoinDetuneURL: ["93034ac8d9eba50e3354.mp3", "376a8b2b79947dabac6d.mp3"],
|
||||
userLeaveDetuneURL: ["fea7a918aecf33a04116.mp3", "9ea3b5ecbcad19a243e6.mp3"],
|
||||
userMovedDetuneURL: ["e490f52f12a18334ae94.mp3", "af380400eb22d2fa80dc.mp3"],
|
||||
vibingWumpusURL: ["38b1c58d275e828aa9b6.mp3"]
|
||||
};
|
||||
|
||||
const settings = definePluginSettings({
|
||||
discodoDetuneURL: {
|
||||
description: "Audio URL for discodo sound",
|
||||
type: OptionType.STRING,
|
||||
default: "https://www.myinstants.com/media/sounds/explosion-m.mp3",
|
||||
restartNeeded: true
|
||||
},
|
||||
activitiesRocketTimeURL: {
|
||||
description: "Audio URL for activities rocket time sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
activityEndURL: {
|
||||
description: "Audio URL for activity end sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
activityLaunchURL: {
|
||||
description: "Audio URL for activity launch sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
activityUserJoinURL: {
|
||||
description: "Audio URL for activity user join sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
activityUserLeftURL: {
|
||||
description: "Audio URL for activity user left sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
asmrMessage1URL: {
|
||||
description: "Audio URL for asmr message1 sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
bitMessage1URL: {
|
||||
description: "Audio URL for bit message1 sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
bopMessage1URL: {
|
||||
description: "Audio URL for bop message1 sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
callCallingURL: {
|
||||
description: "Audio URL for call calling sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
callRingingURL: {
|
||||
description: "Audio URL for call ringing sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
callRingingBeatURL: {
|
||||
description: "Audio URL for call ringing beat sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
callRingingHalloweenURL: {
|
||||
description: "Audio URL for call ringing halloween sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
callRingingSnowHalationURL: {
|
||||
description: "Audio URL for call ringing snow halation sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
callRingingSnowsgivingURL: {
|
||||
description: "Audio URL for call ringing snowsgiving sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
clipErrorURL: {
|
||||
description: "Audio URL for clip error sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
clipSaveURL: {
|
||||
description: "Audio URL for clip save sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
ddrDownURL: {
|
||||
description: "Audio URL for ddr down sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
ddrLeftURL: {
|
||||
description: "Audio URL for ddr left sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
ddrRightURL: {
|
||||
description: "Audio URL for ddr right sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
ddrUpURL: {
|
||||
description: "Audio URL for ddr up sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
deafenURL: {
|
||||
description: "Audio URL for deafen sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
disconnectDetuneURL: {
|
||||
description: "Audio URL for disconnect sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
duckyMessage1URL: {
|
||||
description: "Audio URL for ducky message1 sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
hangStatusSelectURL: {
|
||||
description: "Audio URL for hang status select sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
highfiveClapURL: {
|
||||
description: "Audio URL for highfive clap sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
highfiveWhistleURL: {
|
||||
description: "Audio URL for highfive whistle sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
humanManURL: {
|
||||
description: "Audio URL for human man sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
lofiMessage1URL: {
|
||||
description: "Audio URL for lofi message1 sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
mention1URL: {
|
||||
description: "Audio URL for mention1 sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
mention2URL: {
|
||||
description: "Audio URL for mention2 sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
mention3URL: {
|
||||
description: "Audio URL for mention3 sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
message2URL: {
|
||||
description: "Audio URL for message2 sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
message3URL: {
|
||||
description: "Audio URL for message3 sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
muteDetuneURL: {
|
||||
description: "Audio URL for mute sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
overlayunlockURL: {
|
||||
description: "Audio URL for overlay unlock sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
poggermodeAchievementUnlockURL: {
|
||||
description: "Audio URL for poggermode achievement unlock sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
poggermodeApplauseURL: {
|
||||
description: "Audio URL for poggermode applause sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
poggermodeEnabledURL: {
|
||||
description: "Audio URL for poggermode enabled sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
poggermodeMessageSendURL: {
|
||||
description: "Audio URL for poggermode message send sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
pttStartDetuneURL: {
|
||||
description: "Audio URL for ptt start sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
pttStopDetuneURL: {
|
||||
description: "Audio URL for ptt stop sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
reconnectURL: {
|
||||
description: "Audio URL for reconnect sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
robotManURL: {
|
||||
description: "Audio URL for robot man sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
stageWaitingURL: {
|
||||
description: "Audio URL for stage waiting sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
successURL: {
|
||||
description: "Audio URL for success sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
},
|
||||
vibingWumpusURL: {
|
||||
description: "Audio URL for vibing wumpus sound",
|
||||
type: OptionType.STRING,
|
||||
default: "",
|
||||
restartNeeded: true
|
||||
}
|
||||
});
|
||||
|
||||
function getSoundURL(settingKey: string) {
|
||||
const url = settings.store[settingKey];
|
||||
const knownAudioExtensions = [".mp3", ".wav", ".ogg", ".flac", ".aac", ".m4a"];
|
||||
|
||||
const isValidURL = (url: string) => {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const hasValidExtension = (url: string) => {
|
||||
return knownAudioExtensions.some(ext => url.toLowerCase().endsWith(ext));
|
||||
};
|
||||
|
||||
const addHttpsIfMissing = (url: string) => {
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
return `https://${url}`;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
const correctedURL = addHttpsIfMissing(url);
|
||||
|
||||
if (correctedURL && isValidURL(correctedURL) && hasValidExtension(correctedURL)) {
|
||||
return correctedURL;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "CustomSounds",
|
||||
description: "Replace Discord sounds with a custom ones",
|
||||
authors: [Devs.ScattrdBlade],
|
||||
settings,
|
||||
patches: Object.keys(soundFileMapping).flatMap(settingKey =>
|
||||
soundFileMapping[settingKey].map(file => ({
|
||||
find: file,
|
||||
replacement: {
|
||||
match: /e\.exports\s*=\s*n\.p\s*\+\s*"[a-zA-Z0-9]+\.(mp3|wav|ogg|flac|aac|m4a)"/,
|
||||
replace: () => `e.exports="${getSoundURL(settingKey)}"`
|
||||
}
|
||||
}))
|
||||
)
|
||||
});
|
13
src/equicordplugins/fakeProfile/constants.ts
Normal file
13
src/equicordplugins/fakeProfile/constants.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export const BASE_URL = "https://i.sampath.tech";
|
||||
export const API_URL = BASE_URL + "/v3/users";
|
||||
export const SKU_ID_DISCORD = "100101099222222";
|
||||
export const SKU_ID = "100101099222224";
|
||||
export const GUILD_ID = "1117373291095662623";
|
||||
export const INVITE_KEY = "ffmkewQ4R7";
|
||||
export const VERSION = "v2.17";
|
644
src/equicordplugins/fakeProfile/index.tsx
Normal file
644
src/equicordplugins/fakeProfile/index.tsx
Normal file
|
@ -0,0 +1,644 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges";
|
||||
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { classNameFactory, enableStyle } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Link } from "@components/Link";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { closeModal, Modals, openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy } from "@webpack";
|
||||
import { Button, Forms, Toasts, Tooltip, useEffect, useState } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
import virtualMerge from "virtual-merge";
|
||||
|
||||
import { API_URL, BASE_URL, SKU_ID, SKU_ID_DISCORD, VERSION } from "./constants";
|
||||
const CustomizationSection = findByCodeLazy(".customizationSectionBackground");
|
||||
const cl = classNameFactory("vc-decoration-");
|
||||
|
||||
import style from "./style.css?managed";
|
||||
import { AvatarDecoration, Colors, fakeProfileSectionProps, UserProfile, UserProfileData } from "./types";
|
||||
|
||||
|
||||
|
||||
|
||||
let UsersData = {} as Record<string, UserProfileData>;
|
||||
const UserBadges: Record<string, ProfileBadge[]> = {};
|
||||
const updateBadgesForAllUsers = () => {
|
||||
Object.keys(UsersData).forEach(userId => {
|
||||
const newBadges = UsersData[userId].badges;
|
||||
const existingBadges = UserBadges[userId] || [];
|
||||
if (newBadges) {
|
||||
newBadges.forEach((badge, index) => {
|
||||
const existingBadge = existingBadges[index];
|
||||
|
||||
if (!existingBadge) {
|
||||
const newBadge = {
|
||||
image: badge.icon,
|
||||
position: BadgePosition.START,
|
||||
description: badge.description,
|
||||
props: {
|
||||
style: {
|
||||
borderRadius: "15%",
|
||||
transform: "scale(0.9)"
|
||||
}
|
||||
},
|
||||
shouldShow: userInfo => userInfo.userId === userId,
|
||||
onClick() {
|
||||
const modalKey = openModal(props => (
|
||||
<ErrorBoundary noop onError={() => {
|
||||
closeModal(modalKey);
|
||||
VencordNative.native.openExternal("https://github.com/sampathgujarathi/fakeProfile");
|
||||
}}>
|
||||
<Modals.ModalRoot {...props}>
|
||||
<Modals.ModalHeader>
|
||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||
<Forms.FormTitle
|
||||
tag="h2"
|
||||
style={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
margin: 0
|
||||
}}
|
||||
>
|
||||
fakeProfile
|
||||
</Forms.FormTitle>
|
||||
</Flex>
|
||||
</Modals.ModalHeader>
|
||||
<Modals.ModalContent>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<img
|
||||
role="presentation"
|
||||
src="https://cdn.discordapp.com/emojis/1217777696650563614.webp"
|
||||
alt=""
|
||||
style={{ margin: "auto", display: "block" }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ padding: "0.5em", textAlign: "center" }}>
|
||||
<Forms.FormText>
|
||||
Disclaimer: This badge is generated by the fakeProfile plugin. Please be aware that it may not represent genuine credentials or affiliations. Thank you for your understanding.
|
||||
</Forms.FormText>
|
||||
</div>
|
||||
</Modals.ModalContent>
|
||||
|
||||
</Modals.ModalRoot>
|
||||
|
||||
</ErrorBoundary>
|
||||
));
|
||||
},
|
||||
};
|
||||
|
||||
addBadge(newBadge);
|
||||
|
||||
if (!UserBadges[userId]) {
|
||||
UserBadges[userId] = [];
|
||||
}
|
||||
|
||||
UserBadges[userId].splice(index, 0, newBadge);
|
||||
}
|
||||
});
|
||||
}
|
||||
existingBadges.forEach((existingBadge, index) => {
|
||||
const badgeStillExists = newBadges && newBadges[index];
|
||||
|
||||
if (!badgeStillExists) {
|
||||
removeBadge(existingBadge);
|
||||
UserBadges[userId].splice(index, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
async function loadfakeProfile(noCache = false) {
|
||||
try {
|
||||
const init = {} as RequestInit;
|
||||
if (noCache)
|
||||
init.cache = "no-cache";
|
||||
|
||||
const response = await fetch(API_URL + "/fakeProfile", init);
|
||||
const data = await response.json();
|
||||
UsersData = data;
|
||||
} catch (error) {
|
||||
console.error("Error loading fake profile:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function getUserEffect(profileId: string) {
|
||||
return UsersData[profileId] ? UsersData[profileId].profile_effect : null;
|
||||
}
|
||||
|
||||
|
||||
function encode(primary: number, accent: number): string {
|
||||
const message = `[#${primary.toString(16).padStart(6, "0")},#${accent.toString(16).padStart(6, "0")}]`;
|
||||
const padding = "";
|
||||
const encoded = Array.from(message)
|
||||
.map(x => x.codePointAt(0))
|
||||
.filter(x => x! >= 0x20 && x! <= 0x7f)
|
||||
.map(x => String.fromCodePoint(x! + 0xe0000))
|
||||
.join("");
|
||||
|
||||
return (padding || "") + " " + encoded;
|
||||
}
|
||||
|
||||
function decode(bio: string): Array<number> | null {
|
||||
if (bio == null) return null;
|
||||
const colorString = bio.match(
|
||||
/\u{e005b}\u{e0023}([\u{e0061}-\u{e0066}\u{e0041}-\u{e0046}\u{e0030}-\u{e0039}]+?)\u{e002c}\u{e0023}([\u{e0061}-\u{e0066}\u{e0041}-\u{e0046}\u{e0030}-\u{e0039}]+?)\u{e005d}/u,
|
||||
);
|
||||
if (colorString != null) {
|
||||
const parsed = [...colorString[0]]
|
||||
.map(x => String.fromCodePoint(x.codePointAt(0)! - 0xe0000))
|
||||
.join("");
|
||||
const colors = parsed
|
||||
.substring(1, parsed.length - 1)
|
||||
.split(",")
|
||||
.map(x => parseInt(x.replace("#", "0x"), 16));
|
||||
|
||||
return colors;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const settings = definePluginSettings({
|
||||
enableProfileEffects: {
|
||||
description: "Allows you to use profile effects",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false
|
||||
},
|
||||
enableProfileThemes: {
|
||||
description: "Allows you to use profile themes",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false
|
||||
},
|
||||
enableCustomBadges: {
|
||||
description: "Allows you to use custom badges",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
restartNeeded: true
|
||||
},
|
||||
enableAvatarDecorations: {
|
||||
description: "Allows you to use discord avatar decorations",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false
|
||||
},
|
||||
showCustomBadgesinmessage: {
|
||||
description: "Show custom badges in message",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
restartNeeded: true
|
||||
},
|
||||
nitroFirst: {
|
||||
description: "Banner/Avatar to use if both Nitro and fakeProfile Banner/Avatar are present",
|
||||
type: OptionType.SELECT,
|
||||
options: [
|
||||
{ label: "Nitro", value: true, default: true },
|
||||
{ label: "fakeProfile", value: false },
|
||||
]
|
||||
},
|
||||
voiceBackground: {
|
||||
description: "Use fakeProfile banners as voice chat backgrounds",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function fakeProfileSection({ hideTitle = false, hideDivider = false, noMargin = false }: fakeProfileSectionProps) {
|
||||
return <CustomizationSection
|
||||
title={!hideTitle && "fakeProfile"}
|
||||
hasBackground={true}
|
||||
hideDivider={hideDivider}
|
||||
className={noMargin && cl("section-remove-margin")}
|
||||
>
|
||||
<Flex>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await loadfakeProfile(true);
|
||||
updateBadgesForAllUsers();
|
||||
Toasts.show({
|
||||
message: "Successfully refetched fakeProfile!",
|
||||
id: Toasts.genId(),
|
||||
type: Toasts.Type.SUCCESS
|
||||
});
|
||||
}}
|
||||
size={Button.Sizes.SMALL}
|
||||
>
|
||||
Refetch fakeProfile
|
||||
</Button>
|
||||
</Flex>
|
||||
</CustomizationSection>;
|
||||
}
|
||||
|
||||
const openModalOnClick = () => {
|
||||
const modalKey = openModal(props => (
|
||||
<ErrorBoundary noop onError={() => {
|
||||
closeModal(modalKey);
|
||||
VencordNative.native.openExternal("https://github.com/sampathgujarathi/fakeProfile");
|
||||
}}>
|
||||
<Modals.ModalRoot {...props}>
|
||||
<Modals.ModalHeader>
|
||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||
<Forms.FormTitle
|
||||
tag="h2"
|
||||
style={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
margin: 0
|
||||
}}
|
||||
>
|
||||
fakeProfile
|
||||
</Forms.FormTitle>
|
||||
</Flex>
|
||||
</Modals.ModalHeader>
|
||||
<Modals.ModalContent>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<img
|
||||
role="presentation"
|
||||
src="https://cdn.discordapp.com/emojis/1217777696650563614.webp"
|
||||
alt=""
|
||||
style={{ margin: "auto", display: "block" }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ padding: "0.5em", textAlign: "center" }}>
|
||||
<Forms.FormText>
|
||||
Disclaimer: This badge is generated by the fakeProfile plugin. Please be aware that it may not represent genuine credentials or affiliations. Thank you for your understanding.
|
||||
</Forms.FormText>
|
||||
</div>
|
||||
</Modals.ModalContent>
|
||||
</Modals.ModalRoot>
|
||||
</ErrorBoundary>
|
||||
));
|
||||
};
|
||||
|
||||
function ImageIcon(path: string) {
|
||||
return ({ tooltip }: { tooltip: string; }) => (
|
||||
<Tooltip text={tooltip} >
|
||||
{(tooltipProps: any) => (
|
||||
<img {...tooltipProps} src={path} height={20} width={20} />
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
const BadgeIcon = ({ user, badgeImg, badgeText }: { user: User, badgeImg: string, badgeText: string; }) => {
|
||||
if (UsersData[user.id]?.badges) {
|
||||
const Icon = ImageIcon(badgeImg);
|
||||
const tooltip = badgeText;
|
||||
return <Icon tooltip={tooltip} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
const BadgeMain = ({ user, wantMargin = true, wantTopMargin = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; }) => {
|
||||
|
||||
const validBadges = UsersData[user.id]?.badges;
|
||||
if (!validBadges || validBadges.length === 0) return null;
|
||||
|
||||
const icons = validBadges.map((badge, index) => (
|
||||
<div onClick={openModalOnClick} >
|
||||
<BadgeIcon
|
||||
key={index}
|
||||
user={user}
|
||||
badgeImg={badge.icon}
|
||||
badgeText={badge.description}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
|
||||
return (
|
||||
<span
|
||||
className="custom-badge"
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginLeft: wantMargin ? 4 : 0,
|
||||
verticalAlign: "top",
|
||||
position: "relative",
|
||||
top: wantTopMargin ? 2 : 0,
|
||||
padding: !wantMargin ? 1 : 0,
|
||||
gap: 2
|
||||
}}
|
||||
>
|
||||
{icons}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default definePlugin({
|
||||
name: "fakeProfile",
|
||||
description: "Unlock Discord profile effects, themes, avatar decorations, and custom badges without the need for Nitro.",
|
||||
authors: [EquicordDevs.Sampath, Devs.Alyxia, Devs.Remty, Devs.AutumnVN, Devs.pylix, Devs.TheKodeToad],
|
||||
dependencies: ["MessageDecorationsAPI"],
|
||||
start: async () => {
|
||||
enableStyle(style);
|
||||
await loadfakeProfile();
|
||||
if (settings.store.enableCustomBadges) {
|
||||
updateBadgesForAllUsers();
|
||||
}
|
||||
if (settings.store.showCustomBadgesinmessage) {
|
||||
addDecoration("custom-badge", props =>
|
||||
<ErrorBoundary noop>
|
||||
<BadgeMain user={props.message?.author} wantTopMargin={true} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
const response = await fetch(BASE_URL + "/fakeProfile");
|
||||
const data = await response.json();
|
||||
if (data.version !== VERSION) {
|
||||
Toasts.show({
|
||||
message: "There is an update available for the fakeProfile plugin.",
|
||||
id: Toasts.genId(),
|
||||
type: Toasts.Type.MESSAGE,
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM
|
||||
}
|
||||
});
|
||||
}
|
||||
setInterval(async () => {
|
||||
await loadfakeProfile(true);
|
||||
if (settings.store.enableCustomBadges) {
|
||||
updateBadgesForAllUsers();
|
||||
}
|
||||
}, data.reloadInterval);
|
||||
},
|
||||
stop: () => {
|
||||
if (settings.store.showCustomBadgesinmessage) {
|
||||
removeDecoration("custom-badge");
|
||||
}
|
||||
},
|
||||
patches: [
|
||||
{
|
||||
find: "UserProfileStore",
|
||||
replacement: {
|
||||
match: /(?<=getUserProfile\(\i\){return )(\i\[\i\])/,
|
||||
replace: "$self.profileDecodeHook($1)"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "getAvatarDecorationURL:",
|
||||
replacement: {
|
||||
match: /(?<=function \i\(\i\){)(?=let{avatarDecoration)/,
|
||||
replace: "const vcDecoration = (() => { return $self.getAvatarDecorationURL(arguments[0]); })(); if (vcDecoration) return vcDecoration;"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".USER_SETTINGS_RESET_PROFILE_THEME",
|
||||
replacement: {
|
||||
match: /RESET_PROFILE_THEME}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/,
|
||||
replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "DefaultCustomizationSections",
|
||||
replacement: {
|
||||
match: /(?<=USER_SETTINGS_AVATAR_DECORATION},"decoration"\),)/,
|
||||
replace: "$self.fakeProfileSection(),"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".NITRO_BANNER,",
|
||||
replacement: {
|
||||
match: /\?\(0,\i\.jsx\)\(\i,{type:\i,shown/,
|
||||
replace: "&&$self.shouldShowBadge(arguments[0])$&"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "=!1,canUsePremiumCustomization:",
|
||||
replacement: {
|
||||
match: /(\i)\.premiumType/,
|
||||
replace: "$self.premiumHook($1)||$&"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".banner)==null",
|
||||
replacement: {
|
||||
match: /(?<=void 0:)\i.getPreviewBanner\(\i,\i,\i\)/,
|
||||
replace: "$self.useBannerHook(arguments[0])||$&"
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "\"data-selenium-video-tile\":",
|
||||
predicate: () => settings.store.voiceBackground,
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=function\((\i),\i\)\{)(?=let.{20,40},style:)/,
|
||||
replace: "$1.style=$self.voiceBackgroundHook($1);"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "getUserAvatarURL:",
|
||||
replacement: [
|
||||
{
|
||||
match: /(getUserAvatarURL:)(\i),/,
|
||||
replace: "$1$self.getAvatarHook($2),"
|
||||
},
|
||||
{
|
||||
match: /(getUserAvatarURL:\i\(\){return )(\i)}/,
|
||||
replace: "$1$self.getAvatarHook($2)}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "isAvatarDecorationAnimating:",
|
||||
group: true,
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=TryItOut:\i,guildId:\i}\),)(?<=user:(\i).+?)/,
|
||||
replace: "vcAvatarDecoration=$self.useUserAvatarDecoration($1),"
|
||||
},
|
||||
{
|
||||
match: /(?<={avatarDecoration:).{1,20}?(?=,)(?<=avatarDecorationOverride:(\i).+?)/,
|
||||
replace: "$1??vcAvatarDecoration??($&)"
|
||||
},
|
||||
{
|
||||
match: /(?<=size:\i}\),\[)/,
|
||||
replace: "vcAvatarDecoration,"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "renderAvatarWithPopout(){",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=\)\({(?:(?:.(?!\)}))*,)?avatarDecoration:)(\i)\.avatarDecoration(?=,|}\))/,
|
||||
replace: "$self.useUserAvatarDecoration($1)??$&"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
settingsAboutComponent: () => (
|
||||
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle tag="h3">Usage</Forms.FormTitle>
|
||||
<Link href="https://github.com/sampathgujarathi/fakeProfile">CLICK HERE TO GET PROFILE EFFECTS, CUSTOM BADGES, BANNER OR ANIMATED PFP</Link>
|
||||
<Forms.FormText>
|
||||
Enable Profile Themes to use fake profile themes. <br />
|
||||
To set your own colors:
|
||||
<ul>
|
||||
<li>• go to your profile settings</li>
|
||||
<li>• choose your own colors in the Nitro preview</li>
|
||||
<li>• click the "Copy 3y3" button</li>
|
||||
<li>• paste the invisible text anywhere in your bio</li>
|
||||
</ul><br />
|
||||
</Forms.FormText>
|
||||
</Forms.FormSection>
|
||||
),
|
||||
settings,
|
||||
profileDecodeHook(user: UserProfile) {
|
||||
if (user) {
|
||||
if (settings.store.enableProfileEffects || settings.store.enableProfileThemes) {
|
||||
let mergeData: Partial<UserProfile> = {};
|
||||
const profileEffect = getUserEffect(user.userId);
|
||||
const colors = decode(user.bio);
|
||||
if (settings.store.enableProfileEffects && profileEffect) {
|
||||
mergeData = {
|
||||
...mergeData,
|
||||
profileEffectId: profileEffect
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
if (settings.store.enableProfileThemes && colors) {
|
||||
mergeData = {
|
||||
...mergeData,
|
||||
premiumType: 2,
|
||||
themeColors: colors
|
||||
};
|
||||
}
|
||||
return virtualMerge(user, mergeData as UserProfile);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
return user;
|
||||
},
|
||||
SKU_ID_DISCORD,
|
||||
SKU_ID,
|
||||
useUserAvatarDecoration(user?: User): { asset: string; skuId: string; animated: boolean; } | null {
|
||||
const [avatarDecoration, setAvatarDecoration] = useState<{ asset: string; skuId: string; animated: boolean; } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserAssets = async () => {
|
||||
try {
|
||||
if (user?.id) {
|
||||
const userAssetsData = UsersData[user.id];
|
||||
if (userAssetsData?.decoration) {
|
||||
setAvatarDecoration({
|
||||
asset: userAssetsData.decoration?.asset,
|
||||
skuId: userAssetsData.decoration?.skuId,
|
||||
animated: userAssetsData.decoration?.animated
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching user assets:", error);
|
||||
}
|
||||
};
|
||||
fetchUserAssets();
|
||||
}, [user, UsersData]);
|
||||
|
||||
if (!user || !settings.store.enableAvatarDecorations) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return avatarDecoration ? {
|
||||
asset: avatarDecoration.asset,
|
||||
skuId: avatarDecoration.skuId,
|
||||
animated: avatarDecoration.animated
|
||||
} : null;
|
||||
},
|
||||
voiceBackgroundHook({ className, participantUserId }: any) {
|
||||
if (className.includes("tile_")) {
|
||||
if (UsersData[participantUserId]) {
|
||||
return {
|
||||
backgroundImage: `url(${UsersData[participantUserId].banner})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat"
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
useBannerHook({ displayProfile }: any) {
|
||||
if (displayProfile?.banner && settings.store.nitroFirst) return;
|
||||
if (UsersData[displayProfile?.userId] && UsersData[displayProfile?.userId].banner) return UsersData[displayProfile?.userId].banner;
|
||||
},
|
||||
|
||||
premiumHook({ userId }: any) {
|
||||
if (UsersData[userId]) return 2;
|
||||
},
|
||||
|
||||
shouldShowBadge({ displayProfile, user }: any) {
|
||||
return displayProfile?.banner && (UsersData[user.id] || settings.store.nitroFirst);
|
||||
},
|
||||
getAvatarHook: (original: any) => (user: User, animated: boolean, size: number) => {
|
||||
if (!settings.store.nitroFirst && user.avatar?.startsWith("a_")) return original(user, animated, size);
|
||||
|
||||
return UsersData[user.id]?.avatar ?? original(user, animated, size);
|
||||
},
|
||||
getAvatarDecorationURL({ avatarDecoration, canAnimate }: { avatarDecoration: AvatarDecoration | null; canAnimate?: boolean; }) {
|
||||
if (!avatarDecoration || !settings.store.enableAvatarDecorations) return;
|
||||
if (canAnimate && avatarDecoration?.animated !== false) {
|
||||
if (avatarDecoration?.skuId === SKU_ID) {
|
||||
const url = new URL(`https://i.sampath.tech/avatar-decoration-presets/a_${avatarDecoration?.asset}.png`);
|
||||
return url.toString();
|
||||
} else {
|
||||
const url = new URL(`https://cdn.discordapp.com/avatar-decoration-presets/${avatarDecoration?.asset}.png`);
|
||||
return url.toString();
|
||||
}
|
||||
} else {
|
||||
if (avatarDecoration?.skuId === SKU_ID) {
|
||||
const url = new URL(`https://i.sampath.tech/avatar-decoration-presets/${avatarDecoration?.asset}.png`);
|
||||
return url.toString();
|
||||
} else {
|
||||
const url = new URL(`https://cdn.discordapp.com/avatar-decoration-presets/${avatarDecoration?.asset}.png?passthrough=false`);
|
||||
return url.toString();
|
||||
}
|
||||
}
|
||||
},
|
||||
fakeProfileSection: ErrorBoundary.wrap(fakeProfileSection),
|
||||
toolboxActions: {
|
||||
async "Refetch fakeProfile"() {
|
||||
await loadfakeProfile(true);
|
||||
updateBadgesForAllUsers();
|
||||
Toasts.show({
|
||||
message: "Successfully refetched fakeProfile!",
|
||||
id: Toasts.genId(),
|
||||
type: Toasts.Type.SUCCESS
|
||||
});
|
||||
}
|
||||
},
|
||||
addCopy3y3Button: ErrorBoundary.wrap(function ({ primary, accent }: Colors) {
|
||||
return <Button
|
||||
onClick={() => {
|
||||
const colorString = encode(primary, accent);
|
||||
copyWithToast(colorString);
|
||||
}}
|
||||
color={Button.Colors.PRIMARY}
|
||||
size={Button.Sizes.XLARGE}
|
||||
className={Margins.left16}
|
||||
>Copy 3y3
|
||||
</Button >;
|
||||
}, { noop: true }),
|
||||
});
|
12
src/equicordplugins/fakeProfile/style.css
Normal file
12
src/equicordplugins/fakeProfile/style.css
Normal file
|
@ -0,0 +1,12 @@
|
|||
:is([class*="userProfile"], [class*="userPopout"]) [class*="bannerPremium"] {
|
||||
background: center / cover no-repeat;
|
||||
}
|
||||
|
||||
[class*="NonPremium"]:has([class*="bannerPremium"]) [class*="avatarPositionNormal"],
|
||||
[class*="PremiumWithoutBanner"]:has([class*="bannerPremium"]) [class*="avatarPositionPremiumNoBanner"] {
|
||||
top: 76px;
|
||||
}
|
||||
|
||||
[style*="background-image"] [class*="background_"] {
|
||||
background-color: transparent !important;
|
||||
}
|
49
src/equicordplugins/fakeProfile/types.ts
Normal file
49
src/equicordplugins/fakeProfile/types.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { User } from "discord-types/general";
|
||||
|
||||
export interface Badge {
|
||||
asset: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
link?: string;
|
||||
}
|
||||
|
||||
export interface DecorationData {
|
||||
asset: string;
|
||||
skuId: string;
|
||||
animated: boolean;
|
||||
}
|
||||
export interface AvatarDecoration {
|
||||
asset: string;
|
||||
skuId: string;
|
||||
animated: boolean;
|
||||
}
|
||||
export interface UserProfile extends User {
|
||||
profileEffectId: string;
|
||||
userId: string;
|
||||
themeColors?: Array<number>;
|
||||
|
||||
}
|
||||
export interface UserProfileData {
|
||||
profile_effect: string;
|
||||
banner: string;
|
||||
avatar: string;
|
||||
badges: Badge[];
|
||||
decoration: DecorationData;
|
||||
}
|
||||
|
||||
export interface Colors {
|
||||
primary: number;
|
||||
accent: number;
|
||||
}
|
||||
|
||||
export interface fakeProfileSectionProps {
|
||||
hideTitle?: boolean;
|
||||
hideDivider?: boolean;
|
||||
noMargin?: boolean;
|
||||
}
|
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;
|
||||
}
|
67
src/equicordplugins/grammarFix/index.ts
Normal file
67
src/equicordplugins/grammarFix/index.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import {
|
||||
addPreSendListener,
|
||||
removePreSendListener,
|
||||
SendListener,
|
||||
} from "@api/MessageEvents";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
const getPresend = dictionary => {
|
||||
const presendObject: SendListener = (_, msg) => {
|
||||
msg.content = msg.content.trim();
|
||||
if (!msg.content.includes("```") && /\w/.test(msg.content.charAt(0))) {
|
||||
const re = new RegExp(
|
||||
`(^|(?<=[^A-Z0-9]+))(${Object.keys(dictionary)
|
||||
.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
||||
.join("|")})((?=[^A-Z0-9]+)|$)`,
|
||||
"gi"
|
||||
);
|
||||
if (re !== null) {
|
||||
msg.content = msg.content.replace(re, match => {
|
||||
return dictionary[match.toLowerCase()] || match;
|
||||
});
|
||||
}
|
||||
|
||||
if (/[A-Z0-9]/i.test(msg.content.charAt(msg.content.length - 1))) {
|
||||
if (!msg.content.startsWith("http", msg.content.lastIndexOf(" ") + 1))
|
||||
msg.content += ".";
|
||||
}
|
||||
|
||||
// Ensure sentences are capitalized after punctuation
|
||||
msg.content = msg.content.replace(/([.!?])\s*(\w)/g, match =>
|
||||
match.toUpperCase()
|
||||
);
|
||||
|
||||
// Ensure the first character of the entire message is capitalized
|
||||
if (!msg.content.startsWith("http")) {
|
||||
msg.content =
|
||||
msg.content.charAt(0).toUpperCase() + msg.content.slice(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
return presendObject;
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "GrammarFix",
|
||||
description: "Automatic punctuation, capitalization, and word replacement.",
|
||||
authors: [EquicordDevs.unstream],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
async start() {
|
||||
let dictionary = await fetch(
|
||||
"https://raw.githubusercontent.com/wont-stream/dictionary/3d52fecd9aca5dfee0fcde0df2c2af357f977df7/index.min.json"
|
||||
);
|
||||
dictionary = await dictionary.json();
|
||||
|
||||
addPreSendListener(getPresend(dictionary));
|
||||
},
|
||||
stop() {
|
||||
removePreSendListener(getPresend({}));
|
||||
},
|
||||
});
|
152
src/equicordplugins/newPluginsManager/NewPluginsModal.tsx
Normal file
152
src/equicordplugins/newPluginsManager/NewPluginsModal.tsx
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { PluginCard } from "@components/PluginSettings";
|
||||
import { ChangeList } from "@utils/ChangeList";
|
||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { Alerts, Button, Flex, Forms, Parser, React, Text, Tooltip, useMemo } from "@webpack/common";
|
||||
|
||||
import Plugins from "~plugins";
|
||||
|
||||
import { getNewPlugins, writeKnownPlugins } from "./knownPlugins";
|
||||
|
||||
const cl = classNameFactory("vc-plugins-");
|
||||
|
||||
let hasSeen = false;
|
||||
|
||||
// Most of this was stolen from PluginSettings directly.
|
||||
|
||||
export function NewPluginsModal({ modalProps, newPlugins }: { modalProps: ModalProps; newPlugins: Set<string>; }) {
|
||||
const settings = useSettings();
|
||||
const changes = React.useMemo(() => new ChangeList<string>(), []);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => void (changes.hasChanges && Alerts.show({
|
||||
title: "Restart required",
|
||||
body: (
|
||||
<>
|
||||
<p>The following plugins require a restart:</p>
|
||||
<div>{changes.map((s, i) => (
|
||||
<>
|
||||
{i > 0 && ", "}
|
||||
{Parser.parse("`" + s + "`")}
|
||||
</>
|
||||
))}</div>
|
||||
</>
|
||||
),
|
||||
confirmText: "Restart now",
|
||||
cancelText: "Later!",
|
||||
onConfirm: () => location.reload()
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const depMap = React.useMemo(() => {
|
||||
const o = {} as Record<string, string[]>;
|
||||
for (const plugin in Plugins) {
|
||||
const deps = Plugins[plugin].dependencies;
|
||||
if (deps) {
|
||||
for (const dep of deps) {
|
||||
o[dep] ??= [];
|
||||
o[dep].push(plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}, []);
|
||||
|
||||
const sortedPlugins = useMemo(() => [...newPlugins].map(pn => Plugins[pn])
|
||||
.sort((a, b) => a.name.localeCompare(b.name)), []);
|
||||
|
||||
const plugins = [] as JSX.Element[];
|
||||
const requiredPlugins = [] as JSX.Element[];
|
||||
|
||||
for (const p of sortedPlugins) {
|
||||
if (p.hidden)
|
||||
continue;
|
||||
|
||||
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
|
||||
|
||||
if (isRequired) {
|
||||
const tooltipText = p.required
|
||||
? "This plugin is required for Vencord to function."
|
||||
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
|
||||
|
||||
requiredPlugins.push(
|
||||
<Tooltip text={tooltipText} key={p.name}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<PluginCard
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onRestartNeeded={name => changes.handleChange(name)}
|
||||
disabled={true}
|
||||
plugin={p}
|
||||
key={p.name}
|
||||
/>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
plugins.push(
|
||||
<PluginCard
|
||||
onRestartNeeded={name => changes.handleChange(name)}
|
||||
disabled={false}
|
||||
plugin={p}
|
||||
key={p.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return <ModalRoot {...modalProps} size={ModalSize.MEDIUM} >
|
||||
<ModalHeader>
|
||||
<Text variant="heading-lg/semibold">New Plugins ({[...plugins, ...requiredPlugins].length})</Text>
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<div className={cl("grid")}>
|
||||
{[...plugins, ...requiredPlugins]}
|
||||
</div>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<Flex direction={Flex.Direction.HORIZONTAL_REVERSE}>
|
||||
<Button
|
||||
color={Button.Colors.GREEN}
|
||||
onClick={async () => {
|
||||
await writeKnownPlugins();
|
||||
modalProps.onClose();
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</Flex>
|
||||
</ModalFooter>
|
||||
</ModalRoot>;
|
||||
}
|
||||
|
||||
|
||||
function makeDependencyList(deps: string[]) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Forms.FormText>This plugin is required by:</Forms.FormText>
|
||||
{deps.map((dep: string) => <Forms.FormText className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export async function openNewPluginsModal() {
|
||||
const newPlugins = await getNewPlugins();
|
||||
if (newPlugins.size && !hasSeen) {
|
||||
hasSeen = true;
|
||||
openModal(modalProps => (
|
||||
<NewPluginsModal
|
||||
modalProps={modalProps}
|
||||
newPlugins={newPlugins}
|
||||
/>
|
||||
));
|
||||
}
|
||||
}
|
25
src/equicordplugins/newPluginsManager/index.tsx
Normal file
25
src/equicordplugins/newPluginsManager/index.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 { KNOWN_PLUGINS_DATA_KEY } from "./knownPlugins";
|
||||
import { openNewPluginsModal } from "./NewPluginsModal";
|
||||
|
||||
export default definePlugin({
|
||||
name: "NewPluginsManager",
|
||||
description: "Utility that notifies you when new plugins are added to Vencord",
|
||||
authors: [Devs.Sqaaakoi],
|
||||
enabledByDefault: true,
|
||||
flux: {
|
||||
async POST_CONNECTION_OPEN() {
|
||||
openNewPluginsModal();
|
||||
}
|
||||
},
|
||||
openNewPluginsModal,
|
||||
KNOWN_PLUGINS_DATA_KEY
|
||||
});
|
32
src/equicordplugins/newPluginsManager/knownPlugins.ts
Normal file
32
src/equicordplugins/newPluginsManager/knownPlugins.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 plugins from "~plugins";
|
||||
|
||||
export const KNOWN_PLUGINS_DATA_KEY = "NewPluginsManager_KnownPlugins";
|
||||
|
||||
export async function getKnownPlugins(): Promise<Set<string>> {
|
||||
let knownPlugins = await DataStore.get(KNOWN_PLUGINS_DATA_KEY) as string[];
|
||||
if (knownPlugins === undefined) {
|
||||
knownPlugins = Object.keys(plugins);
|
||||
DataStore.set(KNOWN_PLUGINS_DATA_KEY, knownPlugins);
|
||||
}
|
||||
return new Set(knownPlugins);
|
||||
}
|
||||
|
||||
export async function getNewPlugins(): Promise<Set<string>> {
|
||||
const currentPlugins = Object.keys(plugins);
|
||||
const knownPlugins = await getKnownPlugins();
|
||||
return new Set(currentPlugins.filter(p => !knownPlugins.has(p)));
|
||||
}
|
||||
|
||||
export async function writeKnownPlugins(): Promise<void> {
|
||||
const currentPlugins = Object.keys(plugins);
|
||||
const knownPlugins = await getKnownPlugins();
|
||||
DataStore.set(KNOWN_PLUGINS_DATA_KEY, [...new Set([...currentPlugins, ...knownPlugins])]);
|
||||
}
|
|
@ -19,7 +19,7 @@
|
|||
import { Logger } from "@utils/Logger";
|
||||
import { lodash } from "@webpack/common";
|
||||
|
||||
import { MicrophoneProfile, MicrophoneStore } from "../../betterMicrophone/stores";
|
||||
import { MicrophoneProfile, MicrophoneStore } from "../../betterMicrophone.desktop/stores";
|
||||
import { ProfilableStore, replaceObjectValuesIfExist, types } from "..";
|
||||
|
||||
export function getDefaultAudioTransportationOptions(connection: types.Connection) {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
import { Logger } from "@utils/Logger";
|
||||
import { lodash } from "@webpack/common";
|
||||
|
||||
import { ScreenshareProfile, ScreenshareStore } from "../../betterScreenshare/stores";
|
||||
import { ScreenshareProfile, ScreenshareStore } from "../../betterScreenshare.desktop/stores";
|
||||
import { ProfilableStore, replaceObjectValuesIfExist, types, utils } from "..";
|
||||
|
||||
|
||||
|
|
144
src/equicordplugins/replaceActivityTypes/ReplaceSettings.tsx
Normal file
144
src/equicordplugins/replaceActivityTypes/ReplaceSettings.tsx
Normal file
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { identity } from "@utils/misc";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Card, Forms, React, Select, SnowflakeUtils, Switch } from "@webpack/common";
|
||||
|
||||
import { makeEmptyAppId } from ".";
|
||||
import { ActivityType, RpcApp, SettingsProps } from "./types";
|
||||
|
||||
|
||||
const RPCUtils = findByPropsLazy("fetchApplicationsRPC", "getRemoteIconURL");
|
||||
|
||||
const cachedApps: any = {};
|
||||
async function lookupApp(appId: string): Promise<RpcApp | null> {
|
||||
if (cachedApps[appId]) return cachedApps[appId];
|
||||
const socket: any = {};
|
||||
try {
|
||||
await RPCUtils.fetchApplicationsRPC(socket, appId);
|
||||
console.log(`Lookup finished for ${socket.application.name}`);
|
||||
cachedApps[appId] = socket.application;
|
||||
return socket.application;
|
||||
} catch {
|
||||
console.log(`Lookup failed for ${appId}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidSnowflake(v: string) {
|
||||
const regex = /^\d{17,20}$/;
|
||||
return regex.test(v) && !Number.isNaN(SnowflakeUtils.extractTimestamp(v));
|
||||
}
|
||||
|
||||
export function ReplaceTutorial() {
|
||||
return (
|
||||
<>
|
||||
<Forms.FormTitle tag="h3">How to get an Application ID</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
The method of getting an app's id will differ depending on what app it is. If the source code is available you can most likely find it inside the app's repository.
|
||||
</Forms.FormText>
|
||||
<Forms.FormText>
|
||||
Another method is to start the app in question, then open Discord's console and look for a log from RPCServer saying something like
|
||||
<code>"cmd: 'SET_ACTIVITY'"</code> with your app's name somewhere inside
|
||||
</Forms.FormText>
|
||||
|
||||
<Forms.FormTitle tag="h3" style={{ color: "var(--text-danger)", textAlign: "center" }}>
|
||||
Note: ActivityTypes other than Playing will only show timestamps on Mobile. It's a Discord issue.
|
||||
</Forms.FormTitle>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ReplaceSettings({ appIds, update, save }: SettingsProps) {
|
||||
async function onChange(val: string | boolean, index: number, key: string) {
|
||||
if (index === appIds.length - 1)
|
||||
appIds.push(makeEmptyAppId());
|
||||
|
||||
appIds[index][key] = val;
|
||||
|
||||
if (val && key === "appId") {
|
||||
const tempApp = await lookupApp(val.toString());
|
||||
appIds[index].appName = tempApp?.name || "Unknown";
|
||||
}
|
||||
|
||||
if (appIds[index].appId === "" && index !== appIds.length - 1)
|
||||
appIds.splice(index, 1);
|
||||
|
||||
save();
|
||||
update();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
appIds.map((setting, i) =>
|
||||
<Card style={{ padding: "1em 1em 0" }}>
|
||||
<Switch
|
||||
value={setting.enabled}
|
||||
onChange={value => {
|
||||
onChange(value, i, "enabled");
|
||||
}}
|
||||
className={Margins.bottom16}
|
||||
hideBorder={true}
|
||||
>
|
||||
{setting.appName}
|
||||
</Switch>
|
||||
<Forms.FormTitle>Application ID</Forms.FormTitle>
|
||||
<CheckedTextInput
|
||||
value={setting.appId}
|
||||
onChange={async v => {
|
||||
onChange(v, i, "appId");
|
||||
}}
|
||||
validate={v =>
|
||||
!v || isValidSnowflake(v) || "Invalid appId, must be a snowflake"
|
||||
}
|
||||
/>
|
||||
{setting.activityType === ActivityType.STREAMING &&
|
||||
<>
|
||||
<Forms.FormTitle>Stream URL</Forms.FormTitle>
|
||||
<CheckedTextInput
|
||||
value={setting.streamUrl}
|
||||
onChange={async v => {
|
||||
onChange(v, i, "streamUrl");
|
||||
}}
|
||||
validate={st => !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(st) && "Only Twitch and Youtube urls will work." || true}
|
||||
/>
|
||||
</>}
|
||||
<Forms.FormTitle>New activity type</Forms.FormTitle>
|
||||
<Select
|
||||
options={[
|
||||
{ label: "Playing", value: ActivityType.PLAYING },
|
||||
{ label: "Watching", value: ActivityType.WATCHING },
|
||||
{ label: "Listening", value: ActivityType.LISTENING },
|
||||
{ label: "Competing", value: ActivityType.COMPETING },
|
||||
{ label: "Streaming", value: ActivityType.STREAMING }
|
||||
]}
|
||||
select={value => {
|
||||
onChange(value, i, "activityType");
|
||||
}}
|
||||
className={Margins.bottom16}
|
||||
isSelected={value => setting.activityType === value}
|
||||
serialize={identity}
|
||||
/>
|
||||
<Switch
|
||||
value={setting.swapNameAndDetails}
|
||||
onChange={value => {
|
||||
onChange(value, i, "swapNameAndDetails");
|
||||
}}
|
||||
className={Margins.bottom16}
|
||||
hideBorder={true}
|
||||
>
|
||||
Swap presence name and details
|
||||
</Switch>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
90
src/equicordplugins/replaceActivityTypes/index.tsx
Normal file
90
src/equicordplugins/replaceActivityTypes/index.tsx
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 your mom lol
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { DataStore } from "@api/index";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { React } from "@webpack/common";
|
||||
|
||||
import { ReplaceSettings, ReplaceTutorial } from "./ReplaceSettings";
|
||||
import { Activity, ActivityType, AppIdSetting } from "./types";
|
||||
|
||||
const APP_IDS_KEY = "ReplaceActivityType_appids";
|
||||
|
||||
export const makeEmptyAppId: () => AppIdSetting = () => ({
|
||||
appId: "",
|
||||
appName: "Unknown",
|
||||
streamUrl: "",
|
||||
swapNameAndDetails: false,
|
||||
activityType: ActivityType.PLAYING,
|
||||
enabled: true
|
||||
});
|
||||
|
||||
let appIds = [makeEmptyAppId()];
|
||||
|
||||
const settings = definePluginSettings({
|
||||
replacedAppIds: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "",
|
||||
component: () => {
|
||||
const update = useForceUpdater();
|
||||
return (
|
||||
<>
|
||||
<ReplaceSettings
|
||||
appIds={appIds}
|
||||
update={update}
|
||||
save={async () => DataStore.set(APP_IDS_KEY, appIds)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "ReplaceActivityTypes",
|
||||
description: "Replace the activity type (Playing) of any rich presence app (rats in my vencord?)",
|
||||
authors: [Devs.Nyako],
|
||||
patches: [
|
||||
// how has this patch not broken yet lol (i do not like fixing patches tho)
|
||||
{
|
||||
find: '="LocalActivityStore",',
|
||||
replacement: {
|
||||
match: /LOCAL_ACTIVITY_UPDATE:function\((\i)\)\{/,
|
||||
replace: "$&$self.patchActivity($1.activity);",
|
||||
}
|
||||
}
|
||||
],
|
||||
settings,
|
||||
settingsAboutComponent: () => <ReplaceTutorial />,
|
||||
|
||||
async start() {
|
||||
appIds = await DataStore.get(APP_IDS_KEY) ?? [makeEmptyAppId()];
|
||||
},
|
||||
|
||||
patchActivity(activity: Activity) {
|
||||
if (!activity) return;
|
||||
console.log(activity);
|
||||
appIds.forEach(app => {
|
||||
if (app.enabled && app.appId === activity.application_id) {
|
||||
activity.type = app.activityType;
|
||||
|
||||
if (app.activityType === ActivityType.STREAMING && app.streamUrl) {
|
||||
activity.url = app.streamUrl;
|
||||
}
|
||||
|
||||
if (app.swapNameAndDetails) {
|
||||
const media = activity.details;
|
||||
activity.details = activity.name;
|
||||
activity.name = media;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
60
src/equicordplugins/replaceActivityTypes/types.ts
Normal file
60
src/equicordplugins/replaceActivityTypes/types.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
|
||||
*/
|
||||
|
||||
export type AppIdSetting = {
|
||||
appName: string;
|
||||
appId: string;
|
||||
swapNameAndDetails: boolean;
|
||||
activityType: ActivityType;
|
||||
streamUrl: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export interface Activity {
|
||||
state: string;
|
||||
details: string;
|
||||
timestamps?: {
|
||||
start?: number;
|
||||
end?: number;
|
||||
};
|
||||
url?: string;
|
||||
assets: ActivityAssets;
|
||||
buttons?: Array<string>;
|
||||
name: string;
|
||||
application_id: string;
|
||||
metadata?: {
|
||||
button_urls?: Array<string>;
|
||||
};
|
||||
type: number;
|
||||
}
|
||||
|
||||
export interface ActivityAssets {
|
||||
large_image: string;
|
||||
large_text: string;
|
||||
small_image: string;
|
||||
small_text: string;
|
||||
}
|
||||
|
||||
export const enum ActivityType {
|
||||
PLAYING = 0,
|
||||
STREAMING = 1,
|
||||
LISTENING = 2,
|
||||
WATCHING = 3,
|
||||
COMPETING = 5
|
||||
}
|
||||
|
||||
export interface SettingsProps {
|
||||
appIds: AppIdSetting[];
|
||||
update: () => void;
|
||||
save: () => void;
|
||||
}
|
||||
|
||||
export interface RpcApp {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
flags: number;
|
||||
}
|
100
src/equicordplugins/translatePlus/index.tsx
Normal file
100
src/equicordplugins/translatePlus/index.tsx
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addButton, removeButton } 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 { translate } from "./utils/misc";
|
||||
import { TranslateChatBarIcon, TranslateIcon } from "./utils/TranslateIcon";
|
||||
import { handleTranslate, TranslationAccessory } from "./utils/TranslationAccessory";
|
||||
|
||||
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="vc-trans"
|
||||
label="Translate"
|
||||
icon={TranslateIcon}
|
||||
action={async () => {
|
||||
const trans = await translate("received", message.content);
|
||||
handleTranslate(message.id, trans);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "Translate+",
|
||||
description: "Translate messages with Google Translate and Toki Pona AI",
|
||||
authors: [Devs.Ven, EquicordDevs.Prince527],
|
||||
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"],
|
||||
settings,
|
||||
contextMenus: {
|
||||
"message": messageCtxPatch
|
||||
},
|
||||
// not used, just here in case some other plugin wants it or w/e
|
||||
translate,
|
||||
|
||||
start() {
|
||||
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
||||
|
||||
addChatBarButton("vc-translate", TranslateChatBarIcon);
|
||||
|
||||
addButton("vc-translate", message => {
|
||||
if (!message.content) return null;
|
||||
|
||||
return {
|
||||
label: "Translate",
|
||||
icon: TranslateIcon,
|
||||
message,
|
||||
channel: ChannelStore.getChannel(message.channel_id),
|
||||
onClick: async () => {
|
||||
const trans = await translate("received", message.content);
|
||||
handleTranslate(message.id, trans);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
this.preSend = addPreSendListener(async (_, message) => {
|
||||
if (!settings.store.autoTranslate) return;
|
||||
if (!message.content) return;
|
||||
|
||||
message.content = (await translate("sent", message.content)).text;
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removeChatBarButton("vc-translate");
|
||||
removeButton("vc-translate");
|
||||
removeAccessory("vc-translation");
|
||||
},
|
||||
});
|
75237
src/equicordplugins/translatePlus/misc/dictionary.ts
Normal file
75237
src/equicordplugins/translatePlus/misc/dictionary.ts
Normal file
File diff suppressed because it is too large
Load diff
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 Language = 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;
|
69
src/equicordplugins/translatePlus/settings.ts
Normal file
69
src/equicordplugins/translatePlus/settings.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { OptionType } from "@utils/types";
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
receivedInput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Input language for received messages",
|
||||
default: "auto",
|
||||
hidden: true
|
||||
},
|
||||
receivedOutput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Output language for received messages",
|
||||
default: "en",
|
||||
hidden: true
|
||||
},
|
||||
sentInput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Input language for sent messages",
|
||||
default: "auto",
|
||||
hidden: true
|
||||
},
|
||||
sentOutput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Output language for sent messages",
|
||||
default: "en",
|
||||
hidden: true
|
||||
},
|
||||
autoTranslate: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Automatically translate your messages before sending. You can also shift/right click the translate button to toggle this",
|
||||
default: false
|
||||
},
|
||||
showChatBarButton: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show translate button in chat bar",
|
||||
default: true
|
||||
},
|
||||
tokiPonaAPI: {
|
||||
type: OptionType.STRING,
|
||||
description: "An API to translate messages to Toki Pona",
|
||||
default: "https://aiapi.serversmp.xyz/toki"
|
||||
},
|
||||
tokiPonaAuth: {
|
||||
type: OptionType.STRING,
|
||||
description: "An API key to use with the Toki Pona API",
|
||||
default: ""
|
||||
}
|
||||
}).withPrivateSettings<{
|
||||
showAutoTranslateAlert: boolean;
|
||||
}>();
|
41
src/equicordplugins/translatePlus/style.css
Normal file
41
src/equicordplugins/translatePlus/style.css
Normal file
|
@ -0,0 +1,41 @@
|
|||
.vc-trans-modal-content {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.vc-trans-modal-header {
|
||||
justify-content: space-between;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.vc-trans-modal-header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.vc-trans-accessory {
|
||||
color: var(--text-muted);
|
||||
margin-top: 0.5em;
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.vc-trans-accessory svg {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.vc-trans-dismiss {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
color: var(--text-link);
|
||||
}
|
||||
|
||||
.vc-trans-dismiss:is(:hover, :focus) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.vc-trans-auto-translate {
|
||||
color: var(--green-360);
|
||||
}
|
||||
|
||||
.vc-trans-chat-button {
|
||||
scale: 1.085;
|
||||
}
|
87
src/equicordplugins/translatePlus/utils/TranslateIcon.tsx
Normal file
87
src/equicordplugins/translatePlus/utils/TranslateIcon.tsx
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 { ChatBarButton } from "@api/ChatButtons";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
import { openModal } from "@utils/modal";
|
||||
import { Alerts, Forms } from "@webpack/common";
|
||||
|
||||
import { settings } from "../settings";
|
||||
import { cl } from "./misc";
|
||||
import { TranslateModal } from "./TranslateModal";
|
||||
|
||||
export function TranslateIcon({ height = 24, width = 24, className }: { height?: number; width?: number; className?: string; }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 96 960 960"
|
||||
height={height}
|
||||
width={width}
|
||||
className={classes(cl("icon"), className)}
|
||||
>
|
||||
<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" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||
const { autoTranslate, showChatBarButton } = settings.use(["autoTranslate", "showChatBarButton"]);
|
||||
|
||||
if (!isMainChat || !showChatBarButton) return null;
|
||||
|
||||
const toggle = () => {
|
||||
const newState = !autoTranslate;
|
||||
settings.store.autoTranslate = newState;
|
||||
if (newState && settings.store.showAutoTranslateAlert !== false)
|
||||
Alerts.show({
|
||||
title: "Vencord Auto-Translate Enabled",
|
||||
body: <>
|
||||
<Forms.FormText>
|
||||
You just enabled auto translate (by right clicking the Translate icon). Any message you send will automatically be translated before being sent.
|
||||
</Forms.FormText>
|
||||
<Forms.FormText className={Margins.top16}>
|
||||
If this was an accident, disable it again, or it will change your message content before sending.
|
||||
</Forms.FormText>
|
||||
</>,
|
||||
cancelText: "Disable Auto-Translate",
|
||||
confirmText: "Got it",
|
||||
secondaryConfirmText: "Don't show again",
|
||||
onConfirmSecondary: () => settings.store.showAutoTranslateAlert = false,
|
||||
onCancel: () => settings.store.autoTranslate = false
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ChatBarButton
|
||||
tooltip="Open Translate Modal"
|
||||
onClick={e => {
|
||||
if (e.shiftKey) return toggle();
|
||||
|
||||
openModal(props => (
|
||||
<TranslateModal rootProps={props} />
|
||||
));
|
||||
}}
|
||||
onContextMenu={() => toggle()}
|
||||
buttonProps={{
|
||||
"aria-haspopup": "dialog"
|
||||
}}
|
||||
>
|
||||
<TranslateIcon className={cl({ "auto-translate": autoTranslate, "chat-button": true })} />
|
||||
</ChatBarButton>
|
||||
);
|
||||
};
|
101
src/equicordplugins/translatePlus/utils/TranslateModal.tsx
Normal file
101
src/equicordplugins/translatePlus/utils/TranslateModal.tsx
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 { Margins } from "@utils/margins";
|
||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
|
||||
import { Forms, SearchableSelect, Switch, useMemo } from "@webpack/common";
|
||||
|
||||
import { Languages } from "../misc/languages";
|
||||
import { settings } from "../settings";
|
||||
import { cl } from "./misc";
|
||||
|
||||
const LanguageSettingKeys = ["receivedInput", "receivedOutput", "sentInput", "sentOutput"] as const;
|
||||
|
||||
function LanguageSelect({ settingsKey, includeAuto }: { settingsKey: typeof LanguageSettingKeys[number]; includeAuto: boolean; }) {
|
||||
const currentValue = settings.use([settingsKey])[settingsKey];
|
||||
|
||||
const options = useMemo(
|
||||
() => {
|
||||
const options = Object.entries(Languages).map(([value, label]) => ({ value, label }));
|
||||
if (!includeAuto)
|
||||
options.shift();
|
||||
|
||||
return options;
|
||||
}, []
|
||||
);
|
||||
|
||||
return (
|
||||
<section className={Margins.bottom16}>
|
||||
<Forms.FormTitle tag="h3">
|
||||
{settings.def[settingsKey].description}
|
||||
</Forms.FormTitle>
|
||||
|
||||
<SearchableSelect
|
||||
options={options}
|
||||
value={options.find(o => o.value === currentValue)}
|
||||
placeholder={"Select a language"}
|
||||
maxVisibleItems={5}
|
||||
closeOnSelect={true}
|
||||
onChange={v => settings.store[settingsKey] = v}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function AutoTranslateToggle() {
|
||||
const value = settings.use(["autoTranslate"]).autoTranslate;
|
||||
|
||||
return (
|
||||
<Switch
|
||||
value={value}
|
||||
onChange={v => settings.store.autoTranslate = v}
|
||||
note={settings.def.autoTranslate.description}
|
||||
hideBorder
|
||||
>
|
||||
Auto Translate
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export function TranslateModal({ rootProps }: { rootProps: ModalProps; }) {
|
||||
return (
|
||||
<ModalRoot {...rootProps}>
|
||||
<ModalHeader className={cl("modal-header")}>
|
||||
<Forms.FormTitle tag="h2">
|
||||
Translate
|
||||
</Forms.FormTitle>
|
||||
<ModalCloseButton onClick={rootProps.onClose} />
|
||||
</ModalHeader>
|
||||
|
||||
<ModalContent className={cl("modal-content")}>
|
||||
{LanguageSettingKeys.map(s => (
|
||||
<LanguageSelect
|
||||
key={s}
|
||||
settingsKey={s}
|
||||
includeAuto={s.endsWith("Input")}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Forms.FormDivider className={Margins.bottom16} />
|
||||
|
||||
<AutoTranslateToggle />
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { Parser, useEffect, useState } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
import { Languages } from "../misc/languages";
|
||||
import { cl, TranslationValue } from "./misc";
|
||||
import { TranslateIcon } from "./TranslateIcon";
|
||||
|
||||
const TranslationSetters = new Map<string, (v: TranslationValue) => void>();
|
||||
|
||||
export function handleTranslate(messageId: string, data: TranslationValue) {
|
||||
TranslationSetters.get(messageId)!(data);
|
||||
}
|
||||
|
||||
function Dismiss({ onDismiss }: { onDismiss: () => void; }) {
|
||||
return (
|
||||
<button
|
||||
onClick={onDismiss}
|
||||
className={cl("dismiss")}
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function TranslationAccessory({ message }: { message: Message; }) {
|
||||
const [translation, setTranslation] = useState<TranslationValue>();
|
||||
|
||||
useEffect(() => {
|
||||
// Ignore MessageLinkEmbeds messages
|
||||
if ((message as any).vencordEmbeddedBy) return;
|
||||
|
||||
TranslationSetters.set(message.id, setTranslation);
|
||||
|
||||
return () => void TranslationSetters.delete(message.id);
|
||||
}, []);
|
||||
|
||||
if (!translation) return null;
|
||||
|
||||
return (
|
||||
<span className={cl("accessory")}>
|
||||
<TranslateIcon width={16} height={16} />
|
||||
{Parser.parse(translation.text)}
|
||||
{" "}
|
||||
(translated from {Languages[translation.src] ?? translation.src} - <Dismiss onDismiss={() => setTranslation(undefined)} />)
|
||||
</span>
|
||||
);
|
||||
}
|
136
src/equicordplugins/translatePlus/utils/misc.ts
Normal file
136
src/equicordplugins/translatePlus/utils/misc.ts
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* 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 { classNameFactory } from "@api/Styles";
|
||||
|
||||
import { settings } from "../settings";
|
||||
|
||||
export const cl = classNameFactory("vc-trans-");
|
||||
|
||||
interface TranslationData {
|
||||
src: string;
|
||||
sentences: {
|
||||
// 🏳️⚧️
|
||||
trans: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface TranslationValue {
|
||||
src: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
function isTokiPona(string) {
|
||||
const words = string.split(/\s+/);
|
||||
|
||||
const matches = string.match(/\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) || [];
|
||||
|
||||
const percentage = (matches.length / words.length) * 100;
|
||||
|
||||
return percentage >= 50;
|
||||
}
|
||||
|
||||
import { rawDictionary } from "../misc/dictionary";
|
||||
|
||||
function translateShavian(message) {
|
||||
const dictionary = JSON.parse(rawDictionary);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
export async function translate(kind: "received" | "sent", text: string): Promise<TranslationValue> {
|
||||
let output;
|
||||
|
||||
if (isTokiPona(text)) {
|
||||
const [api, auth] = [settings.store.tokiPonaAPI, settings.store.tokiPonaAuth];
|
||||
|
||||
const translate = await (await fetch(api, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Basic ${auth}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text: text,
|
||||
src: "tl",
|
||||
target: "en"
|
||||
})
|
||||
})).json();
|
||||
|
||||
output = {
|
||||
src: "tp",
|
||||
text: translate.translation[0]
|
||||
};
|
||||
} else if (/[\u{10450}-\u{1047F}]+/u.test(text)) {
|
||||
output = {
|
||||
src: "sh",
|
||||
text: translateShavian(text)
|
||||
};
|
||||
} else {
|
||||
const [sourceLang, targetLang] = [settings.store[kind + "Input"], settings.store[kind + "Output"]];
|
||||
|
||||
const translate = await (await fetch(`https://translate.googleapis.com/translate_a/single?${new URLSearchParams({ client: "gtx", sl: sourceLang, tl: targetLang, dt: "t", dj: "1", source: "input", q: text })}`)).json();
|
||||
|
||||
output = {
|
||||
src: translate.src,
|
||||
text: translate.sentences.map(s => s.trans).filter(Boolean).join("")
|
||||
};
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
|
@ -679,6 +679,22 @@ export const EquicordDevs = Object.freeze({
|
|||
name: "walrus",
|
||||
id: 305317288775778306n,
|
||||
},
|
||||
Prince527: {
|
||||
name: "Prince527",
|
||||
id: 364105797162237952n,
|
||||
},
|
||||
unstream: {
|
||||
name: "Unstream",
|
||||
id: 1125315673829154837n,
|
||||
},
|
||||
ThePirateStoner: {
|
||||
name: "ThePirateStoner",
|
||||
id: 1196220620376121381n
|
||||
},
|
||||
Sampath: {
|
||||
name: "Sampath",
|
||||
id: 984015688807100419n,
|
||||
},
|
||||
} satisfies Record<string, Dev>);
|
||||
|
||||
export const SuncordDevs = /* #__PURE__*/ Object.freeze({
|
||||
|
|
Loading…
Add table
Reference in a new issue