mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-01-09 17:36:23 +00:00
Merge branch 'dev' into feat/vcnarrator-ignoreself
This commit is contained in:
commit
e6a8601178
15 changed files with 156 additions and 59 deletions
|
@ -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": {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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});"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
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 };
|
||||
});
|
||||
},
|
||||
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
...(settings.store.showLastFmLogo && {
|
||||
small_image: await getApplicationAsset("lastfm-small"),
|
||||
small_text: "Last.fm",
|
||||
small_text: "Last.fm"
|
||||
}),
|
||||
} : {
|
||||
large_image: await getApplicationAsset("lastfm-large"),
|
||||
large_text: trackData.album || undefined,
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 >
|
||||
|
|
|
@ -414,6 +414,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
coolelectronics: {
|
||||
name: "coolelectronics",
|
||||
id: 696392247205298207n,
|
||||
},
|
||||
Av32000: {
|
||||
name: "Av32000",
|
||||
id: 593436735380127770n,
|
||||
}
|
||||
} satisfies Record<string, Dev>);
|
||||
|
||||
|
|
1
src/webpack/common/types/utils.d.ts
vendored
1
src/webpack/common/types/utils.d.ts
vendored
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue