Compare commits

...

27 commits

Author SHA1 Message Date
b23bbc958f Merge branch 'main' of https://www.coastalcommits.com/SeaswimmerTheFsh/Vencord 2024-02-27 18:26:51 -05:00
9f52e0a5b1 updated message logger enhanced 2024-02-27 18:26:36 -05:00
4709c87afe updated some userplugins 2024-02-27 18:26:36 -05:00
1510e8f7a8 added ServerDownload 2024-02-27 18:26:36 -05:00
6a5660d3ba added vc-timezones 2024-02-27 18:26:35 -05:00
44350f02cd added a bunch of custom plugins 2024-02-27 18:26:35 -05:00
86d42200d5 Added DontLeak 2024-02-27 18:26:35 -05:00
61329466bf removed the userplugins readme because it causes a build error 2024-02-27 18:26:35 -05:00
28c36f9c0d added submodules 2024-02-27 18:26:35 -05:00
17120a9771 removed userplugins 2024-02-27 18:26:35 -05:00
8c8dcf789e Revert "changing to coastalcommits mirrors"
This reverts commit 09c3025e81.
2024-02-27 18:26:35 -05:00
428749001f changing to coastalcommits mirrors 2024-02-27 18:26:34 -05:00
e77a7b142d changed gitignore 2024-02-27 18:26:34 -05:00
Vendicated
8ccd731aee
bump to v1.7.0 2024-02-27 13:41:55 +01:00
Andrei Neacsu
1afa185f57
LastfmRichPresence: Add an option to hide the Last.fm logo (#2189)
Co-authored-by: V <vendicated@riseup.net>
2024-02-27 12:30:27 +00:00
Lualt
76de8c424e
feat(plugin) FakeNitro: Allow customising hyperlink text (#2192)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-02-27 13:27:37 +01:00
Nuckyz
ed5e1be7a4
Add permissions checks for FakeNitro actions (#2160)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-02-27 13:19:05 +01:00
WackyModer
27696ed62a
whoReacted: fix reaction count being off by one (#2209)
Co-authored-by: V <vendicated@riseup.net>
2024-02-27 11:31:51 +00:00
Vendicated
b9d0a1c563
SpotifyControls: fix seekbar grabber alignment 2024-02-27 12:01:15 +01:00
Av32000
5e7b4e9c92
SpotifyControls: export album cover as CSS variable for themes (#2197)
--vc-spotify-track-image
2024-02-27 10:27:34 +00:00
Nuckyz
9958f5a2ea
Fix ReviewDB 2024-02-27 05:04:54 -03:00
Sqaaakoi
414184ef25
ImageZoom: negate the border offsetting the lens (#2117)
Co-authored-by: Lewis Crichton <lewi@lewisakura.moe>
2024-02-25 23:51:09 +00:00
Vendicated
2d8715adf0
SuperReactionTweaks: only super react by default if user has nitro 2024-02-23 13:06:03 +01:00
Nuckyz
e3fd954512
Fix Decor patch 2024-02-22 21:31:15 -03:00
Manti
f922f0bc0d
fix reviewdb auth not working on userscript (#2194) 2024-02-20 18:13:25 +00:00
Vendicated
604f4c49af
VoiceMessages: Add warning if audio file is not OggOpus 2024-02-18 18:22:48 +01:00
Margot
c3030bbad0
MuteNewGuild -> NewGuildSettings; add 'show all channels' option (#2065)
Co-authored-by: MopigamesYT <mopigames@proton.me>
Co-authored-by: V <vendicated@riseup.net>
2024-02-18 17:42:26 +01:00
19 changed files with 205 additions and 76 deletions

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

@ -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

@ -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",
@ -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

@ -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

@ -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

@ -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

@ -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;