merge unconflict
This commit is contained in:
@@ -19,12 +19,9 @@
|
|||||||
- More Plugins
|
- More Plugins
|
||||||
- Utility (ex.: ToneIndicators)
|
- Utility (ex.: ToneIndicators)
|
||||||
- Fun (ex.: DecTalk, MoreMarkdown)
|
- Fun (ex.: DecTalk, MoreMarkdown)
|
||||||
- In-built core functions (ex.: Server Themes)
|
- Customization (ex.: Server Themes)
|
||||||
- Alien Cats
|
- Alien Cats
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Server themes are currently disabled, as they'll be rewritten soon.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export * from "./channel";
|
||||||
export * from "./commands";
|
export * from "./commands";
|
||||||
export * from "./messages";
|
export * from "./messages";
|
||||||
export * from "./channel";
|
export * from "./misc";
|
||||||
|
|||||||
4
packages/discord-types/enums/misc.ts
Normal file
4
packages/discord-types/enums/misc.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const enum CloudUploadPlatform {
|
||||||
|
REACT_NATIVE = 0,
|
||||||
|
WEB = 1,
|
||||||
|
}
|
||||||
1
packages/discord-types/src/index.d.ts
vendored
1
packages/discord-types/src/index.d.ts
vendored
@@ -4,6 +4,7 @@ export * from "./components";
|
|||||||
export * from "./flux";
|
export * from "./flux";
|
||||||
export * from "./fluxEvents";
|
export * from "./fluxEvents";
|
||||||
export * from "./menu";
|
export * from "./menu";
|
||||||
|
export * from "./modules";
|
||||||
export * from "./stores";
|
export * from "./stores";
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
export * as Webpack from "../webpack";
|
export * as Webpack from "../webpack";
|
||||||
|
|||||||
74
packages/discord-types/src/modules/CloudUpload.d.ts
vendored
Normal file
74
packages/discord-types/src/modules/CloudUpload.d.ts
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import EventEmitter from "events";
|
||||||
|
import { CloudUploadPlatform } from "../../enums";
|
||||||
|
|
||||||
|
interface BaseUploadItem {
|
||||||
|
platform: CloudUploadPlatform;
|
||||||
|
id?: string;
|
||||||
|
origin?: string;
|
||||||
|
isThumbnail?: boolean;
|
||||||
|
clip?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReactNativeUploadItem extends BaseUploadItem {
|
||||||
|
platform: CloudUploadPlatform.REACT_NATIVE;
|
||||||
|
uri: string;
|
||||||
|
filename?: string;
|
||||||
|
mimeType?: string;
|
||||||
|
durationSecs?: number;
|
||||||
|
waveform?: string;
|
||||||
|
isRemix?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebUploadItem extends BaseUploadItem {
|
||||||
|
platform: CloudUploadPlatform.WEB;
|
||||||
|
file: File;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CloudUploadItem = ReactNativeUploadItem | WebUploadItem;
|
||||||
|
|
||||||
|
export class CloudUpload extends EventEmitter {
|
||||||
|
constructor(item: CloudUploadItem, channelId: string, showLargeMessageDialog?: boolean, reactNativeFileIndex?: number);
|
||||||
|
|
||||||
|
channelId: string;
|
||||||
|
classification: string;
|
||||||
|
clip: unknown;
|
||||||
|
contentHash: unknown;
|
||||||
|
currentSize: number;
|
||||||
|
description: string | null;
|
||||||
|
durationSecs: number | undefined;
|
||||||
|
etag: string | undefined;
|
||||||
|
error: unknown;
|
||||||
|
filename: string;
|
||||||
|
id: string;
|
||||||
|
isImage: boolean;
|
||||||
|
isRemix: boolean | undefined;
|
||||||
|
isThumbnail: boolean;
|
||||||
|
isVideo: boolean;
|
||||||
|
item: {
|
||||||
|
file: File;
|
||||||
|
platform: CloudUploadPlatform;
|
||||||
|
origin: string;
|
||||||
|
};
|
||||||
|
loaded: number;
|
||||||
|
mimeType: string;
|
||||||
|
origin: string;
|
||||||
|
postCompressionSize: number | undefined;
|
||||||
|
preCompressionSize: number;
|
||||||
|
responseUrl: string;
|
||||||
|
sensitive: boolean;
|
||||||
|
showLargeMessageDialog: boolean;
|
||||||
|
spoiler: boolean;
|
||||||
|
startTime: number;
|
||||||
|
status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED" | "REMOVED_FROM_MSG_DRAFT";
|
||||||
|
uniqueId: string;
|
||||||
|
uploadedFilename: string;
|
||||||
|
waveform: string | undefined;
|
||||||
|
|
||||||
|
// there are many more methods than just these but I didn't find them particularly useful
|
||||||
|
upload(): Promise<void>;
|
||||||
|
cancel(): void;
|
||||||
|
delete(): Promise<void>;
|
||||||
|
getSize(): number;
|
||||||
|
maybeConvertToWebP(): Promise<void>;
|
||||||
|
removeFromMsgDraft(): void;
|
||||||
|
}
|
||||||
1
packages/discord-types/src/modules/index.d.ts
vendored
Normal file
1
packages/discord-types/src/modules/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./CloudUpload";
|
||||||
11
packages/discord-types/src/stores/StreamerModeStore.d.ts
vendored
Normal file
11
packages/discord-types/src/stores/StreamerModeStore.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { FluxStore } from "@vencord/discord-types";
|
||||||
|
|
||||||
|
export class StreamerModeStore extends FluxStore {
|
||||||
|
get autoToggle(): boolean;
|
||||||
|
get disableNotifications(): boolean;
|
||||||
|
get disableSounds(): boolean;
|
||||||
|
get enableContentProtection(): boolean;
|
||||||
|
get enabled(): boolean;
|
||||||
|
get hideInstantInvites(): boolean;
|
||||||
|
get hidePersonalInformation(): boolean;
|
||||||
|
}
|
||||||
42
packages/discord-types/src/stores/VoiceStateStore.d.ts
vendored
Normal file
42
packages/discord-types/src/stores/VoiceStateStore.d.ts
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { DiscordRecord } from "../common";
|
||||||
|
import { FluxStore } from "./FluxStore";
|
||||||
|
|
||||||
|
export type UserVoiceStateRecords = Record<string, VoiceState>;
|
||||||
|
export type VoiceStates = Record<string, UserVoiceStateRecords>;
|
||||||
|
|
||||||
|
export interface VoiceState extends DiscordRecord {
|
||||||
|
userId: string;
|
||||||
|
channelId: string | null | undefined;
|
||||||
|
sessionId: string | null | undefined;
|
||||||
|
mute: boolean;
|
||||||
|
deaf: boolean;
|
||||||
|
selfMute: boolean;
|
||||||
|
selfDeaf: boolean;
|
||||||
|
selfVideo: boolean;
|
||||||
|
selfStream: boolean | undefined;
|
||||||
|
suppress: boolean;
|
||||||
|
requestToSpeakTimestamp: string | null | undefined;
|
||||||
|
discoverable: boolean;
|
||||||
|
|
||||||
|
isVoiceMuted(): boolean;
|
||||||
|
isVoiceDeafened(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VoiceStateStore extends FluxStore {
|
||||||
|
getAllVoiceStates(): VoiceStates;
|
||||||
|
|
||||||
|
getVoiceStates(guildId?: string | null): UserVoiceStateRecords;
|
||||||
|
getVoiceStatesForChannel(channelId: string): UserVoiceStateRecords;
|
||||||
|
getVideoVoiceStatesForChannel(channelId: string): UserVoiceStateRecords;
|
||||||
|
|
||||||
|
getVoiceState(guildId: string | null, userId: string): VoiceState | undefined;
|
||||||
|
getUserVoiceChannelId(guildId: string | null, userId: string): string | undefined;
|
||||||
|
getVoiceStateForChannel(channelId: string, userId?: string): VoiceState | undefined;
|
||||||
|
getVoiceStateForUser(userId: string): VoiceState | undefined;
|
||||||
|
|
||||||
|
getCurrentClientVoiceChannelId(guildId: string | null): string | undefined;
|
||||||
|
isCurrentClientInVoiceChannel(): boolean;
|
||||||
|
|
||||||
|
isInChannel(channelId: string, userId?: string): boolean;
|
||||||
|
hasVideo(channelId: string): boolean;
|
||||||
|
}
|
||||||
2
packages/discord-types/src/stores/index.d.ts
vendored
2
packages/discord-types/src/stores/index.d.ts
vendored
@@ -12,10 +12,12 @@ export * from "./RelationshipStore";
|
|||||||
export * from "./SelectedChannelStore";
|
export * from "./SelectedChannelStore";
|
||||||
export * from "./SelectedGuildStore";
|
export * from "./SelectedGuildStore";
|
||||||
export * from "./StickersStore";
|
export * from "./StickersStore";
|
||||||
|
export * from "./StreamerModeStore";
|
||||||
export * from "./ThemeStore";
|
export * from "./ThemeStore";
|
||||||
export * from "./TypingStore";
|
export * from "./TypingStore";
|
||||||
export * from "./UserProfileStore";
|
export * from "./UserProfileStore";
|
||||||
export * from "./UserStore";
|
export * from "./UserStore";
|
||||||
|
export * from "./VoiceStateStore";
|
||||||
export * from "./WindowStore";
|
export * from "./WindowStore";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import type { Channel, CustomEmoji, Message } from "@vencord/discord-types";
|
import type { Channel, CloudUpload, CustomEmoji, Message } from "@vencord/discord-types";
|
||||||
import { MessageStore } from "@webpack/common";
|
import { MessageStore } from "@webpack/common";
|
||||||
import type { Promisable } from "type-fest";
|
import type { Promisable } from "type-fest";
|
||||||
|
|
||||||
@@ -30,30 +30,6 @@ export interface MessageObject {
|
|||||||
tts: boolean;
|
tts: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Upload {
|
|
||||||
classification: string;
|
|
||||||
currentSize: number;
|
|
||||||
description: string | null;
|
|
||||||
filename: string;
|
|
||||||
id: string;
|
|
||||||
isImage: boolean;
|
|
||||||
isVideo: boolean;
|
|
||||||
item: {
|
|
||||||
file: File;
|
|
||||||
platform: number;
|
|
||||||
};
|
|
||||||
loaded: number;
|
|
||||||
mimeType: string;
|
|
||||||
preCompressionSize: number;
|
|
||||||
responseUrl: string;
|
|
||||||
sensitive: boolean;
|
|
||||||
showLargeMessageDialog: boolean;
|
|
||||||
spoiler: boolean;
|
|
||||||
status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED";
|
|
||||||
uniqueId: string;
|
|
||||||
uploadedFilename: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageReplyOptions {
|
export interface MessageReplyOptions {
|
||||||
messageReference: Message["messageReference"];
|
messageReference: Message["messageReference"];
|
||||||
allowedMentions?: {
|
allowedMentions?: {
|
||||||
@@ -64,7 +40,7 @@ export interface MessageReplyOptions {
|
|||||||
|
|
||||||
export interface MessageOptions {
|
export interface MessageOptions {
|
||||||
stickers?: string[];
|
stickers?: string[];
|
||||||
uploads?: Upload[];
|
uploads?: CloudUpload[];
|
||||||
replyOptions: MessageReplyOptions;
|
replyOptions: MessageReplyOptions;
|
||||||
content: string;
|
content: string;
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
|
|||||||
@@ -16,7 +16,10 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { isPrimitiveReactNode } from "@utils/react";
|
||||||
import { waitFor } from "@webpack";
|
import { waitFor } from "@webpack";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
let NoticesModule: any;
|
let NoticesModule: any;
|
||||||
waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m);
|
waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m);
|
||||||
@@ -36,7 +39,11 @@ export function nextNotice() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showNotice(message: string, buttonText: string, onOkClick: () => void) {
|
export function showNotice(message: ReactNode, buttonText: string, onOkClick: () => void) {
|
||||||
noticesQueue.push(["GENERIC", message, buttonText, onOkClick]);
|
const notice = isPrimitiveReactNode(message)
|
||||||
|
? message
|
||||||
|
: <ErrorBoundary fallback={() => "Error Showing Notice"}>{message}</ErrorBoundary>;
|
||||||
|
|
||||||
|
noticesQueue.push(["GENERIC", notice, buttonText, onOkClick]);
|
||||||
if (!currentNotice) nextNotice();
|
if (!currentNotice) nextNotice();
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ export default definePlugin({
|
|||||||
{
|
{
|
||||||
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
|
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
|
match: /(?<=\]\}\)),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
|
||||||
replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" +
|
replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" +
|
||||||
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},`
|
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,11 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Upload } from "@api/MessageEvents";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { CloudUpload } from "@vencord/discord-types";
|
||||||
import { findByCodeLazy } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
import { useState } from "@webpack/common";
|
import { useState } from "@webpack/common";
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
AnonymiseUploadButton: ErrorBoundary.wrap(({ upload }: { upload: Upload; }) => {
|
AnonymiseUploadButton: ErrorBoundary.wrap(({ upload }: { upload: CloudUpload; }) => {
|
||||||
const [anonymise, setAnonymise] = useState(upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault);
|
const [anonymise, setAnonymise] = useState(upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault);
|
||||||
|
|
||||||
function onToggleAnonymise() {
|
function onToggleAnonymise() {
|
||||||
@@ -110,7 +110,7 @@ export default definePlugin({
|
|||||||
);
|
);
|
||||||
}, { noop: true }),
|
}, { noop: true }),
|
||||||
|
|
||||||
anonymise(upload: Upload) {
|
anonymise(upload: CloudUpload) {
|
||||||
if ((upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault) === false) {
|
if ((upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault) === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/plugins/clearURLs/README.md
Normal file
11
src/plugins/clearURLs/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# ClearURLs
|
||||||
|
|
||||||
|
Automatically removes tracking elements from URLs you send.
|
||||||
|
|
||||||
|
Uses data from the [ClearURLs browser extension](https://clearurls.xyz/).
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
**Before:** `https://www.amazon.com/dp/exampleProduct/ref=sxin_0_pb?__mk_de_DE=ÅMÅŽÕÑ&keywords=tea&pd_rd_i=exampleProduct&pd_rd_r=8d39e4cd-1e4f-43db-b6e7-72e969a84aa5&pd_rd_w=1pcKM&pd_rd_wg=hYrNl&pf_rd_p=50bbfd25-5ef7-41a2-68d6-74d854b30e30&pf_rd_r=0GMWD0YYKA7XFGX55ADP&qid=1517757263&rnid=2914120011`
|
||||||
|
|
||||||
|
**After:** `https://www.amazon.com/dp/exampleProduct/`
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const defaultRules = [
|
|
||||||
"action_object_map",
|
|
||||||
"action_type_map",
|
|
||||||
"action_ref_map",
|
|
||||||
"spm@*.aliexpress.com",
|
|
||||||
"scm@*.aliexpress.com",
|
|
||||||
"aff_platform",
|
|
||||||
"aff_trace_key",
|
|
||||||
"algo_expid@*.aliexpress.*",
|
|
||||||
"algo_pvid@*.aliexpress.*",
|
|
||||||
"btsid",
|
|
||||||
"ws_ab_test",
|
|
||||||
"pd_rd_*@amazon.*",
|
|
||||||
"_encoding@amazon.*",
|
|
||||||
"psc@amazon.*",
|
|
||||||
"tag@amazon.*",
|
|
||||||
"ref_@amazon.*",
|
|
||||||
"pf_rd_*@amazon.*",
|
|
||||||
"pf@amazon.*",
|
|
||||||
"crid@amazon.*",
|
|
||||||
"keywords@amazon.*",
|
|
||||||
"sprefix@amazon.*",
|
|
||||||
"sr@amazon.*",
|
|
||||||
"ie@amazon.*",
|
|
||||||
"node@amazon.*",
|
|
||||||
"qid@amazon.*",
|
|
||||||
"callback@bilibili.com",
|
|
||||||
"cvid@bing.com",
|
|
||||||
"form@bing.com",
|
|
||||||
"sk@bing.com",
|
|
||||||
"sp@bing.com",
|
|
||||||
"sc@bing.com",
|
|
||||||
"qs@bing.com",
|
|
||||||
"pq@bing.com",
|
|
||||||
"sc_cid",
|
|
||||||
"mkt_tok",
|
|
||||||
"trk",
|
|
||||||
"trkCampaign",
|
|
||||||
"ga_*",
|
|
||||||
"gclid",
|
|
||||||
"gclsrc",
|
|
||||||
"hmb_campaign",
|
|
||||||
"hmb_medium",
|
|
||||||
"hmb_source",
|
|
||||||
"spReportId",
|
|
||||||
"spJobID",
|
|
||||||
"spUserID",
|
|
||||||
"spMailingID",
|
|
||||||
"itm_*",
|
|
||||||
"s_cid",
|
|
||||||
"elqTrackId",
|
|
||||||
"elqTrack",
|
|
||||||
"assetType",
|
|
||||||
"assetId",
|
|
||||||
"recipientId",
|
|
||||||
"campaignId",
|
|
||||||
"siteId",
|
|
||||||
"mc_cid",
|
|
||||||
"mc_eid",
|
|
||||||
"pk_*",
|
|
||||||
"sc_campaign",
|
|
||||||
"sc_channel",
|
|
||||||
"sc_content",
|
|
||||||
"sc_medium",
|
|
||||||
"sc_outcome",
|
|
||||||
"sc_geo",
|
|
||||||
"sc_country",
|
|
||||||
"nr_email_referer",
|
|
||||||
"vero_conv",
|
|
||||||
"vero_id",
|
|
||||||
"yclid",
|
|
||||||
"_openstat",
|
|
||||||
"mbid",
|
|
||||||
"cmpid",
|
|
||||||
"cid",
|
|
||||||
"c_id",
|
|
||||||
"campaign_id",
|
|
||||||
"Campaign",
|
|
||||||
"hash@ebay.*",
|
|
||||||
"fb_action_ids",
|
|
||||||
"fb_action_types",
|
|
||||||
"fb_ref",
|
|
||||||
"fb_source",
|
|
||||||
"fbclid",
|
|
||||||
"refsrc@facebook.com",
|
|
||||||
"hrc@facebook.com",
|
|
||||||
"gs_l",
|
|
||||||
"gs_lcp@google.*",
|
|
||||||
"ved@google.*",
|
|
||||||
"ei@google.*",
|
|
||||||
"sei@google.*",
|
|
||||||
"gws_rd@google.*",
|
|
||||||
"gs_gbg@google.*",
|
|
||||||
"gs_mss@google.*",
|
|
||||||
"gs_rn@google.*",
|
|
||||||
"_hsenc",
|
|
||||||
"_hsmi",
|
|
||||||
"__hssc",
|
|
||||||
"__hstc",
|
|
||||||
"hsCtaTracking",
|
|
||||||
"source@sourceforge.net",
|
|
||||||
"position@sourceforge.net",
|
|
||||||
"t@*.twitter.com",
|
|
||||||
"s@*.twitter.com",
|
|
||||||
"ref_*@*.twitter.com",
|
|
||||||
"t@*.x.com",
|
|
||||||
"s@*.x.com",
|
|
||||||
"ref_*@*.x.com",
|
|
||||||
"t@*.fixupx.com",
|
|
||||||
"s@*.fixupx.com",
|
|
||||||
"ref_*@*.fixupx.com",
|
|
||||||
"t@*.fxtwitter.com",
|
|
||||||
"s@*.fxtwitter.com",
|
|
||||||
"ref_*@*.fxtwitter.com",
|
|
||||||
"t@*.twittpr.com",
|
|
||||||
"s@*.twittpr.com",
|
|
||||||
"ref_*@*.twittpr.com",
|
|
||||||
"t@*.fixvx.com",
|
|
||||||
"s@*.fixvx.com",
|
|
||||||
"ref_*@*.fixvx.com",
|
|
||||||
"tt_medium",
|
|
||||||
"tt_content",
|
|
||||||
"lr@yandex.*",
|
|
||||||
"redircnt@yandex.*",
|
|
||||||
"feature@*.youtube.com",
|
|
||||||
"kw@*.youtube.com",
|
|
||||||
"si@*.youtube.com",
|
|
||||||
"pp@*.youtube.com",
|
|
||||||
"si@*.youtu.be",
|
|
||||||
"wt_zmc",
|
|
||||||
"utm_source",
|
|
||||||
"utm_content",
|
|
||||||
"utm_medium",
|
|
||||||
"utm_campaign",
|
|
||||||
"utm_term",
|
|
||||||
"si@open.spotify.com",
|
|
||||||
"igshid",
|
|
||||||
"igsh",
|
|
||||||
"share_id@reddit.com",
|
|
||||||
"si@soundcloud.com",
|
|
||||||
];
|
|
||||||
@@ -22,77 +22,74 @@ import {
|
|||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
import { defaultRules } from "./defaultRules";
|
const CLEAR_URLS_JSON_URL = "https://raw.githubusercontent.com/ClearURLs/Rules/master/data.min.json";
|
||||||
|
|
||||||
// From lodash
|
interface Provider {
|
||||||
const reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
|
urlPattern: string;
|
||||||
const reHasRegExpChar = RegExp(reRegExpChar.source);
|
completeProvider: boolean;
|
||||||
|
rules?: string[];
|
||||||
|
rawRules?: string[];
|
||||||
|
referralMarketing?: string[];
|
||||||
|
exceptions?: string[];
|
||||||
|
redirections?: string[];
|
||||||
|
forceRedirection?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClearUrlsData {
|
||||||
|
providers: Record<string, Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RuleSet {
|
||||||
|
name: string;
|
||||||
|
urlPattern: RegExp;
|
||||||
|
rules?: RegExp[];
|
||||||
|
rawRules?: RegExp[];
|
||||||
|
exceptions?: RegExp[];
|
||||||
|
}
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "ClearURLs",
|
name: "ClearURLs",
|
||||||
description: "Removes tracking garbage from URLs",
|
description: "Automatically removes tracking elements from URLs you send",
|
||||||
authors: [Devs.adryd],
|
authors: [Devs.adryd, Devs.thororen],
|
||||||
|
|
||||||
start() {
|
rules: [] as RuleSet[],
|
||||||
this.createRules();
|
|
||||||
|
async start() {
|
||||||
|
await this.createRules();
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.rules = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
onBeforeMessageSend(_, msg) {
|
onBeforeMessageSend(_, msg) {
|
||||||
return this.onSend(msg);
|
return this.cleanMessage(msg);
|
||||||
},
|
},
|
||||||
|
|
||||||
onBeforeMessageEdit(_cid, _mid, msg) {
|
onBeforeMessageEdit(_cid, _mid, msg) {
|
||||||
return this.onSend(msg);
|
return this.cleanMessage(msg);
|
||||||
},
|
},
|
||||||
|
|
||||||
escapeRegExp(str: string) {
|
async createRules() {
|
||||||
return (str && reHasRegExpChar.test(str))
|
const res = await fetch(CLEAR_URLS_JSON_URL)
|
||||||
? str.replace(reRegExpChar, "\\$&")
|
.then(res => res.json()) as ClearUrlsData;
|
||||||
: (str || "");
|
|
||||||
},
|
|
||||||
|
|
||||||
createRules() {
|
this.rules = [];
|
||||||
// Can be extended upon once user configs are available
|
|
||||||
// Eg. (useDefaultRules: boolean, customRules: Array[string])
|
|
||||||
const rules = defaultRules;
|
|
||||||
|
|
||||||
this.universalRules = new Set();
|
for (const [name, provider] of Object.entries(res.providers)) {
|
||||||
this.rulesByHost = new Map();
|
const urlPattern = new RegExp(provider.urlPattern, "i");
|
||||||
this.hostRules = new Map();
|
|
||||||
|
|
||||||
for (const rule of rules) {
|
const rules = provider.rules?.map(rule => new RegExp(rule, "i"));
|
||||||
const splitRule = rule.split("@");
|
const rawRules = provider.rawRules?.map(rule => new RegExp(rule, "i"));
|
||||||
const paramRule = new RegExp(
|
const exceptions = provider.exceptions?.map(ex => new RegExp(ex, "i"));
|
||||||
"^" +
|
|
||||||
this.escapeRegExp(splitRule[0]).replace(/\\\*/, ".+?") +
|
|
||||||
"$"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!splitRule[1]) {
|
this.rules.push({
|
||||||
this.universalRules.add(paramRule);
|
name,
|
||||||
continue;
|
urlPattern,
|
||||||
}
|
rules,
|
||||||
const hostRule = new RegExp(
|
rawRules,
|
||||||
"^(www\\.)?" +
|
exceptions,
|
||||||
this.escapeRegExp(splitRule[1])
|
});
|
||||||
.replace(/\\\./, "\\.")
|
|
||||||
.replace(/^\\\*\\\./, "(.+?\\.)?")
|
|
||||||
.replace(/\\\*/, ".+?") +
|
|
||||||
"$"
|
|
||||||
);
|
|
||||||
const hostRuleIndex = hostRule.toString();
|
|
||||||
|
|
||||||
this.hostRules.set(hostRuleIndex, hostRule);
|
|
||||||
if (this.rulesByHost.get(hostRuleIndex) == null) {
|
|
||||||
this.rulesByHost.set(hostRuleIndex, new Set());
|
|
||||||
}
|
|
||||||
this.rulesByHost.get(hostRuleIndex).add(paramRule);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
removeParam(rule: string | RegExp, param: string, parent: URLSearchParams) {
|
|
||||||
if (param === rule || rule instanceof RegExp && rule.test(param)) {
|
|
||||||
parent.delete(param);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -106,34 +103,40 @@ export default definePlugin({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cheap way to check if there are any search params
|
// Cheap way to check if there are any search params
|
||||||
if (url.searchParams.entries().next().done) {
|
if (url.searchParams.entries().next().done) return match;
|
||||||
// If there are none, we don't need to modify anything
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check all universal rules
|
// Check rules for each provider that matches
|
||||||
this.universalRules.forEach(rule => {
|
this.rules.forEach(({ urlPattern, exceptions, rawRules, rules }) => {
|
||||||
url.searchParams.forEach((_value, param, parent) => {
|
if (!urlPattern.test(url.href) || exceptions?.some(ex => ex.test(url.href))) return;
|
||||||
this.removeParam(rule, param, parent);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check rules for each hosts that match
|
const toDelete: string[] = [];
|
||||||
this.hostRules.forEach((regex, hostRuleName) => {
|
|
||||||
if (!regex.test(url.hostname)) return;
|
if (rules) {
|
||||||
this.rulesByHost.get(hostRuleName).forEach(rule => {
|
// Add matched params to delete list
|
||||||
url.searchParams.forEach((_value, param, parent) => {
|
url.searchParams.forEach((_, param) => {
|
||||||
this.removeParam(rule, param, parent);
|
if (rules.some(rule => rule.test(param))) {
|
||||||
|
toDelete.push(param);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete matched params from list
|
||||||
|
toDelete.forEach(param => url.searchParams.delete(param));
|
||||||
|
|
||||||
|
// Match and remove any raw rules
|
||||||
|
let cleanedUrl = url.href;
|
||||||
|
rawRules?.forEach(rawRule => {
|
||||||
|
cleanedUrl = cleanedUrl.replace(rawRule, "");
|
||||||
});
|
});
|
||||||
|
url = new URL(cleanedUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
return url.toString();
|
return url.toString();
|
||||||
},
|
},
|
||||||
|
|
||||||
onSend(msg: MessageObject) {
|
cleanMessage(msg: MessageObject) {
|
||||||
// Only run on messages that contain URLs
|
// Only run on messages that contain URLs
|
||||||
if (msg.content.match(/http(s)?:\/\//)) {
|
if (/http(s)?:\/\//.test(msg.content)) {
|
||||||
msg.content = msg.content.replace(
|
msg.content = msg.content.replace(
|
||||||
/(https?:\/\/[^\s<]+[^<.,:;"'>)|\]\s])/g,
|
/(https?:\/\/[^\s<]+[^<.,:;"'>)|\]\s])/g,
|
||||||
match => this.replacer(match)
|
match => this.replacer(match)
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
// Change top right chat toolbar button from the help one to the dev one
|
// Change top right chat toolbar button from the help one to the dev one
|
||||||
{
|
{
|
||||||
find: '"M9 3v18"',
|
find: '?"BACK_FORWARD_NAVIGATION":',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /hasBugReporterAccess:(\i)/,
|
match: /hasBugReporterAccess:(\i)/,
|
||||||
replace: "_hasBugReporterAccess:$1=true"
|
replace: "_hasBugReporterAccess:$1=true"
|
||||||
|
|||||||
@@ -159,7 +159,6 @@ function makeBypassPatches(): Omit<Patch, "plugin"> {
|
|||||||
{ func: "canUseHighVideoUploadQuality", predicate: () => settings.store.enableStreamQualityBypass },
|
{ func: "canUseHighVideoUploadQuality", predicate: () => settings.store.enableStreamQualityBypass },
|
||||||
{ func: "canStreamQuality", predicate: () => settings.store.enableStreamQualityBypass },
|
{ func: "canStreamQuality", predicate: () => settings.store.enableStreamQualityBypass },
|
||||||
{ func: "canUseClientThemes" },
|
{ func: "canUseClientThemes" },
|
||||||
{ func: "canUseCustomNotificationSounds" },
|
|
||||||
{ func: "canUsePremiumAppIcons" }
|
{ func: "canUsePremiumAppIcons" }
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -175,8 +174,8 @@ function makeBypassPatches(): Omit<Patch, "plugin"> {
|
|||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "FakeNitro",
|
name: "FakeNitro",
|
||||||
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN],
|
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN, Devs.sadan],
|
||||||
description: "Allows you to stream in nitro quality, send fake emojis/stickers, use client themes and custom Discord notifications.",
|
description: "Allows you to send fake emojis/stickers, use nitro themes, and stream in nitro quality",
|
||||||
dependencies: ["MessageEventsAPI"],
|
dependencies: ["MessageEventsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
@@ -274,6 +273,14 @@ export default definePlugin({
|
|||||||
replace: (_, rest, backgroundGradientPresetId, originalCall, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme},()=>${originalCall});`
|
replace: (_, rest, backgroundGradientPresetId, originalCall, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme},()=>${originalCall});`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// Allow users to use custom client themes
|
||||||
|
{
|
||||||
|
find: "customUserThemeSettings:{",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=\i=)\(0,\i\.\i\)\(\i\.\i\.TIER_2\)(?=,|;)/g,
|
||||||
|
replace: "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
find: '["strong","em","u","text","inlineCode","s","spoiler"]',
|
find: '["strong","em","u","text","inlineCode","s","spoiler"]',
|
||||||
replacement: [
|
replacement: [
|
||||||
@@ -387,24 +394,15 @@ export default definePlugin({
|
|||||||
if (premiumType !== 2) {
|
if (premiumType !== 2) {
|
||||||
proto.appearance ??= AppearanceSettingsActionCreators.create();
|
proto.appearance ??= AppearanceSettingsActionCreators.create();
|
||||||
|
|
||||||
if (UserSettingsProtoStore.settings.appearance?.theme != null) {
|
const protoStoreAppearenceSettings = UserSettingsProtoStore.settings.appearance;
|
||||||
const appearanceSettingsDummy = AppearanceSettingsActionCreators.create({
|
|
||||||
theme: UserSettingsProtoStore.settings.appearance.theme
|
|
||||||
});
|
|
||||||
|
|
||||||
proto.appearance.theme = appearanceSettingsDummy.theme;
|
const appearanceSettingsOverwrite = AppearanceSettingsActionCreators.create({
|
||||||
}
|
...proto.appearance,
|
||||||
|
theme: protoStoreAppearenceSettings?.theme,
|
||||||
|
clientThemeSettings: protoStoreAppearenceSettings?.clientThemeSettings
|
||||||
|
});
|
||||||
|
|
||||||
if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null) {
|
proto.appearance = appearanceSettingsOverwrite;
|
||||||
const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators.create({
|
|
||||||
backgroundGradientPresetId: {
|
|
||||||
value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
proto.appearance.clientThemeSettings ??= clientThemeSettingsDummy;
|
|
||||||
proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummy.backgroundGradientPresetId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
new Logger("FakeNitro").error(err);
|
new Logger("FakeNitro").error(err);
|
||||||
|
|||||||
@@ -20,18 +20,17 @@ import { definePluginSettings } from "@api/Settings";
|
|||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "@webpack/common";
|
import { useCallback, useEffect, useRef, useState } from "@webpack/common";
|
||||||
|
|
||||||
interface SearchBarComponentProps {
|
interface SearchBarComponentProps {
|
||||||
ref?: React.MutableRefObject<any>;
|
ref?: React.RefObject<any>;
|
||||||
autoFocus: boolean;
|
autoFocus: boolean;
|
||||||
className: string;
|
|
||||||
size: string;
|
size: string;
|
||||||
onChange: (query: string) => void;
|
onChange: (query: string) => void;
|
||||||
onClear: () => void;
|
onClear: () => void;
|
||||||
query: string;
|
query: string;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TSearchBarComponent =
|
type TSearchBarComponent =
|
||||||
@@ -59,9 +58,6 @@ interface Instance {
|
|||||||
forceUpdate: () => void;
|
forceUpdate: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchBarFullRow");
|
|
||||||
|
|
||||||
export const settings = definePluginSettings({
|
export const settings = definePluginSettings({
|
||||||
searchOption: {
|
searchOption: {
|
||||||
type: OptionType.SELECT,
|
type: OptionType.SELECT,
|
||||||
@@ -136,7 +132,7 @@ export default definePlugin({
|
|||||||
|
|
||||||
function SearchBar({ instance, SearchBarComponent }: { instance: Instance; SearchBarComponent: TSearchBarComponent; }) {
|
function SearchBar({ instance, SearchBarComponent }: { instance: Instance; SearchBarComponent: TSearchBarComponent; }) {
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
const ref = useRef<{ containerRef?: React.MutableRefObject<HTMLDivElement>; } | null>(null);
|
const ref = useRef<{ containerRef?: React.RefObject<HTMLDivElement>; } | null>(null);
|
||||||
|
|
||||||
const onChange = useCallback((searchQuery: string) => {
|
const onChange = useCallback((searchQuery: string) => {
|
||||||
setQuery(searchQuery);
|
setQuery(searchQuery);
|
||||||
@@ -152,7 +148,7 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc
|
|||||||
|
|
||||||
// scroll back to top
|
// scroll back to top
|
||||||
ref.current?.containerRef?.current
|
ref.current?.containerRef?.current
|
||||||
.closest("#gif-picker-tab-panel")
|
?.closest("#gif-picker-tab-panel")
|
||||||
?.querySelector("[class|=\"content\"]")
|
?.querySelector("[class|=\"content\"]")
|
||||||
?.firstElementChild?.scrollTo(0, 0);
|
?.firstElementChild?.scrollTo(0, 0);
|
||||||
|
|
||||||
@@ -181,8 +177,8 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc
|
|||||||
<SearchBarComponent
|
<SearchBarComponent
|
||||||
ref={ref}
|
ref={ref}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className={containerClasses.searchBar}
|
|
||||||
size="md"
|
size="md"
|
||||||
|
className=""
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onClear={() => {
|
onClear={() => {
|
||||||
setQuery("");
|
setQuery("");
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
[class^="panels"] [class^="avatarWrapper"] {
|
/* make gap smaller since we're adding an extra button */
|
||||||
min-width: 88px;
|
[class^="panels"] [class^="buttons"] {
|
||||||
}
|
gap: 4px;
|
||||||
|
}
|
||||||
@@ -47,6 +47,7 @@ interface Activity {
|
|||||||
buttons?: Array<string>;
|
buttons?: Array<string>;
|
||||||
name: string;
|
name: string;
|
||||||
application_id: string;
|
application_id: string;
|
||||||
|
status_display_type?: number;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
button_urls?: Array<string>;
|
button_urls?: Array<string>;
|
||||||
};
|
};
|
||||||
@@ -134,6 +135,25 @@ const settings = definePluginSettings({
|
|||||||
type: OptionType.STRING,
|
type: OptionType.STRING,
|
||||||
default: "some music",
|
default: "some music",
|
||||||
},
|
},
|
||||||
|
statusDisplayType: {
|
||||||
|
description: "Show the track / artist name in the member list",
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Don't show (shows generic listening message)",
|
||||||
|
value: "off",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Show artist name",
|
||||||
|
value: "artist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Show track name",
|
||||||
|
value: "track"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
nameFormat: {
|
nameFormat: {
|
||||||
description: "Show name of song and artist in status name",
|
description: "Show name of song and artist in status name",
|
||||||
type: OptionType.SELECT,
|
type: OptionType.SELECT,
|
||||||
@@ -346,6 +366,11 @@ export default definePlugin({
|
|||||||
|
|
||||||
details: trackData.name,
|
details: trackData.name,
|
||||||
state: trackData.artist,
|
state: trackData.artist,
|
||||||
|
status_display_type: {
|
||||||
|
"off": 0,
|
||||||
|
"artist": 1,
|
||||||
|
"track": 2
|
||||||
|
}[settings.store.statusDisplayType],
|
||||||
assets,
|
assets,
|
||||||
|
|
||||||
buttons: buttons.length ? buttons.map(v => v.label) : undefined,
|
buttons: buttons.length ? buttons.map(v => v.label) : undefined,
|
||||||
|
|||||||
17
src/plugins/memberCount/CircleIcon.tsx
Normal file
17
src/plugins/memberCount/CircleIcon.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function CircleIcon({ className }: { className?: string; }) {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" className={className}>
|
||||||
|
<circle
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="8"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,16 +6,38 @@
|
|||||||
|
|
||||||
import { getCurrentChannel } from "@utils/discord";
|
import { getCurrentChannel } from "@utils/discord";
|
||||||
import { isObjectEmpty } from "@utils/misc";
|
import { isObjectEmpty } from "@utils/misc";
|
||||||
import { SelectedChannelStore, Tooltip, useEffect, useStateFromStores } from "@webpack/common";
|
import { ChannelStore, PermissionsBits, PermissionStore, SelectedChannelStore, Tooltip, useEffect, useStateFromStores, VoiceStateStore } from "@webpack/common";
|
||||||
|
|
||||||
import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat, ThreadMemberListStore } from ".";
|
import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat, settings, ThreadMemberListStore } from ".";
|
||||||
|
import { CircleIcon } from "./CircleIcon";
|
||||||
import { OnlineMemberCountStore } from "./OnlineMemberCountStore";
|
import { OnlineMemberCountStore } from "./OnlineMemberCountStore";
|
||||||
|
import { VoiceIcon } from "./VoiceIcon";
|
||||||
|
|
||||||
export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) {
|
export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) {
|
||||||
const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
|
const { voiceActivity } = settings.use(["voiceActivity"]);
|
||||||
|
const includeVoice = voiceActivity && !isTooltip;
|
||||||
|
|
||||||
|
const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
|
||||||
const guildId = isTooltip ? tooltipGuildId! : currentChannel?.guild_id;
|
const guildId = isTooltip ? tooltipGuildId! : currentChannel?.guild_id;
|
||||||
|
|
||||||
|
const voiceActivityCount = useStateFromStores(
|
||||||
|
[VoiceStateStore],
|
||||||
|
() => {
|
||||||
|
if (!includeVoice) return 0;
|
||||||
|
|
||||||
|
const voiceStates = VoiceStateStore.getVoiceStates(guildId);
|
||||||
|
if (!voiceStates) return 0;
|
||||||
|
|
||||||
|
return Object.values(voiceStates)
|
||||||
|
.filter(({ channelId }) => {
|
||||||
|
if (!channelId) return false;
|
||||||
|
const channel = ChannelStore.getChannel(channelId);
|
||||||
|
return channel && PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel);
|
||||||
|
})
|
||||||
|
.length;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const totalCount = useStateFromStores(
|
const totalCount = useStateFromStores(
|
||||||
[GuildMemberCountStore],
|
[GuildMemberCountStore],
|
||||||
() => GuildMemberCountStore.getMemberCount(guildId)
|
() => GuildMemberCountStore.getMemberCount(guildId)
|
||||||
@@ -51,26 +73,37 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t
|
|||||||
if (totalCount == null)
|
if (totalCount == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
const formattedVoiceCount = numberFormat(voiceActivityCount ?? 0);
|
||||||
const formattedOnlineCount = onlineCount != null ? numberFormat(onlineCount) : "?";
|
const formattedOnlineCount = onlineCount != null ? numberFormat(onlineCount) : "?";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cl("widget", { tooltip: isTooltip, "member-list": !isTooltip })}>
|
<div className={cl("widget", { tooltip: isTooltip, "member-list": !isTooltip })}>
|
||||||
<Tooltip text={`${formattedOnlineCount} online in this channel`} position="bottom">
|
<Tooltip text={`${formattedOnlineCount} online in this channel`} position="bottom">
|
||||||
{props => (
|
{props => (
|
||||||
<div {...props}>
|
<div {...props} className={cl("container")}>
|
||||||
<span className={cl("online-dot")} />
|
<CircleIcon className={cl("online-count")} />
|
||||||
<span className={cl("online")}>{formattedOnlineCount}</span>
|
<span className={cl("online")}>{formattedOnlineCount}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip text={`${numberFormat(totalCount)} total server members`} position="bottom">
|
<Tooltip text={`${numberFormat(totalCount)} total server members`} position="bottom">
|
||||||
{props => (
|
{props => (
|
||||||
<div {...props}>
|
<div {...props} className={cl("container")}>
|
||||||
<span className={cl("total-dot")} />
|
<CircleIcon className={cl("total-count")} />
|
||||||
<span className={cl("total")}>{numberFormat(totalCount)}</span>
|
<span className={cl("total")}>{numberFormat(totalCount)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{includeVoice && voiceActivityCount > 0 &&
|
||||||
|
<Tooltip text={`${formattedVoiceCount} members in voice`} position="bottom">
|
||||||
|
{props => (
|
||||||
|
<div {...props} className={cl("container")}>
|
||||||
|
<VoiceIcon className={cl("voice-icon")} />
|
||||||
|
<span className={cl("voice")}>{formattedVoiceCount}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/plugins/memberCount/VoiceIcon.tsx
Normal file
14
src/plugins/memberCount/VoiceIcon.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function VoiceIcon({ className }: { className?: string; }) {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 32 32" fill="currentColor" className={className}>
|
||||||
|
<path d="M15.6668 3C14.2523 3 12.8958 3.5619 11.8956 4.5621C10.8954 5.56229 10.3335 6.91884 10.3335 8.33333V13.6666C10.3335 15.0811 10.8954 16.4378 11.8956 17.438C12.8958 18.4381 14.2523 19 15.6668 19C17.0813 19 18.4378 18.4381 19.438 17.438C20.4382 16.4378 21.0001 15.0811 21.0001 13.6666V8.33333C21.0001 6.91884 20.4382 5.56229 19.438 4.5621C18.4378 3.5619 17.0813 3 15.6668 3Z" />
|
||||||
|
<path d="M7.66667 13.6666C7.66667 13.313 7.52619 12.9739 7.27614 12.7238C7.02609 12.4738 6.68695 12.3333 6.33333 12.3333C5.97971 12.3333 5.64057 12.4738 5.39052 12.7238C5.14047 12.9739 5 13.313 5 13.6666C4.99911 16.2653 5.94692 18.7749 7.66545 20.7243C9.38399 22.6736 11.7551 23.9285 14.3334 24.2533V27H11.6667C11.3131 27 10.9739 27.1404 10.7239 27.3905C10.4738 27.6405 10.3334 27.9797 10.3334 28.3333C10.3334 28.6869 10.4738 29.0261 10.7239 29.2761C10.9739 29.5262 11.3131 29.6666 11.6667 29.6666H19.6667C20.0203 29.6666 20.3595 29.5262 20.6095 29.2761C20.8596 29.0261 21 28.6869 21 28.3333C21 27.9797 20.8596 27.6405 20.6095 27.3905C20.3595 27.1404 20.0203 27 19.6667 27H17V24.2533C19.5783 23.9285 21.9494 22.6736 23.6679 20.7243C25.3864 18.7749 26.3343 16.2653 26.3334 13.6666C26.3334 13.313 26.1929 12.9739 25.9428 12.7238C25.6928 12.4738 25.3536 12.3333 25 12.3333C24.6464 12.3333 24.3073 12.4738 24.0572 12.7238C23.8072 12.9739 23.6667 13.313 23.6667 13.6666C23.6667 15.7884 22.8238 17.8232 21.3235 19.3235C19.8233 20.8238 17.7884 21.6666 15.6667 21.6666C13.545 21.6666 11.5101 20.8238 10.0098 19.3235C8.50952 17.8232 7.66667 15.7884 7.66667 13.6666Z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -36,19 +36,23 @@ export const ThreadMemberListStore = findStoreLazy("ThreadMemberListStore") as F
|
|||||||
getMemberListSections(channelId?: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; };
|
getMemberListSections(channelId?: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const settings = definePluginSettings({
|
||||||
const settings = definePluginSettings({
|
|
||||||
toolTip: {
|
toolTip: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "If the member count should be displayed on the server tooltip",
|
description: "Show member count on the server tooltip",
|
||||||
default: true,
|
default: true,
|
||||||
restartNeeded: true
|
restartNeeded: true
|
||||||
},
|
},
|
||||||
memberList: {
|
memberList: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "If the member count should be displayed on the member list",
|
description: "Show member count in the member list",
|
||||||
default: true,
|
default: true,
|
||||||
restartNeeded: true
|
restartNeeded: true
|
||||||
|
},
|
||||||
|
voiceActivity: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Show voice activity with member count in the member list",
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,8 +62,8 @@ export const cl = classNameFactory("vc-membercount-");
|
|||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MemberCount",
|
name: "MemberCount",
|
||||||
description: "Shows the amount of online & total members in the server member list and tooltip",
|
description: "Shows the number of online members, total members, and users in voice channels on the server — in the member list and tooltip.",
|
||||||
authors: [Devs.Ven, Devs.Commandtechno],
|
authors: [Devs.Ven, Devs.Commandtechno, Devs.Apexo],
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
@@ -82,6 +86,6 @@ export default definePlugin({
|
|||||||
predicate: () => settings.store.toolTip
|
predicate: () => settings.store.toolTip
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
render: ErrorBoundary.wrap(MemberCount, { noop: true }),
|
render: ErrorBoundary.wrap(() => <MemberCount />, { noop: true }),
|
||||||
renderTooltip: ErrorBoundary.wrap(guild => <MemberCount isTooltip tooltipGuildId={guild.id} />, { noop: true })
|
renderTooltip: ErrorBoundary.wrap(guild => <MemberCount isTooltip tooltipGuildId={guild.id} />, { noop: true })
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
.vc-membercount-widget {
|
.vc-membercount-widget {
|
||||||
|
gap: 0.85em;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
|
|
||||||
--color-online: var(--green-360);
|
--color-online: var(--green-360);
|
||||||
--color-total: var(--primary-400);
|
--color-total: var(--primary-400);
|
||||||
|
--color-voice: var(--primary-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-membercount-tooltip {
|
.vc-membercount-tooltip {
|
||||||
@@ -13,8 +15,16 @@
|
|||||||
|
|
||||||
.vc-membercount-member-list {
|
.vc-membercount-member-list {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
padding-inline: 1em;
|
padding-inline: 1em;
|
||||||
|
line-height: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-membercount-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-membercount-online {
|
.vc-membercount-online {
|
||||||
@@ -25,20 +35,26 @@
|
|||||||
color: var(--color-total);
|
color: var(--color-total);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-membercount-online-dot {
|
.vc-membercount-voice {
|
||||||
background-color: var(--color-online);
|
color: var(--color-voice);
|
||||||
display: inline-block;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-membercount-total-dot {
|
.vc-membercount-online-count {
|
||||||
display: inline-block;
|
fill: var(--status-online);
|
||||||
width: 6px;
|
width: 18px;
|
||||||
height: 6px;
|
height: 18px;
|
||||||
border-radius: 50%;
|
|
||||||
border: 3px solid var(--color-total);
|
|
||||||
margin: 0 0.5em 0 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-membercount-total-count {
|
||||||
|
fill: none;
|
||||||
|
stroke: var(--status-offline);
|
||||||
|
stroke-width: 4px;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-membercount-voice-icon {
|
||||||
|
color: var(--color-voice);
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
@@ -98,7 +98,7 @@ export default definePlugin({
|
|||||||
if (!isNonNullish(nonce)) return null;
|
if (!isNonNullish(nonce)) return null;
|
||||||
|
|
||||||
// Bots basically never send a nonce, and if someone does do it then it's usually not a snowflake
|
// Bots basically never send a nonce, and if someone does do it then it's usually not a snowflake
|
||||||
if (message.bot) return null;
|
if (message.author.bot) return null;
|
||||||
|
|
||||||
let isDiscordKotlin = false;
|
let isDiscordKotlin = false;
|
||||||
let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds
|
let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds
|
||||||
|
|||||||
@@ -128,8 +128,10 @@ const HTMLReact = (data, _1, _2, _3) => {
|
|||||||
console.error(data.content);
|
console.error(data.content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react/no-children-prop
|
return (
|
||||||
return <ShadowDomComponent className="HTMLMessageContent" children={{ __html: trueContent }} />;
|
// eslint-disable-next-line react/no-children-prop
|
||||||
|
<ShadowDomComponent className="HTMLMessageContentWrapper" children={{ __html: `<span class="HTMLMessageContent">${trueContent}</span>` }} />
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function escapeRegex(str: string): string {
|
function escapeRegex(str: string): string {
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ export default definePlugin({
|
|||||||
replacement: {
|
replacement: {
|
||||||
// The two groups inside the first group grab the minified names of the variables,
|
// The two groups inside the first group grab the minified names of the variables,
|
||||||
// they are then referenced later to find unviewedTrialCount + unviewedDiscountCount.
|
// they are then referenced later to find unviewedTrialCount + unviewedDiscountCount.
|
||||||
match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,300}\i=)\1\+\2/,
|
match: /(\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.+?)\2\+\3/,
|
||||||
replace: "0"
|
replace: (_, rest) => `${rest}0`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { definePluginSettings } from "@api/Settings";
|
|||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import type { Message } from "@vencord/discord-types";
|
import type { Message } from "@vencord/discord-types";
|
||||||
|
import { ChannelStore, GuildMemberStore } from "@webpack/common";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
userList: {
|
userList: {
|
||||||
@@ -28,16 +29,22 @@ const settings = definePluginSettings({
|
|||||||
type: OptionType.STRING,
|
type: OptionType.STRING,
|
||||||
default: "1234567890123445,1234567890123445",
|
default: "1234567890123445,1234567890123445",
|
||||||
},
|
},
|
||||||
|
roleList: {
|
||||||
|
description:
|
||||||
|
"List of roles to allow or exempt pings for (separated by commas or spaces)",
|
||||||
|
type: OptionType.STRING,
|
||||||
|
default: "1234567890123445,1234567890123445",
|
||||||
|
},
|
||||||
shouldPingListed: {
|
shouldPingListed: {
|
||||||
description: "Behaviour",
|
description: "Behaviour",
|
||||||
type: OptionType.SELECT,
|
type: OptionType.SELECT,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: "Do not ping the listed users",
|
label: "Do not ping the listed users / roles",
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Only ping the listed users",
|
label: "Only ping the listed users / roles",
|
||||||
value: true,
|
value: true,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
@@ -57,7 +64,14 @@ export default definePlugin({
|
|||||||
settings,
|
settings,
|
||||||
|
|
||||||
shouldMention(message: Message, isHoldingShift: boolean) {
|
shouldMention(message: Message, isHoldingShift: boolean) {
|
||||||
const isListed = settings.store.userList.includes(message.author.id);
|
let isListed = settings.store.userList.includes(message.author.id);
|
||||||
|
|
||||||
|
const channel = ChannelStore.getChannel(message.channel_id);
|
||||||
|
if (channel?.guild_id && !isListed) {
|
||||||
|
const roles = GuildMemberStore.getMember(channel.guild_id, message.author.id)?.roles;
|
||||||
|
isListed = !!roles && roles.some(role => settings.store.roleList.includes(role));
|
||||||
|
}
|
||||||
|
|
||||||
const isExempt = settings.store.shouldPingListed ? isListed : !isListed;
|
const isExempt = settings.store.shouldPingListed ? isListed : !isListed;
|
||||||
return settings.store.inverseShiftReply ? isHoldingShift !== isExempt : !isHoldingShift && isExempt;
|
return settings.store.inverseShiftReply ? isHoldingShift !== isExempt : !isHoldingShift && isExempt;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,17 +38,21 @@ export default definePlugin({
|
|||||||
description: "If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit",
|
description: "If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit",
|
||||||
authors: [Devs.ProffDea],
|
authors: [Devs.ProffDea],
|
||||||
settings,
|
settings,
|
||||||
patches: [{
|
patches: [
|
||||||
find: ".getDesktopType()===",
|
|
||||||
replacement: [{
|
|
||||||
match: /(\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\)/,
|
|
||||||
replace: "$&if(!$self.isPrivateChannelRead(arguments[0]?.message))return;else "
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
match: /sound:(\i\?\i:void 0,soundpack:\i,volume:\i,onClick)/,
|
find: ".getDesktopType()===",
|
||||||
replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
|
replacement: [
|
||||||
}]
|
{
|
||||||
}],
|
match: /(\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\)/,
|
||||||
|
replace: "$&if(!$self.isPrivateChannelRead(arguments[0]?.message))return;else "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /sound:(\i\?\i:void 0,volume:\i,onClick)/,
|
||||||
|
replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
isPrivateChannelRead(message: MessageJSON) {
|
isPrivateChannelRead(message: MessageJSON) {
|
||||||
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
|
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
|||||||
import { generateId, sendBotMessage } from "@api/Commands";
|
import { generateId, sendBotMessage } from "@api/Commands";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { StartAt } from "@utils/types";
|
import definePlugin, { StartAt } from "@utils/types";
|
||||||
import { MessageAttachment } from "@vencord/discord-types";
|
import { CloudUpload, MessageAttachment } from "@vencord/discord-types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common";
|
import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common";
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ const getImageBox = (url: string): Promise<{ width: number, height: number; } |
|
|||||||
const getAttachments = async (channelId: string) =>
|
const getAttachments = async (channelId: string) =>
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
UploadStore.getUploads(channelId, DraftType.ChannelMessage)
|
UploadStore.getUploads(channelId, DraftType.ChannelMessage)
|
||||||
.map(async (upload: any) => {
|
.map(async (upload: CloudUpload) => {
|
||||||
const { isImage, filename, spoiler, item: { file } } = upload;
|
const { isImage, filename, spoiler, item: { file } } = upload;
|
||||||
const url = URL.createObjectURL(file);
|
const url = URL.createObjectURL(file);
|
||||||
const attachment: MessageAttachment = {
|
const attachment: MessageAttachment = {
|
||||||
@@ -53,7 +53,7 @@ const getAttachments = async (channelId: string) =>
|
|||||||
filename: spoiler ? "SPOILER_" + filename : filename,
|
filename: spoiler ? "SPOILER_" + filename : filename,
|
||||||
// weird eh? if i give it the normal content type the preview doenst work
|
// weird eh? if i give it the normal content type the preview doenst work
|
||||||
content_type: undefined,
|
content_type: undefined,
|
||||||
size: await upload.getSize(),
|
size: upload.getSize(),
|
||||||
spoiler,
|
spoiler,
|
||||||
// discord adds query params to the url, so we need to add a hash to prevent that
|
// discord adds query params to the url, so we need to add a hash to prevent that
|
||||||
url: url + "#",
|
url: url + "#",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const Engines = {
|
|||||||
Yandex: "https://yandex.com/images/search?rpt=imageview&url=",
|
Yandex: "https://yandex.com/images/search?rpt=imageview&url=",
|
||||||
SauceNAO: "https://saucenao.com/search.php?url=",
|
SauceNAO: "https://saucenao.com/search.php?url=",
|
||||||
IQDB: "https://iqdb.org/?url=",
|
IQDB: "https://iqdb.org/?url=",
|
||||||
|
Bing: "https://www.bing.com/images/search?view=detailv2&iss=sbi&q=imgurl:",
|
||||||
TinEye: "https://www.tineye.com/search?url=",
|
TinEye: "https://www.tineye.com/search?url=",
|
||||||
ImgOps: "https://imgops.com/start?url="
|
ImgOps: "https://imgops.com/start?url="
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -34,7 +34,13 @@ export default definePlugin({
|
|||||||
|
|
||||||
if (settings.store.serverBlockList.includes(guildId)) return;
|
if (settings.store.serverBlockList.includes(guildId)) return;
|
||||||
const res = await fetch(`https://api.zoid.one/nexulien/servercss/${guildId}`);
|
const res = await fetch(`https://api.zoid.one/nexulien/servercss/${guildId}`);
|
||||||
if (!res.ok) return;
|
if (!res.ok) {
|
||||||
|
const styleEl = document.getElementById("server-styling");
|
||||||
|
if (styleEl) {
|
||||||
|
styleEl.remove();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const css = await res.text();
|
const css = await res.text();
|
||||||
let styleEl = document.getElementById("server-styling");
|
let styleEl = document.getElementById("server-styling");
|
||||||
if (!styleEl) {
|
if (!styleEl) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
|||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { Channel, Message, User } from "@vencord/discord-types";
|
import { Channel, Message, User } from "@vencord/discord-types";
|
||||||
import { RelationshipStore } from "@webpack/common";
|
import { RelationshipStore, StreamerModeStore } from "@webpack/common";
|
||||||
|
|
||||||
interface UsernameProps {
|
interface UsernameProps {
|
||||||
author: { nick: string; authorId: string; };
|
author: { nick: string; authorId: string; };
|
||||||
@@ -74,7 +74,9 @@ export default definePlugin({
|
|||||||
const { mode, friendNicknames, displayNames, inReplies } = settings.store;
|
const { mode, friendNicknames, displayNames, inReplies } = settings.store;
|
||||||
|
|
||||||
const user = userOverride ?? message.author;
|
const user = userOverride ?? message.author;
|
||||||
let { username } = user;
|
let username = StreamerModeStore.enabled
|
||||||
|
? user.username[0] + "…"
|
||||||
|
: user.username;
|
||||||
|
|
||||||
if (displayNames)
|
if (displayNames)
|
||||||
username = user.globalName || username;
|
username = user.globalName || username;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import "./spotifyStyles.css";
|
|||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
|
import { CopyIcon, ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
|
||||||
import { debounce } from "@shared/debounce";
|
import { debounce } from "@shared/debounce";
|
||||||
import { openImageModal } from "@utils/discord";
|
import { openImageModal } from "@utils/discord";
|
||||||
import { classes, copyWithToast } from "@utils/misc";
|
import { classes, copyWithToast } from "@utils/misc";
|
||||||
@@ -76,27 +76,28 @@ function Button(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CopyContextMenu({ name, path }: { name: string; path: string; }) {
|
function CopyContextMenu({ name, type, path }: { type: string; name: string; path: string; }) {
|
||||||
const copyId = `spotify-copy-${name}`;
|
|
||||||
const openId = `spotify-open-${name}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu.Menu
|
<Menu.Menu
|
||||||
navId={`spotify-${name}-menu`}
|
navId="vc-spotify-menu"
|
||||||
onClose={() => FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
|
onClose={ContextMenuApi.closeContextMenu}
|
||||||
aria-label={`Spotify ${name} Menu`}
|
aria-label={`Spotify ${type} Menu`}
|
||||||
>
|
>
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
key={copyId}
|
id="vc-spotify-copy-name"
|
||||||
id={copyId}
|
label={`Copy ${type} Name`}
|
||||||
label={`Copy ${name} Link`}
|
action={() => copyWithToast(name)}
|
||||||
|
icon={CopyIcon}
|
||||||
|
/>
|
||||||
|
<Menu.MenuItem
|
||||||
|
id="vc-spotify-copy-link"
|
||||||
|
label={`Copy ${type} Link`}
|
||||||
action={() => copyWithToast("https://open.spotify.com" + path)}
|
action={() => copyWithToast("https://open.spotify.com" + path)}
|
||||||
icon={LinkIcon}
|
icon={LinkIcon}
|
||||||
/>
|
/>
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
key={openId}
|
id="vc-spotify-open"
|
||||||
id={openId}
|
label={`Open ${type} in Spotify`}
|
||||||
label={`Open ${name} in Spotify`}
|
|
||||||
action={() => SpotifyStore.openExternal(path)}
|
action={() => SpotifyStore.openExternal(path)}
|
||||||
icon={OpenExternalIcon}
|
icon={OpenExternalIcon}
|
||||||
/>
|
/>
|
||||||
@@ -104,11 +105,6 @@ function CopyContextMenu({ name, path }: { name: string; path: string; }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeContextMenu(name: string, path: string) {
|
|
||||||
return (e: React.MouseEvent<HTMLElement, MouseEvent>) =>
|
|
||||||
ContextMenuApi.openContextMenu(e, () => <CopyContextMenu name={name} path={path} />);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Controls() {
|
function Controls() {
|
||||||
const [isPlaying, shuffle, repeat] = useStateFromStores(
|
const [isPlaying, shuffle, repeat] = useStateFromStores(
|
||||||
[SpotifyStore],
|
[SpotifyStore],
|
||||||
@@ -259,13 +255,14 @@ function AlbumContextMenu({ track }: { track: Track; }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeLinkProps(name: string, condition: unknown, path: string) {
|
function makeLinkProps(type: "Song" | "Artist" | "Album", condition: unknown, name: string, path: string) {
|
||||||
if (!condition) return {};
|
if (!condition) return {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
role: "link",
|
role: "link",
|
||||||
onClick: () => SpotifyStore.openExternal(path),
|
onClick: () => SpotifyStore.openExternal(path),
|
||||||
onContextMenu: makeContextMenu(name, path)
|
onContextMenu: e =>
|
||||||
|
ContextMenuApi.openContextMenu(e, () => <CopyContextMenu type={type} name={name} path={path} />)
|
||||||
} satisfies React.HTMLAttributes<HTMLElement>;
|
} satisfies React.HTMLAttributes<HTMLElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +303,7 @@ function Info({ track }: { track: Track; }) {
|
|||||||
id={cl("song-title")}
|
id={cl("song-title")}
|
||||||
className={cl("ellipoverflow")}
|
className={cl("ellipoverflow")}
|
||||||
title={track.name}
|
title={track.name}
|
||||||
{...makeLinkProps("Song", track.id, `/track/${track.id}`)}
|
{...makeLinkProps("Song", track.id, track.name, `/track/${track.id}`)}
|
||||||
>
|
>
|
||||||
{track.name}
|
{track.name}
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
@@ -319,7 +316,7 @@ function Info({ track }: { track: Track; }) {
|
|||||||
className={cl("artist")}
|
className={cl("artist")}
|
||||||
style={{ fontSize: "inherit" }}
|
style={{ fontSize: "inherit" }}
|
||||||
title={a.name}
|
title={a.name}
|
||||||
{...makeLinkProps("Artist", a.id, `/artist/${a.id}`)}
|
{...makeLinkProps("Artist", a.id, a.name, `/artist/${a.id}`)}
|
||||||
>
|
>
|
||||||
{a.name}
|
{a.name}
|
||||||
</span>
|
</span>
|
||||||
@@ -336,7 +333,7 @@ function Info({ track }: { track: Track; }) {
|
|||||||
className={cl("album")}
|
className={cl("album")}
|
||||||
style={{ fontSize: "inherit" }}
|
style={{ fontSize: "inherit" }}
|
||||||
title={track.album.name}
|
title={track.album.name}
|
||||||
{...makeLinkProps("Album", track.album.id, `/album/${track.album.id}`)}
|
{...makeLinkProps("Album", track.album.id, track.album.name, `/album/${track.album.id}`)}
|
||||||
>
|
>
|
||||||
{track.album.name}
|
{track.album.name}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { classNameFactory } from "@api/Styles";
|
|||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { Channel } from "@vencord/discord-types";
|
import { Channel } from "@vencord/discord-types";
|
||||||
import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy, mapMangledModuleLazy } from "@webpack";
|
import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, mapMangledModuleLazy } from "@webpack";
|
||||||
import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores } from "@webpack/common";
|
import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common";
|
||||||
|
|
||||||
const cl = classNameFactory("vc-uvs-");
|
const cl = classNameFactory("vc-uvs-");
|
||||||
|
|
||||||
@@ -18,7 +18,6 @@ const { useChannelName } = mapMangledModuleLazy("#{intl::GROUP_DM_ALONE}", {
|
|||||||
useChannelName: filters.byCode("()=>null==")
|
useChannelName: filters.byCode("()=>null==")
|
||||||
});
|
});
|
||||||
const getDMChannelIcon = findByCodeLazy(".getChannelIconURL({");
|
const getDMChannelIcon = findByCodeLazy(".getChannelIconURL({");
|
||||||
const VoiceStateStore = findStoreLazy("VoiceStateStore");
|
|
||||||
|
|
||||||
const Avatar = findComponentByCodeLazy(".status)/2):0");
|
const Avatar = findComponentByCodeLazy(".status)/2):0");
|
||||||
const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL");
|
const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL");
|
||||||
@@ -84,7 +83,7 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) {
|
|||||||
const voiceStates = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStatesForChannel(channel.id));
|
const voiceStates = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStatesForChannel(channel.id));
|
||||||
|
|
||||||
const users = useMemo(
|
const users = useMemo(
|
||||||
() => Object.values<any>(voiceStates).map(voiceState => UserStore.getUser(voiceState.userId)).filter(user => user != null),
|
() => Object.values(voiceStates).map(voiceState => UserStore.getUser(voiceState.userId)).filter(user => user != null),
|
||||||
[voiceStates]
|
[voiceStates]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -139,7 +138,7 @@ export interface VoiceChannelIndicatorProps {
|
|||||||
const clickTimers = {} as Record<string, any>;
|
const clickTimers = {} as Record<string, any>;
|
||||||
|
|
||||||
export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
|
export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
|
||||||
const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined);
|
const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId);
|
||||||
|
|
||||||
const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId);
|
const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId);
|
||||||
if (channel == null) return null;
|
if (channel == null) return null;
|
||||||
|
|||||||
@@ -22,13 +22,12 @@ import { Logger } from "@utils/Logger";
|
|||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { wordsToTitle } from "@utils/text";
|
import { wordsToTitle } from "@utils/text";
|
||||||
import definePlugin, { ReporterTestable } from "@utils/types";
|
import definePlugin, { ReporterTestable } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore, VoiceStateStore } from "@webpack/common";
|
||||||
import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common";
|
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
import { getCurrentVoice, settings } from "./settings";
|
import { getCurrentVoice, settings } from "./settings";
|
||||||
|
|
||||||
interface VoiceState {
|
interface VoiceStateChangeEvent {
|
||||||
userId: string;
|
userId: string;
|
||||||
channelId?: string;
|
channelId?: string;
|
||||||
oldChannelId?: string;
|
oldChannelId?: string;
|
||||||
@@ -38,8 +37,6 @@ interface VoiceState {
|
|||||||
selfMute: boolean;
|
selfMute: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VoiceStateStore = findByPropsLazy("getVoiceStatesForChannel", "getCurrentClientVoiceChannelId");
|
|
||||||
|
|
||||||
// Mute/Deaf for other people than you is commented out, because otherwise someone can spam it and it will be annoying
|
// Mute/Deaf for other people than you is commented out, because otherwise someone can spam it and it will be annoying
|
||||||
// Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would
|
// Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would
|
||||||
// not say the second mute, which would lead you to believe they're unmuted
|
// not say the second mute, which would lead you to believe they're unmuted
|
||||||
@@ -88,7 +85,7 @@ let StatusMap = {} as Record<string, {
|
|||||||
// for some ungodly reason
|
// for some ungodly reason
|
||||||
let myLastChannelId: string | undefined;
|
let myLastChannelId: string | undefined;
|
||||||
|
|
||||||
function getTypeAndChannelId({ channelId, oldChannelId }: VoiceState, isMe: boolean) {
|
function getTypeAndChannelId({ channelId, oldChannelId }: VoiceStateChangeEvent, isMe: boolean) {
|
||||||
if (isMe && channelId !== myLastChannelId) {
|
if (isMe && channelId !== myLastChannelId) {
|
||||||
oldChannelId = myLastChannelId;
|
oldChannelId = myLastChannelId;
|
||||||
myLastChannelId = channelId;
|
myLastChannelId = channelId;
|
||||||
@@ -163,7 +160,7 @@ export default definePlugin({
|
|||||||
settings,
|
settings,
|
||||||
|
|
||||||
flux: {
|
flux: {
|
||||||
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
|
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceStateChangeEvent[]; }) {
|
||||||
const myGuildId = SelectedGuildStore.getGuildId();
|
const myGuildId = SelectedGuildStore.getGuildId();
|
||||||
const myChanId = SelectedChannelStore.getVoiceChannelId();
|
const myChanId = SelectedChannelStore.getVoiceChannelId();
|
||||||
const myId = UserStore.getCurrentUser().id;
|
const myId = UserStore.getCurrentUser().id;
|
||||||
@@ -195,7 +192,7 @@ export default definePlugin({
|
|||||||
|
|
||||||
AUDIO_TOGGLE_SELF_MUTE() {
|
AUDIO_TOGGLE_SELF_MUTE() {
|
||||||
const chanId = SelectedChannelStore.getVoiceChannelId()!;
|
const chanId = SelectedChannelStore.getVoiceChannelId()!;
|
||||||
const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState;
|
const s = VoiceStateStore.getVoiceStateForChannel(chanId);
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
|
|
||||||
const event = s.mute || s.selfMute ? "unmute" : "mute";
|
const event = s.mute || s.selfMute ? "unmute" : "mute";
|
||||||
@@ -204,7 +201,7 @@ export default definePlugin({
|
|||||||
|
|
||||||
AUDIO_TOGGLE_SELF_DEAF() {
|
AUDIO_TOGGLE_SELF_DEAF() {
|
||||||
const chanId = SelectedChannelStore.getVoiceChannelId()!;
|
const chanId = SelectedChannelStore.getVoiceChannelId()!;
|
||||||
const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState;
|
const s = VoiceStateStore.getVoiceStateForChannel(chanId);
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
|
|
||||||
const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen";
|
const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen";
|
||||||
|
|||||||
@@ -129,9 +129,10 @@ export default definePlugin({
|
|||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '"M9 3v18"',
|
find: '?"BACK_FORWARD_NAVIGATION":',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /focusSectionProps:"HELP".{0,20},className:(\i\.button)\}\),/,
|
// TODO: (?:\.button) is for stable compat and should be removed soon:tm:
|
||||||
|
match: /focusSectionProps:"HELP".{0,20},className:(\i(?:\.button)?)\}\),/,
|
||||||
replace: "$& $self.renderVencordPopoutButton($1),"
|
replace: "$& $self.renderVencordPopoutButton($1),"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModa
|
|||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { chooseFile } from "@utils/web";
|
import { chooseFile } from "@utils/web";
|
||||||
|
import { CloudUpload as TCloudUpload } from "@vencord/discord-types";
|
||||||
|
import { CloudUploadPlatform } from "@vencord/discord-types/enums";
|
||||||
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
||||||
import { Button, Card, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
|
import { Button, Card, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
|
||||||
import { ComponentType } from "react";
|
import { ComponentType } from "react";
|
||||||
@@ -37,7 +39,7 @@ import { cl } from "./utils";
|
|||||||
import { VoicePreview } from "./VoicePreview";
|
import { VoicePreview } from "./VoicePreview";
|
||||||
import { VoiceRecorderWeb } from "./WebRecorder";
|
import { VoiceRecorderWeb } from "./WebRecorder";
|
||||||
|
|
||||||
const CloudUpload = findLazy(m => m.prototype?.trackUploadFinished);
|
const CloudUpload: typeof TCloudUpload = findLazy(m => m.prototype?.trackUploadFinished);
|
||||||
const PendingReplyStore = findStoreLazy("PendingReplyStore");
|
const PendingReplyStore = findStoreLazy("PendingReplyStore");
|
||||||
const OptionClasses = findByPropsLazy("optionName", "optionIcon", "optionLabel");
|
const OptionClasses = findByPropsLazy("optionName", "optionIcon", "optionLabel");
|
||||||
|
|
||||||
@@ -92,8 +94,8 @@ function sendAudio(blob: Blob, meta: AudioMetadata) {
|
|||||||
const upload = new CloudUpload({
|
const upload = new CloudUpload({
|
||||||
file: new File([blob], "voice-message.ogg", { type: "audio/ogg; codecs=opus" }),
|
file: new File([blob], "voice-message.ogg", { type: "audio/ogg; codecs=opus" }),
|
||||||
isThumbnail: false,
|
isThumbnail: false,
|
||||||
platform: 1,
|
platform: CloudUploadPlatform.WEB,
|
||||||
}, channelId, false, 0);
|
}, channelId);
|
||||||
|
|
||||||
upload.on("complete", () => {
|
upload.on("complete", () => {
|
||||||
RestAPI.post({
|
RestAPI.post({
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const settings = definePluginSettings({
|
|||||||
multiplier: {
|
multiplier: {
|
||||||
description: "Volume Multiplier",
|
description: "Volume Multiplier",
|
||||||
type: OptionType.SLIDER,
|
type: OptionType.SLIDER,
|
||||||
markers: makeRange(1, 5, 1),
|
markers: makeRange(1, 5, 0.5),
|
||||||
default: 2,
|
default: 2,
|
||||||
stickToMarkers: true,
|
stickToMarkers: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||||||
name: "V",
|
name: "V",
|
||||||
id: 343383572805058560n,
|
id: 343383572805058560n,
|
||||||
},
|
},
|
||||||
|
Apexo: {
|
||||||
|
name: "Apexo",
|
||||||
|
id: 228548952687902720n
|
||||||
|
},
|
||||||
Arjix: {
|
Arjix: {
|
||||||
name: "ArjixWasTaken",
|
name: "ArjixWasTaken",
|
||||||
id: 674710789138939916n,
|
id: 674710789138939916n,
|
||||||
@@ -640,7 +644,11 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||||||
iamme: {
|
iamme: {
|
||||||
name: "i am me",
|
name: "i am me",
|
||||||
id: 984392761929256980n,
|
id: 984392761929256980n,
|
||||||
}
|
},
|
||||||
|
thororen: {
|
||||||
|
name: "thororen",
|
||||||
|
id: 848339671629299742n
|
||||||
|
},
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// iife so #__PURE__ works correctly
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { MessageObject } from "@api/MessageEvents";
|
import { MessageObject } from "@api/MessageEvents";
|
||||||
import { Channel, Guild, GuildFeatures, Message, User } from "@vencord/discord-types";
|
import { Channel, CloudUpload, Guild, GuildFeatures, Message, User } from "@vencord/discord-types";
|
||||||
import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
|
import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
|
||||||
import { Except } from "type-fest";
|
import { Except } from "type-fest";
|
||||||
|
|
||||||
@@ -117,6 +117,20 @@ interface MessageOptions {
|
|||||||
replied_user: boolean;
|
replied_user: boolean;
|
||||||
};
|
};
|
||||||
stickerIds: string[];
|
stickerIds: string[];
|
||||||
|
attachmentsToUpload: CloudUpload[];
|
||||||
|
poll: {
|
||||||
|
allow_multiselect: boolean;
|
||||||
|
answers: Array<{
|
||||||
|
poll_media: {
|
||||||
|
text: string;
|
||||||
|
attachment_ids?: unknown;
|
||||||
|
emoji?: { name: string; id?: string; };
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
duration: number;
|
||||||
|
layout_type: number;
|
||||||
|
question: { text: string; };
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendMessage(
|
export function sendMessage(
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common";
|
import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common";
|
||||||
import { ActionDispatch } from "react";
|
import { ActionDispatch, ReactNode } from "react";
|
||||||
|
|
||||||
import { checkIntersecting } from "./misc";
|
import { checkIntersecting } from "./misc";
|
||||||
|
|
||||||
@@ -25,6 +25,14 @@ export * from "./lazyReact";
|
|||||||
|
|
||||||
export const NoopComponent = () => null;
|
export const NoopComponent = () => null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a React node is a primitive (string, number, bigint, boolean, undefined)
|
||||||
|
*/
|
||||||
|
export function isPrimitiveReactNode(node: ReactNode): boolean {
|
||||||
|
const t = typeof node;
|
||||||
|
return t === "string" || t === "number" || t === "bigint" || t === "boolean" || t === "undefined";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if an element is on screen
|
* Check if an element is on screen
|
||||||
* @param intersectOnly If `true`, will only update the state when the element comes into view
|
* @param intersectOnly If `true`, will only update the state when the element comes into view
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ import { moment } from "@webpack/common";
|
|||||||
// Utils for readable text transformations eg: `toTitle(fromKebab())`
|
// Utils for readable text transformations eg: `toTitle(fromKebab())`
|
||||||
|
|
||||||
// Case style to words
|
// Case style to words
|
||||||
export const wordsFromCamel = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase());
|
export const wordsFromCamel = (text: string) =>
|
||||||
|
text.split(/(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])/).map(w => /^[A-Z]{2,}$/.test(w) ? w : w.toLowerCase());
|
||||||
export const wordsFromSnake = (text: string) => text.toLowerCase().split("_");
|
export const wordsFromSnake = (text: string) => text.toLowerCase().split("_");
|
||||||
export const wordsFromKebab = (text: string) => text.toLowerCase().split("-");
|
export const wordsFromKebab = (text: string) => text.toLowerCase().split("-");
|
||||||
export const wordsFromPascal = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase());
|
export const wordsFromPascal = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase());
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ waitFor(m => m.name === "MenuCheckboxItem", (_, id) => {
|
|||||||
|
|
||||||
waitFor(filters.componentByCode('path:["empty"]'), m => Menu.Menu = m);
|
waitFor(filters.componentByCode('path:["empty"]'), m => Menu.Menu = m);
|
||||||
waitFor(filters.componentByCode("sliderContainer", "slider", "handleSize:16", "=100"), m => Menu.MenuSliderControl = m);
|
waitFor(filters.componentByCode("sliderContainer", "slider", "handleSize:16", "=100"), m => Menu.MenuSliderControl = m);
|
||||||
waitFor(filters.componentByCode('role:"searchbox', "top:2", "query:"), m => Menu.MenuSearchControl = m);
|
waitFor(filters.componentByCode(".SEARCH)", ".focus()", "query:"), m => Menu.MenuSearchControl = m);
|
||||||
|
|
||||||
export const ContextMenuApi: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN', {
|
export const ContextMenuApi: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN', {
|
||||||
closeContextMenu: filters.byCode("CONTEXT_MENU_CLOSE"),
|
closeContextMenu: filters.byCode("CONTEXT_MENU_CLOSE"),
|
||||||
|
|||||||
@@ -47,12 +47,14 @@ export let SelectedGuildStore: t.SelectedGuildStore;
|
|||||||
export let ChannelStore: t.ChannelStore;
|
export let ChannelStore: t.ChannelStore;
|
||||||
export let TypingStore: t.TypingStore;
|
export let TypingStore: t.TypingStore;
|
||||||
export let RelationshipStore: t.RelationshipStore;
|
export let RelationshipStore: t.RelationshipStore;
|
||||||
|
export let VoiceStateStore: t.VoiceStateStore;
|
||||||
|
|
||||||
export let EmojiStore: t.EmojiStore;
|
export let EmojiStore: t.EmojiStore;
|
||||||
export let StickersStore: t.StickersStore;
|
export let StickersStore: t.StickersStore;
|
||||||
export let ThemeStore: t.ThemeStore;
|
export let ThemeStore: t.ThemeStore;
|
||||||
export let WindowStore: t.WindowStore;
|
export let WindowStore: t.WindowStore;
|
||||||
export let DraftStore: t.DraftStore;
|
export let DraftStore: t.DraftStore;
|
||||||
|
export let StreamerModeStore: t.StreamerModeStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see jsdoc of {@link t.useStateFromStores}
|
* @see jsdoc of {@link t.useStateFromStores}
|
||||||
@@ -79,6 +81,8 @@ waitForStore("WindowStore", m => WindowStore = m);
|
|||||||
waitForStore("EmojiStore", m => EmojiStore = m);
|
waitForStore("EmojiStore", m => EmojiStore = m);
|
||||||
waitForStore("StickersStore", m => StickersStore = m);
|
waitForStore("StickersStore", m => StickersStore = m);
|
||||||
waitForStore("TypingStore", m => TypingStore = m);
|
waitForStore("TypingStore", m => TypingStore = m);
|
||||||
|
waitForStore("VoiceStateStore", m => VoiceStateStore = m);
|
||||||
|
waitForStore("StreamerModeStore", m => StreamerModeStore = m);
|
||||||
waitForStore("ThemeStore", m => {
|
waitForStore("ThemeStore", m => {
|
||||||
ThemeStore = m;
|
ThemeStore = m;
|
||||||
// Importing this directly can easily cause circular imports. For this reason, use a non import access here.
|
// Importing this directly can easily cause circular imports. For this reason, use a non import access here.
|
||||||
|
|||||||
Reference in New Issue
Block a user