diff --git a/.eslintrc.json b/.eslintrc.json index c9b23e925..736f5a2ce 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -67,6 +67,7 @@ "no-mixed-spaces-and-tabs": "error", "indent": ["error", 4, { "SwitchCase": 1 }], "arrow-parens": ["error", "as-needed"], + "linebreak-style": ["error", "unix"], "eol-last": ["error", "always"], "@typescript-eslint/func-call-spacing": ["error", "never"], "no-multi-spaces": "error", diff --git a/.vscode/settings.json b/.vscode/settings.json index 4d0f85aa6..bd47dbcce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,8 @@ "[typescriptreact]": { "editor.defaultFormatter": "vscode.typescript-language-features" }, + "files.eol": "\n", + "files.insertFinalNewline": true, "javascript.format.semicolons": "insert", "typescript.format.semicolons": "insert", "typescript.preferences.quoteStyle": "double", diff --git a/packages/discord-types/scripts/changeReporter/config.mts b/packages/discord-types/scripts/changeReporter/config.mts index 2057cddf8..8c3a7a643 100644 --- a/packages/discord-types/scripts/changeReporter/config.mts +++ b/packages/discord-types/scripts/changeReporter/config.mts @@ -448,6 +448,11 @@ export default { type: "class", }, }, + "./general/DisplayProfile.ts": { + DisplayProfile: { + type: "class", + }, + }, "./general/Draft.ts": { DraftType: { type: "enum", diff --git a/packages/discord-types/src/general/DisplayProfile.ts b/packages/discord-types/src/general/DisplayProfile.ts new file mode 100644 index 000000000..6fb9089bc --- /dev/null +++ b/packages/discord-types/src/general/DisplayProfile.ts @@ -0,0 +1,67 @@ +/* + * discord-types + * Copyright (C) 2024 Vencord project contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import type { Nullish, OptionalTuple } from "../internal"; +import type { GuildMemberProfile } from "./GuildMemberProfile"; +import type { ProfileBadge, ProfileThemeColors, UserProfile } from "./UserProfile"; + +export declare class DisplayProfile< + FetchFailed extends boolean = boolean, + Guild extends boolean = boolean +> { + constructor( + userProfile: UserProfile, + ...guildMemberProfile: Guild extends true + ? [guildMemberProfile: GuildMemberProfile] + : [guildMemberProfile?: Nullish] + ); + + get application(): UserProfile["application"]; + get canEditThemes(): boolean; + get canUsePremiumProfileCustomization(): boolean; + getBadges(): ProfileBadge[]; + getBannerURL(options: { + canAnimate?: boolean | undefined /* = false */; + size: number; + }): string | undefined; + getLegacyUsername(): UserProfile["legacyUsername"]; + getPreviewBanner( + previewBanner?: string | Nullish, + canAnimate?: boolean | undefined, + size?: number | undefined /* = 480 */ + ): string | Nullish; + getPreviewBio(previewBio?: string | Nullish): (true extends Guild ? boolean : false) extends infer GuildValue + ? GuildValue extends true + ? { isUsingGuildValue: true; value: string; } + : { isUsingGuildValue: false; value: string | undefined; } + : never; + getPreviewThemeColors( + previewThemeColors?: OptionalTuple | Nullish + ): UserProfile["themeColors"]; + hasFullProfile(): boolean; + hasPremiumCustomization(): boolean; + hasThemeColors(): boolean; + isUsingGuildMemberBanner(): true extends Guild ? boolean : false; + isUsingGuildMemberBio(): true extends Guild ? boolean : false; + isUsingGuildMemberPronouns(): true extends Guild ? boolean : false; + get premiumGuildSince(): UserProfile["premiumGuildSince"]; + get premiumSince(): UserProfile["premiumSince"]; + get premiumType(): UserProfile["premiumType"]; + get primaryColor(): UserProfile["accentColor"]; + + _guildMemberProfile: Guild extends true ? GuildMemberProfile : Nullish; + _userProfile: UserProfile; + accentColor: UserProfile["accentColor"]; + banner: UserProfile["banner"]; + bio: UserProfile["bio"]; + guildId: Guild extends true ? string : undefined; + /** @todo Does not seem to be implemented. */ + popoutAnimationParticleType: UserProfile["popoutAnimationParticleType"]; + profileEffectId: UserProfile["profileEffectId"]; + pronouns: UserProfile["pronouns"]; + themeColors: UserProfile["themeColors"]; + userId: UserProfile["userId"]; +} diff --git a/packages/discord-types/src/general/UserProfile.ts b/packages/discord-types/src/general/UserProfile.ts index 95298f3e3..ec1a652ac 100644 --- a/packages/discord-types/src/general/UserProfile.ts +++ b/packages/discord-types/src/general/UserProfile.ts @@ -38,15 +38,25 @@ export interface UserProfileFetchFailed { accentColor: null; application: null; applicationRoleConnections: []; + /** Never present if fetch failed. */ + badges?: undefined; banner: null; bio: ""; connectedAccounts: []; lastFetched: number; legacyUsername: null; + /** Never present if fetch failed. */ + popoutAnimationParticleType?: undefined; premiumGuildSince: null; premiumSince: null; + /** Never present if fetch failed. */ + premiumType?: undefined; + /** Never present if fetch failed. */ + profileEffectId?: undefined; profileFetchFailed: true; pronouns: ""; + /** Never present if fetch failed. */ + themeColors?: undefined; userId: string; } diff --git a/packages/discord-types/src/general/index.ts b/packages/discord-types/src/general/index.ts index 93d1ad1a8..3af448713 100644 --- a/packages/discord-types/src/general/index.ts +++ b/packages/discord-types/src/general/index.ts @@ -10,6 +10,7 @@ export * from "./ApplicationRecord"; export * from "./channels"; export * from "./Clan"; export * from "./CompanyRecord"; +export * from "./DisplayProfile"; export * from "./Draft"; export * from "./emojis"; export * from "./Frecency"; diff --git a/packages/discord-types/src/internal.ts b/packages/discord-types/src/internal.ts index d0f7cb438..1b2742f45 100644 --- a/packages/discord-types/src/internal.ts +++ b/packages/discord-types/src/internal.ts @@ -37,6 +37,10 @@ export type Optional & { [Key in Exclude]?: T[Key] | Value; } : Omit & { [Key in Keys]?: T[Key] | Value; }; +/** @internal */ +export type OptionalTuple + = { [Key in keyof T]?: T[Key] | Value; }; + /** @internal */ export type PartialOnUndefined = { [Key in keyof T as undefined extends T[Key] ? never : Key]: T[Key]; } diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts index 66861d86c..a49984b4b 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/src/webpack/common/types/utils.d.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import type { GuildMember, GuildRecord, UserRecord } from "@vencord/discord-types"; +import type { DisplayProfile, GuildMember, GuildRecord, UserProfileStore, UserRecord, UserStore } from "@vencord/discord-types"; import type { ReactNode } from "react"; import type { LiteralUnion } from "type-fest"; @@ -139,6 +139,7 @@ export interface Constants { FriendsSections: Record; } +// zustand store export interface ExpressionPickerStore { closeExpressionPicker(activeViewType?: any): void; openExpressionPicker(activeView: LiteralUnion<"emoji" | "gif" | "sticker", string>, activeViewType?: any): void; @@ -197,41 +198,14 @@ export interface UserUtils { humanizeStatus: any; } -export class DisplayProfile { - userId: string; - banner?: string; - bio?: string; - pronouns?: string; - accentColor?: number; - themeColors?: number[]; - popoutAnimationParticleType?: any; - profileEffectId?: string; - _userProfile?: any; - _guildMemberProfile?: any; - canUsePremiumProfileCustomization: boolean; - canEditThemes: boolean; - premiumGuildSince: Date | null; - premiumSince: Date | null; - premiumType?: number; - primaryColor?: number; - - getBadges(): { - id: string; - description: string; - icon: string; - link?: string; - }[]; - getBannerURL(options: { canAnimate: boolean; size: number; }): string; - getLegacyUsername(): string | null; - hasFullProfile(): boolean; - hasPremiumCustomization(): boolean; - hasThemeColors(): boolean; - isUsingGuildMemberBanner(): boolean; - isUsingGuildMemberBio(): boolean; - isUsingGuildMemberPronouns(): boolean; -} - export interface DisplayProfileUtils { - getDisplayProfile(userId: string, guildId?: string, customStores?: any): DisplayProfile | null; - useDisplayProfile(userId: string, guildId?: string, customStores?: any): DisplayProfile | null; + getDisplayProfile( + userId: string, + guildId?: string | null | undefined, + stores?: [ + Pick, + Pick + ] | undefined /* = [UserStore, UserProfileStore] */ + ): DisplayProfile | null; + useDisplayProfile(userId: string, guildId?: string | null | undefined): DisplayProfile | null; } diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index ba443d2c6..b174f187c 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -130,9 +130,8 @@ export const DisplayProfileUtils: t.DisplayProfileUtils = mapMangledModuleLazy(/ const openExpressionPickerMatcher = canonicalizeMatch(/setState\({activeView:\i,activeViewType:/); -// TODO: type // zustand store -export const ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", { +export const ExpressionPickerStore: t.ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", { closeExpressionPicker: filters.byCode("setState({activeView:null"), openExpressionPicker: m => typeof m === "function" && openExpressionPickerMatcher.test(m.toString()), });