reacts and custom emoji reacts

This commit is contained in:
Moon.eth
2026-07-30 17:38:23 +09:00
parent 5b7b4360d2
commit b814d79f19
7 changed files with 384 additions and 3 deletions
+29
View File
@@ -3,6 +3,7 @@ import { ApiClient } from './client'
import {
fetchNotifications,
postStatus,
setEmojiReaction,
updateProfileFields,
updatePublicProfile,
votePoll,
@@ -152,6 +153,34 @@ describe('poll endpoints', () => {
})
})
describe('emoji reaction endpoints', () => {
it.each([
[true, 'PUT'],
[false, 'DELETE'],
] as const)('uses the Pleroma/Akkoma reaction endpoint (on=%s)', async (on, method) => {
const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => {
expect(init?.method).toBe(method)
return new Response(JSON.stringify({ id: 'status/one' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
})
vi.stubGlobal('fetch', fetchMock)
await setEmojiReaction(
new ApiClient('example.test', 'token'),
'status/one',
'party_blob@remote.example',
on,
)
expect(fetchMock).toHaveBeenCalledWith(
'https://example.test/api/v1/pleroma/statuses/status%2Fone/reactions/party_blob%40remote.example',
expect.any(Object),
)
})
})
describe('fetchNotifications', () => {
it('defensively applies requested types when a server ignores the filter', async () => {
vi.stubGlobal(
+20
View File
@@ -314,6 +314,26 @@ export function reblogStatus(api: ApiClient, id: string, on: boolean): Promise<S
return api.post<Status>(`/api/v1/statuses/${encodeURIComponent(id)}/${action}`)
}
/**
* Add or remove one of the reactions already present on a status.
*
* Pleroma and Akkoma share this endpoint. `emoji` can be a Unicode emoji, a
* local custom shortcode, or the server-qualified `shortcode@host` returned in
* `pleroma.emoji_reactions`.
*/
export async function setEmojiReaction(
api: ApiClient,
id: string,
emoji: string,
on: boolean,
): Promise<Status> {
const path =
`/api/v1/pleroma/statuses/${encodeURIComponent(id)}` +
`/reactions/${encodeURIComponent(emoji)}`
const { data } = await api.raw<Status>(path, { method: on ? 'PUT' : 'DELETE' })
return data
}
export function votePoll(api: ApiClient, id: string, choices: number[]): Promise<Poll> {
return api.post<Poll>(`/api/v1/polls/${encodeURIComponent(id)}/votes`, { choices })
}
+17
View File
@@ -163,6 +163,21 @@ export interface Poll {
own_votes?: number[]
}
/** Pleroma/Akkoma's per-status emoji reaction summary. */
export interface EmojiReaction {
/** Unicode emoji, local custom shortcode, or `shortcode@remote.host`. */
name: string
count: number
/** Whether the authenticated viewer contributed to this count. */
me: boolean
/** Present for custom emoji reactions. */
url?: string | null
/** Included by some Pleroma/Akkoma status renderers. */
account_ids?: string[]
/** Included by the expanded reactions endpoint, but usually absent on timelines. */
accounts?: Account[]
}
export interface Status {
id: string
uri: string
@@ -205,6 +220,8 @@ export interface Status {
conversation_id?: number
content?: Record<string, string>
spoiler_text?: Record<string, string>
/** Pleroma/Akkoma emoji reactions, including custom emoji URLs. */
emoji_reactions?: EmojiReaction[]
}
}