From 82c10de2651107cb19c788d756cdeeb84122c0b0 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Sun, 29 Oct 2023 00:50:00 +0200 Subject: [PATCH 01/30] upd: change deps, fix a few bugs, update converter Fixes User and Notes count bug (transfem-org/Sharkey#113) Fixes build issues due to types (transfem-org/Sharkey#111) Return accounts and notes like Iceshrimp Use MFM class from Iceshrimp to fix HTML output for mastodon --- packages/backend/src/core/MfmService.ts | 208 ++++++++++++++++++ .../api/mastodon/MastodonApiServerService.ts | 44 ++-- .../src/server/api/mastodon/converters.ts | 177 ++++++++------- .../server/api/mastodon/endpoints/account.ts | 73 +++--- .../server/api/mastodon/endpoints/filter.ts | 8 +- .../api/mastodon/endpoints/notifications.ts | 9 +- .../server/api/mastodon/endpoints/search.ts | 6 +- .../server/api/mastodon/endpoints/status.ts | 67 +++--- .../server/api/mastodon/endpoints/timeline.ts | 44 ++-- packages/megalodon/package.json | 8 +- packages/megalodon/src/entities/account.ts | 9 +- packages/megalodon/src/entities/status.ts | 2 +- packages/megalodon/src/misskey.ts | 2 +- packages/megalodon/src/misskey/api_client.ts | 6 +- 14 files changed, 421 insertions(+), 242 deletions(-) diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index b275d1b14..91690aec1 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -388,4 +388,212 @@ export class MfmService { return `

${doc.body.innerHTML}

`; } + + @bindThis + public async toMastoHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], inline = false, quoteUri: string | null = null) { + if (nodes == null) { + return null; + } + + const { window } = new Window(); + + const doc = window.document; + + async function appendChildren(children: mfm.MfmNode[], targetElement: any): Promise { + if (children) { + for (const child of await Promise.all(children.map(async (x) => await (handlers as any)[x.type](x)))) targetElement.appendChild(child); + } + } + + const handlers: { + [K in mfm.MfmNode['type']]: (node: mfm.NodeType) => any; + } = { + async bold(node) { + const el = doc.createElement('span'); + el.textContent = '**'; + await appendChildren(node.children, el); + el.textContent += '**'; + return el; + }, + + async small(node) { + const el = doc.createElement('small'); + await appendChildren(node.children, el); + return el; + }, + + async strike(node) { + const el = doc.createElement('span'); + el.textContent = '~~'; + await appendChildren(node.children, el); + el.textContent += '~~'; + return el; + }, + + async italic(node) { + const el = doc.createElement('span'); + el.textContent = '*'; + await appendChildren(node.children, el); + el.textContent += '*'; + return el; + }, + + async fn(node) { + const el = doc.createElement('span'); + el.textContent = '*'; + await appendChildren(node.children, el); + el.textContent += '*'; + return el; + }, + + blockCode(node) { + const pre = doc.createElement('pre'); + const inner = doc.createElement('code'); + + const nodes = node.props.code + .split(/\r\n|\r|\n/) + .map((x) => doc.createTextNode(x)); + + for (const x of intersperse('br', nodes)) { + inner.appendChild(x === 'br' ? doc.createElement('br') : x); + } + + pre.appendChild(inner); + return pre; + }, + + async center(node) { + const el = doc.createElement('div'); + await appendChildren(node.children, el); + return el; + }, + + emojiCode(node) { + return doc.createTextNode(`\u200B:${node.props.name}:\u200B`); + }, + + unicodeEmoji(node) { + return doc.createTextNode(node.props.emoji); + }, + + hashtag: (node) => { + const a = doc.createElement('a'); + a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`); + a.textContent = `#${node.props.hashtag}`; + a.setAttribute('rel', 'tag'); + a.setAttribute('class', 'hashtag'); + return a; + }, + + inlineCode(node) { + const el = doc.createElement('code'); + el.textContent = node.props.code; + return el; + }, + + mathInline(node) { + const el = doc.createElement('code'); + el.textContent = node.props.formula; + return el; + }, + + mathBlock(node) { + const el = doc.createElement('code'); + el.textContent = node.props.formula; + return el; + }, + + async link(node) { + const a = doc.createElement('a'); + a.setAttribute('rel', 'nofollow noopener noreferrer'); + a.setAttribute('target', '_blank'); + a.setAttribute('href', node.props.url); + await appendChildren(node.children, a); + return a; + }, + + async mention(node) { + const { username, host, acct } = node.props; + const resolved = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host); + + const el = doc.createElement('span'); + if (!resolved) { + el.textContent = acct; + } else { + el.setAttribute('class', 'h-card'); + el.setAttribute('translate', 'no'); + const a = doc.createElement('a'); + a.setAttribute('href', resolved.url ? resolved.url : resolved.uri); + a.className = 'u-url mention'; + const span = doc.createElement('span'); + span.textContent = resolved.username || username; + a.textContent = '@'; + a.appendChild(span); + el.appendChild(a); + } + + return el; + }, + + async quote(node) { + const el = doc.createElement('blockquote'); + await appendChildren(node.children, el); + return el; + }, + + text(node) { + const el = doc.createElement('span'); + const nodes = node.props.text + .split(/\r\n|\r|\n/) + .map((x) => doc.createTextNode(x)); + + for (const x of intersperse('br', nodes)) { + el.appendChild(x === 'br' ? doc.createElement('br') : x); + } + + return el; + }, + + url(node) { + const a = doc.createElement('a'); + a.setAttribute('rel', 'nofollow noopener noreferrer'); + a.setAttribute('target', '_blank'); + a.setAttribute('href', node.props.url); + a.textContent = node.props.url.replace(/^https?:\/\//, ''); + return a; + }, + + search: (node) => { + const a = doc.createElement('a'); + a.setAttribute('href', `https"google.com/${node.props.query}`); + a.textContent = node.props.content; + return a; + }, + + async plain(node) { + const el = doc.createElement('span'); + await appendChildren(node.children, el); + return el; + }, + }; + + await appendChildren(nodes, doc.body); + + if (quoteUri !== null) { + const a = doc.createElement('a'); + a.setAttribute('href', quoteUri); + a.textContent = quoteUri.replace(/^https?:\/\//, ''); + + const quote = doc.createElement('span'); + quote.setAttribute('class', 'quote-inline'); + quote.appendChild(doc.createElement('br')); + quote.appendChild(doc.createElement('br')); + quote.innerHTML += 'RE: '; + quote.appendChild(a); + + doc.body.appendChild(quote); + } + + return inline ? doc.body.innerHTML : `

${doc.body.innerHTML}

`; + } } diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index a8c45b98f..9a2890f50 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; -import { convertId, IdConvertType as IdType, convertAccount, convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList } from './converters.js'; +import { convertAccount, convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList } from './converters.js'; import { getInstance } from './endpoints/meta.js'; import { ApiAuthMastodon, ApiAccountMastodon, ApiFilterMastodon, ApiNotifyMastodon, ApiSearchMastodon, ApiTimelineMastodon, ApiStatusMastodon } from './endpoints.js'; import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; @@ -128,7 +128,7 @@ export class MastodonApiServerService { const client = getClient(BASE_URL, accessTokens); try { const data = await client.dismissInstanceAnnouncement( - convertId(_request.body['id'], IdType.SharkeyId), + _request.body['id'], ); reply.send(data.data); } catch (e: any) { @@ -236,7 +236,7 @@ export class MastodonApiServerService { const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt // displayed without being logged in try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.verifyCredentials()); } catch (e: any) { /* console.error(e); */ @@ -286,7 +286,7 @@ export class MastodonApiServerService { ids = [ids]; } users = ids; - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.getRelationships(users)); } catch (e: any) { /* console.error(e); */ @@ -302,7 +302,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const sharkId = convertId(_request.params.id, IdType.SharkeyId); + const sharkId = _request.params.id; const data = await client.getAccount(sharkId); const profile = await this.userProfilesRepository.findOneBy({ userId: sharkId }); data.data.fields = profile?.fields.map(f => ({ ...f, verified_at: null })) || []; @@ -319,7 +319,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.getStatuses()); } catch (e: any) { /* console.error(e); @@ -347,7 +347,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.getFollowers()); } catch (e: any) { /* console.error(e); @@ -361,7 +361,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.getFollowing()); } catch (e: any) { /* console.error(e); @@ -375,7 +375,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getAccountLists(convertId(_request.params.id, IdType.SharkeyId)); + const data = await client.getAccountLists(_request.params.id); reply.send(data.data.map((list) => convertList(list))); } catch (e: any) { /* console.error(e); @@ -389,7 +389,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.addFollow()); } catch (e: any) { /* console.error(e); @@ -403,7 +403,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.rmFollow()); } catch (e: any) { /* console.error(e); @@ -417,7 +417,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.addBlock()); } catch (e: any) { /* console.error(e); @@ -431,7 +431,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.rmBlock()); } catch (e: any) { /* console.error(e); @@ -445,7 +445,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.addMute()); } catch (e: any) { /* console.error(e); @@ -459,7 +459,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.rmMute()); } catch (e: any) { /* console.error(e); @@ -487,7 +487,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.getBookmarks()); } catch (e: any) { /* console.error(e); @@ -501,7 +501,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.getFavourites()); } catch (e: any) { /* console.error(e); @@ -515,7 +515,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.getMutes()); } catch (e: any) { /* console.error(e); @@ -529,7 +529,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.getBlocks()); } catch (e: any) { /* console.error(e); @@ -557,7 +557,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.acceptFollow()); } catch (e: any) { /* console.error(e); @@ -571,7 +571,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); reply.send(await account.rejectFollow()); } catch (e: any) { /* console.error(e); @@ -813,7 +813,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.updateMedia(convertId(_request.params.id, IdType.SharkeyId), _request.body!); + const data = await client.updateMedia(_request.params.id, _request.body!); reply.send(convertAttachment(data.data)); } catch (e: any) { /* console.error(e); */ diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index cbd2550f9..a83914560 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -3,14 +3,13 @@ import { MfmService } from '@/core/MfmService.js'; import { DI } from '@/di-symbols.js'; import { Inject } from '@nestjs/common'; import { Entity } from 'megalodon'; -import { parse } from 'mfm-js'; +import mfm from 'mfm-js'; import { GetterService } from '../GetterService.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js'; import type { MiUser } from '@/models/User.js'; import type { NoteEditRepository, NotesRepository, UsersRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; - -const CHAR_COLLECTION = '0123456789abcdefghijklmnopqrstuvwxyz'; +import { awaitAll } from '@/misc/prelude/await-all.js'; export enum IdConvertType { MastodonId, @@ -18,13 +17,13 @@ export enum IdConvertType { } export const escapeMFM = (text: string): string => text - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'") - .replace(/`/g, "`") - .replace(/\r?\n/g, "
"); + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") + .replace(/`/g, "`") + .replace(/\r?\n/g, "
"); export class MastoConverters { private MfmService: MfmService; @@ -49,7 +48,7 @@ export class MastoConverters { this.GetterService = new GetterService(this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); } - private encode(u: MiUser, m: IMentionedRemoteUsers): MastodonEntity.Mention { + private encode(u: MiUser, m: IMentionedRemoteUsers): Entity.Mention { let acct = u.username; let acctUrl = `https://${u.host || this.config.host}/@${u.username}`; let url: string | null = null; @@ -73,89 +72,97 @@ export class MastoConverters { }); } + public async convertAccount(account: Entity.Account) { + return awaitAll({ + id: account.id, + username: account.username, + acct: account.acct, + fqn: account.fqn, + display_name: account.display_name || account.username, + locked: account.locked, + created_at: account.created_at, + followers_count: account.followers_count, + following_count: account.following_count, + statuses_count: account.statuses_count, + note: account.note, + url: account.url, + avatar: account.avatar, + avatar_static: account.avatar, + header: account.header, + header_static: account.header, + emojis: account.emojis, + moved: null, //FIXME + fields: [], + bot: false, + discoverable: true, + }); + } + public async convertStatus(status: Entity.Status) { - status.account = convertAccount(status.account); + const convertedAccount = this.convertAccount(status.account); const note = await this.GetterService.getNote(status.id); - status.id = convertId(status.id, IdConvertType.MastodonId); - if (status.in_reply_to_account_id) status.in_reply_to_account_id = convertId( - status.in_reply_to_account_id, - IdConvertType.MastodonId, - ); - if (status.in_reply_to_id) status.in_reply_to_id = convertId(status.in_reply_to_id, IdConvertType.MastodonId); - status.media_attachments = status.media_attachments.map((attachment) => - convertAttachment(attachment), - ); - // This will eventually be improved with a rewrite of this file + const mentions = Promise.all(note.mentions.map(p => this.getUser(p) .then(u => this.encode(u, JSON.parse(note.mentionedRemoteUsers))) .catch(() => null))) - .then(p => p.filter(m => m)) as Promise; - status.mentions = await mentions; - status.mentions = status.mentions.map((mention) => ({ - ...mention, - id: convertId(mention.id, IdConvertType.MastodonId), - })); - const convertedMFM = this.MfmService.toHtml(parse(status.content), JSON.parse(note.mentionedRemoteUsers)); - status.content = status.content ? convertedMFM?.replace(/&/g, "&").replaceAll(`&`, "\'") as string : status.content; - if (status.poll) status.poll = convertPoll(status.poll); - if (status.reblog) status.reblog = convertStatus(status.reblog); - - return status; - } -} + .then(p => p.filter(m => m)) as Promise; -export function convertId(in_id: string, id_convert_type: IdConvertType): string { - switch (id_convert_type) { - case IdConvertType.MastodonId: { - let out = BigInt(0); - const lowerCaseId = in_id.toLowerCase(); - for (let i = 0; i < lowerCaseId.length; i++) { - const charValue = numFromChar(lowerCaseId.charAt(i)); - out += BigInt(charValue) * BigInt(36) ** BigInt(i); - } - return out.toString(); - } - - case IdConvertType.SharkeyId: { - let input = BigInt(in_id); - let outStr = ''; - while (input > BigInt(0)) { - const remainder = Number(input % BigInt(36)); - outStr = charFromNum(remainder) + outStr; - input /= BigInt(36); - } - const ReversedoutStr = outStr.split('').reduce((acc, char) => char + acc, ''); - return ReversedoutStr; - } - - default: - throw new Error('Invalid ID conversion type'); - } -} + const content = note.text !== null + ? this.MfmService.toMastoHtml(mfm.parse(note.text!), JSON.parse(note.mentionedRemoteUsers), false, null) + .then(p => p ?? escapeMFM(note.text!)) + : ''; -function numFromChar(character: string): number { - for (let i = 0; i < CHAR_COLLECTION.length; i++) { - if (CHAR_COLLECTION.charAt(i) === character) { - return i; - } - } + const tags = note.tags.map(tag => { + return { + name: tag, + url: `${this.config.url}/tags/${tag}`, + } as Entity.Tag; + }); - throw new Error('Invalid character in parsed base36 id'); -} - -function charFromNum(number: number): string { - if (number >= 0 && number < CHAR_COLLECTION.length) { - return CHAR_COLLECTION.charAt(number); - } else { - throw new Error('Invalid number for base-36 encoding'); + // noinspection ES6MissingAwait + return await awaitAll({ + id: note.id, + uri: note.uri ?? `https://${this.config.host}/notes/${note.id}`, + url: note.url ?? note.uri ?? `https://${this.config.host}/notes/${note.id}`, + account: convertedAccount, + in_reply_to_id: note.replyId, + in_reply_to_account_id: note.replyUserId, + reblog: status.reblog, + content: content, + content_type: 'text/x.misskeymarkdown', + text: note.text, + created_at: status.created_at, + emojis: status.emojis, + replies_count: note.repliesCount, + reblogs_count: note.renoteCount, + favourites_count: status.favourites_count, + reblogged: false, + favourited: status.favourited, + muted: status.muted, + sensitive: status.sensitive, + spoiler_text: note.cw ? note.cw : '', + visibility: status.visibility, + media_attachments: status.media_attachments, + mentions: mentions, + tags: tags, + card: null, //FIXME + poll: status.poll ?? null, + application: null, //FIXME + language: null, //FIXME + pinned: null, + reactions: status.emoji_reactions, + emoji_reactions: status.emoji_reactions, + bookmarked: false, + quote: false, + edited_at: note.updatedAt?.toISOString(), + }); } } function simpleConvert(data: any) { // copy the object to bypass weird pass by reference bugs const result = Object.assign({}, data); - result.id = convertId(data.id, IdConvertType.MastodonId); return result; } @@ -180,7 +187,6 @@ export function convertFeaturedTag(tag: Entity.FeaturedTag) { export function convertNotification(notification: Entity.Notification) { notification.account = convertAccount(notification.account); - notification.id = convertId(notification.id, IdConvertType.MastodonId); if (notification.status) notification.status = convertStatus(notification.status); return notification; } @@ -200,19 +206,9 @@ export function convertRelationship(relationship: Entity.Relationship) { export function convertStatus(status: Entity.Status) { status.account = convertAccount(status.account); - status.id = convertId(status.id, IdConvertType.MastodonId); - if (status.in_reply_to_account_id) status.in_reply_to_account_id = convertId( - status.in_reply_to_account_id, - IdConvertType.MastodonId, - ); - if (status.in_reply_to_id) status.in_reply_to_id = convertId(status.in_reply_to_id, IdConvertType.MastodonId); status.media_attachments = status.media_attachments.map((attachment) => convertAttachment(attachment), ); - status.mentions = status.mentions.map((mention) => ({ - ...mention, - id: convertId(mention.id, IdConvertType.MastodonId), - })); if (status.poll) status.poll = convertPoll(status.poll); if (status.reblog) status.reblog = convertStatus(status.reblog); @@ -224,7 +220,6 @@ export function convertStatusSource(status: Entity.StatusSource) { } export function convertConversation(conversation: Entity.Conversation) { - conversation.id = convertId(conversation.id, IdConvertType.MastodonId); conversation.accounts = conversation.accounts.map(convertAccount); if (conversation.last_status) { conversation.last_status = convertStatus(conversation.last_status); diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 24ebe0c48..802f1f5a6 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -1,7 +1,10 @@ -import { convertId, IdConvertType as IdType, convertAccount, convertRelationship, convertStatus } from '../converters.js'; -import { argsToBools, convertTimelinesArgsId, limitToInt } from './timeline.js'; +import { MastoConverters, convertRelationship } from '../converters.js'; +import { argsToBools, limitToInt } from './timeline.js'; import type { MegalodonInterface } from 'megalodon'; import type { FastifyRequest } from 'fastify'; +import { NoteEditRepository, NotesRepository, UsersRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import type { Config } from '@/config.js'; const relationshipModel = { id: '', @@ -24,18 +27,25 @@ export class ApiAccountMastodon { private request: FastifyRequest; private client: MegalodonInterface; private BASE_URL: string; + private mastoconverter: MastoConverters; - constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string) { + constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string, + config: Config, + usersrepo: UsersRepository, + notesrepo: NotesRepository, + noteeditrepo: NoteEditRepository, + userentity: UserEntityService, + ) { this.request = request; this.client = client; this.BASE_URL = BASE_URL; + this.mastoconverter = new MastoConverters(config, usersrepo, notesrepo, noteeditrepo, userentity); } public async verifyCredentials() { try { const data = await this.client.verifyAccountCredentials(); const acct = data.data; - acct.id = convertId(acct.id, IdType.MastodonId); acct.display_name = acct.display_name || acct.username; acct.url = `${this.BASE_URL}/@${acct.url}`; acct.note = acct.note || ''; @@ -61,7 +71,7 @@ export class ApiAccountMastodon { public async lookup() { try { const data = await this.client.search((this.request.query as any).acct, { type: 'accounts' }); - return convertAccount(data.data.accounts[0]); + return this.mastoconverter.convertAccount(data.data.accounts[0]); } catch (e: any) { /* console.error(e) console.error(e.response.data); */ @@ -79,7 +89,7 @@ export class ApiAccountMastodon { const reqIds = []; for (let i = 0; i < users.length; i++) { - reqIds.push(convertId(users[i], IdType.SharkeyId)); + reqIds.push(users[i]); } const data = await this.client.getRelationships(reqIds); @@ -93,11 +103,8 @@ export class ApiAccountMastodon { public async getStatuses() { try { - const data = await this.client.getAccountStatuses( - convertId((this.request.params as any).id, IdType.SharkeyId), - convertTimelinesArgsId(argsToBools(limitToInt(this.request.query as any))) - ); - return data.data.map((status) => convertStatus(status)); + const data = await this.client.getAccountStatuses((this.request.params as any).id, argsToBools(limitToInt(this.request.query as any))); + return data.data.map((status) => this.mastoconverter.convertStatus(status)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -108,10 +115,10 @@ export class ApiAccountMastodon { public async getFollowers() { try { const data = await this.client.getAccountFollowers( - convertId((this.request.params as any).id, IdType.SharkeyId), - convertTimelinesArgsId(limitToInt(this.request.query as any)), + (this.request.params as any).id, + limitToInt(this.request.query as any), ); - return data.data.map((account) => convertAccount(account)); + return data.data.map((account) => this.mastoconverter.convertAccount(account)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -122,10 +129,10 @@ export class ApiAccountMastodon { public async getFollowing() { try { const data = await this.client.getAccountFollowing( - convertId((this.request.params as any).id, IdType.SharkeyId), - convertTimelinesArgsId(limitToInt(this.request.query as any)), + (this.request.params as any).id, + limitToInt(this.request.query as any), ); - return data.data.map((account) => convertAccount(account)); + return data.data.map((account) => this.mastoconverter.convertAccount(account)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -135,7 +142,7 @@ export class ApiAccountMastodon { public async addFollow() { try { - const data = await this.client.followAccount( convertId((this.request.params as any).id, IdType.SharkeyId) ); + const data = await this.client.followAccount( (this.request.params as any).id ); const acct = convertRelationship(data.data); acct.following = true; return acct; @@ -148,7 +155,7 @@ export class ApiAccountMastodon { public async rmFollow() { try { - const data = await this.client.unfollowAccount( convertId((this.request.params as any).id, IdType.SharkeyId) ); + const data = await this.client.unfollowAccount( (this.request.params as any).id ); const acct = convertRelationship(data.data); acct.following = false; return acct; @@ -161,7 +168,7 @@ export class ApiAccountMastodon { public async addBlock() { try { - const data = await this.client.blockAccount( convertId((this.request.params as any).id, IdType.SharkeyId) ); + const data = await this.client.blockAccount( (this.request.params as any).id ); return convertRelationship(data.data); } catch (e: any) { console.error(e); @@ -172,7 +179,7 @@ export class ApiAccountMastodon { public async rmBlock() { try { - const data = await this.client.unblockAccount( convertId((this.request.params as any).id, IdType.SharkeyId) ); + const data = await this.client.unblockAccount( (this.request.params as any).id ); return convertRelationship(data.data); } catch (e: any) { console.error(e); @@ -184,7 +191,7 @@ export class ApiAccountMastodon { public async addMute() { try { const data = await this.client.muteAccount( - convertId((this.request.params as any).id, IdType.SharkeyId), + (this.request.params as any).id, this.request.body as any, ); return convertRelationship(data.data); @@ -197,7 +204,7 @@ export class ApiAccountMastodon { public async rmMute() { try { - const data = await this.client.unmuteAccount( convertId((this.request.params as any).id, IdType.SharkeyId) ); + const data = await this.client.unmuteAccount( (this.request.params as any).id ); return convertRelationship(data.data); } catch (e: any) { console.error(e); @@ -208,8 +215,8 @@ export class ApiAccountMastodon { public async getBookmarks() { try { - const data = await this.client.getBookmarks( convertTimelinesArgsId(limitToInt(this.request.query as any)) ); - return data.data.map((status) => convertStatus(status)); + const data = await this.client.getBookmarks( limitToInt(this.request.query as any) ); + return data.data.map((status) => this.mastoconverter.convertStatus(status)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -219,8 +226,8 @@ export class ApiAccountMastodon { public async getFavourites() { try { - const data = await this.client.getFavourites( convertTimelinesArgsId(limitToInt(this.request.query as any)) ); - return data.data.map((status) => convertStatus(status)); + const data = await this.client.getFavourites( limitToInt(this.request.query as any) ); + return data.data.map((status) => this.mastoconverter.convertStatus(status)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -230,8 +237,8 @@ export class ApiAccountMastodon { public async getMutes() { try { - const data = await this.client.getMutes( convertTimelinesArgsId(limitToInt(this.request.query as any)) ); - return data.data.map((account) => convertAccount(account)); + const data = await this.client.getMutes( limitToInt(this.request.query as any) ); + return data.data.map((account) => this.mastoconverter.convertAccount(account)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -241,8 +248,8 @@ export class ApiAccountMastodon { public async getBlocks() { try { - const data = await this.client.getBlocks( convertTimelinesArgsId(limitToInt(this.request.query as any)) ); - return data.data.map((account) => convertAccount(account)); + const data = await this.client.getBlocks( limitToInt(this.request.query as any) ); + return data.data.map((account) => this.mastoconverter.convertAccount(account)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -252,7 +259,7 @@ export class ApiAccountMastodon { public async acceptFollow() { try { - const data = await this.client.acceptFollowRequest( convertId((this.request.params as any).id, IdType.SharkeyId) ); + const data = await this.client.acceptFollowRequest( (this.request.params as any).id ); return convertRelationship(data.data); } catch (e: any) { console.error(e); @@ -263,7 +270,7 @@ export class ApiAccountMastodon { public async rejectFollow() { try { - const data = await this.client.rejectFollowRequest( convertId((this.request.params as any).id, IdType.SharkeyId) ); + const data = await this.client.rejectFollowRequest( (this.request.params as any).id ); return convertRelationship(data.data); } catch (e: any) { console.error(e); diff --git a/packages/backend/src/server/api/mastodon/endpoints/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts index e27bc956f..212c79b25 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/filter.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/filter.ts @@ -1,4 +1,4 @@ -import { IdConvertType as IdType, convertId, convertFilter } from '../converters.js'; +import { convertFilter } from '../converters.js'; import type { MegalodonInterface } from 'megalodon'; import type { FastifyRequest } from 'fastify'; @@ -23,7 +23,7 @@ export class ApiFilterMastodon { public async getFilter() { try { - const data = await this.client.getFilter( convertId((this.request.params as any).id, IdType.SharkeyId) ); + const data = await this.client.getFilter( (this.request.params as any).id ); return convertFilter(data.data); } catch (e: any) { console.error(e); @@ -45,7 +45,7 @@ export class ApiFilterMastodon { public async updateFilter() { try { const body: any = this.request.body; - const data = await this.client.updateFilter(convertId((this.request.params as any).id, IdType.SharkeyId), body.pharse, body.context); + const data = await this.client.updateFilter((this.request.params as any).id, body.pharse, body.context); return convertFilter(data.data); } catch (e: any) { console.error(e); @@ -55,7 +55,7 @@ export class ApiFilterMastodon { public async rmFilter() { try { - const data = await this.client.deleteFilter( convertId((this.request.params as any).id, IdType.SharkeyId) ); + const data = await this.client.deleteFilter( (this.request.params as any).id ); return data.data; } catch (e: any) { console.error(e); diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts index dc801dd05..c4628b58c 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts @@ -1,5 +1,4 @@ -import { IdConvertType as IdType, convertId, convertNotification } from '../converters.js'; -import { convertTimelinesArgsId } from './timeline.js'; +import { convertNotification } from '../converters.js'; import type { MegalodonInterface, Entity } from 'megalodon'; import type { FastifyRequest } from 'fastify'; @@ -19,7 +18,7 @@ export class ApiNotifyMastodon { public async getNotifications() { try { - const data = await this.client.getNotifications( convertTimelinesArgsId(toLimitToInt(this.request.query)) ); + const data = await this.client.getNotifications( toLimitToInt(this.request.query) ); const notifs = data.data; const processed = notifs.map((n: Entity.Notification) => { const convertedn = convertNotification(n); @@ -39,7 +38,7 @@ export class ApiNotifyMastodon { public async getNotification() { try { - const data = await this.client.getNotification( convertId((this.request.params as any).id, IdType.SharkeyId) ); + const data = await this.client.getNotification( (this.request.params as any).id ); const notif = convertNotification(data.data); if (notif.type !== 'follow' && notif.type !== 'follow_request' && notif.type === 'reaction') notif.type = 'favourite'; return notif; @@ -51,7 +50,7 @@ export class ApiNotifyMastodon { public async rmNotification() { try { - const data = await this.client.dismissNotification( convertId((this.request.params as any).id, IdType.SharkeyId) ); + const data = await this.client.dismissNotification( (this.request.params as any).id ); return data.data; } catch (e: any) { console.error(e); diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index 5c68402ed..d5839ff1c 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -1,6 +1,6 @@ import { Converter } from 'megalodon'; import { convertAccount, convertStatus } from '../converters.js'; -import { convertTimelinesArgsId, limitToInt } from './timeline.js'; +import { limitToInt } from './timeline.js'; import type { MegalodonInterface } from 'megalodon'; import type { FastifyRequest } from 'fastify'; @@ -71,7 +71,7 @@ export class ApiSearchMastodon { public async SearchV1() { try { - const query: any = convertTimelinesArgsId(limitToInt(this.request.query as any)); + const query: any = limitToInt(this.request.query as any); const type = query.type || ''; const data = await this.client.search(query.q, { type: type, ...query }); return data.data; @@ -83,7 +83,7 @@ export class ApiSearchMastodon { public async SearchV2() { try { - const query: any = convertTimelinesArgsId(limitToInt(this.request.query as any)); + const query: any = limitToInt(this.request.query as any); const type = query.type; const acct = !type || type === 'accounts' ? await this.client.search(query.q, { type: 'accounts', ...query }) : null; const stat = !type || type === 'statuses' ? await this.client.search(query.q, { type: 'statuses', ...query }) : null; diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index 2690a1036..a15e9761b 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -1,8 +1,8 @@ import querystring from 'querystring'; import { emojiRegexAtStartToEnd } from '@/misc/emoji-regex.js'; -import { convertId, IdConvertType as IdType, convertAccount, convertAttachment, convertPoll, convertStatusSource, MastoConverters } from '../converters.js'; +import { convertAttachment, convertPoll, convertStatusSource, MastoConverters } from '../converters.js'; import { getClient } from '../MastodonApiServerService.js'; -import { convertTimelinesArgsId, limitToInt } from './timeline.js'; +import { limitToInt } from './timeline.js'; import type { Entity } from 'megalodon'; import type { FastifyInstance } from 'fastify'; import type { Config } from '@/config.js'; @@ -29,7 +29,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getStatus(convertId(_request.params.id, IdType.SharkeyId)); + const data = await client.getStatus(_request.params.id); reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); @@ -44,8 +44,8 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getStatusSource(convertId(_request.params.id, IdType.SharkeyId)); - reply.send(convertStatusSource(data.data)); + const data = await client.getStatusSource(_request.params.id); + reply.send(data.data); } catch (e: any) { console.error(e); reply.code(_request.is404 ? 404 : 401).send(e.response.data); @@ -60,10 +60,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); const query: any = _request.query; try { - const data = await client.getStatusContext( - convertId(_request.params.id, IdType.SharkeyId), - convertTimelinesArgsId(limitToInt(query)), - ); + const data = await client.getStatusContext(_request.params.id, limitToInt(query)); data.data.ancestors = await Promise.all(data.data.ancestors.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status))); data.data.descendants = await Promise.all(data.data.descendants.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status))); reply.send(data.data); @@ -91,8 +88,8 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getStatusRebloggedBy(convertId(_request.params.id, IdType.SharkeyId)); - reply.send(data.data.map((account: Entity.Account) => convertAccount(account))); + const data = await client.getStatusRebloggedBy(_request.params.id); + reply.send(data.data.map((account: Entity.Account) => this.mastoconverter.convertAccount(account))); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -106,8 +103,8 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getStatusFavouritedBy(convertId(_request.params.id, IdType.SharkeyId)); - reply.send(data.data.map((account: Entity.Account) => convertAccount(account))); + const data = await client.getStatusFavouritedBy(_request.params.id); + reply.send(data.data.map((account: Entity.Account) => this.mastoconverter.convertAccount(account))); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -121,7 +118,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getMedia(convertId(_request.params.id, IdType.SharkeyId)); + const data = await client.getMedia(_request.params.id); reply.send(convertAttachment(data.data)); } catch (e: any) { console.error(e); @@ -136,7 +133,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getPoll(convertId(_request.params.id, IdType.SharkeyId)); + const data = await client.getPoll(_request.params.id); reply.send(convertPoll(data.data)); } catch (e: any) { console.error(e); @@ -152,7 +149,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); const body: any = _request.body; try { - const data = await client.votePoll(convertId(_request.params.id, IdType.SharkeyId), body.choices); + const data = await client.votePoll(_request.params.id, body.choices); reply.send(convertPoll(data.data)); } catch (e: any) { console.error(e); @@ -168,8 +165,6 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); let body: any = _request.body; try { - if (body.in_reply_to_id) body.in_reply_to_id = convertId(body.in_reply_to_id, IdType.SharkeyId); - if (body.quote_id) body.quote_id = convertId(body.quote_id, IdType.SharkeyId); if ( (!body.poll && body['poll[options][]']) || (!body.media_ids && body['media_ids[]']) @@ -201,9 +196,6 @@ export class ApiStatusMastodon { } if (!body.media_ids) body.media_ids = undefined; if (body.media_ids && !body.media_ids.length) body.media_ids = undefined; - if (body.media_ids) { - body.media_ids = (body.media_ids as string[]).map((p) => convertId(p, IdType.SharkeyId)); - } const { sensitive } = body; body.sensitive = typeof sensitive === 'string' ? sensitive === 'true' : sensitive; @@ -241,10 +233,7 @@ export class ApiStatusMastodon { try { if (!body.media_ids) body.media_ids = undefined; if (body.media_ids && !body.media_ids.length) body.media_ids = undefined; - if (body.media_ids) { - body.media_ids = (body.media_ids as string[]).map((p) => convertId(p, IdType.SharkeyId)); - } - const data = await client.editStatus(convertId(_request.params.id, IdType.SharkeyId), body); + const data = await client.editStatus(_request.params.id, body); reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); @@ -259,10 +248,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = (await client.createEmojiReaction( - convertId(_request.params.id, IdType.SharkeyId), - '❤', - )) as any; + const data = (await client.createEmojiReaction(_request.params.id, '❤')) as any; reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); @@ -277,10 +263,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.deleteEmojiReaction( - convertId(_request.params.id, IdType.SharkeyId), - '❤', - ); + const data = await client.deleteEmojiReaction(_request.params.id, '❤'); reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); @@ -295,7 +278,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.reblogStatus(convertId(_request.params.id, IdType.SharkeyId)); + const data = await client.reblogStatus(_request.params.id); reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); @@ -310,7 +293,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.unreblogStatus(convertId(_request.params.id, IdType.SharkeyId)); + const data = await client.unreblogStatus(_request.params.id); reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); @@ -325,7 +308,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.bookmarkStatus(convertId(_request.params.id, IdType.SharkeyId)); + const data = await client.bookmarkStatus(_request.params.id); reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); @@ -340,7 +323,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.unbookmarkStatus(convertId(_request.params.id, IdType.SharkeyId)); + const data = await client.unbookmarkStatus(_request.params.id); reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); @@ -355,7 +338,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.pinStatus(convertId(_request.params.id, IdType.SharkeyId)); + const data = await client.pinStatus(_request.params.id); reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); @@ -370,7 +353,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.unpinStatus(convertId(_request.params.id, IdType.SharkeyId)); + const data = await client.unpinStatus(_request.params.id); reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); @@ -385,7 +368,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.createEmojiReaction(convertId(_request.params.id, IdType.SharkeyId), _request.params.name); + const data = await client.createEmojiReaction(_request.params.id, _request.params.name); reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); @@ -400,7 +383,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.deleteEmojiReaction(convertId(_request.params.id, IdType.SharkeyId), _request.params.name); + const data = await client.deleteEmojiReaction(_request.params.id, _request.params.name); reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); @@ -415,7 +398,7 @@ export class ApiStatusMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.deleteStatus(convertId(_request.params.id, IdType.SharkeyId)); + const data = await client.deleteStatus(_request.params.id); reply.send(data.data); } catch (e: any) { console.error(e); diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index e4f510ea2..b1b487f39 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -1,5 +1,5 @@ import { ParsedUrlQuery } from 'querystring'; -import { convertId, IdConvertType as IdType, convertAccount, convertConversation, convertList, MastoConverters } from '../converters.js'; +import { convertConversation, convertList, MastoConverters } from '../converters.js'; import { getClient } from '../MastodonApiServerService.js'; import type { Entity } from 'megalodon'; import type { FastifyInstance } from 'fastify'; @@ -32,13 +32,6 @@ export function argsToBools(q: ParsedUrlQuery) { return q; } -export function convertTimelinesArgsId(q: ParsedUrlQuery) { - if (typeof q.min_id === 'string') q.min_id = convertId(q.min_id, IdType.SharkeyId); - if (typeof q.max_id === 'string') q.max_id = convertId(q.max_id, IdType.SharkeyId); - if (typeof q.since_id === 'string') q.since_id = convertId(q.since_id, IdType.SharkeyId); - return q; -} - export class ApiTimelineMastodon { private fastify: FastifyInstance; private mastoconverter: MastoConverters; @@ -56,8 +49,8 @@ export class ApiTimelineMastodon { try { const query: any = _request.query; const data = query.local === 'true' - ? await client.getLocalTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query)))) - : await client.getPublicTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query)))); + ? await client.getLocalTimeline(argsToBools(limitToInt(query))) + : await client.getPublicTimeline(argsToBools(limitToInt(query))); reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)))); } catch (e: any) { console.error(e); @@ -74,7 +67,7 @@ export class ApiTimelineMastodon { const client = getClient(BASE_URL, accessTokens); try { const query: any = _request.query; - const data = await client.getHomeTimeline(convertTimelinesArgsId(limitToInt(query))); + const data = await client.getHomeTimeline(limitToInt(query)); reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)))); } catch (e: any) { console.error(e); @@ -92,7 +85,7 @@ export class ApiTimelineMastodon { try { const query: any = _request.query; const params: any = _request.params; - const data = await client.getTagTimeline(params.hashtag, convertTimelinesArgsId(limitToInt(query))); + const data = await client.getTagTimeline(params.hashtag, limitToInt(query)); reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)))); } catch (e: any) { console.error(e); @@ -110,7 +103,7 @@ export class ApiTimelineMastodon { try { const query: any = _request.query; const params: any = _request.params; - const data = await client.getListTimeline(convertId(params.id, IdType.SharkeyId), convertTimelinesArgsId(limitToInt(query))); + const data = await client.getListTimeline(params.id, limitToInt(query)); reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)))); } catch (e: any) { console.error(e); @@ -127,7 +120,7 @@ export class ApiTimelineMastodon { const client = getClient(BASE_URL, accessTokens); try { const query: any = _request.query; - const data = await client.getConversationTimeline(convertTimelinesArgsId(limitToInt(query))); + const data = await client.getConversationTimeline(limitToInt(query)); reply.send(data.data.map((conversation: Entity.Conversation) => convertConversation(conversation))); } catch (e: any) { console.error(e); @@ -144,7 +137,7 @@ export class ApiTimelineMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); const params: any = _request.params; - const data = await client.getList(convertId(params.id, IdType.SharkeyId)); + const data = await client.getList(params.id); reply.send(convertList(data.data)); } catch (e: any) { console.error(e); @@ -178,11 +171,8 @@ export class ApiTimelineMastodon { const client = getClient(BASE_URL, accessTokens); const params: any = _request.params; const query: any = _request.query; - const data = await client.getAccountsInList( - convertId(params.id, IdType.SharkeyId), - convertTimelinesArgsId(query), - ); - reply.send(data.data.map((account: Entity.Account) => convertAccount(account))); + const data = await client.getAccountsInList(params.id, query); + reply.send(data.data.map((account: Entity.Account) => this.mastoconverter.convertAccount(account))); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -199,10 +189,7 @@ export class ApiTimelineMastodon { const client = getClient(BASE_URL, accessTokens); const params: any = _request.params; const query: any = _request.query; - const data = await client.addAccountsToList( - convertId(params.id, IdType.SharkeyId), - (query.accounts_id as string[]).map((id) => convertId(id, IdType.SharkeyId)), - ); + const data = await client.addAccountsToList(params.id, query.accounts_id); reply.send(data.data); } catch (e: any) { console.error(e); @@ -220,10 +207,7 @@ export class ApiTimelineMastodon { const client = getClient(BASE_URL, accessTokens); const params: any = _request.params; const query: any = _request.query; - const data = await client.deleteAccountsFromList( - convertId(params.id, IdType.SharkeyId), - (query.accounts_id as string[]).map((id) => convertId(id, IdType.SharkeyId)), - ); + const data = await client.deleteAccountsFromList(params.id, query.accounts_id); reply.send(data.data); } catch (e: any) { console.error(e); @@ -258,7 +242,7 @@ export class ApiTimelineMastodon { const client = getClient(BASE_URL, accessTokens); const body: any = _request.body; const params: any = _request.params; - const data = await client.updateList(convertId(params.id, IdType.SharkeyId), body.title); + const data = await client.updateList(params.id, body.title); reply.send(convertList(data.data)); } catch (e: any) { console.error(e); @@ -275,7 +259,7 @@ export class ApiTimelineMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); const params: any = _request.params; - const data = await client.deleteList(convertId(params.id, IdType.SharkeyId)); + const data = await client.deleteList(params.id); reply.send(data.data); } catch (e: any) { console.error(e); diff --git a/packages/megalodon/package.json b/packages/megalodon/package.json index ebd958834..3bee6b8ae 100644 --- a/packages/megalodon/package.json +++ b/packages/megalodon/package.json @@ -64,15 +64,15 @@ "socks-proxy-agent": "^8.0.2", "typescript": "5.1.6", "uuid": "^9.0.1", - "ws": "8.14.2" - }, - "devDependencies": { + "ws": "8.14.2", "@types/core-js": "^2.5.6", "@types/form-data": "^2.5.0", "@types/jest": "^29.5.5", "@types/object-assign-deep": "^0.4.1", "@types/parse-link-header": "^2.0.1", - "@types/uuid": "^9.0.4", + "@types/uuid": "^9.0.4" + }, + "devDependencies": { "@typescript-eslint/eslint-plugin": "^6.7.2", "@typescript-eslint/parser": "^6.7.2", "eslint": "^8.49.0", diff --git a/packages/megalodon/src/entities/account.ts b/packages/megalodon/src/entities/account.ts index 89c0f17c4..e2219dd04 100644 --- a/packages/megalodon/src/entities/account.ts +++ b/packages/megalodon/src/entities/account.ts @@ -5,15 +5,16 @@ namespace Entity { export type Account = { id: string + fqn?: string username: string acct: string display_name: string locked: boolean discoverable?: boolean - group: boolean | null - noindex: boolean | null - suspended: boolean | null - limited: boolean | null + group?: boolean | null + noindex?: boolean | null + suspended?: boolean | null + limited?: boolean | null created_at: string followers_count: number following_count: number diff --git a/packages/megalodon/src/entities/status.ts b/packages/megalodon/src/entities/status.ts index 8842981eb..da36a0471 100644 --- a/packages/megalodon/src/entities/status.ts +++ b/packages/megalodon/src/entities/status.ts @@ -17,7 +17,7 @@ namespace Entity { in_reply_to_account_id: string | null reblog: Status | null content: string - plain_content: string | null + plain_content?: string | null created_at: string emojis: Emoji[] replies_count: number diff --git a/packages/megalodon/src/misskey.ts b/packages/megalodon/src/misskey.ts index de2b6e2e7..a8c7c44d0 100644 --- a/packages/megalodon/src/misskey.ts +++ b/packages/megalodon/src/misskey.ts @@ -2017,7 +2017,7 @@ export default class Misskey implements MegalodonInterface { } if (options.exclude_type) { params = Object.assign(params, { - excludeType: options.exclude_type.map(e => MisskeyAPI.Converter.encodeNotificationType(e)) + excludeTypes: options.exclude_type.map(e => MisskeyAPI.Converter.encodeNotificationType(e)) }) } } diff --git a/packages/megalodon/src/misskey/api_client.ts b/packages/megalodon/src/misskey/api_client.ts index c30886f90..66347fc46 100644 --- a/packages/megalodon/src/misskey/api_client.ts +++ b/packages/megalodon/src/misskey/api_client.ts @@ -78,8 +78,10 @@ namespace MisskeyAPI { acct = `${u.username}@${u.host}`; acctUrl = `https://${u.host}/@${u.username}`; } + const fqn = `${u.username}@${u.host ?? host}`; return { id: u.id, + fqn: fqn, username: u.username, acct: acct, display_name: u.name ? u.name : '', @@ -465,8 +467,8 @@ namespace MisskeyAPI { export const stats = (s: Entity.Stats): MegalodonEntity.Stats => { return { - user_count: s.usersCount, - status_count: s.notesCount, + user_count: s.originalUsersCount, + status_count: s.originalNotesCount, domain_count: s.instances } } From f92773d995716094df01b3d6b5a0d2f3ff4f98e2 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Sun, 29 Oct 2023 01:00:54 +0200 Subject: [PATCH 02/30] fix: lockfile --- pnpm-lock.yaml | 158 +++++++++---------------------------------------- 1 file changed, 28 insertions(+), 130 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac9b31ca2..cd052f667 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -994,7 +994,7 @@ importers: version: 7.5.0 storybook-addon-misskey-theme: specifier: github:misskey-dev/storybook-addon-misskey-theme - version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.5.0)(@storybook/components@7.4.6)(@storybook/core-events@7.5.0)(@storybook/manager-api@7.5.0)(@storybook/preview-api@7.5.0)(@storybook/theming@7.5.0)(@storybook/types@7.5.0)(react-dom@18.2.0)(react@18.2.0) + version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.5.0)(@storybook/components@7.5.0)(@storybook/core-events@7.5.0)(@storybook/manager-api@7.5.0)(@storybook/preview-api@7.5.0)(@storybook/theming@7.5.0)(@storybook/types@7.5.0)(react-dom@18.2.0)(react@18.2.0) summaly: specifier: github:misskey-dev/summaly version: github.com/misskey-dev/summaly/d2d8db49943ccb201c1b1b283e9d0a630519fac7 @@ -1016,9 +1016,27 @@ importers: packages/megalodon: dependencies: + '@types/core-js': + specifier: ^2.5.6 + version: 2.5.6 + '@types/form-data': + specifier: ^2.5.0 + version: 2.5.0 + '@types/jest': + specifier: ^29.5.5 + version: 29.5.5 '@types/oauth': specifier: ^0.9.2 version: 0.9.2 + '@types/object-assign-deep': + specifier: ^0.4.1 + version: 0.4.1 + '@types/parse-link-header': + specifier: ^2.0.1 + version: 2.0.1 + '@types/uuid': + specifier: ^9.0.4 + version: 9.0.4 '@types/ws': specifier: ^8.5.5 version: 8.5.5 @@ -1056,24 +1074,6 @@ importers: specifier: 8.14.2 version: 8.14.2(bufferutil@4.0.7)(utf-8-validate@6.0.3) devDependencies: - '@types/core-js': - specifier: ^2.5.6 - version: 2.5.6 - '@types/form-data': - specifier: ^2.5.0 - version: 2.5.0 - '@types/jest': - specifier: ^29.5.5 - version: 29.5.5 - '@types/object-assign-deep': - specifier: ^0.4.1 - version: 0.4.1 - '@types/parse-link-header': - specifier: ^2.0.1 - version: 2.0.1 - '@types/uuid': - specifier: ^9.0.4 - version: 9.0.4 '@typescript-eslint/eslint-plugin': specifier: ^6.7.2 version: 6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.49.0)(typescript@5.1.6) @@ -1794,7 +1794,6 @@ packages: dependencies: '@babel/highlight': 7.22.13 chalk: 2.4.2 - dev: true /@babel/compat-data@7.22.9: resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==} @@ -2049,7 +2048,6 @@ packages: '@babel/helper-validator-identifier': 7.22.15 chalk: 2.4.2 js-tokens: 4.0.0 - dev: true /@babel/parser@7.21.8: resolution: {integrity: sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==} @@ -4162,7 +4160,6 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: jest-get-type: 29.6.3 - dev: true /@jest/expect@29.7.0: resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} @@ -4247,7 +4244,6 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@sinclair/typebox': 0.27.8 - dev: true /@jest/source-map@29.6.3: resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} @@ -4322,7 +4318,6 @@ packages: '@types/node': 20.8.6 '@types/yargs': 17.0.19 chalk: 4.1.2 - dev: true /@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.2.2)(vite@4.4.11): resolution: {integrity: sha512-2D6y7fNvFmsLmRt6UCOFJPvFoPMJGT0Uh1Wg0RaigUp7kdQPs6yYn8Dmx6GZkOH/NW0yMTwRz/p0SRMMRo50vA==} @@ -5510,7 +5505,6 @@ packages: /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - dev: true /@sindresorhus/is@4.6.0: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} @@ -6466,17 +6460,6 @@ packages: - supports-color dev: true - /@storybook/channels@7.4.6: - resolution: {integrity: sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==} - dependencies: - '@storybook/client-logger': 7.4.6 - '@storybook/core-events': 7.4.6 - '@storybook/global': 5.0.0 - qs: 6.11.1 - telejson: 7.2.0 - tiny-invariant: 1.3.1 - dev: true - /@storybook/channels@7.5.0: resolution: {integrity: sha512-/7QJS1UA7TX3uhZqCpjv4Ib8nfMnDOJrBWvjiXiUONaRcSk/he5X+W1Zz/c7dgt+wkYuAh+evjc7glIaBhVNVQ==} dependencies: @@ -6540,12 +6523,6 @@ packages: - utf-8-validate dev: true - /@storybook/client-logger@7.4.6: - resolution: {integrity: sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==} - dependencies: - '@storybook/global': 5.0.0 - dev: true - /@storybook/client-logger@7.5.0: resolution: {integrity: sha512-JV7J9vc69f9Il4uW62NIeweUU7O38VwFWxtCkhd0bcBA/9RG0go4M2avzxYYEAe9kIOX9IBBk8WGzMacwW4gKQ==} dependencies: @@ -6573,29 +6550,6 @@ packages: - supports-color dev: true - /@storybook/components@7.4.6(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-nIRBhewAgrJJVafyCzuaLx1l+YOfvvD5dOZ0JxZsxJsefOdw1jFpUqUZ5fIpQ2moyvrR0mAUFw378rBfMdHz5Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - '@radix-ui/react-select': 1.2.2(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-toolbar': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@storybook/client-logger': 7.4.6 - '@storybook/csf': 0.1.0 - '@storybook/global': 5.0.0 - '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.4.6 - memoizerific: 1.11.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - use-resize-observer: 9.1.0(react-dom@18.2.0)(react@18.2.0) - util-deprecate: 1.0.2 - transitivePeerDependencies: - - '@types/react' - - '@types/react-dom' - dev: true - /@storybook/components@7.5.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-6lmZ6PbS27xN32vTJ/NvgaiKkFIQRzZuBeBIg2u+FoAEgCiCwRXjZKe/O8NZC2Xr0uf97+7U2P0kD4Hwr9SNhw==} peerDependencies: @@ -6657,12 +6611,6 @@ packages: - supports-color dev: true - /@storybook/core-events@7.4.6: - resolution: {integrity: sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==} - dependencies: - ts-dedent: 2.2.0 - dev: true - /@storybook/core-events@7.5.0: resolution: {integrity: sha512-FsD+clTzayqprbVllnL8LLch+uCslJFDgsv7Zh99/zoi7OHtHyauoCZkdLBSiDzgc84qS41dY19HqX1/y7cnOw==} dependencies: @@ -6995,20 +6943,6 @@ packages: ts-dedent: 2.2.0 dev: true - /@storybook/theming@7.4.6(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) - '@storybook/client-logger': 7.4.6 - '@storybook/global': 5.0.0 - memoizerific: 1.11.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: true - /@storybook/theming@7.5.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-uTo97oh+pvmlfsZocFq5qae0zGo0VGk7oiBqNSSw6CiTqE1rIuSxoPrMAY+oCTWCUZV7DjONIGvpnGl2QALsAw==} peerDependencies: @@ -7023,15 +6957,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/types@7.4.6: - resolution: {integrity: sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==} - dependencies: - '@storybook/channels': 7.4.6 - '@types/babel__core': 7.20.0 - '@types/express': 4.17.17 - file-system-cache: 2.3.0 - dev: true - /@storybook/types@7.5.0: resolution: {integrity: sha512-fiOUnHKFi/UZSfvc53F0WEQCiquqcSqslL3f5EffwQRiXfeXlGavJb0kU03BO+CvOXcliRn6qKSF2dL0Rgb7Xw==} dependencies: @@ -7087,7 +7012,7 @@ packages: ts-dedent: 2.2.0 type-fest: 2.19.0 vue: 3.3.4 - vue-component-type-helpers: 1.8.19 + vue-component-type-helpers: 1.8.22 transitivePeerDependencies: - encoding - supports-color @@ -7694,7 +7619,7 @@ packages: /@types/core-js@2.5.6: resolution: {integrity: sha512-zLzoC7avO4EYUUYCSzDaahSP1QJEpZQcPxqs91mPeFdh2NS4hQBcnRoEc9RuXfJ8cdN/KXUWukMmZGcKaWeOvw==} - dev: true + dev: false /@types/cross-spawn@6.0.2: resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==} @@ -7786,7 +7711,7 @@ packages: deprecated: This is a stub types definition. form-data provides its own type definitions, so you do not need this installed. dependencies: form-data: 4.0.0 - dev: true + dev: false /@types/glob@7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} @@ -7818,19 +7743,16 @@ packages: /@types/istanbul-lib-coverage@2.0.4: resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} - dev: true /@types/istanbul-lib-report@3.0.0: resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} dependencies: '@types/istanbul-lib-coverage': 2.0.4 - dev: true /@types/istanbul-reports@3.0.1: resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} dependencies: '@types/istanbul-lib-report': 3.0.0 - dev: true /@types/jest@28.1.3: resolution: {integrity: sha512-Tsbjk8Y2hkBaY/gJsataeb4q9Mubw9EOz7+4RjPkzD5KjTvHHs7cpws22InaoXxAVAhF5HfFbzJjo6oKWqSZLw==} @@ -7844,7 +7766,6 @@ packages: dependencies: expect: 29.7.0 pretty-format: 29.7.0 - dev: true /@types/js-levenshtein@1.1.1: resolution: {integrity: sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==} @@ -7978,7 +7899,7 @@ packages: /@types/object-assign-deep@0.4.1: resolution: {integrity: sha512-uWJatOM1JKDdF6Fwa16124b76BtxvTz5Lv+ORGuI7dwqU4iqExXpeHrHOi1c8BU4FgSJ6PdH0skR9Zmz8+MUqQ==} - dev: true + dev: false /@types/offscreencanvas@2019.3.0: resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==} @@ -7992,7 +7913,7 @@ packages: /@types/parse-link-header@2.0.1: resolution: {integrity: sha512-BrKNSrRTqn3UkMXvdVtr/znJch0PMBpEvEP8oBkxDx7eEGntuFLI+WpA5HGsNHK4SlqyhaMa+Ks0ViwyixQB5w==} - dev: true + dev: false /@types/pg@8.10.5: resolution: {integrity: sha512-GS3ebGcSJQqKSnq4/WnSH1XQvx0vTDLEmqLENk7onKvTnry9BWPsZiZeUMJlEPw+5bCQDzfxZFhxlUztpNCKgQ==} @@ -8135,7 +8056,6 @@ packages: /@types/stack-utils@2.0.1: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} - dev: true /@types/throttle-debounce@5.0.0: resolution: {integrity: sha512-Pb7k35iCGFcGPECoNE4DYp3Oyf2xcTd3FbFQxXUI9hEYKUl6YX+KLf7HrBmgVcD05nl50LIH6i+80js4iYmWbw==} @@ -8159,7 +8079,6 @@ packages: /@types/uuid@9.0.4: resolution: {integrity: sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==} - dev: true /@types/uuid@9.0.5: resolution: {integrity: sha512-xfHdwa1FMJ082prjSJpoEI57GZITiQz10r3vEJCHa2khEFQjKy91aWKz6+zybzssCvXUwE1LQWgWVwZ4nYUvHQ==} @@ -8202,7 +8121,6 @@ packages: /@types/yargs-parser@21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} - dev: true /@types/yargs@16.0.5: resolution: {integrity: sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==} @@ -8214,7 +8132,6 @@ packages: resolution: {integrity: sha512-cAx3qamwaYX9R0fzOIZAlFpo4A+1uBVCxqpKz9D26uTF4srRXaGTTsikQmaotCtNdbhzyUH7ft6p9ktz9s6UNQ==} dependencies: '@types/yargs-parser': 21.0.0 - dev: true /@types/yauzl@2.10.0: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} @@ -8960,7 +8877,6 @@ packages: engines: {node: '>=4'} dependencies: color-convert: 1.9.3 - dev: true /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} @@ -8971,7 +8887,6 @@ packages: /ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - dev: true /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} @@ -9902,7 +9817,6 @@ packages: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 - dev: true /chalk@3.0.0: resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} @@ -10054,7 +9968,6 @@ packages: /ci-info@3.7.1: resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==} engines: {node: '>=8'} - dev: true /cjs-module-lexer@1.2.2: resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==} @@ -10172,7 +10085,6 @@ packages: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: color-name: 1.1.3 - dev: true /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -10182,7 +10094,6 @@ packages: /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: true /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -10967,7 +10878,6 @@ packages: /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true /diff@5.1.0: resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} @@ -11358,12 +11268,10 @@ packages: /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} - dev: true /escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} - dev: true /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} @@ -11775,7 +11683,6 @@ packages: jest-matcher-utils: 29.7.0 jest-message-util: 29.7.0 jest-util: 29.7.0 - dev: true /exponential-backoff@3.1.1: resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} @@ -13758,7 +13665,6 @@ packages: diff-sequences: 29.6.3 jest-get-type: 29.6.3 pretty-format: 29.7.0 - dev: true /jest-docblock@29.7.0: resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} @@ -13807,7 +13713,6 @@ packages: /jest-get-type@29.6.3: resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true /jest-haste-map@29.7.0: resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} @@ -13854,7 +13759,6 @@ packages: jest-diff: 29.7.0 jest-get-type: 29.6.3 pretty-format: 29.7.0 - dev: true /jest-message-util@29.7.0: resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} @@ -13869,7 +13773,6 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 - dev: true /jest-mock@27.5.1: resolution: {integrity: sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==} @@ -14027,7 +13930,6 @@ packages: ci-info: 3.7.1 graceful-fs: 4.2.11 picomatch: 2.3.1 - dev: true /jest-validate@29.7.0: resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} @@ -14136,7 +14038,6 @@ packages: /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true /js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} @@ -16601,7 +16502,6 @@ packages: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.2.0 - dev: true /pretty-hrtime@1.0.3: resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==} @@ -17090,7 +16990,6 @@ packages: /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: true /react-refresh@0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} @@ -18227,7 +18126,6 @@ packages: engines: {node: '>=10'} dependencies: escape-string-regexp: 2.0.0 - dev: true /stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -19667,8 +19565,8 @@ packages: resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} dev: true - /vue-component-type-helpers@1.8.19: - resolution: {integrity: sha512-1OANGSZK4pzHF4uc86usWi+o5Y0zgoDtqWkPg6Am6ot+jHSAmpOah59V/4N82So5xRgivgCxGgK09lBy1XNUfQ==} + /vue-component-type-helpers@1.8.22: + resolution: {integrity: sha512-LK3wJHs3vJxHG292C8cnsRusgyC5SEZDCzDCD01mdE/AoREFMl2tzLRuzwyuEsOIz13tqgBcnvysN3Lxsa14Fw==} dev: true /vue-demi@0.13.11(vue@3.3.4): @@ -20210,7 +20108,7 @@ packages: sharp: 0.31.3 dev: false - github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.5.0)(@storybook/components@7.4.6)(@storybook/core-events@7.5.0)(@storybook/manager-api@7.5.0)(@storybook/preview-api@7.5.0)(@storybook/theming@7.5.0)(@storybook/types@7.5.0)(react-dom@18.2.0)(react@18.2.0): + github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.5.0)(@storybook/components@7.5.0)(@storybook/core-events@7.5.0)(@storybook/manager-api@7.5.0)(@storybook/preview-api@7.5.0)(@storybook/theming@7.5.0)(@storybook/types@7.5.0)(react-dom@18.2.0)(react@18.2.0): resolution: {tarball: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640} id: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640 name: storybook-addon-misskey-theme @@ -20232,7 +20130,7 @@ packages: optional: true dependencies: '@storybook/blocks': 7.5.0(react-dom@18.2.0)(react@18.2.0) - '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0) + '@storybook/components': 7.5.0(react-dom@18.2.0)(react@18.2.0) '@storybook/core-events': 7.5.0 '@storybook/manager-api': 7.5.0(react-dom@18.2.0)(react@18.2.0) '@storybook/preview-api': 7.5.0 From 43f27a639fb75c9e4ccc8bea02eaf83382b93ea1 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Sun, 29 Oct 2023 02:06:19 +0200 Subject: [PATCH 03/30] upd: simplify importing of mastoconverter, fix bug Lets you import stuff into mastoconverter without needing to also import them everywhere else Fixes not being able to get statuses on accounts --- packages/backend/src/server/ServerModule.ts | 2 + .../api/mastodon/MastodonApiServerService.ts | 41 ++++++++++--------- .../src/server/api/mastodon/converters.ts | 26 ++++-------- .../server/api/mastodon/endpoints/account.ts | 16 +++----- .../server/api/mastodon/endpoints/status.ts | 4 +- .../server/api/mastodon/endpoints/timeline.ts | 4 +- 6 files changed, 39 insertions(+), 54 deletions(-) diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index fc6f01960..fc5eece01 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -23,6 +23,7 @@ import { SigninService } from './api/SigninService.js'; import { SignupApiService } from './api/SignupApiService.js'; import { StreamingApiServerService } from './api/StreamingApiServerService.js'; import { ClientServerService } from './web/ClientServerService.js'; +import { MastoConverters } from './api/mastodon/converters.js'; import { FeedService } from './web/FeedService.js'; import { UrlPreviewService } from './web/UrlPreviewService.js'; import { MainChannelService } from './api/stream/channels/main.js'; @@ -87,6 +88,7 @@ import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js'; OpenApiServerService, MastodonApiServerService, OAuth2ProviderService, + MastoConverters, ], exports: [ ServerService, diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index 9a2890f50..13cf45368 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; -import { convertAccount, convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList } from './converters.js'; +import { convertAccount, convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList, MastoConverters } from './converters.js'; import { getInstance } from './endpoints/meta.js'; import { ApiAuthMastodon, ApiAccountMastodon, ApiFilterMastodon, ApiNotifyMastodon, ApiSearchMastodon, ApiTimelineMastodon, ApiStatusMastodon } from './endpoints.js'; import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; @@ -37,6 +37,7 @@ export class MastodonApiServerService { private config: Config, private metaService: MetaService, private userEntityService: UserEntityService, + private mastoConverter: MastoConverters, ) { } @bindThis @@ -236,7 +237,7 @@ export class MastodonApiServerService { const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt // displayed without being logged in try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.verifyCredentials()); } catch (e: any) { /* console.error(e); */ @@ -286,7 +287,7 @@ export class MastodonApiServerService { ids = [ids]; } users = ids; - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.getRelationships(users)); } catch (e: any) { /* console.error(e); */ @@ -319,7 +320,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.getStatuses()); } catch (e: any) { /* console.error(e); @@ -347,7 +348,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.getFollowers()); } catch (e: any) { /* console.error(e); @@ -361,7 +362,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.getFollowing()); } catch (e: any) { /* console.error(e); @@ -389,7 +390,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.addFollow()); } catch (e: any) { /* console.error(e); @@ -403,7 +404,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.rmFollow()); } catch (e: any) { /* console.error(e); @@ -417,7 +418,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.addBlock()); } catch (e: any) { /* console.error(e); @@ -431,7 +432,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.rmBlock()); } catch (e: any) { /* console.error(e); @@ -445,7 +446,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.addMute()); } catch (e: any) { /* console.error(e); @@ -459,7 +460,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.rmMute()); } catch (e: any) { /* console.error(e); @@ -487,7 +488,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.getBookmarks()); } catch (e: any) { /* console.error(e); @@ -501,7 +502,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.getFavourites()); } catch (e: any) { /* console.error(e); @@ -515,7 +516,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.getMutes()); } catch (e: any) { /* console.error(e); @@ -529,7 +530,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.getBlocks()); } catch (e: any) { /* console.error(e); @@ -557,7 +558,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.acceptFollow()); } catch (e: any) { /* console.error(e); @@ -571,7 +572,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await account.rejectFollow()); } catch (e: any) { /* console.error(e); @@ -756,7 +757,7 @@ export class MastodonApiServerService { //#endregion //#region Timelines - const TLEndpoint = new ApiTimelineMastodon(fastify, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const TLEndpoint = new ApiTimelineMastodon(fastify, this.config, this.mastoConverter); // GET Endpoints TLEndpoint.getTL(); @@ -781,7 +782,7 @@ export class MastodonApiServerService { //#endregion //#region Status - const NoteEndpoint = new ApiStatusMastodon(fastify, this.config, this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); + const NoteEndpoint = new ApiStatusMastodon(fastify, this.mastoConverter); // GET Endpoints NoteEndpoint.getStatus(); diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index a83914560..6019b825e 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -1,7 +1,7 @@ import type { Config } from '@/config.js'; import { MfmService } from '@/core/MfmService.js'; import { DI } from '@/di-symbols.js'; -import { Inject } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { Entity } from 'megalodon'; import mfm from 'mfm-js'; import { GetterService } from '../GetterService.js'; @@ -25,27 +25,15 @@ export const escapeMFM = (text: string): string => text .replace(/`/g, "`") .replace(/\r?\n/g, "
"); +@Injectable() export class MastoConverters { - private MfmService: MfmService; - private GetterService: GetterService; - constructor( @Inject(DI.config) private config: Config, - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.noteEditRepository) - private noteEditRepository: NoteEditRepository, - - private userEntityService: UserEntityService + private mfmService: MfmService, + private getterService: GetterService, ) { - this.MfmService = new MfmService(this.config); - this.GetterService = new GetterService(this.usersRepository, this.notesRepository, this.noteEditRepository, this.userEntityService); } private encode(u: MiUser, m: IMentionedRemoteUsers): Entity.Mention { @@ -67,7 +55,7 @@ export class MastoConverters { } public async getUser(id: string): Promise { - return this.GetterService.getUser(id).then(p => { + return this.getterService.getUser(id).then(p => { return p; }); } @@ -100,7 +88,7 @@ export class MastoConverters { public async convertStatus(status: Entity.Status) { const convertedAccount = this.convertAccount(status.account); - const note = await this.GetterService.getNote(status.id); + const note = await this.getterService.getNote(status.id); const mentions = Promise.all(note.mentions.map(p => this.getUser(p) @@ -109,7 +97,7 @@ export class MastoConverters { .then(p => p.filter(m => m)) as Promise; const content = note.text !== null - ? this.MfmService.toMastoHtml(mfm.parse(note.text!), JSON.parse(note.mentionedRemoteUsers), false, null) + ? this.mfmService.toMastoHtml(mfm.parse(note.text!), JSON.parse(note.mentionedRemoteUsers), false, null) .then(p => p ?? escapeMFM(note.text!)) : ''; diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 802f1f5a6..f2525c723 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -5,6 +5,7 @@ import type { FastifyRequest } from 'fastify'; import { NoteEditRepository, NotesRepository, UsersRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { Config } from '@/config.js'; +import { Injectable } from '@nestjs/common'; const relationshipModel = { id: '', @@ -23,23 +24,16 @@ const relationshipModel = { note: '', }; +@Injectable() export class ApiAccountMastodon { private request: FastifyRequest; private client: MegalodonInterface; private BASE_URL: string; - private mastoconverter: MastoConverters; - constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string, - config: Config, - usersrepo: UsersRepository, - notesrepo: NotesRepository, - noteeditrepo: NoteEditRepository, - userentity: UserEntityService, - ) { + constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string, private mastoconverter: MastoConverters) { this.request = request; this.client = client; this.BASE_URL = BASE_URL; - this.mastoconverter = new MastoConverters(config, usersrepo, notesrepo, noteeditrepo, userentity); } public async verifyCredentials() { @@ -104,7 +98,9 @@ export class ApiAccountMastodon { public async getStatuses() { try { const data = await this.client.getAccountStatuses((this.request.params as any).id, argsToBools(limitToInt(this.request.query as any))); - return data.data.map((status) => this.mastoconverter.convertStatus(status)); + const a = await Promise.all(data.data.map(async (status) => await this.mastoconverter.convertStatus(status))); + console.error(a); + return a; } catch (e: any) { console.error(e); console.error(e.response.data); diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index a15e9761b..294d38f20 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -18,9 +18,9 @@ export class ApiStatusMastodon { private fastify: FastifyInstance; private mastoconverter: MastoConverters; - constructor(fastify: FastifyInstance, config: Config, usersrepo: UsersRepository, notesrepo: NotesRepository, noteeditrepo: NoteEditRepository, userentity: UserEntityService) { + constructor(fastify: FastifyInstance, mastoconverter: MastoConverters) { this.fastify = fastify; - this.mastoconverter = new MastoConverters(config, usersrepo, notesrepo, noteeditrepo, userentity); + this.mastoconverter = mastoconverter; } public async getStatus() { diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index b1b487f39..152d4a772 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -34,11 +34,9 @@ export function argsToBools(q: ParsedUrlQuery) { export class ApiTimelineMastodon { private fastify: FastifyInstance; - private mastoconverter: MastoConverters; - constructor(fastify: FastifyInstance, config: Config, usersRepository: UsersRepository, notesRepository: NotesRepository, noteEditRepository: NoteEditRepository, userEntityService: UserEntityService) { + constructor(fastify: FastifyInstance, config: Config, private mastoconverter: MastoConverters) { this.fastify = fastify; - this.mastoconverter = new MastoConverters(config, usersRepository, notesRepository, noteEditRepository, userEntityService); } public async getTL() { From 90b666e626371ad91db9a07cb59176f95e35e2ac Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Sun, 29 Oct 2023 02:20:05 +0200 Subject: [PATCH 04/30] fix: await all results on favourites and reblogged --- packages/backend/src/server/api/mastodon/endpoints/account.ts | 4 +--- packages/backend/src/server/api/mastodon/endpoints/status.ts | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index f2525c723..a1f3773fd 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -98,9 +98,7 @@ export class ApiAccountMastodon { public async getStatuses() { try { const data = await this.client.getAccountStatuses((this.request.params as any).id, argsToBools(limitToInt(this.request.query as any))); - const a = await Promise.all(data.data.map(async (status) => await this.mastoconverter.convertStatus(status))); - console.error(a); - return a; + return await Promise.all(data.data.map(async (status) => await this.mastoconverter.convertStatus(status))); } catch (e: any) { console.error(e); console.error(e.response.data); diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index 294d38f20..e5202244e 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -89,7 +89,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getStatusRebloggedBy(_request.params.id); - reply.send(data.data.map((account: Entity.Account) => this.mastoconverter.convertAccount(account))); + reply.send(await Promise.all(data.data.map(async (account: Entity.Account) => await this.mastoconverter.convertAccount(account)))); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -104,7 +104,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getStatusFavouritedBy(_request.params.id); - reply.send(data.data.map((account: Entity.Account) => this.mastoconverter.convertAccount(account))); + reply.send(await Promise.all(data.data.map(async (account: Entity.Account) => await this.mastoconverter.convertAccount(account)))); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); From 95bcfd8ef3274f06e7a0f9e5e5062769c93d079b Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Sun, 29 Oct 2023 02:23:43 +0200 Subject: [PATCH 05/30] fix: followers and following not being able to load --- packages/backend/src/server/api/mastodon/endpoints/account.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index a1f3773fd..39da0e4a5 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -112,7 +112,7 @@ export class ApiAccountMastodon { (this.request.params as any).id, limitToInt(this.request.query as any), ); - return data.data.map((account) => this.mastoconverter.convertAccount(account)); + return await Promise.all(data.data.map(async (account) => await this.mastoconverter.convertAccount(account))); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -126,7 +126,7 @@ export class ApiAccountMastodon { (this.request.params as any).id, limitToInt(this.request.query as any), ); - return data.data.map((account) => this.mastoconverter.convertAccount(account)); + return await Promise.all(data.data.map(async (account) => await this.mastoconverter.convertAccount(account))); } catch (e: any) { console.error(e); console.error(e.response.data); From 549bcf70db6575390f4a89f2d880332a76949c7b Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Sun, 29 Oct 2023 02:59:36 +0100 Subject: [PATCH 06/30] upd: convertAccount now fetches info from the DB --- .../api/mastodon/MastodonApiServerService.ts | 5 +- .../src/server/api/mastodon/converters.ts | 80 +++++++++++++------ packages/megalodon/src/entities/field.ts | 2 +- .../megalodon/src/misskey/entities/field.ts | 1 + 4 files changed, 59 insertions(+), 29 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index 13cf45368..49274f707 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -305,9 +305,8 @@ export class MastodonApiServerService { try { const sharkId = _request.params.id; const data = await client.getAccount(sharkId); - const profile = await this.userProfilesRepository.findOneBy({ userId: sharkId }); - data.data.fields = profile?.fields.map(f => ({ ...f, verified_at: null })) || []; - reply.send(convertAccount(data.data)); + const account = await this.mastoConverter.convertAccount(data.data); + reply.send(account); } catch (e: any) { /* console.error(e); console.error(e.response.data); */ diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index 6019b825e..55cf60c66 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -1,15 +1,16 @@ -import type { Config } from '@/config.js'; -import { MfmService } from '@/core/MfmService.js'; -import { DI } from '@/di-symbols.js'; import { Inject, Injectable } from '@nestjs/common'; import { Entity } from 'megalodon'; import mfm from 'mfm-js'; -import { GetterService } from '../GetterService.js'; +import { DI } from '@/di-symbols.js'; +import { MfmService } from '@/core/MfmService.js'; +import type { Config } from '@/config.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js'; import type { MiUser } from '@/models/User.js'; -import type { NoteEditRepository, NotesRepository, UsersRepository } from '@/models/_.js'; +import type { NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; +import { GetterService } from '../GetterService.js'; +import { CustomEmojiService } from '@/core/CustomEmojiService.js'; export enum IdConvertType { MastodonId, @@ -17,13 +18,13 @@ export enum IdConvertType { } export const escapeMFM = (text: string): string => text - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'") - .replace(/`/g, "`") - .replace(/\r?\n/g, "
"); + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/`/g, '`') + .replace(/\r?\n/g, '
'); @Injectable() export class MastoConverters { @@ -31,8 +32,15 @@ export class MastoConverters { @Inject(DI.config) private config: Config, + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + private mfmService: MfmService, private getterService: GetterService, + private customEmojiService: CustomEmojiService, ) { } @@ -60,29 +68,51 @@ export class MastoConverters { }); } + private async encodeField(f: Entity.Field): Promise { + return { + name: f.name, + value: await this.mfmService.toMastoHtml(mfm.parse(f.value), [], true) ?? escapeMFM(f.value), + verified_at: null, + }; + } + public async convertAccount(account: Entity.Account) { + const user = await this.getUser(account.id); + const profile = await this.userProfilesRepository.findOneBy({ userId: user.id }); + const emojis = await this.customEmojiService.populateEmojis(user.emojis, user.host ? user.host : this.config.host); + const emoji: Entity.Emoji[] = []; + Object.entries(emojis).forEach(entry => { + const [key, value] = entry; + emoji.push({ + shortcode: key, + static_url: value, + url: value, + visible_in_picker: true, + category: undefined, + }); + }); return awaitAll({ id: account.id, username: account.username, acct: account.acct, fqn: account.fqn, display_name: account.display_name || account.username, - locked: account.locked, + locked: user.isLocked, created_at: account.created_at, - followers_count: account.followers_count, - following_count: account.following_count, - statuses_count: account.statuses_count, - note: account.note, + followers_count: user.followersCount, + following_count: user.followingCount, + statuses_count: user.notesCount, + note: profile?.description ?? account.note, url: account.url, - avatar: account.avatar, - avatar_static: account.avatar, - header: account.header, - header_static: account.header, - emojis: account.emojis, + avatar: user.avatarUrl ? user.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png', + avatar_static: user.avatarUrl ? user.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png', + header: user.bannerUrl ? user.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png', + header_static: user.bannerUrl ? user.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png', + emojis: emoji, moved: null, //FIXME - fields: [], - bot: false, - discoverable: true, + fields: Promise.all(profile?.fields.map(async p => this.encodeField(p)) ?? []), + bot: user.isBot, + discoverable: user.isExplorable, }); } diff --git a/packages/megalodon/src/entities/field.ts b/packages/megalodon/src/entities/field.ts index 03e4604b0..71ce3c0cf 100644 --- a/packages/megalodon/src/entities/field.ts +++ b/packages/megalodon/src/entities/field.ts @@ -2,6 +2,6 @@ namespace Entity { export type Field = { name: string value: string - verified_at: string | null + verified_at?: string | null } } diff --git a/packages/megalodon/src/misskey/entities/field.ts b/packages/megalodon/src/misskey/entities/field.ts index 8bbb2d7c4..1e61178e5 100644 --- a/packages/megalodon/src/misskey/entities/field.ts +++ b/packages/megalodon/src/misskey/entities/field.ts @@ -3,5 +3,6 @@ namespace MisskeyEntity { name: string; value: string; verified?: string; + verified_at?: string; }; } From d8b0078ffa3bd952b9cf237b1177a40a90d5ebda Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Sun, 29 Oct 2023 03:23:09 +0100 Subject: [PATCH 07/30] fix: emojis not loading on local statuses --- .../src/server/api/mastodon/converters.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index 55cf60c66..e2c9a2d34 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -9,8 +9,9 @@ import type { MiUser } from '@/models/User.js'; import type { NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; -import { GetterService } from '../GetterService.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; +import { GetterService } from '../GetterService.js'; +import { ReactionService } from '@/core/ReactionService.js'; export enum IdConvertType { MastodonId, @@ -41,6 +42,7 @@ export class MastoConverters { private mfmService: MfmService, private getterService: GetterService, private customEmojiService: CustomEmojiService, + private reactionService: ReactionService, ) { } @@ -119,6 +121,24 @@ export class MastoConverters { public async convertStatus(status: Entity.Status) { const convertedAccount = this.convertAccount(status.account); const note = await this.getterService.getNote(status.id); + const noteUser = await this.getUser(status.account.id); + + const reactionEmojiNames = Object.keys(note.reactions) + .filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) + .map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', '')); + + const emojis = await this.customEmojiService.populateEmojis(reactionEmojiNames, noteUser.host ? noteUser.host : this.config.host); + const emoji: Entity.Emoji[] = []; + Object.entries(emojis).forEach(entry => { + const [key, value] = entry; + emoji.push({ + shortcode: key, + static_url: value, + url: value, + visible_in_picker: true, + category: undefined, + }); + }); const mentions = Promise.all(note.mentions.map(p => this.getUser(p) @@ -151,7 +171,7 @@ export class MastoConverters { content_type: 'text/x.misskeymarkdown', text: note.text, created_at: status.created_at, - emojis: status.emojis, + emojis: emoji, replies_count: note.repliesCount, reblogs_count: note.renoteCount, favourites_count: status.favourites_count, From 8fd669ff7ddd793442597adff6386661e944d089 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Sun, 29 Oct 2023 03:30:35 +0100 Subject: [PATCH 08/30] fix: statuses using wrong emojis --- packages/backend/src/server/api/mastodon/converters.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index e2c9a2d34..22bc3cf6f 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -123,11 +123,7 @@ export class MastoConverters { const note = await this.getterService.getNote(status.id); const noteUser = await this.getUser(status.account.id); - const reactionEmojiNames = Object.keys(note.reactions) - .filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) - .map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', '')); - - const emojis = await this.customEmojiService.populateEmojis(reactionEmojiNames, noteUser.host ? noteUser.host : this.config.host); + const emojis = await this.customEmojiService.populateEmojis(note.emojis, noteUser.host ? noteUser.host : this.config.host); const emoji: Entity.Emoji[] = []; Object.entries(emojis).forEach(entry => { const [key, value] = entry; From c53323d23748161a0f5cd9d46b4b711c7260383b Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Sun, 29 Oct 2023 12:18:18 +0100 Subject: [PATCH 09/30] upd: add history endpoint, make sure all areas use new convertAccount --- .../api/mastodon/MastodonApiServerService.ts | 20 ++-- .../src/server/api/mastodon/converters.ts | 96 ++++++++++++++++--- .../server/api/mastodon/endpoints/search.ts | 8 +- .../server/api/mastodon/endpoints/status.ts | 3 +- packages/megalodon/src/entities/list.ts | 3 +- packages/megalodon/src/misskey/api_client.ts | 2 +- 6 files changed, 104 insertions(+), 28 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index 49274f707..32534d9ef 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; -import { convertAccount, convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList, MastoConverters } from './converters.js'; +import { convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList, MastoConverters } from './converters.js'; import { getInstance } from './endpoints/meta.js'; import { ApiAuthMastodon, ApiAccountMastodon, ApiFilterMastodon, ApiNotifyMastodon, ApiSearchMastodon, ApiTimelineMastodon, ApiStatusMastodon } from './endpoints.js'; import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; @@ -102,8 +102,8 @@ export class MastodonApiServerService { }, order: { id: 'ASC' }, }); - const contact = admin == null ? null : convertAccount((await client.getAccount(admin.id)).data); - reply.send(await getInstance(data.data, contact, this.config, await this.metaService.fetch())); + const contact = admin == null ? null : await this.mastoConverter.convertAccount((await client.getAccount(admin.id)).data); + reply.send(await getInstance(data.data, contact as Entity.Account, this.config, await this.metaService.fetch())); } catch (e: any) { /* console.error(e); */ reply.code(401).send(e.response.data); @@ -252,7 +252,7 @@ export class MastodonApiServerService { // displayed without being logged in try { const data = await client.updateCredentials(_request.body!); - reply.send(convertAccount(data.data)); + reply.send(await this.mastoConverter.convertAccount(data.data)); } catch (e: any) { /* console.error(e); */ reply.code(401).send(e.response.data); @@ -268,7 +268,7 @@ export class MastodonApiServerService { const data = await client.search((_request.query as any).acct, { type: 'accounts' }); const profile = await this.userProfilesRepository.findOneBy({ userId: data.data.accounts[0].id }); data.data.accounts[0].fields = profile?.fields.map(f => ({ ...f, verified_at: null })) || []; - reply.send(convertAccount(data.data.accounts[0])); + reply.send(await this.mastoConverter.convertAccount(data.data.accounts[0])); } catch (e: any) { /* console.error(e); */ reply.code(401).send(e.response.data); @@ -544,7 +544,7 @@ export class MastodonApiServerService { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getFollowRequests( ((_request.query as any) || { limit: 20 }).limit ); - reply.send(data.data.map((account) => convertAccount(account as Entity.Account))); + reply.send(await Promise.all(data.data.map(async (account) => await this.mastoConverter.convertAccount(account as Entity.Account)))); } catch (e: any) { /* console.error(e); console.error(e.response.data); */ @@ -587,7 +587,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const search = new ApiSearchMastodon(_request, client, BASE_URL); + const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await search.SearchV1()); } catch (e: any) { /* console.error(e); @@ -601,7 +601,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const search = new ApiSearchMastodon(_request, client, BASE_URL); + const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await search.SearchV2()); } catch (e: any) { /* console.error(e); @@ -615,7 +615,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const search = new ApiSearchMastodon(_request, client, BASE_URL); + const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await search.getStatusTrends()); } catch (e: any) { /* console.error(e); @@ -629,7 +629,7 @@ export class MastodonApiServerService { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const search = new ApiSearchMastodon(_request, client, BASE_URL); + const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await search.getSuggestions()); } catch (e: any) { /* console.error(e); diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index 22bc3cf6f..51556adf2 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -7,11 +7,11 @@ import type { Config } from '@/config.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js'; import type { MiUser } from '@/models/User.js'; import type { NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { GetterService } from '../GetterService.js'; -import { ReactionService } from '@/core/ReactionService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { IdService } from '@/core/IdService.js'; export enum IdConvertType { MastodonId, @@ -39,10 +39,14 @@ export class MastoConverters { @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, + @Inject(DI.noteEditRepository) + private noteEditRepository: NoteEditRepository, + private mfmService: MfmService, private getterService: GetterService, private customEmojiService: CustomEmojiService, - private reactionService: ReactionService, + private idService: IdService, + private driveFileEntityService: DriveFileEntityService, ) { } @@ -64,6 +68,39 @@ export class MastoConverters { }; } + public fileType(s: string): 'unknown' | 'image' | 'gifv' | 'video' | 'audio' { + if (s === 'image/gif') { + return 'gifv'; + } + if (s.includes('image')) { + return 'image'; + } + if (s.includes('video')) { + return 'video'; + } + if (s.includes('audio')) { + return 'audio'; + } + return 'unknown'; + } + + public encodeFile(f: any): Entity.Attachment { + return { + id: f.id, + type: this.fileType(f.type), + url: f.url, + remote_url: f.url, + preview_url: f.thumbnailUrl, + text_url: f.url, + meta: { + width: f.properties.width, + height: f.properties.height + }, + description: f.comment ? f.comment : null, + blurhash: f.blurhash ? f.blurhash : null + }; + } + public async getUser(id: string): Promise { return this.getterService.getUser(id).then(p => { return p; @@ -78,7 +115,7 @@ export class MastoConverters { }; } - public async convertAccount(account: Entity.Account) { + public async convertAccount(account: Entity.Account | MiUser) { const user = await this.getUser(account.id); const profile = await this.userProfilesRepository.findOneBy({ userId: user.id }); const emojis = await this.customEmojiService.populateEmojis(user.emojis, user.host ? user.host : this.config.host); @@ -93,19 +130,26 @@ export class MastoConverters { category: undefined, }); }); + const fqn = `${user.username}@${user.host ?? this.config.hostname}`; + let acct = user.username; + let acctUrl = `https://${user.host || this.config.host}/@${user.username}`; + if (user.host) { + acct = `${user.username}@${user.host}`; + acctUrl = `https://${user.host}/@${user.username}`; + } return awaitAll({ id: account.id, - username: account.username, - acct: account.acct, - fqn: account.fqn, - display_name: account.display_name || account.username, + username: user.username, + acct: acct, + fqn: fqn, + display_name: user.name ?? user.username, locked: user.isLocked, - created_at: account.created_at, + created_at: this.idService.parse(user.id).date.toISOString(), followers_count: user.followersCount, following_count: user.followingCount, statuses_count: user.notesCount, - note: profile?.description ?? account.note, - url: account.url, + note: profile?.description ?? '', + url: user.uri ?? acctUrl, avatar: user.avatarUrl ? user.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png', avatar_static: user.avatarUrl ? user.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png', header: user.bannerUrl ? user.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png', @@ -118,6 +162,36 @@ export class MastoConverters { }); } + public async getEdits(id: string) { + const note = await this.getterService.getNote(id); + if (!note) { + return {}; + } + + const noteUser = await this.getUser(note.userId).then(async (p) => await this.convertAccount(p)); + const edits = await this.noteEditRepository.find({ where: { noteId: note.id }, order: { id: 'ASC' } }); + const history: Promise[] = []; + + let lastDate = this.idService.parse(note.id).date; + for (const edit of edits) { + const files = this.driveFileEntityService.packManyByIds(edit.fileIds); + const item = { + account: noteUser, + content: this.mfmService.toMastoHtml(mfm.parse(edit.newText ?? ''), JSON.parse(note.mentionedRemoteUsers)).then(p => p ?? ''), + created_at: lastDate.toISOString(), + emojis: [], + sensitive: files.then(files => files.length > 0 ? files.some((f) => f.isSensitive) : false), + spoiler_text: edit.cw ?? '', + poll: null, + media_attachments: files.then(files => files.length > 0 ? files.map((f) => this.encodeFile(f)) : []) + }; + lastDate = edit.updatedAt; + history.push(awaitAll(item)); + } + + return await Promise.all(history); + } + public async convertStatus(status: Entity.Status) { const convertedAccount = this.convertAccount(status.account); const note = await this.getterService.getNote(status.id); diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index d5839ff1c..772ef3307 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -1,5 +1,5 @@ import { Converter } from 'megalodon'; -import { convertAccount, convertStatus } from '../converters.js'; +import { MastoConverters, convertAccount, convertStatus } from '../converters.js'; import { limitToInt } from './timeline.js'; import type { MegalodonInterface } from 'megalodon'; import type { FastifyRequest } from 'fastify'; @@ -63,7 +63,7 @@ export class ApiSearchMastodon { private client: MegalodonInterface; private BASE_URL: string; - constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string) { + constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string, private mastoConverter: MastoConverters) { this.request = request; this.client = client; this.BASE_URL = BASE_URL; @@ -89,8 +89,8 @@ export class ApiSearchMastodon { const stat = !type || type === 'statuses' ? await this.client.search(query.q, { type: 'statuses', ...query }) : null; const tags = !type || type === 'hashtags' ? await this.client.search(query.q, { type: 'hashtags', ...query }) : null; const data = { - accounts: acct?.data.accounts.map((account) => convertAccount(account)) ?? [], - statuses: stat?.data.statuses.map((status) => convertStatus(status)) ?? [], + accounts: await Promise.all(acct?.data.accounts.map(async (account) => await this.mastoConverter.convertAccount(account)) ?? []), + statuses: await Promise.all(stat?.data.statuses.map(async (status) => await this.mastoConverter.convertStatus(status)) ?? []), hashtags: tags?.data.hashtags ?? [], }; return data; diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index e5202244e..fe77646af 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -74,7 +74,8 @@ export class ApiStatusMastodon { public async getHistory() { this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/history', async (_request, reply) => { try { - reply.send([]); + const edits = await this.mastoconverter.getEdits(_request.params.id); + reply.send(edits); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); diff --git a/packages/megalodon/src/entities/list.ts b/packages/megalodon/src/entities/list.ts index 58c264aba..281f02b11 100644 --- a/packages/megalodon/src/entities/list.ts +++ b/packages/megalodon/src/entities/list.ts @@ -2,7 +2,8 @@ namespace Entity { export type List = { id: string title: string - replies_policy: RepliesPolicy | null + replies_policy?: RepliesPolicy | null + exclusive?: RepliesPolicy | null } export type RepliesPolicy = 'followed' | 'list' | 'none' diff --git a/packages/megalodon/src/misskey/api_client.ts b/packages/megalodon/src/misskey/api_client.ts index 66347fc46..520928c9f 100644 --- a/packages/megalodon/src/misskey/api_client.ts +++ b/packages/megalodon/src/misskey/api_client.ts @@ -391,7 +391,7 @@ namespace MisskeyAPI { export const list = (l: Entity.List): MegalodonEntity.List => ({ id: l.id, title: l.name, - replies_policy: null + exclusive: null }) export const encodeNotificationType = ( From b596a4978f51e72e4cfe789ac56a7f9b7d95b1ba Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Sun, 29 Oct 2023 12:49:41 +0100 Subject: [PATCH 10/30] upd: add quoteUri resolving and correct reblog/quote handling --- .../src/server/api/mastodon/converters.ts | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index 51556adf2..038319139 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -192,6 +192,11 @@ export class MastoConverters { return await Promise.all(history); } + private async convertReblog(status: Entity.Status | null): Promise { + if (!status) return null; + return await this.convertStatus(status); + } + public async convertStatus(status: Entity.Status) { const convertedAccount = this.convertAccount(status.account); const note = await this.getterService.getNote(status.id); @@ -216,11 +221,6 @@ export class MastoConverters { .catch(() => null))) .then(p => p.filter(m => m)) as Promise; - const content = note.text !== null - ? this.mfmService.toMastoHtml(mfm.parse(note.text!), JSON.parse(note.mentionedRemoteUsers), false, null) - .then(p => p ?? escapeMFM(note.text!)) - : ''; - const tags = note.tags.map(tag => { return { name: tag, @@ -228,6 +228,20 @@ export class MastoConverters { } as Entity.Tag; }); + const isQuote = note.renoteId && note.text ? true : false; + + const renote = note.renoteId ? this.getterService.getNote(note.renoteId) : null; + + const quoteUri = Promise.resolve(renote).then(renote => { + if (!renote || !isQuote) return null; + return renote.url ?? renote.uri ?? `${this.config.url}/notes/${renote.id}`; + }); + + const content = note.text !== null + ? quoteUri.then(quoteUri => this.mfmService.toMastoHtml(mfm.parse(note.text!), JSON.parse(note.mentionedRemoteUsers), false, quoteUri)) + .then(p => p ?? escapeMFM(note.text!)) + : ''; + // noinspection ES6MissingAwait return await awaitAll({ id: note.id, @@ -236,7 +250,7 @@ export class MastoConverters { account: convertedAccount, in_reply_to_id: note.replyId, in_reply_to_account_id: note.replyUserId, - reblog: status.reblog, + reblog: !isQuote ? await this.convertReblog(status.reblog) : null, content: content, content_type: 'text/x.misskeymarkdown', text: note.text, @@ -262,7 +276,7 @@ export class MastoConverters { reactions: status.emoji_reactions, emoji_reactions: status.emoji_reactions, bookmarked: false, - quote: false, + quote: isQuote ? await this.convertReblog(status.reblog) : null, edited_at: note.updatedAt?.toISOString(), }); } From cd1083cc3074f570e57877e125e305193b1a108f Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Sun, 29 Oct 2023 14:31:26 +0100 Subject: [PATCH 11/30] fix: relationships not working on some clients --- .../backend/src/server/api/mastodon/MastodonApiServerService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index 32534d9ef..efc2302f3 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -282,7 +282,7 @@ export class MastodonApiServerService { // displayed without being logged in let users; try { - let ids = _request.query ? (_request.query as any)['id[]'] : null; + let ids = _request.query ? (_request.query as any)['id[]'] ?? (_request.query as any)['id'] : null; if (typeof ids === 'string') { ids = [ids]; } From 8736560e5ff9e9038a48e7f26e01ec66ad3c00d5 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Sun, 29 Oct 2023 21:51:12 +0100 Subject: [PATCH 12/30] fix: reply edits not staying attached --- packages/megalodon/src/misskey.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/megalodon/src/misskey.ts b/packages/megalodon/src/misskey.ts index a8c7c44d0..69a8972d6 100644 --- a/packages/megalodon/src/misskey.ts +++ b/packages/megalodon/src/misskey.ts @@ -1148,6 +1148,7 @@ export default class Misskey implements MegalodonInterface { media_ids?: Array | null poll?: { options?: Array; expires_in?: number; multiple?: boolean; hide_totals?: boolean } visibility?: "public" | "unlisted" | "private" | "direct" + in_reply_to_id?: string } ): Promise> { let params = { @@ -1160,6 +1161,11 @@ export default class Misskey implements MegalodonInterface { fileIds: _options.media_ids }) } + if (_options.in_reply_to_id) { + params = Object.assign(params, { + replyId: _options.in_reply_to_id + }) + } if (_options.poll) { let pollParam = { choices: _options.poll.options, From b57ec5e2eb5e7ad30d9c989105cb2ac34de9d2ba Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Mon, 30 Oct 2023 00:27:04 +0100 Subject: [PATCH 13/30] test: avatar and header uploading --- packages/backend/src/misc/prelude/array.ts | 4 ++ .../api/mastodon/MastodonApiServerService.ts | 43 +++++++++++++++++-- packages/megalodon/src/misskey.ts | 10 +++++ 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/misc/prelude/array.ts b/packages/backend/src/misc/prelude/array.ts index b2f29bcec..8438b6480 100644 --- a/packages/backend/src/misc/prelude/array.ts +++ b/packages/backend/src/misc/prelude/array.ts @@ -142,3 +142,7 @@ export function toArray(x: T | T[] | undefined): T[] { export function toSingle(x: T | T[] | undefined): T | undefined { return Array.isArray(x) ? x[0] : x; } + +export function toSingleLast(x: T | T[] | undefined): T | undefined { + return Array.isArray(x) ? x.at(-1) : x; +} diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index efc2302f3..0bfa1a39f 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -3,16 +3,18 @@ import megalodon, { Entity, MegalodonInterface } from 'megalodon'; import querystring from 'querystring'; import { IsNull } from 'typeorm'; import multer from 'fastify-multer'; -import type { NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { AccessTokensRepository, NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; -import { convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList, MastoConverters } from './converters.js'; +import { convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList, MastoConverters } from './converters.js'; import { getInstance } from './endpoints/meta.js'; import { ApiAuthMastodon, ApiAccountMastodon, ApiFilterMastodon, ApiNotifyMastodon, ApiSearchMastodon, ApiTimelineMastodon, ApiStatusMastodon } from './endpoints.js'; import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DriveService } from '@/core/DriveService.js'; +import { toSingleLast } from '@/misc/prelude/array.js'; export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface { const accessTokenArr = authorization?.split(' ') ?? [null]; @@ -33,10 +35,13 @@ export class MastodonApiServerService { private userProfilesRepository: UserProfilesRepository, @Inject(DI.noteEditRepository) private noteEditRepository: NoteEditRepository, + @Inject(DI.accessTokensRepository) + private accessTokensRepository: AccessTokensRepository, @Inject(DI.config) private config: Config, private metaService: MetaService, private userEntityService: UserEntityService, + private driveService: DriveService, private mastoConverter: MastoConverters, ) { } @@ -245,16 +250,46 @@ export class MastodonApiServerService { } }); - fastify.patch('/v1/accounts/update_credentials', { preHandler: upload.none() }, async (_request, reply) => { + fastify.patch('/v1/accounts/update_credentials', { preHandler: upload.any() }, async (_request, reply) => { const BASE_URL = `${_request.protocol}://${_request.hostname}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt // displayed without being logged in try { + if (_request.files.length > 0) { + const tokeninfo = await this.accessTokensRepository.findOneBy({ token: accessTokens }); + console.error(tokeninfo); + if (tokeninfo && (_request.files as any)['avatar']) { + const file = toSingleLast((_request.files as any)['avatar']); + const user = await this.usersRepository.findOneBy({ id: tokeninfo.userId }); + const upload = await this.driveService.addFile({ + user: { id: tokeninfo.userId, host: user ? user.host : null }, + path: file.path, + name: file.originalname !== null && file.originalname !== 'file' ? file.originalname : undefined, + sensitive: false, + }); + if (upload.type.startsWith('image/')) { + (_request.body as any).avatar = upload.id; + } + } + if (tokeninfo && (_request.files as any)['header']) { + const file = toSingleLast((_request.files as any)['header']); + const user = await this.usersRepository.findOneBy({ id: tokeninfo.userId }); + const upload = await this.driveService.addFile({ + user: { id: tokeninfo.userId, host: user ? user.host : null }, + path: file.path, + name: file.originalname !== null && file.originalname !== 'file' ? file.originalname : undefined, + sensitive: false, + }); + if (upload.type.startsWith('image/')) { + (_request.body as any).header = upload.id; + } + } + } const data = await client.updateCredentials(_request.body!); reply.send(await this.mastoConverter.convertAccount(data.data)); } catch (e: any) { - /* console.error(e); */ + //console.error(e); reply.code(401).send(e.response.data); } }); diff --git a/packages/megalodon/src/misskey.ts b/packages/megalodon/src/misskey.ts index 69a8972d6..b81fb77a6 100644 --- a/packages/megalodon/src/misskey.ts +++ b/packages/megalodon/src/misskey.ts @@ -238,6 +238,16 @@ export default class Misskey implements MegalodonInterface { description: options.note }) } + if (options.avatar) { + params = Object.assign(params, { + avatarId: options.avatar + }) + } + if (options.header) { + params = Object.assign(params, { + bannerId: options.header + }) + } if (options.locked !== undefined) { params = Object.assign(params, { isLocked: options.locked.toString() === 'true' ? true : false From 2aa7c1ae34d2942a5a338510777a5a7e082b2a24 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Mon, 30 Oct 2023 00:29:58 +0100 Subject: [PATCH 14/30] upd: remove Bearer from auth when looking up token --- .../src/server/api/mastodon/MastodonApiServerService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index 0bfa1a39f..a956cf903 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -256,8 +256,8 @@ export class MastodonApiServerService { const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt // displayed without being logged in try { - if (_request.files.length > 0) { - const tokeninfo = await this.accessTokensRepository.findOneBy({ token: accessTokens }); + if (_request.files.length > 0 && accessTokens) { + const tokeninfo = await this.accessTokensRepository.findOneBy({ token: accessTokens.replace('Bearer ', '') }); console.error(tokeninfo); if (tokeninfo && (_request.files as any)['avatar']) { const file = toSingleLast((_request.files as any)['avatar']); From e8e4bafa6410fce77ec70efceec842926c666cfa Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Mon, 30 Oct 2023 00:34:19 +0100 Subject: [PATCH 15/30] upd: remove host lookup on file updating --- .../src/server/api/mastodon/MastodonApiServerService.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index a956cf903..06aaebf03 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -261,9 +261,8 @@ export class MastodonApiServerService { console.error(tokeninfo); if (tokeninfo && (_request.files as any)['avatar']) { const file = toSingleLast((_request.files as any)['avatar']); - const user = await this.usersRepository.findOneBy({ id: tokeninfo.userId }); const upload = await this.driveService.addFile({ - user: { id: tokeninfo.userId, host: user ? user.host : null }, + user: { id: tokeninfo.userId, host: null }, path: file.path, name: file.originalname !== null && file.originalname !== 'file' ? file.originalname : undefined, sensitive: false, @@ -274,9 +273,8 @@ export class MastodonApiServerService { } if (tokeninfo && (_request.files as any)['header']) { const file = toSingleLast((_request.files as any)['header']); - const user = await this.usersRepository.findOneBy({ id: tokeninfo.userId }); const upload = await this.driveService.addFile({ - user: { id: tokeninfo.userId, host: user ? user.host : null }, + user: { id: tokeninfo.userId, host: null }, path: file.path, name: file.originalname !== null && file.originalname !== 'file' ? file.originalname : undefined, sensitive: false, From be7d3859c3629dffb8e3d4a421978f67ddc3c02b Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Mon, 30 Oct 2023 00:45:38 +0100 Subject: [PATCH 16/30] test: update debug lines --- .../src/server/api/mastodon/MastodonApiServerService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index 06aaebf03..ac66d8d49 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -258,8 +258,9 @@ export class MastodonApiServerService { try { if (_request.files.length > 0 && accessTokens) { const tokeninfo = await this.accessTokensRepository.findOneBy({ token: accessTokens.replace('Bearer ', '') }); - console.error(tokeninfo); + console.error(_request.files); if (tokeninfo && (_request.files as any)['avatar']) { + console.error('avatar'); const file = toSingleLast((_request.files as any)['avatar']); const upload = await this.driveService.addFile({ user: { id: tokeninfo.userId, host: null }, @@ -267,6 +268,7 @@ export class MastodonApiServerService { name: file.originalname !== null && file.originalname !== 'file' ? file.originalname : undefined, sensitive: false, }); + console.error(upload); if (upload.type.startsWith('image/')) { (_request.body as any).avatar = upload.id; } From d942d4d0d01505b1a532b502181704f6310226fb Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Mon, 30 Oct 2023 00:57:12 +0100 Subject: [PATCH 17/30] upd: change resolving of _request.files --- .../api/mastodon/MastodonApiServerService.ts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index ac66d8d49..cc61a234d 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -258,14 +258,19 @@ export class MastodonApiServerService { try { if (_request.files.length > 0 && accessTokens) { const tokeninfo = await this.accessTokensRepository.findOneBy({ token: accessTokens.replace('Bearer ', '') }); - console.error(_request.files); - if (tokeninfo && (_request.files as any)['avatar']) { + const avatar = (_request.files as any).find((obj: any) => { + return obj.fieldname === 'avatar'; + }); + const header = (_request.files as any).find((obj: any) => { + return obj.fieldname === 'header'; + }); + + if (tokeninfo && avatar) { console.error('avatar'); - const file = toSingleLast((_request.files as any)['avatar']); const upload = await this.driveService.addFile({ user: { id: tokeninfo.userId, host: null }, - path: file.path, - name: file.originalname !== null && file.originalname !== 'file' ? file.originalname : undefined, + path: avatar.path, + name: avatar.originalname !== null && avatar.originalname !== 'file' ? avatar.originalname : undefined, sensitive: false, }); console.error(upload); @@ -273,12 +278,11 @@ export class MastodonApiServerService { (_request.body as any).avatar = upload.id; } } - if (tokeninfo && (_request.files as any)['header']) { - const file = toSingleLast((_request.files as any)['header']); + if (tokeninfo && (_request.files as any)['header']) { const upload = await this.driveService.addFile({ user: { id: tokeninfo.userId, host: null }, - path: file.path, - name: file.originalname !== null && file.originalname !== 'file' ? file.originalname : undefined, + path: header.path, + name: header.originalname !== null && header.originalname !== 'file' ? header.originalname : undefined, sensitive: false, }); if (upload.type.startsWith('image/')) { From c88fbe843a10c56dbe74311a8eeb4b6b1e025d7b Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Mon, 30 Oct 2023 01:04:55 +0100 Subject: [PATCH 18/30] upd: remove debug lines, fix header not being detected --- .../src/server/api/mastodon/MastodonApiServerService.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index cc61a234d..bdf3af058 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -258,27 +258,27 @@ export class MastodonApiServerService { try { if (_request.files.length > 0 && accessTokens) { const tokeninfo = await this.accessTokensRepository.findOneBy({ token: accessTokens.replace('Bearer ', '') }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const avatar = (_request.files as any).find((obj: any) => { return obj.fieldname === 'avatar'; }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const header = (_request.files as any).find((obj: any) => { return obj.fieldname === 'header'; }); if (tokeninfo && avatar) { - console.error('avatar'); const upload = await this.driveService.addFile({ user: { id: tokeninfo.userId, host: null }, path: avatar.path, name: avatar.originalname !== null && avatar.originalname !== 'file' ? avatar.originalname : undefined, sensitive: false, }); - console.error(upload); if (upload.type.startsWith('image/')) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any (_request.body as any).avatar = upload.id; } - } - if (tokeninfo && (_request.files as any)['header']) { + } else if (tokeninfo && header) { const upload = await this.driveService.addFile({ user: { id: tokeninfo.userId, host: null }, path: header.path, @@ -286,6 +286,7 @@ export class MastodonApiServerService { sensitive: false, }); if (upload.type.startsWith('image/')) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any (_request.body as any).header = upload.id; } } From 81def9457bdf15a566d808dec63e344ee0ebc7d5 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Mon, 30 Oct 2023 01:32:48 +0100 Subject: [PATCH 19/30] upd: allow updating of fields --- .../api/mastodon/MastodonApiServerService.ts | 16 ++++++++++- .../server/api/mastodon/endpoints/account.ts | 28 ++++++++----------- packages/megalodon/src/misskey.ts | 5 ++++ 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index bdf3af058..55e4615b9 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -14,7 +14,6 @@ import { ApiAuthMastodon, ApiAccountMastodon, ApiFilterMastodon, ApiNotifyMastod import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveService } from '@/core/DriveService.js'; -import { toSingleLast } from '@/misc/prelude/array.js'; export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface { const accessTokenArr = authorization?.split(' ') ?? [null]; @@ -256,6 +255,7 @@ export class MastodonApiServerService { const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt // displayed without being logged in try { + // Check if there is an Header or Avatar being uploaded, if there is proceed to upload it to the drive of the user and then set it. if (_request.files.length > 0 && accessTokens) { const tokeninfo = await this.accessTokensRepository.findOneBy({ token: accessTokens.replace('Bearer ', '') }); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -291,6 +291,20 @@ export class MastodonApiServerService { } } } + + if ((_request.body as any).fields_attributes) { + const fields = (_request.body as any).fields_attributes.map((field: any) => { + if (!(field.name.trim() === '' && field.value.trim() === '')) { + if (field.name.trim() === '') return reply.code(400).send('Field name can not be empty'); + if (field.value.trim() === '') return reply.code(400).send('Field value can not be empty'); + } + return { + ...field, + }; + }); + (_request.body as any).fields_attributes = fields.filter((field: any) => field.name.trim().length > 0 && field.value.length > 0); + } + const data = await client.updateCredentials(_request.body!); reply.send(await this.mastoConverter.convertAccount(data.data)); } catch (e: any) { diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 39da0e4a5..694879764 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -39,22 +39,18 @@ export class ApiAccountMastodon { public async verifyCredentials() { try { const data = await this.client.verifyAccountCredentials(); - const acct = data.data; - acct.display_name = acct.display_name || acct.username; - acct.url = `${this.BASE_URL}/@${acct.url}`; - acct.note = acct.note || ''; - acct.avatar_static = acct.avatar; - acct.header = acct.header || '/static-assets/transparent.png'; - acct.header_static = acct.header || '/static-assets/transparent.png'; - acct.source = { - note: acct.note, - fields: acct.fields, - privacy: '', - sensitive: false, - language: '', - }; - console.log(acct); - return acct; + const acct = await this.mastoconverter.convertAccount(data.data); + const newAcct = Object.assign({}, acct, { + source: { + note: acct.note, + fields: acct.fields, + privacy: '', + sensitive: false, + language: '', + }, + }); + console.log(newAcct); + return newAcct; } catch (e: any) { /* console.error(e); console.error(e.response.data); */ diff --git a/packages/megalodon/src/misskey.ts b/packages/megalodon/src/misskey.ts index b81fb77a6..a0b222c23 100644 --- a/packages/megalodon/src/misskey.ts +++ b/packages/megalodon/src/misskey.ts @@ -248,6 +248,11 @@ export default class Misskey implements MegalodonInterface { bannerId: options.header }) } + if (options.fields_attributes) { + params = Object.assign(params, { + fields: options.fields_attributes + }) + } if (options.locked !== undefined) { params = Object.assign(params, { isLocked: options.locked.toString() === 'true' ? true : false From e24a57402b0769fac49174f5f47a8d7a46828812 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Mon, 30 Oct 2023 01:47:24 +0100 Subject: [PATCH 20/30] chore: lint --- .../src/server/api/mastodon/MastodonApiServerService.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index 55e4615b9..4a81320de 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -292,7 +292,9 @@ export class MastodonApiServerService { } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((_request.body as any).fields_attributes) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const fields = (_request.body as any).fields_attributes.map((field: any) => { if (!(field.name.trim() === '' && field.value.trim() === '')) { if (field.name.trim() === '') return reply.code(400).send('Field name can not be empty'); @@ -302,9 +304,10 @@ export class MastodonApiServerService { ...field, }; }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any (_request.body as any).fields_attributes = fields.filter((field: any) => field.name.trim().length > 0 && field.value.length > 0); } - + const data = await client.updateCredentials(_request.body!); reply.send(await this.mastoConverter.convertAccount(data.data)); } catch (e: any) { From 46bb5f2dac16a54bcaedafaed397c8cdc02d7008 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Mon, 30 Oct 2023 12:53:28 +0100 Subject: [PATCH 21/30] fix: lists not being received properly --- .../src/server/api/mastodon/endpoints/timeline.ts | 3 +-- packages/megalodon/src/misskey.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index 152d4a772..0f2b20815 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -151,8 +151,7 @@ export class ApiTimelineMastodon { const BASE_URL = `${_request.protocol}://${_request.hostname}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); - const account = await client.verifyAccountCredentials(); - const data = await client.getLists(account.data.id); + const data = await client.getLists(); reply.send(data.data.map((list: Entity.List) => convertList(list))); } catch (e: any) { console.error(e); diff --git a/packages/megalodon/src/misskey.ts b/packages/megalodon/src/misskey.ts index a0b222c23..7d68d4edd 100644 --- a/packages/megalodon/src/misskey.ts +++ b/packages/megalodon/src/misskey.ts @@ -1892,9 +1892,15 @@ export default class Misskey implements MegalodonInterface { /** * POST /api/users/lists/list */ - public async getLists(id: string): Promise>> { + public async getLists(id?: string): Promise>> { + if (id) { + return this.client + .post>('/api/users/lists/list', { userId: id }) + .then(res => ({ ...res, data: res.data.map(l => MisskeyAPI.Converter.list(l)) })) + } + return this.client - .post>('/api/users/lists/list', { userId: id }) + .post>('/api/users/lists/list', {}) .then(res => ({ ...res, data: res.data.map(l => MisskeyAPI.Converter.list(l)) })) } From 4fa15f27c717f2aba27a8c38cb1ca18629cdd20d Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Mon, 30 Oct 2023 13:00:58 +0100 Subject: [PATCH 22/30] fix: deleting a list returning an error as success --- packages/backend/src/server/api/mastodon/endpoints/timeline.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index 0f2b20815..f81b63b9a 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -257,7 +257,7 @@ export class ApiTimelineMastodon { const client = getClient(BASE_URL, accessTokens); const params: any = _request.params; const data = await client.deleteList(params.id); - reply.send(data.data); + reply.send({}); } catch (e: any) { console.error(e); console.error(e.response.data); From 20a8bb0467d5328ad1ecdbae8fba6c622bad41c6 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Mon, 30 Oct 2023 13:09:20 +0100 Subject: [PATCH 23/30] fix: search being broken on dev through mastodon --- packages/backend/src/core/SearchService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index 6103b0e0f..ccdee87b1 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -183,7 +183,7 @@ export class SearchService { } } const res = await this.meilisearchNoteIndex!.search(q, { - sort: [`createdAt:${opts.order}`], + sort: [`createdAt:${opts.order ? opts.order : 'desc'}`], matchingStrategy: 'all', attributesToRetrieve: ['id', 'createdAt'], filter: compileQuery(filter), From d15c588080a56811d19b03d054080d5be64f64f1 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Tue, 31 Oct 2023 11:31:31 +0100 Subject: [PATCH 24/30] upd: change handling of suggestions and status trends --- .../server/api/mastodon/endpoints/search.ts | 101 +++++------------- packages/megalodon/src/megalodon.ts | 2 +- 2 files changed, 30 insertions(+), 73 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index 772ef3307..633a3d05e 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -1,63 +1,8 @@ -import { Converter } from 'megalodon'; -import { MastoConverters, convertAccount, convertStatus } from '../converters.js'; +import { MastoConverters } from '../converters.js'; import { limitToInt } from './timeline.js'; import type { MegalodonInterface } from 'megalodon'; import type { FastifyRequest } from 'fastify'; -async function getHighlight( - BASE_URL: string, - domain: string, - accessTokens: string | undefined, -) { - const accessTokenArr = accessTokens?.split(' ') ?? [null]; - const accessToken = accessTokenArr[accessTokenArr.length - 1]; - try { - const apicall = await fetch(`${BASE_URL}/api/notes/featured`, - { - method: 'POST', - headers: { - 'Accept': 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ i: accessToken }), - }); - const api = await apicall.json(); - const data: MisskeyEntity.Note[] = api; - return data.map((note) => Converter.note(note, domain)); - } catch (e: any) { - console.log(e); - console.log(e.response.data); - return []; - } -} - -async function getFeaturedUser( BASE_URL: string, host: string, accessTokens: string | undefined, limit: number ) { - const accessTokenArr = accessTokens?.split(' ') ?? [null]; - const accessToken = accessTokenArr[accessTokenArr.length - 1]; - try { - const apicall = await fetch(`${BASE_URL}/api/users`, - { - method: 'POST', - headers: { - 'Accept': 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ i: accessToken, limit, origin: 'local', sort: '+follower', state: 'alive' }), - }); - const api = await apicall.json(); - const data: MisskeyEntity.UserDetail[] = api; - return data.map((u) => { - return { - source: 'past_interactions', - account: Converter.userDetail(u, host), - }; - }); - } catch (e: any) { - console.log(e); - console.log(e.response.data); - return []; - } -} export class ApiSearchMastodon { private request: FastifyRequest; private client: MegalodonInterface; @@ -89,8 +34,8 @@ export class ApiSearchMastodon { const stat = !type || type === 'statuses' ? await this.client.search(query.q, { type: 'statuses', ...query }) : null; const tags = !type || type === 'hashtags' ? await this.client.search(query.q, { type: 'hashtags', ...query }) : null; const data = { - accounts: await Promise.all(acct?.data.accounts.map(async (account) => await this.mastoConverter.convertAccount(account)) ?? []), - statuses: await Promise.all(stat?.data.statuses.map(async (status) => await this.mastoConverter.convertStatus(status)) ?? []), + accounts: await Promise.all(acct?.data.accounts.map(async (account: any) => await this.mastoConverter.convertAccount(account)) ?? []), + statuses: await Promise.all(stat?.data.statuses.map(async (status: any) => await this.mastoConverter.convertStatus(status)) ?? []), hashtags: tags?.data.hashtags ?? [], }; return data; @@ -102,27 +47,39 @@ export class ApiSearchMastodon { public async getStatusTrends() { try { - const data = await getHighlight( - this.BASE_URL, - this.request.hostname, - this.request.headers.authorization, - ); - return data.map((status) => convertStatus(status)); + let map; + await fetch(`${this.BASE_URL}/api/notes/featured`, + { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + }) + .then(res => res.json()) + .then((data) => { + map = data.map((status: any) => this.mastoConverter.convertStatus(status)); + }); + return map; } catch (e: any) { console.error(e); - return e.response.data; + return []; } } public async getSuggestions() { try { - const data = await getFeaturedUser( - this.BASE_URL, - this.request.hostname, - this.request.headers.authorization, - (this.request.query as any).limit || 20, - ); - return data.map((suggestion) => { suggestion.account = convertAccount(suggestion.account); return suggestion; }); + const data = await fetch(`${this.BASE_URL}/api/users`, + { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ i: this.request.headers.authorization?.replace('Bearer ', ''), limit: (this.request.query as any).limit || 20, origin: 'local', sort: '+follower', state: 'alive' }), + }).then((res) => res.json()).then((data) => data.map(((entry: any) => { return { source: 'global', account: entry }; }))); + return Promise.all(data.map(async (suggestion: any) => { suggestion.account = await this.mastoConverter.convertAccount(suggestion.account); return suggestion; })); } catch (e: any) { console.error(e); return e.response.data; diff --git a/packages/megalodon/src/megalodon.ts b/packages/megalodon/src/megalodon.ts index 19cd5c555..e2245f7c2 100644 --- a/packages/megalodon/src/megalodon.ts +++ b/packages/megalodon/src/megalodon.ts @@ -1041,7 +1041,7 @@ export interface MegalodonInterface { * * @return Array of lists. */ - getLists(id: string): Promise>> + getLists(id?: string): Promise>> /** * Show a single list. * From 0a9ca195e27321c8f959bfb69a7272a70bfaaa89 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Tue, 31 Oct 2023 11:34:25 +0100 Subject: [PATCH 25/30] fix: create trends/tags endpoint --- .../api/mastodon/MastodonApiServerService.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index 4a81320de..8a9339259 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -207,6 +207,20 @@ export class MastodonApiServerService { } }); + fastify.get('/v1/trends/tags', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.hostname}`; + const accessTokens = _request.headers.authorization; + const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt + // displayed without being logged in + try { + const data = await client.getInstanceTrends(); + reply.send(data.data); + } catch (e: any) { + /* console.error(e); */ + reply.code(401).send(e.response.data); + } + }); + fastify.post('/v1/apps', async (_request, reply) => { const BASE_URL = `${_request.protocol}://${_request.hostname}`; const client = getClient(BASE_URL, ''); // we are using this here, because in private mode some info isnt From 171ba6f4f579d7bec3b662e1b19651056a50f62b Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Tue, 31 Oct 2023 11:38:23 +0100 Subject: [PATCH 26/30] chore: change style of getStatusTrends --- .../src/server/api/mastodon/endpoints/search.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index 633a3d05e..2087d13ad 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -47,8 +47,7 @@ export class ApiSearchMastodon { public async getStatusTrends() { try { - let map; - await fetch(`${this.BASE_URL}/api/notes/featured`, + const data = await fetch(`${this.BASE_URL}/api/notes/featured`, { method: 'POST', headers: { @@ -58,10 +57,8 @@ export class ApiSearchMastodon { body: JSON.stringify({}), }) .then(res => res.json()) - .then((data) => { - map = data.map((status: any) => this.mastoConverter.convertStatus(status)); - }); - return map; + .then(data => data.map((status: any) => this.mastoConverter.convertStatus(status))); + return data; } catch (e: any) { console.error(e); return []; @@ -82,7 +79,7 @@ export class ApiSearchMastodon { return Promise.all(data.map(async (suggestion: any) => { suggestion.account = await this.mastoConverter.convertAccount(suggestion.account); return suggestion; })); } catch (e: any) { console.error(e); - return e.response.data; + return []; } } } From 98389114a8cba8c41f861bf7f8036827069042bf Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Tue, 31 Oct 2023 11:46:08 +0100 Subject: [PATCH 27/30] fix: suggestions --- packages/backend/src/server/api/mastodon/endpoints/search.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index 2087d13ad..432a1f144 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -74,8 +74,8 @@ export class ApiSearchMastodon { 'Accept': 'application/json', 'Content-Type': 'application/json', }, - body: JSON.stringify({ i: this.request.headers.authorization?.replace('Bearer ', ''), limit: (this.request.query as any).limit || 20, origin: 'local', sort: '+follower', state: 'alive' }), - }).then((res) => res.json()).then((data) => data.map(((entry: any) => { return { source: 'global', account: entry }; }))); + body: JSON.stringify({ limit: (this.request.query as any).limit || 20, origin: 'local', sort: '+follower', state: 'alive' }), + }).then((res) => res.json()).then(data => data.map(((entry: any) => { return { source: 'global', account: entry }; }))); return Promise.all(data.map(async (suggestion: any) => { suggestion.account = await this.mastoConverter.convertAccount(suggestion.account); return suggestion; })); } catch (e: any) { console.error(e); From c18efe0042a8f87a7fc204e6f7bc29ff4f505e7e Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Tue, 31 Oct 2023 11:49:34 +0100 Subject: [PATCH 28/30] upd: add empty return on trends/links --- .../src/server/api/mastodon/MastodonApiServerService.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index 8a9339259..c0e4ea80d 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -221,6 +221,11 @@ export class MastodonApiServerService { } }); + fastify.get('/v1/trends/links', async (_request, reply) => { + // As we do not have any system for news/links this will just return empty + reply.send([]); + }); + fastify.post('/v1/apps', async (_request, reply) => { const BASE_URL = `${_request.protocol}://${_request.hostname}`; const client = getClient(BASE_URL, ''); // we are using this here, because in private mode some info isnt From 6673c7fc13e45141ce9ec10cf4b8d9eb25f6a1fc Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Tue, 31 Oct 2023 12:06:47 +0100 Subject: [PATCH 29/30] fix: limit query being returned as string instead of int --- packages/backend/src/server/api/mastodon/endpoints/search.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index 432a1f144..500129c90 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -74,7 +74,7 @@ export class ApiSearchMastodon { 'Accept': 'application/json', 'Content-Type': 'application/json', }, - body: JSON.stringify({ limit: (this.request.query as any).limit || 20, origin: 'local', sort: '+follower', state: 'alive' }), + body: JSON.stringify({ i: this.request.headers.authorization?.replace('Bearer ', ''), limit: parseInt((this.request.query as any).limit) || 20, origin: 'local', sort: '+follower', state: 'alive' }), }).then((res) => res.json()).then(data => data.map(((entry: any) => { return { source: 'global', account: entry }; }))); return Promise.all(data.map(async (suggestion: any) => { suggestion.account = await this.mastoConverter.convertAccount(suggestion.account); return suggestion; })); } catch (e: any) { From b57b644ec6ba51c5d561fc499e21dc9632858746 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Tue, 31 Oct 2023 12:13:37 +0100 Subject: [PATCH 30/30] chore: remove console log on verify_credentials --- packages/backend/src/server/api/mastodon/endpoints/account.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 694879764..07d9efb8c 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -49,7 +49,6 @@ export class ApiAccountMastodon { language: '', }, }); - console.log(newAcct); return newAcct; } catch (e: any) { /* console.error(e);