20b99dce48
Signed-off-by: Ajay Bura <ajbura@gmail.com>
185 lines
5.8 KiB
JavaScript
185 lines
5.8 KiB
JavaScript
import { emojis } from './emoji';
|
|
|
|
// Custom emoji are stored in one of three places:
|
|
// - User emojis, which are stored in account data
|
|
// - Room emojis, which are stored in state events in a room
|
|
// - Emoji packs, which are rooms of emojis referenced in the account data or in a room's
|
|
// cannonical space
|
|
//
|
|
// Emojis and packs referenced from within a user's account data should be available
|
|
// globally, while emojis and packs in rooms and spaces should only be available within
|
|
// those spaces and rooms
|
|
|
|
class ImagePack {
|
|
// Convert a raw image pack into a more maliable format
|
|
//
|
|
// Takes an image pack as per MSC 2545 (e.g. as in the Matrix spec), and converts it to a
|
|
// format used here, while filling in defaults.
|
|
//
|
|
// The room argument is the room the pack exists in, which is used as a fallback for
|
|
// missing properties
|
|
//
|
|
// Returns `null` if the rawPack is not a properly formatted image pack, although there
|
|
// is still a fair amount of tolerance for malformed packs.
|
|
static parsePack(rawPack, room) {
|
|
if (typeof rawPack.images === 'undefined') {
|
|
return null;
|
|
}
|
|
|
|
const pack = rawPack.pack ?? {};
|
|
|
|
const displayName = pack.display_name ?? (room ? room.name : undefined);
|
|
const avatar = pack.avatar_url ?? (room ? room.getMxcAvatarUrl() : undefined);
|
|
const usage = pack.usage ?? ['emoticon', 'sticker'];
|
|
const { attribution } = pack;
|
|
const images = Object.entries(rawPack.images).flatMap((e) => {
|
|
const data = e[1];
|
|
const shortcode = e[0];
|
|
const mxc = data.url;
|
|
const body = data.body ?? shortcode;
|
|
const { info } = data;
|
|
const usage_ = data.usage ?? usage;
|
|
|
|
if (mxc) {
|
|
return [{
|
|
shortcode, mxc, body, info, usage: usage_,
|
|
}];
|
|
}
|
|
return [];
|
|
});
|
|
|
|
return new ImagePack(displayName, avatar, usage, attribution, images);
|
|
}
|
|
|
|
constructor(displayName, avatar, usage, attribution, images) {
|
|
this.displayName = displayName;
|
|
this.avatar = avatar;
|
|
this.usage = usage;
|
|
this.attribution = attribution;
|
|
this.images = images;
|
|
}
|
|
|
|
// Produce a list of emoji in this image pack
|
|
getEmojis() {
|
|
return this.images.filter((i) => i.usage.indexOf('emoticon') !== -1);
|
|
}
|
|
|
|
// Produce a list of stickers in this image pack
|
|
getStickers() {
|
|
return this.images.filter((i) => i.usage.indexOf('sticker') !== -1);
|
|
}
|
|
}
|
|
|
|
// Retrieve a list of user emojis
|
|
//
|
|
// Result is an ImagePack, or null if the user hasn't set up or has deleted their personal
|
|
// image pack.
|
|
//
|
|
// Accepts a reference to a matrix client as the only argument
|
|
function getUserImagePack(mx) {
|
|
const accountDataEmoji = mx.getAccountData('im.ponies.user_emotes');
|
|
if (!accountDataEmoji) {
|
|
return null;
|
|
}
|
|
|
|
const userImagePack = ImagePack.parsePack(accountDataEmoji.event.content);
|
|
if (userImagePack) userImagePack.displayName ??= 'Your Emoji';
|
|
return userImagePack;
|
|
}
|
|
|
|
// Produces a list of all of the emoji packs in a room
|
|
//
|
|
// Returns a list of `ImagePack`s. This does not include packs in spaces that contain
|
|
// this room.
|
|
function getPacksInRoom(room) {
|
|
const packs = room.currentState.getStateEvents('im.ponies.room_emotes');
|
|
|
|
return packs
|
|
.map((p) => ImagePack.parsePack(p.event.content, room))
|
|
.filter((p) => p !== null);
|
|
}
|
|
|
|
// Produce a list of all image packs which should be shown for a given room
|
|
//
|
|
// This includes packs in that room, the user's personal images, and will eventually
|
|
// include the user's enabled global image packs and space-level packs.
|
|
//
|
|
// This differs from getPacksInRoom, as the former only returns packs that are directly in
|
|
// a room, whereas this function returns all packs which should be shown to the user while
|
|
// they are in this room.
|
|
//
|
|
// Packs will be returned in the order that shortcode conflicts should be resolved, with
|
|
// higher priority packs coming first.
|
|
function getRelevantPacks(room) {
|
|
return [].concat(
|
|
getUserImagePack(room.client) ?? [],
|
|
getPacksInRoom(room),
|
|
);
|
|
}
|
|
|
|
// Returns all user+room emojis and all standard unicode emojis
|
|
//
|
|
// Accepts a reference to a matrix client as the only argument
|
|
//
|
|
// Result is a map from shortcode to the corresponding emoji. If two emoji share a
|
|
// shortcode, only one will be presented, with priority given to custom emoji.
|
|
//
|
|
// Will eventually be expanded to include all emojis revelant to a room and the user
|
|
function getShortcodeToEmoji(room) {
|
|
const allEmoji = new Map();
|
|
|
|
emojis.forEach((emoji) => {
|
|
if (emoji.shortcodes.constructor.name === 'Array') {
|
|
emoji.shortcodes.forEach((shortcode) => {
|
|
allEmoji.set(shortcode, emoji);
|
|
});
|
|
} else {
|
|
allEmoji.set(emoji.shortcodes, emoji);
|
|
}
|
|
});
|
|
|
|
getRelevantPacks(room).reverse()
|
|
.flatMap((pack) => pack.getEmojis())
|
|
.forEach((emoji) => {
|
|
allEmoji.set(emoji.shortcode, emoji);
|
|
});
|
|
|
|
return allEmoji;
|
|
}
|
|
|
|
function getShortcodeToCustomEmoji(room) {
|
|
const allEmoji = new Map();
|
|
|
|
getRelevantPacks(room).reverse()
|
|
.flatMap((pack) => pack.getEmojis())
|
|
.forEach((emoji) => {
|
|
allEmoji.set(emoji.shortcode, emoji);
|
|
});
|
|
|
|
return allEmoji;
|
|
}
|
|
|
|
// Produces a special list of emoji specifically for auto-completion
|
|
//
|
|
// This list contains each emoji once, with all emoji being deduplicated by shortcode.
|
|
// However, the order of the standard emoji will have been preserved, and alternate
|
|
// shortcodes for the standard emoji will not be considered.
|
|
//
|
|
// Standard emoji are guaranteed to be earlier in the list than custom emoji
|
|
function getEmojiForCompletion(room) {
|
|
const allEmoji = new Map();
|
|
getRelevantPacks(room).reverse()
|
|
.flatMap((pack) => pack.getEmojis())
|
|
.forEach((emoji) => {
|
|
allEmoji.set(emoji.shortcode, emoji);
|
|
});
|
|
|
|
return emojis.filter((e) => !allEmoji.has(e.shortcode))
|
|
.concat(Array.from(allEmoji.values()));
|
|
}
|
|
|
|
export {
|
|
getUserImagePack,
|
|
getShortcodeToEmoji, getShortcodeToCustomEmoji,
|
|
getRelevantPacks, getEmojiForCompletion,
|
|
};
|