pleroma/packages/pl-fe/src/actions/circle.ts
marcin mikołajczak 966b04fdf0 Call it pl-fe internally
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-08-28 13:41:08 +02:00

118 lines
4.1 KiB
TypeScript

// Loosely adapted from twitter-interaction-circles, licensed under MIT License
// https://github.com/duiker101/twitter-interaction-circles
import { getClient } from 'pl-fe/api';
import type { PaginatedResponse, Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
interface Interaction {
acct: string;
avatar?: string;
avatar_description?: string;
replies: number;
reblogs: number;
favourites: number;
}
const processCircle = (setProgress: (progress: {
state: 'pending' | 'fetchingStatuses' | 'fetchingFavourites' | 'fetchingAvatars' | 'drawing' | 'done';
progress: number;
}) => void) =>
async (dispatch: AppDispatch, getState: () => RootState) => {
setProgress({ state: 'pending', progress: 0 });
const client = getClient(getState());
const me = getState().me as string;
const interactions: Record<string, Interaction> = {};
const initInteraction = (accountId: string) => {
if (interactions[accountId]) return;
interactions[accountId] = {
acct: '',
replies: 0,
reblogs: 0,
favourites: 0,
};
};
const fetchStatuses = async (next: (() => Promise<PaginatedResponse<Status>>) | null) => {
const response = await (next?.() || client.accounts.getAccountStatuses(me, { limit: 40 }));
response.items.forEach((status) => {
if (status.reblog) {
if (status.reblog.account.id === me) return;
initInteraction(status.reblog.account.id);
const interaction = interactions[status.reblog.account.id];
interaction.reblogs += 1;
interaction.acct = status.reblog.account.acct;
interaction.avatar = status.reblog.account.avatar_static || status.reblog.account.avatar;
interaction.avatar_description = status.reblog.account.avatar_description;
} else if (status.in_reply_to_account_id) {
if (status.in_reply_to_account_id === me) return;
initInteraction(status.in_reply_to_account_id);
const interaction = interactions[status.in_reply_to_account_id];
interaction.replies += 1;
}
});
return response.next;
};
const fetchFavourites = async (next: (() => Promise<PaginatedResponse<Status>>) | null) => { // limit 40
const response = await (next?.() || client.myAccount.getFavourites({ limit: 40 }));
response.items.forEach((status) => {
if (status.account.id === me) return;
initInteraction(status.account.id);
const interaction = interactions[status.account.id];
interaction.favourites += 1;
interaction.acct = status.account.acct;
interaction.avatar = status.account.avatar_static || status.account.avatar;
interaction.avatar_description = status.account.avatar_description;
});
return response.next;
};
for (let next: (() => Promise<PaginatedResponse<Status>>) | null = null, i = 0; i < 20; i++) {
next = await fetchStatuses(next);
setProgress({ state: 'fetchingStatuses', progress: (i / 20) * 40 });
if (!next) break;
}
for (let next: (() => Promise<PaginatedResponse<Status>>) | null = null, i = 0; i < 20; i++) {
next = await fetchFavourites(next);
setProgress({ state: 'fetchingFavourites', progress: 40 + (i / 20) * 40 });
if (!next) break;
}
const result = await Promise.all(Object.entries(interactions).map(([id, { acct, avatar, avatar_description, favourites, reblogs, replies }]) => {
const score = favourites + replies * 1.1 + reblogs * 1.3;
return { id, acct, avatar, avatar_description, score };
}).toSorted((a, b) => b.score - a.score).slice(0, 49).map(async (interaction, index, array) => {
setProgress({ state: 'fetchingAvatars', progress: 80 + (index / array.length) * 10 });
if (interaction.acct) return interaction;
const account = await client.accounts.getAccount(interaction.id);
interaction.acct = account.acct;
interaction.avatar = account.avatar_static || account.avatar;
interaction.avatar_description = account.avatar_description;
return interaction;
}));
return result;
};
export {
processCircle,
};