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
					
				|  | @ -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`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正 | ||||
|  |  | |||
|  | @ -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, | ||||
| 			}); | ||||
| 		} | ||||
|  |  | |||
|  | @ -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 }) => { | ||||
|  |  | |||
|  | @ -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<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, { | ||||
| 		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, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue