summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMeiMei <30769358+mei23@users.noreply.github.com>2018-10-02 16:27:36 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2018-10-02 16:27:36 +0900
commitc09a2a37fe3ab130969dd143b0ee65706871951f (patch)
tree92b3874cd806d135d920192be469a99a4cd4d6dd /src
parent:art: (diff)
downloadmisskey-c09a2a37fe3ab130969dd143b0ee65706871951f.tar.gz
misskey-c09a2a37fe3ab130969dd143b0ee65706871951f.tar.bz2
misskey-c09a2a37fe3ab130969dd143b0ee65706871951f.zip
リモートのピン留め投稿取得対応 (#2798)
* Fetch featured * Handle featured change * Fix typo
Diffstat (limited to 'src')
-rw-r--r--src/models/user.ts1
-rw-r--r--src/remote/activitypub/kernel/add/index.ts22
-rw-r--r--src/remote/activitypub/kernel/index.ts10
-rw-r--r--src/remote/activitypub/kernel/remove/index.ts22
-rw-r--r--src/remote/activitypub/models/note.ts4
-rw-r--r--src/remote/activitypub/models/person.ts46
-rw-r--r--src/remote/activitypub/resolver.ts4
-rw-r--r--src/remote/activitypub/type.ts10
-rw-r--r--src/server/api/endpoints/i/pin.ts40
-rw-r--r--src/server/api/endpoints/i/unpin.ts30
-rw-r--r--src/services/i/pin.ts73
11 files changed, 199 insertions, 63 deletions
diff --git a/src/models/user.ts b/src/models/user.ts
index 8ff91d3f45..d2124bda74 100644
--- a/src/models/user.ts
+++ b/src/models/user.ts
@@ -113,6 +113,7 @@ export interface ILocalUser extends IUserBase {
export interface IRemoteUser extends IUserBase {
inbox: string;
sharedInbox?: string;
+ featured?: string;
endpoints: string[];
uri: string;
url?: string;
diff --git a/src/remote/activitypub/kernel/add/index.ts b/src/remote/activitypub/kernel/add/index.ts
new file mode 100644
index 0000000000..eb2dba5b21
--- /dev/null
+++ b/src/remote/activitypub/kernel/add/index.ts
@@ -0,0 +1,22 @@
+import { IRemoteUser } from '../../../../models/user';
+import { IAdd } from '../../type';
+import { resolveNote } from '../../models/note';
+import { addPinned } from '../../../../services/i/pin';
+
+export default async (actor: IRemoteUser, activity: IAdd): Promise<void> => {
+ if ('actor' in activity && actor.uri !== activity.actor) {
+ throw new Error('invalid actor');
+ }
+
+ if (activity.target == null) {
+ throw new Error('target is null');
+ }
+
+ if (activity.target === actor.featured) {
+ const note = await resolveNote(activity.object);
+ await addPinned(actor, note._id);
+ return;
+ }
+
+ throw new Error(`unknown target: ${activity.target}`);
+};
diff --git a/src/remote/activitypub/kernel/index.ts b/src/remote/activitypub/kernel/index.ts
index 752a9bd2e2..52b0efc730 100644
--- a/src/remote/activitypub/kernel/index.ts
+++ b/src/remote/activitypub/kernel/index.ts
@@ -8,6 +8,8 @@ import like from './like';
import announce from './announce';
import accept from './accept';
import reject from './reject';
+import add from './add';
+import remove from './remove';
const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
switch (activity.type) {
@@ -31,6 +33,14 @@ const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
await reject(actor, activity);
break;
+ case 'Add':
+ await add(actor, activity).catch(err => console.log(err));
+ break;
+
+ case 'Remove':
+ await remove(actor, activity).catch(err => console.log(err));
+ break;
+
case 'Announce':
await announce(actor, activity);
break;
diff --git a/src/remote/activitypub/kernel/remove/index.ts b/src/remote/activitypub/kernel/remove/index.ts
new file mode 100644
index 0000000000..91b207c80d
--- /dev/null
+++ b/src/remote/activitypub/kernel/remove/index.ts
@@ -0,0 +1,22 @@
+import { IRemoteUser } from '../../../../models/user';
+import { IRemove } from '../../type';
+import { resolveNote } from '../../models/note';
+import { removePinned } from '../../../../services/i/pin';
+
+export default async (actor: IRemoteUser, activity: IRemove): Promise<void> => {
+ if ('actor' in activity && actor.uri !== activity.actor) {
+ throw new Error('invalid actor');
+ }
+
+ if (activity.target == null) {
+ throw new Error('target is null');
+ }
+
+ if (activity.target === actor.featured) {
+ const note = await resolveNote(activity.object);
+ await removePinned(actor, note._id);
+ return;
+ }
+
+ throw new Error(`unknown target: ${activity.target}`);
+};
diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts
index b4afda765a..d49cf53079 100644
--- a/src/remote/activitypub/models/note.ts
+++ b/src/remote/activitypub/models/note.ts
@@ -56,7 +56,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
log(`Creating the Note: ${note.id}`);
// 投稿者をフェッチ
- const actor = await resolvePerson(note.attributedTo) as IRemoteUser;
+ const actor = await resolvePerson(note.attributedTo, null, resolver) as IRemoteUser;
// 投稿者が凍結されていたらスキップ
if (actor.isSuspended) {
@@ -73,7 +73,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
visibility = 'followers';
} else {
visibility = 'specified';
- visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri)));
+ visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri, null, resolver)));
}
}
//#endergion
diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts
index dff38f5460..ee95e43ad3 100644
--- a/src/remote/activitypub/models/person.ts
+++ b/src/remote/activitypub/models/person.ts
@@ -3,15 +3,16 @@ import { toUnicode } from 'punycode';
import * as debug from 'debug';
import config from '../../../config';
-import User, { validateUsername, isValidName, IUser, IRemoteUser } from '../../../models/user';
+import User, { validateUsername, isValidName, IUser, IRemoteUser, isRemoteUser } from '../../../models/user';
import Resolver from '../resolver';
import { resolveImage } from './image';
-import { isCollectionOrOrderedCollection, IPerson } from '../type';
+import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type';
import { IDriveFile } from '../../../models/drive-file';
import Meta from '../../../models/meta';
import htmlToMFM from '../../../mfm/html-to-mfm';
import { updateUserStats } from '../../../services/update-chart';
import { URL } from 'url';
+import { resolveNote } from './note';
const log = debug('misskey:activitypub');
@@ -155,6 +156,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
},
inbox: person.inbox,
sharedInbox: person.sharedInbox,
+ featured: person.featured,
endpoints: person.endpoints,
uri: person.id,
url: person.url,
@@ -211,6 +213,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
user.bannerUrl = bannerUrl;
//#endregion
+ await updateFeatured(user._id).catch(err => console.log(err));
return user;
}
@@ -282,6 +285,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
updatedAt: new Date(),
inbox: person.inbox,
sharedInbox: person.sharedInbox,
+ featured: person.featured,
avatarId: avatar ? avatar._id : null,
bannerId: banner ? banner._id : null,
avatarUrl: (avatar && avatar.metadata.thumbnailUrl) ? avatar.metadata.thumbnailUrl : (avatar && avatar.metadata.url) ? avatar.metadata.url : null,
@@ -303,6 +307,8 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
},
}
});
+
+ await updateFeatured(exist._id).catch(err => console.log(err));
}
/**
@@ -311,7 +317,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
* Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
*/
-export async function resolvePerson(uri: string, verifier?: string): Promise<IUser> {
+export async function resolvePerson(uri: string, verifier?: string, resolver?: Resolver): Promise<IUser> {
if (typeof uri !== 'string') throw 'uri is not string';
//#region このサーバーに既に登録されていたらそれを返す
@@ -323,5 +329,37 @@ export async function resolvePerson(uri: string, verifier?: string): Promise<IUs
//#endregion
// リモートサーバーからフェッチしてきて登録
- return await createPerson(uri);
+ if (resolver == null) resolver = new Resolver();
+ return await createPerson(uri, resolver);
+}
+
+export async function updateFeatured(userId: mongo.ObjectID) {
+ const user = await User.findOne({ _id: userId });
+ if (!isRemoteUser(user)) return;
+ if (!user.featured) return;
+
+ log(`Updating the featured: ${user.uri}`);
+
+ const resolver = new Resolver();
+
+ // Resolve to (Ordered)Collection Object
+ const collection = await resolver.resolveCollection(user.featured);
+ if (!isCollectionOrOrderedCollection(collection)) throw new Error(`Object is not Collection or OrderedCollection`);
+
+ // Resolve to Object(may be Note) arrays
+ const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems;
+ const items = await resolver.resolve(unresolvedItems);
+ if (!Array.isArray(items)) throw new Error(`Collection items is not an array`);
+
+ // Resolve and regist Notes
+ const featuredNotes = await Promise.all(items
+ .filter(item => item.type === 'Note')
+ .slice(0, 5)
+ .map(item => resolveNote(item, resolver)));
+
+ await User.update({ _id: user._id }, {
+ $set: {
+ pinnedNoteIds: featuredNotes.map(note => note._id)
+ }
+ });
}
diff --git a/src/remote/activitypub/resolver.ts b/src/remote/activitypub/resolver.ts
index e1c12e7e62..215e5e8704 100644
--- a/src/remote/activitypub/resolver.ts
+++ b/src/remote/activitypub/resolver.ts
@@ -19,11 +19,11 @@ export default class Resolver {
switch (collection.type) {
case 'Collection':
- collection.objects = collection.object.items;
+ collection.objects = collection.items;
break;
case 'OrderedCollection':
- collection.objects = collection.object.orderedItems;
+ collection.objects = collection.orderedItems;
break;
default:
diff --git a/src/remote/activitypub/type.ts b/src/remote/activitypub/type.ts
index 7bbea5fd18..5c06ee4ffe 100644
--- a/src/remote/activitypub/type.ts
+++ b/src/remote/activitypub/type.ts
@@ -91,6 +91,14 @@ export interface IReject extends IActivity {
type: 'Reject';
}
+export interface IAdd extends IActivity {
+ type: 'Add';
+}
+
+export interface IRemove extends IActivity {
+ type: 'Remove';
+}
+
export interface ILike extends IActivity {
type: 'Like';
_misskey_reaction: string;
@@ -109,5 +117,7 @@ export type Object =
IFollow |
IAccept |
IReject |
+ IAdd |
+ IRemove |
ILike |
IAnnounce;
diff --git a/src/server/api/endpoints/i/pin.ts b/src/server/api/endpoints/i/pin.ts
index f9ae032b11..bf729ca091 100644
--- a/src/server/api/endpoints/i/pin.ts
+++ b/src/server/api/endpoints/i/pin.ts
@@ -1,8 +1,7 @@
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
-import User, { ILocalUser } from '../../../../models/user';
-import Note from '../../../../models/note';
+import { ILocalUser } from '../../../../models/user';
import { pack } from '../../../../models/user';
-import { deliverPinnedChange } from '../../../../services/i/pin';
+import { addPinned } from '../../../../services/i/pin';
import getParams from '../../get-params';
export const meta = {
@@ -27,41 +26,18 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
- // Fetch pinee
- const note = await Note.findOne({
- _id: ps.noteId,
- userId: user._id
- });
-
- if (note === null) {
- return rej('note not found');
- }
-
- const pinnedNoteIds = user.pinnedNoteIds || [];
-
- if (pinnedNoteIds.length > 5) {
- return rej('cannot pin more notes');
+ // Processing
+ try {
+ await addPinned(user, ps.noteId);
+ } catch (e) {
+ return rej(e.message);
}
- if (pinnedNoteIds.some(id => id.equals(note._id))) {
- return rej('already exists');
- }
-
- pinnedNoteIds.unshift(note._id);
-
- await User.update(user._id, {
- $set: {
- pinnedNoteIds: pinnedNoteIds
- }
- });
-
+ // Serialize
const iObj = await pack(user, user, {
detail: true
});
// Send response
res(iObj);
-
- // Send Add to followers
- deliverPinnedChange(user._id, note._id, true);
});
diff --git a/src/server/api/endpoints/i/unpin.ts b/src/server/api/endpoints/i/unpin.ts
index 82625ae5fb..2a81993e4b 100644
--- a/src/server/api/endpoints/i/unpin.ts
+++ b/src/server/api/endpoints/i/unpin.ts
@@ -1,8 +1,7 @@
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
-import User, { ILocalUser } from '../../../../models/user';
-import Note from '../../../../models/note';
+import { ILocalUser } from '../../../../models/user';
import { pack } from '../../../../models/user';
-import { deliverPinnedChange } from '../../../../services/i/pin';
+import { removePinned } from '../../../../services/i/pin';
import getParams from '../../get-params';
export const meta = {
@@ -27,31 +26,18 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
- // Fetch unpinee
- const note = await Note.findOne({
- _id: ps.noteId,
- userId: user._id
- });
-
- if (note === null) {
- return rej('note not found');
+ // Processing
+ try {
+ await removePinned(user, ps.noteId);
+ } catch (e) {
+ return rej(e.message);
}
- const pinnedNoteIds = (user.pinnedNoteIds || []).filter(id => !id.equals(note._id));
-
- await User.update(user._id, {
- $set: {
- pinnedNoteIds: pinnedNoteIds
- }
- });
-
+ // Serialize
const iObj = await pack(user, user, {
detail: true
});
// Send response
res(iObj);
-
- // Send Remove to followers
- deliverPinnedChange(user._id, note._id, false);
});
diff --git a/src/services/i/pin.ts b/src/services/i/pin.ts
index 8b7287e68d..a39cc1e597 100644
--- a/src/services/i/pin.ts
+++ b/src/services/i/pin.ts
@@ -1,12 +1,83 @@
import config from '../../config';
import * as mongo from 'mongodb';
-import User, { isLocalUser, isRemoteUser, ILocalUser } from '../../models/user';
+import User, { isLocalUser, isRemoteUser, ILocalUser, IUser } from '../../models/user';
+import Note from '../../models/note';
import Following from '../../models/following';
import renderAdd from '../../remote/activitypub/renderer/add';
import renderRemove from '../../remote/activitypub/renderer/remove';
import packAp from '../../remote/activitypub/renderer';
import { deliver } from '../../queue';
+/**
+ * 指定した投稿をピン留めします
+ * @param user
+ * @param noteId
+ */
+export async function addPinned(user: IUser, noteId: mongo.ObjectID) {
+ // Fetch pinee
+ const note = await Note.findOne({
+ _id: noteId,
+ userId: user._id
+ });
+
+ if (note === null) {
+ throw new Error('note not found');
+ }
+
+ const pinnedNoteIds = user.pinnedNoteIds || [];
+
+ if (pinnedNoteIds.length > 5) {
+ throw new Error('cannot pin more notes');
+ }
+
+ if (pinnedNoteIds.some(id => id.equals(note._id))) {
+ throw new Error('already exists');
+ }
+
+ pinnedNoteIds.unshift(note._id);
+
+ await User.update(user._id, {
+ $set: {
+ pinnedNoteIds: pinnedNoteIds
+ }
+ });
+
+ // Deliver to remote followers
+ if (isLocalUser(user)) {
+ deliverPinnedChange(user._id, note._id, true);
+ }
+}
+
+/**
+ * 指定した投稿のピン留めを解除します
+ * @param user
+ * @param noteId
+ */
+export async function removePinned(user: IUser, noteId: mongo.ObjectID) {
+ // Fetch unpinee
+ const note = await Note.findOne({
+ _id: noteId,
+ userId: user._id
+ });
+
+ if (note === null) {
+ throw new Error('note not found');
+ }
+
+ const pinnedNoteIds = (user.pinnedNoteIds || []).filter(id => !id.equals(note._id));
+
+ await User.update(user._id, {
+ $set: {
+ pinnedNoteIds: pinnedNoteIds
+ }
+ });
+
+ // Deliver to remote followers
+ if (isLocalUser(user)) {
+ deliverPinnedChange(user._id, noteId, false);
+ }
+}
+
export async function deliverPinnedChange(userId: mongo.ObjectID, noteId: mongo.ObjectID, isAddition: boolean) {
const user = await User.findOne({
_id: userId