Merge branch 'develop' into feature/2024.10
This commit is contained in:
commit
6c13dc04f2
26 changed files with 849 additions and 366 deletions
|
|
@ -32,7 +32,7 @@ import { AbuseReportService } from '@/core/AbuseReportService.js';
|
|||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
import { fromTuple } from '@/misc/from-tuple.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
|
||||
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isApObject, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
|
||||
import { ApNoteService } from './models/ApNoteService.js';
|
||||
import { ApLoggerService } from './ApLoggerService.js';
|
||||
import { ApDbResolverService } from './ApDbResolverService.js';
|
||||
|
|
@ -166,7 +166,7 @@ export class ApInboxService {
|
|||
} else if (isAnnounce(activity)) {
|
||||
return await this.announce(actor, activity, resolver);
|
||||
} else if (isLike(activity)) {
|
||||
return await this.like(actor, activity);
|
||||
return await this.like(actor, activity, resolver);
|
||||
} else if (isUndo(activity)) {
|
||||
return await this.undo(actor, activity, resolver);
|
||||
} else if (isBlock(activity)) {
|
||||
|
|
@ -198,10 +198,13 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async like(actor: MiRemoteUser, activity: ILike): Promise<string> {
|
||||
private async like(actor: MiRemoteUser, activity: ILike, resolver?: Resolver): Promise<string> {
|
||||
const targetUri = getApId(activity.object);
|
||||
|
||||
const note = await this.apNoteService.fetchNote(targetUri);
|
||||
const object = fromTuple(activity.object);
|
||||
if (!object) return 'skip: activity has no object property';
|
||||
|
||||
const note = await this.apNoteService.resolveNote(object, { resolver });
|
||||
if (!note) return `skip: target note not found ${targetUri}`;
|
||||
|
||||
await this.apNoteService.extractEmojis(activity.tag ?? [], actor.host).catch(() => null);
|
||||
|
|
@ -272,8 +275,12 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
if (activity.target === actor.featured) {
|
||||
const object = fromTuple(activity.object);
|
||||
const note = await this.apNoteService.resolveNote(object, { resolver });
|
||||
const activityObject = fromTuple(activity.object);
|
||||
if (isApObject(activityObject) && !isPost(activityObject)) {
|
||||
return `unsupported featured object type: ${getApType(activityObject)}`;
|
||||
}
|
||||
|
||||
const note = await this.apNoteService.resolveNote(activityObject, { resolver });
|
||||
if (note == null) return 'note not found';
|
||||
await this.notePiningService.addPinned(actor, note.id);
|
||||
return;
|
||||
|
|
@ -386,7 +393,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async create(actor: MiRemoteUser, activity: ICreate, resolver?: Resolver): Promise<string | void> {
|
||||
private async create(actor: MiRemoteUser, activity: ICreate | IUpdate, resolver?: Resolver): Promise<string | void> {
|
||||
const uri = getApId(activity);
|
||||
|
||||
this.logger.info(`Create: ${uri}`);
|
||||
|
|
@ -421,14 +428,14 @@ export class ApInboxService {
|
|||
});
|
||||
|
||||
if (isPost(object)) {
|
||||
await this.createNote(resolver, actor, object, false, activity);
|
||||
await this.createNote(resolver, actor, object, false);
|
||||
} else {
|
||||
return `Unknown type: ${getApType(object)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async createNote(resolver: Resolver, actor: MiRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
|
||||
private async createNote(resolver: Resolver, actor: MiRemoteUser, note: IObject, silent = false): Promise<string> {
|
||||
const uri = getApId(note);
|
||||
|
||||
if (typeof note === 'object') {
|
||||
|
|
@ -643,6 +650,10 @@ export class ApInboxService {
|
|||
|
||||
if (activity.target === actor.featured) {
|
||||
const activityObject = fromTuple(activity.object);
|
||||
if (isApObject(activityObject) && !isPost(activityObject)) {
|
||||
return `unsupported featured object type: ${getApType(activityObject)}`;
|
||||
}
|
||||
|
||||
const note = await this.apNoteService.resolveNote(activityObject, { resolver });
|
||||
if (note == null) return 'note not found';
|
||||
await this.notePiningService.removePinned(actor, note.id);
|
||||
|
|
@ -787,7 +798,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async update(actor: MiRemoteUser, activity: IUpdate, resolver?: Resolver): Promise<string> {
|
||||
private async update(actor: MiRemoteUser, activity: IUpdate, resolver?: Resolver): Promise<string | void> {
|
||||
if (actor.uri !== activity.actor) {
|
||||
return 'skip: invalid actor';
|
||||
}
|
||||
|
|
@ -806,9 +817,19 @@ export class ApInboxService {
|
|||
await this.apPersonService.updatePerson(actor.uri, resolver, object);
|
||||
return 'ok: Person updated';
|
||||
} else if (getApType(object) === 'Question') {
|
||||
// If we get an Update(Question) for a note that doesn't exist, then create it instead
|
||||
if (!await this.apNoteService.hasNote(object)) {
|
||||
return await this.create(actor, activity, resolver);
|
||||
}
|
||||
|
||||
await this.apQuestionService.updateQuestion(object, actor, resolver).catch(err => console.error(err));
|
||||
return 'ok: Question updated';
|
||||
} else if (isPost(object)) {
|
||||
// If we get an Update(Note) for a note that doesn't exist, then create it instead
|
||||
if (!await this.apNoteService.hasNote(object)) {
|
||||
return await this.create(actor, activity, resolver);
|
||||
}
|
||||
|
||||
await this.apNoteService.updateNote(object, actor, resolver).catch(err => console.error(err));
|
||||
return 'ok: Note updated';
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -142,6 +142,15 @@ export class ApNoteService {
|
|||
return await this.apDbResolverService.getNoteFromApId(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided object / ID exists in the local database.
|
||||
*/
|
||||
@bindThis
|
||||
public async hasNote(object: string | IObject | [string | IObject]): Promise<boolean> {
|
||||
const uri = getApId(object);
|
||||
return await this.notesRepository.existsBy({ uri });
|
||||
}
|
||||
|
||||
/**
|
||||
* Noteを作成します。
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -343,6 +343,7 @@ export interface IMove extends IActivity {
|
|||
target: IObject | string;
|
||||
}
|
||||
|
||||
export const isApObject = (object: string | IObject): object is IObject => typeof(object) === 'object';
|
||||
export const isCreate = (object: IObject): object is ICreate => getApType(object) === 'Create';
|
||||
export const isDelete = (object: IObject): object is IDelete => getApType(object) === 'Delete';
|
||||
export const isUpdate = (object: IObject): object is IUpdate => getApType(object) === 'Update';
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { bindThis } from '@/decorators.js';
|
|||
import { DebounceLoader } from '@/misc/loader.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||
import { isPackedPureRenote } from '@/misc/is-renote.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
import type { CacheService } from '../CacheService.js';
|
||||
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
||||
|
|
@ -180,10 +181,9 @@ export class NoteEntityService implements OnModuleInit {
|
|||
} else {
|
||||
// フォロワーかどうか
|
||||
// TODO: 当関数呼び出しごとにクエリが走るのは重そうだからなんとかする
|
||||
const appearNote = packedNote.renote ?? packedNote;
|
||||
const isFollowing = await this.followingsRepository.exists({
|
||||
where: {
|
||||
followeeId: appearNote.userId,
|
||||
followeeId: packedNote.userId,
|
||||
followerId: meId,
|
||||
},
|
||||
});
|
||||
|
|
@ -193,6 +193,14 @@ export class NoteEntityService implements OnModuleInit {
|
|||
}
|
||||
}
|
||||
|
||||
// If this is a pure renote (boost), then we should *also* check the boosted note's visibility.
|
||||
// Otherwise we can have empty notes on the timeline, which is not good.
|
||||
// Notes are packed in depth-first order, so we can safely grab the "isHidden" property to avoid duplicated checks.
|
||||
// This is pulled out to ensure that we check both the renote *and* the boosted note.
|
||||
if (packedNote.renote?.isHidden && isPackedPureRenote(packedNote)) {
|
||||
hide = true;
|
||||
}
|
||||
|
||||
if (!hide && meId && packedNote.userId !== meId) {
|
||||
const isBlocked = (await this.cacheService.userBlockedCache.fetch(meId)).has(packedNote.userId);
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@ type PackedQuote =
|
|||
fileIds: NonNullable<Packed<'Note'>['fileIds']>
|
||||
});
|
||||
|
||||
type PackedPureRenote = PackedRenote & {
|
||||
text: NonNullable<Packed<'Note'>['text']>;
|
||||
cw: NonNullable<Packed<'Note'>['cw']>;
|
||||
replyId: NonNullable<Packed<'Note'>['replyId']>;
|
||||
poll: NonNullable<Packed<'Note'>['poll']>;
|
||||
fileIds: NonNullable<Packed<'Note'>['fileIds']>;
|
||||
}
|
||||
|
||||
export function isRenotePacked(note: Packed<'Note'>): note is PackedRenote {
|
||||
return note.renoteId != null;
|
||||
}
|
||||
|
|
@ -82,3 +90,7 @@ export function isQuotePacked(note: PackedRenote): note is PackedQuote {
|
|||
note.poll != null ||
|
||||
(note.fileIds != null && note.fileIds.length > 0);
|
||||
}
|
||||
|
||||
export function isPackedPureRenote(note: Packed<'Note'>): note is PackedPureRenote {
|
||||
return isRenotePacked(note) && !isQuotePacked(note);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
|
||||
import { StatusError } from '@/misc/status-error.js';
|
||||
import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
|
||||
import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js';
|
||||
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||
|
|
@ -134,7 +135,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
// 何故かeがundefinedで来ることがある
|
||||
if (!e) return '?';
|
||||
|
||||
if (e instanceof Bull.UnrecoverableError || e.name === 'AbortError') {
|
||||
if (e instanceof Bull.UnrecoverableError || e.name === 'AbortError' || e instanceof StatusError) {
|
||||
return `${e.name}: ${e.message}`;
|
||||
}
|
||||
|
||||
|
|
@ -148,12 +149,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
function renderJob(job?: Bull.Job) {
|
||||
if (!job) return '?';
|
||||
|
||||
return {
|
||||
name: job.name || undefined,
|
||||
const info: Record<string, string> = {
|
||||
info: getJobInfo(job),
|
||||
failedReason: job.failedReason || undefined,
|
||||
data: job.data,
|
||||
};
|
||||
|
||||
if (job.name) info.name = job.name;
|
||||
if (job.failedReason) info.failedReason = job.failedReason;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
//#region system
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { URL } from 'node:url';
|
|||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import httpSignature from '@peertube/http-signature';
|
||||
import * as Bull from 'bullmq';
|
||||
import { AbortError } from 'node-fetch';
|
||||
import type Logger from '@/logger.js';
|
||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
|
||||
|
|
@ -238,6 +239,19 @@ export class InboxProcessorService implements OnApplicationShutdown {
|
|||
return e.message;
|
||||
}
|
||||
}
|
||||
|
||||
if (e instanceof StatusError) {
|
||||
if (e.isRetryable) {
|
||||
return `temporary error ${e.statusCode}`;
|
||||
} else {
|
||||
return `skip: permanent error ${e.statusCode}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (e instanceof AbortError) {
|
||||
return 'request aborted';
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
return 'ok';
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
|
|||
},
|
||||
...(endpoint.meta.limit ? {
|
||||
'429': {
|
||||
description: 'To many requests',
|
||||
description: 'Too many requests',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue