Merge pull request #1368 from akihikodaki/misc
Distribute posts from remote
This commit is contained in:
		
							
								
								
									
										50
									
								
								src/post/create.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/post/create.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| import parseAcct from '../acct/parse'; | ||||
| import Post from '../models/post'; | ||||
| import User from '../models/user'; | ||||
|  | ||||
| export default async (post, reply, repost, atMentions) => { | ||||
| 	post.mentions = []; | ||||
|  | ||||
| 	function addMention(mentionee) { | ||||
| 		// Reject if already added | ||||
| 		if (post.mentions.some(x => x.equals(mentionee))) return; | ||||
|  | ||||
| 		// Add mention | ||||
| 		post.mentions.push(mentionee); | ||||
| 	} | ||||
|  | ||||
| 	if (reply) { | ||||
| 		// Add mention | ||||
| 		addMention(reply.userId); | ||||
| 		post.replyId = reply._id; | ||||
| 		post._reply = { userId: reply.userId }; | ||||
| 	} else { | ||||
| 		post.replyId = null; | ||||
| 		post._reply = null; | ||||
| 	} | ||||
|  | ||||
| 	if (repost) { | ||||
| 		if (post.text) { | ||||
| 			// Add mention | ||||
| 			addMention(repost.userId); | ||||
| 		} | ||||
|  | ||||
| 		post.repostId = repost._id; | ||||
| 		post._repost = { userId: repost.userId }; | ||||
| 	} else { | ||||
| 		post.repostId = null; | ||||
| 		post._repost = null; | ||||
| 	} | ||||
|  | ||||
| 	await Promise.all(atMentions.map(async mention => { | ||||
| 		// Fetch mentioned user | ||||
| 		// SELECT _id | ||||
| 		const { _id } = await User | ||||
| 			.findOne(parseAcct(mention), { _id: true }); | ||||
|  | ||||
| 		// Add mention | ||||
| 		addMention(_id); | ||||
| 	})); | ||||
|  | ||||
| 	return Post.insert(post); | ||||
| }; | ||||
							
								
								
									
										216
									
								
								src/post/distribute.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								src/post/distribute.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,216 @@ | ||||
| import Channel from '../models/channel'; | ||||
| import Mute from '../models/mute'; | ||||
| import Following from '../models/following'; | ||||
| import Post from '../models/post'; | ||||
| import Watching from '../models/post-watching'; | ||||
| import ChannelWatching from '../models/channel-watching'; | ||||
| import User from '../models/user'; | ||||
| import stream, { publishChannelStream } from '../publishers/stream'; | ||||
| import notify from '../publishers/notify'; | ||||
| import pushSw from '../publishers/push-sw'; | ||||
| import watch from './watch'; | ||||
|  | ||||
| export default async (user, mentions, post) => { | ||||
| 	const promises = [ | ||||
| 		User.update({ _id: user._id }, { | ||||
| 			// Increment my posts count | ||||
| 			$inc: { | ||||
| 				postsCount: 1 | ||||
| 			}, | ||||
|  | ||||
| 			$set: { | ||||
| 				latestPost: post._id | ||||
| 			} | ||||
| 		}), | ||||
| 	] as Array<Promise<any>>; | ||||
|  | ||||
| 	function addMention(mentionee, reason) { | ||||
| 		// Publish event | ||||
| 		if (!user._id.equals(mentionee)) { | ||||
| 			promises.push(Mute.find({ | ||||
| 				muterId: mentionee, | ||||
| 				deletedAt: { $exists: false } | ||||
| 			}).then(mentioneeMutes => { | ||||
| 				const mentioneesMutedUserIds = mentioneeMutes.map(m => m.muteeId.toString()); | ||||
| 				if (mentioneesMutedUserIds.indexOf(user._id.toString()) == -1) { | ||||
| 					stream(mentionee, reason, post); | ||||
| 					pushSw(mentionee, reason, post); | ||||
| 				} | ||||
| 			})); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// タイムラインへの投稿 | ||||
| 	if (!post.channelId) { | ||||
| 		// Publish event to myself's stream | ||||
| 		stream(user._id, 'post', post); | ||||
|  | ||||
| 		// Fetch all followers | ||||
| 		const followers = await Following | ||||
| 			.find({ | ||||
| 				followeeId: user._id, | ||||
| 				// 削除されたドキュメントは除く | ||||
| 				deletedAt: { $exists: false } | ||||
| 			}, { | ||||
| 				followerId: true, | ||||
| 				_id: false | ||||
| 			}); | ||||
|  | ||||
| 		// Publish event to followers stream | ||||
| 		followers.forEach(following => | ||||
| 			stream(following.followerId, 'post', post)); | ||||
| 	} | ||||
|  | ||||
| 	// チャンネルへの投稿 | ||||
| 	if (post.channelId) { | ||||
| 		// Increment channel index(posts count) | ||||
| 		promises.push(Channel.update({ _id: post.channelId }, { | ||||
| 			$inc: { | ||||
| 				index: 1 | ||||
| 			} | ||||
| 		})); | ||||
|  | ||||
| 		// Publish event to channel | ||||
| 		publishChannelStream(post.channelId, 'post', post); | ||||
|  | ||||
| 		// Get channel watchers | ||||
| 		const watches = await ChannelWatching.find({ | ||||
| 			channelId: post.channelId, | ||||
| 			// 削除されたドキュメントは除く | ||||
| 			deletedAt: { $exists: false } | ||||
| 		}); | ||||
|  | ||||
| 		// チャンネルの視聴者(のタイムライン)に配信 | ||||
| 		watches.forEach(w => { | ||||
| 			stream(w.userId, 'post', post); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	// If has in reply to post | ||||
| 	if (post.replyId) { | ||||
| 		promises.push( | ||||
| 			// Increment replies count | ||||
| 			Post.update({ _id: post.replyId }, { | ||||
| 				$inc: { | ||||
| 					repliesCount: 1 | ||||
| 				} | ||||
| 			}), | ||||
|  | ||||
| 			// 自分自身へのリプライでない限りは通知を作成 | ||||
| 			notify(post.reply.userId, user._id, 'reply', { | ||||
| 				postId: post._id | ||||
| 			}), | ||||
|  | ||||
| 			// Fetch watchers | ||||
| 			Watching | ||||
| 				.find({ | ||||
| 					postId: post.replyId, | ||||
| 					userId: { $ne: user._id }, | ||||
| 					// 削除されたドキュメントは除く | ||||
| 					deletedAt: { $exists: false } | ||||
| 				}, { | ||||
| 					fields: { | ||||
| 						userId: true | ||||
| 					} | ||||
| 				}) | ||||
| 				.then(watchers => { | ||||
| 					watchers.forEach(watcher => { | ||||
| 						notify(watcher.userId, user._id, 'reply', { | ||||
| 							postId: post._id | ||||
| 						}); | ||||
| 					}); | ||||
| 				}) | ||||
| 		); | ||||
|  | ||||
| 		// Add mention | ||||
| 		addMention(post.reply.userId, 'reply'); | ||||
|  | ||||
| 		// この投稿をWatchする | ||||
| 		if (user.account.settings.autoWatch !== false) { | ||||
| 			promises.push(watch(user._id, post.reply)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// If it is repost | ||||
| 	if (post.repostId) { | ||||
| 		const type = post.text ? 'quote' : 'repost'; | ||||
|  | ||||
| 		promises.push( | ||||
| 			// Notify | ||||
| 			notify(post.repost.userId, user._id, type, { | ||||
| 				postId: post._id | ||||
| 			}), | ||||
|  | ||||
| 			// Fetch watchers | ||||
| 			Watching | ||||
| 				.find({ | ||||
| 					postId: post.repostId, | ||||
| 					userId: { $ne: user._id }, | ||||
| 					// 削除されたドキュメントは除く | ||||
| 					deletedAt: { $exists: false } | ||||
| 				}, { | ||||
| 					fields: { | ||||
| 						userId: true | ||||
| 					} | ||||
| 				}) | ||||
| 				.then(watchers => { | ||||
| 					watchers.forEach(watcher => { | ||||
| 						notify(watcher.userId, user._id, type, { | ||||
| 							postId: post._id | ||||
| 						}); | ||||
| 					}); | ||||
| 				}), | ||||
|  | ||||
| 			// この投稿をWatchする | ||||
| 			// TODO: ユーザーが「Repostしたときに自動でWatchする」設定を | ||||
| 			//       オフにしていた場合はしない | ||||
| 			watch(user._id, post.repost) | ||||
| 		); | ||||
|  | ||||
| 		// If it is quote repost | ||||
| 		if (post.text) { | ||||
| 			// Add mention | ||||
| 			addMention(post.repost.userId, 'quote'); | ||||
| 		} else { | ||||
| 			// Publish event | ||||
| 			if (!user._id.equals(post.repost.userId)) { | ||||
| 				stream(post.repost.userId, 'repost', post); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// 今までで同じ投稿をRepostしているか | ||||
| 		const existRepost = await Post.findOne({ | ||||
| 			userId: user._id, | ||||
| 			repostId: post.repostId, | ||||
| 			_id: { | ||||
| 				$ne: post._id | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		if (!existRepost) { | ||||
| 			// Update repostee status | ||||
| 			promises.push(Post.update({ _id: post.repostId }, { | ||||
| 				$inc: { | ||||
| 					repostCount: 1 | ||||
| 				} | ||||
| 			})); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Resolve all mentions | ||||
| 	await Promise.all(mentions.map(async mention => { | ||||
| 		// 既に言及されたユーザーに対する返信や引用repostの場合も無視 | ||||
| 		if (post.reply && post.reply.userId.equals(mention)) return; | ||||
| 		if (post.repost && post.repost.userId.equals(mention)) return; | ||||
|  | ||||
| 		// Add mention | ||||
| 		addMention(mention, 'mention'); | ||||
|  | ||||
| 		// Create notification | ||||
| 		await notify(mention, user._id, 'mention', { | ||||
| 			postId: post._id | ||||
| 		}); | ||||
| 	})); | ||||
|  | ||||
| 	return Promise.all(promises); | ||||
| }; | ||||
| @@ -1,5 +1,5 @@ | ||||
| import * as mongodb from 'mongodb'; | ||||
| import Watching from '../../../models/post-watching'; | ||||
| import Watching from '../models/post-watching'; | ||||
| 
 | ||||
| export default async (me: mongodb.ObjectID, post: object) => { | ||||
| 	// 自分の投稿はwatchできない
 | ||||
| @@ -2,5 +2,5 @@ import User from '../../models/user'; | ||||
| import act from '../../remote/activitypub/act'; | ||||
|  | ||||
| export default ({ data }, done) => User.findOne({ _id: data.actor }) | ||||
| 	.then(actor => act(actor, data.outbox)) | ||||
| 	.then(actor => act(actor, data.outbox, data.distribute)) | ||||
| 	.then(() => done(), done); | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import create from '../create'; | ||||
|  | ||||
| export default (resolver, actor, activity) => { | ||||
| export default (resolver, actor, activity, distribute) => { | ||||
| 	if ('actor' in activity && actor.account.uri !== activity.actor) { | ||||
| 		throw new Error(); | ||||
| 	} | ||||
|  | ||||
| 	return create(resolver, actor, activity.object); | ||||
| 	return create(resolver, actor, activity.object, distribute); | ||||
| }; | ||||
|   | ||||
| @@ -2,10 +2,10 @@ import create from './create'; | ||||
| import createObject from '../create'; | ||||
| import Resolver from '../resolver'; | ||||
|  | ||||
| export default (actor, value) => { | ||||
| export default (actor, value, distribute) => { | ||||
| 	return new Resolver().resolve(value).then(resolved => Promise.all(resolved.map(async promisedResult => { | ||||
| 		const { resolver, object } = await promisedResult; | ||||
| 		const created = await (await createObject(resolver, actor, [object]))[0]; | ||||
| 		const created = await (await createObject(resolver, actor, [object], distribute))[0]; | ||||
|  | ||||
| 		if (created !== null) { | ||||
| 			return created; | ||||
| @@ -13,7 +13,7 @@ export default (actor, value) => { | ||||
|  | ||||
| 		switch (object.type) { | ||||
| 		case 'Create': | ||||
| 			return create(resolver, actor, object); | ||||
| 			return create(resolver, actor, object, distribute); | ||||
|  | ||||
| 		default: | ||||
| 			return null; | ||||
|   | ||||
| @@ -1,8 +1,11 @@ | ||||
| import { JSDOM } from 'jsdom'; | ||||
| import config from '../../config'; | ||||
| import Post from '../../models/post'; | ||||
| import { pack as packPost } from '../../models/post'; | ||||
| import RemoteUserObject, { IRemoteUserObject } from '../../models/remote-user-object'; | ||||
| import { IRemoteUser } from '../../models/user'; | ||||
| import uploadFromUrl from '../../drive/upload-from-url'; | ||||
| import createPost from '../../post/create'; | ||||
| import distributePost from '../../post/distribute'; | ||||
| import Resolver from './resolver'; | ||||
| const createDOMPurify = require('dompurify'); | ||||
|  | ||||
| @@ -16,28 +19,37 @@ function createRemoteUserObject($ref, $id, { id }) { | ||||
| 	return RemoteUserObject.insert({ uri: id, object }); | ||||
| } | ||||
|  | ||||
| async function createImage(actor, object) { | ||||
| 	if ('attributedTo' in object && actor.account.uri !== object.attributedTo) { | ||||
| class Creator { | ||||
| 	private actor: IRemoteUser; | ||||
| 	private distribute: boolean; | ||||
|  | ||||
| 	constructor(actor, distribute) { | ||||
| 		this.actor = actor; | ||||
| 		this.distribute = distribute; | ||||
| 	} | ||||
|  | ||||
| 	private async createImage(object) { | ||||
| 		if ('attributedTo' in object && this.actor.account.uri !== object.attributedTo) { | ||||
| 			throw new Error(); | ||||
| 		} | ||||
|  | ||||
| 	const { _id } = await uploadFromUrl(object.url, actor); | ||||
| 		const { _id } = await uploadFromUrl(object.url, this.actor); | ||||
| 		return createRemoteUserObject('driveFiles.files', _id, object); | ||||
| } | ||||
| 	} | ||||
|  | ||||
| async function createNote(resolver, actor, object) { | ||||
| 	if ('attributedTo' in object && actor.account.uri !== object.attributedTo) { | ||||
| 	private async createNote(resolver, object) { | ||||
| 		if ('attributedTo' in object && this.actor.account.uri !== object.attributedTo) { | ||||
| 			throw new Error(); | ||||
| 		} | ||||
|  | ||||
| 		const mediaIds = 'attachment' in object && | ||||
| 		(await Promise.all(await create(resolver, actor, object.attachment))) | ||||
| 			(await Promise.all(await this.create(resolver, object.attachment))) | ||||
| 				.filter(media => media !== null && media.object.$ref === 'driveFiles.files') | ||||
| 				.map(({ object }) => object.$id); | ||||
|  | ||||
| 		const { window } = new JSDOM(object.content); | ||||
|  | ||||
| 	const { _id } = await Post.insert({ | ||||
| 		const inserted = await createPost({ | ||||
| 			channelId: undefined, | ||||
| 			index: undefined, | ||||
| 			createdAt: new Date(object.published), | ||||
| @@ -47,41 +59,58 @@ async function createNote(resolver, actor, object) { | ||||
| 			poll: undefined, | ||||
| 			text: window.document.body.textContent, | ||||
| 			textHtml: object.content && createDOMPurify(window).sanitize(object.content), | ||||
| 		userId: actor._id, | ||||
| 			userId: this.actor._id, | ||||
| 			appId: null, | ||||
| 			viaMobile: false, | ||||
| 			geo: undefined | ||||
| 	}); | ||||
| 		}, null, null, []); | ||||
|  | ||||
| 		const promisedRemoteUserObject = createRemoteUserObject('posts', inserted._id, object); | ||||
| 		const promises = []; | ||||
|  | ||||
| 		if (this.distribute) { | ||||
| 			promises.push(distributePost(this.actor, inserted.mentions, packPost(inserted))); | ||||
| 		} | ||||
|  | ||||
| 		// Register to search database | ||||
| 		if (object.content && config.elasticsearch.enable) { | ||||
| 			const es = require('../../db/elasticsearch'); | ||||
|  | ||||
| 			promises.push(new Promise((resolve, reject) => { | ||||
| 				es.index({ | ||||
| 					index: 'misskey', | ||||
| 					type: 'post', | ||||
| 			id: _id.toString(), | ||||
| 					id: inserted._id.toString(), | ||||
| 					body: { | ||||
| 						text: window.document.body.textContent | ||||
| 					} | ||||
| 		}); | ||||
| 				}, resolve); | ||||
| 			})); | ||||
| 		} | ||||
|  | ||||
| 	return createRemoteUserObject('posts', _id, object); | ||||
| } | ||||
| 		await Promise.all(promises); | ||||
|  | ||||
| export default async function create(parentResolver: Resolver, actor, value): Promise<Array<Promise<IRemoteUserObject>>> { | ||||
| 		return promisedRemoteUserObject; | ||||
| 	} | ||||
|  | ||||
| 	public async create(parentResolver, value): Promise<Array<Promise<IRemoteUserObject>>> { | ||||
| 		const results = await parentResolver.resolveRemoteUserObjects(value); | ||||
|  | ||||
| 		return results.map(promisedResult => promisedResult.then(({ resolver, object }) => { | ||||
| 			switch (object.type) { | ||||
| 			case 'Image': | ||||
| 			return createImage(actor, object); | ||||
| 				return this.createImage(object); | ||||
|  | ||||
| 			case 'Note': | ||||
| 			return createNote(resolver, actor, object); | ||||
| 				return this.createNote(resolver, object); | ||||
| 			} | ||||
|  | ||||
| 			return null; | ||||
| 		})); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| export default (resolver: Resolver, actor, value, distribute?: boolean) => { | ||||
| 	const creator = new Creator(actor, distribute); | ||||
| 	return creator.create(resolver, value); | ||||
| }; | ||||
|   | ||||
| @@ -52,10 +52,10 @@ export default async (value, usernameLower, hostLower, acctLower) => { | ||||
| 		bannerId: null, | ||||
| 		createdAt: Date.parse(object.published), | ||||
| 		description: summaryDOM.textContent, | ||||
| 		followersCount: followers.totalItem, | ||||
| 		followingCount: following.totalItem, | ||||
| 		followersCount: followers ? followers.totalItem || 0 : 0, | ||||
| 		followingCount: following ? following.totalItem || 0 : 0, | ||||
| 		name: object.name, | ||||
| 		postsCount: outbox.totalItem, | ||||
| 		postsCount: outbox ? outbox.totalItem || 0 : 0, | ||||
| 		driveCapacity: 1024 * 1024 * 8, // 8MiB | ||||
| 		username: object.preferredUsername, | ||||
| 		usernameLower, | ||||
|   | ||||
| @@ -6,10 +6,14 @@ import queue from '../../queue'; | ||||
| import parseAcct from '../../acct/parse'; | ||||
|  | ||||
| const app = express(); | ||||
| app.disable('x-powered-by'); | ||||
| app.use(bodyParser.json()); | ||||
|  | ||||
| app.post('/@:user/inbox', async (req, res) => { | ||||
| app.disable('x-powered-by'); | ||||
|  | ||||
| app.post('/@:user/inbox', bodyParser.json({ | ||||
| 	type() { | ||||
| 		return true; | ||||
| 	} | ||||
| }), async (req, res) => { | ||||
| 	let parsed; | ||||
|  | ||||
| 	req.headers.authorization = 'Signature ' + req.headers.signature; | ||||
| @@ -51,6 +55,7 @@ app.post('/@:user/inbox', async (req, res) => { | ||||
| 		type: 'performActivityPub', | ||||
| 		actor: user._id, | ||||
| 		outbox: req.body, | ||||
| 		distribute: true, | ||||
| 	}).save(); | ||||
|  | ||||
| 	return res.status(202).end(); | ||||
|   | ||||
| @@ -3,24 +3,16 @@ | ||||
|  */ | ||||
| import $ from 'cafy'; | ||||
| import deepEqual = require('deep-equal'); | ||||
| import renderAcct from '../../../../acct/render'; | ||||
| import config from '../../../../config'; | ||||
| import html from '../../../../text/html'; | ||||
| import parse from '../../../../text/parse'; | ||||
| import Post, { IPost, isValidText, isValidCw } from '../../../../models/post'; | ||||
| import User, { ILocalUser } from '../../../../models/user'; | ||||
| import Post, { IPost, isValidText, isValidCw, pack } from '../../../../models/post'; | ||||
| import { ILocalUser } from '../../../../models/user'; | ||||
| import Channel, { IChannel } from '../../../../models/channel'; | ||||
| import Following from '../../../../models/following'; | ||||
| import Mute from '../../../../models/mute'; | ||||
| import DriveFile from '../../../../models/drive-file'; | ||||
| import Watching from '../../../../models/post-watching'; | ||||
| import ChannelWatching from '../../../../models/channel-watching'; | ||||
| import { pack } from '../../../../models/post'; | ||||
| import watch from '../../common/watch-post'; | ||||
| import stream, { publishChannelStream } from '../../../../publishers/stream'; | ||||
| import notify from '../../../../publishers/notify'; | ||||
| import pushSw from '../../../../publishers/push-sw'; | ||||
| import getAcct from '../../../../acct/render'; | ||||
| import parseAcct from '../../../../acct/parse'; | ||||
| import config from '../../../../config'; | ||||
| import create from '../../../../post/create'; | ||||
| import distribute from '../../../../post/distribute'; | ||||
|  | ||||
| /** | ||||
|  * Create a post | ||||
| @@ -251,226 +243,7 @@ module.exports = (params, user: ILocalUser, app) => new Promise(async (res, rej) | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	// 投稿を作成 | ||||
| 	const post = await Post.insert({ | ||||
| 		createdAt: new Date(), | ||||
| 		channelId: channel ? channel._id : undefined, | ||||
| 		index: channel ? channel.index + 1 : undefined, | ||||
| 		mediaIds: files ? files.map(file => file._id) : [], | ||||
| 		replyId: reply ? reply._id : undefined, | ||||
| 		repostId: repost ? repost._id : undefined, | ||||
| 		poll: poll, | ||||
| 		text: text, | ||||
| 		textHtml: tokens === null ? null : html(tokens), | ||||
| 		cw: cw, | ||||
| 		tags: tags, | ||||
| 		userId: user._id, | ||||
| 		appId: app ? app._id : null, | ||||
| 		viaMobile: viaMobile, | ||||
| 		geo, | ||||
|  | ||||
| 		// 以下非正規化データ | ||||
| 		_reply: reply ? { userId: reply.userId } : undefined, | ||||
| 		_repost: repost ? { userId: repost.userId } : undefined, | ||||
| 	}); | ||||
|  | ||||
| 	// Serialize | ||||
| 	const postObj = await pack(post); | ||||
|  | ||||
| 	// Reponse | ||||
| 	res({ | ||||
| 		createdPost: postObj | ||||
| 	}); | ||||
|  | ||||
| 	//#region Post processes | ||||
|  | ||||
| 	User.update({ _id: user._id }, { | ||||
| 		$set: { | ||||
| 			latestPost: post | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	const mentions = []; | ||||
|  | ||||
| 	async function addMention(mentionee, reason) { | ||||
| 		// Reject if already added | ||||
| 		if (mentions.some(x => x.equals(mentionee))) return; | ||||
|  | ||||
| 		// Add mention | ||||
| 		mentions.push(mentionee); | ||||
|  | ||||
| 		// Publish event | ||||
| 		if (!user._id.equals(mentionee)) { | ||||
| 			const mentioneeMutes = await Mute.find({ | ||||
| 				muterId: mentionee, | ||||
| 				deletedAt: { $exists: false } | ||||
| 			}); | ||||
| 			const mentioneesMutedUserIds = mentioneeMutes.map(m => m.muteeId.toString()); | ||||
| 			if (mentioneesMutedUserIds.indexOf(user._id.toString()) == -1) { | ||||
| 				stream(mentionee, reason, postObj); | ||||
| 				pushSw(mentionee, reason, postObj); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// タイムラインへの投稿 | ||||
| 	if (!channel) { | ||||
| 		// Publish event to myself's stream | ||||
| 		stream(user._id, 'post', postObj); | ||||
|  | ||||
| 		// Fetch all followers | ||||
| 		const followers = await Following | ||||
| 			.find({ | ||||
| 				followeeId: user._id, | ||||
| 				// 削除されたドキュメントは除く | ||||
| 				deletedAt: { $exists: false } | ||||
| 			}, { | ||||
| 				followerId: true, | ||||
| 				_id: false | ||||
| 			}); | ||||
|  | ||||
| 		// Publish event to followers stream | ||||
| 		followers.forEach(following => | ||||
| 			stream(following.followerId, 'post', postObj)); | ||||
| 	} | ||||
|  | ||||
| 	// チャンネルへの投稿 | ||||
| 	if (channel) { | ||||
| 		// Increment channel index(posts count) | ||||
| 		Channel.update({ _id: channel._id }, { | ||||
| 			$inc: { | ||||
| 				index: 1 | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		// Publish event to channel | ||||
| 		publishChannelStream(channel._id, 'post', postObj); | ||||
|  | ||||
| 		// Get channel watchers | ||||
| 		const watches = await ChannelWatching.find({ | ||||
| 			channelId: channel._id, | ||||
| 			// 削除されたドキュメントは除く | ||||
| 			deletedAt: { $exists: false } | ||||
| 		}); | ||||
|  | ||||
| 		// チャンネルの視聴者(のタイムライン)に配信 | ||||
| 		watches.forEach(w => { | ||||
| 			stream(w.userId, 'post', postObj); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	// Increment my posts count | ||||
| 	User.update({ _id: user._id }, { | ||||
| 		$inc: { | ||||
| 			postsCount: 1 | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	// If has in reply to post | ||||
| 	if (reply) { | ||||
| 		// Increment replies count | ||||
| 		Post.update({ _id: reply._id }, { | ||||
| 			$inc: { | ||||
| 				repliesCount: 1 | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		// 自分自身へのリプライでない限りは通知を作成 | ||||
| 		notify(reply.userId, user._id, 'reply', { | ||||
| 			postId: post._id | ||||
| 		}); | ||||
|  | ||||
| 		// Fetch watchers | ||||
| 		Watching | ||||
| 			.find({ | ||||
| 				postId: reply._id, | ||||
| 				userId: { $ne: user._id }, | ||||
| 				// 削除されたドキュメントは除く | ||||
| 				deletedAt: { $exists: false } | ||||
| 			}, { | ||||
| 				fields: { | ||||
| 					userId: true | ||||
| 				} | ||||
| 			}) | ||||
| 			.then(watchers => { | ||||
| 				watchers.forEach(watcher => { | ||||
| 					notify(watcher.userId, user._id, 'reply', { | ||||
| 						postId: post._id | ||||
| 					}); | ||||
| 				}); | ||||
| 			}); | ||||
|  | ||||
| 		// この投稿をWatchする | ||||
| 		if (user.account.settings.autoWatch !== false) { | ||||
| 			watch(user._id, reply); | ||||
| 		} | ||||
|  | ||||
| 		// Add mention | ||||
| 		addMention(reply.userId, 'reply'); | ||||
| 	} | ||||
|  | ||||
| 	// If it is repost | ||||
| 	if (repost) { | ||||
| 		// Notify | ||||
| 		const type = text ? 'quote' : 'repost'; | ||||
| 		notify(repost.userId, user._id, type, { | ||||
| 			postId: post._id | ||||
| 		}); | ||||
|  | ||||
| 		// Fetch watchers | ||||
| 		Watching | ||||
| 			.find({ | ||||
| 				postId: repost._id, | ||||
| 				userId: { $ne: user._id }, | ||||
| 				// 削除されたドキュメントは除く | ||||
| 				deletedAt: { $exists: false } | ||||
| 			}, { | ||||
| 				fields: { | ||||
| 					userId: true | ||||
| 				} | ||||
| 			}) | ||||
| 			.then(watchers => { | ||||
| 				watchers.forEach(watcher => { | ||||
| 					notify(watcher.userId, user._id, type, { | ||||
| 						postId: post._id | ||||
| 					}); | ||||
| 				}); | ||||
| 			}); | ||||
|  | ||||
| 		// この投稿をWatchする | ||||
| 		// TODO: ユーザーが「Repostしたときに自動でWatchする」設定を | ||||
| 		//       オフにしていた場合はしない | ||||
| 		watch(user._id, repost); | ||||
|  | ||||
| 		// If it is quote repost | ||||
| 		if (text) { | ||||
| 			// Add mention | ||||
| 			addMention(repost.userId, 'quote'); | ||||
| 		} else { | ||||
| 			// Publish event | ||||
| 			if (!user._id.equals(repost.userId)) { | ||||
| 				stream(repost.userId, 'repost', postObj); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// 今までで同じ投稿をRepostしているか | ||||
| 		const existRepost = await Post.findOne({ | ||||
| 			userId: user._id, | ||||
| 			repostId: repost._id, | ||||
| 			_id: { | ||||
| 				$ne: post._id | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		if (!existRepost) { | ||||
| 			// Update repostee status | ||||
| 			Post.update({ _id: repost._id }, { | ||||
| 				$inc: { | ||||
| 					repostCount: 1 | ||||
| 				} | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| 	let atMentions = []; | ||||
|  | ||||
| 	// If has text content | ||||
| 	if (text) { | ||||
| @@ -486,40 +259,42 @@ module.exports = (params, user: ILocalUser, app) => new Promise(async (res, rej) | ||||
| 				registerHashtags(user, hashtags); | ||||
| 		*/ | ||||
| 		// Extract an '@' mentions | ||||
| 		const atMentions = tokens | ||||
| 		atMentions = tokens | ||||
| 			.filter(t => t.type == 'mention') | ||||
| 			.map(getAcct) | ||||
| 			.map(renderAcct) | ||||
| 			// Drop dupulicates | ||||
| 			.filter((v, i, s) => s.indexOf(v) == i); | ||||
|  | ||||
| 		// Resolve all mentions | ||||
| 		await Promise.all(atMentions.map(async (mention) => { | ||||
| 			// Fetch mentioned user | ||||
| 			// SELECT _id | ||||
| 			const mentionee = await User | ||||
| 				.findOne(parseAcct(mention), { _id: true }); | ||||
|  | ||||
| 			// When mentioned user not found | ||||
| 			if (mentionee == null) return; | ||||
|  | ||||
| 			// 既に言及されたユーザーに対する返信や引用repostの場合も無視 | ||||
| 			if (reply && reply.userId.equals(mentionee._id)) return; | ||||
| 			if (repost && repost.userId.equals(mentionee._id)) return; | ||||
|  | ||||
| 			// Add mention | ||||
| 			addMention(mentionee._id, 'mention'); | ||||
|  | ||||
| 			// Create notification | ||||
| 			notify(mentionee._id, user._id, 'mention', { | ||||
| 				postId: post._id | ||||
| 			}); | ||||
|  | ||||
| 			return; | ||||
| 		})); | ||||
| 	} | ||||
|  | ||||
| 	// 投稿を作成 | ||||
| 	const post = await create({ | ||||
| 		createdAt: new Date(), | ||||
| 		channelId: channel ? channel._id : undefined, | ||||
| 		index: channel ? channel.index + 1 : undefined, | ||||
| 		mediaIds: files ? files.map(file => file._id) : [], | ||||
| 		poll: poll, | ||||
| 		text: text, | ||||
| 		textHtml: tokens === null ? null : html(tokens), | ||||
| 		cw: cw, | ||||
| 		tags: tags, | ||||
| 		userId: user._id, | ||||
| 		appId: app ? app._id : null, | ||||
| 		viaMobile: viaMobile, | ||||
| 		geo | ||||
| 	}, reply, repost, atMentions); | ||||
|  | ||||
| 	// Serialize | ||||
| 	const postObj = await pack(post); | ||||
|  | ||||
| 	// Reponse | ||||
| 	res({ | ||||
| 		createdPost: postObj | ||||
| 	}); | ||||
|  | ||||
| 	distribute(user, post.mentions, postObj); | ||||
|  | ||||
| 	// Register to search database | ||||
| 	if (text && config.elasticsearch.enable) { | ||||
| 	if (post.text && config.elasticsearch.enable) { | ||||
| 		const es = require('../../../db/elasticsearch'); | ||||
|  | ||||
| 		es.index({ | ||||
| @@ -531,15 +306,4 @@ module.exports = (params, user: ILocalUser, app) => new Promise(async (res, rej) | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	// Append mentions data | ||||
| 	if (mentions.length > 0) { | ||||
| 		Post.update({ _id: post._id }, { | ||||
| 			$set: { | ||||
| 				mentions: mentions | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	//#endregion | ||||
| }); | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import $ from 'cafy'; | ||||
| import Vote from '../../../../../models/poll-vote'; | ||||
| import Post from '../../../../../models/post'; | ||||
| import Watching from '../../../../../models/post-watching'; | ||||
| import watch from '../../../common/watch-post'; | ||||
| import watch from '../../../../../post/watch'; | ||||
| import { publishPostStream } from '../../../../../publishers/stream'; | ||||
| import notify from '../../../../../publishers/notify'; | ||||
|  | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import Reaction from '../../../../../models/post-reaction'; | ||||
| import Post, { pack as packPost } from '../../../../../models/post'; | ||||
| import { pack as packUser } from '../../../../../models/user'; | ||||
| import Watching from '../../../../../models/post-watching'; | ||||
| import watch from '../../../common/watch-post'; | ||||
| import watch from '../../../../../post/watch'; | ||||
| import { publishPostStream } from '../../../../../publishers/stream'; | ||||
| import notify from '../../../../../publishers/notify'; | ||||
| import pushSw from '../../../../../publishers/push-sw'; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo