mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-04-29 18:16:58 +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 (user == null) throw new Error('note not found');
|
||||||
|
|
||||||
if (this.userEntityService.isLocalUser(user)) {
|
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.apDeliverManagerService.deliverToFollowers(user, content);
|
||||||
this.relayService.deliverToRelays(user, content);
|
this.relayService.deliverToRelays(user, content);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFil
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { appendContentWarning } from '@/misc/append-content-warning.js';
|
||||||
import { JsonLdService } from './JsonLdService.js';
|
import { JsonLdService } from './JsonLdService.js';
|
||||||
import { ApMfmService } from './ApMfmService.js';
|
import { ApMfmService } from './ApMfmService.js';
|
||||||
import { CONTEXT } from './misc/contexts.js';
|
import { CONTEXT } from './misc/contexts.js';
|
||||||
|
@ -339,7 +340,7 @@ export class ApRendererService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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[]> => {
|
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) });
|
||||||
|
@ -353,14 +354,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.renderNote(inReplyToNote, false);
|
inReplyTo = await this.renderNote(inReplyToNote, inReplyToUser, false);
|
||||||
} else {
|
} else {
|
||||||
inReplyTo = `${this.config.url}/notes/${inReplyToNote.id}`;
|
inReplyTo = `${this.config.url}/notes/${inReplyToNote.id}`;
|
||||||
}
|
}
|
||||||
|
@ -423,7 +424,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);
|
||||||
|
|
||||||
|
|
|
@ -156,11 +156,12 @@ export class Resolver {
|
||||||
case 'notes':
|
case 'notes':
|
||||||
return this.notesRepository.findOneByOrFail({ id: parsed.id })
|
return this.notesRepository.findOneByOrFail({ id: parsed.id })
|
||||||
.then(async note => {
|
.then(async note => {
|
||||||
|
const author = await this.usersRepository.findOneByOrFail({ id: note.userId });
|
||||||
if (parsed.rest === 'activity') {
|
if (parsed.rest === 'activity') {
|
||||||
// this refers to the create activity and not the note itself
|
// 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 {
|
} else {
|
||||||
return this.apRendererService.renderNote(note);
|
return this.apRendererService.renderNote(note, author);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
case 'users':
|
case 'users':
|
||||||
|
|
|
@ -103,15 +103,16 @@ export class ActivityPubServerService {
|
||||||
/**
|
/**
|
||||||
* Pack Create<Note> or Announce Activity
|
* Pack Create<Note> or Announce Activity
|
||||||
* @param note Note
|
* @param note Note
|
||||||
|
* @param author Author of the note
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private async packActivity(note: MiNote): Promise<any> {
|
private async packActivity(note: MiNote, author: MiUser): Promise<any> {
|
||||||
if (isRenote(note) && !isQuote(note)) {
|
if (isRenote(note) && !isQuote(note)) {
|
||||||
const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId });
|
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.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
|
@bindThis
|
||||||
|
@ -506,7 +507,7 @@ export class ActivityPubServerService {
|
||||||
this.notesRepository.findOneByOrFail({ id: pining.noteId }))))
|
this.notesRepository.findOneByOrFail({ id: pining.noteId }))))
|
||||||
.filter(note => !note.localOnly && ['public', 'home'].includes(note.visibility));
|
.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(
|
const rendered = this.apRendererService.renderOrderedCollection(
|
||||||
`${this.config.url}/users/${userId}/collections/featured`,
|
`${this.config.url}/users/${userId}/collections/featured`,
|
||||||
|
@ -579,7 +580,7 @@ export class ActivityPubServerService {
|
||||||
|
|
||||||
if (sinceId) notes.reverse();
|
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(
|
const rendered = this.apRendererService.renderOrderedCollectionPage(
|
||||||
`${partOf}?${url.query({
|
`${partOf}?${url.query({
|
||||||
page: 'true',
|
page: 'true',
|
||||||
|
@ -723,7 +724,9 @@ export class ActivityPubServerService {
|
||||||
|
|
||||||
if (!this.config.checkActivityPubGetSignature) reply.header('Cache-Control', 'public, max-age=180');
|
if (!this.config.checkActivityPubGetSignature) reply.header('Cache-Control', 'public, max-age=180');
|
||||||
this.setResponseType(request, reply);
|
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
|
// note activity
|
||||||
|
@ -746,7 +749,9 @@ export class ActivityPubServerService {
|
||||||
|
|
||||||
if (!this.config.checkActivityPubGetSignature) reply.header('Cache-Control', 'public, max-age=180');
|
if (!this.config.checkActivityPubGetSignature) reply.header('Cache-Control', 'public, max-age=180');
|
||||||
this.setResponseType(request, reply);
|
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
|
// outbox
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
|
@ -20,7 +22,7 @@ import { CoreModule } from '@/core/CoreModule.js';
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import type { IActor, IApDocument, ICollection, IObject, IPost } from '@/core/activitypub/type.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 { DI } from '@/di-symbols.js';
|
||||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
import { DownloadService } from '@/core/DownloadService.js';
|
import { DownloadService } from '@/core/DownloadService.js';
|
||||||
|
@ -93,6 +95,7 @@ describe('ActivityPub', () => {
|
||||||
let rendererService: ApRendererService;
|
let rendererService: ApRendererService;
|
||||||
let jsonLdService: JsonLdService;
|
let jsonLdService: JsonLdService;
|
||||||
let resolver: MockResolver;
|
let resolver: MockResolver;
|
||||||
|
let idService: IdService;
|
||||||
|
|
||||||
const metaInitial = {
|
const metaInitial = {
|
||||||
cacheRemoteFiles: true,
|
cacheRemoteFiles: true,
|
||||||
|
@ -140,6 +143,7 @@ describe('ActivityPub', () => {
|
||||||
imageService = app.get<ApImageService>(ApImageService);
|
imageService = app.get<ApImageService>(ApImageService);
|
||||||
jsonLdService = app.get<JsonLdService>(JsonLdService);
|
jsonLdService = app.get<JsonLdService>(JsonLdService);
|
||||||
resolver = new MockResolver(await app.resolve<LoggerService>(LoggerService));
|
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
|
// Prevent ApPersonService from fetching instance, as it causes Jest import-after-test error
|
||||||
const federatedInstanceService = app.get<FederatedInstanceService>(FederatedInstanceService);
|
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