append mandatory CW in Update(Note) activities

This commit is contained in:
Hazelnoot 2025-02-12 15:11:19 -05:00
parent 583f55bc5a
commit c54b6bf55d
3 changed files with 97 additions and 53 deletions

View file

@ -224,13 +224,7 @@ export class NoteEditService implements OnApplicationShutdown {
} }
@bindThis @bindThis
public async edit(user: { public async edit(user: MiUser, editid: MiNote['id'], data: Option, silent = false): Promise<MiNote> {
id: MiUser['id'];
username: MiUser['username'];
host: MiUser['host'];
isBot: MiUser['isBot'];
noindex: MiUser['noindex'];
}, editid: MiNote['id'], data: Option, silent = false): Promise<MiNote> {
if (!editid) { if (!editid) {
throw new Error('fail'); throw new Error('fail');
} }
@ -584,13 +578,7 @@ export class NoteEditService implements OnApplicationShutdown {
} }
@bindThis @bindThis
private async postNoteEdited(note: MiNote, oldNote: MiNote, user: { private async postNoteEdited(note: MiNote, oldNote: MiNote, user: MiUser, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
id: MiUser['id'];
username: MiUser['username'];
host: MiUser['host'];
isBot: MiUser['isBot'];
noindex: MiUser['noindex'];
}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
// Register host // Register host
if (this.meta.enableStatsForFederatedInstances) { if (this.meta.enableStatsForFederatedInstances) {
if (this.userEntityService.isRemoteUser(user)) { if (this.userEntityService.isRemoteUser(user)) {
@ -703,7 +691,7 @@ export class NoteEditService implements OnApplicationShutdown {
//#region AP deliver //#region AP deliver
if (!data.localOnly && this.userEntityService.isLocalUser(user)) { if (!data.localOnly && this.userEntityService.isLocalUser(user)) {
(async () => { (async () => {
const noteActivity = await this.renderNoteOrRenoteActivity(data, note); const noteActivity = await this.renderNoteOrRenoteActivity(data, note, user);
const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity); const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity);
// メンションされたリモートユーザーに配送 // メンションされたリモートユーザーに配送
@ -834,14 +822,12 @@ export class NoteEditService implements OnApplicationShutdown {
} }
@bindThis @bindThis
private async renderNoteOrRenoteActivity(data: Option, note: MiNote) { private async renderNoteOrRenoteActivity(data: Option, note: MiNote, user: MiUser) {
if (data.localOnly) return null; if (data.localOnly) return null;
const user = await this.usersRepository.findOneBy({ id: note.userId });
if (user == null) throw new Error('user not found');
const content = this.isRenote(data) && !this.isQuote(data) const content = this.isRenote(data) && !this.isQuote(data)
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note) ? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
: this.apRendererService.renderUpdate(await this.apRendererService.renderUpNote(note, false), user); : this.apRendererService.renderUpdate(await this.apRendererService.renderUpNote(note, user, false), user);
return this.apRendererService.addContext(content); return this.apRendererService.addContext(content);
} }

View file

@ -642,7 +642,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public async renderUpNote(note: MiNote, dive = true): Promise<IPost> { public async renderUpNote(note: MiNote, author: MiUser, dive = true): Promise<IPost> {
const getPromisedFiles = async (ids: string[]): Promise<MiDriveFile[]> => { const getPromisedFiles = async (ids: string[]): Promise<MiDriveFile[]> => {
if (ids.length === 0) return []; if (ids.length === 0) return [];
const items = await this.driveFilesRepository.findBy({ id: In(ids) }); const items = await this.driveFilesRepository.findBy({ id: In(ids) });
@ -656,14 +656,14 @@ export class ApRendererService {
inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId }); inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
if (inReplyToNote != null) { 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) { if (inReplyToNote.uri) {
inReplyTo = inReplyToNote.uri; inReplyTo = inReplyToNote.uri;
} else { } else {
if (dive) { if (dive) {
inReplyTo = await this.renderUpNote(inReplyToNote, false); inReplyTo = await this.renderUpNote(inReplyToNote, inReplyToUser, false);
} else { } else {
inReplyTo = `${this.config.url}/notes/${inReplyToNote.id}`; inReplyTo = `${this.config.url}/notes/${inReplyToNote.id}`;
} }
@ -726,7 +726,12 @@ export class ApRendererService {
apAppend += `\n\nRE: ${quote}`; 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); const { content } = this.apMfmService.getNoteHtml(note, apAppend);

View file

@ -483,38 +483,38 @@ describe('ActivityPub', () => {
}); });
describe(ApRendererService, () => { describe(ApRendererService, () => {
describe('renderNote', () => { let note: MiNote;
let note: MiNote; let author: MiUser;
let author: MiUser;
beforeEach(() => { beforeEach(() => {
author = new MiUser({ author = new MiUser({
id: idService.gen(), 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,
});
}); });
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('renderNote', () => {
describe('summary', () => { describe('summary', () => {
// I actually don't know why it does this, but the logic was already there so I've preserved it. // 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 () => { it('should be special character when CW is empty string', async () => {
@ -566,5 +566,58 @@ describe('ActivityPub', () => {
}); });
}); });
}); });
describe('renderUpnote', () => {
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.renderUpNote(note, author, false);
expect(result.summary).toBe(String.fromCharCode(0x200B));
});
it('should be undefined when CW is null', async () => {
const result = await rendererService.renderUpNote(note, author, false);
expect(result.summary).toBeUndefined();
});
it('should be CW when present without mandatoryCW', async () => {
note.cw = 'original';
const result = await rendererService.renderUpNote(note, author, false);
expect(result.summary).toBe('original');
});
it('should be mandatoryCW when present without CW', async () => {
author.mandatoryCW = 'mandatory';
const result = await rendererService.renderUpNote(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.renderUpNote(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.renderUpNote(note, author, false);
expect(result.summary).toBe('original and mandatory');
});
});
});
}); });
}); });