From 54578f69656ed534dfb28c4bf67b03d7b30bd261 Mon Sep 17 00:00:00 2001 From: Marie Date: Sun, 1 Oct 2023 01:58:06 +0200 Subject: [PATCH] upd: add MFM to HTML support and Mentions parsing to mastodon api (#33) * upd: attempt to turn MFM to html on mastodon * revert: recent change until better implementation later * chore: remove unused packages * Update docker.yml * upd: add MFM to HTML for timelines and status view * chore: lint * upd: megalodon resolve urls * upd: add spliting * test: local user mention * test: change local user url in mention * upd: change check * test: megalodon changes * upd: edit resolving of local users This is starting to drive me nuts * upd: remove the @ symbol in query * fix: make renderPerson return host instead of null for local * upd: change url for local user * upd: change limit * upd: add url to output * upd: add mastodon boolean * test: test different format * fix: test of different format * test: change up resolving * fix: forgot to provide url * upd: change lookup function a bit * test: substring * test: regex * upd: remove substr * test: new regexs * dirty test * test: one last attempt for today * upd: fix build error * upd: take input from iceshrimp dev * upd: parse remote statuses * upd: fix pleroma users misformatted urls * upd: add uri to normal user * fix: forgot to push updated types * fix: resolving broke * fix: html not converting correctly * fix: return default img if no banner * upd: swap out img used for no header, set fallback avatar * fix: html escaped & and ' symbols * upd: fix ' converting into 39; and get profile fields * upd: resolve fields on lookup --------- Co-authored-by: Amelia Yukii <123300075+Insert5StarName@users.noreply.github.com> --- .github/workflows/docker.yml | 6 ++ packages/backend/assets/transparent.png | Bin 0 -> 68 bytes packages/backend/src/core/MfmService.ts | 7 ++ .../src/server/api/endpoints/ap/show.ts | 2 +- .../api/mastodon/MastodonApiServerService.ts | 20 +++- .../src/server/api/mastodon/converters.ts | 95 ++++++++++++++++++ .../server/api/mastodon/endpoints/account.ts | 2 +- .../server/api/mastodon/endpoints/status.ts | 39 +++---- .../server/api/mastodon/endpoints/timeline.ts | 17 ++-- packages/megalodon/src/misskey.ts | 68 ++++++++++++- packages/megalodon/src/misskey/api_client.ts | 20 ++-- .../megalodon/src/misskey/entities/user.ts | 1 + .../src/misskey/entities/userDetail.ts | 1 + 13 files changed, 237 insertions(+), 41 deletions(-) create mode 100644 packages/backend/assets/transparent.png diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8140d8675e..db2bc7faa4 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,6 +1,12 @@ name: Publish Docker image on: + push: + branches: + - stable + paths: + - packages/** + - locales/** release: types: [published] workflow_dispatch: diff --git a/packages/backend/assets/transparent.png b/packages/backend/assets/transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..240ca4f8d4edca6d5905acf71bdd9f88d4bd3127 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kP61+gajamnSrtAhou#e O#o+1c=d#Wzp$Py;d<%g9 literal 0 HcmV?d00001 diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index b275d1b142..19e2288100 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -339,6 +339,13 @@ export class MfmService { mention: (node) => { const a = doc.createElement('a'); const { username, host, acct } = node.props; +/* if (mastodon) { + const splitacct = acct.split("@"); + a.setAttribute('href', splitacct[2] !== this.config.host && splitacct[2] !== undefined ? `https://${splitacct[2]}/@${splitacct[1]}` : `https://${this.config.host}/${acct}`); + a.className = 'u-url mention'; + a.textContent = acct; + return a; + } */ const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host); a.setAttribute('href', remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${this.config.url}/${acct}`); a.className = 'u-url mention'; diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index f442fbdd2f..6cdd617561 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -27,7 +27,7 @@ export const meta = { requireCredential: true, limit: { - duration: ms('1hour'), + duration: ms('1minute'), max: 30, }, diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index fe9f1fc871..2653bbdbdf 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -3,7 +3,7 @@ import megalodon, { Entity, MegalodonInterface } from 'megalodon'; import querystring from 'querystring'; import { IsNull } from 'typeorm'; import multer from 'fastify-multer'; -import type { UsersRepository } from '@/models/_.js'; +import type { NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import type { Config } from '@/config.js'; @@ -12,6 +12,7 @@ import { convertId, IdConvertType as IdType, convertAccount, convertAnnouncement 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'; export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface { const accessTokenArr = authorization?.split(' ') ?? [null]; @@ -26,9 +27,14 @@ export class MastodonApiServerService { constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, @Inject(DI.config) private config: Config, private metaService: MetaService, + private userEntityService: UserEntityService, ) { } @bindThis @@ -256,8 +262,10 @@ 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); - reply.send(await account.lookup()); + 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])); } catch (e: any) { /* console.error(e); */ reply.code(401).send(e.response.data); @@ -294,6 +302,8 @@ export class MastodonApiServerService { try { const sharkId = convertId(_request.params.id, IdType.SharkeyId); 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)); } catch (e: any) { /* console.error(e); @@ -744,7 +754,7 @@ export class MastodonApiServerService { //#endregion //#region Timelines - const TLEndpoint = new ApiTimelineMastodon(fastify); + const TLEndpoint = new ApiTimelineMastodon(fastify, this.config, this.usersRepository, this.notesRepository, this.userEntityService); // GET Endpoints TLEndpoint.getTL(); @@ -769,7 +779,7 @@ export class MastodonApiServerService { //#endregion //#region Status - const NoteEndpoint = new ApiStatusMastodon(fastify); + const NoteEndpoint = new ApiStatusMastodon(fastify, this.config, this.usersRepository, this.notesRepository, this.userEntityService); // 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 58b8dc23ca..4f4524736d 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -1,4 +1,14 @@ +import type { Config } from '@/config.js'; +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 { GetterService } from '../GetterService.js'; +import type { IMentionedRemoteUsers } from '@/models/Note.js'; +import type { MiUser } from '@/models/User.js'; +import type { NotesRepository, UsersRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; const CHAR_COLLECTION = '0123456789abcdefghijklmnopqrstuvwxyz'; @@ -7,6 +17,91 @@ export enum IdConvertType { SharkeyId, } +export const escapeMFM = (text: string): string => text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") + .replace(/`/g, "`") + .replace(/\r?\n/g, "
"); + +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, + + private userEntityService: UserEntityService + ) { + this.MfmService = new MfmService(this.config); + this.GetterService = new GetterService(this.usersRepository, this.notesRepository, this.userEntityService); + } + + private encode(u: MiUser, m: IMentionedRemoteUsers): MastodonEntity.Mention { + let acct = u.username; + let acctUrl = `https://${u.host || this.config.host}/@${u.username}`; + let url: string | null = null; + if (u.host) { + const info = m.find(r => r.username === u.username && r.host === u.host); + acct = `${u.username}@${u.host}`; + acctUrl = `https://${u.host}/@${u.username}`; + if (info) url = info.url ?? info.uri; + } + return { + id: u.id, + username: u.username, + acct: acct, + url: url ?? acctUrl, + }; + } + + public async getUser(id: string): Promise { + return this.GetterService.getUser(id).then(p => { + return p; + }) + } + + public async convertStatus(status: Entity.Status) { + status.account = 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(`&` , "\'")! : status.content; + if (status.poll) status.poll = convertPoll(status.poll); + if (status.reblog) status.reblog = convertStatus(status.reblog); + + return status; + } +} + export function convertId(in_id: string, id_convert_type: IdConvertType): string { switch (id_convert_type) { case IdConvertType.MastodonId: { diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 4abb5fff19..24ebe0c48b 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -63,7 +63,7 @@ export class ApiAccountMastodon { const data = await this.client.search((this.request.query as any).acct, { type: 'accounts' }); return convertAccount(data.data.accounts[0]); } catch (e: any) { - /* console.error(e); + /* console.error(e) console.error(e.response.data); */ return e.response; } diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index a295564b90..46dce65081 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -1,10 +1,13 @@ import querystring from 'querystring'; import { emojiRegexAtStartToEnd } from '@/misc/emoji-regex.js'; -import { convertId, IdConvertType as IdType, convertAccount, convertAttachment, convertPoll, convertStatus, convertStatusSource } from '../converters.js'; +import { convertId, IdConvertType as IdType, convertAccount, convertAttachment, convertPoll, convertStatusSource, MastoConverters } from '../converters.js'; import { getClient } from '../MastodonApiServerService.js'; import { convertTimelinesArgsId, limitToInt } from './timeline.js'; import type { Entity } from 'megalodon'; import type { FastifyInstance } from 'fastify'; +import type { Config } from '@/config.js'; +import { NotesRepository, UsersRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; function normalizeQuery(data: any) { const str = querystring.stringify(data); @@ -13,9 +16,11 @@ function normalizeQuery(data: any) { export class ApiStatusMastodon { private fastify: FastifyInstance; + private mastoconverter: MastoConverters; - constructor(fastify: FastifyInstance) { + constructor(fastify: FastifyInstance, config: Config, usersrepo: UsersRepository, notesrepo: NotesRepository, userentity: UserEntityService) { this.fastify = fastify; + this.mastoconverter = new MastoConverters(config, usersrepo, notesrepo, userentity); } public async getStatus() { @@ -25,7 +30,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getStatus(convertId(_request.params.id, IdType.SharkeyId)); - reply.send(convertStatus(data.data)); + reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); reply.code(_request.is404 ? 404 : 401).send(e.response.data); @@ -59,8 +64,8 @@ export class ApiStatusMastodon { convertId(_request.params.id, IdType.SharkeyId), convertTimelinesArgsId(limitToInt(query)), ); - data.data.ancestors = data.data.ancestors.map((status: Entity.Status) => convertStatus(status)); - data.data.descendants = data.data.descendants.map((status: Entity.Status) => convertStatus(status)); + 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); } catch (e: any) { console.error(e); @@ -219,7 +224,7 @@ export class ApiStatusMastodon { } const data = await client.postStatus(text, body); - reply.send(convertStatus(data.data as Entity.Status)); + reply.send(await this.mastoconverter.convertStatus(data.data as Entity.Status)); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -240,7 +245,7 @@ export class ApiStatusMastodon { 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); - reply.send(convertStatus(data.data)); + reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); reply.code(_request.is404 ? 404 : 401).send(e.response.data); @@ -258,7 +263,7 @@ export class ApiStatusMastodon { convertId(_request.params.id, IdType.SharkeyId), '❤', )) as any; - reply.send(convertStatus(data.data)); + reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -276,7 +281,7 @@ export class ApiStatusMastodon { convertId(_request.params.id, IdType.SharkeyId), '❤', ); - reply.send(convertStatus(data.data)); + reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -291,7 +296,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); try { const data = await client.reblogStatus(convertId(_request.params.id, IdType.SharkeyId)); - reply.send(convertStatus(data.data)); + reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -306,7 +311,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); try { const data = await client.unreblogStatus(convertId(_request.params.id, IdType.SharkeyId)); - reply.send(convertStatus(data.data)); + reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -321,7 +326,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); try { const data = await client.bookmarkStatus(convertId(_request.params.id, IdType.SharkeyId)); - reply.send(convertStatus(data.data)); + reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -336,7 +341,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); try { const data = await client.unbookmarkStatus(convertId(_request.params.id, IdType.SharkeyId)); - reply.send(convertStatus(data.data)); + reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -351,7 +356,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); try { const data = await client.pinStatus(convertId(_request.params.id, IdType.SharkeyId)); - reply.send(convertStatus(data.data)); + reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -366,7 +371,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); try { const data = await client.unpinStatus(convertId(_request.params.id, IdType.SharkeyId)); - reply.send(convertStatus(data.data)); + reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -381,7 +386,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); try { const data = await client.createEmojiReaction(convertId(_request.params.id, IdType.SharkeyId), _request.params.name); - reply.send(convertStatus(data.data)); + reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); @@ -396,7 +401,7 @@ export class ApiStatusMastodon { const client = getClient(BASE_URL, accessTokens); try { const data = await client.deleteEmojiReaction(convertId(_request.params.id, IdType.SharkeyId), _request.params.name); - reply.send(convertStatus(data.data)); + reply.send(await this.mastoconverter.convertStatus(data.data)); } catch (e: any) { console.error(e); reply.code(401).send(e.response.data); diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index a171205161..bb66a7707c 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -1,8 +1,11 @@ import { ParsedUrlQuery } from 'querystring'; -import { convertId, IdConvertType as IdType, convertAccount, convertConversation, convertList, convertStatus } from '../converters.js'; +import { convertId, IdConvertType as IdType, convertAccount, convertConversation, convertList, MastoConverters } from '../converters.js'; import { getClient } from '../MastodonApiServerService.js'; import type { Entity } from 'megalodon'; import type { FastifyInstance } from 'fastify'; +import type { Config } from '@/config.js'; +import { NotesRepository, UsersRepository } from '@/models/_.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; export function limitToInt(q: ParsedUrlQuery) { const object: any = q; @@ -38,9 +41,11 @@ export function convertTimelinesArgsId(q: ParsedUrlQuery) { export class ApiTimelineMastodon { private fastify: FastifyInstance; + private mastoconverter: MastoConverters; - constructor(fastify: FastifyInstance) { + constructor(fastify: FastifyInstance, config: Config, usersRepository: UsersRepository, notesRepository: NotesRepository, userEntityService: UserEntityService) { this.fastify = fastify; + this.mastoconverter = new MastoConverters(config, usersRepository, notesRepository, userEntityService); } public async getTL() { @@ -53,7 +58,7 @@ export class ApiTimelineMastodon { const data = query.local === 'true' ? await client.getLocalTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query)))) : await client.getPublicTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query)))); - reply.send(data.data.map((status: Entity.Status) => convertStatus(status))); + reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)))); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -70,7 +75,7 @@ export class ApiTimelineMastodon { try { const query: any = _request.query; const data = await client.getHomeTimeline(convertTimelinesArgsId(limitToInt(query))); - reply.send(data.data.map((status: Entity.Status) => convertStatus(status))); + reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)))); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -88,7 +93,7 @@ export class ApiTimelineMastodon { const query: any = _request.query; const params: any = _request.params; const data = await client.getTagTimeline(params.hashtag, convertTimelinesArgsId(limitToInt(query))); - reply.send(data.data.map((status: Entity.Status) => convertStatus(status))); + reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)))); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -106,7 +111,7 @@ export class ApiTimelineMastodon { const query: any = _request.query; const params: any = _request.params; const data = await client.getListTimeline(convertId(params.id, IdType.SharkeyId), convertTimelinesArgsId(limitToInt(query))); - reply.send(data.data.map((status: Entity.Status) => convertStatus(status))); + reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)))); } 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 104d27a5d3..de2b6e2e78 100644 --- a/packages/megalodon/src/misskey.ts +++ b/packages/megalodon/src/misskey.ts @@ -2122,6 +2122,40 @@ export default class Misskey implements MegalodonInterface { ): Promise> { switch (options.type) { case 'accounts': { + if (q.startsWith("http://") || q.startsWith("https://")) { + return this.client + .post("/api/ap/show", { uri: q }) + .then(async (res) => { + if (res.status != 200 || res.data.type != "User") { + res.status = 200; + res.statusText = "OK"; + res.data = { + accounts: [], + statuses: [], + hashtags: [], + }; + + return res; + } + + const account = await MisskeyAPI.Converter.userDetail( + res.data.object as MisskeyAPI.Entity.UserDetail, + this.baseUrl, + ); + + return { + ...res, + data: { + accounts: + options?.max_id && options?.max_id >= account.id + ? [] + : [account], + statuses: [], + hashtags: [], + }, + }; + }); + } let params = { query: q } @@ -2151,7 +2185,7 @@ export default class Misskey implements MegalodonInterface { }); } try { - const match = q.match(/^@?(?[a-zA-Z0-9_]+)(?:@(?[a-zA-Z0-9-.]+\.[a-zA-Z0-9-]+)|)$/); + const match = params.query.match(/^@?(?[a-zA-Z0-9_]+)(?:@(?[a-zA-Z0-9-.]+\.[a-zA-Z0-9-]+)|)$/); if (match) { const lookupQuery = { username: match.groups?.user, @@ -2195,6 +2229,38 @@ export default class Misskey implements MegalodonInterface { })) } case 'statuses': { + if (q.startsWith("http://") || q.startsWith("https://")) { + return this.client + .post("/api/ap/show", { uri: q }) + .then(async (res) => { + if (res.status != 200 || res.data.type != "Note") { + res.status = 200; + res.statusText = "OK"; + res.data = { + accounts: [], + statuses: [], + hashtags: [], + }; + + return res; + } + + const post = await MisskeyAPI.Converter.note( + res.data.object as MisskeyAPI.Entity.Note, + this.baseUrl + ); + + return { + ...res, + data: { + accounts: [], + statuses: + options?.max_id && options.max_id >= post.id ? [] : [post], + hashtags: [], + }, + }; + }); + } let params = { query: q } diff --git a/packages/megalodon/src/misskey/api_client.ts b/packages/megalodon/src/misskey/api_client.ts index 6582cf3e77..c30886f903 100644 --- a/packages/megalodon/src/misskey/api_client.ts +++ b/packages/megalodon/src/misskey/api_client.ts @@ -93,11 +93,11 @@ namespace MisskeyAPI { following_count: u.followingCount ? u.followingCount : 0, statuses_count: u.notesCount ? u.notesCount : 0, note: u.description ? u.description : '', - url: acctUrl, - avatar: u.avatarUrl, - avatar_static: u.avatarUrl, - header: u.bannerUrl ? u.bannerUrl : '', - header_static: u.bannerUrl ? u.bannerUrl : '', + url: u.uri ?? acctUrl, + avatar: u.avatarUrl ? u.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png', + avatar_static: u.avatarUrl ? u.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png', + header: u.bannerUrl ? u.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png', + header_static: u.bannerUrl ? u.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png', emojis: mapEmojis(u.emojis), moved: null, fields: [], @@ -128,11 +128,11 @@ namespace MisskeyAPI { following_count: u.followingCount, statuses_count: u.notesCount, note: u.description ? u.description.replace(/\n|\\n/g, "
") : '', - url: acctUrl, - avatar: u.avatarUrl, - avatar_static: u.avatarUrl, - header: u.bannerUrl, - header_static: u.bannerUrl, + url: u.uri ?? acctUrl, + avatar: u.avatarUrl ? u.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png', + avatar_static: u.avatarUrl ? u.avatarUrl : 'https://dev.joinsharkey.org/static-assets/avatar.png', + header: u.bannerUrl ? u.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png', + header_static: u.bannerUrl ? u.bannerUrl : 'https://dev.joinsharkey.org/static-assets/transparent.png', emojis: mapEmojis(u.emojis), moved: null, fields: [], diff --git a/packages/megalodon/src/misskey/entities/user.ts b/packages/megalodon/src/misskey/entities/user.ts index 782e04d795..e5e5592701 100644 --- a/packages/megalodon/src/misskey/entities/user.ts +++ b/packages/megalodon/src/misskey/entities/user.ts @@ -12,6 +12,7 @@ namespace MisskeyEntity { notesCount?: number host: string | null avatarUrl: string + uri?: string bannerUrl?: string | null avatarColor: string emojis: Array | { [key: string]: string } diff --git a/packages/megalodon/src/misskey/entities/userDetail.ts b/packages/megalodon/src/misskey/entities/userDetail.ts index 607d9a511e..bf0e3c2c29 100644 --- a/packages/megalodon/src/misskey/entities/userDetail.ts +++ b/packages/megalodon/src/misskey/entities/userDetail.ts @@ -16,6 +16,7 @@ namespace MisskeyEntity { emojis: Array | { [key: string]: string } createdAt: string bannerUrl: string + uri: string bannerColor: string isLocked: boolean isSilenced: boolean