diff --git a/CHANGELOG.md b/CHANGELOG.md index c5eb698385..ab9f5f8000 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Server - チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正 - Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949) +- Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006) - Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036) - Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059) - Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正 diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index 10e3ed2682..9d810ddc84 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -14,6 +14,8 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; +import { MfmService } from "@/core/MfmService.js"; +import { parse as mfmParse } from 'mfm-js'; @Injectable() export class FeedService { @@ -33,6 +35,7 @@ export class FeedService { private userEntityService: UserEntityService, private driveFileEntityService: DriveFileEntityService, private idService: IdService, + private mfmService: MfmService, ) { } @@ -76,13 +79,14 @@ export class FeedService { id: In(note.fileIds), }) : []; const file = files.find(file => file.type.startsWith('image/')); + const text = note.text; feed.addItem({ title: `New note by ${author.name}`, link: `${this.config.url}/notes/${note.id}`, date: this.idService.parse(note.id).date, description: note.cw ?? undefined, - content: note.text ?? undefined, + content: text ? this.mfmService.toHtml(mfmParse(text), JSON.parse(note.mentionedRemoteUsers)) ?? undefined : undefined, image: file ? this.driveFileEntityService.getPublicUrl(file) : undefined, }); } diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts index 4851ed14be..7efd688ec2 100644 --- a/packages/backend/test/e2e/fetch-resource.ts +++ b/packages/backend/test/e2e/fetch-resource.ts @@ -153,6 +153,23 @@ describe('Webリソース', () => { path: path('nonexisting'), status: 404, })); + + describe(' has entry such ', () => { + beforeEach(() => { + post(alice, { text: "**a**" }) + }); + + test('MFMを含まない。', async () => { + const content = await simpleGet(path(alice.username), "*/*", undefined, res => res.text()); + const _body: unknown = content.body; + // JSONフィードのときは改めて文字列化する + const body: string = typeof (_body) === "object" ? JSON.stringify(_body) : _body as string; + + if (body.includes("**a**")) { + throw new Error("MFM shouldn't be included"); + } + }); + }) }); describe.each([{ path: '/api/foo' }])('$path', ({ path }) => { diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 86814fffe0..aad4ab37c9 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -17,6 +17,7 @@ import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/val import { entities } from '../src/postgres.js'; import { loadConfig } from '../src/config.js'; import type * as misskey from 'misskey-js'; +import { type Response } from 'node-fetch'; export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js'; @@ -454,7 +455,7 @@ export type SimpleGetResponse = { type: string | null, location: string | null }; -export const simpleGet = async (path: string, accept = '*/*', cookie: any = undefined): Promise => { +export const simpleGet = async (path: string, accept = '*/*', cookie: any = undefined, bodyExtractor: (res: Response) => Promise = _ => Promise.resolve(null)): Promise => { const res = await relativeFetch(path, { headers: { Accept: accept, @@ -482,7 +483,7 @@ export const simpleGet = async (path: string, accept = '*/*', cookie: any = unde const body = jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() : htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) : - null; + await bodyExtractor(res); return { status: res.status,