114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
import path from 'node:path';
|
|
|
|
import cache from '@/utils/cache';
|
|
import got from '@/utils/got';
|
|
import { parseDate } from '@/utils/parse-date';
|
|
import { art } from '@/utils/render';
|
|
|
|
import type { MisskeyNote, MisskeyUser } from './types';
|
|
|
|
const allowSiteList = ['misskey.io', 'madost.one', 'mk.nixnet.social'];
|
|
|
|
const parseNotes = (data: MisskeyNote[], site: string, simplifyAuthor: boolean = false) =>
|
|
data.map((item: MisskeyNote) => {
|
|
const isRenote = item.renote && Object.keys(item.renote).length > 0;
|
|
const isReply = item.reply && Object.keys(item.reply).length > 0;
|
|
const noteToUse: MisskeyNote = isRenote ? (item.renote as MisskeyNote) : item;
|
|
|
|
const host = noteToUse.user.host ?? site;
|
|
const author = simplifyAuthor ? String(noteToUse.user.name) : `${noteToUse.user.name} (${noteToUse.user.username}@${host})`;
|
|
|
|
const description = art(path.join(__dirname, 'templates/note.art'), {
|
|
text: noteToUse.text,
|
|
files: noteToUse.files,
|
|
reply: item.reply,
|
|
site,
|
|
});
|
|
|
|
let title = '';
|
|
if (isReply && item.reply) {
|
|
const replyToHost = item.reply.user.host ?? site;
|
|
const replyToAuthor = simplifyAuthor ? item.reply.user.name : `${item.reply.user.name} (${item.reply.user.username}@${replyToHost})`;
|
|
title = `Reply to ${replyToAuthor}: "${noteToUse.text ?? ''}"`;
|
|
} else if (isRenote) {
|
|
title = `Renote: ${author}: "${noteToUse.text ?? ''}"`;
|
|
} else {
|
|
title = `${author}: "${noteToUse.text ?? ''}"`;
|
|
}
|
|
|
|
/**
|
|
* For renotes from non-Misskey instances (e.g. Mastodon, Pleroma),
|
|
* we can't use noteToUse.id to link to the original note since:
|
|
* 1. The URL format differs from Misskey's /notes/{id} pattern
|
|
* 2. Direct access to the original note may not be possible
|
|
* Therefore, we link to the renote itself in such cases
|
|
*/
|
|
let noteId = noteToUse.id;
|
|
|
|
if (isRenote) {
|
|
const renoteHost = item.user.host ?? site;
|
|
const noteHost = noteToUse.user.host ?? site;
|
|
|
|
// Use renote's ID if the note is from a different host or not in allowSiteList
|
|
if (renoteHost !== noteHost || !allowSiteList.includes(noteHost)) {
|
|
noteId = item.id;
|
|
}
|
|
}
|
|
const link = `https://${host}/notes/${noteId}`;
|
|
const pubDate = parseDate(noteToUse.createdAt);
|
|
|
|
return {
|
|
title,
|
|
description,
|
|
pubDate,
|
|
link,
|
|
author,
|
|
};
|
|
});
|
|
async function getUserTimelineByUsername(username, site, { withRenotes = false, mediaOnly = false }) {
|
|
const searchUrl = `https://${site}/api/users/search-by-username-and-host`;
|
|
const cacheUid = `misskey_username/${site}/${username}`;
|
|
|
|
const userData = (await cache.tryGet(cacheUid, async () => {
|
|
const searchResponse = await got({
|
|
method: 'post',
|
|
url: searchUrl,
|
|
json: {
|
|
username,
|
|
host: site,
|
|
detail: true,
|
|
limit: 1,
|
|
},
|
|
});
|
|
const user = searchResponse.data.find((item) => item.username === username);
|
|
|
|
if (!user) {
|
|
throw new Error(`username ${username} not found`);
|
|
}
|
|
return user;
|
|
})) as MisskeyUser;
|
|
|
|
const accountId = userData.id;
|
|
const avatarUrl = userData.avatarUrl;
|
|
|
|
// https://misskey.io/api-doc#tag/users/operation/users___notes
|
|
const usernotesUrl = `https://${site}/api/users/notes`;
|
|
const usernotesResponse = await got({
|
|
method: 'post',
|
|
url: usernotesUrl,
|
|
json: {
|
|
userId: accountId,
|
|
withChannelNotes: true,
|
|
withRenotes,
|
|
withReplies: !mediaOnly, // Disable replies if mediaOnly is true
|
|
withFiles: mediaOnly,
|
|
limit: 10,
|
|
offset: 0,
|
|
},
|
|
});
|
|
const accountData = usernotesResponse.data;
|
|
return { site, accountId, accountData, avatarUrl };
|
|
}
|
|
|
|
export default { parseNotes, getUserTimelineByUsername, allowSiteList };
|