feat(wip): ap update note

This commit is contained in:
Nya Candy 2024-01-26 09:28:05 +08:00 committed by fly_mc
parent 87ecfe55c8
commit 8576085906
5 changed files with 210 additions and 19 deletions

View file

@ -0,0 +1,146 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Brackets, In } from 'typeorm';
import { Injectable, Inject } from '@nestjs/common';
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
import type { MiNote, IMentionedRemoteUsers } from '@/models/Note.js';
import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/_.js';
import { RelayService } from '@/core/RelayService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import NotesChart from '@/core/chart/charts/notes.js';
import PerUserNotesChart from '@/core/chart/charts/per-user-notes.js';
import InstanceChart from '@/core/chart/charts/instance.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { isPureRenote } from '@/misc/is-pure-renote.js';
import { IdService } from '@/core/IdService.js';
@Injectable()
export class NoteUpdateService {
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.instancesRepository)
private instancesRepository: InstancesRepository,
private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
private globalEventService: GlobalEventService,
private relayService: RelayService,
private federatedInstanceService: FederatedInstanceService,
private apRendererService: ApRendererService,
private apDeliverManagerService: ApDeliverManagerService,
private metaService: MetaService,
private searchService: SearchService,
private moderationLogService: ModerationLogService,
private notesChart: NotesChart,
private perUserNotesChart: PerUserNotesChart,
private instanceChart: InstanceChart,
private idService: IdService,
) {}
/**
* Update note
* @param user Note creator
* @param note Note to update
* @param ps New note info
*/
async update(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, ps: Pick<MiNote, 'text' | 'cw'>, quiet = false, updater?: MiUser) {
const newNote = {
...note,
...ps, // Overwrite updated fields
};
if (!quiet) {
this.globalEventService.publishNoteStream(note.id, 'updated', {
cw: ps.cw,
text: ps.text ?? '', // prevent null
});
if (this.userEntityService.isLocalUser(user) && !note.localOnly) {
const content = this.apRendererService.renderUpdateNote(await this.apRendererService.renderNote(newNote, false), newNote);
this.deliverToConcerned(user, note, content);
}
}
this.searchService.indexNote(newNote);
await this.notesRepository.update({ id: note.id }, {
updatedAt: new Date(),
history: [...(note.history || []), {
createdAt: (note.updatedAt || this.idService.parse(note.id).date).toISOString(),
cw: note.cw,
text: note.text,
}],
cw: ps.cw,
text: ps.text,
});
// Currently not implemented
// if (updater && (note.userId !== updater.id)) {
// const user = await this.usersRepository.findOneByOrFail({ id: note.userId });
// this.moderationLogService.log(updater, 'updateNote', {
// noteId: note.id,
// noteUserId: note.userId,
// noteUserUsername: user.username,
// noteUserHost: user.host,
// note: note,
// });
// }
}
@bindThis
private async getMentionedRemoteUsers(note: MiNote) {
const where = [] as any[];
// mention / reply / dm
const uris = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri);
if (uris.length > 0) {
where.push(
{ uri: In(uris) },
);
}
// renote / quote
if (note.renoteUserId) {
where.push({
id: note.renoteUserId,
});
}
if (where.length === 0) return [];
return await this.usersRepository.find({
where,
}) as MiRemoteUser[];
}
@bindThis
private async deliverToConcerned(user: { id: MiLocalUser['id']; host: null; }, note: MiNote, content: any) {
this.apDeliverManagerService.deliverToFollowers(user, content);
this.relayService.deliverToRelays(user, content);
const remoteUsers = await this.getMentionedRemoteUsers(note);
for (const remoteUser of remoteUsers) {
this.apDeliverManagerService.deliverToUser(user, content, remoteUser);
}
}
}

View file

@ -770,6 +770,9 @@ export class ApInboxService {
} else if (getApType(object) === 'Question') {
await this.apQuestionService.updateQuestion(object, resolver).catch(err => console.error(err));
return 'ok: Question updated';
} else if (isPost(object)) {
await this.apNoteService.updateNote(object, resolver);
return 'ok: Post updated';
} else {
return `skip: Unknown type: ${getApType(object)}`;
}

View file

@ -593,6 +593,18 @@ export class ApRendererService {
};
}
@bindThis
public renderUpdateNote(object: string | IPost, note: { id: MiNote['id'], userId: MiNote['userId'] }): IUpdate {
return {
id: `${this.config.url}/notes/${note.id}#updates/${new Date().getTime()}`,
actor: this.userEntityService.genLocalUserUri(note.userId),
type: 'Update',
to: ['https://www.w3.org/ns/activitystreams#Public'],
object,
published: new Date().toISOString(),
};
}
@bindThis
public renderVote(user: { id: MiUser['id'] }, vote: MiPollVote, note: MiNote, poll: MiPoll, pollOwner: MiRemoteUser): ICreate {
return {

View file

@ -6,7 +6,7 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { PollsRepository, EmojisRepository } from '@/models/_.js';
import type { PollsRepository, EmojisRepository, NotesRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import type { MiRemoteUser } from '@/models/User.js';
import type { MiNote } from '@/models/Note.js';
@ -16,6 +16,7 @@ import { MetaService } from '@/core/MetaService.js';
import { AppLockService } from '@/core/AppLockService.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import { NoteCreateService } from '@/core/NoteCreateService.js';
import { NoteUpdateService } from '@/core/NoteUpdateService.js';
import type Logger from '@/logger.js';
import { IdService } from '@/core/IdService.js';
import { PollService } from '@/core/PollService.js';
@ -52,6 +53,9 @@ export class ApNoteService {
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private idService: IdService,
private apMfmService: ApMfmService,
private apResolverService: ApResolverService,
@ -69,6 +73,7 @@ export class ApNoteService {
private appLockService: AppLockService,
private pollService: PollService,
private noteCreateService: NoteCreateService,
private noteUpdateService: NoteUpdateService,
private apDbResolverService: ApDbResolverService,
private apLoggerService: ApLoggerService,
) {
@ -325,6 +330,45 @@ export class ApNoteService {
}
}
@bindThis
public async updateNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<void> {
const uri = typeof value === 'string' ? value : value.id;
if (uri == null) throw new Error('uri is null');
// Is from local
if (uri.startsWith(`${this.config.url}/`)) throw new Error('uri points local');
const originNote = await this.notesRepository.findOneBy({ uri });
if (originNote == null) throw new Error('Note is not registered');
// Process new note
const note = value as IPost;
// Fetch note author
if (note.attributedTo == null) {
throw new Error('invalid note.attributedTo: ' + note.attributedTo);
}
const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as MiRemoteUser;
const cw = note.summary || null;
// Text parsing
let text: string | null = null;
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
text = note.source.content;
} else if (typeof note._misskey_content !== 'undefined') {
text = note._misskey_content;
} else if (typeof note.content === 'string') {
text = this.apMfmService.htmlToMfm(note.content, note.tag);
}
await this.noteUpdateService.update(actor, originNote, {
cw,
text,
}, silent);
}
/**
* Noteを解決します
*

View file

@ -13,6 +13,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { ApiError } from '../../error.js';
import { IdService } from "@/core/IdService.js";
import { NoteUpdateService } from '@/core/NoteUpdateService.js';
export const meta = {
tags: ['notes'],
@ -58,12 +59,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private getterService: GetterService,
private globalEventService: GlobalEventService,
private idService: IdService,
private noteUpdateService: NoteUpdateService,
) {
super(meta, paramDef, async (ps, me) => {
const note = await this.getterService.getNote(ps.noteId).catch(err => {
@ -75,21 +72,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchNote);
}
await this.notesRepository.update({ id: note.id }, {
updatedAt: new Date(),
history: [...(note.history || []), {
createdAt: (note.updatedAt || this.idService.parse(note.id).date).toISOString(),
cw: note.cw,
text: note.text,
}],
cw: ps.cw,
await this.noteUpdateService.update(await this.usersRepository.findOneByOrFail({ id: note.userId }), note, {
text: ps.text,
});
this.globalEventService.publishNoteStream(note.id, 'updated', {
cw: ps.cw,
text: ps.text,
});
}, false, me);
});
}
}