Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/models/drive-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ DriveFile.createIndex('metadata.uri');
DriveFile.createIndex('metadata.userId');
DriveFile.createIndex('metadata.folderId');
DriveFile.createIndex('metadata._user.host');
DriveFile.createIndex('metadata.apId', { sparse: true, unique: true });
export default DriveFile;

export const DriveFileChunk = db.get('driveFiles.chunks');
Expand Down Expand Up @@ -48,6 +49,9 @@ export type IMetadata = {
folderId: mongo.ObjectID;
comment: string;

/** ActivityPub object ID */
apId?: string;

/**
* リモートインスタンスから取得した場合の元URL
*/
Expand Down
5 changes: 4 additions & 1 deletion src/remote/activitypub/kernel/update/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { IRemoteUser } from '../../../../models/user';
import { IUpdate, isActor, getApType, isPost } from '../../type';
import { IUpdate, isActor, getApType, isPost, isDocument } from '../../type';
import { apLogger } from '../../logger';
import { updateQuestion } from '../../models/question';
import Resolver from '../../resolver';
import { updatePerson } from '../../models/person';
import updateNote from './note';
import { updateImage } from '../../models/image';

/**
* Updateアクティビティを捌きます
Expand All @@ -31,6 +32,8 @@ export default async (actor: IRemoteUser, activity: IUpdate): Promise<string> =>
return `ok: Question updated`;
} else if (isPost(object)) {
return await updateNote(actor, object);
} else if (isDocument(object)) {
return await updateImage(actor, object);
} else {
return `skip: Unknown type: ${getApType(object)}`;
}
Expand Down
124 changes: 92 additions & 32 deletions src/remote/activitypub/models/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,62 +4,41 @@ import DriveFile, { IDriveFile } from '../../../models/drive-file';
import Resolver from '../resolver';
import fetchMeta from '../../../misc/fetch-meta';
import { apLogger } from '../logger';
import { IObject, isDocument } from '../type';
import { IApDocument, IObject, isDocument } from '../type';
import { StatusError } from '../../../misc/fetch';
import { oidEquals } from '../../../prelude/oid';
import { toApHost } from '../../../misc/convert-host';

const logger = apLogger;

/**
* Imageを作成します。
*/
export async function createImage(actor: IRemoteUser, value: IObject): Promise<IDriveFile | null | undefined> {
// 投稿者が凍結か削除されていたらスキップ
if (actor.isSuspended || actor.isDeleted) {
return null;
}

const image = await new Resolver().resolve(value);
const { document, apId } = await getApDocument(actor, value);

if (!isDocument(image)) return null;
if (!document) return null; // not Document

if (typeof image.url !== 'string') {
return null;
}

logger.info(`Creating the Image: ${image.url}`);
logger.info(`Creating the Image: ${document.url} apId=${apId}`);

const instance = await fetchMeta();
const cache = instance.cacheRemoteFiles;

let file;
try {
file = await uploadFromUrl({ url: image.url, user: actor, uri: image.url, sensitive: !!image.sensitive, isLink: !cache });
file = await uploadFromUrl({ url: document.url as string, user: actor, uri: document.url as string, sensitive: !!document.sensitive, isLink: !cache, apId });
} catch (e) {
// 4xxの場合は添付されてなかったことにする
if (e instanceof StatusError && e.isPermanentError) {
logger.warn(`Ignored image: ${image.url} - ${e.statusCode}`);
logger.warn(`Ignored image: ${document.url} - ${e.statusCode}`);
return null;
}

throw e;
}

if (file.metadata?.isRemote) {
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、
// URLを更新する
if (file.metadata.url !== image.url) {
file = await DriveFile.findOneAndUpdate({ _id: file._id }, {
$set: {
'metadata.url': image.url,
'metadata.uri': image.url
}
}, {
returnNewDocument: true
});
}
}

return file;
const fresh = await detectChangeAndUpdate(file, document);
return fresh;
}

/**
Expand All @@ -69,8 +48,89 @@ export async function createImage(actor: IRemoteUser, value: IObject): Promise<I
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
*/
export async function resolveImage(actor: IRemoteUser, value: IObject): Promise<IDriveFile | null | undefined> {
// TODO
const { document, apId } = await getApDocument(actor, value);

if (!document) return null; // not Document

if (apId) {
const exists = await DriveFile.findOne({ apId: document.id });

if (exists) {
const fresh = await detectChangeAndUpdate(exists, document);
return fresh;
}
}

// リモートサーバーからフェッチしてきて登録
return await createImage(actor, value);
}

export async function updateImage(actor: IRemoteUser, value: IObject): Promise<string> {
const { document, apId } = await getApDocument(actor, value);

if (!document) return `skip: invalid Document`;
if (!apId) return `skip: invalid apId=${apId}`;

const exists = await DriveFile.findOne({ apId });
if (!exists) return `skip: not existant apId=${apId}`;

// check owner
if (!oidEquals(exists.metadata?.userId, actor._id)) return `skip: invalid owner`;

await detectChangeAndUpdate(exists, document);

return `ok: updated apId=${apId}`;
}

/**
* Detect DriveFile changes and update
* @param exists Current DB entity
* @param document Incomming AP Document
* @returns Fresh DriveFile
*/
async function detectChangeAndUpdate(exists: IDriveFile, document: IApDocument) {
const set = { } as any;

// detect general metadata changes
if (typeof document.sensitive === 'boolean') set.isSensitive = document.sensitive;
if (typeof document.name === 'string') set.name = document.name;

// detect src url change
if (exists.metadata?.isRemote && exists.metadata?.url !== document.url) {
set['metadata.url'] = document.url;
set['metadata.uri'] = document.url;
}

// TODO: no peform no needd

const fresh = await DriveFile.findOneAndUpdate({ _id: exists._id }, {
$set: set
}, {
returnNewDocument: true
});

if (!fresh) throw 'unex 23414r2t';
return fresh;
}

/**
* Get validated Document and apId (if available)
*/
async function getApDocument(actor: IRemoteUser, value: IObject): Promise<{ document?: IApDocument, apId?: string }> {
// check actor available
if (actor.isSuspended || actor.isDeleted) return { };

const document = await new Resolver().resolve(value);

// check valid Document
if (!isDocument(document)) return { };
if (typeof document.url !== 'string') return { };

// provide apId if valid
let apId: string | undefined = undefined;
try {
if (typeof document.id === 'string' && toApHost(document.id) === toApHost(actor.uri)) apId = document.id;
} catch { }

return { document, apId };
}
2 changes: 2 additions & 0 deletions src/remote/activitypub/renderer/document.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { IDriveFile } from '../../../models/drive-file';
import getDriveFileUrl from '../../../misc/get-drive-file-url';
import config from '../../../config';

export default (file: IDriveFile) => ({
id: `${config.url}/activitypub/documents/${file._id}`,
type: 'Document',
mediaType: file.contentType,
sensitive: !!file.metadata?.isSensitive,
Expand Down
7 changes: 5 additions & 2 deletions src/remote/activitypub/renderer/image.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { IDriveFile } from '../../../models/drive-file';
import getDriveFileUrl from '../../../misc/get-drive-file-url';
import config from '../../../config';

export default (file: IDriveFile) => ({
id: `${config.url}/activitypub/documents/${file._id}`,
type: 'Image',
url: getDriveFileUrl(file),
sensitive: !!file.metadata?.isSensitive
mediaType: file.contentType,
sensitive: !!file.metadata?.isSensitive,
url: getDriveFileUrl(file)
});
23 changes: 22 additions & 1 deletion src/server/api/endpoints/drive/files/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { publishDriveStream } from '../../../../../services/stream';
import define from '../../../define';
import Note from '../../../../../models/note';
import { ApiError } from '../../../error';
import { renderActivity } from '../../../../../remote/activitypub/renderer';
import renderUpdate from '../../../../../remote/activitypub/renderer/update';
import renderDocument from '../../../../../remote/activitypub/renderer/document';
import { deliverToFollowers } from '../../../../../remote/activitypub/deliver-manager';

export const meta = {
desc: {
Expand Down Expand Up @@ -84,7 +88,7 @@ export default define(meta, async (ps, user) => {
_id: ps.fileId
});

if (file === null) {
if (file == null) {
throw new ApiError(meta.errors.noSuchFile);
}

Expand Down Expand Up @@ -142,5 +146,22 @@ export default define(meta, async (ps, user) => {
// Publish fileUpdated event
publishDriveStream(user._id, 'fileUpdated', fileObj);

if (!user.noFederation && file.metadata?.attachedNoteIds) {
const n = await Note.findOne({
_id: { $in: file.metadata?.attachedNoteIds }, //
deletedAt: { $exists: false },
userId: user._id,
visibility: { $in: ['public', 'home'] },
localOnly: { $ne: true },
copyOnce: { $ne: true }
});

if (n) {
const content = renderActivity(renderUpdate(await renderDocument(file), user));
deliverToFollowers(user, content);
}
}


return fileObj;
});
8 changes: 6 additions & 2 deletions src/services/drive/add-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ type AddFileArgs = {
uri?: string | null;
/** CommMark file as sensitiveent */
sensitive?: boolean;
apId?: string;
}

/**
Expand All @@ -405,6 +406,7 @@ export async function addFile({
url = null,
uri = null,
sensitive = false,
apId = undefined,
}: AddFileArgs): Promise<IDriveFile> {
const info = await getFileInfo(path);
logger.info(`${JSON.stringify(info)}`);
Expand Down Expand Up @@ -512,9 +514,11 @@ export async function addFile({
properties: properties,
withoutChunks: isLink,
isRemote: isLink,
isSensitive: (isLocalUser(user) && user.settings?.alwaysMarkNsfw) || sensitive
isSensitive: (isLocalUser(user) && user.settings?.alwaysMarkNsfw) || sensitive,
} as IMetadata;

if (apId != null) metadata.apId = apId;

if (url !== null) {
metadata.src = url;

Expand Down Expand Up @@ -558,7 +562,7 @@ export async function addFile({
driveFile = await (save(path, detectedName, info, metadata, drive));
}

if (!driveFile) throw 'Failed to create drivefile ${e}';
if (!driveFile) throw `Failed to create drivefile`;

logger.succ(`drive file has been created ${driveFile._id}`);

Expand Down
10 changes: 6 additions & 4 deletions src/services/drive/upload-from-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Args = {
sensitive?: boolean;
force?: boolean;
isLink?: boolean;
apId?: string;
}

export async function uploadFromUrl({
Expand All @@ -25,7 +26,8 @@ export async function uploadFromUrl({
uri = null,
sensitive = false,
force = false,
isLink = false
isLink = false,
apId,
}: Args): Promise<IDriveFile> {
// Create temp file
const [path, cleanup] = await createTemp();
Expand All @@ -41,11 +43,11 @@ export async function uploadFromUrl({
name = null;
}

let driveFile: IDriveFile;
let driveFile: IDriveFile | null = null;
let error;

try {
driveFile = await addFile({ user, path, name, folderId, force, isLink, url, uri, sensitive });
driveFile = await addFile({ user, path, name, folderId, force, isLink, url, uri, sensitive, apId });
logger.succ(`Got: ${driveFile._id}`);
} catch (e) {
error = e;
Expand All @@ -58,7 +60,7 @@ export async function uploadFromUrl({
// clean-up
cleanup();

if (error) {
if (error || !driveFile) {
throw error;
} else {
return driveFile;
Expand Down