diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index eae7645321..c6f4696c08 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, IsNull } from 'typeorm'; import { Feed } from 'feed'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, NotesRepository, UserProfilesRepository } from '@/models/_.js'; +import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import type { MiUser } from '@/models/User.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -16,13 +16,23 @@ import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import { MfmService } from "@/core/MfmService.js"; import { parse as mfmParse } from 'mfm-js'; +import { MiNote } from '@/models/Note.js'; +import { isQuote, isRenote } from '@/misc/is-renote.js'; +import { getNoteSummary } from '@/misc/get-note-summary.js'; +import Logger from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; @Injectable() export class FeedService { + private readonly logger: Logger; + constructor( @Inject(DI.config) private config: Config, + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, @@ -36,13 +46,17 @@ export class FeedService { private driveFileEntityService: DriveFileEntityService, private idService: IdService, private mfmService: MfmService, + + loggerService: LoggerService, ) { + this.logger = loggerService.getLogger('feed'); } @bindThis public async packFeed(user: MiUser) { const author = { link: `${this.config.url}/@${user.username}`, + email: `${user.username}@${this.config.host}`, name: user.name ?? user.username, }; @@ -51,7 +65,6 @@ export class FeedService { const notes = await this.notesRepository.find({ where: { userId: user.id, - renoteId: IsNull(), visibility: In(['public', 'home']), }, order: { id: -1 }, @@ -75,22 +88,111 @@ export class FeedService { }); for (const note of notes) { - const files = note.fileIds.length > 0 ? await this.driveFilesRepository.findBy({ - id: In(note.fileIds), - }) : []; - const file = files.find(file => file.type.startsWith('image/')); - const text = note.text; + let contentStr = await this.noteToString(note, true); + let next = note.renoteId ? note.renoteId : note.replyId; + let depth = 10; + const noteintitle = true; + let title = `Post by ${author.name}`; + while (depth > 0 && next) { + const finding = await this.findById(next); + contentStr += finding.text; + next = finding.next; + depth -= 1; + } + + if (noteintitle) { + if (note.renoteId) { + title = `Boost by ${author.name}`; + } else if (note.replyId) { + title = `Reply by ${author.name}`; + } else { + title = `Post by ${author.name}`; + } + const effectiveNote = + !isQuote(note) && note.renote != null ? note.renote : note; + const content = getNoteSummary(effectiveNote); + if (content) { + title += `: ${content}`; + } + } feed.addItem({ - title: `New note by ${author.name}`, + title: this.escapeCDATA(title).substring(0, 100), link: `${this.config.url}/notes/${note.id}`, date: this.idService.parse(note.id).date, - description: note.cw ?? undefined, - content: text ? this.mfmService.toHtml(mfmParse(text), JSON.parse(note.mentionedRemoteUsers)) ?? undefined : undefined, - image: file ? this.driveFileEntityService.getPublicUrl(file) : undefined, + description: this.escapeCDATA(note.cw) ?? undefined, + content: this.escapeCDATA(contentStr) || undefined, }); } return feed; } + + private escapeCDATA(str: string) { + return str?.replaceAll("]]>", "]]]]>").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ""); + } + + private async noteToString(note: MiNote, isTheNote = false) { + const author = isTheNote + ? null + : await this.usersRepository.findOneByOrFail({ id: note.userId }); + let outstr = author + ? `${author.name}(@${author.username}@${ + author.host ? author.host : this.config.host + }) ${ + note.renoteId ? "renotes" : note.replyId ? "replies" : "says" + }:
` + : ""; + const files = note.fileIds?.length ? await this.driveFilesRepository.findBy({ + id: In(note.fileIds), + }) : []; + let fileEle = ""; + for (const file of files) { + if (file.type.startsWith("image/")) { + fileEle += `
`; + } else if (file.type.startsWith("audio/")) { + fileEle += `