@nodebb/nodebb-plugin-reactions 3.1.3 → 3.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/helpers.js +7 -5
- package/library.js +91 -17
- package/package-lock.json +1330 -0
- package/package.json +1 -1
- package/test/index.js +6 -13
package/helpers.js
CHANGED
|
@@ -46,10 +46,6 @@ function resolveByName(name) {
|
|
|
46
46
|
return null;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
* Resolve FEP-c0e0 reaction content (unicode grapheme, `:shortcode:`, bare name,
|
|
51
|
-
* or a custom-emoji `tag`) to a local emoji name. Returns null when unresolvable.
|
|
52
|
-
*/
|
|
53
49
|
function resolveReaction(content, tag) {
|
|
54
50
|
if (typeof content !== 'string' || !content.trim()) {
|
|
55
51
|
return null;
|
|
@@ -90,7 +86,13 @@ function resolveReaction(content, tag) {
|
|
|
90
86
|
if (!reaction && Array.isArray(tag)) {
|
|
91
87
|
const emojiTag = tag.find(t => t && t.type === 'Emoji' && typeof t.name === 'string');
|
|
92
88
|
if (emojiTag) {
|
|
93
|
-
|
|
89
|
+
const name = emojiTag.name.replace(/^:|:$/g, '');
|
|
90
|
+
// Try to resolve as a local emoji first
|
|
91
|
+
reaction = resolveByName(name);
|
|
92
|
+
// If not local, return the tag so the caller can cache the custom emoji
|
|
93
|
+
if (!reaction) {
|
|
94
|
+
return { emoji: name, emojiTag };
|
|
95
|
+
}
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
package/library.js
CHANGED
|
@@ -30,6 +30,49 @@ function parse(name) {
|
|
|
30
30
|
return emoji ? emojiParser.buildEmoji(emoji, '') : '';
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
async function parseReaction(name, sourceUrl) {
|
|
34
|
+
const emoji = nameToEmoji(name) || helpers.getEmojiTable()[helpers.getEmojiAliases()[name]];
|
|
35
|
+
if (emoji) {
|
|
36
|
+
return emojiParser.buildEmoji(emoji, '');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Try to find the custom emoji: first by source URL hostname, then by scanning emoji:ap:lookup
|
|
40
|
+
let hostname = null;
|
|
41
|
+
if (typeof sourceUrl === 'string') {
|
|
42
|
+
try {
|
|
43
|
+
hostname = new URL(sourceUrl).hostname;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
// Not a valid URL (e.g. numeric pid)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let metadata = null;
|
|
50
|
+
if (hostname) {
|
|
51
|
+
metadata = await activitypub.emoji.getEmoji(name, hostname);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Fallback: scan emoji:ap:lookup for any entry matching this shortcode
|
|
55
|
+
if (!metadata) {
|
|
56
|
+
const allKeys = await db.getObjectKeys('emoji:ap:lookup');
|
|
57
|
+
if (allKeys && allKeys.length > 0) {
|
|
58
|
+
const matchKey = allKeys.find(k => k.startsWith(`${name}:`));
|
|
59
|
+
if (matchKey) {
|
|
60
|
+
const idx = matchKey.indexOf(':');
|
|
61
|
+
if (idx !== -1) {
|
|
62
|
+
hostname = matchKey.slice(idx + 1);
|
|
63
|
+
metadata = await activitypub.emoji.getEmoji(name, hostname);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (metadata) {
|
|
70
|
+
const proxyUrl = activitypub.emoji.getProxyUrl(name, hostname);
|
|
71
|
+
return `<img class="not-responsive emoji" src="${proxyUrl}" title=":${name}:" />`;
|
|
72
|
+
}
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
|
|
33
76
|
const ReactionsPlugin = module.exports;
|
|
34
77
|
|
|
35
78
|
ReactionsPlugin.init = async function (params) {
|
|
@@ -150,7 +193,7 @@ ReactionsPlugin.getPostReactions = async function (data) {
|
|
|
150
193
|
for (const reaction of reactions) {
|
|
151
194
|
const reactionSet = `pid:${post.pid}:reaction:${reaction}`;
|
|
152
195
|
const uids = reactionSetToUsersMap.get(reactionSet);
|
|
153
|
-
const reactionImage =
|
|
196
|
+
const reactionImage = await parseReaction(reaction, post.pid);
|
|
154
197
|
if (Array.isArray(uids) && reactionImage) {
|
|
155
198
|
post.reactions.push({
|
|
156
199
|
pid: post.pid,
|
|
@@ -215,7 +258,7 @@ ReactionsPlugin.getMessageReactions = async function (data) {
|
|
|
215
258
|
for (const reaction of reactions) {
|
|
216
259
|
const reactionSet = `mid:${msg.mid}:reaction:${reaction}`;
|
|
217
260
|
const uids = reactionSetToUsersMap.get(reactionSet);
|
|
218
|
-
const reactionImage =
|
|
261
|
+
const reactionImage = await parseReaction(reaction, msg.mid);
|
|
219
262
|
if (Array.isArray(uids) && reactionImage) {
|
|
220
263
|
msg.reactions.push({
|
|
221
264
|
mid: msg.mid,
|
|
@@ -290,7 +333,7 @@ async function sendPostEvent(data, eventName) {
|
|
|
290
333
|
reaction: data.reaction,
|
|
291
334
|
reactionCount,
|
|
292
335
|
totalReactions,
|
|
293
|
-
reactionImage:
|
|
336
|
+
reactionImage: await parseReaction(data.reaction, data.pid),
|
|
294
337
|
});
|
|
295
338
|
} catch (e) {
|
|
296
339
|
console.error(e);
|
|
@@ -314,7 +357,7 @@ async function sendMessageEvent(data, eventName) {
|
|
|
314
357
|
reaction: data.reaction,
|
|
315
358
|
reactionCount,
|
|
316
359
|
totalReactions,
|
|
317
|
-
reactionImage:
|
|
360
|
+
reactionImage: await parseReaction(data.reaction, data.mid),
|
|
318
361
|
});
|
|
319
362
|
} catch (e) {
|
|
320
363
|
console.error(e);
|
|
@@ -501,13 +544,36 @@ ReactionsPlugin.applyEmojiReact = async function (activity) {
|
|
|
501
544
|
|
|
502
545
|
const id = await resolveReactionPost(object);
|
|
503
546
|
if (!id) {
|
|
504
|
-
return;
|
|
547
|
+
return false;
|
|
505
548
|
}
|
|
506
549
|
|
|
507
|
-
const
|
|
508
|
-
if (!
|
|
550
|
+
const resolved = helpers.resolveReaction(content, tag);
|
|
551
|
+
if (!resolved) {
|
|
509
552
|
activitypub.helpers.log(`[reactions/ap] Unresolvable reaction content (${JSON.stringify(content)}), ignoring.`);
|
|
510
|
-
return;
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
let reaction;
|
|
557
|
+
let emojiTag;
|
|
558
|
+
if (typeof resolved === 'object' && resolved.emoji) {
|
|
559
|
+
reaction = resolved.emoji;
|
|
560
|
+
emojiTag = resolved.emojiTag;
|
|
561
|
+
} else {
|
|
562
|
+
reaction = resolved;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (emojiTag) {
|
|
566
|
+
const cacheTag = { name: emojiTag.name };
|
|
567
|
+
if (emojiTag.icon && emojiTag.icon.url) {
|
|
568
|
+
cacheTag.icon = emojiTag.icon;
|
|
569
|
+
} else if (emojiTag.id) {
|
|
570
|
+
cacheTag.icon = { url: emojiTag.id, mediaType: emojiTag.mediaType || null };
|
|
571
|
+
} else {
|
|
572
|
+
cacheTag = null;
|
|
573
|
+
}
|
|
574
|
+
if (cacheTag) {
|
|
575
|
+
await activitypub.emoji.cacheEmoji(cacheTag);
|
|
576
|
+
}
|
|
511
577
|
}
|
|
512
578
|
|
|
513
579
|
const allowed = await privileges.posts.can('posts:upvote', id, activitypub._constants.uid);
|
|
@@ -519,6 +585,7 @@ ReactionsPlugin.applyEmojiReact = async function (activity) {
|
|
|
519
585
|
activitypub.helpers.log(`[reactions/ap] id ${id} (${reaction}) via ${actor}`);
|
|
520
586
|
await ReactionsPlugin.addPostReaction(id, actor, reaction);
|
|
521
587
|
await activitypub.feps.announce(object.id, activity);
|
|
588
|
+
return true;
|
|
522
589
|
};
|
|
523
590
|
|
|
524
591
|
ReactionsPlugin.undoEmojiReact = async function (activity) {
|
|
@@ -526,19 +593,22 @@ ReactionsPlugin.undoEmojiReact = async function (activity) {
|
|
|
526
593
|
|
|
527
594
|
const id = await resolveReactionPost(object);
|
|
528
595
|
if (!id) {
|
|
529
|
-
return;
|
|
596
|
+
return false;
|
|
530
597
|
}
|
|
531
598
|
|
|
532
|
-
const
|
|
533
|
-
if (!
|
|
599
|
+
const resolved = helpers.resolveReaction(content, tag);
|
|
600
|
+
if (!resolved) {
|
|
534
601
|
activitypub.helpers.log(`[reactions/ap] Unresolvable reaction content in undo, ignoring.`);
|
|
535
|
-
return;
|
|
602
|
+
return false;
|
|
536
603
|
}
|
|
537
604
|
|
|
605
|
+
const reaction = typeof resolved === 'object' ? resolved.emoji : resolved;
|
|
606
|
+
|
|
538
607
|
activitypub.helpers.log(`[reactions/ap] undo id ${id} (${reaction}) via ${actor}`);
|
|
539
608
|
await ReactionsPlugin.removePostReaction(id, actor, reaction);
|
|
540
609
|
await ReactionsPlugin.rescindPostReaction(id, actor, reaction);
|
|
541
610
|
await activitypub.feps.announce(object.id, activity);
|
|
611
|
+
return true;
|
|
542
612
|
};
|
|
543
613
|
|
|
544
614
|
ReactionsPlugin.handleEmojiReact = async function (context) {
|
|
@@ -547,10 +617,11 @@ ReactionsPlugin.handleEmojiReact = async function (context) {
|
|
|
547
617
|
};
|
|
548
618
|
|
|
549
619
|
ReactionsPlugin.handleLike = async function (context) {
|
|
550
|
-
// FEP-c0e0: a Like with content is an emoji reaction; a plain Like falls through to core
|
|
551
620
|
if (typeof context.activity.content === 'string' && context.activity.content.trim()) {
|
|
552
|
-
await ReactionsPlugin.applyEmojiReact(context.activity);
|
|
553
|
-
|
|
621
|
+
const applied = await ReactionsPlugin.applyEmojiReact(context.activity);
|
|
622
|
+
if (applied) {
|
|
623
|
+
return { ...context, claimed: true };
|
|
624
|
+
}
|
|
554
625
|
}
|
|
555
626
|
return context;
|
|
556
627
|
};
|
|
@@ -615,8 +686,11 @@ ReactionsPlugin.handleAnnounce = async function (context) {
|
|
|
615
686
|
}
|
|
616
687
|
}
|
|
617
688
|
|
|
618
|
-
await ReactionsPlugin.applyEmojiReact(object);
|
|
619
|
-
|
|
689
|
+
const applied = await ReactionsPlugin.applyEmojiReact(object);
|
|
690
|
+
if (applied) {
|
|
691
|
+
return { ...context, claimed: true };
|
|
692
|
+
}
|
|
693
|
+
return context;
|
|
620
694
|
};
|
|
621
695
|
|
|
622
696
|
SocketPlugins.reactions = {
|