mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-16 12:10:28 +03:00
Add support for sending room-local emoji (#209)
* Add support for sending room-local emoji Does not add support for sending a room's emoji outside of that room, but enables users to send an emoji if the packs in a room support it. Does not include room emoji in the picker YET. * Amend PR #209: Don't freak out if the `pack` tag is missing * Amending PR: Refactor emojifier, use better method for retrieving packs * Amending PR: Improve resiliance to bad data in emoji state events * Amend PR: Remove redundant code, fix crash on edit
This commit is contained in:
parent
f9b70d65d8
commit
9ea9bf4035
3 changed files with 174 additions and 76 deletions
|
|
@ -10,26 +10,114 @@ import { emojis } from './emoji';
|
|||
// globally, while emojis and packs in rooms and spaces should only be available within
|
||||
// those spaces and rooms
|
||||
|
||||
// Retrieve a list of user emojis
|
||||
//
|
||||
// Result is a list of objects, each with a shortcode and an mxc property
|
||||
//
|
||||
// Accepts a reference to a matrix client as the only argument
|
||||
function getUserEmoji(mx) {
|
||||
const accountDataEmoji = mx.getAccountData('im.ponies.user_emotes');
|
||||
if (!accountDataEmoji) {
|
||||
return [];
|
||||
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);
|
||||
}
|
||||
|
||||
const { images } = accountDataEmoji.event.content;
|
||||
const mapped = Object.entries(images).map((e) => ({
|
||||
shortcode: e[0],
|
||||
mxc: e[1].url,
|
||||
}));
|
||||
return mapped;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns all user emojis and all standard unicode emojis
|
||||
// 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
|
||||
//
|
||||
|
|
@ -37,7 +125,7 @@ function getUserEmoji(mx) {
|
|||
// 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(mx) {
|
||||
function getShortcodeToEmoji(room) {
|
||||
const allEmoji = new Map();
|
||||
|
||||
emojis.forEach((emoji) => {
|
||||
|
|
@ -50,9 +138,11 @@ function getShortcodeToEmoji(mx) {
|
|||
}
|
||||
});
|
||||
|
||||
getUserEmoji(mx).forEach((emoji) => {
|
||||
allEmoji.set(emoji.shortcode, emoji);
|
||||
});
|
||||
getRelevantPacks(room).reverse()
|
||||
.flatMap((pack) => pack.getEmojis())
|
||||
.forEach((emoji) => {
|
||||
allEmoji.set(emoji.shortcode, emoji);
|
||||
});
|
||||
|
||||
return allEmoji;
|
||||
}
|
||||
|
|
@ -64,14 +154,16 @@ function getShortcodeToEmoji(mx) {
|
|||
// shortcodes for the standard emoji will not be considered.
|
||||
//
|
||||
// Standard emoji are guaranteed to be earlier in the list than custom emoji
|
||||
function getEmojiForCompletion(mx) {
|
||||
function getEmojiForCompletion(room) {
|
||||
const allEmoji = new Map();
|
||||
getUserEmoji(mx).forEach((emoji) => {
|
||||
allEmoji.set(emoji.shortcode, emoji);
|
||||
});
|
||||
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 { getUserEmoji, getShortcodeToEmoji, getEmojiForCompletion };
|
||||
export { getUserImagePack, getShortcodeToEmoji, getEmojiForCompletion };
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) {
|
|||
setCmd({ prefix, suggestions: commands });
|
||||
},
|
||||
':': () => {
|
||||
const emojis = getEmojiForCompletion(mx);
|
||||
const emojis = getEmojiForCompletion(mx.getRoom(roomId));
|
||||
asyncSearch.setup(emojis, { keys: ['shortcode'], isContain: true, limit: 20 });
|
||||
setCmd({ prefix, suggestions: emojis.slice(26, 46) });
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue