リモートのピン留め投稿取得対応 (#2798)
* Fetch featured * Handle featured change * Fix typo
This commit is contained in:
		
							parent
							
								
									b5745877ca
								
							
						
					
					
						commit
						c09a2a37fe
					
				
					 11 changed files with 199 additions and 63 deletions
				
			
		| 
						 | 
					@ -113,6 +113,7 @@ export interface ILocalUser extends IUserBase {
 | 
				
			||||||
export interface IRemoteUser extends IUserBase {
 | 
					export interface IRemoteUser extends IUserBase {
 | 
				
			||||||
	inbox: string;
 | 
						inbox: string;
 | 
				
			||||||
	sharedInbox?: string;
 | 
						sharedInbox?: string;
 | 
				
			||||||
 | 
						featured?: string;
 | 
				
			||||||
	endpoints: string[];
 | 
						endpoints: string[];
 | 
				
			||||||
	uri: string;
 | 
						uri: string;
 | 
				
			||||||
	url?: string;
 | 
						url?: string;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/remote/activitypub/kernel/add/index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/remote/activitypub/kernel/add/index.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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}`);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,8 @@ import like from './like';
 | 
				
			||||||
import announce from './announce';
 | 
					import announce from './announce';
 | 
				
			||||||
import accept from './accept';
 | 
					import accept from './accept';
 | 
				
			||||||
import reject from './reject';
 | 
					import reject from './reject';
 | 
				
			||||||
 | 
					import add from './add';
 | 
				
			||||||
 | 
					import remove from './remove';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
 | 
					const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
 | 
				
			||||||
	switch (activity.type) {
 | 
						switch (activity.type) {
 | 
				
			||||||
| 
						 | 
					@ -31,6 +33,14 @@ const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
 | 
				
			||||||
		await reject(actor, activity);
 | 
							await reject(actor, activity);
 | 
				
			||||||
		break;
 | 
							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':
 | 
						case 'Announce':
 | 
				
			||||||
		await announce(actor, activity);
 | 
							await announce(actor, activity);
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/remote/activitypub/kernel/remove/index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/remote/activitypub/kernel/remove/index.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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}`);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -56,7 +56,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
 | 
				
			||||||
	log(`Creating the Note: ${note.id}`);
 | 
						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) {
 | 
						if (actor.isSuspended) {
 | 
				
			||||||
| 
						 | 
					@ -73,7 +73,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
 | 
				
			||||||
			visibility = 'followers';
 | 
								visibility = 'followers';
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			visibility = 'specified';
 | 
								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
 | 
						//#endergion
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,15 +3,16 @@ import { toUnicode } from 'punycode';
 | 
				
			||||||
import * as debug from 'debug';
 | 
					import * as debug from 'debug';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import config from '../../../config';
 | 
					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 Resolver from '../resolver';
 | 
				
			||||||
import { resolveImage } from './image';
 | 
					import { resolveImage } from './image';
 | 
				
			||||||
import { isCollectionOrOrderedCollection, IPerson } from '../type';
 | 
					import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type';
 | 
				
			||||||
import { IDriveFile } from '../../../models/drive-file';
 | 
					import { IDriveFile } from '../../../models/drive-file';
 | 
				
			||||||
import Meta from '../../../models/meta';
 | 
					import Meta from '../../../models/meta';
 | 
				
			||||||
import htmlToMFM from '../../../mfm/html-to-mfm';
 | 
					import htmlToMFM from '../../../mfm/html-to-mfm';
 | 
				
			||||||
import { updateUserStats } from '../../../services/update-chart';
 | 
					import { updateUserStats } from '../../../services/update-chart';
 | 
				
			||||||
import { URL } from 'url';
 | 
					import { URL } from 'url';
 | 
				
			||||||
 | 
					import { resolveNote } from './note';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const log = debug('misskey:activitypub');
 | 
					const log = debug('misskey:activitypub');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -155,6 +156,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			inbox: person.inbox,
 | 
								inbox: person.inbox,
 | 
				
			||||||
			sharedInbox: person.sharedInbox,
 | 
								sharedInbox: person.sharedInbox,
 | 
				
			||||||
 | 
								featured: person.featured,
 | 
				
			||||||
			endpoints: person.endpoints,
 | 
								endpoints: person.endpoints,
 | 
				
			||||||
			uri: person.id,
 | 
								uri: person.id,
 | 
				
			||||||
			url: person.url,
 | 
								url: person.url,
 | 
				
			||||||
| 
						 | 
					@ -211,6 +213,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
 | 
				
			||||||
	user.bannerUrl = bannerUrl;
 | 
						user.bannerUrl = bannerUrl;
 | 
				
			||||||
	//#endregion
 | 
						//#endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						await updateFeatured(user._id).catch(err => console.log(err));
 | 
				
			||||||
	return user;
 | 
						return user;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -282,6 +285,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
 | 
				
			||||||
			updatedAt: new Date(),
 | 
								updatedAt: new Date(),
 | 
				
			||||||
			inbox: person.inbox,
 | 
								inbox: person.inbox,
 | 
				
			||||||
			sharedInbox: person.sharedInbox,
 | 
								sharedInbox: person.sharedInbox,
 | 
				
			||||||
 | 
								featured: person.featured,
 | 
				
			||||||
			avatarId: avatar ? avatar._id : null,
 | 
								avatarId: avatar ? avatar._id : null,
 | 
				
			||||||
			bannerId: banner ? banner._id : null,
 | 
								bannerId: banner ? banner._id : null,
 | 
				
			||||||
			avatarUrl: (avatar && avatar.metadata.thumbnailUrl) ? avatar.metadata.thumbnailUrl : (avatar && avatar.metadata.url) ? avatar.metadata.url : 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に対象のPersonが登録されていればそれを返し、そうでなければ
 | 
				
			||||||
 * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
 | 
					 * リモートサーバーからフェッチして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';
 | 
						if (typeof uri !== 'string') throw 'uri is not string';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	//#region このサーバーに既に登録されていたらそれを返す
 | 
						//#region このサーバーに既に登録されていたらそれを返す
 | 
				
			||||||
| 
						 | 
					@ -323,5 +329,37 @@ export async function resolvePerson(uri: string, verifier?: string): Promise<IUs
 | 
				
			||||||
	//#endregion
 | 
						//#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)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,11 +19,11 @@ export default class Resolver {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		switch (collection.type) {
 | 
							switch (collection.type) {
 | 
				
			||||||
		case 'Collection':
 | 
							case 'Collection':
 | 
				
			||||||
			collection.objects = collection.object.items;
 | 
								collection.objects = collection.items;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case 'OrderedCollection':
 | 
							case 'OrderedCollection':
 | 
				
			||||||
			collection.objects = collection.object.orderedItems;
 | 
								collection.objects = collection.orderedItems;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -91,6 +91,14 @@ export interface IReject extends IActivity {
 | 
				
			||||||
	type: 'Reject';
 | 
						type: 'Reject';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IAdd extends IActivity {
 | 
				
			||||||
 | 
						type: 'Add';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IRemove extends IActivity {
 | 
				
			||||||
 | 
						type: 'Remove';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ILike extends IActivity {
 | 
					export interface ILike extends IActivity {
 | 
				
			||||||
	type: 'Like';
 | 
						type: 'Like';
 | 
				
			||||||
	_misskey_reaction: string;
 | 
						_misskey_reaction: string;
 | 
				
			||||||
| 
						 | 
					@ -109,5 +117,7 @@ export type Object =
 | 
				
			||||||
	IFollow |
 | 
						IFollow |
 | 
				
			||||||
	IAccept |
 | 
						IAccept |
 | 
				
			||||||
	IReject |
 | 
						IReject |
 | 
				
			||||||
 | 
						IAdd |
 | 
				
			||||||
 | 
						IRemove |
 | 
				
			||||||
	ILike |
 | 
						ILike |
 | 
				
			||||||
	IAnnounce;
 | 
						IAnnounce;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,7 @@
 | 
				
			||||||
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
 | 
					import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
 | 
				
			||||||
import User, { ILocalUser } from '../../../../models/user';
 | 
					import { ILocalUser } from '../../../../models/user';
 | 
				
			||||||
import Note from '../../../../models/note';
 | 
					 | 
				
			||||||
import { pack } 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';
 | 
					import getParams from '../../get-params';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const meta = {
 | 
					export const meta = {
 | 
				
			||||||
| 
						 | 
					@ -27,41 +26,18 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
 | 
				
			||||||
	const [ps, psErr] = getParams(meta, params);
 | 
						const [ps, psErr] = getParams(meta, params);
 | 
				
			||||||
	if (psErr) return rej(psErr);
 | 
						if (psErr) return rej(psErr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Fetch pinee
 | 
						// Processing
 | 
				
			||||||
	const note = await Note.findOne({
 | 
						try {
 | 
				
			||||||
		_id: ps.noteId,
 | 
							await addPinned(user, ps.noteId);
 | 
				
			||||||
		userId: user._id
 | 
						} catch (e) {
 | 
				
			||||||
	});
 | 
							return rej(e.message);
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (note === null) {
 | 
					 | 
				
			||||||
		return rej('note not found');
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const pinnedNoteIds = user.pinnedNoteIds || [];
 | 
						// Serialize
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (pinnedNoteIds.length > 5) {
 | 
					 | 
				
			||||||
		return rej('cannot pin more notes');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (pinnedNoteIds.some(id => id.equals(note._id))) {
 | 
					 | 
				
			||||||
		return rej('already exists');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pinnedNoteIds.unshift(note._id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	await User.update(user._id, {
 | 
					 | 
				
			||||||
		$set: {
 | 
					 | 
				
			||||||
			pinnedNoteIds: pinnedNoteIds
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const iObj = await pack(user, user, {
 | 
						const iObj = await pack(user, user, {
 | 
				
			||||||
		detail: true
 | 
							detail: true
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Send response
 | 
						// Send response
 | 
				
			||||||
	res(iObj);
 | 
						res(iObj);
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Send Add to followers
 | 
					 | 
				
			||||||
	deliverPinnedChange(user._id, note._id, true);
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,7 @@
 | 
				
			||||||
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
 | 
					import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
 | 
				
			||||||
import User, { ILocalUser } from '../../../../models/user';
 | 
					import { ILocalUser } from '../../../../models/user';
 | 
				
			||||||
import Note from '../../../../models/note';
 | 
					 | 
				
			||||||
import { pack } 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';
 | 
					import getParams from '../../get-params';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const meta = {
 | 
					export const meta = {
 | 
				
			||||||
| 
						 | 
					@ -27,31 +26,18 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
 | 
				
			||||||
	const [ps, psErr] = getParams(meta, params);
 | 
						const [ps, psErr] = getParams(meta, params);
 | 
				
			||||||
	if (psErr) return rej(psErr);
 | 
						if (psErr) return rej(psErr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Fetch unpinee
 | 
						// Processing
 | 
				
			||||||
	const note = await Note.findOne({
 | 
						try {
 | 
				
			||||||
		_id: ps.noteId,
 | 
							await removePinned(user, ps.noteId);
 | 
				
			||||||
		userId: user._id
 | 
						} catch (e) {
 | 
				
			||||||
	});
 | 
							return rej(e.message);
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (note === null) {
 | 
					 | 
				
			||||||
		return rej('note not found');
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const pinnedNoteIds = (user.pinnedNoteIds || []).filter(id => !id.equals(note._id));
 | 
						// Serialize
 | 
				
			||||||
 | 
					 | 
				
			||||||
	await User.update(user._id, {
 | 
					 | 
				
			||||||
		$set: {
 | 
					 | 
				
			||||||
			pinnedNoteIds: pinnedNoteIds
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const iObj = await pack(user, user, {
 | 
						const iObj = await pack(user, user, {
 | 
				
			||||||
		detail: true
 | 
							detail: true
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Send response
 | 
						// Send response
 | 
				
			||||||
	res(iObj);
 | 
						res(iObj);
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Send Remove to followers
 | 
					 | 
				
			||||||
	deliverPinnedChange(user._id, note._id, false);
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,83 @@
 | 
				
			||||||
import config from '../../config';
 | 
					import config from '../../config';
 | 
				
			||||||
import * as mongo from 'mongodb';
 | 
					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 Following from '../../models/following';
 | 
				
			||||||
import renderAdd from '../../remote/activitypub/renderer/add';
 | 
					import renderAdd from '../../remote/activitypub/renderer/add';
 | 
				
			||||||
import renderRemove from '../../remote/activitypub/renderer/remove';
 | 
					import renderRemove from '../../remote/activitypub/renderer/remove';
 | 
				
			||||||
import packAp from '../../remote/activitypub/renderer';
 | 
					import packAp from '../../remote/activitypub/renderer';
 | 
				
			||||||
import { deliver } from '../../queue';
 | 
					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) {
 | 
					export async function deliverPinnedChange(userId: mongo.ObjectID, noteId: mongo.ObjectID, isAddition: boolean) {
 | 
				
			||||||
	const user = await User.findOne({
 | 
						const user = await User.findOne({
 | 
				
			||||||
		_id: userId
 | 
							_id: userId
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue