fix(backend): フィードのノートのMFMはHTMLにレンダーしてから返す (#14006)
* fix(backend): フィードのノートのMFMはHTMLにレンダーしてから返す (test wip) * chore: beforeEachを使う? * fix: プレーンテキストにフォールバックしてMFMが含まれていないか調べる方針を実装 * fix: application/jsonだとパースされるのでその作用をキャンセル * build: fix lint error * docs: update CHANGELOG.md --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
parent
ef205fb60e
commit
ac12ab8629
4 changed files with 26 additions and 3 deletions
|
@ -12,6 +12,7 @@
|
||||||
### Server
|
### Server
|
||||||
- チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
- チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
||||||
- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
|
- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
|
||||||
|
- Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006)
|
||||||
- Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036)
|
- Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036)
|
||||||
- Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059)
|
- Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059)
|
||||||
- Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正
|
- Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正
|
||||||
|
|
|
@ -14,6 +14,8 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { MfmService } from "@/core/MfmService.js";
|
||||||
|
import { parse as mfmParse } from 'mfm-js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FeedService {
|
export class FeedService {
|
||||||
|
@ -33,6 +35,7 @@ export class FeedService {
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private driveFileEntityService: DriveFileEntityService,
|
private driveFileEntityService: DriveFileEntityService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
|
private mfmService: MfmService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,13 +79,14 @@ export class FeedService {
|
||||||
id: In(note.fileIds),
|
id: In(note.fileIds),
|
||||||
}) : [];
|
}) : [];
|
||||||
const file = files.find(file => file.type.startsWith('image/'));
|
const file = files.find(file => file.type.startsWith('image/'));
|
||||||
|
const text = note.text;
|
||||||
|
|
||||||
feed.addItem({
|
feed.addItem({
|
||||||
title: `New note by ${author.name}`,
|
title: `New note by ${author.name}`,
|
||||||
link: `${this.config.url}/notes/${note.id}`,
|
link: `${this.config.url}/notes/${note.id}`,
|
||||||
date: this.idService.parse(note.id).date,
|
date: this.idService.parse(note.id).date,
|
||||||
description: note.cw ?? undefined,
|
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,
|
image: file ? this.driveFileEntityService.getPublicUrl(file) : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,6 +153,23 @@ describe('Webリソース', () => {
|
||||||
path: path('nonexisting'),
|
path: path('nonexisting'),
|
||||||
status: 404,
|
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 }) => {
|
describe.each([{ path: '/api/foo' }])('$path', ({ path }) => {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/val
|
||||||
import { entities } from '../src/postgres.js';
|
import { entities } from '../src/postgres.js';
|
||||||
import { loadConfig } from '../src/config.js';
|
import { loadConfig } from '../src/config.js';
|
||||||
import type * as misskey from 'misskey-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';
|
export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js';
|
||||||
|
|
||||||
|
@ -454,7 +455,7 @@ export type SimpleGetResponse = {
|
||||||
type: string | null,
|
type: string | null,
|
||||||
location: string | null
|
location: string | null
|
||||||
};
|
};
|
||||||
export const simpleGet = async (path: string, accept = '*/*', cookie: any = undefined): Promise<SimpleGetResponse> => {
|
export const simpleGet = async (path: string, accept = '*/*', cookie: any = undefined, bodyExtractor: (res: Response) => Promise<string | null> = _ => Promise.resolve(null)): Promise<SimpleGetResponse> => {
|
||||||
const res = await relativeFetch(path, {
|
const res = await relativeFetch(path, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: accept,
|
Accept: accept,
|
||||||
|
@ -482,7 +483,7 @@ export const simpleGet = async (path: string, accept = '*/*', cookie: any = unde
|
||||||
const body =
|
const body =
|
||||||
jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() :
|
jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() :
|
||||||
htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) :
|
htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) :
|
||||||
null;
|
await bodyExtractor(res);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: res.status,
|
status: res.status,
|
||||||
|
|
Loading…
Reference in a new issue