mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-11-01 05:54:12 +00:00 
			
		
		
		
	append default CW when rendering AP Note objects
				
					
				
			This commit is contained in:
		
							parent
							
								
									563e32316f
								
							
						
					
					
						commit
						6c2034a373
					
				
					 5 changed files with 117 additions and 15 deletions
				
			
		|  | @ -100,7 +100,7 @@ export class PollService { | |||
| 		if (user == null) throw new Error('note not found'); | ||||
| 
 | ||||
| 		if (this.userEntityService.isLocalUser(user)) { | ||||
| 			const content = this.apRendererService.addContext(this.apRendererService.renderUpdate(await this.apRendererService.renderNote(note, false), user)); | ||||
| 			const content = this.apRendererService.addContext(this.apRendererService.renderUpdate(await this.apRendererService.renderNote(note, user, false), user)); | ||||
| 			this.apDeliverManagerService.deliverToFollowers(user, content); | ||||
| 			this.relayService.deliverToRelays(user, content); | ||||
| 		} | ||||
|  |  | |||
|  | @ -28,6 +28,7 @@ import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFil | |||
| import { bindThis } from '@/decorators.js'; | ||||
| import { CustomEmojiService } from '@/core/CustomEmojiService.js'; | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| import { appendContentWarning } from '@/misc/append-content-warning.js'; | ||||
| import { JsonLdService } from './JsonLdService.js'; | ||||
| import { ApMfmService } from './ApMfmService.js'; | ||||
| import { CONTEXT } from './misc/contexts.js'; | ||||
|  | @ -339,7 +340,7 @@ export class ApRendererService { | |||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	public async renderNote(note: MiNote, dive = true): Promise<IPost> { | ||||
| 	public async renderNote(note: MiNote, author: MiUser, dive = true): Promise<IPost> { | ||||
| 		const getPromisedFiles = async (ids: string[]): Promise<MiDriveFile[]> => { | ||||
| 			if (ids.length === 0) return []; | ||||
| 			const items = await this.driveFilesRepository.findBy({ id: In(ids) }); | ||||
|  | @ -353,14 +354,14 @@ export class ApRendererService { | |||
| 			inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId }); | ||||
| 
 | ||||
| 			if (inReplyToNote != null) { | ||||
| 				const inReplyToUserExist = await this.usersRepository.exists({ where: { id: inReplyToNote.userId } }); | ||||
| 				const inReplyToUser = await this.usersRepository.findOneBy({ id: inReplyToNote.userId }); | ||||
| 
 | ||||
| 				if (inReplyToUserExist) { | ||||
| 				if (inReplyToUser) { | ||||
| 					if (inReplyToNote.uri) { | ||||
| 						inReplyTo = inReplyToNote.uri; | ||||
| 					} else { | ||||
| 						if (dive) { | ||||
| 							inReplyTo = await this.renderNote(inReplyToNote, false); | ||||
| 							inReplyTo = await this.renderNote(inReplyToNote, inReplyToUser, false); | ||||
| 						} else { | ||||
| 							inReplyTo = `${this.config.url}/notes/${inReplyToNote.id}`; | ||||
| 						} | ||||
|  | @ -423,7 +424,12 @@ export class ApRendererService { | |||
| 			apAppend += `\n\nRE: ${quote}`; | ||||
| 		} | ||||
| 
 | ||||
| 		const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; | ||||
| 		let summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; | ||||
| 
 | ||||
| 		// Apply mandatory CW, if applicable
 | ||||
| 		if (author.mandatoryCW) { | ||||
| 			summary = appendContentWarning(summary, author.mandatoryCW); | ||||
| 		} | ||||
| 
 | ||||
| 		const { content } = this.apMfmService.getNoteHtml(note, apAppend); | ||||
| 
 | ||||
|  |  | |||
|  | @ -156,11 +156,12 @@ export class Resolver { | |||
| 			case 'notes': | ||||
| 				return this.notesRepository.findOneByOrFail({ id: parsed.id }) | ||||
| 					.then(async note => { | ||||
| 						const author = await this.usersRepository.findOneByOrFail({ id: note.userId }); | ||||
| 						if (parsed.rest === 'activity') { | ||||
| 							// this refers to the create activity and not the note itself
 | ||||
| 							return this.apRendererService.addContext(this.apRendererService.renderCreate(await this.apRendererService.renderNote(note), note)); | ||||
| 							return this.apRendererService.addContext(this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, author), note)); | ||||
| 						} else { | ||||
| 							return this.apRendererService.renderNote(note); | ||||
| 							return this.apRendererService.renderNote(note, author); | ||||
| 						} | ||||
| 					}); | ||||
| 			case 'users': | ||||
|  |  | |||
|  | @ -103,15 +103,16 @@ export class ActivityPubServerService { | |||
| 	/** | ||||
| 	 * Pack Create<Note> or Announce Activity | ||||
| 	 * @param note Note | ||||
| 	 * @param author Author of the note | ||||
| 	 */ | ||||
| 	@bindThis | ||||
| 	private async packActivity(note: MiNote): Promise<any> { | ||||
| 	private async packActivity(note: MiNote, author: MiUser): Promise<any> { | ||||
| 		if (isRenote(note) && !isQuote(note)) { | ||||
| 			const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId }); | ||||
| 			return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note); | ||||
| 		} | ||||
| 
 | ||||
| 		return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note); | ||||
| 		return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, author, false), note); | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
|  | @ -506,7 +507,7 @@ export class ActivityPubServerService { | |||
| 			this.notesRepository.findOneByOrFail({ id: pining.noteId })))) | ||||
| 			.filter(note => !note.localOnly && ['public', 'home'].includes(note.visibility)); | ||||
| 
 | ||||
| 		const renderedNotes = await Promise.all(pinnedNotes.map(note => this.apRendererService.renderNote(note))); | ||||
| 		const renderedNotes = await Promise.all(pinnedNotes.map(note => this.apRendererService.renderNote(note, user))); | ||||
| 
 | ||||
| 		const rendered = this.apRendererService.renderOrderedCollection( | ||||
| 			`${this.config.url}/users/${userId}/collections/featured`, | ||||
|  | @ -579,7 +580,7 @@ export class ActivityPubServerService { | |||
| 
 | ||||
| 			if (sinceId) notes.reverse(); | ||||
| 
 | ||||
| 			const activities = await Promise.all(notes.map(note => this.packActivity(note))); | ||||
| 			const activities = await Promise.all(notes.map(note => this.packActivity(note, user))); | ||||
| 			const rendered = this.apRendererService.renderOrderedCollectionPage( | ||||
| 				`${partOf}?${url.query({ | ||||
| 					page: 'true', | ||||
|  | @ -723,7 +724,9 @@ export class ActivityPubServerService { | |||
| 
 | ||||
| 			if (!this.config.checkActivityPubGetSignature) reply.header('Cache-Control', 'public, max-age=180'); | ||||
| 			this.setResponseType(request, reply); | ||||
| 			return this.apRendererService.addContext(await this.apRendererService.renderNote(note, false)); | ||||
| 
 | ||||
| 			const author = await this.usersRepository.findOneByOrFail({ id: note.userId }); | ||||
| 			return this.apRendererService.addContext(await this.apRendererService.renderNote(note, author, false)); | ||||
| 		}); | ||||
| 
 | ||||
| 		// note activity
 | ||||
|  | @ -746,7 +749,9 @@ export class ActivityPubServerService { | |||
| 
 | ||||
| 			if (!this.config.checkActivityPubGetSignature) reply.header('Cache-Control', 'public, max-age=180'); | ||||
| 			this.setResponseType(request, reply); | ||||
| 			return (this.apRendererService.addContext(await this.packActivity(note))); | ||||
| 
 | ||||
| 			const author = await this.usersRepository.findOneByOrFail({ id: note.userId }); | ||||
| 			return (this.apRendererService.addContext(await this.packActivity(note, author))); | ||||
| 		}); | ||||
| 
 | ||||
| 		// outbox
 | ||||
|  |  | |||
|  | @ -3,6 +3,8 @@ | |||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| import { IdService } from '@/core/IdService.js'; | ||||
| 
 | ||||
| process.env.NODE_ENV = 'test'; | ||||
| 
 | ||||
| import * as assert from 'assert'; | ||||
|  | @ -20,7 +22,7 @@ import { CoreModule } from '@/core/CoreModule.js'; | |||
| import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; | ||||
| import { LoggerService } from '@/core/LoggerService.js'; | ||||
| import type { IActor, IApDocument, ICollection, IObject, IPost } from '@/core/activitypub/type.js'; | ||||
| import { MiMeta, MiNote, UserProfilesRepository } from '@/models/_.js'; | ||||
| import { MiMeta, MiNote, MiUser, UserProfilesRepository } from '@/models/_.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { secureRndstr } from '@/misc/secure-rndstr.js'; | ||||
| import { DownloadService } from '@/core/DownloadService.js'; | ||||
|  | @ -93,6 +95,7 @@ describe('ActivityPub', () => { | |||
| 	let rendererService: ApRendererService; | ||||
| 	let jsonLdService: JsonLdService; | ||||
| 	let resolver: MockResolver; | ||||
| 	let idService: IdService; | ||||
| 
 | ||||
| 	const metaInitial = { | ||||
| 		cacheRemoteFiles: true, | ||||
|  | @ -140,6 +143,7 @@ describe('ActivityPub', () => { | |||
| 		imageService = app.get<ApImageService>(ApImageService); | ||||
| 		jsonLdService = app.get<JsonLdService>(JsonLdService); | ||||
| 		resolver = new MockResolver(await app.resolve<LoggerService>(LoggerService)); | ||||
| 		idService = app.get<IdService>(IdService); | ||||
| 
 | ||||
| 		// Prevent ApPersonService from fetching instance, as it causes Jest import-after-test error
 | ||||
| 		const federatedInstanceService = app.get<FederatedInstanceService>(FederatedInstanceService); | ||||
|  | @ -477,4 +481,90 @@ describe('ActivityPub', () => { | |||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| 	describe(ApRendererService, () => { | ||||
| 		describe('renderNote', () => { | ||||
| 			let note: MiNote; | ||||
| 			let author: MiUser; | ||||
| 
 | ||||
| 			beforeEach(() => { | ||||
| 				author = new MiUser({ | ||||
| 					id: idService.gen(), | ||||
| 				}); | ||||
| 				note = new MiNote({ | ||||
| 					id: idService.gen(), | ||||
| 					userId: author.id, | ||||
| 					visibility: 'public', | ||||
| 					localOnly: false, | ||||
| 					text: 'Note text', | ||||
| 					cw: null, | ||||
| 					renoteCount: 0, | ||||
| 					repliesCount: 0, | ||||
| 					clippedCount: 0, | ||||
| 					reactions: {}, | ||||
| 					fileIds: [], | ||||
| 					attachedFileTypes: [], | ||||
| 					visibleUserIds: [], | ||||
| 					mentions: [], | ||||
| 					// This is fucked tbh - it's JSON stored in a TEXT column that gets parsed/serialized all over the place
 | ||||
| 					mentionedRemoteUsers: '[]', | ||||
| 					reactionAndUserPairCache: [], | ||||
| 					emojis: [], | ||||
| 					tags: [], | ||||
| 					hasPoll: false, | ||||
| 				}); | ||||
| 			}); | ||||
| 
 | ||||
| 			describe('summary', () => { | ||||
| 				// I actually don't know why it does this, but the logic was already there so I've preserved it.
 | ||||
| 				it('should be special character when CW is empty string', async () => { | ||||
| 					note.cw = ''; | ||||
| 
 | ||||
| 					const result = await rendererService.renderNote(note, author, false); | ||||
| 
 | ||||
| 					expect(result.summary).toBe(String.fromCharCode(0x200B)); | ||||
| 				}); | ||||
| 
 | ||||
| 				it('should be undefined when CW is null', async () => { | ||||
| 					const result = await rendererService.renderNote(note, author, false); | ||||
| 
 | ||||
| 					expect(result.summary).toBeUndefined(); | ||||
| 				}); | ||||
| 
 | ||||
| 				it('should be CW when present without mandatoryCW', async () => { | ||||
| 					note.cw = 'original'; | ||||
| 
 | ||||
| 					const result = await rendererService.renderNote(note, author, false); | ||||
| 
 | ||||
| 					expect(result.summary).toBe('original'); | ||||
| 				}); | ||||
| 
 | ||||
| 				it('should be mandatoryCW when present without CW', async () => { | ||||
| 					author.mandatoryCW = 'mandatory'; | ||||
| 
 | ||||
| 					const result = await rendererService.renderNote(note, author, false); | ||||
| 
 | ||||
| 					expect(result.summary).toBe('mandatory'); | ||||
| 				}); | ||||
| 
 | ||||
| 				it('should be merged when CW and mandatoryCW are both present', async () => { | ||||
| 					note.cw = 'original'; | ||||
| 					author.mandatoryCW = 'mandatory'; | ||||
| 
 | ||||
| 					const result = await rendererService.renderNote(note, author, false); | ||||
| 
 | ||||
| 					expect(result.summary).toBe('original, mandatory'); | ||||
| 				}); | ||||
| 
 | ||||
| 				it('should be CW when CW includes mandatoryCW', async () => { | ||||
| 					note.cw = 'original and mandatory'; | ||||
| 					author.mandatoryCW = 'mandatory'; | ||||
| 
 | ||||
| 					const result = await rendererService.renderNote(note, author, false); | ||||
| 
 | ||||
| 					expect(result.summary).toBe('original and mandatory'); | ||||
| 				}); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}); | ||||
| }); | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue