Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
6ec2a06023
45 changed files with 980 additions and 363 deletions
|
|
@ -136,6 +136,8 @@ export default defineConfig(
|
|||
"use-isnan": "error",
|
||||
"prefer-const": ["error", { destructuring: "all" }],
|
||||
"prefer-spread": "error",
|
||||
// These are old deprecated browser globals which may be used by mistake, e.g. `addEventListener(e => console.log(event))`
|
||||
"no-restricted-globals": ["error", "event", "name"],
|
||||
|
||||
// Plugin Rules
|
||||
"simple-import-sort/imports": "error",
|
||||
|
|
|
|||
19
packages/discord-types/src/components.d.ts
vendored
19
packages/discord-types/src/components.d.ts
vendored
|
|
@ -331,6 +331,15 @@ declare enum PopoutAnimation {
|
|||
|
||||
type PopoutPosition = "top" | "bottom" | "left" | "right" | "center" | "window_center";
|
||||
|
||||
export interface PopoutProps {
|
||||
position: PopoutPosition;
|
||||
nudge: number;
|
||||
isPositioned: boolean;
|
||||
setPopoutRef(ref: any): void;
|
||||
closePopout(): void;
|
||||
updatePosition(): void;
|
||||
}
|
||||
|
||||
export type Popout = ComponentType<{
|
||||
children(
|
||||
thing: {
|
||||
|
|
@ -347,14 +356,8 @@ export type Popout = ComponentType<{
|
|||
): ReactNode;
|
||||
shouldShow?: boolean;
|
||||
targetElementRef: RefObject<any>;
|
||||
renderPopout(args: {
|
||||
closePopout(): void;
|
||||
isPositioned: boolean;
|
||||
nudge: number;
|
||||
position: PopoutPosition;
|
||||
setPopoutRef(ref: any): void;
|
||||
updatePosition(): void;
|
||||
}): ReactNode;
|
||||
renderPopout(props: PopoutProps): ReactNode;
|
||||
preload?(): Promise<any>;
|
||||
|
||||
onRequestOpen?(): void;
|
||||
onRequestClose?(): void;
|
||||
|
|
|
|||
|
|
@ -41,8 +41,7 @@ import { popNotice, showNotice } from "./api/Notices";
|
|||
import { NotificationData, showNotification } from "./api/Notifications";
|
||||
import { initPluginManager, PMLogger, startAllPlugins } from "./api/PluginManager";
|
||||
import { PlainSettings, Settings, SettingsStore } from "./api/Settings";
|
||||
import { getCloudSettings, putCloudSettings, shouldCloudSync } from "./api/SettingsSync/cloudSync";
|
||||
import { localStorage } from "./utils/localStorage";
|
||||
import { areLocalSettingsDirty, getCloudSettings, getCloudSyncDirection, markLocalSettingsDirty, putCloudSettings, shouldCloudSync } from "./api/SettingsSync/cloudSync";
|
||||
import { relaunch } from "./utils/native";
|
||||
import { checkForUpdates, isOutdated as getIsOutdated, update, UpdateLogger } from "./utils/updater";
|
||||
import { onceReady } from "./webpack";
|
||||
|
|
@ -53,11 +52,6 @@ if (IS_REPORTER) {
|
|||
}
|
||||
|
||||
async function syncSettings() {
|
||||
// Check if cloud auth exists for current user before attempting sync
|
||||
if (localStorage.Vencord_cloudSyncDirection === undefined) {
|
||||
// by default, sync bi-directionally
|
||||
localStorage.Vencord_cloudSyncDirection = "both";
|
||||
}
|
||||
const hasCloudAuth = await dsGet("Vencord_cloudSecret");
|
||||
if (!hasCloudAuth) {
|
||||
if (Settings.cloud.authenticated) {
|
||||
|
|
@ -93,9 +87,9 @@ async function syncSettings() {
|
|||
if (
|
||||
Settings.cloud.settingsSync && // if it's enabled
|
||||
Settings.cloud.authenticated && // if cloud integrations are enabled
|
||||
localStorage.Vencord_cloudSyncDirection !== "manual" // if we're not in manual mode
|
||||
getCloudSyncDirection() !== "manual" // if we're not in manual mode
|
||||
) {
|
||||
if (localStorage.Vencord_settingsDirty && shouldCloudSync("push")) {
|
||||
if (areLocalSettingsDirty() && shouldCloudSync("push")) {
|
||||
await putCloudSettings();
|
||||
} else if (shouldCloudSync("pull") && await getCloudSettings(false)) { // if we synchronized something (false means no sync)
|
||||
// we show a notification here instead of allowing getCloudSettings() to show one to declutter the amount of
|
||||
|
|
@ -118,7 +112,7 @@ async function syncSettings() {
|
|||
}, 60_000);
|
||||
|
||||
SettingsStore.addGlobalChangeListener(() => {
|
||||
localStorage.Vencord_settingsDirty = true;
|
||||
markLocalSettingsDirty();
|
||||
saveSettingsOnFrequentAction();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
position: absolute;
|
||||
z-index: 2147483647;
|
||||
right: 1rem;
|
||||
width: 25vw;
|
||||
width: min(25vw, 400px);
|
||||
min-height: 10vh;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import { traceFunction } from "@debug/Tracer";
|
|||
import { Logger } from "@utils/Logger";
|
||||
import { onlyOnce } from "@utils/onlyOnce";
|
||||
import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches";
|
||||
import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
|
||||
import { DefinedSettings, Patch, Plugin, PluginDef, PluginSettingDef, ReporterTestable, StartAt } from "@utils/types";
|
||||
import { FluxEvents } from "@vencord/discord-types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
import { patches } from "@webpack/patcher";
|
||||
|
|
@ -60,13 +60,32 @@ export function isPluginEnabled(p: string) {
|
|||
Settings.plugins[p]?.enabled
|
||||
) ?? false;
|
||||
}
|
||||
|
||||
export function isPluginRequired(p: string) {
|
||||
return (
|
||||
Plugins[p]?.required
|
||||
) ?? false;
|
||||
}
|
||||
|
||||
export function isSettingHidden(settings: DefinedSettings, setting: PluginSettingDef) {
|
||||
if (!("hidden" in setting)) return false;
|
||||
|
||||
return typeof setting.hidden === "function"
|
||||
? setting.hidden.call(settings)
|
||||
: Boolean(setting.hidden);
|
||||
}
|
||||
|
||||
export function isSettingDisabled(settings: DefinedSettings, setting: PluginSettingDef) {
|
||||
if (!("disabled" in setting)) return false;
|
||||
|
||||
return typeof setting.disabled === "function"
|
||||
? setting.disabled.call(settings)
|
||||
: Boolean(setting.disabled);
|
||||
}
|
||||
|
||||
export function hasAnyVisibleSettings({ settings }: Plugin) {
|
||||
return !!settings && Object.values(settings.def).some(s => !isSettingHidden(settings, s));
|
||||
}
|
||||
|
||||
export function addPatch(newPatch: Omit<Patch, "plugin">, pluginName: string, pluginPath = `Vencord.Plugins.plugins[${JSON.stringify(pluginName)}]`) {
|
||||
// TODO: this causes crashes
|
||||
if (pluginName === "Vesktop" && newPatch.find === ".STREAMING_AUTO_STREAMER_MODE,") return;
|
||||
|
|
@ -416,22 +435,11 @@ export const initPluginManager = onlyOnce(function init() {
|
|||
|
||||
for (const p of pluginsValues) {
|
||||
if (p.settings) {
|
||||
p.options ??= {};
|
||||
|
||||
p.settings.pluginName = p.name;
|
||||
for (const name in p.settings.def) {
|
||||
const def = p.settings.def[name];
|
||||
const checks = p.settings.checks?.[name];
|
||||
p.options[name] = { ...def, ...checks };
|
||||
}
|
||||
}
|
||||
|
||||
if (p.options) {
|
||||
for (const name in p.options) {
|
||||
const opt = p.options[name];
|
||||
if (opt.onChange != null) {
|
||||
SettingsStore.addChangeListener(`plugins.${p.name}.${name}`, opt.onChange);
|
||||
}
|
||||
for (const [key, def] of Object.entries(p.settings.def)) {
|
||||
if (def.onChange)
|
||||
SettingsStore.addChangeListener(`plugins.${p.name}.${key}`, def.onChange);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ export const SettingsStore = new SettingsStoreClass(settings, {
|
|||
if (path.startsWith("plugins.")) {
|
||||
const plugin = path.slice("plugins.".length);
|
||||
if (plugin in plugins) {
|
||||
const setting = plugins[plugin].options?.[key];
|
||||
const setting = plugins[plugin].settings?.def[key];
|
||||
if (!setting) return v;
|
||||
|
||||
if ("default" in setting)
|
||||
|
|
@ -208,7 +208,7 @@ if (!IS_REPORTER) {
|
|||
* Same as {@link Settings} but unproxied. You should treat this as readonly,
|
||||
* as modifying properties on this will not save to disk or call settings
|
||||
* listeners.
|
||||
* WARNING: default values specified in plugin.options will not be ensured here. In other words,
|
||||
* WARNING: default values specified in plugin.settings will not be ensured here. In other words,
|
||||
* settings for which you specified a default value may be uninitialised. If you need proper
|
||||
* handling for default values, use {@link Settings}
|
||||
*/
|
||||
|
|
@ -353,7 +353,13 @@ export function definePluginSettings<
|
|||
Checks extends SettingsChecks<Def>,
|
||||
PrivateSettings extends object = {}
|
||||
>(def: Def, checks?: Checks) {
|
||||
const definedSettings: DefinedSettings<Def, Checks, PrivateSettings> = {
|
||||
if (checks) {
|
||||
for (const [name, check] of Object.entries(checks)) {
|
||||
Object.assign(def[name], check);
|
||||
}
|
||||
}
|
||||
|
||||
const definedSettings: DefinedSettings<Def, PrivateSettings> = {
|
||||
get store() {
|
||||
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
|
||||
return Settings.plugins[definedSettings.pluginName] as any;
|
||||
|
|
@ -368,11 +374,10 @@ export function definePluginSettings<
|
|||
: [`plugins.${definedSettings.pluginName}.*`]
|
||||
) as UseSettings<Settings>[]).plugins[definedSettings.pluginName] as any,
|
||||
def,
|
||||
checks: checks ?? {} as any,
|
||||
pluginName: "",
|
||||
|
||||
withPrivateSettings<T extends object>() {
|
||||
return this as DefinedSettings<Def, Checks, T>;
|
||||
return this as DefinedSettings<Def, T>;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,14 @@ const API_VERSION_STORE_KEY = "Vencord_cloudApiVersions";
|
|||
|
||||
type ApiVersion = "v2" | "v1";
|
||||
|
||||
const SYNC_DIRECTION_KEY = "Vencord_cloudSyncDirection";
|
||||
const SETTINGS_DIRTY_KEY = "Vencord_settingsDirty";
|
||||
export const getCloudSyncDirection = () => localStorage.getItem(SYNC_DIRECTION_KEY) || "both";
|
||||
export const setCloudSyncDirection = (direction: "push" | "pull" | "both" | "manual") => localStorage.setItem(SYNC_DIRECTION_KEY, direction);
|
||||
export const areLocalSettingsDirty = () => localStorage.getItem(SETTINGS_DIRTY_KEY) === "true";
|
||||
export const markLocalSettingsDirty = () => localStorage.setItem(SETTINGS_DIRTY_KEY, "true");
|
||||
export const markLocalSettingsClean = () => localStorage.removeItem(SETTINGS_DIRTY_KEY);
|
||||
|
||||
async function loadApiVersionMap(): Promise<Record<string, ApiVersion>> {
|
||||
return await DataStore.get<Record<string, ApiVersion>>(API_VERSION_STORE_KEY) ?? {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
*/
|
||||
|
||||
import { showNotice } from "@api/Notices";
|
||||
import { isPluginEnabled, pluginRequiresRestart, startDependenciesRecursive, startPlugin, stopPlugin } from "@api/PluginManager";
|
||||
import { hasAnyVisibleSettings, isPluginEnabled, pluginRequiresRestart, startDependenciesRecursive, startPlugin, stopPlugin } from "@api/PluginManager";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { CogWheel, InfoIcon } from "@components/Icons";
|
||||
import { AddonCard } from "@components/settings/AddonCard";
|
||||
import { classNameFactory } from "@utils/css";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { OptionType, Plugin } from "@utils/types";
|
||||
import { Plugin } from "@utils/types";
|
||||
import { React, showToast, Toasts } from "@webpack/common";
|
||||
|
||||
import { PluginMeta } from "~plugins";
|
||||
|
|
@ -146,7 +146,7 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
|||
onClick={() => openPluginModal(plugin, onRestartNeeded)}
|
||||
className={cl("info-button")}
|
||||
>
|
||||
{plugin.settings?.def && Object.values(plugin.settings.def).some(s => s.type !== OptionType.CUSTOM && !s.hidden)
|
||||
{hasAnyVisibleSettings(plugin)
|
||||
? <CogWheel className={cl("info-icon")} />
|
||||
: <InfoIcon className={cl("info-icon")} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
import "./PluginModal.css";
|
||||
|
||||
import { generateId } from "@api/Commands";
|
||||
import { hasAnyVisibleSettings, isSettingHidden } from "@api/PluginManager";
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { BaseText } from "@components/BaseText";
|
||||
import { Button } from "@components/Button";
|
||||
|
|
@ -86,7 +87,7 @@ function PluginTags({ tags }: { tags: PluginTag[]; }) {
|
|||
|
||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||
const pluginSettings = useSettings([`plugins.${plugin.name}.*`]).plugins[plugin.name];
|
||||
const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options));
|
||||
const hasSettings = hasAnyVisibleSettings(plugin);
|
||||
|
||||
// avoid layout shift by showing dummy users while loading users
|
||||
const fallbackAuthors = useMemo(() => [makeDummyUser({ username: "Loading...", id: "-1465912127305809920" })], []);
|
||||
|
|
@ -114,14 +115,17 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
}
|
||||
|
||||
function renderSettings() {
|
||||
if (!hasSettings || !plugin.options)
|
||||
const { settings } = plugin;
|
||||
if (!hasSettings || !settings)
|
||||
return <Paragraph>There are no settings for this plugin.</Paragraph>;
|
||||
|
||||
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
||||
if (setting.type === OptionType.CUSTOM || setting.hidden) return null;
|
||||
const options = Object.entries(settings.def).map(([key, setting]) => {
|
||||
if (setting.type === OptionType.CUSTOM) return null;
|
||||
|
||||
if (isSettingHidden(settings, setting)) return null;
|
||||
|
||||
function onChange(newValue: any) {
|
||||
const option = plugin.options?.[key];
|
||||
const option = plugin.settings!.def[key];
|
||||
if (!option || option.type === OptionType.CUSTOM) return;
|
||||
|
||||
pluginSettings[key] = newValue;
|
||||
|
|
@ -134,10 +138,10 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
<ErrorBoundary noop key={key}>
|
||||
<Component
|
||||
id={key}
|
||||
option={setting}
|
||||
setting={setting}
|
||||
onChange={debounce(onChange)}
|
||||
pluginSettings={pluginSettings}
|
||||
definedSettings={plugin.settings}
|
||||
definedSettings={settings}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16,20 +16,21 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { isSettingDisabled } from "@api/PluginManager";
|
||||
import { Switch } from "@components/Switch";
|
||||
import { PluginOptionBoolean } from "@utils/types";
|
||||
import { PluginSettingBooleanDef } from "@utils/types";
|
||||
import { React, useState } from "@webpack/common";
|
||||
|
||||
import { resolveError, SettingProps, SettingsSection } from "./Common";
|
||||
|
||||
export function BooleanSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginOptionBoolean>) {
|
||||
const def = pluginSettings[id] ?? option.default;
|
||||
export function BooleanSetting({ setting, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginSettingBooleanDef>) {
|
||||
const def = pluginSettings[id] ?? setting.default;
|
||||
|
||||
const [state, setState] = useState(def ?? false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
function handleChange(newValue: boolean): void {
|
||||
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
|
||||
const isValid = setting.isValid?.call(definedSettings, newValue) ?? true;
|
||||
|
||||
setState(newValue);
|
||||
setError(resolveError(isValid));
|
||||
|
|
@ -40,11 +41,11 @@ export function BooleanSetting({ option, pluginSettings, definedSettings, id, on
|
|||
}
|
||||
|
||||
return (
|
||||
<SettingsSection tag="label" name={id} description={option.description} error={error} inlineSetting>
|
||||
<SettingsSection tag="label" name={id} description={setting.description} error={error} inlineSetting>
|
||||
<Switch
|
||||
checked={state}
|
||||
onChange={handleChange}
|
||||
disabled={option.disabled?.call(definedSettings) ?? false}
|
||||
disabled={isSettingDisabled(definedSettings, setting)}
|
||||
/>
|
||||
</SettingsSection>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,24 +8,24 @@ import { BaseText } from "@components/BaseText";
|
|||
import { classNameFactory } from "@utils/css";
|
||||
import { classes } from "@utils/misc";
|
||||
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
||||
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
||||
import { DefinedSettings, PluginSettingDefCommon } from "@utils/types";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
export const cl = classNameFactory("vc-plugins-setting-");
|
||||
|
||||
interface SettingBaseProps<T> {
|
||||
option: T;
|
||||
setting: T;
|
||||
onChange(newValue: any): void;
|
||||
pluginSettings: {
|
||||
[setting: string]: any;
|
||||
enabled: boolean;
|
||||
};
|
||||
id: string;
|
||||
definedSettings?: DefinedSettings;
|
||||
definedSettings: DefinedSettings;
|
||||
}
|
||||
|
||||
export type SettingProps<T extends PluginOptionBase> = SettingBaseProps<T>;
|
||||
export type ComponentSettingProps<T extends Omit<PluginOptionBase, "description" | "placeholder">> = SettingBaseProps<T>;
|
||||
export type SettingProps<T extends PluginSettingDefCommon> = SettingBaseProps<T>;
|
||||
export type ComponentSettingProps<T extends Omit<PluginSettingDefCommon, "description" | "placeholder">> = SettingBaseProps<T>;
|
||||
|
||||
export function resolveError(isValidResult: boolean | string) {
|
||||
if (typeof isValidResult === "string") return isValidResult;
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { PluginOptionComponent } from "@utils/types";
|
||||
import { PluginSettingComponentDef } from "@utils/types";
|
||||
|
||||
import { ComponentSettingProps } from "./Common";
|
||||
|
||||
export function ComponentSetting({ option, onChange }: ComponentSettingProps<PluginOptionComponent>) {
|
||||
return option.component({ setValue: onChange, option });
|
||||
export function ComponentSetting({ setting, onChange }: ComponentSettingProps<PluginSettingComponentDef>) {
|
||||
return setting.component({ setValue: onChange, option: setting });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,24 +16,25 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { OptionType, PluginOptionNumber } from "@utils/types";
|
||||
import { isSettingDisabled } from "@api/PluginManager";
|
||||
import { OptionType, PluginSettingBigIntDef, PluginSettingNumberDef } from "@utils/types";
|
||||
import { React, TextInput, useState } from "@webpack/common";
|
||||
|
||||
import { resolveError, SettingProps, SettingsSection } from "./Common";
|
||||
|
||||
const MAX_SAFE_NUMBER = BigInt(Number.MAX_SAFE_INTEGER);
|
||||
|
||||
export function NumberSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginOptionNumber>) {
|
||||
export function NumberSetting({ setting, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginSettingNumberDef | PluginSettingBigIntDef>) {
|
||||
function serialize(value: any) {
|
||||
if (option.type === OptionType.BIGINT) return BigInt(value);
|
||||
if (setting.type === OptionType.BIGINT) return BigInt(value);
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
const [state, setState] = useState<any>(`${pluginSettings[id] ?? option.default ?? 0}`);
|
||||
const [state, setState] = useState<any>(`${pluginSettings[id] ?? setting.default ?? 0}`);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
function handleChange(newValue: any) {
|
||||
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
|
||||
const isValid = setting.isValid?.call(definedSettings, newValue) ?? true;
|
||||
|
||||
setError(resolveError(isValid));
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ export function NumberSetting({ option, pluginSettings, definedSettings, id, onC
|
|||
onChange(serialize(newValue));
|
||||
}
|
||||
|
||||
if (option.type === OptionType.NUMBER && BigInt(newValue) >= MAX_SAFE_NUMBER) {
|
||||
if (setting.type === OptionType.NUMBER && BigInt(newValue) >= MAX_SAFE_NUMBER) {
|
||||
setState(`${Number.MAX_SAFE_INTEGER}`);
|
||||
} else {
|
||||
setState(newValue);
|
||||
|
|
@ -49,15 +50,15 @@ export function NumberSetting({ option, pluginSettings, definedSettings, id, onC
|
|||
}
|
||||
|
||||
return (
|
||||
<SettingsSection name={id} description={option.description} error={error}>
|
||||
<SettingsSection name={id} description={setting.description} error={error}>
|
||||
<TextInput
|
||||
type="number"
|
||||
pattern="-?[0-9]+"
|
||||
placeholder={option.placeholder ?? "Enter a number"}
|
||||
placeholder={setting.placeholder ?? "Enter a number"}
|
||||
value={state}
|
||||
onChange={handleChange}
|
||||
disabled={option.disabled?.call(definedSettings) ?? false}
|
||||
{...option.componentProps}
|
||||
disabled={isSettingDisabled(definedSettings, setting)}
|
||||
{...setting.componentProps}
|
||||
/>
|
||||
</SettingsSection>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16,19 +16,20 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { PluginOptionSelect } from "@utils/types";
|
||||
import { isSettingDisabled } from "@api/PluginManager";
|
||||
import { PluginSettingSelectDef } from "@utils/types";
|
||||
import { React, Select, useState } from "@webpack/common";
|
||||
|
||||
import { resolveError, SettingProps, SettingsSection } from "./Common";
|
||||
|
||||
export function SelectSetting({ option, pluginSettings, definedSettings, onChange, id }: SettingProps<PluginOptionSelect>) {
|
||||
const def = pluginSettings[id] ?? option.options?.find(o => o.default)?.value;
|
||||
export function SelectSetting({ setting, pluginSettings, definedSettings, onChange, id }: SettingProps<PluginSettingSelectDef>) {
|
||||
const def = pluginSettings[id] ?? setting.options?.find(o => o.default)?.value;
|
||||
|
||||
const [state, setState] = useState<any>(def ?? null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
function handleChange(newValue: any) {
|
||||
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
|
||||
const isValid = setting.isValid?.call(definedSettings, newValue) ?? true;
|
||||
|
||||
setState(newValue);
|
||||
setError(resolveError(isValid));
|
||||
|
|
@ -39,17 +40,17 @@ export function SelectSetting({ option, pluginSettings, definedSettings, onChang
|
|||
}
|
||||
|
||||
return (
|
||||
<SettingsSection name={id} description={option.description} error={error}>
|
||||
<SettingsSection name={id} description={setting.description} error={error}>
|
||||
<Select
|
||||
placeholder={option.placeholder ?? "Select an option"}
|
||||
options={option.options}
|
||||
placeholder={setting.placeholder ?? "Select an option"}
|
||||
options={setting.options}
|
||||
maxVisibleItems={5}
|
||||
closeOnSelect={true}
|
||||
select={handleChange}
|
||||
isSelected={v => v === state}
|
||||
serialize={v => String(v)}
|
||||
isDisabled={option.disabled?.call(definedSettings) ?? false}
|
||||
{...option.componentProps}
|
||||
isDisabled={isSettingDisabled(definedSettings, setting)}
|
||||
{...setting.componentProps}
|
||||
/>
|
||||
</SettingsSection>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16,18 +16,19 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { PluginOptionSlider } from "@utils/types";
|
||||
import { isSettingDisabled } from "@api/PluginManager";
|
||||
import { PluginSettingSliderDef } from "@utils/types";
|
||||
import { React, Slider, useState } from "@webpack/common";
|
||||
|
||||
import { resolveError, SettingProps, SettingsSection } from "./Common";
|
||||
|
||||
export function SliderSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginOptionSlider>) {
|
||||
const def = pluginSettings[id] ?? option.default;
|
||||
export function SliderSetting({ setting, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginSettingSliderDef>) {
|
||||
const def = pluginSettings[id] ?? setting.default;
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
function handleChange(newValue: number): void {
|
||||
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
|
||||
const isValid = setting.isValid?.call(definedSettings, newValue) ?? true;
|
||||
|
||||
setError(resolveError(isValid));
|
||||
|
||||
|
|
@ -37,17 +38,17 @@ export function SliderSetting({ option, pluginSettings, definedSettings, id, onC
|
|||
}
|
||||
|
||||
return (
|
||||
<SettingsSection name={id} description={option.description} error={error}>
|
||||
<SettingsSection name={id} description={setting.description} error={error}>
|
||||
<Slider
|
||||
markers={option.markers}
|
||||
minValue={option.markers[0]}
|
||||
maxValue={option.markers[option.markers.length - 1]}
|
||||
markers={setting.markers}
|
||||
minValue={setting.markers[0]}
|
||||
maxValue={setting.markers[setting.markers.length - 1]}
|
||||
initialValue={def}
|
||||
onValueChange={handleChange}
|
||||
onValueRender={(v: number) => String(v.toFixed(2))}
|
||||
stickToMarkers={option.stickToMarkers ?? true}
|
||||
disabled={option.disabled?.call(definedSettings) ?? false}
|
||||
{...option.componentProps}
|
||||
stickToMarkers={setting.stickToMarkers ?? true}
|
||||
disabled={isSettingDisabled(definedSettings, setting)}
|
||||
{...setting.componentProps}
|
||||
/>
|
||||
</SettingsSection>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16,17 +16,18 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { PluginOptionString } from "@utils/types";
|
||||
import { isSettingDisabled } from "@api/PluginManager";
|
||||
import { PluginSettingStringDef } from "@utils/types";
|
||||
import { React, TextArea, TextInput, useState } from "@webpack/common";
|
||||
|
||||
import { resolveError, SettingProps, SettingsSection } from "./Common";
|
||||
|
||||
export function TextSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginOptionString>) {
|
||||
const [state, setState] = useState(pluginSettings[id] ?? option.default ?? null);
|
||||
export function TextSetting({ setting, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginSettingStringDef>) {
|
||||
const [state, setState] = useState(pluginSettings[id] ?? setting.default ?? null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
|
||||
const isValid = setting.isValid?.call(definedSettings, newValue) ?? true;
|
||||
|
||||
setState(newValue);
|
||||
setError(resolveError(isValid));
|
||||
|
|
@ -37,22 +38,22 @@ export function TextSetting({ option, pluginSettings, definedSettings, id, onCha
|
|||
}
|
||||
|
||||
return (
|
||||
<SettingsSection name={id} description={option.description} error={error}>
|
||||
{option.multiline
|
||||
<SettingsSection name={id} description={setting.description} error={error}>
|
||||
{setting.multiline
|
||||
? <TextArea
|
||||
placeholder={option.placeholder ?? "Enter a value"}
|
||||
placeholder={setting.placeholder ?? "Enter a value"}
|
||||
value={state}
|
||||
onChange={handleChange}
|
||||
disabled={option.disabled?.call(definedSettings) ?? false}
|
||||
{...option.componentProps} />
|
||||
disabled={isSettingDisabled(definedSettings, setting)}
|
||||
{...setting.componentProps} />
|
||||
: <TextInput
|
||||
type="text"
|
||||
placeholder={option.placeholder ?? "Enter a value"}
|
||||
placeholder={setting.placeholder ?? "Enter a value"}
|
||||
value={state}
|
||||
onChange={handleChange}
|
||||
maxLength={null}
|
||||
disabled={option.disabled?.call(definedSettings) ?? false}
|
||||
{...option.componentProps}
|
||||
disabled={isSettingDisabled(definedSettings, setting)}
|
||||
{...setting.componentProps}
|
||||
/>
|
||||
}
|
||||
</SettingsSection>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import { Link } from "@components/Link";
|
|||
import { Notice } from "@components/Notice";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
|
||||
import { localStorage } from "@utils/localStorage";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||
import { isPluginEnabled, plugins } from "@api/PluginManager";
|
||||
import { isPluginEnabled, isSettingDisabled, isSettingHidden, plugins } from "@api/PluginManager";
|
||||
import { Settings, useSettings } from "@api/Settings";
|
||||
import { openPluginModal, openSettingsTabModal, PluginsTab, ThemesTab } from "@components/settings";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
|
|
@ -81,8 +81,8 @@ export function buildPluginMenuEntries(includeEmpty = false) {
|
|||
|
||||
let hasAnyOption = false;
|
||||
|
||||
if (p.options) for (const [key, option] of Object.entries(p.options)) {
|
||||
if ("hidden" in option && option.hidden) continue;
|
||||
if (p.settings) for (const [key, option] of Object.entries(p.settings.def)) {
|
||||
if (isSettingHidden(p.settings, option)) continue;
|
||||
|
||||
hasAnyOption = true;
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ export function buildPluginMenuEntries(includeEmpty = false) {
|
|||
id: `${p.name}-${key}`,
|
||||
key: key,
|
||||
label: wordsToTitle(wordsFromCamel(key)),
|
||||
disabled: "disabled" in option ? option.disabled?.call(p.settings) : false,
|
||||
disabled: isSettingDisabled(p.settings, option)
|
||||
};
|
||||
|
||||
switch (option.type) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
import "./spotifyStyles.css";
|
||||
|
||||
import { Settings } from "@api/Settings";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { CopyIcon, ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
|
@ -29,6 +28,7 @@ import { copyWithToast, openImageModal } from "@utils/discord";
|
|||
import { classes } from "@utils/misc";
|
||||
import { ContextMenuApi, FluxDispatcher, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common";
|
||||
|
||||
import { settings } from "../settings";
|
||||
import { SeekBar } from "./SeekBar";
|
||||
import { SpotifyStore, Track } from "./SpotifyStore";
|
||||
|
||||
|
|
@ -132,7 +132,7 @@ function Controls() {
|
|||
<Shuffle />
|
||||
</Button>
|
||||
<Button onClick={() => {
|
||||
Settings.plugins.MusicControls.previousButtonRestartsTrack && SpotifyStore.position > 3000 ? SpotifyStore.seek(0) : SpotifyStore.prev();
|
||||
settings.store.previousButtonRestartsTrack && SpotifyStore.position > 3000 ? SpotifyStore.seek(0) : SpotifyStore.prev();
|
||||
}}>
|
||||
<SkipPrev />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -17,11 +17,12 @@
|
|||
*/
|
||||
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { Settings } from "@api/Settings";
|
||||
import OpenInAppPlugin from "@plugins/openInApp";
|
||||
import { findByProps, findByPropsLazy, proxyLazyWebpack } from "@webpack";
|
||||
import { Flux, FluxDispatcher } from "@webpack/common";
|
||||
|
||||
import { settings } from ".";
|
||||
|
||||
export interface Track {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
@ -91,7 +92,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
|
|||
public isSettingPosition = false;
|
||||
|
||||
public openExternal(path: string) {
|
||||
const url = Settings.plugins.SpotifyControls.useSpotifyUris || isPluginEnabled(OpenInAppPlugin.name)
|
||||
const url = settings.store.useSpotifyUris || isPluginEnabled(OpenInAppPlugin.name)
|
||||
? "spotify:" + path.replaceAll("/", (_, idx) => idx === 0 ? "" : ":")
|
||||
: "https://open.spotify.com" + path;
|
||||
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
|
|||
title: "Equicord QuickCSS Editor",
|
||||
autoHideMenuBar: true,
|
||||
darkTheme: true,
|
||||
backgroundColor: nativeTheme.shouldUseDarkColors ? "#1e1e1e" : "white",
|
||||
webPreferences: {
|
||||
preload: join(__dirname, "preload.js"),
|
||||
contextIsolation: true,
|
||||
|
|
|
|||
|
|
@ -107,6 +107,11 @@ const settings = definePluginSettings({
|
|||
{ label: "Below Activity Settings", value: "belowActivity" },
|
||||
{ label: "At the very bottom", value: "bottom" },
|
||||
] as { label: string; value: SettingsLocation; default?: boolean; }[]
|
||||
},
|
||||
includeVencordInfoWhenCopying: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Also copy Vencord info (Vencord, Electron, Chromium) when clicking the version info in the bottom left area of the Settings page",
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -254,7 +259,7 @@ export default definePlugin({
|
|||
belowNitro: "billing_section",
|
||||
aboveActivity: "activity_section",
|
||||
belowActivity: "activity_section",
|
||||
bottom: "logout_section"
|
||||
bottom: "utility_section"
|
||||
};
|
||||
|
||||
const key = places[settingsLocation] ?? places.top;
|
||||
|
|
@ -317,6 +322,7 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
getInfoString() {
|
||||
if (!settings.store.includeVencordInfoWhenCopying) return "";
|
||||
return "\n" + this.getInfoRows().join("\n");
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -60,15 +60,20 @@ const settings = definePluginSettings({
|
|||
randomisedLength: {
|
||||
description: "Random characters length",
|
||||
type: OptionType.NUMBER,
|
||||
default: 7,
|
||||
disabled: () => settings.store.method !== Methods.Random,
|
||||
default: 7
|
||||
},
|
||||
consistent: {
|
||||
description: "Consistent filename",
|
||||
type: OptionType.STRING,
|
||||
default: "image",
|
||||
disabled: () => settings.store.method !== Methods.Consistent,
|
||||
default: "image"
|
||||
},
|
||||
}, {
|
||||
randomisedLength: {
|
||||
disabled() { return this.store.method !== Methods.Random; },
|
||||
},
|
||||
consistent: {
|
||||
disabled() { return this.store.method !== Methods.Consistent; },
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
* 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 { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { canonicalizeMatch } from "@utils/patches";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
hide: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Hide notes",
|
||||
default: false,
|
||||
restartNeeded: true
|
||||
},
|
||||
noSpellCheck: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Disable spellcheck in notes",
|
||||
disabled: () => Settings.plugins.BetterNotesBox.hide,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "BetterNotesBox",
|
||||
description: "Hide notes or disable spellcheck (Configure in settings!!)",
|
||||
authors: [Devs.Ven],
|
||||
tags: ["Customisation", "Utility"],
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "hideNote:",
|
||||
all: true,
|
||||
// Some modules match the find but the replacement is returned untouched
|
||||
noWarn: true,
|
||||
predicate: () => settings.store.hide,
|
||||
replacement: {
|
||||
match: /hideNote:.+?(?=([,}].*?\)))/g,
|
||||
replace: (m, rest) => {
|
||||
const destructuringMatch = rest.match(/}=.+/);
|
||||
if (destructuringMatch) {
|
||||
const defaultValueMatch = m.match(canonicalizeMatch(/hideNote:(\i)=!?\d/));
|
||||
return defaultValueMatch ? `hideNote:${defaultValueMatch[1]}=!0` : m;
|
||||
}
|
||||
|
||||
return "hideNote:!0";
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "#{intl::NOTE_PLACEHOLDER}",
|
||||
replacement: {
|
||||
match: /#{intl::NOTE_PLACEHOLDER}\),/,
|
||||
replace: "$&spellCheck:!$self.noSpellCheck,"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
get noSpellCheck() {
|
||||
return settings.store.noSpellCheck;
|
||||
}
|
||||
});
|
||||
|
|
@ -9,12 +9,18 @@ import { getUserSettingLazy } from "@api/UserSettings";
|
|||
import { ImageIcon } from "@components/Icons";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getCurrentGuild, openImageModal } from "@utils/discord";
|
||||
import { getCurrentChannel, getCurrentGuild, openImageModal } from "@utils/discord";
|
||||
import { isTruthy } from "@utils/guards";
|
||||
import { classes } from "@utils/misc";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { GuildRoleStore, Menu, PermissionStore } from "@webpack/common";
|
||||
import { Guild, PopoutProps, Role } from "@vencord/discord-types";
|
||||
import { findByCodeLazy, findByPropsLazy, findCssClassesLazy } from "@webpack";
|
||||
import { GuildRoleStore, Menu, PermissionStore, Popout, useRef } from "@webpack/common";
|
||||
import { ComponentType } from "react";
|
||||
|
||||
const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild");
|
||||
const MenuItemClasses = findCssClassesLazy("item", "labelContainer", "colorDefault", "label", "iconContainer");
|
||||
const loadRoleMembers = findByCodeLazy(".GUILD_ROLE_MEMBER_IDS(", "requestMembersById");
|
||||
|
||||
const DeveloperMode = getUserSettingLazy("appearance", "developerMode")!;
|
||||
|
||||
|
|
@ -65,6 +71,14 @@ function AppearanceIcon() {
|
|||
);
|
||||
}
|
||||
|
||||
function RoleMembersIcon() {
|
||||
return (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12 10a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM11.53 11A9.53 9.53 0 0 0 2 20.53c0 .81.66 1.47 1.47 1.47h.22c.24 0 .44-.17.5-.4.29-1.12.84-2.17 1.32-2.91.14-.21.43-.1.4.15l-.26 2.61c-.02.3.2.55.5.55h11.7a.5.5 0 0 0 .5-.55l-.27-2.6c-.02-.26.27-.37.41-.16.48.74 1.03 1.8 1.32 2.9.06.24.26.41.5.41h.22c.81 0 1.47-.66 1.47-1.47A9.53 9.53 0 0 0 12.47 11h-.94Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
roleIconFileFormat: {
|
||||
type: OptionType.SELECT,
|
||||
|
|
@ -87,6 +101,101 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
interface RoleMemberPopoutProps {
|
||||
popoutProps: PopoutProps;
|
||||
guildId: string;
|
||||
channelId: string;
|
||||
roleId: string;
|
||||
}
|
||||
type RoleMemberPopout = ComponentType<RoleMemberPopoutProps>;
|
||||
|
||||
let RoleMemberPopout: RoleMemberPopout = () => null;
|
||||
|
||||
export function buildExtraRoleContextMenuItems(role: Role, guild: Guild, popoutRef?: React.RefObject<any>) {
|
||||
if (!role) return { before: [], after: [] };
|
||||
|
||||
const before = [
|
||||
PermissionStore.getGuildPermissionProps(guild).canManageRoles && (
|
||||
<Menu.MenuItem
|
||||
key="vc-edit-role"
|
||||
id="vc-edit-role"
|
||||
label="Edit Role"
|
||||
action={async () => {
|
||||
await GuildSettingsActions.open(guild.id, "ROLES");
|
||||
GuildSettingsActions.selectRole(role.id);
|
||||
}}
|
||||
icon={PencilIcon}
|
||||
/>
|
||||
),
|
||||
role.colorString && (
|
||||
<Menu.MenuItem
|
||||
key="vc-copy-role-color"
|
||||
id="vc-copy-role-color"
|
||||
label="Copy Role Color"
|
||||
action={() => copyToClipboard(role.colorString!)}
|
||||
icon={AppearanceIcon}
|
||||
/>
|
||||
)
|
||||
].filter(isTruthy);
|
||||
|
||||
const after = [
|
||||
role.icon && (
|
||||
<Menu.MenuItem
|
||||
key="vc-view-role-icon"
|
||||
id="vc-view-role-icon"
|
||||
label="View Role Icon"
|
||||
action={() => {
|
||||
openImageModal({
|
||||
url: `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`,
|
||||
height: 128,
|
||||
width: 128
|
||||
});
|
||||
}}
|
||||
icon={ImageIcon}
|
||||
/>
|
||||
),
|
||||
popoutRef && (
|
||||
<Menu.MenuItem
|
||||
key="vc-view-role-members"
|
||||
id="vc-view-role-members"
|
||||
label="View Role Members"
|
||||
render={() => (
|
||||
<Popout
|
||||
position="right"
|
||||
align="center"
|
||||
targetElementRef={popoutRef}
|
||||
preload={() => loadRoleMembers(guild.id, role.id)}
|
||||
renderPopout={popoutProps => (
|
||||
<RoleMemberPopout
|
||||
popoutProps={popoutProps}
|
||||
guildId={guild.id}
|
||||
channelId={getCurrentChannel()!.id}
|
||||
roleId={role.id}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{popoutProps => (
|
||||
<div
|
||||
className={classes(MenuItemClasses.item, MenuItemClasses.labelContainer, MenuItemClasses.colorDefault)}
|
||||
ref={popoutRef}
|
||||
role="menuitem"
|
||||
{...popoutProps}
|
||||
>
|
||||
<div className={MenuItemClasses.label}>View Role Members</div>
|
||||
<div className={MenuItemClasses.iconContainer}>
|
||||
<RoleMembersIcon />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Popout>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
].filter(isTruthy);
|
||||
|
||||
return { before, after };
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "BetterRoleContext",
|
||||
description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile",
|
||||
|
|
@ -96,13 +205,27 @@ export default definePlugin({
|
|||
|
||||
settings,
|
||||
|
||||
patches: [{
|
||||
find: ".ROLE_MENTION)",
|
||||
replacement: {
|
||||
match: /function (\i)(?=.+?renderPopout:.{0,20}\1,\{guildId:\i,channelId:\i)/,
|
||||
replace: "$self.RoleMembers=$1;$&"
|
||||
}
|
||||
}],
|
||||
|
||||
start() {
|
||||
// DeveloperMode needs to be enabled for the context menu to be shown
|
||||
DeveloperMode.updateSetting(true);
|
||||
},
|
||||
|
||||
set RoleMembers(component: RoleMemberPopout) {
|
||||
RoleMemberPopout = component;
|
||||
},
|
||||
|
||||
contextMenus: {
|
||||
"dev-context"(children, { id }: { id: string; }) {
|
||||
const popoutRef = useRef(null);
|
||||
|
||||
const guild = getCurrentGuild();
|
||||
if (!guild) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const settings = definePluginSettings({
|
|||
description: "Allow click on role dot in profile popout to copy role color",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
|
|
|
|||
|
|
@ -12,6 +12,15 @@ import definePlugin, { OptionType } from "@utils/types";
|
|||
|
||||
let style: HTMLStyleElement;
|
||||
|
||||
const settings = definePluginSettings({
|
||||
blurAmount: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "Blur Amount (in pixels)",
|
||||
default: 10,
|
||||
onChange: setCss
|
||||
}
|
||||
});
|
||||
|
||||
function setCss() {
|
||||
style.textContent = `
|
||||
.vc-nsfw-img [class*=imageContainer],
|
||||
|
|
@ -26,20 +35,6 @@ function setCss() {
|
|||
`;
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
blurAmount: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "Blur Amount (in pixels)",
|
||||
default: 10,
|
||||
onChange: setCss
|
||||
},
|
||||
blurAllChannels: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Blur attachments in all channels (not just NSFW)",
|
||||
default: false
|
||||
},
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "BlurNSFW",
|
||||
description: "Blur attachments in NSFW channels until hovered",
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { HeadingSecondary } from "@components/Heading";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import CustomRpcPlugin from "@plugins/customRPC";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Margins } from "@utils/margins";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -85,7 +86,7 @@ function ImportCustomRPCComponent() {
|
|||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const id = Settings.plugins.CustomRPC?.appID as string | undefined;
|
||||
const id = CustomRpcPlugin.settings.store.appID;
|
||||
if (!id) {
|
||||
return showToast("CustomRPC application ID is not set.", Toasts.Type.FAILURE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ const settings = definePluginSettings({
|
|||
default: false,
|
||||
},
|
||||
statusName: {
|
||||
description: "Custom status text",
|
||||
description: "Custom status text. You can use the following variables: {artist} | {album} | {title}",
|
||||
type: OptionType.STRING,
|
||||
default: "some music",
|
||||
},
|
||||
|
|
@ -176,11 +176,11 @@ const settings = definePluginSettings({
|
|||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
},
|
||||
alwaysHideArt: {
|
||||
description: "Disable downloading album art",
|
||||
showAlbumCover: {
|
||||
description: "Show album cover. Disabling this will display a placeholder. Useful if your Music has inappropriate art",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
},
|
||||
default: true,
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
|
|
@ -258,7 +258,7 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
getLargeImage(track: TrackData): string | undefined {
|
||||
if (!settings.store.alwaysHideArt && track.imageUrl && !track.imageUrl.includes(LASTFM_PLACEHOLDER_IMAGE_HASH))
|
||||
if (settings.store.showAlbumCover && track.imageUrl && !track.imageUrl.includes(LASTFM_PLACEHOLDER_IMAGE_HASH))
|
||||
return track.imageUrl;
|
||||
|
||||
if (settings.store.missingArt === "placeholder")
|
||||
|
|
@ -315,9 +315,15 @@ export default definePlugin({
|
|||
case NameFormat.SongOnly:
|
||||
return trackData.name;
|
||||
case NameFormat.AlbumName:
|
||||
return trackData.album || settings.store.statusName;
|
||||
return trackData.album || settings.store.statusName
|
||||
.replaceAll("{artist}", trackData.artist || "")
|
||||
.replaceAll("{album}", trackData.album || "")
|
||||
.replaceAll("{title}", trackData.name || "");
|
||||
default:
|
||||
return settings.store.statusName;
|
||||
return settings.store.statusName
|
||||
.replaceAll("{artist}", trackData.artist || "")
|
||||
.replaceAll("{album}", trackData.album || "")
|
||||
.replaceAll("{title}", trackData.name || "");
|
||||
}
|
||||
})();
|
||||
|
||||
|
|
|
|||
29
src/plugins/noMiddleClickPaste/index.ts
Normal file
29
src/plugins/noMiddleClickPaste/index.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs, IS_LINUX } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
function preventMiddleClick(e: MouseEvent) {
|
||||
if (e.button === 1) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "NoMiddleClickPaste",
|
||||
description: "Disable Linux middle-click paste - Linux only",
|
||||
authors: [Devs.Darxoon],
|
||||
hidden: !IS_LINUX,
|
||||
|
||||
start() {
|
||||
window.addEventListener("mouseup", preventMiddleClick);
|
||||
},
|
||||
|
||||
stop() {
|
||||
window.removeEventListener("mouseup", preventMiddleClick);
|
||||
},
|
||||
});
|
||||
|
|
@ -27,6 +27,13 @@ export default definePlugin({
|
|||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: '"PLAINTEXT_PREVIEW":"OTHER"',
|
||||
replacement: {
|
||||
match: /"IMAGE"===\i\|\|"VIDEO"===\i\|\|"CLIP"===\i/,
|
||||
replace: "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "renderAttachments(",
|
||||
predicate: () => settings.store.inlineVideo,
|
||||
|
|
|
|||
|
|
@ -20,14 +20,15 @@ import { BaseText } from "@components/BaseText";
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
|
||||
import { cl, getGuildPermissionSpecMap } from "@plugins/permissionsViewer/utils";
|
||||
import { buildExtraRoleContextMenuItems } from "@plugins/betterRoleContext";
|
||||
import { cl, getGuildPermissionSpecMap, loadGetGuildPermissionSpecMap } from "@plugins/permissionsViewer/utils";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { getIntlMessage, getUniqueUsername } from "@utils/discord";
|
||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModalLazy } from "@utils/modal";
|
||||
import { Guild, Role, RoleOrUserPermission, UnicodeEmoji, User } from "@vencord/discord-types";
|
||||
import { PermissionOverwriteType } from "@vencord/discord-types/enums";
|
||||
import { findByCodeLazy } from "@webpack";
|
||||
import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildRoleStore, i18n, Menu, PermissionsBits, ScrollerThin, Tooltip, useEffect, useMemo, UserStore, useState, useStateFromStores } from "@webpack/common";
|
||||
import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildRoleStore, i18n, Menu, PermissionsBits, ScrollerThin, Tooltip, useEffect, useMemo, useRef, UserStore, useState, useStateFromStores } from "@webpack/common";
|
||||
|
||||
import { settings } from "..";
|
||||
import { PermissionAllowedIcon, PermissionDefaultIcon, PermissionDeniedIcon } from "./icons";
|
||||
|
|
@ -205,25 +206,50 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
|
|||
);
|
||||
}
|
||||
|
||||
function IDIcon() {
|
||||
return (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M15.3 14.48c-.46.45-1.08.67-1.86.67h-1.39V9.2h1.39c.78 0 1.4.22 1.86.67.46.45.68 1.22.68 2.31 0 1.1-.22 1.86-.68 2.31Z" />
|
||||
<path fill="currentColor" fillRule="evenodd" clipRule="evenodd" d="M5 2a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3V5a3 3 0 0 0-3-3H5Zm1 15h2.04V7.34H6V17Zm4-9.66V17h3.44c1.46 0 2.6-.42 3.38-1.25.8-.83 1.2-2.02 1.2-3.58s-.4-2.75-1.2-3.58c-.79-.83-1.92-1.25-3.38-1.25H10Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ViewAsRoleIcon() {
|
||||
return (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M20.7 12.7a1 1 0 0 0 0-1.4l-5-5a1 1 0 1 0-1.4 1.4l3.29 3.3H4a1 1 0 1 0 0 2h13.59l-3.3 3.3a1 1 0 0 0 1.42 1.4l5-5Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: string; onClose: () => void; }) {
|
||||
const popoutRef = useRef(null);
|
||||
const role = GuildRoleStore.getRole(guild.id, roleId);
|
||||
const { before, after } = buildExtraRoleContextMenuItems(role, guild, popoutRef);
|
||||
|
||||
return (
|
||||
<Menu.Menu
|
||||
navId={cl("role-context-menu")}
|
||||
onClose={ContextMenuApi.closeContextMenu}
|
||||
aria-label="Role Options"
|
||||
>
|
||||
{before}
|
||||
|
||||
<Menu.MenuItem
|
||||
id={cl("copy-role-id")}
|
||||
label={getIntlMessage("COPY_ID_ROLE")}
|
||||
action={() => {
|
||||
copyToClipboard(roleId);
|
||||
}}
|
||||
icon={IDIcon}
|
||||
action={() => copyToClipboard(roleId)}
|
||||
/>
|
||||
|
||||
{after}
|
||||
|
||||
{(settings.store as any).unsafeViewAsRole && (
|
||||
<Menu.MenuItem
|
||||
id={cl("view-as-role")}
|
||||
label={getIntlMessage("VIEW_AS_ROLE")}
|
||||
icon={ViewAsRoleIcon}
|
||||
action={() => {
|
||||
const role = GuildRoleStore.getRole(guild.id, roleId);
|
||||
if (!role) return;
|
||||
|
|
@ -267,12 +293,16 @@ function UserContextMenu({ userId }: { userId: string; }) {
|
|||
const RolesAndUsersPermissions = ErrorBoundary.wrap(RolesAndUsersPermissionsComponent);
|
||||
|
||||
export default function openRolesAndUsersPermissionsModal(permissions: Array<RoleOrUserPermission>, guild: Guild, header: string) {
|
||||
return openModal(modalProps => (
|
||||
<RolesAndUsersPermissions
|
||||
modalProps={modalProps}
|
||||
permissions={permissions}
|
||||
guild={guild}
|
||||
header={header}
|
||||
/>
|
||||
));
|
||||
return openModalLazy(async () => {
|
||||
await loadGetGuildPermissionSpecMap();
|
||||
|
||||
return modalProps => (
|
||||
<RolesAndUsersPermissions
|
||||
modalProps={modalProps}
|
||||
permissions={permissions}
|
||||
guild={guild}
|
||||
header={header}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ import definePlugin, { OptionType } from "@utils/types";
|
|||
import type { Guild, RoleOrUserPermission } from "@vencord/discord-types";
|
||||
import { PermissionOverwriteType } from "@vencord/discord-types/enums";
|
||||
import { findCssClassesLazy } from "@webpack";
|
||||
import { Button, ChannelStore, Dialog, GuildMemberStore, GuildRoleStore, GuildStore, match, Menu, PermissionsBits, Popout, useRef, UserStore } from "@webpack/common";
|
||||
import { Button, ChannelStore, Dialog, GuildMemberStore, GuildRoleStore, GuildStore, match, Menu, PermissionsBits, Popout, useEffect, useRef, UserStore } from "@webpack/common";
|
||||
|
||||
import openRolesAndUsersPermissionsModal from "./components/RolesAndUsersPermissions";
|
||||
import UserPermissions from "./components/UserPermissions";
|
||||
import { getSortedRolesForMember, sortPermissionOverwrites } from "./utils";
|
||||
import { getSortedRolesForMember, loadGetGuildPermissionSpecMap, sortPermissionOverwrites } from "./utils";
|
||||
|
||||
const PopoutClasses = findCssClassesLazy("container", "popoutRoleDot");
|
||||
|
||||
|
|
@ -175,11 +175,12 @@ export default definePlugin({
|
|||
],
|
||||
|
||||
ViewPermissionsButton: ErrorBoundary.wrap(({ className, guild, userId }: { className: string; guild: Guild; userId: string; }) => {
|
||||
const buttonRef = useRef(null);
|
||||
useEffect(() => void loadGetGuildPermissionSpecMap(), []);
|
||||
|
||||
const guildMember = GuildMemberStore.getMember(guild.id, userId);
|
||||
if (!guildMember) return null;
|
||||
|
||||
const buttonRef = useRef(null);
|
||||
|
||||
return (
|
||||
<Popout
|
||||
position="bottom"
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@
|
|||
import { classNameFactory } from "@utils/css";
|
||||
import { Guild, GuildMember, Role } from "@vencord/discord-types";
|
||||
import { PermissionOverwriteType } from "@vencord/discord-types/enums";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { extractAndLoadChunksLazy, findByPropsLazy } from "@webpack";
|
||||
import { GuildRoleStore } from "@webpack/common";
|
||||
|
||||
import { PermissionsSortOrder, settings } from ".";
|
||||
|
||||
export const loadGetGuildPermissionSpecMap = extractAndLoadChunksLazy([".PRIMARY,badgeTooltipDelay:"]);
|
||||
export const { getGuildPermissionSpecMap } = findByPropsLazy("getGuildPermissionSpecMap");
|
||||
|
||||
export const cl = classNameFactory("vc-permviewer-");
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
*/
|
||||
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { BaseText } from "@components/BaseText";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import PermissionsViewerPlugin from "@plugins/permissionsViewer";
|
||||
|
|
@ -146,7 +145,7 @@ function HiddenChannelLockScreen({ channel }: { channel: Channel; }) {
|
|||
});
|
||||
}
|
||||
|
||||
if (Settings.plugins.PermissionsViewer.enabled) {
|
||||
if (isPluginEnabled(PermissionsViewerPlugin.name)) {
|
||||
setPermissions(sortPermissionOverwrites(Object.values(permissionOverwrites).map(overwrite => ({
|
||||
type: overwrite.type,
|
||||
id: overwrite.id,
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@
|
|||
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { TooltipContainer } from "@components/TooltipContainer";
|
||||
import { classes } from "@utils/misc";
|
||||
import { openModal } from "@utils/modal";
|
||||
import { IconComponent } from "@utils/types";
|
||||
import { Alerts, Tooltip, useEffect, useState } from "@webpack/common";
|
||||
import { Alerts, useEffect, useState } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import { TranslateModal } from "./TranslateModal";
|
||||
import { openTranslateModal } from "./TranslateModal";
|
||||
import { cl } from "./utils";
|
||||
|
||||
export const TranslateIcon: IconComponent = ({ height = 20, width = 20, className }) => {
|
||||
|
|
@ -79,10 +79,7 @@ export const TranslateChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
|||
tooltip="Open Translate Modal"
|
||||
onClick={e => {
|
||||
if (e.shiftKey) return toggle();
|
||||
|
||||
openModal(props => (
|
||||
<TranslateModal rootProps={props} />
|
||||
));
|
||||
else openTranslateModal();
|
||||
}}
|
||||
onContextMenu={toggle}
|
||||
buttonProps={{
|
||||
|
|
@ -95,9 +92,9 @@ export const TranslateChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
|||
|
||||
if (shouldShowTranslateEnabledTooltip && settings.store.showAutoTranslateTooltip)
|
||||
return (
|
||||
<Tooltip text="Auto Translate Enabled" forceOpen>
|
||||
{() => button}
|
||||
</Tooltip>
|
||||
<TooltipContainer text="Auto Translate Enabled" forceOpen>
|
||||
{button}
|
||||
</TooltipContainer>
|
||||
);
|
||||
|
||||
return button;
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { BaseText } from "@components/BaseText";
|
||||
import { Divider } from "@components/Divider";
|
||||
import { FormSwitch } from "@components/FormSwitch";
|
||||
import { HeadingPrimary, HeadingSecondary } from "@components/Heading";
|
||||
import { HeadingSecondary } from "@components/Heading";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
|
||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
|
||||
import { SearchableSelect, useMemo } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
|
|
@ -77,9 +78,9 @@ export function TranslateModal({ rootProps }: { rootProps: ModalProps; }) {
|
|||
return (
|
||||
<ModalRoot {...rootProps}>
|
||||
<ModalHeader className={cl("modal-header")}>
|
||||
<HeadingPrimary className={cl("modal-title")}>
|
||||
<BaseText tag="h2" size="lg" weight="semibold" className={cl("modal-title")}>
|
||||
Translate
|
||||
</HeadingPrimary>
|
||||
</BaseText>
|
||||
<ModalCloseButton onClick={rootProps.onClose} />
|
||||
</ModalHeader>
|
||||
|
||||
|
|
@ -99,3 +100,7 @@ export function TranslateModal({ rootProps }: { rootProps: ModalProps; }) {
|
|||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export function openTranslateModal() {
|
||||
openModal(props => <TranslateModal rootProps={props} />);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,10 +62,10 @@ let tooltipTimeout: any;
|
|||
|
||||
export default definePlugin({
|
||||
name: "Translate",
|
||||
description: "Translate messages with Google Translate or DeepL",
|
||||
description: "Translate messages with Google Translate, DeepL or Kagi.",
|
||||
dependencies: ["ChatInputButtonAPI", "MessageAccessoriesAPI", "MessagePopoverAPI"],
|
||||
tags: ["Chat", "Utility"],
|
||||
authors: [Devs.Ven, Devs.AshtonMemer],
|
||||
authors: [Devs.Ven, Devs.AshtonMemer, Devs.koish1],
|
||||
settings,
|
||||
contextMenus: {
|
||||
"message": messageCtxPatch
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ copy(Object.fromEntries(
|
|||
|
||||
export type GoogleLanguage = keyof typeof GoogleLanguages;
|
||||
export type DeeplLanguage = keyof typeof DeeplLanguages;
|
||||
export type KagiLanguage = keyof typeof KagiLanguages;
|
||||
|
||||
export const GoogleLanguages = {
|
||||
"auto": "Detect language",
|
||||
|
|
@ -318,3 +319,392 @@ export function deeplLanguageToGoogleLanguage(language: string) {
|
|||
return language;
|
||||
}
|
||||
}
|
||||
|
||||
// Generated using:
|
||||
// curl https://translate.kagi.com/api/list-languages | jq 'map({(.language | ascii_downcase): .name}) | add | to_entries | sort_by(.value) | from_entries'
|
||||
// Add "auto": "Detect language", as kagi has no "language" for Detect Language, however anything not
|
||||
// in the list will work as auto. Using "auto" will have this work using Google Translate defaults.
|
||||
// Retrieved 2025.06.16
|
||||
export const KagiLanguages = {
|
||||
"auto": "Detect language",
|
||||
"om": "(Afan)/Oromoor/Oriya",
|
||||
"ab": "Abkhazian",
|
||||
"ace": "Acehnese",
|
||||
"ach": "Acholi",
|
||||
"aa": "Afar",
|
||||
"af": "Afrikaans",
|
||||
"ak": "Akan",
|
||||
"sq": "Albanian",
|
||||
"alz": "Alur",
|
||||
"am": "Amharic",
|
||||
"ar": "Arabic",
|
||||
"ar_dz": "Arabic (Algeria)",
|
||||
"ar_eg": "Arabic (Egypt)",
|
||||
"ar_iq": "Arabic (Iraq)",
|
||||
"ar_jo": "Arabic (Jordan)",
|
||||
"ar_kw": "Arabic (Kuwait)",
|
||||
"ar_lb": "Arabic (Lebanon)",
|
||||
"ar_ly": "Arabic (Libya)",
|
||||
"ar_ma": "Arabic (Morocco)",
|
||||
"ar_qa": "Arabic (Qatar)",
|
||||
"ar_sa": "Arabic (Saudi Arabia)",
|
||||
"ar_sy": "Arabic (Syria)",
|
||||
"ar_tn": "Arabic (Tunisia)",
|
||||
"ar_ae": "Arabic (UAE)",
|
||||
"an": "Aragonese",
|
||||
"hy": "Armenian",
|
||||
"as": "Assamese",
|
||||
"av": "Avaric",
|
||||
"ae": "Avestan",
|
||||
"ava": "Awadhi",
|
||||
"ay": "Aymara",
|
||||
"az": "Azeri",
|
||||
"ban": "Balinese",
|
||||
"bal": "Baluchi",
|
||||
"bm": "Bambara",
|
||||
"bci": "Baoulé",
|
||||
"ba": "Bashkir",
|
||||
"eu": "Basque",
|
||||
"btk": "Batak",
|
||||
"be": "Belarusian",
|
||||
"bem": "Bemba",
|
||||
"bn": "Bengali",
|
||||
"bn_bd": "Bengali (Bangladesh)",
|
||||
"bn_in": "Bengali (India)",
|
||||
"bew": "Betawi",
|
||||
"bho": "Bhojpuri",
|
||||
"dz": "Bhutani",
|
||||
"bh": "Bihari",
|
||||
"bcl": "Bikol",
|
||||
"bi": "Bislama",
|
||||
"bs": "Bosnian",
|
||||
"br": "Breton",
|
||||
"bg": "Bulgarian",
|
||||
"my": "Burmese",
|
||||
"bua": "Buryat",
|
||||
"km": "Cambodian",
|
||||
"yue": "Cantonese",
|
||||
"ca": "Catalan",
|
||||
"ceb": "Cebuano",
|
||||
"ch": "Chamorro",
|
||||
"ce": "Chechen",
|
||||
"chr": "Cherokee",
|
||||
"ny": "Chichewa",
|
||||
"zh": "Chinese",
|
||||
"zh_hk": "Chinese (Hong Kong)",
|
||||
"zh_mo": "Chinese (Macau)",
|
||||
"zh_cn": "Chinese (Simplified)",
|
||||
"zh_sg": "Chinese (Singapore)",
|
||||
"zh_tw": "Chinese (Traditional)",
|
||||
"cu": "Church Slavonic",
|
||||
"chk": "Chuukese",
|
||||
"cv": "Chuvash",
|
||||
"kw": "Cornish",
|
||||
"co": "Corsican",
|
||||
"cr": "Cree",
|
||||
"crh": "Crimean Tatar",
|
||||
"hr": "Croatian",
|
||||
"hr_ba": "Croatian (Bosnia and Herzegovina)",
|
||||
"hr_hr": "Croatian (Croatia)",
|
||||
"cs": "Czech",
|
||||
"da": "Danish",
|
||||
"din": "Dinka",
|
||||
"div": "Divehi",
|
||||
"dv": "Divehi",
|
||||
"doi": "Dogri",
|
||||
"dov": "Dombe",
|
||||
"nl": "Dutch",
|
||||
"nl_aw": "Dutch (Aruba)",
|
||||
"nl_be": "Dutch (Belgium)",
|
||||
"nl_cw": "Dutch (Curaçao)",
|
||||
"nl_nl": "Dutch (Netherlands)",
|
||||
"nl_sr": "Dutch (Suriname)",
|
||||
"dyu": "Dyula",
|
||||
"elvish": "Elvish (Sindarin)",
|
||||
"emoji": "Emoji Speak",
|
||||
"en": "English",
|
||||
"en_au": "English (Australia)",
|
||||
"en_ca": "English (Canada)",
|
||||
"en_in": "English (India)",
|
||||
"en_ie": "English (Ireland)",
|
||||
"en_nz": "English (New Zealand)",
|
||||
"en_ph": "English (Philippines)",
|
||||
"en_sg": "English (Singapore)",
|
||||
"en_za": "English (South Africa)",
|
||||
"en_gb": "English (UK)",
|
||||
"en_us": "English (US)",
|
||||
"eo": "Esperanto",
|
||||
"et": "Estonian",
|
||||
"ee": "Ewe",
|
||||
"mk": "FYRO Macedonian",
|
||||
"fo": "Faroese",
|
||||
"fa": "Farsi",
|
||||
"fj": "Fiji",
|
||||
"fil": "Filipino",
|
||||
"fi": "Finnish",
|
||||
"fon": "Fon",
|
||||
"fr": "French",
|
||||
"fr_be": "French (Belgium)",
|
||||
"fr_ca": "French (Canada)",
|
||||
"fr_ci": "French (Côte d'Ivoire)",
|
||||
"fr_fr": "French (France)",
|
||||
"fr_sn": "French (Senegal)",
|
||||
"fr_ch": "French (Switzerland)",
|
||||
"fy": "Frisian",
|
||||
"fur": "Friulian",
|
||||
"ff": "Fulah",
|
||||
"gaa": "Ga",
|
||||
"gd": "Gaelic",
|
||||
"gl": "Galician",
|
||||
"lg": "Ganda",
|
||||
"ka": "Georgian",
|
||||
"de": "German",
|
||||
"de_at": "German (Austria)",
|
||||
"de_de": "German (Germany)",
|
||||
"de_li": "German (Liechtenstein)",
|
||||
"de_lu": "German (Luxembourg)",
|
||||
"el": "Greek",
|
||||
"kl": "Greenlandic",
|
||||
"gn": "Guarani",
|
||||
"gu": "Gujarati",
|
||||
"ht": "Haitian",
|
||||
"ha": "Hausa",
|
||||
"haw": "Hawaiian",
|
||||
"he": "Hebrew",
|
||||
"hz": "Herero",
|
||||
"hil": "Hiligaynon",
|
||||
"hi": "Hindi",
|
||||
"hi_fj": "Hindi (Fiji)",
|
||||
"hi_in": "Hindi (India)",
|
||||
"ho": "Hiri Motu",
|
||||
"hu": "Hungarian",
|
||||
"hrx": "Hunsrik",
|
||||
"iba": "Iban",
|
||||
"is": "Icelandic",
|
||||
"io": "Ido",
|
||||
"ig": "Igbo",
|
||||
"ilo": "Ilocano",
|
||||
"id": "Indonesian",
|
||||
"ia": "Interlingua",
|
||||
"ie": "Interlingue",
|
||||
"iu": "Inuktitut",
|
||||
"ik": "Inupiak",
|
||||
"ga": "Irish",
|
||||
"it": "Italian",
|
||||
"it_it": "Italian (Italy)",
|
||||
"it_sm": "Italian (San Marino)",
|
||||
"it_ch": "Italian (Switzerland)",
|
||||
"it_va": "Italian (Vatican City)",
|
||||
"jam": "Jamaican Patois",
|
||||
"ja": "Japanese",
|
||||
"jv": "Javanese",
|
||||
"cpe": "Jingpo",
|
||||
"kn": "Kannada",
|
||||
"kr": "Kanuri",
|
||||
"ks": "Kashmiri",
|
||||
"kk": "Kazakh",
|
||||
"kha": "Khasi",
|
||||
"ki": "Kikuyu",
|
||||
"rw": "Kinyarwanda",
|
||||
"ky": "Kirghiz",
|
||||
"rn": "Kirundi",
|
||||
"ktu": "Kituba",
|
||||
"klingon": "Klingon",
|
||||
"kv": "Komi",
|
||||
"kg": "Kongo",
|
||||
"kok": "Konkani",
|
||||
"ko": "Korean",
|
||||
"ko_kp": "Korean (North Korea)",
|
||||
"ko_kr": "Korean (South Korea)",
|
||||
"kj": "Kuanyama",
|
||||
"ku": "Kurdish",
|
||||
"ckb": "Kurdish (Sorani)",
|
||||
"lo": "Laothian",
|
||||
"ltg": "Latgalian",
|
||||
"la": "Latin",
|
||||
"lv": "Latvian",
|
||||
"lij": "Ligurian",
|
||||
"li": "Limburgan",
|
||||
"lim": "Limburgish",
|
||||
"ln": "Lingala",
|
||||
"lt": "Lithuanian",
|
||||
"lmo": "Lombard",
|
||||
"lu": "Luba_Katanga",
|
||||
"luo": "Luo",
|
||||
"lb": "Luxembourgish",
|
||||
"mad": "Madurese",
|
||||
"mai": "Maithili",
|
||||
"mak": "Makassar",
|
||||
"mg": "Malagasy",
|
||||
"ms": "Malay",
|
||||
"ms_bn": "Malay (Brunei Darussalam)",
|
||||
"ms_id": "Malay (Indonesia)",
|
||||
"ms_arab": "Malay (Jawi)",
|
||||
"ms_my": "Malay (Malaysia)",
|
||||
"ms_sg": "Malay (Singapore)",
|
||||
"ml": "Malayalam",
|
||||
"mt": "Maltese",
|
||||
"gv": "Manx",
|
||||
"mi": "Maori",
|
||||
"mr": "Marathi",
|
||||
"mh": "Marshallese",
|
||||
"mwr": "Marwadi",
|
||||
"middle_english": "Middle English",
|
||||
"mo": "Moldavian",
|
||||
"mn": "Mongolian",
|
||||
"nqo": "NKo",
|
||||
"nhe": "Nahuatl",
|
||||
"na": "Nauru",
|
||||
"nv": "Navajo",
|
||||
"ndc": "Ndau",
|
||||
"ng": "Ndonga",
|
||||
"new": "Nepalbhasa",
|
||||
"ne": "Nepali",
|
||||
"nd": "North Ndebele",
|
||||
"ns": "Northern Sotho",
|
||||
"nb": "Norwegian (Bokmål)",
|
||||
"nn": "Norwegian (Nynorsk)",
|
||||
"nus": "Nuer",
|
||||
"oc": "Occitan",
|
||||
"oj": "Ojibwa",
|
||||
"or": "Oriya",
|
||||
"os": "Ossetian",
|
||||
"pi": "Pali",
|
||||
"pag": "Pangasinan",
|
||||
"pap": "Papiamento",
|
||||
"ps": "Pashto/Pushto",
|
||||
"per": "Persian",
|
||||
"fa_af": "Persian (Afghanistan)",
|
||||
"fa_ir": "Persian (Iran)",
|
||||
"fa_tj": "Persian (Tajikistan)",
|
||||
"pirate": "Pirate Speak",
|
||||
"pl": "Polish",
|
||||
"pt": "Portuguese",
|
||||
"pt_ao": "Portuguese (Angola)",
|
||||
"pt_br": "Portuguese (Brazil)",
|
||||
"pt_cv": "Portuguese (Cape Verde)",
|
||||
"pt_mz": "Portuguese (Mozambique)",
|
||||
"pt_pt": "Portuguese (Portugal)",
|
||||
"pa": "Punjabi",
|
||||
"qu": "Quechua",
|
||||
"qu_bo": "Quechua (Bolivia)",
|
||||
"qu_ec": "Quechua (Ecuador)",
|
||||
"qu_pe": "Quechua (Peru)",
|
||||
"kek": "Qʼeqchiʼ",
|
||||
"rm": "Rhaeto_Romanic",
|
||||
"rom": "Romani",
|
||||
"ro": "Romanian",
|
||||
"ru": "Russian",
|
||||
"ru_by": "Russian (Belarus)",
|
||||
"ru_kz": "Russian (Kazakhstan)",
|
||||
"ru_kg": "Russian (Kyrgyzstan)",
|
||||
"ru_ru": "Russian (Russia)",
|
||||
"se": "Sami",
|
||||
"se_fi": "Sami (Finland)",
|
||||
"se_no": "Sami (Norway)",
|
||||
"se_se": "Sami (Sweden)",
|
||||
"sm": "Samoan",
|
||||
"sg": "Sangro",
|
||||
"sa": "Sanskrit",
|
||||
"sat": "Santali",
|
||||
"sc": "Sardinian",
|
||||
"sr": "Serbian",
|
||||
"sr_ba": "Serbian (Bosnia and Herzegovina)",
|
||||
"sr_sp": "Serbian (Serbia and Montenegro)",
|
||||
"sh": "Serbo_Croatian",
|
||||
"st": "Sesotho",
|
||||
"shn": "Shan",
|
||||
"sn": "Shona",
|
||||
"ii": "Sichuan Yi",
|
||||
"scn": "Sicilian",
|
||||
"szl": "Silesian",
|
||||
"sd": "Sindhi",
|
||||
"si": "Singhalese",
|
||||
"ss": "Siswati",
|
||||
"sk": "Slovak",
|
||||
"sl": "Slovenian",
|
||||
"so": "Somali",
|
||||
"sb": "Sorbian",
|
||||
"nr": "South Ndebele",
|
||||
"es": "Spanish",
|
||||
"es_ar": "Spanish (Argentina)",
|
||||
"es_bo": "Spanish (Bolivia)",
|
||||
"es_cl": "Spanish (Chile)",
|
||||
"es_co": "Spanish (Colombia)",
|
||||
"es_cr": "Spanish (Costa Rica)",
|
||||
"es_cu": "Spanish (Cuba)",
|
||||
"es_do": "Spanish (Dominican Republic)",
|
||||
"es_ec": "Spanish (Ecuador)",
|
||||
"es_sv": "Spanish (El Salvador)",
|
||||
"es_gq": "Spanish (Equatorial Guinea)",
|
||||
"es_gt": "Spanish (Guatemala)",
|
||||
"es_hn": "Spanish (Honduras)",
|
||||
"es_mx": "Spanish (Mexico)",
|
||||
"es_ni": "Spanish (Nicaragua)",
|
||||
"es_pa": "Spanish (Panama)",
|
||||
"es_py": "Spanish (Paraguay)",
|
||||
"es_pe": "Spanish (Peru)",
|
||||
"es_pr": "Spanish (Puerto Rico)",
|
||||
"es_es": "Spanish (Spain)",
|
||||
"es_uy": "Spanish (Uruguay)",
|
||||
"es_ve": "Spanish (Venezuela)",
|
||||
"su": "Sundanese",
|
||||
"sus": "Susu",
|
||||
"sx": "Sutu",
|
||||
"sw": "Swahili",
|
||||
"sv": "Swedish",
|
||||
"sv_fi": "Swedish (Finland)",
|
||||
"sv_se": "Swedish (Sweden)",
|
||||
"gsw": "Swiss German",
|
||||
"de_ch": "Swiss High German",
|
||||
"syr": "Syriac",
|
||||
"tl": "Tagalog",
|
||||
"ty": "Tahitian",
|
||||
"tg": "Tajik",
|
||||
"ta": "Tamil",
|
||||
"ta_in": "Tamil (India)",
|
||||
"ta_my": "Tamil (Malaysia)",
|
||||
"ta_sg": "Tamil (Singapore)",
|
||||
"ta_lk": "Tamil (Sri Lanka)",
|
||||
"tt": "Tatar",
|
||||
"te": "Telugu",
|
||||
"tet": "Tetum",
|
||||
"th": "Thai",
|
||||
"bo": "Tibetan",
|
||||
"ti": "Tigrinya",
|
||||
"tiv": "Tiv",
|
||||
"tpi": "Tok Pisin",
|
||||
"to": "Tonga",
|
||||
"ts": "Tsonga",
|
||||
"tn": "Tswana",
|
||||
"tum": "Tumbuka",
|
||||
"tr": "Turkish",
|
||||
"tr_cy": "Turkish (Cyprus)",
|
||||
"tr_tr": "Turkish (Turkey)",
|
||||
"tk": "Turkmen",
|
||||
"tyv": "Tuvan",
|
||||
"tw": "Twi",
|
||||
"udm": "Udmurt",
|
||||
"ug": "Uighur",
|
||||
"uk": "Ukrainian",
|
||||
"ur": "Urdu",
|
||||
"ur_in": "Urdu (India)",
|
||||
"ur_pk": "Urdu (Pakistan)",
|
||||
"uz": "Uzbek",
|
||||
"ve": "Venda",
|
||||
"vec": "Venetian",
|
||||
"vi": "Vietnamese",
|
||||
"vo": "Volapuk",
|
||||
"wa": "Walloon",
|
||||
"war": "Waray",
|
||||
"cy": "Welsh",
|
||||
"wo": "Wolof",
|
||||
"xh": "Xhosa",
|
||||
"sah": "Yakut",
|
||||
"yi": "Yiddish",
|
||||
"yo": "Yoruba",
|
||||
"yua": "Yucatec Maya",
|
||||
"zap": "Zapotec",
|
||||
"za": "Zhuang",
|
||||
"zu": "Zulu"
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -27,3 +27,28 @@ export async function makeDeeplTranslateRequest(_: IpcMainInvokeEvent, pro: bool
|
|||
return { status: -1, data: String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
export async function makeKagiTranslateRequest(_: IpcMainInvokeEvent, token: string, text: string, sourceLang: string, targetLang: string) {
|
||||
const url = "https://translate.kagi.com/api/translate";
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Cookie": `kagi_session=${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text,
|
||||
from: sourceLang,
|
||||
to: targetLang,
|
||||
model: "standard"
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
return { status: res.status, data };
|
||||
} catch (e) {
|
||||
return { status: -1, data: String(e) };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,68 +17,89 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Button } from "@components/Button";
|
||||
import { OptionType } from "@utils/types";
|
||||
|
||||
import { openTranslateModal } from "./TranslateModal";
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
receivedInput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Language that received messages should be translated from",
|
||||
description: "Language incoming messages are translated from",
|
||||
default: "auto",
|
||||
hidden: true
|
||||
},
|
||||
receivedOutput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Language that received messages should be translated to",
|
||||
description: "Language incoming messages are translated to",
|
||||
default: "en",
|
||||
hidden: true
|
||||
},
|
||||
sentInput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Language that your own messages should be translated from",
|
||||
description: "Language your messages are translated from",
|
||||
default: "auto",
|
||||
hidden: true
|
||||
},
|
||||
sentOutput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Language that your own messages should be translated to",
|
||||
description: "Language your messages are translated to",
|
||||
default: "en",
|
||||
hidden: true
|
||||
},
|
||||
|
||||
service: {
|
||||
type: OptionType.SELECT,
|
||||
description: IS_WEB ? "Translation service (Not supported on Web!)" : "Translation service",
|
||||
disabled: () => IS_WEB,
|
||||
description: IS_WEB ? "Translation provider (not available on web)" : "Translation provider",
|
||||
hidden: IS_WEB,
|
||||
options: [
|
||||
{ label: "Google Translate", value: "google", default: true },
|
||||
{ label: "DeepL Free", value: "deepl" },
|
||||
{ label: "DeepL Pro", value: "deepl-pro" }
|
||||
{ label: "DeepL Free — API key required", value: "deepl" },
|
||||
{ label: "DeepL Pro — API key required", value: "deepl-pro" },
|
||||
{ label: "Kagi Translate — API key required", value: "kagi" }
|
||||
] as const,
|
||||
onChange: resetLanguageDefaults
|
||||
},
|
||||
deeplApiKey: {
|
||||
type: OptionType.STRING,
|
||||
description: "DeepL API key",
|
||||
default: "",
|
||||
placeholder: "Get your API key from https://deepl.com/your-account",
|
||||
disabled: () => IS_WEB
|
||||
description: "Your DeepL API key (from deepl.com/your-account)",
|
||||
default: ""
|
||||
},
|
||||
kagiSession: {
|
||||
type: OptionType.STRING,
|
||||
description: "Your Kagi session token (from kagi.com/settings?p=user_details)",
|
||||
default: ""
|
||||
},
|
||||
autoTranslate: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Automatically translate your messages before sending. You can also shift/right click the translate button to toggle this",
|
||||
description: "Automatically translate your messages before sending. You can also Shift+click or right-click the translate button to toggle this",
|
||||
default: false
|
||||
},
|
||||
showAutoTranslateTooltip: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show a tooltip on the ChatBar button whenever a message is automatically translated",
|
||||
description: "Show a tooltip on the chat bar button when a message is auto-translated",
|
||||
default: true
|
||||
},
|
||||
manageTranslateSettings: {
|
||||
type: OptionType.COMPONENT,
|
||||
component: () => (
|
||||
<Button onClick={openTranslateModal}>
|
||||
Customize translation languages & Auto-Translate
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
}, {
|
||||
deeplApiKey: {
|
||||
hidden() { return this.store.service !== "deepl" && this.store.service !== "deepl-pro"; }
|
||||
},
|
||||
kagiSession: {
|
||||
hidden() { return this.store.service !== "kagi"; }
|
||||
}
|
||||
}).withPrivateSettings<{
|
||||
showAutoTranslateAlert: boolean;
|
||||
}>();
|
||||
|
||||
export function resetLanguageDefaults() {
|
||||
if (IS_WEB || settings.store.service === "google") {
|
||||
if (IS_WEB || settings.store.service === "google" || settings.store.service === "kagi") {
|
||||
settings.store.receivedInput = "auto";
|
||||
settings.store.receivedOutput = "en";
|
||||
settings.store.sentInput = "auto";
|
||||
|
|
@ -21,7 +21,7 @@ import { onlyOnce } from "@utils/onlyOnce";
|
|||
import { PluginNative } from "@utils/types";
|
||||
import { showToast, Toasts } from "@webpack/common";
|
||||
|
||||
import { DeeplLanguages, deeplLanguageToGoogleLanguage, GoogleLanguages } from "./languages";
|
||||
import { DeeplLanguages, deeplLanguageToGoogleLanguage, GoogleLanguages, KagiLanguages } from "./languages";
|
||||
import { resetLanguageDefaults, settings } from "./settings";
|
||||
|
||||
export const cl = classNameFactory("vc-trans-");
|
||||
|
|
@ -40,19 +40,43 @@ interface DeeplData {
|
|||
}[];
|
||||
}
|
||||
|
||||
interface KagiData {
|
||||
translation: string;
|
||||
detected_language: {
|
||||
label: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TranslationValue {
|
||||
sourceLanguage: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export const getLanguages = () => IS_WEB || settings.store.service === "google"
|
||||
? GoogleLanguages
|
||||
: DeeplLanguages;
|
||||
export const getLanguages = () => {
|
||||
if (IS_WEB) {
|
||||
return GoogleLanguages;
|
||||
}
|
||||
switch (settings.store.service) {
|
||||
case "google":
|
||||
return GoogleLanguages;
|
||||
case "kagi":
|
||||
return KagiLanguages;
|
||||
default:
|
||||
return DeeplLanguages;
|
||||
}
|
||||
};
|
||||
|
||||
export async function translate(kind: "received" | "sent", text: string): Promise<TranslationValue> {
|
||||
const translate = IS_WEB || settings.store.service === "google"
|
||||
? googleTranslate
|
||||
: deeplTranslate;
|
||||
const translate = IS_WEB ? googleTranslate : (() => {
|
||||
switch (settings.store.service) {
|
||||
case "google":
|
||||
return googleTranslate;
|
||||
case "kagi":
|
||||
return kagiTranslate;
|
||||
default:
|
||||
return deeplTranslate;
|
||||
}
|
||||
})();
|
||||
|
||||
try {
|
||||
return await translate(
|
||||
|
|
@ -153,3 +177,25 @@ async function deeplTranslate(text: string, sourceLang: string, targetLang: stri
|
|||
text: translations[0].text
|
||||
};
|
||||
}
|
||||
|
||||
async function kagiTranslate(text: string, sourceLang: string, targetLang: string): Promise<TranslationValue> {
|
||||
const { status, data } = await Native.makeKagiTranslateRequest(
|
||||
settings.store.kagiSession, text, sourceLang, targetLang
|
||||
);
|
||||
|
||||
switch (status) {
|
||||
case 200:
|
||||
break;
|
||||
case 401:
|
||||
throw "Invalid or expired Kagi session token";
|
||||
default:
|
||||
throw new Error(`Failed to translate "${text}" (${sourceLang} -> ${targetLang})\n${status} ${data}`);
|
||||
}
|
||||
|
||||
const { detected_language, translation }: KagiData = data;
|
||||
|
||||
return {
|
||||
sourceLanguage: detected_language.label,
|
||||
text: translation
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@
|
|||
|
||||
import "./style.css";
|
||||
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { buildSeveralUsers } from "@plugins/typingTweaks";
|
||||
import TypingTweaksPlugin, { buildSeveralUsers } from "@plugins/typingTweaks";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getIntlMessage } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -85,7 +86,7 @@ function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: s
|
|||
break;
|
||||
}
|
||||
default: {
|
||||
tooltipText = Settings.plugins.TypingTweaks.enabled
|
||||
tooltipText = isPluginEnabled(TypingTweaksPlugin.name)
|
||||
? buildSeveralUsers({ users: [a, b].map(UserStore.getUser), count: typingUsersArray.length - 2, guildId })
|
||||
: getIntlMessage("SEVERAL_USERS_TYPING");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -620,6 +620,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
name: "jamesbt365",
|
||||
id: 158567567487795200n
|
||||
},
|
||||
Darxoon: {
|
||||
name: "Darxoon",
|
||||
id: 409745838898937866n
|
||||
},
|
||||
samsam: {
|
||||
name: "samsam",
|
||||
id: 400482410279469056n
|
||||
|
|
@ -628,6 +632,11 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
name: "Cootshk",
|
||||
id: 921605971577548820n
|
||||
},
|
||||
koish1: {
|
||||
name: "koish1",
|
||||
id: 291089948709486593n,
|
||||
badge: false,
|
||||
},
|
||||
thororen: {
|
||||
name: "thororen",
|
||||
id: 848339671629299742n
|
||||
|
|
|
|||
|
|
@ -173,12 +173,6 @@ export interface PluginDef {
|
|||
* Which parts of the plugin can be tested by the reporter. Defaults to all parts
|
||||
*/
|
||||
reporterTestable?: number;
|
||||
/**
|
||||
* Optionally provide settings that the user can configure in the Plugins tab of settings.
|
||||
* @deprecated Use `settings` instead
|
||||
*/
|
||||
// TODO: Remove when everything is migrated to `settings`
|
||||
options?: Record<string, PluginOptionsItem>;
|
||||
/**
|
||||
* Optionally provide settings that the user can configure in the Plugins tab of settings.
|
||||
*/
|
||||
|
|
@ -280,22 +274,21 @@ export const enum OptionType {
|
|||
|
||||
export type SettingsDefinition = Record<string, PluginSettingDef>;
|
||||
export type SettingsChecks<D extends SettingsDefinition> = {
|
||||
[K in keyof D]?: D[K] extends PluginSettingComponentDef ? IsDisabled<DefinedSettings<D>> :
|
||||
(IsDisabled<DefinedSettings<D>> & IsValid<PluginSettingType<D[K]>, DefinedSettings<D>>);
|
||||
[K in keyof D]?: D[K] extends PluginSettingComponentDef ? IsDisabledOrHidden<DefinedSettings<D>> :
|
||||
(IsDisabledOrHidden<DefinedSettings<D>> & IsValid<PluginSettingType<D[K]>, DefinedSettings<D>>);
|
||||
};
|
||||
|
||||
export type PluginSettingDef =
|
||||
(PluginSettingCommon & PluginSettingCustomDef & Pick<PluginSettingCommon, "onChange">) |
|
||||
(PluginSettingComponentDef & Omit<PluginSettingCommon, "description" | "placeholder">) | ((
|
||||
| PluginSettingStringDef
|
||||
| PluginSettingNumberDef
|
||||
| PluginSettingBooleanDef
|
||||
| PluginSettingSelectDef
|
||||
| PluginSettingSliderDef
|
||||
| PluginSettingBigIntDef
|
||||
) & PluginSettingCommon);
|
||||
| PluginSettingCustomDef
|
||||
| PluginSettingComponentDef
|
||||
| PluginSettingStringDef
|
||||
| PluginSettingNumberDef
|
||||
| PluginSettingBooleanDef
|
||||
| PluginSettingSelectDef
|
||||
| PluginSettingSliderDef
|
||||
| PluginSettingBigIntDef;
|
||||
|
||||
export interface PluginSettingCommon {
|
||||
export interface PluginSettingDefCommon extends IsDisabledOrHidden, IsValid<unknown> {
|
||||
description: string;
|
||||
placeholder?: string;
|
||||
onChange?(newValue: any): void;
|
||||
|
|
@ -304,50 +297,50 @@ export interface PluginSettingCommon {
|
|||
*/
|
||||
restartNeeded?: boolean;
|
||||
componentProps?: Record<string, any>;
|
||||
/**
|
||||
* Hide this setting from the settings UI
|
||||
*/
|
||||
hidden?: boolean;
|
||||
/**
|
||||
* Set this if the setting only works on Browser or Desktop, not both
|
||||
*/
|
||||
target?: "WEB" | "DESKTOP" | "BOTH";
|
||||
}
|
||||
|
||||
interface IsDisabled<D = unknown> {
|
||||
interface IsDisabledOrHidden<D extends DefinedSettings = DefinedSettings> {
|
||||
/**
|
||||
* Checks if this setting should be disabled
|
||||
* Whether this setting should be disabled
|
||||
*/
|
||||
disabled?(this: D): boolean;
|
||||
disabled?: boolean | ((this: D) => boolean);
|
||||
/**
|
||||
* Whether this setting should be hidden from the user
|
||||
*/
|
||||
hidden?: boolean | ((this: D) => boolean);
|
||||
}
|
||||
|
||||
interface IsValid<T, D = unknown> {
|
||||
interface IsValid<T, D extends DefinedSettings = DefinedSettings> {
|
||||
/**
|
||||
* Prevents the user from saving settings if this is false or a string
|
||||
* Whether the value the user provided is valid. Either return a boolean for a generic error message or a string for a custom error message.
|
||||
*/
|
||||
isValid?(this: D, value: T): boolean | string;
|
||||
}
|
||||
|
||||
export interface PluginSettingStringDef {
|
||||
export interface PluginSettingStringDef extends PluginSettingDefCommon {
|
||||
type: OptionType.STRING;
|
||||
default?: string;
|
||||
/** Whether to use a multiline text area */
|
||||
multiline?: boolean;
|
||||
}
|
||||
export interface PluginSettingNumberDef {
|
||||
export interface PluginSettingNumberDef extends PluginSettingDefCommon {
|
||||
type: OptionType.NUMBER;
|
||||
default?: number;
|
||||
}
|
||||
export interface PluginSettingBigIntDef {
|
||||
export interface PluginSettingBigIntDef extends PluginSettingDefCommon {
|
||||
type: OptionType.BIGINT;
|
||||
default?: BigInt;
|
||||
}
|
||||
export interface PluginSettingBooleanDef {
|
||||
export interface PluginSettingBooleanDef extends PluginSettingDefCommon {
|
||||
type: OptionType.BOOLEAN;
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
export interface PluginSettingSelectDef {
|
||||
export interface PluginSettingSelectDef extends PluginSettingDefCommon {
|
||||
type: OptionType.SELECT;
|
||||
options: readonly PluginSettingSelectOption[];
|
||||
}
|
||||
|
|
@ -358,12 +351,13 @@ export interface PluginSettingSelectOption {
|
|||
default?: boolean;
|
||||
}
|
||||
|
||||
export interface PluginSettingCustomDef {
|
||||
export interface PluginSettingCustomDef extends PluginSettingDefCommon {
|
||||
type: OptionType.CUSTOM;
|
||||
default?: any;
|
||||
onChange?: PluginSettingDefCommon["onChange"];
|
||||
}
|
||||
|
||||
export interface PluginSettingSliderDef {
|
||||
export interface PluginSettingSliderDef extends PluginSettingDefCommon {
|
||||
type: OptionType.SLIDER;
|
||||
/**
|
||||
* All the possible values in the slider. Needs at least two values.
|
||||
|
|
@ -379,11 +373,14 @@ export interface PluginSettingSliderDef {
|
|||
stickToMarkers?: boolean;
|
||||
}
|
||||
|
||||
export interface IPluginOptionComponentProps {
|
||||
export interface PluginSettingComponentDef extends Omit<PluginSettingDefCommon, "description" | "placeholder"> {
|
||||
type: OptionType.COMPONENT;
|
||||
component: (props: PluginSettingComponentProps) => ReactNode | Promise<ReactNode>;
|
||||
default?: any;
|
||||
}
|
||||
export interface PluginSettingComponentProps {
|
||||
/**
|
||||
* Run this when the value changes.
|
||||
*
|
||||
* NOTE: The user will still need to click save to apply these changes.
|
||||
*/
|
||||
setValue(newValue: any): void;
|
||||
/**
|
||||
|
|
@ -392,12 +389,6 @@ export interface IPluginOptionComponentProps {
|
|||
option: PluginSettingComponentDef;
|
||||
}
|
||||
|
||||
export interface PluginSettingComponentDef {
|
||||
type: OptionType.COMPONENT;
|
||||
component: (props: IPluginOptionComponentProps) => ReactNode | Promise<ReactNode>;
|
||||
default?: any;
|
||||
}
|
||||
|
||||
/** Maps a `PluginSettingDef` to its value type */
|
||||
type PluginSettingType<O extends PluginSettingDef> = O extends PluginSettingStringDef ? string :
|
||||
O extends PluginSettingNumberDef ? number :
|
||||
|
|
@ -420,56 +411,32 @@ type SettingsStore<D extends SettingsDefinition> = {
|
|||
/** An instance of defined plugin settings */
|
||||
export interface DefinedSettings<
|
||||
Def extends SettingsDefinition = SettingsDefinition,
|
||||
Checks extends SettingsChecks<Def> = {},
|
||||
PrivateSettings extends object = {}
|
||||
> {
|
||||
/** Shorthand for `Vencord.Settings.plugins.PluginName`, but with typings */
|
||||
/** Definitions of each setting */
|
||||
def: Def;
|
||||
/** Reactive Read/Write settings store - This is a Proxy, use .plain for a plain object */
|
||||
store: SettingsStore<Def> & PrivateSettings;
|
||||
/** Shorthand for `Vencord.PlainSettings.plugins.PluginName`, but with typings */
|
||||
/** Pure Read-only settings object */
|
||||
plain: SettingsStore<Def> & PrivateSettings;
|
||||
/**
|
||||
* React hook for getting the settings for this plugin
|
||||
* @param filter optional filter to avoid rerenders for irrelevent settings
|
||||
* @param filter optional filter to avoid rerenders for irrelevant settings
|
||||
*/
|
||||
use<F extends Extract<keyof Def | keyof PrivateSettings, string>>(filter?: F[]): Pick<SettingsStore<Def> & PrivateSettings, F>;
|
||||
/** Definitions of each setting */
|
||||
def: Def;
|
||||
/** Setting methods with return values that could rely on other settings */
|
||||
checks: Checks;
|
||||
/**
|
||||
* Name of the plugin these settings belong to,
|
||||
* will be an empty string until plugin is initialized
|
||||
*/
|
||||
pluginName: string;
|
||||
|
||||
withPrivateSettings<T extends object>(): DefinedSettings<Def, Checks, T>;
|
||||
/** Extend this Settings object type with more properties, useful for non-user-facing settings */
|
||||
withPrivateSettings<T extends object>(): DefinedSettings<Def, T>;
|
||||
}
|
||||
|
||||
export type PartialExcept<T, R extends keyof T> = Partial<T> & Required<Pick<T, R>>;
|
||||
|
||||
export type IpcRes<V = any> = { ok: true; value: V; } | { ok: false, error: any; };
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Legacy Options Types */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
export type PluginOptionBase = PluginSettingCommon & IsDisabled;
|
||||
export type PluginOptionsItem =
|
||||
| PluginOptionString
|
||||
| PluginOptionNumber
|
||||
| PluginOptionBoolean
|
||||
| PluginOptionSelect
|
||||
| PluginOptionSlider
|
||||
| PluginOptionComponent
|
||||
| PluginOptionCustom;
|
||||
export type PluginOptionString = PluginSettingStringDef & PluginSettingCommon & IsDisabled & IsValid<string>;
|
||||
export type PluginOptionNumber = (PluginSettingNumberDef | PluginSettingBigIntDef) & PluginSettingCommon & IsDisabled & IsValid<number | BigInt>;
|
||||
export type PluginOptionBoolean = PluginSettingBooleanDef & PluginSettingCommon & IsDisabled & IsValid<boolean>;
|
||||
export type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid<PluginSettingSelectOption>;
|
||||
export type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid<number>;
|
||||
export type PluginOptionComponent = PluginSettingComponentDef & Omit<PluginSettingCommon, "description" | "placeholder">;
|
||||
export type PluginOptionCustom = PluginSettingCustomDef & Pick<PluginSettingCommon, "onChange">;
|
||||
|
||||
export type PluginNative<PluginExports extends Record<string, (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any>> = {
|
||||
[key in keyof PluginExports]:
|
||||
PluginExports[key] extends (event: Electron.IpcMainInvokeEvent, ...args: infer Args) => infer Return
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue