feat(plugin): FavoriteEmojiFirst (#1110)

Co-authored-by: V <vendicated@riseup.net>
This commit is contained in:
Syncx 2023-05-17 12:38:15 +10:00 committed by GitHub
parent 341151a718
commit 1d6b78f6c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 193 additions and 20 deletions

@ -18,24 +18,15 @@
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { MessageStore } from "@webpack/common"; import { MessageStore } from "@webpack/common";
import { CustomEmoji } from "@webpack/types";
import type { Channel, Message } from "discord-types/general"; import type { Channel, Message } from "discord-types/general";
import type { Promisable } from "type-fest"; import type { Promisable } from "type-fest";
const MessageEventsLogger = new Logger("MessageEvents", "#e5c890"); const MessageEventsLogger = new Logger("MessageEvents", "#e5c890");
export interface Emoji {
require_colons: boolean,
originalName: string,
animated: boolean;
guildId: string,
name: string,
url: string,
id: string,
}
export interface MessageObject { export interface MessageObject {
content: string, content: string,
validNonShortcutEmojis: Emoji[]; validNonShortcutEmojis: CustomEmoji[];
invalidEmojis: any[]; invalidEmojis: any[];
tts: boolean; tts: boolean;
} }

@ -24,12 +24,11 @@ import { Margins } from "@utils/margins";
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal"; import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy, findStoreLazy } from "@webpack"; import { findByCodeLazy, findStoreLazy } from "@webpack";
import { FluxDispatcher, Forms, GuildStore, Menu, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
import { Promisable } from "type-fest"; import { Promisable } from "type-fest";
const MANAGE_EMOJIS_AND_STICKERS = 1n << 30n; const MANAGE_EMOJIS_AND_STICKERS = 1n << 30n;
const GuildEmojiStore = findStoreLazy("EmojiStore");
const StickersStore = findStoreLazy("StickersStore"); const StickersStore = findStoreLazy("StickersStore");
const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS("); const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS(");
@ -129,7 +128,7 @@ function getGuildCandidates(data: Data) {
const { isAnimated } = data as Emoji; const { isAnimated } = data as Emoji;
const emojiSlots = g.getMaxEmojiSlots(); const emojiSlots = g.getMaxEmojiSlots();
const { emojis } = GuildEmojiStore.getGuilds()[g.id]; const { emojis } = EmojiStore.getGuilds()[g.id];
let count = 0; let count = 0;
for (const emoji of emojis) for (const emoji of emojis)

@ -24,7 +24,7 @@ import { getCurrentGuild } from "@utils/discord";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { ChannelStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common"; import { ChannelStore, EmojiStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common";
import type { Message } from "discord-types/general"; import type { Message } from "discord-types/general";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
@ -38,8 +38,6 @@ const StickerStore = findStoreLazy("StickersStore") as {
getAllGuildStickers(): Map<string, Sticker[]>; getAllGuildStickers(): Map<string, Sticker[]>;
getStickerById(id: string): Sticker | undefined; getStickerById(id: string): Sticker | undefined;
}; };
const EmojiStore = findStoreLazy("EmojiStore");
function searchProtoClass(localName: string, parentProtoClass: any) { function searchProtoClass(localName: string, parentProtoClass: any) {
if (!parentProtoClass) return; if (!parentProtoClass) return;

@ -0,0 +1,83 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { EmojiStore } from "@webpack/common";
import { Emoji } from "@webpack/types";
interface EmojiAutocompleteState {
query?: {
type: string;
typeInfo: {
sentinel: string;
};
results: {
emojis: Emoji[] & { sliceTo?: number; };
};
};
}
export default definePlugin({
name: "FavoriteEmojiFirst",
authors: [Devs.Aria, Devs.Ven],
description: "Puts your favorite emoji first in the emoji autocomplete.",
patches: [
{
find: ".activeCommandOption",
replacement: [
{
// = someFunc(a.selectedIndex); ...trackEmojiSearch({ state: theState, isInPopoutExperimental: someBool })
match: /=\i\(\i\.selectedIndex\);(?=.+?state:(\i),isInPopoutExperiment:\i)/,
// self.sortEmojis(theState)
replace: "$&$self.sortEmojis($1);"
},
// set maxCount to Infinity so our sortEmojis callback gets the entire list, not just the first 10
// and remove Discord's emojiResult slice, storing the endIndex on the array for us to use later
{
// searchEmojis(...,maxCount: stuff) ... endEmojis = emojis.slice(0, maxCount - gifResults.length)
match: /,maxCount:(\i)(.+?)=(\i)\.slice\(0,(\1-\i\.length)\)/,
// ,maxCount:Infinity ... endEmojis = (emojis.sliceTo = n, emojis)
replace: ",maxCount:Infinity$2=($3.sliceTo=$4,$3)"
}
]
}
],
sortEmojis({ query }: EmojiAutocompleteState) {
if (
query?.type !== "EMOJIS_AND_STICKERS"
|| query.typeInfo?.sentinel !== ":"
|| !query.results?.emojis?.length
) return;
const emojiContext = EmojiStore.getDisambiguatedEmojiContext();
query.results.emojis = query.results.emojis.sort((a, b) => {
const aIsFavorite = emojiContext.isFavoriteEmojiWithoutFetchingLatest(a);
const bIsFavorite = emojiContext.isFavoriteEmojiWithoutFetchingLatest(b);
if (aIsFavorite && !bIsFavorite) return -1;
if (!aIsFavorite && bIsFavorite) return 1;
return 0;
}).slice(0, query.results.emojis.sliceTo ?? 10);
}
});

@ -20,8 +20,8 @@ import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { LazyComponent } from "@utils/react"; import { LazyComponent } from "@utils/react";
import { formatDuration } from "@utils/text"; import { formatDuration } from "@utils/text";
import { find, findByPropsLazy, findStoreLazy } from "@webpack"; import { find, findByPropsLazy } from "@webpack";
import { FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common";
import type { Channel } from "discord-types/general"; import type { Channel } from "discord-types/general";
import type { ComponentType } from "react"; import type { ComponentType } from "react";
@ -94,7 +94,6 @@ const TagComponent = LazyComponent(() => find(m => {
return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill"); return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill");
})); }));
const EmojiStore = findStoreLazy("EmojiStore");
const EmojiParser = findByPropsLazy("convertSurrogateToName"); const EmojiParser = findByPropsLazy("convertSurrogateToName");
const EmojiUtils = findByPropsLazy("getURL", "buildEmojiReactionColorsPlatformed"); const EmojiUtils = findByPropsLazy("getURL", "buildEmojiReactionColorsPlatformed");

@ -49,6 +49,7 @@ export let RelationshipStore: Stores.RelationshipStore & t.FluxStore & {
getSince(userId: string): string; getSince(userId: string): string;
}; };
export let EmojiStore: t.EmojiStore;
export let WindowStore: t.WindowStore; export let WindowStore: t.WindowStore;
export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', { export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', {
@ -87,3 +88,4 @@ waitForStore("ReadStateStore", m => ReadStateStore = m);
waitForStore("GuildChannelStore", m => GuildChannelStore = m); waitForStore("GuildChannelStore", m => GuildChannelStore = m);
waitForStore("MessageStore", m => MessageStore = m); waitForStore("MessageStore", m => MessageStore = m);
waitForStore("WindowStore", m => WindowStore = m); waitForStore("WindowStore", m => WindowStore = m);
waitForStore("EmojiStore", m => EmojiStore = m);

@ -16,6 +16,8 @@
* 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 { Channel } from "discord-types/general";
import { FluxDispatcher, FluxEvents } from "./utils"; import { FluxDispatcher, FluxEvents } from "./utils";
export class FluxStore { export class FluxStore {
@ -38,3 +40,102 @@ export class WindowStore extends FluxStore {
isFocused(): boolean; isFocused(): boolean;
windowSize(): Record<"width" | "height", number>; windowSize(): Record<"width" | "height", number>;
} }
type Emoji = CustomEmoji | UnicodeEmoji;
export interface CustomEmoji {
allNamesString: string;
animated: boolean;
available: boolean;
guildId: string;
id: string;
managed: boolean;
name: string;
originalName?: string;
require_colons: boolean;
roles: string[];
url: string;
}
export interface UnicodeEmoji {
diversityChildren: Record<any, any>;
emojiObject: {
names: string[];
surrogates: string;
unicodeVersion: number;
};
index: number;
surrogates: string;
uniqueName: string;
useSpriteSheet: boolean;
get allNamesString(): string;
get animated(): boolean;
get defaultDiversityChild(): any;
get hasDiversity(): boolean | undefined;
get hasDiversityParent(): boolean | undefined;
get hasMultiDiversity(): boolean | undefined;
get hasMultiDiversityParent(): boolean | undefined;
get managed(): boolean;
get name(): string;
get names(): string[];
get optionallyDiverseSequence(): string | undefined;
get unicodeVersion(): number;
get url(): string;
}
export class EmojiStore extends FluxStore {
getCustomEmojiById(id?: string | null): CustomEmoji;
getUsableCustomEmojiById(id?: string | null): CustomEmoji;
getGuilds(): Record<string, {
id: string;
_emojiMap: Record<string, CustomEmoji>;
_emojis: CustomEmoji[];
get emojis(): CustomEmoji[];
get rawEmojis(): CustomEmoji[];
_usableEmojis: CustomEmoji[];
get usableEmojis(): CustomEmoji[];
_emoticons: any[];
get emoticons(): any[];
}>;
getGuildEmoji(guildId?: string | null): CustomEmoji[];
getNewlyAddedEmoji(guildId?: string | null): CustomEmoji[];
getTopEmoji(guildId?: string | null): CustomEmoji[];
getTopEmojisMetadata(guildId?: string | null): {
emojiIds: string[];
topEmojisTTL: number;
};
hasPendingUsage(): boolean;
hasUsableEmojiInAnyGuild(): boolean;
searchWithoutFetchingLatest(data: any): any;
getSearchResultsOrder(...args: any[]): any;
getState(): {
pendingUsages: { key: string, timestamp: number; }[];
};
searchWithoutFetchingLatest(data: {
channel: Channel,
query: string;
count?: number;
intention: number;
includeExternalGuilds?: boolean;
matchComparator?(name: string): boolean;
}): Record<"locked" | "unlocked", Emoji[]>;
getDisambiguatedEmojiContext(): {
backfillTopEmojis: Record<any, any>;
customEmojis: Record<string, CustomEmoji>;
emojisById: Record<string, CustomEmoji>;
emojisByName: Record<string, CustomEmoji>;
emoticonRegex: RegExp | null;
emoticonsByName: Record<string, any>;
escapedEmoticonNames: string;
favoriteNamesAndIds?: any;
favorites?: any;
frequentlyUsed?: any;
groupedCustomEmojis: Record<string, CustomEmoji[]>;
guildId?: string;
isFavoriteEmojiWithoutFetchingLatest(e: Emoji): boolean;
newlyAddedEmoji: Record<string, CustomEmoji[]>;
topEmojis?: any;
unicodeAliases: Record<string, string>;
get favoriteEmojisWithoutFetchingLatest(): Emoji[];
};
}