Files
ncd-fe/packages/pl-api/lib/client/grouped-notifications.ts
nicole mikołajczyk d5d453e645 pl-api: allow importing parts of the client
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
2026-02-23 10:39:26 +01:00

243 lines
8.3 KiB
TypeScript

import omit from 'lodash.omit';
import pick from 'lodash.pick';
import * as v from 'valibot';
import { accountSchema, groupedNotificationsResultsSchema } from '../entities';
import { filteredArray } from '../entities/utils';
import { type RequestMeta } from '../request';
import type { PlApiBaseClient } from '../client-base';
import type {
Account,
GroupedNotificationsResults,
Notification,
NotificationGroup,
Status,
} from '../entities';
import type {
GetGroupedNotificationsParams,
GetUnreadNotificationGroupCountParams,
} from '../params/grouped-notifications';
import type { PaginatedResponse } from '../responses';
type EmptyObject = Record<string, never>;
const GROUPED_TYPES = [
'favourite',
'reblog',
'emoji_reaction',
'event_reminder',
'participation_accepted',
'participation_request',
];
const _groupNotifications = (
{ previous, next, items, ...response }: PaginatedResponse<Notification>,
params?: GetGroupedNotificationsParams,
): PaginatedResponse<GroupedNotificationsResults, false> => {
const notificationGroups: Array<NotificationGroup> = [];
for (const notification of items) {
let existingGroup: NotificationGroup | undefined;
if ((params?.grouped_types || GROUPED_TYPES).includes(notification.type)) {
existingGroup = notificationGroups.find(
(notificationGroup) =>
notificationGroup.type === notification.type &&
(notification.type === 'emoji_reaction' && notificationGroup.type === 'emoji_reaction'
? notification.emoji === notificationGroup.emoji
: true) &&
// @ts-expect-error used optional chaining
notificationGroup.status_id === notification.status?.id,
);
}
if (existingGroup) {
existingGroup.notifications_count += 1;
existingGroup.page_min_id = notification.id;
existingGroup.sample_account_ids.push(notification.account.id);
} else {
notificationGroups.push({
...omit(notification, ['account', 'status', 'target']),
group_key: notification.id,
notifications_count: 1,
most_recent_notification_id: notification.id,
page_min_id: notification.id,
page_max_id: notification.id,
latest_page_notification_at: notification.created_at,
sample_account_ids: [notification.account.id],
// @ts-expect-error used optional chaining
status_id: notification.status?.id,
// @ts-expect-error used optional chaining
target_id: notification.target?.id,
});
}
}
const groupedNotificationsResults: GroupedNotificationsResults = {
accounts: Object.values(
items.reduce<Record<string, Account>>((accounts, notification) => {
accounts[notification.account.id] = notification.account;
if ('target' in notification) accounts[notification.target.id] = notification.target;
return accounts;
}, {}),
),
statuses: Object.values(
items.reduce<Record<string, Status>>((statuses, notification) => {
if ('status' in notification && notification.status)
statuses[notification.status.id] = notification.status;
return statuses;
}, {}),
),
notification_groups: notificationGroups,
};
return {
...response,
previous: previous ? async () => _groupNotifications(await previous(), params) : null,
next: next ? async () => _groupNotifications(await next(), params) : null,
items: groupedNotificationsResults,
};
};
/**
* It is recommended to only use this with features{@link Features.groupedNotifications} available. However, there is a fallback that groups the notifications client-side.
*/
const groupedNotifications = (
client: PlApiBaseClient & {
notifications: ReturnType<typeof import('./notifications').notifications>;
},
) => {
const category = {
/**
* Get all grouped notifications
* Return grouped notifications concerning the user. This API returns Link headers containing links to the next/previous page. However, the links can also be constructed dynamically using query params and `id` values.
*
* Requires features{@link Features.groupedNotifications}.
* @see {@link https://docs.joinmastodon.org/methods/grouped_notifications/#get-grouped}
*/
getGroupedNotifications: async (params: GetGroupedNotificationsParams, meta?: RequestMeta) => {
if (client.features.groupedNotifications) {
return client.paginatedGet(
'/api/v2/notifications',
{ ...meta, params },
groupedNotificationsResultsSchema,
false,
);
}
const response = await client.notifications.getNotifications(
pick(params, [
'max_id',
'since_id',
'limit',
'min_id',
'types',
'exclude_types',
'account_id',
'include_filtered',
]),
);
return _groupNotifications(response, params);
},
/**
* Get a single notification group
* View information about a specific notification group with a given group key.
*
* Requires features{@link Features.groupedNotifications}.
* @see {@link https://docs.joinmastodon.org/methods/grouped_notifications/#get-notification-group}
*/
getNotificationGroup: async (groupKey: string) => {
if (client.features.groupedNotifications) {
const response = await client.request(`/api/v2/notifications/${groupKey}`);
return v.parse(groupedNotificationsResultsSchema, response.json);
}
const response = await client.request(`/api/v1/notifications/${groupKey}`);
return _groupNotifications({
previous: null,
next: null,
items: [response.json],
partial: false,
}).items;
},
/**
* Dismiss a single notification group
* Dismiss a single notification group from the server.
*
* Requires features{@link Features.groupedNotifications}.
* @see {@link https://docs.joinmastodon.org/methods/grouped_notifications/#dismiss-group}
*/
dismissNotificationGroup: async (groupKey: string) => {
if (client.features.groupedNotifications) {
const response = await client.request<EmptyObject>(
`/api/v2/notifications/${groupKey}/dismiss`,
{
method: 'POST',
},
);
return response.json;
}
return client.notifications.dismissNotification(groupKey);
},
/**
* Get accounts of all notifications in a notification group
*
* Requires features{@link Features.groupedNotifications}.
* @see {@link https://docs.joinmastodon.org/methods/grouped_notifications/#get-group-accounts}
*/
getNotificationGroupAccounts: async (groupKey: string) => {
if (client.features.groupedNotifications) {
const response = await client.request(`/api/v2/notifications/${groupKey}/accounts`);
return v.parse(filteredArray(accountSchema), response.json);
}
return (await category.getNotificationGroup(groupKey)).accounts;
},
/**
* Get the number of unread notifications
* Get the (capped) number of unread notification groups for the current user. A notification is considered unread if it is more recent than the notifications read marker. Because the count is dependant on the parameters, it is computed every time and is thus a relatively slow operation (although faster than getting the full corresponding notifications), therefore the number of returned notifications is capped.
*
* Requires features{@link Features.groupedNotifications}.
* @see {@link https://docs.joinmastodon.org/methods/grouped_notifications/#unread-group-count}
*/
getUnreadNotificationGroupCount: async (params: GetUnreadNotificationGroupCountParams) => {
if (client.features.groupedNotifications) {
const response = await client.request('/api/v2/notifications/unread_count', { params });
return v.parse(
v.object({
count: v.number(),
}),
response.json,
);
}
return client.notifications.getUnreadNotificationCount(
pick(params || {}, [
'max_id',
'since_id',
'limit',
'min_id',
'types',
'exclude_types',
'account_id',
]),
);
},
};
return category;
};
export { groupedNotifications };