Compare commits

..

14 Commits

Author SHA1 Message Date
Vendicated
ccca41a168 Bump to v1.0.7 2023-02-24 06:08:45 +01:00
Vendicated
af4c7d8a90 Fix Cards (they look ugly now, wtf Discord) 2023-02-24 05:48:37 +01:00
nick
77c691651e ReviewDB: Show edit instead of create review where applicable (#466)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-18 03:35:51 +01:00
Nuckyz
e14ec96e21 feat(FakeNitro): Bypass client themes and fixes (#504)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-18 03:32:02 +01:00
Vendicated
ff1f337699 Fix QuickCSS on electron 20+ 2023-02-17 15:37:38 +01:00
Nuckyz
3ca87848e5 TypingIndicator: Fix a dumb (#503)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-17 01:31:55 +01:00
Vendicated
9420735bc7 Version 1.0.6 2023-02-16 23:40:38 +01:00
Vendicated
6807820f6c Badges should use ErrorBoundaries 2023-02-16 22:46:51 +01:00
Vendicated
3cad0d60b4 Silly Discord changed a bunch of css vars 2023-02-16 22:40:19 +01:00
Vendicated
fbbc198b1b Fix PlatformIndicator 2023-02-16 22:31:13 +01:00
Nuckyz
224ae979f2 feat(plugins): Typing Indicator (#502) 2023-02-16 03:57:57 +01:00
Lewis Crichton
27fc20118b feat(plugin): RoleColorEverywhere (#482)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-16 02:50:42 +01:00
Nuckyz
60ccd8cc25 Various plugin fixes (#492)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-16 02:00:09 +01:00
Lewis Crichton
5c1519156b feat(plugin): ColorSighted (#501) 2023-02-16 01:46:14 +01:00
18 changed files with 415 additions and 53 deletions

View File

@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
"version": "1.0.5",
"version": "1.0.7",
"description": "The cutest Discord client mod",
"keywords": [],
"homepage": "https://github.com/Vendicated/Vencord#readme",

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { User } from "discord-types/general";
import { ComponentType, HTMLProps } from "react";
@ -52,6 +53,7 @@ const Badges = new Set<ProfileBadge>();
* @param badge The badge to register
*/
export function addBadge(badge: ProfileBadge) {
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
Badges.add(badge);
}

View File

@ -26,8 +26,8 @@ interface SwitchProps {
disabled?: boolean;
}
const SWITCH_ON = "var(--status-green-600)";
const SWITCH_OFF = "var(--primary-dark-400)";
const SWITCH_ON = "var(--green-360)";
const SWITCH_OFF = "var(--primary-400)";
const SwitchClasses = findByPropsLazy("slider", "input", "container");
export function Switch({ checked, onChange, disabled }: SwitchProps) {

View File

@ -91,7 +91,8 @@ ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
webPreferences: {
preload: join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false
nodeIntegration: false,
sandbox: false
}
});
await win.loadURL(`data:text/html;base64,${monacoHtml}`);

View File

@ -0,0 +1,37 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "ColorSighted",
description: "Removes the colorblind-friendly icons from statuses, just like 2015-2017 Discord",
authors: [Devs.lewisakura],
patches: [
{
find: "Masks.STATUS_ONLINE",
replacement: {
// we can use global replacement here - these are specific to the status icons and are used nowhere else,
// so it keeps the patch and plugin small and simple
match: /Masks\.STATUS_(?:IDLE|DND|STREAMING|OFFLINE)/g,
replace: "Masks.STATUS_ONLINE"
}
}
]
});

View File

@ -22,11 +22,25 @@ import { Devs } from "@utils/constants";
import { ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { ChannelStore, UserStore } from "@webpack/common";
import { ChannelStore, PermissionStore, UserStore } from "@webpack/common";
const DRAFT_TYPE = 0;
const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR");
const USE_EXTERNAL_EMOJIS = 1n << 18n;
const USE_EXTERNAL_STICKERS = 1n << 37n;
enum EmojiIntentions {
REACTION = 0,
STATUS = 1,
COMMUNITY_CONTENT = 2,
CHAT = 3,
GUILD_STICKER_RELATED_EMOJI = 4,
GUILD_ROLE_BENEFIT_EMOJI = 5,
COMMUNITY_CONTENT_ONLY = 6,
SOUNDBOARD = 7
}
interface BaseSticker {
available: boolean;
description: string;
@ -58,26 +72,39 @@ migratePluginSettings("FakeNitro", "NitroBypass");
export default definePlugin({
name: "FakeNitro",
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.obscurity],
description: "Allows you to stream in nitro quality and send fake emojis/stickers.",
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.obscurity, Devs.captain],
description: "Allows you to stream in nitro quality, send fake emojis/stickers and use client themes.",
dependencies: ["MessageEventsAPI"],
patches: [
{
find: "canUseAnimatedEmojis:function",
find: ".PREMIUM_LOCKED;",
predicate: () => Settings.plugins.FakeNitro.enableEmojiBypass === true,
replacement: [
"canUseAnimatedEmojis",
"canUseEmojisEverywhere"
].map(func => {
return {
match: new RegExp(`${func}:function\\(.+?\\{`),
replace: "$&return true;"
};
})
{
match: /(?<=(?<intention>\i)=\i\.intention)/,
replace: ",fakeNitroIntention=$<intention>"
},
{
match: /(?<=\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i)(?=\))/g,
replace: ",fakeNitroIntention"
},
{
match: /(?<=&&!\i&&)!(?<canUseExternal>\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
replace: `(!$<canUseExternal>&&![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))`
}
]
},
{
find: "canUseAnimatedEmojis:function",
predicate: () => Settings.plugins.FakeNitro.enableEmojiBypass === true,
replacement: {
match: /(?<=(?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\((?<user>\i))\){(?<premiumCheck>.+?\))/g,
replace: `,fakeNitroIntention){$<premiumCheck>||fakeNitroIntention===undefined||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`
}
},
{
find: "canUseStickersEverywhere:function",
predicate: () => Settings.plugins.FakeNitro.enableStickerBypass === true,
replacement: {
match: /canUseStickersEverywhere:function\(.+?\{/,
@ -93,7 +120,7 @@ export default definePlugin({
}
},
{
find: "canUseAnimatedEmojis:function",
find: "canStreamHighQuality:function",
predicate: () => Settings.plugins.FakeNitro.enableStreamQualityBypass === true,
replacement: [
"canUseHighVideoUploadQuality",
@ -114,6 +141,13 @@ export default definePlugin({
replace: ""
}
},
{
find: "canUseClientThemes:function",
replacement: {
match: /(?<=canUseClientThemes:function\(\i\){)/,
replace: "return true;"
}
}
],
options: {
@ -161,6 +195,22 @@ export default definePlugin({
return (UserStore.getCurrentUser().premiumType ?? 0) > 1;
},
hasPermissionToUseExternalEmojis(channelId: string) {
const channel = ChannelStore.getChannel(channelId);
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true;
return PermissionStore.can(USE_EXTERNAL_EMOJIS, channel);
},
hasPermissionToUseExternalStickers(channelId: string) {
const channel = ChannelStore.getChannel(channelId);
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true;
return PermissionStore.can(USE_EXTERNAL_STICKERS, channel);
},
getStickerLink(stickerId: string) {
return `https://media.discordapp.net/stickers/${stickerId}.png?size=${Settings.plugins.FakeNitro.stickerSize}`;
},
@ -245,7 +295,7 @@ export default definePlugin({
if (!sticker)
break stickerBypass;
if (sticker.available !== false && (this.canUseStickers || (sticker as GuildSticker)?.guild_id === guildId))
if (sticker.available !== false && ((this.canUseStickers && this.hasPermissionToUseExternalStickers(channelId)) || (sticker as GuildSticker)?.guild_id === guildId))
break stickerBypass;
let link = this.getStickerLink(sticker.id);
@ -268,7 +318,7 @@ export default definePlugin({
}
}
if (!this.canUseEmotes && settings.enableEmojiBypass) {
if ((!this.canUseEmotes || !this.hasPermissionToUseExternalEmojis(channelId)) && settings.enableEmojiBypass) {
for (const emoji of messageObj.validNonShortcutEmojis) {
if (!emoji.require_colons) continue;
if (emoji.guildId === guildId && !emoji.animated) continue;
@ -284,22 +334,22 @@ export default definePlugin({
return { cancel: false };
});
if (!this.canUseEmotes && settings.enableEmojiBypass) {
this.preEdit = addPreEditListener((_, __, messageObj) => {
const { guildId } = this;
this.preEdit = addPreEditListener((channelId, __, messageObj) => {
if (this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId)) return;
for (const [emojiStr, _, emojiId] of messageObj.content.matchAll(/(?<!\\)<a?:(\w+):(\d+)>/ig)) {
const emoji = EmojiStore.getCustomEmojiById(emojiId);
if (emoji == null || (emoji.guildId === guildId && !emoji.animated)) continue;
if (!emoji.require_colons) continue;
const { guildId } = this;
const url = emoji.url.replace(/\?size=\d+/, `?size=${Settings.plugins.FakeNitro.emojiSize}`);
messageObj.content = messageObj.content.replace(emojiStr, (match, offset, origStr) => {
return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`;
});
}
});
}
for (const [emojiStr, _, emojiId] of messageObj.content.matchAll(/(?<!\\)<a?:(\w+):(\d+)>/ig)) {
const emoji = EmojiStore.getCustomEmojiById(emojiId);
if (emoji == null || (emoji.guildId === guildId && !emoji.animated)) continue;
if (!emoji.require_colons) continue;
const url = emoji.url.replace(/\?size=\d+/, `?size=${Settings.plugins.FakeNitro.emojiSize}`);
messageObj.content = messageObj.content.replace(emojiStr, (match, offset, origStr) => {
return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`;
});
}
});
},
stop() {

View File

@ -56,7 +56,7 @@ function MemberCount() {
<div {...props}>
<span
style={{
backgroundColor: "var(--status-green-600)",
backgroundColor: "var(--green-360)",
width: "12px",
height: "12px",
borderRadius: "50%",
@ -64,7 +64,7 @@ function MemberCount() {
marginRight: "0.5em"
}}
/>
<span style={{ color: "var(--status-green-600)" }}>{online}</span>
<span style={{ color: "var(--green-360)" }}>{online}</span>
</div>
)}
</Tooltip>
@ -76,13 +76,13 @@ function MemberCount() {
width: "6px",
height: "6px",
borderRadius: "50%",
border: "3px solid var(--status-grey-500)",
border: "3px solid var(--primary-400)",
display: "inline-block",
marginRight: "0.5em",
marginLeft: "1em"
}}
/>
<span style={{ color: "var(--status-grey-500)" }}>{total}</span>
<span style={{ color: "var(--primary-400)" }}>{total}</span>
</div>
)}
</Tooltip>

View File

@ -55,7 +55,7 @@ const Icons = {
};
type Platform = keyof typeof Icons;
const getStatusColor = findByCodeLazy("STATUS_YELLOW", "TWITCH", "STATUS_GREY");
const getStatusColor = findByCodeLazy(".TWITCH", ".STREAMING", ".INVISIBLE");
const PlatformIcon = ({ platform, status }: { platform: Platform, status: string; }) => {
const tooltip = platform[0].toUpperCase() + platform.slice(1);

View File

@ -32,6 +32,7 @@ export default function ReviewsView({ userId }: { userId: string; }) {
fallbackValue: [],
deps: [refetchCount],
});
const username = UserStore.getUser(userId)?.username ?? "";
const dirtyRefetch = () => setRefetchCount(refetchCount + 1);
@ -79,7 +80,7 @@ export default function ReviewsView({ userId }: { userId: string; }) {
<textarea
className={classes(Classes.textarea.replace("textarea", ""), "enter-comment")}
// this produces something like '-_59yqs ...' but since no class exists with that name its fine
placeholder={"Review @" + UserStore.getUser(userId)?.username ?? ""}
placeholder={reviews?.some(r => r.senderdiscordid === UserStore.getCurrentUser().id) ? `Update review for @${username}` : `Review @${username}`}
onKeyDown={onKeyPress}
style={{
marginTop: "6px",

View File

@ -0,0 +1,123 @@
/*
* 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 } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { ChannelStore, GuildMemberStore, GuildStore } from "@webpack/common";
const settings = definePluginSettings({
chatMentions: {
type: OptionType.BOOLEAN,
default: true,
description: "Show role colors in chat mentions (including in the message box)",
restartNeeded: true
},
memberList: {
type: OptionType.BOOLEAN,
default: true,
description: "Show role colors in member list role headers",
restartNeeded: true
},
voiceUsers: {
type: OptionType.BOOLEAN,
default: true,
description: "Show role colors in the voice chat user list",
restartNeeded: true
}
});
export default definePlugin({
name: "RoleColorEverywhere",
authors: [Devs.KingFish, Devs.lewisakura],
description: "Adds the top role color anywhere possible",
patches: [
// Chat Mentions
{
find: 'className:"mention"',
replacement: [
{
match: /user:(\i),channelId:(\i).{0,300}?"@"\.concat\(.+?\)/,
replace: "$&,color:$self.getUserColor($1.id,{channelId:$2})"
}
],
predicate: () => settings.store.chatMentions,
},
// Slate
{
// taken from CommandsAPI
find: ".source,children",
replacement: [
{
match: /function \i\((\i)\).{5,20}id.{5,20}guildId.{5,10}channelId.{100,150}hidePersonalInformation.{5,50}jsx.{5,20},{/,
replace: "$&color:$self.getUserColor($1.id,{guildId:$1.guildId}),"
}
],
predicate: () => settings.store.chatMentions,
},
// Member List Role Names
{
find: ".memberGroupsPlaceholder",
replacement: [
{
match: /(memo\(\(function\((\i)\).{300,500}CHANNEL_MEMBERS_A11Y_LABEL.{100,200}roleIcon.{5,20}null,).," \u2014 ",.\]/,
replace: "$1$self.roleGroupColor($2)]"
},
],
predicate: () => settings.store.memberList,
},
// Voice chat users
{
find: "renderPrioritySpeaker",
replacement: [
{
match: /renderName=function\(\).{50,75}speaking.{50,100}jsx.{5,10}{/,
replace: "$&...$self.getVoiceProps(this.props),"
}
],
predicate: () => settings.store.voiceUsers,
}
],
settings,
getColor(userId: string, { channelId, guildId }: { channelId?: string; guildId?: string; }) {
if (!(guildId ??= ChannelStore.getChannel(channelId!)?.guild_id)) return null;
return GuildMemberStore.getMember(guildId, userId)?.colorString ?? null;
},
getUserColor(userId: string, ids: { channelId?: string; guildId?: string; }) {
const colorString = this.getColor(userId, ids);
return colorString && parseInt(colorString.slice(1), 16);
},
roleGroupColor({ id, count, title, guildId }: { id: string; count: number; title: string; guildId: string; }) {
const guild = GuildStore.getGuild(guildId);
const role = guild?.roles[id];
return <span style={{
color: role?.colorString,
fontWeight: "unset",
letterSpacing: ".05em"
}}>{title} &mdash; {count}</span>;
},
getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) {
return {
style: {
color: this.getColor(userId, { guildId })
}
};
}
});

View File

@ -263,7 +263,7 @@ export default definePlugin({
replacement: [
{
// Remove the divider and the open chat button for the HiddenChannelLockScreen
match: /(?<=function \i\((?<props>\i)\).{1,1800}"more-options-popout"\)\);if\()/,
match: /(?<=function \i\((?<props>\i)\).{1,2000}"more-options-popout"\)\);if\()/,
replace: "(!$self.isHiddenChannel($<props>.channel)||$<props>.inCall)&&"
},
{

View File

@ -56,7 +56,7 @@ function SilentTypingToggle() {
<div className={ButtonWrapperClasses.buttonWrapper}>
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path fill="currentColor" d="M528 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h480c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM128 180v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm288 0v-40c0-6.627-5.373-12-12-12H172c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h232c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12z" />
{isEnabled && <path d="M13 432L590 48" stroke="var(--status-red-500)" stroke-width="72" stroke-linecap="round" />}
{isEnabled && <path d="M13 432L590 48" stroke="var(--red-500)" stroke-width="72" stroke-linecap="round" />}
</svg>
</div>
</Button>

View File

@ -0,0 +1,135 @@
/*
* 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 ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { LazyComponent } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { find, findLazy, findStoreLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
import { buildSeveralUsers } from "./typingTweaks";
const ThreeDots = LazyComponent(() => find(m => m.type?.render?.toString()?.includes("().dots")));
const TypingStore = findStoreLazy("TypingStore");
const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore");
const Formatters = findLazy(m => m.Messages?.SEVERAL_USERS_TYPING);
function getDisplayName(guildId: string, userId: string) {
return GuildMemberStore.getNick(guildId, userId) ?? UserStore.getUser(userId).username;
}
function TypingIndicator({ channelId }: { channelId: string; }) {
const typingUsers: Record<string, number> = useStateFromStores(
[TypingStore],
() => ({ ...TypingStore.getTypingUsers(channelId) as Record<string, number> }),
null,
(old, current) => {
const oldKeys = Object.keys(old);
const currentKeys = Object.keys(current);
return oldKeys.length === currentKeys.length && JSON.stringify(oldKeys) === JSON.stringify(currentKeys);
}
);
const guildId = ChannelStore.getChannel(channelId).guild_id;
if (!settings.store.includeMutedChannels) {
const isChannelMuted = UserGuildSettingsStore.isChannelMuted(guildId, channelId);
if (isChannelMuted) return null;
}
delete typingUsers[UserStore.getCurrentUser().id];
const typingUsersArray = Object.keys(typingUsers);
let tooltipText: string;
switch (typingUsersArray.length) {
case 0: break;
case 1: {
tooltipText = Formatters.Messages.ONE_USER_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]) });
break;
}
case 2: {
tooltipText = Formatters.Messages.TWO_USERS_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]) });
break;
}
case 3: {
tooltipText = Formatters.Messages.THREE_USERS_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]), c: getDisplayName(guildId, typingUsersArray[2]) });
break;
}
default: {
tooltipText = Settings.plugins.TypingTweaks.enabled
? buildSeveralUsers({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]), c: typingUsersArray.length - 2 })
: Formatters.Messages.SEVERAL_USERS_TYPING;
break;
}
}
if (typingUsersArray.length > 0) {
return (
<Tooltip text={tooltipText!}>
{({ onMouseLeave, onMouseEnter }) => (
<div
style={{ marginLeft: 6, zIndex: 0, cursor: "pointer" }}
onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter}
>
<ThreeDots dotRadius={3} themed={true} />
</div>
)}
</Tooltip>
);
}
return null;
}
const settings = definePluginSettings({
includeMutedChannels: {
type: OptionType.BOOLEAN,
description: "Whether to show the typing indicator for muted channels.",
default: false
}
});
export default definePlugin({
name: "TypingIndicator",
description: "Adds an indicator if someone is typing on a channel.",
authors: [Devs.Nuckyz],
settings,
patches: [
{
find: ".UNREAD_HIGHLIGHT",
replacement: {
match: /(?<=(?<channel>\i)=\i\.channel,.+?\(\)\.children.+?:null)/,
replace: ",$self.TypingIndicator($<channel>.id)"
}
}
],
TypingIndicator: (channelId: string) => (
<ErrorBoundary noop>
<TypingIndicator channelId={channelId} />
</ErrorBoundary>
),
});

View File

@ -44,6 +44,15 @@ const settings = definePluginSettings({
}
});
export function buildSeveralUsers({ a, b, c }: { a: string, b: string, c: number; }) {
return [
<strong key="0">{a}</strong>,
", ",
<strong key="2">{b}</strong>,
`, and ${c} others are typing...`
];
}
export default definePlugin({
name: "TypingTweaks",
description: "Show avatars and role colours in the typing indicator",
@ -77,14 +86,7 @@ export default definePlugin({
],
settings,
buildSeveralUsers({ a, b, c }: { a: string, b: string, c: number; }) {
return [
<strong key="0">{a}</strong>,
", ",
<strong key="2">{b}</strong>,
`, and ${c} others are typing...`
];
},
buildSeveralUsers,
mutateChildren(props: any, users: User[], children: any) {
if (!Array.isArray(children)) return children;

View File

@ -48,10 +48,10 @@ export default definePlugin({
},
{
// channel mentions
find: 'className:"channelMention",iconType:(',
find: ".EMOJI_IN_MESSAGE_HOVER",
replacement: {
match: /onClick:(.{1,3}),/,
replace: "onClick:(_vcEv)=>(_vcEv.detail>=2||_vcEv.target.className.includes('MentionText'))&&($1)(),",
match: /onClick:(\i)(?=,.{0,30}className:"channelMention")/,
replace: "onClick:(_vcEv)=>(_vcEv.detail>=2||_vcEv.target.className.includes('MentionText'))&&($1)()",
}
}
],

View File

@ -121,6 +121,13 @@ export default definePlugin({
const reactions = getReactionsWithQueue(message, emoji, type);
const users = Object.values(reactions).filter(Boolean) as User[];
for (const user of users) {
FluxDispatcher.dispatch({
type: "USER_UPDATE",
user
});
}
return (
<div
style={{ marginLeft: "0.5em", transform: "scale(0.9)" }}

View File

@ -196,5 +196,9 @@ export const Devs = /* #__PURE__*/ Object.freeze({
whqwert: {
name: "whqwert",
id: 586239091520176128n
},
lewisakura: {
name: "lewisakura",
id: 96269247411400704n
}
});

View File

@ -32,7 +32,7 @@ export const Forms = {
FormText: waitForComponent<t.FormText>("FormText", m => m.Types?.INPUT_PLACEHOLDER),
};
export const Card = waitForComponent<t.Card>("Card", m => m.Types?.PRIMARY === "cardPrimary");
export const Card = waitForComponent<t.Card>("Card", m => m.Types?.PRIMARY && m.defaultProps);
export const Button = waitForComponent<t.Button>("Button", ["Hovers", "Looks", "Sizes"]);
export const Switch = waitForComponent<t.Switch>("Switch", filters.byCode("tooltipNote", "ringTarget"));
export const Tooltip = waitForComponent<t.Tooltip>("Tooltip", ["Positions", "Colors"]);