diff --git a/packages/discord-types/src/stores/PendingReplyStore.d.ts b/packages/discord-types/src/stores/PendingReplyStore.d.ts index 1819277cb..afbd5bb4c 100644 --- a/packages/discord-types/src/stores/PendingReplyStore.d.ts +++ b/packages/discord-types/src/stores/PendingReplyStore.d.ts @@ -1,26 +1,22 @@ -import { Channel, FluxStore, Message } from ".."; +import { Channel, Message } from "../common"; +import { FluxStore } from "./FluxStore"; -/** Full pending reply state object */ -interface PendingReply { - /** The channel in which the pending reply was created */ +export interface PendingReply { channel: Channel; - /** The message that you're replying to */ message: Message; - /** Whether to ping the person you're replying to */ shouldMention: boolean; - /** Whether to show the ping \@ON/\@OFF toggle. Disabled by default for self-replies. */ showMentionToggle: boolean; } -/** Shallow state object which is persisted between restarts. Based on {@link PendingReply}. */ interface PendingReplyState { channelId: string; messageId: string; shouldMention: boolean; showMentionToggle: boolean; } - export class PendingReplyStore extends FluxStore { getState(): Record; getPendingReply(channelId: string): PendingReply | undefined; + /** Discord doesn't use this method. Also seems to always return undefined */ + getPendingReplyActionSource(channelId: string): unknown; } diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 53b69c0c1..004213b9f 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -138,7 +138,7 @@ export default definePlugin({ replace: "...$1.props,$&" }, { - match: /(?<=__unsupportedReactNodeAsText:\i,forceOpen:.{0,70}?)children:(?=.{0,5}\$\{(\i)\.id\})/, + match: /(?<=forceOpen:.{0,40}?ariaHidden:!0,)children:(?=.{0,50}?(\i)\.id)/, replace: "children:$1.component?$self.renderBadgeComponent({...$1}) :" }, // handle onClick and onContextMenu diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx index 2ecd5a43c..8e1536c40 100644 --- a/src/plugins/anonymiseFileNames/index.tsx +++ b/src/plugins/anonymiseFileNames/index.tsx @@ -60,7 +60,7 @@ const settings = definePluginSettings({ randomisedLength: { description: "Random characters length", type: OptionType.NUMBER, - default: 7, + default: 10, disabled: () => settings.store.method !== Methods.Random, }, consistent: { @@ -134,7 +134,7 @@ export default definePlugin({ const newFilename = (() => { switch (settings.store.method) { case Methods.Random: - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const chars = "ABCDEFabcdef0123456789"; const returnedName = Array.from( { length: settings.store.randomisedLength }, () => chars[Math.floor(Math.random() * chars.length)] diff --git a/src/plugins/customCommands/index.ts b/src/plugins/customCommands/index.ts index 7f26b9d46..b89e4d958 100644 --- a/src/plugins/customCommands/index.ts +++ b/src/plugins/customCommands/index.ts @@ -23,6 +23,7 @@ import { migratePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { sendMessage } from "@utils/discord"; import definePlugin from "@utils/types"; +import { FluxDispatcher, MessageActions, PendingReplyStore } from "@webpack/common"; import { openCreateTagModal } from "./CreateTagModal"; import { getTag, getTags, removeTag, settings, Tag } from "./settings"; @@ -67,7 +68,7 @@ export function registerTagCommand(tag: Tag) { } ], - execute: async (args, ctx) => { + execute: async (args, { channel }) => { const ephemeral = findOption(args, "ephemeral", false); const response = tag.message @@ -78,7 +79,8 @@ export function registerTagCommand(tag: Tag) { .replaceAll("\\n", "\n"); const doSend = ephemeral ? sendBotMessage : sendMessage; - doSend(ctx.channel.id, { content: response }); + doSend(channel.id, { content: response }, false, MessageActions.getSendMessageOptionsForReply(PendingReplyStore.getPendingReply(channel.id))); + FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId: channel.id }); }, [CustomCommandsMarker]: true, }, "CustomCommands"); diff --git a/src/plugins/reviewDB/components/ReviewModal.tsx b/src/plugins/reviewDB/components/ReviewModal.tsx index 720113498..7414148d1 100644 --- a/src/plugins/reviewDB/components/ReviewModal.tsx +++ b/src/plugins/reviewDB/components/ReviewModal.tsx @@ -20,7 +20,7 @@ import { BaseText } from "@components/BaseText"; import ErrorBoundary from "@components/ErrorBoundary"; import { Auth } from "@plugins/reviewDB/auth"; import { ReviewType } from "@plugins/reviewDB/entities"; -import { Response, REVIEWS_PER_PAGE } from "@plugins/reviewDB/reviewDbApi"; +import { REVIEWS_PER_PAGE, UserReviewsData } from "@plugins/reviewDB/reviewDbApi"; import { cl } from "@plugins/reviewDB/utils"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useForceUpdater } from "@utils/react"; @@ -30,7 +30,7 @@ import ReviewComponent from "./ReviewComponent"; import ReviewsView, { ReviewsInputComponent } from "./ReviewsView"; function Modal({ modalProps, modalKey, discordId, name, type }: { modalProps: any; modalKey: string, discordId: string; name: string; type: ReviewType; }) { - const [data, setData] = useState(); + const [data, setData] = useState(); const [signal, refetch] = useForceUpdater(true); const [page, setPage] = useState(1); diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx index 6ad0a3d69..23d46e28e 100644 --- a/src/plugins/reviewDB/components/ReviewsView.tsx +++ b/src/plugins/reviewDB/components/ReviewsView.tsx @@ -19,7 +19,7 @@ import { Paragraph } from "@components/Paragraph"; import { Auth, authorize } from "@plugins/reviewDB/auth"; import { Review, ReviewType } from "@plugins/reviewDB/entities"; -import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "@plugins/reviewDB/reviewDbApi"; +import { addReview, getReviews, REVIEWS_PER_PAGE, UserReviewsData } from "@plugins/reviewDB/reviewDbApi"; import { settings } from "@plugins/reviewDB/settings"; import { cl, showToast } from "@plugins/reviewDB/utils"; import { useAwaiter, useForceUpdater } from "@utils/react"; @@ -40,7 +40,7 @@ interface UserProps { } interface Props extends UserProps { - onFetchReviews(data: Response): void; + onFetchReviews(data: UserReviewsData): void; refetchSignal?: unknown; showInput?: boolean; page?: number; diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index 4039f36d3..8542a0aff 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -20,9 +20,12 @@ import "./style.css"; import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import ErrorBoundary from "@components/ErrorBoundary"; -import { NotesIcon, OpenExternalIcon } from "@components/Icons"; -import { TooltipContainer } from "@components/TooltipContainer"; +import { OpenExternalIcon } from "@components/Icons"; +import { Paragraph } from "@components/Paragraph"; +import { Span } from "@components/Span"; import { Devs } from "@utils/constants"; +import { classes } from "@utils/misc"; +import { useAwaiter } from "@utils/react"; import definePlugin from "@utils/types"; import { Guild, User } from "@vencord/discord-types"; import { findCssClassesLazy } from "@webpack"; @@ -31,11 +34,14 @@ import { Alerts, Clickable, Menu, Parser } from "@webpack/common"; import { Auth, initAuth, updateAuth } from "./auth"; import { openReviewsModal } from "./components/ReviewModal"; import { NotificationType, ReviewType } from "./entities"; -import { getCurrentUserInfo, readNotification } from "./reviewDbApi"; +import { getCurrentUserInfo, getReviews, readNotification } from "./reviewDbApi"; import { settings } from "./settings"; import { showToast } from "./utils"; -const BannerButtonClasses = findCssClassesLazy("bannerButton"); +const DMSideBarClasses = findCssClassesLazy("widgetPreviews"); +const ProfileCardClasses = findCssClassesLazy("cardsList", "firstCardContainer", "card", "container"); +const ProfileCardContainerClasses = findCssClassesLazy("innerContainer", "icons", "icon", "displayCount", "displayCountText", "displayCountTextColor", "breadcrumb"); +const ProfileCardOverlayClasses = findCssClassesLazy("overlay", "isPrivate", "outer"); const guildPopoutPatch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild, onClose(): void; }) => { if (!guild) return; @@ -79,17 +85,19 @@ export default definePlugin({ patches: [ // In the user popout. eg: when clicking the name in chat { - find: ".POPOUT,user:", + // DM profile sidebar + find: ".SIDEBAR,disableToolbar:", replacement: { - match: /children:\[(?=[^[]+?shouldShowTooltip:)/, - replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," + match: /user:(\i),widgets:.{0,100}?\}\),/, + replace: "$&$self.renderProfileComponent({user:$1,isSideBar:true})," } }, { - find: ".SIDEBAR,disableToolbar:", + // User popout + find: /\.POPOUT,onClose:\i}\),nicknameIcons:.+?\.isProvisional/, replacement: { - match: /children:\[(?=[^[]+?\.SIDEBAR}\),\i\.bot)/, - replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," + match: /user:(\i),widgets:.{0,100}?\}\),/, + replace: "$&$self.renderProfileComponent({user:$1})," } } ], @@ -148,16 +156,45 @@ export default definePlugin({ }, 4000); }, - BiteSizeReviewsButton: ErrorBoundary.wrap(({ user }: { user: User; }) => { - return ( - - openReviewsModal(user.id, user.username, ReviewType.User)} - className={BannerButtonClasses.bannerButton} - > - - - + renderProfileComponent: ErrorBoundary.wrap(({ user, isSideBar = false }: { user: User; isSideBar?: boolean; }) => { + const [reviewData] = useAwaiter(() => getReviews(user.id), { deps: [user.id], fallbackValue: null }); + + // Discord are masters at using a crap ton of html elements and css classes to create a simple ui that could have + // been made with less than half of the number of elements, so we have to do this insanity to replicate their ui + const reviewsSection = ( +
+
    +
  • + openReviewsModal(user.id, user.username, ReviewType.User)}> +
    + User Reviews + {!!reviewData?.reviewCount && ( +
    + {reviewData.reviews.slice(0, 4).map((review, idx) => { + const showCount = idx === 3 && reviewData.reviewCount > 4; + + return ( +
    + {review.sender.username} + {showCount && ( +
    + +{reviewData.reviewCount - 3} +
    + )} +
    + ); + })} +
    + )} +
    +
    +
  • +
+
); + + return isSideBar + ?
{reviewsSection}
+ : reviewsSection; }, { noop: true }) }); diff --git a/src/plugins/reviewDB/reviewDbApi.ts b/src/plugins/reviewDB/reviewDbApi.ts index 3fe6dd061..e899216a3 100644 --- a/src/plugins/reviewDB/reviewDbApi.ts +++ b/src/plugins/reviewDB/reviewDbApi.ts @@ -27,7 +27,7 @@ const API_URL = "https://manti.vendicated.dev/api/reviewdb"; export const REVIEWS_PER_PAGE = 50; -export interface Response { +export interface UserReviewsData { message: string; reviews: Review[]; updated: boolean; @@ -47,18 +47,19 @@ async function rdbRequest(path: string, options: RequestInit = {}) { }); } -export async function getReviews(id: string, offset = 0): Promise { +export async function getReviews(id: string, offset = 0): Promise { let flags = 0; if (!settings.store.showWarning) flags |= WarningFlag; const params = new URLSearchParams({ flags: String(flags), - offset: String(offset) + offset: String(offset), + limit: "4" }); const req = await fetch(`${API_URL}/users/${id}/reviews?${params}`); const res = (req.ok) - ? await req.json() as Response + ? await req.json() as UserReviewsData : { message: req.status === 429 ? "You are sending requests too fast. Wait a few seconds and try again." : "An Error occured while fetching reviews. Please try again later.", reviews: [], @@ -93,7 +94,7 @@ export async function getReviews(id: string, offset = 0): Promise { return res; } -export async function addReview(review: any): Promise { +export async function addReview(review: any): Promise { const token = await getToken(); if (!token) { @@ -109,13 +110,13 @@ export async function addReview(review: any): Promise { "Content-Type": "application/json", } }).then(async r => { - const data = await r.json() as Response; + const data = await r.json() as UserReviewsData; showToast(data.message); return r.ok ? data : null; }); } -export async function deleteReview(id: number): Promise { +export async function deleteReview(id: number): Promise { return await rdbRequest(`/users/${id}/reviews`, { method: "DELETE", headers: { @@ -126,7 +127,7 @@ export async function deleteReview(id: number): Promise { reviewid: id }) }).then(async r => { - const data = await r.json() as Response; + const data = await r.json() as UserReviewsData; showToast(data.message); return r.ok ? data : null; }); @@ -142,7 +143,7 @@ export async function reportReview(id: number) { body: JSON.stringify({ reviewid: id, }) - }).then(r => r.json()) as Response; + }).then(r => r.json()) as UserReviewsData; showToast(res.message); } diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 1217eedbd..7b67760f8 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -36,6 +36,7 @@ export let GuildChannelStore: t.GuildChannelStore; export let ReadStateStore: t.ReadStateStore; export let PresenceStore: t.PresenceStore; export let AccessibilityStore: t.AccessibilityStore; +export let PendingReplyStore: t.PendingReplyStore; export let GuildStore: t.GuildStore; export let GuildRoleStore: t.GuildRoleStore; @@ -136,9 +137,9 @@ waitForStore("LocaleStore", m => LocaleStore = m); waitForStore("RTCConnectionStore", m => RTCConnectionStore = m); waitForStore("SoundboardStore", m => SoundboardStore = m); waitForStore("PopoutWindowStore", m => PopoutWindowStore = m); +waitForStore("PendingReplyStore", m => PendingReplyStore = m); waitForStore("ApplicationCommandIndexStore", m => ApplicationCommandIndexStore = m); waitForStore("EditMessageStore", m => EditMessageStore = m); -waitForStore("PendingReplyStore", m => PendingReplyStore = m); waitForStore("ExperimentStore", m => ExperimentStore = m); waitForStore("QuestStore", m => QuestStore = m); waitForStore("UserAffinitiesV2Store", m => UserAffinitiesStore = m);