/* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 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 . */ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { Channel } from "discord-types/general"; import { addContextMenus, removeContextMenus } from "./contextMenus"; import { getPinAt, isPinned, settings, snapshotArray, sortedSnapshot, usePinnedDms } from "./settings"; export default definePlugin({ name: "PinDMs", description: "Allows you to pin private channels to the top of your DM list. To pin/unpin or reorder pins, right click DMs", authors: [Devs.Ven, Devs.Strencher], settings, start: addContextMenus, stop: removeContextMenus, usePinCount(channelIds: string[]) { const pinnedDms = usePinnedDms(); // See comment on 2nd patch for reasoning return channelIds.length ? [pinnedDms.size] : []; }, getChannel(channels: Record, idx: number) { return channels[getPinAt(idx)]; }, isPinned, getSnapshot: sortedSnapshot, getScrollOffset(channelId: string, rowHeight: number, padding: number, preRenderedChildren: number, originalOffset: number) { if (!isPinned(channelId)) return ( (rowHeight + padding) * 2 // header + rowHeight * snapshotArray.length // pins + originalOffset // original pin offset minus pins ); return rowHeight * (snapshotArray.indexOf(channelId) + preRenderedChildren) + padding; }, patches: [ // Patch DM list { find: ".privateChannelsHeaderContainer,", replacement: [ { // filter Discord's privateChannelIds list to remove pins, and pass // pinCount as prop. This needs to be here so that the entire DM list receives // updates on pin/unpin match: /(?<=\i,{channels:\i,)privateChannelIds:(\i),/, replace: "privateChannelIds:$1.filter(c=>!$self.isPinned(c)),pinCount:$self.usePinCount($1)," }, { // sections is an array of numbers, where each element is a section and // the number is the amount of rows. Add our pinCount in second place // - Section 1: buttons for pages like Friends & Library // - Section 2: our pinned dms // - Section 3: the normal dm list match: /(?<=renderRow:this\.renderRow,)sections:\[\i,/, // For some reason, adding our sections when no private channels are ready yet // makes DMs infinitely load. Thus usePinCount returns either a single element // array with the count, or an empty array. Due to spreading, only in the former // case will an element be added to the outer array // Thanks for the fix, Strencher! replace: "$&...this.props.pinCount??[]," }, { // Patch renderSection (renders the header) to set the text to "Pinned DMs" instead of "Direct Messages" // lookbehind is used to lookup parameter name. We could use arguments[0], but // if children ever is wrapped in an iife, it will break match: /children:(\i\.\i\.Messages.DIRECT_MESSAGES)(?<=renderSection=(\i)=>{.+?)/, replace: "children:$2.section===1?'Pinned DMs':$1" }, { // Patch channel lookup inside renderDM // channel=channels[channelIds[row]]; match: /(?<=renderDM=\((\i),(\i)\)=>{.*?this.state,\i=\i\[\i\],\i=)((\i)\[\i\]);/, // section 1 is us, manually get our own channel // section === 1 ? getChannel(channels, row) : channels[channelIds[row]]; replace: "$1===1?$self.getChannel($4,$2):$3;" }, { // Fix getRowHeight's check for whether this is the DMs section // DMS (inlined) === section match: /(?<=getRowHeight=\(.{2,50}?)1===\i/, // DMS (inlined) === section - 1 replace: "$&-1" }, { // Override scrollToChannel to properly account for pinned channels match: /(?<=scrollTo\(\{to:\i\}\):\(\i\+=)(\d+)\*\(.+?(?=,)/, replace: "$self.getScrollOffset(arguments[0],$1,this.props.padding,this.state.preRenderedChildren,$&)" } ] }, // Fix Alt Up/Down navigation { find: ".Routes.APPLICATION_STORE&&", replacement: { // channelIds = __OVERLAY__ ? stuff : [...getStaticPaths(),...channelIds)] match: /(?<=\i=__OVERLAY__\?\i:\[\.\.\.\i\(\),\.\.\.)\i/, // ....concat(pins).concat(toArray(channelIds).filter(c => !isPinned(c))) replace: "$self.getSnapshot().concat($&.filter(c=>!$self.isPinned(c)))" } }, // fix alt+shift+up/down { find: ".getFlattenedGuildIds()],", replacement: { match: /(?<=\i===\i\.ME\?)\i\.\i\.getPrivateChannelIds\(\)/, replace: "$self.getSnapshot().concat($&.filter(c=>!$self.isPinned(c)))" } }, ] });