Merge branch 'dev' into immediate-finds
This commit is contained in:
commit
258e1efdce
|
@ -17,13 +17,16 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings, Settings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
|
import { ErrorCard } from "@components/ErrorCard";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { isTruthy } from "@utils/guards";
|
import { isTruthy } from "@utils/guards";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
|
import { classes } from "@utils/misc";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByCode, findByProps, findComponentByCode } from "@webpack";
|
import { findByCode, findByProps, findComponentByCode } 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 = findByCode("profileThemeStyle:", "--profile-gradient-primary-color");
|
const useProfileThemeStyle = findByCode("profileThemeStyle:", "--profile-gradient-primary-color");
|
||||||
const ActivityComponent = findComponentByCode("onOpenGameProfile");
|
const ActivityComponent = findComponentByCode("onOpenGameProfile");
|
||||||
|
@ -386,17 +389,36 @@ async function setRpc(disable?: boolean) {
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "CustomRPC",
|
name: "CustomRPC",
|
||||||
description: "Allows you to set a custom rich presence.",
|
description: "Allows you to set a custom rich presence.",
|
||||||
authors: [Devs.captain, Devs.AutumnVN],
|
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
|
||||||
start: setRpc,
|
start: setRpc,
|
||||||
stop: () => setRpc(true),
|
stop: () => setRpc(true),
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
settingsAboutComponent: () => {
|
settingsAboutComponent: () => {
|
||||||
const activity = useAwaiter(createActivity);
|
const activity = useAwaiter(createActivity);
|
||||||
|
const gameActivityEnabled = StatusSettingsStores.ShowCurrentGame.useSetting();
|
||||||
const { profileThemeStyle } = useProfileThemeStyle({});
|
const { profileThemeStyle } = useProfileThemeStyle({});
|
||||||
|
|
||||||
return (
|
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>
|
<Forms.FormText>
|
||||||
Go to <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
|
Go to <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
|
||||||
get the application ID.
|
get the application ID.
|
||||||
|
@ -407,7 +429,9 @@ export default definePlugin({
|
||||||
<Forms.FormText>
|
<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".
|
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.FormText>
|
||||||
<Forms.FormDivider />
|
|
||||||
|
<Forms.FormDivider className={Margins.top8} />
|
||||||
|
|
||||||
<div style={{ width: "284px", ...profileThemeStyle }}>
|
<div style={{ width: "284px", ...profileThemeStyle }}>
|
||||||
{activity[0] && <ActivityComponent activity={activity[0]} className={ActivityClassName.activity} channelId={SelectedChannelStore.getChannelId()}
|
{activity[0] && <ActivityComponent activity={activity[0]} className={ActivityClassName.activity} channelId={SelectedChannelStore.getChannelId()}
|
||||||
guild={GuildStore.getGuild(SelectedGuildStore.getLastSelectedGuildId())}
|
guild={GuildStore.getGuild(SelectedGuildStore.getLastSelectedGuildId())}
|
||||||
|
|
|
@ -189,7 +189,7 @@ const hasAttachmentPerms = (channelId: string) => hasPermission(channelId, Permi
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "FakeNitro",
|
name: "FakeNitro",
|
||||||
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN],
|
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"],
|
dependencies: ["MessageEventsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { findExportedComponent } from "@webpack";
|
||||||
import { SnowflakeUtils, Tooltip } from "@webpack/common";
|
import { SnowflakeUtils, Tooltip } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
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 Fill = [FillValue, FillValue, FillValue];
|
||||||
type DiffKey = keyof Diff;
|
type DiffKey = keyof Diff;
|
||||||
|
|
||||||
|
@ -54,10 +54,24 @@ export default definePlugin({
|
||||||
seconds: Math.round(delta % 60),
|
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[];
|
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) {
|
latencyTooltipData(message: Message) {
|
||||||
const { id, nonce } = message;
|
const { id, nonce } = message;
|
||||||
|
@ -73,16 +87,23 @@ export default definePlugin({
|
||||||
const abs = Math.abs(delta);
|
const abs = Math.abs(delta);
|
||||||
const ahead = abs !== delta;
|
const ahead = abs !== delta;
|
||||||
|
|
||||||
const stringDelta = this.stringDelta(abs);
|
const [stringDelta, isSuspectedKotlinDiscord] = this.stringDelta(abs);
|
||||||
|
const isKotlinDiscord = ahead && isSuspectedKotlinDiscord;
|
||||||
|
|
||||||
// Also thanks dziurwa
|
// Also thanks dziurwa
|
||||||
// 2 minutes
|
// 2 minutes
|
||||||
const TROLL_LIMIT = 2 * 60;
|
const TROLL_LIMIT = 2 * 60;
|
||||||
const { latency } = this.settings.store;
|
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() {
|
Tooltip() {
|
||||||
return ErrorBoundary.wrap(({ message }: { message: Message; }) => {
|
return ErrorBoundary.wrap(({ message }: { message: Message; }) => {
|
||||||
|
@ -92,7 +113,7 @@ export default definePlugin({
|
||||||
if (!isNonNullish(d)) return null;
|
if (!isNonNullish(d)) return null;
|
||||||
|
|
||||||
return <Tooltip
|
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"
|
position="top"
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { Flex } from "@components/Flex";
|
||||||
import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
|
import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
|
||||||
import { getUniqueUsername } from "@utils/discord";
|
import { getUniqueUsername } from "@utils/discord";
|
||||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
|
import { Clipboard, ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, i18n, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
|
||||||
import type { Guild } from "discord-types/general";
|
import type { Guild } from "discord-types/general";
|
||||||
|
|
||||||
import { settings } from "..";
|
import { settings } from "..";
|
||||||
|
@ -112,7 +112,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
|
||||||
<div
|
<div
|
||||||
className={cl("perms-list-item", { "perms-list-item-active": selectedItemIndex === index })}
|
className={cl("perms-list-item", { "perms-list-item-active": selectedItemIndex === index })}
|
||||||
onContextMenu={e => {
|
onContextMenu={e => {
|
||||||
if ((settings.store as any).unsafeViewAsRole && permission.type === PermissionType.Role)
|
if (permission.type === PermissionType.Role)
|
||||||
ContextMenuApi.openContextMenu(e, () => (
|
ContextMenuApi.openContextMenu(e, () => (
|
||||||
<RoleContextMenu
|
<RoleContextMenu
|
||||||
guild={guild}
|
guild={guild}
|
||||||
|
@ -120,6 +120,14 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
|
||||||
onClose={modalProps.onClose}
|
onClose={modalProps.onClose}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
else if (permission.type === PermissionType.User) {
|
||||||
|
ContextMenuApi.openContextMenu(e, () => (
|
||||||
|
<UserContextMenu
|
||||||
|
userId={permission.id!}
|
||||||
|
onClose={modalProps.onClose}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(permission.type === PermissionType.Role || permission.type === PermissionType.Owner) && (
|
{(permission.type === PermissionType.Role || permission.type === PermissionType.Owner) && (
|
||||||
|
@ -200,24 +208,53 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str
|
||||||
aria-label="Role Options"
|
aria-label="Role Options"
|
||||||
>
|
>
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="vc-pw-view-as-role"
|
id="vc-copy-role-id"
|
||||||
label="View As Role"
|
label={i18n.Messages.COPY_ID_ROLE}
|
||||||
action={() => {
|
action={() => {
|
||||||
const role = GuildStore.getRole(guild.id, roleId);
|
Clipboard.copy(roleId);
|
||||||
if (!role) return;
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
onClose();
|
{(settings.store as any).unsafeViewAsRole && (
|
||||||
|
<Menu.MenuItem
|
||||||
|
id="vc-pw-view-as-role"
|
||||||
|
label={i18n.Messages.VIEW_AS_ROLE}
|
||||||
|
action={() => {
|
||||||
|
const role = GuildStore.getRole(guild.id, roleId);
|
||||||
|
if (!role) return;
|
||||||
|
|
||||||
FluxDispatcher.dispatch({
|
onClose();
|
||||||
type: "IMPERSONATE_UPDATE",
|
|
||||||
guildId: guild.id,
|
FluxDispatcher.dispatch({
|
||||||
data: {
|
type: "IMPERSONATE_UPDATE",
|
||||||
type: "ROLES",
|
guildId: guild.id,
|
||||||
roles: {
|
data: {
|
||||||
[roleId]: role
|
type: "ROLES",
|
||||||
|
roles: {
|
||||||
|
[roleId]: role
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Menu.Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserContextMenu({ userId, onClose }: { userId: string; onClose: () => void; }) {
|
||||||
|
return (
|
||||||
|
<Menu.Menu
|
||||||
|
navId={cl("user-context-menu")}
|
||||||
|
onClose={ContextMenuApi.closeContextMenu}
|
||||||
|
aria-label="User Options"
|
||||||
|
>
|
||||||
|
<Menu.MenuItem
|
||||||
|
id="vc-copy-user-id"
|
||||||
|
label={i18n.Messages.COPY_ID_USER}
|
||||||
|
action={() => {
|
||||||
|
Clipboard.copy(userId);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Menu.Menu>
|
</Menu.Menu>
|
||||||
|
|
|
@ -18,10 +18,11 @@
|
||||||
|
|
||||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
||||||
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { FluxDispatcher, React } from "@webpack/common";
|
import { FluxDispatcher, Menu, React } from "@webpack/common";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
showIcon: {
|
showIcon: {
|
||||||
|
@ -30,6 +31,11 @@ const settings = definePluginSettings({
|
||||||
description: "Show an icon for toggling the plugin",
|
description: "Show an icon for toggling the plugin",
|
||||||
restartNeeded: true,
|
restartNeeded: true,
|
||||||
},
|
},
|
||||||
|
contextMenu: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Add option to toggle the functionality in the chat input context menu",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
isEnabled: {
|
isEnabled: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Toggle functionality",
|
description: "Toggle functionality",
|
||||||
|
@ -56,13 +62,37 @@ const SilentTypingToggle: ChatBarButton = ({ isMainChat }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const ChatBarContextCheckbox: NavContextMenuPatchCallback = children => {
|
||||||
|
const { isEnabled, contextMenu } = settings.use(["isEnabled", "contextMenu"]);
|
||||||
|
if (!contextMenu) return;
|
||||||
|
|
||||||
|
const group = findGroupChildrenByChildId("submit-button", children);
|
||||||
|
|
||||||
|
if (!group) return;
|
||||||
|
|
||||||
|
const idx = group.findIndex(c => c?.props?.id === "submit-button");
|
||||||
|
|
||||||
|
group.splice(idx + 1, 0,
|
||||||
|
<Menu.MenuCheckboxItem
|
||||||
|
id="vc-silent-typing"
|
||||||
|
label="Enable Silent Typing"
|
||||||
|
checked={isEnabled}
|
||||||
|
action={() => settings.store.isEnabled = !settings.store.isEnabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "SilentTyping",
|
name: "SilentTyping",
|
||||||
authors: [Devs.Ven, Devs.Rini],
|
authors: [Devs.Ven, Devs.Rini, Devs.ImBanana],
|
||||||
description: "Hide that you are typing",
|
description: "Hide that you are typing",
|
||||||
dependencies: ["CommandsAPI", "ChatInputButtonAPI"],
|
dependencies: ["CommandsAPI", "ChatInputButtonAPI"],
|
||||||
settings,
|
settings,
|
||||||
|
contextMenus: {
|
||||||
|
"textarea-context": ChatBarContextCheckbox
|
||||||
|
},
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '.dispatch({type:"TYPING_START_LOCAL"',
|
find: '.dispatch({type:"TYPING_START_LOCAL"',
|
||||||
|
|
|
@ -15,6 +15,7 @@ This allows themes to more easily theme those elements or even do things that ot
|
||||||
### Chat Messages
|
### Chat Messages
|
||||||
|
|
||||||
- `data-author-id` contains the id of the author
|
- `data-author-id` contains the id of the author
|
||||||
|
- `data-author-username` contains the username of the author
|
||||||
- `data-is-self` is a boolean indicating whether this is the current user's message
|
- `data-is-self` is a boolean indicating whether this is the current user's message
|
||||||
|
|
||||||
![image](https://github.com/Vendicated/Vencord/assets/45497981/34bd5053-3381-402f-82b2-9c812cc7e122)
|
![image](https://github.com/Vendicated/Vencord/assets/45497981/34bd5053-3381-402f-82b2-9c812cc7e122)
|
||||||
|
|
|
@ -36,10 +36,12 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
|
|
||||||
getMessageProps(props: { message: Message; }) {
|
getMessageProps(props: { message: Message; }) {
|
||||||
const authorId = props.message?.author?.id;
|
const author = props.message?.author;
|
||||||
|
const authorId = author?.id;
|
||||||
return {
|
return {
|
||||||
"data-author-id": authorId,
|
"data-author-id": authorId,
|
||||||
"data-is-self": authorId && authorId === UserStore.getCurrentUser()?.id
|
"data-author-username": author?.username,
|
||||||
|
"data-is-self": authorId && authorId === UserStore.getCurrentUser()?.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Vencord, a Discord client mod
|
* Vencord, a Discord client mod
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -13,10 +13,7 @@ import { findByProps } from "@webpack";
|
||||||
import { ChannelStore, GuildStore, UserStore } from "@webpack/common";
|
import { ChannelStore, GuildStore, UserStore } from "@webpack/common";
|
||||||
import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general";
|
import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general";
|
||||||
|
|
||||||
const enum ChannelTypes {
|
const { ChannelTypes } = findByProps("ChannelTypes");
|
||||||
DM = 1,
|
|
||||||
GROUP_DM = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
guild_id: string,
|
guild_id: string,
|
||||||
|
@ -72,14 +69,35 @@ interface Call {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MuteStore = findByProps("isSuppressEveryoneEnabled");
|
const MuteStore = findByProps("isSuppressEveryoneEnabled");
|
||||||
|
const Notifs = findByProps("makeTextChatNotification");
|
||||||
const XSLog = new Logger("XSOverlay");
|
const XSLog = new Logger("XSOverlay");
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
ignoreBots: {
|
botNotifications: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Ignore messages from bots",
|
description: "Allow bot notifications",
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
serverNotifications: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Allow server notifications",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
dmNotifications: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Allow Direct Message notifications",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
groupDmNotifications: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Allow Group DM notifications",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
callNotifications: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Allow call notifications",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
pingColor: {
|
pingColor: {
|
||||||
type: OptionType.STRING,
|
type: OptionType.STRING,
|
||||||
description: "User mention color",
|
description: "User mention color",
|
||||||
|
@ -100,6 +118,11 @@ const settings = definePluginSettings({
|
||||||
description: "Notif duration (secs)",
|
description: "Notif duration (secs)",
|
||||||
default: 1.0,
|
default: 1.0,
|
||||||
},
|
},
|
||||||
|
timeoutPerCharacter: {
|
||||||
|
type: OptionType.NUMBER,
|
||||||
|
description: "Duration multiplier per character",
|
||||||
|
default: 0.5
|
||||||
|
},
|
||||||
opacity: {
|
opacity: {
|
||||||
type: OptionType.SLIDER,
|
type: OptionType.SLIDER,
|
||||||
description: "Notif opacity",
|
description: "Notif opacity",
|
||||||
|
@ -124,7 +147,7 @@ export default definePlugin({
|
||||||
settings,
|
settings,
|
||||||
flux: {
|
flux: {
|
||||||
CALL_UPDATE({ call }: { call: Call; }) {
|
CALL_UPDATE({ call }: { call: Call; }) {
|
||||||
if (call?.ringing?.includes(UserStore.getCurrentUser().id)) {
|
if (call?.ringing?.includes(UserStore.getCurrentUser().id) && settings.store.callNotifications) {
|
||||||
const channel = ChannelStore.getChannel(call.channel_id);
|
const channel = ChannelStore.getChannel(call.channel_id);
|
||||||
sendOtherNotif("Incoming call", `${channel.name} is calling you...`);
|
sendOtherNotif("Incoming call", `${channel.name} is calling you...`);
|
||||||
}
|
}
|
||||||
|
@ -134,7 +157,7 @@ export default definePlugin({
|
||||||
try {
|
try {
|
||||||
if (optimistic) return;
|
if (optimistic) return;
|
||||||
const channel = ChannelStore.getChannel(message.channel_id);
|
const channel = ChannelStore.getChannel(message.channel_id);
|
||||||
if (!shouldNotify(message, channel)) return;
|
if (!shouldNotify(message, message.channel_id)) return;
|
||||||
|
|
||||||
const pingColor = settings.store.pingColor.replaceAll("#", "").trim();
|
const pingColor = settings.store.pingColor.replaceAll("#", "").trim();
|
||||||
const channelPingColor = settings.store.channelPingColor.replaceAll("#", "").trim();
|
const channelPingColor = settings.store.channelPingColor.replaceAll("#", "").trim();
|
||||||
|
@ -194,6 +217,7 @@ export default definePlugin({
|
||||||
finalMsg = finalMsg.replace(/<@!?(\d{17,20})>/g, (_, id) => `<color=#${pingColor}><b>@${UserStore.getUser(id)?.username || "unknown-user"}</color></b>`);
|
finalMsg = finalMsg.replace(/<@!?(\d{17,20})>/g, (_, id) => `<color=#${pingColor}><b>@${UserStore.getUser(id)?.username || "unknown-user"}</color></b>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// color role mentions (unity styling btw lol)
|
||||||
if (message.mention_roles.length > 0) {
|
if (message.mention_roles.length > 0) {
|
||||||
for (const roleId of message.mention_roles) {
|
for (const roleId of message.mention_roles) {
|
||||||
const role = GuildStore.getRole(channel.guild_id, roleId);
|
const role = GuildStore.getRole(channel.guild_id, roleId);
|
||||||
|
@ -213,6 +237,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// color channel mentions
|
||||||
if (channelMatches) {
|
if (channelMatches) {
|
||||||
for (const cMatch of channelMatches) {
|
for (const cMatch of channelMatches) {
|
||||||
let channelId = cMatch.split("<#")[1];
|
let channelId = cMatch.split("<#")[1];
|
||||||
|
@ -221,6 +246,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldIgnoreForChannelType(channel)) return;
|
||||||
sendMsgNotif(titleString, finalMsg, message);
|
sendMsgNotif(titleString, finalMsg, message);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
XSLog.error(`Failed to catch MESSAGE_CREATE: ${err}`);
|
XSLog.error(`Failed to catch MESSAGE_CREATE: ${err}`);
|
||||||
|
@ -229,13 +255,20 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function shouldIgnoreForChannelType(channel: Channel) {
|
||||||
|
if (channel.type === ChannelTypes.DM && settings.store.dmNotifications) return false;
|
||||||
|
if (channel.type === ChannelTypes.GROUP_DM && settings.store.groupDmNotifications) return false;
|
||||||
|
else return !settings.store.serverNotifications;
|
||||||
|
}
|
||||||
|
|
||||||
function sendMsgNotif(titleString: string, content: string, message: Message) {
|
function sendMsgNotif(titleString: string, content: string, message: Message) {
|
||||||
|
const timeout = Math.max(settings.store.timeout, content.length * settings.store.timeoutPerCharacter);
|
||||||
fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => {
|
fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => {
|
||||||
const msgData = {
|
const msgData = {
|
||||||
messageType: 1,
|
messageType: 1,
|
||||||
index: 0,
|
index: 0,
|
||||||
timeout: settings.store.timeout,
|
timeout,
|
||||||
height: calculateHeight(cleanMessage(content)),
|
height: calculateHeight(content),
|
||||||
opacity: settings.store.opacity,
|
opacity: settings.store.opacity,
|
||||||
volume: settings.store.volume,
|
volume: settings.store.volume,
|
||||||
audioPath: settings.store.soundPath,
|
audioPath: settings.store.soundPath,
|
||||||
|
@ -254,7 +287,7 @@ function sendOtherNotif(content: string, titleString: string) {
|
||||||
messageType: 1,
|
messageType: 1,
|
||||||
index: 0,
|
index: 0,
|
||||||
timeout: settings.store.timeout,
|
timeout: settings.store.timeout,
|
||||||
height: calculateHeight(cleanMessage(content)),
|
height: calculateHeight(content),
|
||||||
opacity: settings.store.opacity,
|
opacity: settings.store.opacity,
|
||||||
volume: settings.store.volume,
|
volume: settings.store.volume,
|
||||||
audioPath: settings.store.soundPath,
|
audioPath: settings.store.soundPath,
|
||||||
|
@ -267,13 +300,11 @@ function sendOtherNotif(content: string, titleString: string) {
|
||||||
Native.sendToOverlay(msgData);
|
Native.sendToOverlay(msgData);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldNotify(message: Message, channel: Channel) {
|
function shouldNotify(message: Message, channel: string) {
|
||||||
const currentUser = UserStore.getCurrentUser();
|
const currentUser = UserStore.getCurrentUser();
|
||||||
if (message.author.id === currentUser.id) return false;
|
if (message.author.id === currentUser.id) return false;
|
||||||
if (message.author.bot && settings.store.ignoreBots) return false;
|
if (message.author.bot && !settings.store.botNotifications) return false;
|
||||||
if (MuteStore.allowAllMessages(channel) || message.mention_everyone && !MuteStore.isSuppressEveryoneEnabled(message.guild_id)) return true;
|
return Notifs.shouldNotify(message, channel);
|
||||||
|
|
||||||
return message.mentions.some(m => m.id === currentUser.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateHeight(content: string) {
|
function calculateHeight(content: string) {
|
||||||
|
@ -282,7 +313,3 @@ function calculateHeight(content: string) {
|
||||||
if (content.length <= 300) return 200;
|
if (content.length <= 300) return 200;
|
||||||
return 250;
|
return 250;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanMessage(content: string) {
|
|
||||||
return content.replace(new RegExp("<[^>]*>", "g"), "");
|
|
||||||
}
|
|
||||||
|
|
|
@ -418,6 +418,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
name: "Kyuuhachi",
|
name: "Kyuuhachi",
|
||||||
id: 236588665420251137n,
|
id: 236588665420251137n,
|
||||||
},
|
},
|
||||||
|
nin0dev: {
|
||||||
|
name: "nin0dev",
|
||||||
|
id: 886685857560539176n
|
||||||
|
},
|
||||||
Elvyra: {
|
Elvyra: {
|
||||||
name: "Elvyra",
|
name: "Elvyra",
|
||||||
id: 708275751816003615n,
|
id: 708275751816003615n,
|
||||||
|
@ -461,6 +465,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
GabiRP: {
|
GabiRP: {
|
||||||
name: "GabiRP",
|
name: "GabiRP",
|
||||||
id: 507955112027750401n
|
id: 507955112027750401n
|
||||||
|
},
|
||||||
|
ImBanana: {
|
||||||
|
name: "Im_Banana",
|
||||||
|
id: 635250116688871425n
|
||||||
}
|
}
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue