AP Link等は添付ファイル扱いしないようになど (#13754)

* Linkは添付ファイルではない

* CHANGELOG
This commit is contained in:
MeiMei 2024-04-28 10:53:33 +09:00 committed by GitHub
parent 8e8ee2ac73
commit c7d7da8fc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 43 additions and 31 deletions

View file

@ -73,6 +73,7 @@
- Fix: 一部のタイムラインのストリーミングでインスタンスミュートが効かない問題を修正 - Fix: 一部のタイムラインのストリーミングでインスタンスミュートが効かない問題を修正
- Fix: グローバルタイムラインで返信が表示されないことがある問題を修正 - Fix: グローバルタイムラインで返信が表示されないことがある問題を修正
- Fix: リノートをミュートしたユーザの投稿のリノートがミュートされる問題を修正 - Fix: リノートをミュートしたユーザの投稿のリノートがミュートされる問題を修正
- Fix: AP Link等は添付ファイル扱いしないようになど (#13754)
## 2024.3.1 ## 2024.3.1

View file

@ -17,7 +17,7 @@ import { bindThis } from '@/decorators.js';
import { checkHttps } from '@/misc/check-https.js'; import { checkHttps } from '@/misc/check-https.js';
import { ApResolverService } from '../ApResolverService.js'; import { ApResolverService } from '../ApResolverService.js';
import { ApLoggerService } from '../ApLoggerService.js'; import { ApLoggerService } from '../ApLoggerService.js';
import type { IObject } from '../type.js'; import { isDocument, type IObject } from '../type.js';
@Injectable() @Injectable()
export class ApImageService { export class ApImageService {
@ -39,7 +39,7 @@ export class ApImageService {
* Imageを作成します * Imageを作成します
*/ */
@bindThis @bindThis
public async createImage(actor: MiRemoteUser, value: string | IObject): Promise<MiDriveFile> { public async createImage(actor: MiRemoteUser, value: string | IObject): Promise<MiDriveFile | null> {
// 投稿者が凍結されていたらスキップ // 投稿者が凍結されていたらスキップ
if (actor.isSuspended) { if (actor.isSuspended) {
throw new Error('actor has been suspended'); throw new Error('actor has been suspended');
@ -47,16 +47,18 @@ export class ApImageService {
const image = await this.apResolverService.createResolver().resolve(value); const image = await this.apResolverService.createResolver().resolve(value);
if (!isDocument(image)) return null;
if (image.url == null) { if (image.url == null) {
throw new Error('invalid image: url not provided'); return null;
} }
if (typeof image.url !== 'string') { if (typeof image.url !== 'string') {
throw new Error('invalid image: unexpected type of url: ' + JSON.stringify(image.url, null, 2)); return null;
} }
if (!checkHttps(image.url)) { if (!checkHttps(image.url)) {
throw new Error('invalid image: unexpected schema of url: ' + image.url); return null;
} }
this.logger.info(`Creating the Image: ${image.url}`); this.logger.info(`Creating the Image: ${image.url}`);
@ -86,12 +88,11 @@ export class ApImageService {
/** /**
* Imageを解決します * Imageを解決します
* *
* Misskeyに対象のImageが登録されていればそれを返し * ImageをリモートサーバーからフェッチしてMisskeyに登録しそれを返します
* Misskeyに登録しそれを返します
*/ */
@bindThis @bindThis
public async resolveImage(actor: MiRemoteUser, value: string | IObject): Promise<MiDriveFile> { public async resolveImage(actor: MiRemoteUser, value: string | IObject): Promise<MiDriveFile | null> {
// TODO // TODO: Misskeyに対象のImageが登録されていればそれを返す
// リモートサーバーからフェッチしてきて登録 // リモートサーバーからフェッチしてきて登録
return await this.createImage(actor, value); return await this.createImage(actor, value);

View file

@ -4,7 +4,6 @@
*/ */
import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { forwardRef, Inject, Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { PollsRepository, EmojisRepository } from '@/models/_.js'; import type { PollsRepository, EmojisRepository } from '@/models/_.js';
@ -209,15 +208,13 @@ export class ApNoteService {
} }
// 添付ファイル // 添付ファイル
// TODO: attachmentは必ずしもImageではない const files: MiDriveFile[] = [];
// TODO: attachmentは必ずしも配列ではない
const limit = promiseLimit<MiDriveFile>(2); for (const attach of toArray(note.attachment)) {
const files = (await Promise.all(toArray(note.attachment).map(attach => ( attach.sensitive ||= note.sensitive; // Noteがsensitiveなら添付もsensitiveにする
limit(() => this.apImageService.resolveImage(actor, { const file = await this.apImageService.resolveImage(actor, attach);
...attach, if (file) files.push(file);
sensitive: note.sensitive, // Noteがsensitiveなら添付もsensitiveにする }
}))
))));
// リプライ // リプライ
const reply: MiNote | null = note.inReplyTo const reply: MiNote | null = note.inReplyTo

View file

@ -25,6 +25,7 @@ export interface IObject {
endTime?: Date; endTime?: Date;
icon?: any; icon?: any;
image?: any; image?: any;
mediaType?: string;
url?: ApObject | string; url?: ApObject | string;
href?: string; href?: string;
tag?: IObject | IObject[]; tag?: IObject | IObject[];
@ -240,14 +241,14 @@ export interface IKey extends IObject {
} }
export interface IApDocument extends IObject { export interface IApDocument extends IObject {
type: 'Document'; type: 'Audio' | 'Document' | 'Image' | 'Page' | 'Video';
name: string | null;
mediaType: string;
} }
export interface IApImage extends IObject { export const isDocument = (object: IObject): object is IApDocument =>
['Audio', 'Document', 'Image', 'Page', 'Video'].includes(getApType(object));
export interface IApImage extends IApDocument {
type: 'Image'; type: 'Image';
name: string | null;
} }
export interface ICreate extends IActivity { export interface ICreate extends IActivity {

View file

@ -17,7 +17,7 @@ import { GlobalModule } from '@/GlobalModule.js';
import { CoreModule } from '@/core/CoreModule.js'; 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, IPost } from '@/core/activitypub/type.js'; import type { IActor, IApDocument, ICollection, IObject, IPost } from '@/core/activitypub/type.js';
import { MiMeta, MiNote } from '@/models/_.js'; import { MiMeta, MiNote } from '@/models/_.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';
@ -295,7 +295,7 @@ describe('ActivityPub', () => {
await createRandomRemoteUser(resolver, personService), await createRandomRemoteUser(resolver, personService),
imageObject, imageObject,
); );
assert.ok(!driveFile.isLink); assert.ok(driveFile && !driveFile.isLink);
const sensitiveImageObject: IApDocument = { const sensitiveImageObject: IApDocument = {
type: 'Document', type: 'Document',
@ -308,7 +308,7 @@ describe('ActivityPub', () => {
await createRandomRemoteUser(resolver, personService), await createRandomRemoteUser(resolver, personService),
sensitiveImageObject, sensitiveImageObject,
); );
assert.ok(!sensitiveDriveFile.isLink); assert.ok(sensitiveDriveFile && !sensitiveDriveFile.isLink);
}); });
test('cacheRemoteFiles=false disables caching', async () => { test('cacheRemoteFiles=false disables caching', async () => {
@ -324,7 +324,7 @@ describe('ActivityPub', () => {
await createRandomRemoteUser(resolver, personService), await createRandomRemoteUser(resolver, personService),
imageObject, imageObject,
); );
assert.ok(driveFile.isLink); assert.ok(driveFile && driveFile.isLink);
const sensitiveImageObject: IApDocument = { const sensitiveImageObject: IApDocument = {
type: 'Document', type: 'Document',
@ -337,7 +337,7 @@ describe('ActivityPub', () => {
await createRandomRemoteUser(resolver, personService), await createRandomRemoteUser(resolver, personService),
sensitiveImageObject, sensitiveImageObject,
); );
assert.ok(sensitiveDriveFile.isLink); assert.ok(sensitiveDriveFile && sensitiveDriveFile.isLink);
}); });
test('cacheRemoteSensitiveFiles=false only affects sensitive files', async () => { test('cacheRemoteSensitiveFiles=false only affects sensitive files', async () => {
@ -353,7 +353,7 @@ describe('ActivityPub', () => {
await createRandomRemoteUser(resolver, personService), await createRandomRemoteUser(resolver, personService),
imageObject, imageObject,
); );
assert.ok(!driveFile.isLink); assert.ok(driveFile && !driveFile.isLink);
const sensitiveImageObject: IApDocument = { const sensitiveImageObject: IApDocument = {
type: 'Document', type: 'Document',
@ -366,7 +366,19 @@ describe('ActivityPub', () => {
await createRandomRemoteUser(resolver, personService), await createRandomRemoteUser(resolver, personService),
sensitiveImageObject, sensitiveImageObject,
); );
assert.ok(sensitiveDriveFile.isLink); assert.ok(sensitiveDriveFile && sensitiveDriveFile.isLink);
});
test('Link is not an attachment files', async () => {
const linkObject: IObject = {
type: 'Link',
href: 'https://example.com/',
};
const driveFile = await imageService.createImage(
await createRandomRemoteUser(resolver, personService),
linkObject,
);
assert.strictEqual(driveFile, null);
}); });
}); });
}); });