mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 03:19:46 +00:00
Support custom emoji reactions in event_stats
This commit is contained in:
parent
755ed884d4
commit
c40c6e8b30
4 changed files with 88 additions and 24 deletions
10
packages/ditto/utils/custom-emoji.test.ts
Normal file
10
packages/ditto/utils/custom-emoji.test.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { assertEquals } from '@std/assert';
|
||||
|
||||
import { parseEmojiInput } from './custom-emoji.ts';
|
||||
|
||||
Deno.test('parseEmojiInput', () => {
|
||||
assertEquals(parseEmojiInput('+'), { type: 'basic', value: '+' });
|
||||
assertEquals(parseEmojiInput('🚀'), { type: 'native', native: '🚀' });
|
||||
assertEquals(parseEmojiInput(':ditto:'), { type: 'custom', shortcode: 'ditto' });
|
||||
assertEquals(parseEmojiInput('x'), undefined);
|
||||
});
|
||||
|
|
@ -70,3 +70,24 @@ export async function getCustomEmojis(
|
|||
|
||||
return emojis;
|
||||
}
|
||||
|
||||
/** Determine if the input is a native or custom emoji, returning a structured object or throwing an error. */
|
||||
export function parseEmojiInput(input: string):
|
||||
| { type: 'basic'; value: '+' | '-' }
|
||||
| { type: 'native'; native: string }
|
||||
| { type: 'custom'; shortcode: string }
|
||||
| undefined {
|
||||
if (input === '+' || input === '-') {
|
||||
return { type: 'basic', value: input };
|
||||
}
|
||||
|
||||
if (/^\p{RGI_Emoji}$/v.test(input)) {
|
||||
return { type: 'native', native: input };
|
||||
}
|
||||
|
||||
const match = input.match(/^:(\w+):$/);
|
||||
if (match) {
|
||||
const [, shortcode] = match;
|
||||
return { type: 'custom', shortcode };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,16 +141,24 @@ Deno.test('updateStats with kind 7 increments reactions count', async () => {
|
|||
const { kysely, relay } = test;
|
||||
|
||||
const note = genEvent({ kind: 1 });
|
||||
await updateStats({ ...test, event: note });
|
||||
await relay.event(note);
|
||||
|
||||
await updateStats({ ...test, event: genEvent({ kind: 7, content: '+', tags: [['e', note.id]] }) });
|
||||
await updateStats({ ...test, event: genEvent({ kind: 7, content: '😂', tags: [['e', note.id]] }) });
|
||||
|
||||
await updateStats({
|
||||
...test,
|
||||
event: genEvent({
|
||||
kind: 7,
|
||||
content: ':ditto:',
|
||||
tags: [['e', note.id], ['emoji', 'ditto', 'https://ditto.pub/favicon.ico']],
|
||||
}),
|
||||
});
|
||||
|
||||
const stats = await getEventStats(kysely, note.id);
|
||||
|
||||
assertEquals(stats!.reactions, JSON.stringify({ '+': 1, '😂': 1 }));
|
||||
assertEquals(stats!.reactions_count, 2);
|
||||
assertEquals(stats!.reactions, JSON.stringify({ '+': 1, '😂': 1, 'ditto:https://ditto.pub/favicon.ico': 1 }));
|
||||
assertEquals(stats!.reactions_count, 3);
|
||||
});
|
||||
|
||||
Deno.test('updateStats with kind 5 decrements reactions count', async () => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { z } from 'zod';
|
|||
import { findQuoteTag, findReplyTag, getTagSet } from '@/utils/tags.ts';
|
||||
|
||||
import type { DittoConf } from '@ditto/conf';
|
||||
import { parseEmojiInput } from '@/utils/custom-emoji.ts';
|
||||
|
||||
interface UpdateStatsOpts {
|
||||
conf: DittoConf;
|
||||
|
|
@ -154,14 +155,39 @@ async function handleEvent7(opts: UpdateStatsOpts): Promise<void> {
|
|||
const { kysely, event, x = 1 } = opts;
|
||||
|
||||
const id = event.tags.findLast(([name]) => name === 'e')?.[1];
|
||||
const emoji = event.content;
|
||||
const result = parseEmojiInput(event.content);
|
||||
|
||||
if (!id || !result) return;
|
||||
|
||||
let url: URL | undefined;
|
||||
|
||||
if (result.type === 'custom') {
|
||||
const tag = event.tags.find(([name, value]) => name === 'emoji' && value === result.shortcode);
|
||||
try {
|
||||
url = new URL(tag![2]);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let key: string;
|
||||
switch (result.type) {
|
||||
case 'basic':
|
||||
key = result.value;
|
||||
break;
|
||||
case 'native':
|
||||
key = result.native;
|
||||
break;
|
||||
case 'custom':
|
||||
key = `${result.shortcode}:${url}`;
|
||||
break;
|
||||
}
|
||||
|
||||
if (id && emoji && (['+', '-'].includes(emoji) || /^\p{RGI_Emoji}$/v.test(emoji))) {
|
||||
await updateEventStats(kysely, id, ({ reactions }) => {
|
||||
const data: Record<string, number> = JSON.parse(reactions);
|
||||
|
||||
// Increment or decrement the emoji count.
|
||||
data[emoji] = (data[emoji] ?? 0) + x;
|
||||
data[key] = (data[key] ?? 0) + x;
|
||||
|
||||
// Remove reactions with a count of 0 or less.
|
||||
for (const key of Object.keys(data)) {
|
||||
|
|
@ -178,7 +204,6 @@ async function handleEvent7(opts: UpdateStatsOpts): Promise<void> {
|
|||
reactions_count: count,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Update stats for kind 9735 event. */
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue