Merge branch 'main' into plugin/memberListActivities

This commit is contained in:
D3SOX 2024-03-06 14:08:41 +01:00
commit 896421a9fa
No known key found for this signature in database
GPG key ID: 39EC1673FC37B048
30 changed files with 411 additions and 176 deletions

View file

@ -62,7 +62,7 @@ function GM_fetch(url, opt) {
resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
resp.text = () => blobTo("text", blob);
resp.json = async () => JSON.parse(await blobTo("text", blob));
resp.headers = new Headers(parseHeaders(resp.responseHeaders));
resp.headers = parseHeaders(resp.responseHeaders);
resp.ok = resp.status >= 200 && resp.status < 300;
resolve(resp);
};

View file

@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
"version": "1.6.9",
"version": "1.7.0",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {

View file

@ -428,10 +428,11 @@ function runTime(token: string) {
if (searchType === "findComponent") method = "find";
if (searchType === "findExportedComponent") method = "findByProps";
if (searchType === "waitFor" || searchType === "waitForComponent" || searchType === "waitForStore") {
if (searchType === "waitFor" || searchType === "waitForComponent") {
if (typeof args[0] === "string") method = "findByProps";
else method = "find";
}
if (searchType === "waitForStore") method = "findStore";
try {
let result: any;

View file

@ -74,7 +74,7 @@ export interface MessageExtra {
}
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void>;
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
const sendListeners = new Set<SendListener>();
const editListeners = new Set<EditListener>();
@ -84,7 +84,7 @@ export async function _handlePreSend(channelId: string, messageObj: MessageObjec
for (const listener of sendListeners) {
try {
const result = await listener(channelId, messageObj, extra);
if (result && result.cancel === true) {
if (result?.cancel) {
return true;
}
} catch (e) {
@ -97,11 +97,15 @@ export async function _handlePreSend(channelId: string, messageObj: MessageObjec
export async function _handlePreEdit(channelId: string, messageId: string, messageObj: MessageObject) {
for (const listener of editListeners) {
try {
await listener(channelId, messageId, messageObj);
const result = await listener(channelId, messageId, messageObj);
if (result?.cancel) {
return true;
}
} catch (e) {
MessageEventsLogger.error("MessageEditHandler: Listener encountered an unknown error\n", e);
}
}
return false;
}
/**

View file

@ -39,9 +39,7 @@ function validateUrl(url: string) {
async function eraseAllData() {
const res = await fetch(new URL("/v1/", getCloudUrl()), {
method: "DELETE",
headers: new Headers({
Authorization: await getCloudAuth()
})
headers: { Authorization: await getCloudAuth() }
});
if (!res.ok) {

View file

@ -25,10 +25,13 @@ export default definePlugin({
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
patches: [
{
find: '"MessageActionCreators"',
find: ".Messages.EDIT_TEXTAREA_HELP",
replacement: {
match: /async editMessage\(.+?\)\{/,
replace: "$&await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
replace: (match, args) => "" +
`async ${match}` +
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
"return Promise.resolve({shoudClear:true,shouldRefocus:true});"
}
},
{

View file

@ -72,7 +72,7 @@ export default definePlugin({
replacement: [
// Add Decor avatar decoration hook to avatar decoration hook
{
match: /(?<=TryItOut:\i}\),)(?<=user:(\i).+?)/,
match: /(?<=TryItOut:\i,guildId:\i}\),)(?<=user:(\i).+?)/,
replace: "vcDecorAvatarDecoration=$self.useUserDecorAvatarDecoration($1),"
},
// Use added hook

View file

@ -17,14 +17,14 @@
*/
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
import { definePluginSettings, Settings } from "@api/Settings";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
import { ChannelStore, EmojiStore, FluxDispatcher, lodash, Parser, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import { Alerts, ChannelStore, EmojiStore, FluxDispatcher, Forms, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import type { Message } from "discord-types/general";
import { applyPalette, GIFEncoder, quantize } from "gifenc";
import type { ReactElement, ReactNode } from "react";
@ -51,8 +51,6 @@ const PreloadedUserSettingsActionCreators = proxyLazyWebpack(() => UserSettingsA
const AppearanceSettingsActionCreators = proxyLazyWebpack(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass));
const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators));
const USE_EXTERNAL_EMOJIS = 1n << 18n;
const USE_EXTERNAL_STICKERS = 1n << 37n;
const enum EmojiIntentions {
REACTION = 0,
@ -162,8 +160,28 @@ const settings = definePluginSettings({
description: "Whether to use hyperlinks when sending fake emojis and stickers",
type: OptionType.BOOLEAN,
default: true
},
hyperLinkText: {
description: "What text the hyperlink should use. {{NAME}} will be replaced with the emoji name.",
type: OptionType.STRING,
default: "{{NAME}}"
}
});
}).withPrivateSettings<{
disableEmbedPermissionCheck: boolean;
}>();
function hasPermission(channelId: string, permission: bigint) {
const channel = ChannelStore.getChannel(channelId);
if (!channel || channel.isPrivate()) return true;
return PermissionStore.can(permission, channel);
}
const hasExternalEmojiPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.USE_EXTERNAL_EMOJIS);
const hasExternalStickerPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.USE_EXTERNAL_STICKERS);
const hasEmbedPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.EMBED_LINKS);
const hasAttachmentPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.ATTACH_FILES);
export default definePlugin({
name: "FakeNitro",
@ -351,8 +369,8 @@ export default definePlugin({
predicate: () => settings.store.transformEmojis,
replacement: {
// Add the fake nitro emoji notice
match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.*?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/,
replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)`
match: /(?<=emojiDescription:)(\i)(?<=\1=\i\((\i)\).+?)/,
replace: (_, reactNode, props) => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!${props}?.fakeNitroNode?.fake)`
}
},
// Allow using custom app icons
@ -456,7 +474,7 @@ export default definePlugin({
if (typeof firstContent === "string") {
content[0] = firstContent.trimStart();
content[0] || content.shift();
} else if (firstContent?.type === "span") {
} else if (typeof firstContent?.props?.children === "string") {
firstContent.props.children = firstContent.props.children.trimStart();
firstContent.props.children || content.shift();
}
@ -466,7 +484,7 @@ export default definePlugin({
if (typeof lastContent === "string") {
content[lastIndex] = lastContent.trimEnd();
content[lastIndex] || content.pop();
} else if (lastContent?.type === "span") {
} else if (typeof lastContent?.props?.children === "string") {
lastContent.props.children = lastContent.props.children.trimEnd();
lastContent.props.children || content.pop();
}
@ -696,22 +714,6 @@ export default definePlugin({
}
},
hasPermissionToUseExternalEmojis(channelId: string): boolean {
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.store.stickerSize}`;
},
@ -722,7 +724,7 @@ export default definePlugin({
const { frames, width, height } = await parseURL(stickerLink);
const gif = GIFEncoder();
const resolution = Settings.plugins.FakeNitro.stickerSize;
const resolution = settings.store.stickerSize;
const canvas = document.createElement("canvas");
canvas.width = resolution;
@ -783,9 +785,38 @@ export default definePlugin({
return (!origStr[offset] || /\s/.test(origStr[offset])) ? "" : " ";
}
this.preSend = addPreSendListener((channelId, messageObj, extra) => {
function cannotEmbedNotice() {
return new Promise<boolean>(resolve => {
Alerts.show({
title: "Hold on!",
body: <div>
<Forms.FormText>
You are trying to send/edit a message that contains a FakeNitro emoji or sticker
, however you do not have permissions to embed links in the current channel.
Are you sure you want to send this message? Your FakeNitro items will appear as a link only.
</Forms.FormText>
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>
You can disable this notice in the plugin settings.
</Forms.FormText>
</div>,
confirmText: "Send Anyway",
cancelText: "Cancel",
secondaryConfirmText: "Do not show again",
onConfirm: () => resolve(true),
onCloseCallback: () => setImmediate(() => resolve(false)),
onConfirmSecondary() {
settings.store.disableEmbedPermissionCheck = true;
resolve(true);
}
});
});
}
this.preSend = addPreSendListener(async (channelId, messageObj, extra) => {
const { guildId } = this;
let hasBypass = false;
stickerBypass: {
if (!s.enableStickerBypass)
break stickerBypass;
@ -798,7 +829,7 @@ export default definePlugin({
if ("pack_id" in sticker)
break stickerBypass;
const canUseStickers = this.canUseStickers && this.hasPermissionToUseExternalStickers(channelId);
const canUseStickers = this.canUseStickers && hasExternalStickerPerms(channelId);
if (sticker.available !== false && (canUseStickers || sticker.guild_id === guildId))
break stickerBypass;
@ -812,9 +843,24 @@ export default definePlugin({
}
if (sticker.format_type === StickerType.APNG) {
this.sendAnimatedSticker(link, sticker.id, channelId);
if (!hasAttachmentPerms(channelId)) {
Alerts.show({
title: "Hold on!",
body: <div>
<Forms.FormText>
You cannot send this message because it contains an animated FakeNitro sticker,
and you do not have permissions to attach files in the current channel. Please remove the sticker to proceed.
</Forms.FormText>
</div>
});
} else {
this.sendAnimatedSticker(link, sticker.id, channelId);
}
return { cancel: true };
} else {
hasBypass = true;
const url = new URL(link);
url.searchParams.set("name", sticker.name);
@ -824,35 +870,47 @@ export default definePlugin({
}
if (s.enableEmojiBypass) {
const canUseEmotes = this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId);
const canUseEmotes = this.canUseEmotes && hasExternalEmojiPerms(channelId);
for (const emoji of messageObj.validNonShortcutEmojis) {
if (!emoji.require_colons) continue;
if (emoji.available !== false && canUseEmotes) continue;
if (emoji.guildId === guildId && !emoji.animated) continue;
hasBypass = true;
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
const url = new URL(emoji.url);
url.searchParams.set("size", s.emojiSize.toString());
url.searchParams.set("name", emoji.name);
const linkText = s.hyperLinkText.replaceAll("{{NAME}}", emoji.name);
messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => {
return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`;
return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${linkText}](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`;
});
}
}
if (hasBypass && !s.disableEmbedPermissionCheck && !hasEmbedPerms(channelId)) {
if (!await cannotEmbedNotice()) {
return { cancel: true };
}
}
return { cancel: false };
});
this.preEdit = addPreEditListener((channelId, __, messageObj) => {
this.preEdit = addPreEditListener(async (channelId, __, messageObj) => {
if (!s.enableEmojiBypass) return;
const canUseEmotes = this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId);
const { guildId } = this;
let hasBypass = false;
const canUseEmotes = this.canUseEmotes && hasExternalEmojiPerms(channelId);
messageObj.content = messageObj.content.replace(/(?<!\\)<a?:(?:\w+):(\d+)>/ig, (emojiStr, emojiId, offset, origStr) => {
const emoji = EmojiStore.getCustomEmojiById(emojiId);
if (emoji == null) return emojiStr;
@ -860,12 +918,24 @@ export default definePlugin({
if (emoji.available !== false && canUseEmotes) return emojiStr;
if (emoji.guildId === guildId && !emoji.animated) return emojiStr;
hasBypass = true;
const url = new URL(emoji.url);
url.searchParams.set("size", s.emojiSize.toString());
url.searchParams.set("name", emoji.name);
return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`;
const linkText = s.hyperLinkText.replaceAll("{{NAME}}", emoji.name);
return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${linkText}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`;
});
if (hasBypass && !s.disableEmbedPermissionCheck && !hasEmbedPerms(channelId)) {
if (!await cannotEmbedNotice()) {
return { cancel: true };
}
}
return { cancel: false };
});
},

View file

@ -9,6 +9,9 @@
box-shadow: inset 0 0 10px 2px grey;
filter: drop-shadow(0 0 2px grey);
pointer-events: none;
/* negate the border offsetting the lens */
margin: -2px;
}
.vc-imgzoom-square {

View file

@ -170,6 +170,11 @@ const settings = definePluginSettings({
}
],
},
showLastFmLogo: {
description: "show the Last.fm logo by the album cover",
type: OptionType.BOOLEAN,
default: true,
}
});
export default definePlugin({
@ -276,8 +281,10 @@ export default definePlugin({
{
large_image: await getApplicationAsset(largeImage),
large_text: trackData.album || undefined,
small_image: await getApplicationAsset("lastfm-small"),
small_text: "Last.fm",
...(settings.store.showLastFmLogo && {
small_image: await getApplicationAsset("lastfm-small"),
small_text: "Last.fm"
}),
} : {
large_image: await getApplicationAsset("lastfm-large"),
large_text: trackData.album || undefined,

View file

@ -0,0 +1,66 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { getCurrentChannel } from "@utils/discord";
import { SelectedChannelStore, Tooltip, useEffect, useStateFromStores } from "@webpack/common";
import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat } from ".";
import { OnlineMemberCountStore } from "./OnlineMemberCountStore";
export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) {
const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
const guildId = isTooltip ? tooltipGuildId! : currentChannel.guild_id;
const totalCount = useStateFromStores(
[GuildMemberCountStore],
() => GuildMemberCountStore.getMemberCount(guildId)
);
let onlineCount = useStateFromStores(
[OnlineMemberCountStore],
() => OnlineMemberCountStore.getCount(guildId)
);
const { groups } = useStateFromStores(
[ChannelMemberStore],
() => ChannelMemberStore.getProps(guildId, currentChannel.id)
);
if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) {
onlineCount = groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0);
}
useEffect(() => {
OnlineMemberCountStore.ensureCount(guildId);
}, [guildId]);
if (totalCount == null)
return null;
const formattedOnlineCount = onlineCount != null ? numberFormat(onlineCount) : "?";
return (
<div className={cl("widget", { tooltip: isTooltip, "member-list": !isTooltip })}>
<Tooltip text={`${formattedOnlineCount} online in this channel`} position="bottom">
{props => (
<div {...props}>
<span className={cl("online-dot")} />
<span className={cl("online")}>{formattedOnlineCount}</span>
</div>
)}
</Tooltip>
<Tooltip text={`${numberFormat(totalCount)} total server members`} position="bottom">
{props => (
<div {...props}>
<span className={cl("total-dot")} />
<span className={cl("total")}>{numberFormat(totalCount)}</span>
</div>
)}
</Tooltip>
</div>
);
}

View file

@ -0,0 +1,52 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { proxyLazy } from "@utils/lazy";
import { sleep } from "@utils/misc";
import { Queue } from "@utils/Queue";
import { Flux, FluxDispatcher, GuildChannelStore, PrivateChannelsStore } from "@webpack/common";
export const OnlineMemberCountStore = proxyLazy(() => {
const preloadQueue = new Queue();
const onlineMemberMap = new Map<string, number>();
class OnlineMemberCountStore extends Flux.Store {
getCount(guildId: string) {
return onlineMemberMap.get(guildId);
}
async _ensureCount(guildId: string) {
if (onlineMemberMap.has(guildId)) return;
await PrivateChannelsStore.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id);
}
ensureCount(guildId: string) {
if (onlineMemberMap.has(guildId)) return;
preloadQueue.push(() =>
this._ensureCount(guildId)
.then(
() => sleep(200),
() => sleep(200)
)
);
}
}
return new OnlineMemberCountStore(FluxDispatcher, {
GUILD_MEMBER_LIST_UPDATE({ guildId, groups }: { guildId: string, groups: { count: number; id: string; }[]; }) {
onlineMemberMap.set(
guildId,
groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0)
);
},
ONLINE_GUILD_MEMBER_COUNT_UPDATE({ guildId, count }) {
onlineMemberMap.set(guildId, count);
}
});
});

View file

@ -16,101 +16,48 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "./style.css";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord";
import definePlugin from "@utils/types";
import { findStoreLazy } from "@webpack";
import { SelectedChannelStore, Tooltip, useStateFromStores } from "@webpack/common";
import { FluxStore } from "@webpack/types";
const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; };
const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
import { MemberCount } from "./MemberCount";
export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; };
export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; };
};
const sharedIntlNumberFormat = new Intl.NumberFormat();
const numberFormat = (value: number) => sharedIntlNumberFormat.format(value);
function MemberCount() {
const { id: channelId, guild_id: guildId } = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
const { groups } = useStateFromStores(
[ChannelMemberStore],
() => ChannelMemberStore.getProps(guildId, channelId)
);
const total = useStateFromStores(
[GuildMemberCountStore],
() => GuildMemberCountStore.getMemberCount(guildId)
);
if (total == null)
return null;
const online =
(groups.length === 1 && groups[0].id === "unknown")
? 0
: groups.reduce((count, curr) => count + (curr.id === "offline" ? 0 : curr.count), 0);
return (
<Flex id="vc-membercount" style={{
marginTop: "1em",
paddingInline: "1em",
justifyContent: "center",
alignContent: "center",
gap: 0
}}>
<Tooltip text={`${numberFormat(online)} online in this channel`} position="bottom">
{props => (
<div {...props}>
<span
style={{
backgroundColor: "var(--green-360)",
width: "12px",
height: "12px",
borderRadius: "50%",
display: "inline-block",
marginRight: "0.5em"
}}
/>
<span style={{ color: "var(--green-360)" }}>{numberFormat(online)}</span>
</div>
)}
</Tooltip>
<Tooltip text={`${numberFormat(total)} total server members`} position="bottom">
{props => (
<div {...props}>
<span
style={{
width: "6px",
height: "6px",
borderRadius: "50%",
border: "3px solid var(--primary-400)",
display: "inline-block",
marginRight: "0.5em",
marginLeft: "1em"
}}
/>
<span style={{ color: "var(--primary-400)" }}>{numberFormat(total)}</span>
</div>
)}
</Tooltip>
</Flex>
);
}
export const numberFormat = (value: number) => sharedIntlNumberFormat.format(value);
export const cl = classNameFactory("vc-membercount-");
export default definePlugin({
name: "MemberCount",
description: "Shows the amount of online & total members in the server member list",
description: "Shows the amount of online & total members in the server member list and tooltip",
authors: [Devs.Ven, Devs.Commandtechno],
patches: [{
find: "{isSidebarVisible:",
replacement: {
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
replace: ":[$1?.startsWith('members')?$self.render():null,$2"
patches: [
{
find: "{isSidebarVisible:",
replacement: {
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
replace: ":[$1?.startsWith('members')?$self.render():null,$2"
}
},
{
find: ".invitesDisabledTooltip",
replacement: {
match: /(?<=\.VIEW_AS_ROLES_MENTIONS_WARNING.{0,100})]/,
replace: ",$self.renderTooltip(arguments[0].guild)]"
}
}
}],
],
render: ErrorBoundary.wrap(MemberCount, { noop: true })
render: ErrorBoundary.wrap(MemberCount, { noop: true }),
renderTooltip: ErrorBoundary.wrap(guild => <MemberCount isTooltip tooltipGuildId={guild.id} />, { noop: true })
});

View file

@ -0,0 +1,44 @@
.vc-membercount-widget {
display: flex;
align-content: center;
--color-online: var(--green-360);
--color-total: var(--primary-400);
}
.vc-membercount-tooltip {
margin-top: 0.25em;
margin-left: 2px;
}
.vc-membercount-member-list {
justify-content: center;
margin-top: 1em;
padding-inline: 1em;
}
.vc-membercount-online {
color: var(--color-online);
}
.vc-membercount-total {
color: var(--color-total);
}
.vc-membercount-online-dot {
background-color: var(--color-online);
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 0.5em;
}
.vc-membercount-total-dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
border: 3px solid var(--color-total);
margin: 0 0.5em 0 1em;
}

View file

@ -16,16 +16,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings,migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings");
const { toggleShowAllChannels } = findByPropsLazy("toggleShowAllChannels");
const { isOptInEnabledForGuild } = findByPropsLazy("isOptInEnabledForGuild");
const settings = definePluginSettings({
guild: {
description: "Mute Guild",
description: "Mute Guild automatically",
type: OptionType.BOOLEAN,
default: true
},
@ -38,13 +40,20 @@ const settings = definePluginSettings({
description: "Suppress All Role @mentions",
type: OptionType.BOOLEAN,
default: true
},
showAllChannels: {
description: "Show all channels automatically",
type: OptionType.BOOLEAN,
default: true
}
});
migratePluginSettings("NewGuildSettings", "MuteNewGuild");
export default definePlugin({
name: "MuteNewGuild",
description: "Mutes newly joined guilds",
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince],
name: "NewGuildSettings",
description: "Automatically mute new servers and change various other settings upon joining",
tags: ["MuteNewGuild", "mute", "server"],
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince, Devs.Mopi],
patches: [
{
find: ",acceptInvite(",
@ -70,7 +79,9 @@ export default definePlugin({
muted: settings.store.guild,
suppress_everyone: settings.store.everyone,
suppress_roles: settings.store.role
}
);
});
if (settings.store.showAllChannels && isOptInEnabledForGuild(guildId)) {
toggleShowAllChannels(guildId);
}
}
});

View file

@ -27,8 +27,8 @@ export default definePlugin({
{
find: "_ensureAudio(){",
replacement: {
match: /onloadeddata=\(\)=>\{.\.volume=/,
replace: "$&$self.settings.store.notificationVolume/100*"
match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/,
replace: "$self.settings.store.notificationVolume/100*"
},
},
],

View file

@ -59,7 +59,7 @@ export function authorize(callback?: any) {
const url = new URL(response.location);
url.searchParams.append("clientMod", "vencord");
const res = await fetch(url, {
headers: new Headers({ Accept: "application/json" })
headers: { Accept: "application/json" }
});
if (!res.ok) {

View file

@ -32,6 +32,7 @@ const { Editor, Transforms } = findByPropsLazy("Editor", "Transforms");
const { ChatInputTypes } = findByPropsLazy("ChatInputTypes");
const InputComponent = LazyComponent(() => find(m => m.default?.type?.render?.toString().includes("default.CHANNEL_TEXT_AREA")).default);
const { createChannelRecordFromServer } = findByPropsLazy("createChannelRecordFromServer");
interface UserProps {
discordId: string;
@ -125,19 +126,7 @@ export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: {
const inputType = ChatInputTypes.FORM;
inputType.disableAutoFocus = true;
const channel = {
flags_: 256,
guild_id_: null,
id: "0",
getGuildId: () => null,
isPrivate: () => true,
isActiveThread: () => false,
isArchivedLockedThread: () => false,
isDM: () => true,
roles: { "0": { permissions: 0n } },
getRecipientId: () => "0",
hasFlag: () => false,
};
const channel = createChannelRecordFromServer({ id: "0", type: 1 });
return (
<>

View file

@ -118,10 +118,10 @@ export async function addReview(review: any): Promise<Response | null> {
export async function deleteReview(id: number): Promise<Response | null> {
return await rdbRequest(`/users/${id}/reviews`, {
method: "DELETE",
headers: new Headers({
headers: {
"Content-Type": "application/json",
Accept: "application/json",
}),
},
body: JSON.stringify({
reviewid: id
})
@ -135,10 +135,10 @@ export async function deleteReview(id: number): Promise<Response | null> {
export async function reportReview(id: number) {
const res = await rdbRequest("/reports", {
method: "PUT",
headers: new Headers({
headers: {
"Content-Type": "application/json",
Accept: "application/json",
}),
},
body: JSON.stringify({
reviewid: id,
})
@ -150,10 +150,10 @@ export async function reportReview(id: number) {
async function patchBlock(action: "block" | "unblock", userId: string) {
const res = await rdbRequest("/blocks", {
method: "PATCH",
headers: new Headers({
headers: {
"Content-Type": "application/json",
Accept: "application/json",
}),
},
body: JSON.stringify({
action: action,
discordId: userId
@ -180,9 +180,9 @@ export const unblockUser = (userId: string) => patchBlock("unblock", userId);
export async function fetchBlocks(): Promise<ReviewDBUser[]> {
const res = await rdbRequest("/blocks", {
method: "GET",
headers: new Headers({
headers: {
Accept: "application/json",
})
}
});
if (!res.ok) throw new Error(`${res.status}: ${res.statusText}`);

View file

@ -371,6 +371,10 @@ export function Player() {
if (!track || !device?.is_active || shouldHide)
return null;
const exportTrackImageStyle = {
"--vc-spotify-track-image": `url(${track?.album?.image?.url || ""})`,
} as React.CSSProperties;
return (
<ErrorBoundary fallback={() => (
<div className="vc-spotify-fallback">
@ -378,7 +382,7 @@ export function Player() {
<p >Check the console for errors</p>
</div>
)}>
<div id={cl("player")}>
<div id={cl("player")} style={exportTrackImageStyle}>
<Info track={track} />
<SeekBar />
<Controls />

View file

@ -31,7 +31,7 @@ function toggleHoverControls(value: boolean) {
export default definePlugin({
name: "SpotifyControls",
description: "Adds a Spotify player above the account panel",
authors: [Devs.Ven, Devs.afn, Devs.KraXen72],
authors: [Devs.Ven, Devs.afn, Devs.KraXen72, Devs.Av32000],
options: {
hoverControls: {
description: "Show controls on hover",

View file

@ -170,9 +170,16 @@
/* these importants are necessary, it applies a width and height through inline styles */
height: 10px !important;
width: 10px !important;
margin-top: 4px;
background-color: var(--interactive-normal);
border-color: var(--interactive-normal);
color: var(--interactive-normal);
opacity: 0;
transition: opacity 0.1s;
}
#vc-spotify-progress-bar:hover > [class^="slider"] [class^="grabber"] {
opacity: 1;
}
#vc-spotify-progress-text {

View file

@ -7,6 +7,7 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { UserStore } from "@webpack/common";
export const settings = definePluginSettings({
superReactByDefault: {
@ -49,7 +50,7 @@ export default definePlugin({
find: ".trackEmojiSearchEmpty,200",
replacement: {
match: /(\.trackEmojiSearchEmpty,200(?=.+?isBurstReaction:(\i).+?(\i===\i\.EmojiIntention.REACTION)).+?\[\2,\i\]=\i\.useState\().+?\)/,
replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.settings.store.superReactByDefault&&${isReactionIntention})`
replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.shouldSuperReactByDefault&&${isReactionIntention})`
}
}
],
@ -59,5 +60,9 @@ export default definePlugin({
if (settings.store.unlimitedSuperReactionPlaying) return true;
if (playingCount <= settings.store.superReactionPlayingLimit) return true;
return false;
},
get shouldSuperReactByDefault() {
return settings.store.superReactByDefault && UserStore.getCurrentUser().premiumType != null;
}
});

View file

@ -20,13 +20,15 @@ import "./styles.css";
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { Microphone } from "@components/Icons";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react";
import definePlugin from "@utils/types";
import { chooseFile } from "@utils/web";
import { findByPropsLazy, findStoreLazy } from "@webpack";
import { Button, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
import { Button, Card, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
import { ComponentType } from "react";
import { VoiceRecorderDesktop } from "./DesktopRecorder";
@ -164,6 +166,11 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) {
fallbackValue: EMPTY_META,
});
const isUnsupportedFormat = blob && (
!blob.type.startsWith("audio/ogg")
|| blob.type.includes("codecs") && !blob.type.includes("opus")
);
return (
<ModalRoot {...modalProps}>
<ModalHeader>
@ -200,6 +207,16 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) {
recording={isRecording}
/>
{isUnsupportedFormat && (
<Card className={`vc-plugins-restart-card ${Margins.top16}`}>
<Forms.FormText>Voice Messages have to be OggOpus to be playable on iOS. This file is <code>{blob.type}</code> so it will not be playable on iOS.</Forms.FormText>
<Forms.FormText className={Margins.top8}>
To fix it, first convert it to OggOpus, for example using the <Link href="https://convertio.co/mp3-opus/">convertio web converter</Link>
</Forms.FormText>
</Card>
)}
</ModalContent>
<ModalFooter>

View file

@ -69,14 +69,14 @@ function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) {
function makeRenderMoreUsers(users: User[]) {
return function renderMoreUsers(_label: string, _count: number) {
return (
<Tooltip text={users.slice(5).map(u => u.username).join(", ")} >
<Tooltip text={users.slice(4).map(u => u.username).join(", ")} >
{({ onMouseEnter, onMouseLeave }) => (
<div
className={AvatarStyles.moreUsers}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
+{users.length - 5}
+{users.length - 4}
</div>
)}
</Tooltip >

View file

@ -106,7 +106,7 @@ export async function authorizeCloud() {
try {
const res = await fetch(location, {
headers: new Headers({ Accept: "application/json" })
headers: { Accept: "application/json" }
});
const { secret } = await res.json();
if (secret) {

View file

@ -399,6 +399,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "maisy",
id: 257109471589957632n,
},
Mopi: {
name: "Mopi",
id: 1022189106614243350n
},
Grzesiek11: {
name: "Grzesiek11",
id: 368475654662127616n,
@ -410,6 +414,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
coolelectronics: {
name: "coolelectronics",
id: 696392247205298207n,
},
Av32000: {
name: "Av32000",
id: 593436735380127770n,
}
} satisfies Record<string, Dev>);

View file

@ -118,10 +118,10 @@ export async function putCloudSettings(manual?: boolean) {
try {
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
method: "PUT",
headers: new Headers({
headers: {
Authorization: await getCloudAuth(),
"Content-Type": "application/octet-stream"
}),
},
body: deflateSync(new TextEncoder().encode(settings))
});
@ -162,11 +162,11 @@ export async function getCloudSettings(shouldNotify = true, force = false) {
try {
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
method: "GET",
headers: new Headers({
headers: {
Authorization: await getCloudAuth(),
Accept: "application/octet-stream",
"If-None-Match": Settings.cloud.settingsSyncVersion.toString()
}),
},
});
if (res.status === 404) {
@ -251,9 +251,7 @@ export async function deleteCloudSettings() {
try {
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
method: "DELETE",
headers: new Headers({
Authorization: await getCloudAuth()
}),
headers: { Authorization: await getCloudAuth() },
});
if (!res.ok) {

View file

@ -59,6 +59,7 @@ export interface Alerts {
onCancel?(): void;
onConfirm?(): void;
onConfirmSecondary?(): void;
onCloseCallback?(): void;
}): void;
/** This is a noop, it does nothing. */
close(): void;

View file

@ -83,8 +83,8 @@ export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
return true;
}
let devToolsOpen = false;
if (IS_DEV && IS_DISCORD_DESKTOP) {
var devToolsOpen = false;
// At this point in time, DiscordNative has not been exposed yet, so setImmediate is needed
setTimeout(() => {
DiscordNative/* just to make sure */?.window.setDevtoolsCallbacks(() => devToolsOpen = true, () => devToolsOpen = false);