Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
verticalsync committed May 12, 2024
2 parents 9a32f88 + 463952e commit 3ec2df0
Show file tree
Hide file tree
Showing 24 changed files with 382 additions and 129 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "suncord",
"private": "true",
"version": "1.8.9",
"version": "1.9.0",
"description": "A fork of the Discord client mod Vencord.",
"homepage": "https://github.com/verticalsync/Suncord#readme",
"bugs": {
Expand Down
2 changes: 1 addition & 1 deletion src/api/Commands/commandHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import { mergeDefaults } from "@utils/misc";
import { mergeDefaults } from "@utils/mergeDefaults";
import { findByPropsLazy } from "@webpack";
import { MessageActions, SnowflakeUtils } from "@webpack/common";
import { Message } from "discord-types/general";
Expand Down
2 changes: 1 addition & 1 deletion src/api/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { debounce } from "@shared/debounce";
import { SettingsStore as SettingsStoreClass } from "@shared/SettingsStore";
import { localStorage } from "@utils/localStorage";
import { Logger } from "@utils/Logger";
import { mergeDefaults } from "@utils/misc";
import { mergeDefaults } from "@utils/mergeDefaults";
import { putCloudSettings } from "@utils/settingsSync";
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
import { React } from "@webpack/common";
Expand Down
20 changes: 18 additions & 2 deletions src/components/VencordSettings/AddonCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import "./addonCard.css";
import { classNameFactory } from "@api/Styles";
import { Badge } from "@components/Badge";
import { Switch } from "@components/Switch";
import { Text } from "@webpack/common";
import { Text, useRef } from "@webpack/common";
import type { MouseEventHandler, ReactNode } from "react";

const cl = classNameFactory("vc-addon-");
Expand All @@ -42,6 +42,8 @@ interface Props {
}

export function AddonCard({ disabled, isNew, name, infoButton, footer, author, enabled, setEnabled, description, onMouseEnter, onMouseLeave }: Props) {
const titleRef = useRef<HTMLDivElement>(null);
const titleContainerRef = useRef<HTMLDivElement>(null);
return (
<div
className={cl("card", { "card-disabled": disabled })}
Expand All @@ -51,7 +53,21 @@ export function AddonCard({ disabled, isNew, name, infoButton, footer, author, e
<div className={cl("header")}>
<div className={cl("name-author")}>
<Text variant="text-md/bold" className={cl("name")}>
{name}{isNew && <Badge text="NEW" color="#ED4245" />}
<div ref={titleContainerRef} className={cl("title-container")}>
<div
ref={titleRef}
className={cl("title")}
onMouseOver={() => {
const title = titleRef.current!;
const titleContainer = titleContainerRef.current!;

title.style.setProperty("--offset", `${titleContainer.clientWidth - title.scrollWidth}px`);
title.style.setProperty("--duration", `${Math.max(0.5, (title.scrollWidth - titleContainer.clientWidth) / 7)}s`);
}}
>
{name}
</div>
</div>{isNew && <Badge text="NEW" color="#ED4245" />}
</Text>
{!!author && (
<Text variant="text-md/normal" className={cl("author")}>
Expand Down
33 changes: 33 additions & 0 deletions src/components/VencordSettings/addonCard.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,36 @@
.vc-addon-author::before {
content: "by ";
}

.vc-addon-title-container {
width: 100%;
overflow: hidden;
height: 1.25em;
position: relative;
}

.vc-addon-title {
position: absolute;
inset: 0;
overflow: hidden;
text-overflow: ellipsis;
}

@keyframes vc-addon-title {
0% {
transform: translateX(0);
}

50% {
transform: translateX(var(--offset));
}

100% {
transform: translateX(0);
}
}

.vc-addon-title:hover {
overflow: visible;
animation: vc-addon-title var(--duration) linear infinite;
}
18 changes: 17 additions & 1 deletion src/main/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import type { Settings } from "@api/Settings";
import { IpcEvents } from "@shared/IpcEvents";
import { SettingsStore } from "@shared/SettingsStore";
import { mergeDefaults } from "@utils/mergeDefaults";
import { ipcMain } from "electron";
import { mkdirSync, readFileSync, writeFileSync } from "fs";

Expand Down Expand Up @@ -42,7 +43,22 @@ ipcMain.handle(IpcEvents.SET_SETTINGS, (_, data: Settings, pathToNotify?: string
RendererSettings.setData(data, pathToNotify);
});

export const NativeSettings = new SettingsStore(readSettings("native", NATIVE_SETTINGS_FILE));
export interface NativeSettings {
plugins: {
[plugin: string]: {
[setting: string]: any;
};
};
}

const DefaultNativeSettings: NativeSettings = {
plugins: {}
};

const nativeSettings = readSettings<NativeSettings>("native", NATIVE_SETTINGS_FILE);
mergeDefaults(nativeSettings, DefaultNativeSettings);

export const NativeSettings = new SettingsStore(nativeSettings);

NativeSettings.addGlobalChangeListener(() => {
try {
Expand Down
30 changes: 27 additions & 3 deletions src/plugins/customRPC/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
*/

import { definePluginSettings, Settings } from "@api/Settings";
import { ErrorCard } from "@components/ErrorCard";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import { isTruthy } from "@utils/guards";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, StatusSettingsStores, UserStore } from "@webpack/common";

const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color");
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
Expand Down Expand Up @@ -386,17 +389,36 @@ async function setRpc(disable?: boolean) {
export default definePlugin({
name: "CustomRPC",
description: "Allows you to set a custom rich presence.",
authors: [Devs.captain, Devs.AutumnVN],
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
start: setRpc,
stop: () => setRpc(true),
settings,

settingsAboutComponent: () => {
const activity = useAwaiter(createActivity);
const gameActivityEnabled = StatusSettingsStores.ShowCurrentGame.useSetting();
const { profileThemeStyle } = useProfileThemeStyle({});

return (
<>
{!gameActivityEnabled && (
<ErrorCard
className={classes(Margins.top16, Margins.bottom16)}
style={{ padding: "1em" }}
>
<Forms.FormTitle>Notice</Forms.FormTitle>
<Forms.FormText>Game activity isn't enabled, people won't be able to see your custom rich presence!</Forms.FormText>

<Button
color={Button.Colors.TRANSPARENT}
className={Margins.top8}
onClick={() => StatusSettingsStores.ShowCurrentGame.updateSetting(true)}
>
Enable
</Button>
</ErrorCard>
)}

<Forms.FormText>
Go to <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
get the application ID.
Expand All @@ -407,7 +429,9 @@ export default definePlugin({
<Forms.FormText>
If you want to use image link, download your image and reupload the image to <Link href="https://imgur.com">Imgur</Link> and get the image link by right-clicking the image and select "Copy image address".
</Forms.FormText>
<Forms.FormDivider />

<Forms.FormDivider className={Margins.top8} />

<div style={{ width: "284px", ...profileThemeStyle }}>
{activity[0] && <ActivityComponent activity={activity[0]} className={ActivityClassName.activity} channelId={SelectedChannelStore.getChannelId()}
guild={GuildStore.getGuild(SelectedGuildStore.getLastSelectedGuildId())}
Expand Down
38 changes: 34 additions & 4 deletions src/plugins/dearrow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

import "./styles.css";

import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin from "@utils/types";
import definePlugin, { OptionType } from "@utils/types";
import { Tooltip } from "@webpack/common";
import type { Component } from "react";

Expand All @@ -34,11 +35,19 @@ interface Props {
};
}

const enum ReplaceElements {
ReplaceAllElements,
ReplaceTitlesOnly,
ReplaceThumbnailsOnly
}

const embedUrlRe = /https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/;

async function embedDidMount(this: Component<Props>) {
try {
const { embed } = this.props;
const { replaceElements } = settings.store;

if (!embed || embed.dearrow || embed.provider?.name !== "YouTube" || !embed.video?.url) return;

const videoId = embedUrlRe.exec(embed.video.url)?.[1];
Expand All @@ -58,12 +67,12 @@ async function embedDidMount(this: Component<Props>) {
enabled: true
};

if (hasTitle) {
if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) {
embed.dearrow.oldTitle = embed.rawTitle;
embed.rawTitle = titles[0].title.replace(/ >(\S)/g, " $1");
}

if (hasThumb) {
if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) {
embed.dearrow.oldThumb = embed.thumbnail.proxyURL;
embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
}
Expand Down Expand Up @@ -128,10 +137,30 @@ function DearrowButton({ component }: { component: Component<Props>; }) {
);
}

const settings = definePluginSettings({
hideButton: {
description: "Hides the Dearrow button from YouTube embeds",
type: OptionType.BOOLEAN,
default: false,
restartNeeded: true
},
replaceElements: {
description: "Choose which elements of the embed will be replaced",
type: OptionType.SELECT,
restartNeeded: true,
options: [
{ label: "Everything (Titles & Thumbnails)", value: ReplaceElements.ReplaceAllElements, default: true },
{ label: "Titles", value: ReplaceElements.ReplaceTitlesOnly },
{ label: "Thumbnails", value: ReplaceElements.ReplaceThumbnailsOnly },
],
}
});

export default definePlugin({
name: "Dearrow",
description: "Makes YouTube embed titles and thumbnails less sensationalist, powered by Dearrow",
authors: [Devs.Ven],
settings,

embedDidMount,
renderButton(component: Component<Props>) {
Expand All @@ -154,7 +183,8 @@ export default definePlugin({
// add dearrow button
{
match: /children:\[(?=null!=\i\?\i\.renderSuppressButton)/,
replace: "children:[$self.renderButton(this),"
replace: "children:[$self.renderButton(this),",
predicate: () => !settings.store.hideButton
}
]
}],
Expand Down
6 changes: 3 additions & 3 deletions src/plugins/fakeNitro/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const hyperLinkRegex = /\[.+?\]\((https?:\/\/.+?)\)/;

const settings = definePluginSettings({
enableEmojiBypass: {
description: "Allow sending fake emojis",
description: "Allows sending fake emojis (also bypasses missing permission to use custom emojis)",
type: OptionType.BOOLEAN,
default: true,
restartNeeded: true
Expand All @@ -128,7 +128,7 @@ const settings = definePluginSettings({
restartNeeded: true
},
enableStickerBypass: {
description: "Allow sending fake stickers",
description: "Allows sending fake stickers (also bypasses missing permission to use stickers)",
type: OptionType.BOOLEAN,
default: true,
restartNeeded: true
Expand Down Expand Up @@ -189,7 +189,7 @@ const hasAttachmentPerms = (channelId: string) => hasPermission(channelId, Permi
export default definePlugin({
name: "FakeNitro",
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN],
description: "Allows you to stream in nitro quality, send fake emojis/stickers and use client themes.",
description: "Allows you to stream in nitro quality, send fake emojis/stickers, use client themes and custom Discord notifications.",
dependencies: ["MessageEventsAPI"],

settings,
Expand Down
35 changes: 28 additions & 7 deletions src/plugins/messageLatency/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { findExportedComponentLazy } from "@webpack";
import { SnowflakeUtils, Tooltip } from "@webpack/common";
import { Message } from "discord-types/general";

type FillValue = ("status-danger" | "status-warning" | "text-muted");
type FillValue = ("status-danger" | "status-warning" | "status-positive" | "text-muted");
type Fill = [FillValue, FillValue, FillValue];
type DiffKey = keyof Diff;

Expand Down Expand Up @@ -54,10 +54,24 @@ export default definePlugin({
seconds: Math.round(delta % 60),
};

const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${k}` : null;
const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${diff[k] > 1 ? k : k.substring(0, k.length - 1)}` : null;
const keys = Object.keys(diff) as DiffKey[];

return keys.map(str).filter(isNonNullish).join(" ") || "0 seconds";
const ts = keys.reduce((prev, k) => {
const s = str(k);

return prev + (
isNonNullish(s)
? (prev !== ""
? k === "seconds"
? " and "
: " "
: "") + s
: ""
);
}, "");

return [ts || "0 seconds", diff.days === 17 && diff.hours === 1] as const;
},
latencyTooltipData(message: Message) {
const { id, nonce } = message;
Expand All @@ -73,16 +87,23 @@ export default definePlugin({
const abs = Math.abs(delta);
const ahead = abs !== delta;

const stringDelta = this.stringDelta(abs);
const [stringDelta, isSuspectedKotlinDiscord] = this.stringDelta(abs);
const isKotlinDiscord = ahead && isSuspectedKotlinDiscord;

// Also thanks dziurwa
// 2 minutes
const TROLL_LIMIT = 2 * 60;
const { latency } = this.settings.store;

const fill: Fill = delta >= TROLL_LIMIT || ahead ? ["text-muted", "text-muted", "text-muted"] : delta >= (latency * 2) ? ["status-danger", "text-muted", "text-muted"] : ["status-warning", "status-warning", "text-muted"];
const fill: Fill = isKotlinDiscord
? ["status-positive", "status-positive", "text-muted"]
: delta >= TROLL_LIMIT || ahead
? ["text-muted", "text-muted", "text-muted"]
: delta >= (latency * 2)
? ["status-danger", "text-muted", "text-muted"]
: ["status-warning", "status-warning", "text-muted"];

return abs >= latency ? { delta: stringDelta, ahead: abs !== delta, fill } : null;
return abs >= latency ? { delta: stringDelta, ahead, fill, isKotlinDiscord } : null;
},
Tooltip() {
return ErrorBoundary.wrap(({ message }: { message: Message; }) => {
Expand All @@ -92,7 +113,7 @@ export default definePlugin({
if (!isNonNullish(d)) return null;

return <Tooltip
text={d.ahead ? `This user's clock is ${d.delta} ahead` : `This message was sent with a delay of ${d.delta}.`}
text={d.ahead ? `This user's clock is ${d.delta} ahead. ${d.isKotlinDiscord ? "User is suspected to be on an old mobile client" : ""}` : `This message was sent with a delay of ${d.delta}.`}
position="top"
>
{
Expand Down
Loading

0 comments on commit 3ec2df0

Please sign in to comment.