mirror of
https://github.com/paricafe/misskey.git
synced 2025-01-31 09:10:16 -06:00
feat(wip): ap update note
This commit is contained in:
parent
87ecfe55c8
commit
8576085906
5 changed files with 210 additions and 19 deletions
146
packages/backend/src/core/NoteUpdateService.ts
Normal file
146
packages/backend/src/core/NoteUpdateService.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)}`;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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を解決します。
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue