From 50d56bdc2585a3bbd4ad996426f62d6ed186a34d Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 17 Apr 2018 22:31:52 +0900 Subject: Clean up --- src/services/note/create.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'src/services/note/create.ts') diff --git a/src/services/note/create.ts b/src/services/note/create.ts index e35e5ecfbd..391a3d7647 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -89,14 +89,10 @@ export default async (user: IUser, data: { res(note); + // Increment notes count User.update({ _id: user._id }, { - // Increment notes count $inc: { notesCount: 1 - }, - // Update latest note - $set: { - latestNote: note } }); -- cgit v1.2.3-freya From 772bc243af212730d917b8763e7f59687b00d38c Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 18 Apr 2018 14:53:17 +0900 Subject: :v: --- src/services/note/create.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'src/services/note/create.ts') diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 391a3d7647..599126b861 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -1,3 +1,4 @@ +import { MongoError } from 'mongodb'; import Note, { pack, INote } from '../../models/note'; import User, { isLocalUser, IUser, isRemoteUser } from '../../models/user'; import stream, { publishLocalTimelineStream, publishGlobalTimelineStream } from '../../publishers/stream'; @@ -85,7 +86,18 @@ export default async (user: IUser, data: { if (data.uri != null) insert.uri = data.uri; // 投稿を作成 - const note = await Note.insert(insert); + let note: INote; + try { + note = await Note.insert(insert); + } catch (e) { + // duplicate key error + if (e instanceof MongoError && e.code === 11000) { + return res(null); + } + + console.error(e); + return rej('something happened'); + } res(note); -- cgit v1.2.3-freya From 02fd46f64f6fb18e812acda523224011b405d546 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 19 Apr 2018 09:17:42 +0900 Subject: Fix bug --- src/remote/activitypub/models/note.ts | 5 +++-- src/services/note/create.ts | 3 +-- src/services/note/reaction/create.ts | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) (limited to 'src/services/note/create.ts') diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index c947243664..94d723e239 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -47,7 +47,8 @@ export async function createNote(value: any, resolver?: Resolver, silent = false const object = await resolver.resolve(value) as any; if (object == null || object.type !== 'Note') { - throw new Error('invalid note'); + log(`invalid note: ${object}`); + return null; } const note: INoteActivityStreamsObject = object; @@ -62,7 +63,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) visibility = 'unlisted'; if (note.cc.length == 0) visibility = 'private'; // TODO - if (visibility != 'public') throw new Error('unspported visibility'); + if (visibility != 'public') return null; //#endergion // 添付メディア diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 599126b861..bdef5e09fc 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -1,4 +1,3 @@ -import { MongoError } from 'mongodb'; import Note, { pack, INote } from '../../models/note'; import User, { isLocalUser, IUser, isRemoteUser } from '../../models/user'; import stream, { publishLocalTimelineStream, publishGlobalTimelineStream } from '../../publishers/stream'; @@ -91,7 +90,7 @@ export default async (user: IUser, data: { note = await Note.insert(insert); } catch (e) { // duplicate key error - if (e instanceof MongoError && e.code === 11000) { + if (e.code === 11000) { return res(null); } diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts index dd3d4be8b7..dcfbe481af 100644 --- a/src/services/note/reaction/create.ts +++ b/src/services/note/reaction/create.ts @@ -9,7 +9,6 @@ import watch from '../watch'; import renderLike from '../../../remote/activitypub/renderer/like'; import { deliver } from '../../../queue'; import pack from '../../../remote/activitypub/renderer'; -import { MongoError } from 'mongodb'; export default async (user: IUser, note: INote, reaction: string) => new Promise(async (res, rej) => { // Myself @@ -27,8 +26,8 @@ export default async (user: IUser, note: INote, reaction: string) => new Promise }); } catch (e) { // duplicate key error - if (e instanceof MongoError && e.code === 11000) { - return rej('already reacted'); + if (e.code === 11000) { + return res(null); } console.error(e); -- cgit v1.2.3-freya From 0d5bc3be6611a6877a71233ac57c0987027c0398 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 19 Apr 2018 12:43:25 +0900 Subject: ストーキング実装 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1511 --- locales/en.yml | 3 ++ locales/fr.yml | 3 ++ locales/ja.yml | 3 ++ migration/2018-04-19.js | 49 ++++++++++++++++++ .../app/desktop/views/pages/user/user.profile.vue | 30 ++++++++++- src/models/following.ts | 11 ++++ src/models/note.ts | 5 +- src/models/user.ts | 43 ++++++++-------- src/server/api/common/get-friends.ts | 29 +++++++++-- src/server/api/endpoints.ts | 18 +++++++ src/server/api/endpoints/following/stalk.ts | 36 +++++++++++++ src/server/api/endpoints/following/unstalk.ts | 35 +++++++++++++ src/server/api/endpoints/i/notifications.ts | 4 +- src/server/api/endpoints/mute/list.ts | 4 +- src/server/api/endpoints/notes/mentions.ts | 4 +- src/server/api/endpoints/notes/search.ts | 4 +- src/server/api/endpoints/notes/timeline.ts | 60 +++++++++++++++------- src/server/api/endpoints/users/followers.ts | 4 +- src/server/api/endpoints/users/following.ts | 4 +- src/server/api/endpoints/users/recommendation.ts | 4 +- src/services/following/create.ts | 13 ++++- src/services/note/create.ts | 24 +++------ 22 files changed, 310 insertions(+), 80 deletions(-) create mode 100644 migration/2018-04-19.js create mode 100644 src/server/api/endpoints/following/stalk.ts create mode 100644 src/server/api/endpoints/following/unstalk.ts (limited to 'src/services/note/create.ts') diff --git a/locales/en.yml b/locales/en.yml index 9388aedbf8..4eb0a3446c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -419,6 +419,9 @@ desktop/views/pages/user/user.photos.vue: desktop/views/pages/user/user.profile.vue: follows-you: "Follows you" + stalk: "Stalk" + stalking: "Stalking" + unstalk: "Unstalk" mute: "Mute" muted: "Muting" unmute: "Unmute" diff --git a/locales/fr.yml b/locales/fr.yml index cd756194ab..0e0019e565 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -419,6 +419,9 @@ desktop/views/pages/user/user.photos.vue: desktop/views/pages/user/user.profile.vue: follows-you: "Vous suis" + stalk: "ストークする" + stalking: "ストーキングしています" + unstalk: "ストーク解除" mute: "Mettre en sourdine" muted: "Muting" unmute: "Enlever la sourdine" diff --git a/locales/ja.yml b/locales/ja.yml index 96b4a1d0ea..ed32deae57 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -419,6 +419,9 @@ desktop/views/pages/user/user.photos.vue: desktop/views/pages/user/user.profile.vue: follows-you: "フォローされています" + stalk: "ストークする" + stalking: "ストーキングしています" + unstalk: "ストーク解除" mute: "ミュートする" muted: "ミュートしています" unmute: "ミュート解除" diff --git a/migration/2018-04-19.js b/migration/2018-04-19.js new file mode 100644 index 0000000000..b0df22c009 --- /dev/null +++ b/migration/2018-04-19.js @@ -0,0 +1,49 @@ +// for Node.js interpret + +const { default: User } = require('../built/models/user'); +const { default: Following } = require('../built/models/following'); +const { default: zip } = require('@prezzemolo/zip') + +const migrate = async (following) => { + const follower = await User.findOne({ _id: following.followerId }); + const followee = await User.findOne({ _id: following.followeeId }); + const result = await Following.update(following._id, { + $set: { + stalk: true, + _follower: { + host: follower.host, + inbox: follower.host != null ? follower.inbox : undefined + }, + _followee: { + host: followee.host, + inbox: followee.host != null ? followee.inbox : undefined + } + } + }); + return result.ok === 1; +} + +async function main() { + const count = await Following.count({}); + + const dop = Number.parseInt(process.argv[2]) || 5 + const idop = ((count - (count % dop)) / dop) + 1 + + return zip( + 1, + async (time) => { + console.log(`${time} / ${idop}`) + const doc = await Following.find({}, { + limit: dop, skip: time * dop + }) + return Promise.all(doc.map(migrate)) + }, + idop + ).then(a => { + const rv = [] + a.forEach(e => rv.push(...e)) + return rv + }) +} + +main().then(console.dir).catch(console.error) diff --git a/src/client/app/desktop/views/pages/user/user.profile.vue b/src/client/app/desktop/views/pages/user/user.profile.vue index 72750e1b3d..774f300a38 100644 --- a/src/client/app/desktop/views/pages/user/user.profile.vue +++ b/src/client/app/desktop/views/pages/user/user.profile.vue @@ -3,8 +3,14 @@

%i18n:@follows-you%

-

%i18n:@muted% %i18n:@unmute%

-

%i18n:@mute%

+

+ %i18n:@stalking% %i18n:@unstalk% + %i18n:@stalk% +

+

+ %i18n:@muted% %i18n:@unmute% + %i18n:@mute% +

{{ user.description }}
@@ -47,6 +53,26 @@ export default Vue.extend({ }); }, + stalk() { + (this as any).api('following/stalk', { + userId: this.user.id + }).then(() => { + this.user.isStalking = true; + }, () => { + alert('error'); + }); + }, + + unstalk() { + (this as any).api('following/unstalk', { + userId: this.user.id + }).then(() => { + this.user.isStalking = false; + }, () => { + alert('error'); + }); + }, + mute() { (this as any).api('mute/create', { userId: this.user.id diff --git a/src/models/following.ts b/src/models/following.ts index f10e349ee9..4712379a70 100644 --- a/src/models/following.ts +++ b/src/models/following.ts @@ -10,6 +10,17 @@ export type IFollowing = { createdAt: Date; followeeId: mongo.ObjectID; followerId: mongo.ObjectID; + stalk: boolean; + + // 非正規化 + _followee: { + host: string; + inbox?: string; + }, + _follower: { + host: string; + inbox?: string; + } }; /** diff --git a/src/models/note.ts b/src/models/note.ts index 3059593540..d4b16afa4c 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -58,6 +58,7 @@ export type INote = { }; uri: string; + // 非正規化 _reply?: { userId: mongo.ObjectID; }; @@ -66,9 +67,7 @@ export type INote = { }; _user: { host: string; - account: { - inbox?: string; - }; + inbox?: string; }; }; diff --git a/src/models/user.ts b/src/models/user.ts index bcb2a73e24..ca1ca28937 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -5,7 +5,7 @@ import db from '../db/mongodb'; import Note, { pack as packNote, deleteNote } from './note'; import Following, { deleteFollowing } from './following'; import Mute, { deleteMute } from './mute'; -import getFriends from '../server/api/common/get-friends'; +import { getFriendIds } from '../server/api/common/get-friends'; import config from '../config'; import AccessToken, { deleteAccessToken } from './access-token'; import NoteWatching, { deleteNoteWatching } from './note-watching'; @@ -375,33 +375,30 @@ export const pack = ( } if (meId && !meId.equals(_user.id)) { - // Whether the user is following - _user.isFollowing = (async () => { - const follow = await Following.findOne({ + const [following1, following2, mute] = await Promise.all([ + Following.findOne({ followerId: meId, followeeId: _user.id - }); - return follow !== null; - })(); - - // Whether the user is followed - _user.isFollowed = (async () => { - const follow2 = await Following.findOne({ + }), + Following.findOne({ followerId: _user.id, followeeId: meId - }); - return follow2 !== null; - })(); + }), + Mute.findOne({ + muterId: meId, + muteeId: _user.id + }) + ]); + + // Whether the user is following + _user.isFollowing = following1 !== null; + _user.isStalking = following1 && following1.stalk; + + // Whether the user is followed + _user.isFollowed = following2 !== null; // Whether the user is muted - _user.isMuted = (async () => { - const mute = await Mute.findOne({ - muterId: meId, - muteeId: _user.id, - deletedAt: { $exists: false } - }); - return mute !== null; - })(); + _user.isMuted = mute !== null; } if (opts.detail) { @@ -413,7 +410,7 @@ export const pack = ( } if (meId && !meId.equals(_user.id)) { - const myFollowingIds = await getFriends(meId); + const myFollowingIds = await getFriendIds(meId); // Get following you know count _user.followingYouKnowCount = Following.count({ diff --git a/src/server/api/common/get-friends.ts b/src/server/api/common/get-friends.ts index c1cc3957d8..50ba71ea96 100644 --- a/src/server/api/common/get-friends.ts +++ b/src/server/api/common/get-friends.ts @@ -1,10 +1,10 @@ import * as mongodb from 'mongodb'; import Following from '../../../models/following'; -export default async (me: mongodb.ObjectID, includeMe: boolean = true) => { +export const getFriendIds = async (me: mongodb.ObjectID, includeMe = true) => { // Fetch relation to other users who the I follows // SELECT followee - const myfollowing = await Following + const followings = await Following .find({ followerId: me }, { @@ -14,7 +14,7 @@ export default async (me: mongodb.ObjectID, includeMe: boolean = true) => { }); // ID list of other users who the I follows - const myfollowingIds = myfollowing.map(follow => follow.followeeId); + const myfollowingIds = followings.map(following => following.followeeId); if (includeMe) { myfollowingIds.push(me); @@ -22,3 +22,26 @@ export default async (me: mongodb.ObjectID, includeMe: boolean = true) => { return myfollowingIds; }; + +export const getFriends = async (me: mongodb.ObjectID, includeMe = true) => { + // Fetch relation to other users who the I follows + const followings = await Following + .find({ + followerId: me + }); + + // ID list of other users who the I follows + const myfollowings = followings.map(following => ({ + id: following.followeeId, + stalk: following.stalk + })); + + if (includeMe) { + myfollowings.push({ + id: me, + stalk: true + }); + } + + return myfollowings; +}; diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts index e0223c23e0..7cf49debe9 100644 --- a/src/server/api/endpoints.ts +++ b/src/server/api/endpoints.ts @@ -426,6 +426,24 @@ const endpoints: Endpoint[] = [ }, kind: 'following-write' }, + { + name: 'following/stalk', + withCredential: true, + limit: { + duration: ms('1hour'), + max: 100 + }, + kind: 'following-write' + }, + { + name: 'following/unstalk', + withCredential: true, + limit: { + duration: ms('1hour'), + max: 100 + }, + kind: 'following-write' + }, { name: 'notes' diff --git a/src/server/api/endpoints/following/stalk.ts b/src/server/api/endpoints/following/stalk.ts new file mode 100644 index 0000000000..fc8be4924d --- /dev/null +++ b/src/server/api/endpoints/following/stalk.ts @@ -0,0 +1,36 @@ +import $ from 'cafy'; +import Following from '../../../../models/following'; +import { isLocalUser } from '../../../../models/user'; + +/** + * Stalk a user + */ +module.exports = (params, user) => new Promise(async (res, rej) => { + const follower = user; + + // Get 'userId' parameter + const [userId, userIdErr] = $(params.userId).id().$; + if (userIdErr) return rej('invalid userId param'); + + // Fetch following + const following = await Following.findOne({ + followerId: follower._id, + followeeId: userId + }); + + if (following === null) { + return rej('following not found'); + } + + // Stalk + await Following.update({ _id: following._id }, { + $set: { + stalk: true + } + }); + + // Send response + res(); + + // TODO: イベント +}); diff --git a/src/server/api/endpoints/following/unstalk.ts b/src/server/api/endpoints/following/unstalk.ts new file mode 100644 index 0000000000..d7593bcd00 --- /dev/null +++ b/src/server/api/endpoints/following/unstalk.ts @@ -0,0 +1,35 @@ +import $ from 'cafy'; +import Following from '../../../../models/following'; + +/** + * Unstalk a user + */ +module.exports = (params, user) => new Promise(async (res, rej) => { + const follower = user; + + // Get 'userId' parameter + const [userId, userIdErr] = $(params.userId).id().$; + if (userIdErr) return rej('invalid userId param'); + + // Fetch following + const following = await Following.findOne({ + followerId: follower._id, + followeeId: userId + }); + + if (following === null) { + return rej('following not found'); + } + + // Stalk + await Following.update({ _id: following._id }, { + $set: { + stalk: false + } + }); + + // Send response + res(); + + // TODO: イベント +}); diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index 3b4899682d..69a8910898 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -5,7 +5,7 @@ import $ from 'cafy'; import Notification from '../../../../models/notification'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/notification'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; import read from '../../common/read-notification'; /** @@ -62,7 +62,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { if (following) { // ID list of the user itself and other users who the user follows - const followingIds = await getFriends(user._id); + const followingIds = await getFriendIds(user._id); query.$and.push({ notifierId: { diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts index bd80401445..0b8262d6c5 100644 --- a/src/server/api/endpoints/mute/list.ts +++ b/src/server/api/endpoints/mute/list.ts @@ -4,7 +4,7 @@ import $ from 'cafy'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/user'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; /** * Get muted users of a user @@ -34,7 +34,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { if (iknow) { // Get my friends - const myFriends = await getFriends(me._id); + const myFriends = await getFriendIds(me._id); query.muteeId = { $in: myFriends diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index c507acbaec..2d95606b3f 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -3,7 +3,7 @@ */ import $ from 'cafy'; import Note from '../../../../models/note'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; import { pack } from '../../../../models/note'; /** @@ -46,7 +46,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { }; if (following) { - const followingIds = await getFriends(user._id); + const followingIds = await getFriendIds(user._id); query.userId = { $in: followingIds diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts index bfa17b000e..3ff3fbbafa 100644 --- a/src/server/api/endpoints/notes/search.ts +++ b/src/server/api/endpoints/notes/search.ts @@ -6,7 +6,7 @@ const escapeRegexp = require('escape-regexp'); import Note from '../../../../models/note'; import User from '../../../../models/user'; import Mute from '../../../../models/mute'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; import { pack } from '../../../../models/note'; /** @@ -156,7 +156,7 @@ async function search( } if (following != null && me != null) { - const ids = await getFriends(me._id, false); + const ids = await getFriendIds(me._id, false); push({ userId: following ? { $in: ids diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index b5feaac817..8cd23fd369 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -2,11 +2,10 @@ * Module dependencies */ import $ from 'cafy'; -import rap from '@prezzemolo/rap'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import ChannelWatching from '../../../../models/channel-watching'; -import getFriends from '../../common/get-friends'; +import { getFriends } from '../../common/get-friends'; import { pack } from '../../../../models/note'; /** @@ -38,41 +37,66 @@ module.exports = async (params, user, app) => { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } - const { followingIds, watchingChannelIds, mutedUserIds } = await rap({ - // ID list of the user itself and other users who the user follows - followingIds: getFriends(user._id), + const [followings, watchingChannelIds, mutedUserIds] = await Promise.all([ + // フォローを取得 + // Fetch following + getFriends(user._id), // Watchしているチャンネルを取得 - watchingChannelIds: ChannelWatching.find({ + ChannelWatching.find({ userId: user._id, // 削除されたドキュメントは除く deletedAt: { $exists: false } }).then(watches => watches.map(w => w.channelId)), // ミュートしているユーザーを取得 - mutedUserIds: Mute.find({ + Mute.find({ muterId: user._id }).then(ms => ms.map(m => m.muteeId)) - }); + ]); //#region Construct query const sort = { _id: -1 }; + const followQuery = followings.map(f => f.stalk ? { + userId: f.id + } : { + userId: f.id, + + // ストーキングしてないならリプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める) + $or: [{ + // リプライでない + replyId: null + }, { // または + // リプライだが返信先が投稿者自身の投稿 + $expr: { + '$_reply.userId': '$userId' + } + }, { // または + // リプライだが返信先が自分(フォロワー)の投稿 + '_reply.userId': user._id + }, { // または + // 自分(フォロワー)が送信したリプライ + userId: user._id + }] + }); + const query = { $or: [{ - // フォローしている人のタイムラインへの投稿 - userId: { - $in: followingIds - }, - // 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る - $or: [{ - channelId: { - $exists: false - } + $and: [{ + // フォローしている人のタイムラインへの投稿 + $or: followQuery }, { - channelId: null + // 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る + $or: [{ + channelId: { + $exists: false + } + }, { + channelId: null + }] }] }, { // Watchしているチャンネルへの投稿 diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts index 0222313e81..5f03326be8 100644 --- a/src/server/api/endpoints/users/followers.ts +++ b/src/server/api/endpoints/users/followers.ts @@ -5,7 +5,7 @@ import $ from 'cafy'; import User from '../../../../models/user'; import Following from '../../../../models/following'; import { pack } from '../../../../models/user'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; /** * Get followers of a user @@ -52,7 +52,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { // ログインしていてかつ iknow フラグがあるとき if (me && iknow) { // Get my friends - const myFriends = await getFriends(me._id); + const myFriends = await getFriendIds(me._id); query.followerId = { $in: myFriends diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts index 2372f57fbe..9fb135b24d 100644 --- a/src/server/api/endpoints/users/following.ts +++ b/src/server/api/endpoints/users/following.ts @@ -5,7 +5,7 @@ import $ from 'cafy'; import User from '../../../../models/user'; import Following from '../../../../models/following'; import { pack } from '../../../../models/user'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; /** * Get following users of a user @@ -52,7 +52,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { // ログインしていてかつ iknow フラグがあるとき if (me && iknow) { // Get my friends - const myFriends = await getFriends(me._id); + const myFriends = await getFriendIds(me._id); query.followeeId = { $in: myFriends diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index 2a6d36b75b..f72bb04bf1 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -4,7 +4,7 @@ const ms = require('ms'); import $ from 'cafy'; import User, { pack } from '../../../../models/user'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; import Mute from '../../../../models/mute'; /** @@ -24,7 +24,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { if (offsetErr) return rej('invalid offset param'); // ID list of the user itself and other users who the user follows - const followingIds = await getFriends(me._id); + const followingIds = await getFriendIds(me._id); // ミュートしているユーザーを取得 const mutedUserIds = (await Mute.find({ diff --git a/src/services/following/create.ts b/src/services/following/create.ts index 375b028912..3424c55dae 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -13,7 +13,18 @@ export default async function(follower: IUser, followee: IUser, activity?) { const following = await Following.insert({ createdAt: new Date(), followerId: follower._id, - followeeId: followee._id + followeeId: followee._id, + stalk: true, + + // 非正規化 + _follower: { + host: follower.host, + inbox: isRemoteUser(follower) ? follower.inbox : undefined + }, + _followee: { + host: followee.host, + inbox: isRemoteUser(followee) ? followee.inbox : undefined + } }); //#region Increment following count diff --git a/src/services/note/create.ts b/src/services/note/create.ts index bdef5e09fc..32db77011f 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -124,19 +124,8 @@ export default async (user: IUser, data: { publishGlobalTimelineStream(noteObj); // Fetch all followers - const followers = await Following.aggregate([{ - $lookup: { - from: 'users', - localField: 'followerId', - foreignField: '_id', - as: 'user' - } - }, { - $match: { - followeeId: note.userId - } - }], { - _id: false + const followers = await Following.find({ + followeeId: note.userId }); if (!silent) { @@ -157,12 +146,15 @@ export default async (user: IUser, data: { deliver(user, await render(), data.renote._user.inbox); } - Promise.all(followers.map(async follower => { - follower = follower.user[0]; + Promise.all(followers.map(async following => { + const follower = following._follower; if (isLocalUser(follower)) { + // この投稿が返信かつstalkフォローでないならスキップ + if (note.replyId && !following.stalk) return; + // Publish event to followers stream - stream(follower._id, 'note', noteObj); + stream(following.followerId, 'note', noteObj); } else { // フォロワーがリモートユーザーかつ投稿者がローカルユーザーなら投稿を配信 if (isLocalUser(user)) { -- cgit v1.2.3-freya From 6ea168485a31caab5d289fa6d78b975b147402ec Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 19 Apr 2018 14:32:00 +0900 Subject: Fix bug --- src/services/note/create.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/services/note/create.ts') diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 32db77011f..ca01b557d6 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -150,8 +150,11 @@ export default async (user: IUser, data: { const follower = following._follower; if (isLocalUser(follower)) { - // この投稿が返信かつstalkフォローでないならスキップ - if (note.replyId && !following.stalk) return; + // ストーキングしていない場合 + if (!following.stalk) { + // この投稿が返信ならスキップ + if (note.replyId && !note._reply.userId.equals(following.followerId) && !note._reply.userId.equals(note.userId)) return; + } // Publish event to followers stream stream(following.followerId, 'note', noteObj); -- cgit v1.2.3-freya From 20e77196f28178c869402985761bd4c2fa74bf0a Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 22 Apr 2018 10:44:17 +0900 Subject: AP: 投票をレンダリング MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/app/desktop/views/components/notes.note.vue | 2 +- src/client/docs/api/entities/note.yaml | 6 ------ src/client/docs/api/entities/post.yaml | 6 ------ src/models/messaging-message.ts | 1 - src/models/note.ts | 1 - src/remote/activitypub/kernel/delete/note.ts | 1 - src/remote/activitypub/misc/get-note-html.ts | 18 ++++++++++++++++++ src/remote/activitypub/renderer/note.ts | 3 ++- src/server/api/endpoints/messaging/messages/create.ts | 3 --- src/services/note/create.ts | 2 -- 10 files changed, 21 insertions(+), 22 deletions(-) create mode 100644 src/remote/activitypub/misc/get-note-html.ts (limited to 'src/services/note/create.ts') diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue index 1352fc4ee7..ae2793db92 100644 --- a/src/client/app/desktop/views/components/notes.note.vue +++ b/src/client/app/desktop/views/components/notes.note.vue @@ -36,7 +36,7 @@

diff --git a/src/client/docs/api/entities/note.yaml b/src/client/docs/api/entities/note.yaml index 718d331d13..6fd26543bb 100644 --- a/src/client/docs/api/entities/note.yaml +++ b/src/client/docs/api/entities/note.yaml @@ -29,12 +29,6 @@ props: desc: ja: "投稿の本文 (ローカルの場合Markdown風のフォーマット)" en: "The text of this note (in Markdown like format if local)" - - name: "textHtml" - type: "string" - optional: true - desc: - ja: "投稿の本文 (HTML) (投稿時は無視)" - en: "The text of this note (in HTML. Ignored when posting.)" - name: "mediaIds" type: "id(DriveFile)[]" optional: true diff --git a/src/client/docs/api/entities/post.yaml b/src/client/docs/api/entities/post.yaml index 718d331d13..6fd26543bb 100644 --- a/src/client/docs/api/entities/post.yaml +++ b/src/client/docs/api/entities/post.yaml @@ -29,12 +29,6 @@ props: desc: ja: "投稿の本文 (ローカルの場合Markdown風のフォーマット)" en: "The text of this note (in Markdown like format if local)" - - name: "textHtml" - type: "string" - optional: true - desc: - ja: "投稿の本文 (HTML) (投稿時は無視)" - en: "The text of this note (in HTML. Ignored when posting.)" - name: "mediaIds" type: "id(DriveFile)[]" optional: true diff --git a/src/models/messaging-message.ts b/src/models/messaging-message.ts index 9d62fab4fa..a6a50fc8cf 100644 --- a/src/models/messaging-message.ts +++ b/src/models/messaging-message.ts @@ -12,7 +12,6 @@ export interface IMessagingMessage { _id: mongo.ObjectID; createdAt: Date; text: string; - textHtml: string; userId: mongo.ObjectID; recipientId: mongo.ObjectID; isRead: boolean; diff --git a/src/models/note.ts b/src/models/note.ts index d4b16afa4c..02801b62de 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -38,7 +38,6 @@ export type INote = { poll: any; // todo text: string; tags: string[]; - textHtml: string; cw: string; userId: mongo.ObjectID; appId: mongo.ObjectID; diff --git a/src/remote/activitypub/kernel/delete/note.ts b/src/remote/activitypub/kernel/delete/note.ts index 64c342d39b..b2868f69a3 100644 --- a/src/remote/activitypub/kernel/delete/note.ts +++ b/src/remote/activitypub/kernel/delete/note.ts @@ -22,7 +22,6 @@ export default async function(actor: IRemoteUser, uri: string): Promise { $set: { deletedAt: new Date(), text: null, - textHtml: null, mediaIds: [], poll: null } diff --git a/src/remote/activitypub/misc/get-note-html.ts b/src/remote/activitypub/misc/get-note-html.ts new file mode 100644 index 0000000000..a63059a38d --- /dev/null +++ b/src/remote/activitypub/misc/get-note-html.ts @@ -0,0 +1,18 @@ +import { INote } from "../../../models/note"; +import toHtml from '../../../text/html'; +import parse from '../../../text/parse'; +import config from '../../../config'; + +export default function(note: INote) { + if (note.text == null) return null; + + let html = toHtml(parse(note.text)); + + if (note.poll != null) { + const url = `${config.url}/notes/${note._id}`; + // TODO: i18n + html += `

【投票】
${url}

`; + } + + return html; +} diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts index c364b13249..a05c12b388 100644 --- a/src/remote/activitypub/renderer/note.ts +++ b/src/remote/activitypub/renderer/note.ts @@ -4,6 +4,7 @@ import config from '../../../config'; import DriveFile from '../../../models/drive-file'; import Note, { INote } from '../../../models/note'; import User from '../../../models/user'; +import toHtml from '../misc/get-note-html'; export default async function renderNote(note: INote, dive = true) { const promisedFiles = note.mediaIds @@ -48,7 +49,7 @@ export default async function renderNote(note: INote, dive = true) { id: `${config.url}/notes/${note._id}`, type: 'Note', attributedTo, - content: note.textHtml, + content: toHtml(note), published: note.createdAt.toISOString(), to: 'https://www.w3.org/ns/activitystreams#Public', cc: `${attributedTo}/followers`, diff --git a/src/server/api/endpoints/messaging/messages/create.ts b/src/server/api/endpoints/messaging/messages/create.ts index 085e75e6cf..0483b602b2 100644 --- a/src/server/api/endpoints/messaging/messages/create.ts +++ b/src/server/api/endpoints/messaging/messages/create.ts @@ -12,8 +12,6 @@ import { pack } from '../../../../../models/messaging-message'; import publishUserStream from '../../../../../publishers/stream'; import { publishMessagingStream, publishMessagingIndexStream } from '../../../../../publishers/stream'; import pushSw from '../../../../../publishers/push-sw'; -import html from '../../../../../text/html'; -import parse from '../../../../../text/parse'; import config from '../../../../../config'; /** @@ -77,7 +75,6 @@ module.exports = (params, user) => new Promise(async (res, rej) => { fileId: file ? file._id : undefined, recipientId: recipient._id, text: text ? text : undefined, - textHtml: text ? html(parse(text)) : undefined, userId: user._id, isRead: false }); diff --git a/src/services/note/create.ts b/src/services/note/create.ts index ca01b557d6..2380e64ce3 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -15,7 +15,6 @@ import Mute from '../../models/mute'; import pushSw from '../../publishers/push-sw'; import event from '../../publishers/stream'; import parse from '../../text/parse'; -import html from '../../text/html'; import { IApp } from '../../models/app'; export default async (user: IUser, data: { @@ -63,7 +62,6 @@ export default async (user: IUser, data: { replyId: data.reply ? data.reply._id : null, renoteId: data.renote ? data.renote._id : null, text: data.text, - textHtml: tokens === null ? null : html(tokens), poll: data.poll, cw: data.cw, tags, -- cgit v1.2.3-freya From d2d3a7d52b78b7d7d1da4380b062ac2279948cae Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 22 Apr 2018 17:04:52 +0900 Subject: CW --- .../app/desktop/views/components/notes.note.vue | 216 ++++++++++-------- .../app/desktop/views/components/post-form.vue | 77 +++++-- src/client/app/mobile/views/components/note.vue | 250 ++++++++++++--------- .../app/mobile/views/components/post-form.vue | 33 ++- src/models/note.ts | 2 +- src/server/api/endpoints/notes/create.ts | 16 +- src/services/note/create.ts | 2 +- 7 files changed, 369 insertions(+), 227 deletions(-) (limited to 'src/services/note/create.ts') diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue index ae2793db92..6d0e0c8f5c 100644 --- a/src/client/app/desktop/views/components/notes.note.vue +++ b/src/client/app/desktop/views/components/notes.note.vue @@ -34,24 +34,30 @@

{{ p.channel.title }}:

-
- %fa:reply% - - RP: -
-
- -
- -
- {{ tag }} -
- %fa:map-marker-alt% 位置情報 -
-
- +

+ {{ p.cw }} + {{ showContent ? '隠す' : 'もっと見る' }} +

+
+
+ %fa:reply% + + RP: +
+
+ +
+ +
+ {{ tag }} +
+ %fa:map-marker-alt% 位置情報 +
+
+ +
+
-
@@ -113,6 +119,7 @@ export default Vue.extend({ data() { return { + showContent: false, isDetailOpened: false, connection: null, connectionId: null @@ -456,7 +463,7 @@ root(isDark) > .body - > .text + > .cw cursor default display block margin 0 @@ -465,90 +472,117 @@ root(isDark) font-size 1.1em color isDark ? #fff : #717171 - >>> .title - display block - margin-bottom 4px - padding 4px - font-size 90% - text-align center - background isDark ? #2f3944 : #eef1f3 - border-radius 4px - - >>> .code - margin 8px 0 - - >>> .quote - margin 8px - padding 6px 12px - color isDark ? #6f808e : #aaa - border-left solid 3px isDark ? #637182 : #eee - - > .reply + > .text margin-right 8px - color isDark ? #99abbf : #717171 - - > .rp - margin-left 4px - font-style oblique - color #a0bf46 - > .location - margin 4px 0 - font-size 12px - color #ccc + > .toggle + display inline-block + padding 4px 8px + font-size 0.7em + color isDark ? #393f4f : #fff + background isDark ? #687390 : #b1b9c1 + border-radius 2px + cursor pointer + user-select none - > .map - width 100% - height 300px + &:hover + background isDark ? #707b97 : #bbc4ce - &:empty - display none + > .content - > .tags - margin 4px 0 0 0 + > .text + cursor default + display block + margin 0 + padding 0 + overflow-wrap break-word + font-size 1.1em + color isDark ? #fff : #717171 - > * - display inline-block - margin 0 8px 0 0 - padding 2px 8px 2px 16px - font-size 90% - color #8d969e - background #edf0f3 - border-radius 4px - - &:before - content "" + >>> .title display block - position absolute - top 0 - bottom 0 - left 4px - width 8px - height 8px - margin auto 0 - background #fff - border-radius 100% - - &:hover - text-decoration none - background #e2e7ec - - .mk-url-preview - margin-top 8px + margin-bottom 4px + padding 4px + font-size 90% + text-align center + background isDark ? #2f3944 : #eef1f3 + border-radius 4px + + >>> .code + margin 8px 0 + + >>> .quote + margin 8px + padding 6px 12px + color isDark ? #6f808e : #aaa + border-left solid 3px isDark ? #637182 : #eee + + > .reply + margin-right 8px + color isDark ? #99abbf : #717171 + + > .rp + margin-left 4px + font-style oblique + color #a0bf46 + + > .location + margin 4px 0 + font-size 12px + color #ccc - > .channel - margin 0 + > .map + width 100% + height 300px + + &:empty + display none + + > .tags + margin 4px 0 0 0 + + > * + display inline-block + margin 0 8px 0 0 + padding 2px 8px 2px 16px + font-size 90% + color #8d969e + background #edf0f3 + border-radius 4px + + &:before + content "" + display block + position absolute + top 0 + bottom 0 + left 4px + width 8px + height 8px + margin auto 0 + background #fff + border-radius 100% + + &:hover + text-decoration none + background #e2e7ec + + .mk-url-preview + margin-top 8px + + > .channel + margin 0 - > .mk-poll - font-size 80% + > .mk-poll + font-size 80% - > .renote - margin 8px 0 + > .renote + margin 8px 0 - > .mk-note-preview - padding 16px - border dashed 1px isDark ? #4e945e : #c0dac6 - border-radius 8px + > .mk-note-preview + padding 16px + border dashed 1px isDark ? #4e945e : #c0dac6 + border-radius 8px > footer > button diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index 919f97f44d..80d9d66753 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -6,6 +6,7 @@ @drop.stop="onDrop" >
+
@@ -24,6 +25,7 @@ +
@@ -39,7 +41,9 @@ export default Vue.extend({ components: { XDraggable }, + props: ['reply'], + data() { return { posting: false, @@ -47,21 +51,27 @@ export default Vue.extend({ uploadings: [], files: [], poll: false, - geo: null + geo: null, + useCw: false, + cw: null }; }, + mounted() { this.$nextTick(() => { this.focus(); }); }, + methods: { focus() { (this.$refs.text as any).focus(); }, + chooseFile() { (this.$refs.file as any).click(); }, + chooseFileFromDrive() { (this as any).apis.chooseDriveFile({ multiple: true @@ -69,23 +79,29 @@ export default Vue.extend({ files.forEach(this.attachMedia); }); }, + attachMedia(driveFile) { this.files.push(driveFile); this.$emit('change-attached-media', this.files); }, + detachMedia(file) { this.files = this.files.filter(x => x.id != file.id); this.$emit('change-attached-media', this.files); }, + onChangeFile() { Array.from((this.$refs.file as any).files).forEach(this.upload); }, + upload(file) { (this.$refs.uploader as any).upload(file); }, + onChangeUploadings(uploads) { this.$emit('change-uploadings', uploads); }, + setGeo() { if (navigator.geolocation == null) { alert('お使いの端末は位置情報に対応していません'); @@ -100,15 +116,18 @@ export default Vue.extend({ enableHighAccuracy: true }); }, + removeGeo() { this.geo = null; }, + clear() { this.text = ''; this.files = []; this.poll = false; this.$emit('change-attached-media'); }, + post() { this.posting = true; const viaMobile = (this as any).os.i.clientSettings.disableViaMobile !== true; @@ -117,6 +136,7 @@ export default Vue.extend({ mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, replyId: this.reply ? this.reply.id : undefined, poll: this.poll ? (this.$refs.poll as any).get() : undefined, + cw: this.useCw ? this.cw || '' : undefined, geo: this.geo ? { coordinates: [this.geo.longitude, this.geo.latitude], altitude: this.geo.altitude, @@ -133,10 +153,12 @@ export default Vue.extend({ this.posting = false; }); }, + cancel() { this.$emit('cancel'); this.$destroy(); }, + kao() { this.text += getKao(); } @@ -236,14 +258,12 @@ export default Vue.extend({ > .file display none + > input > textarea display block padding 12px margin 0 width 100% - max-width 100% - min-width 100% - min-height 80px font-size 16px color #333 border none @@ -253,6 +273,11 @@ export default Vue.extend({ &:disabled opacity 0.5 + > textarea + max-width 100% + min-width 100% + min-height 80px + > .upload > .drive > .kao diff --git a/src/models/note.ts b/src/models/note.ts index 02801b62de..3c835ed190 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -24,7 +24,7 @@ export function isValidText(text: string): boolean { } export function isValidCw(text: string): boolean { - return text.length <= 100 && text.trim() != ''; + return text.length <= 100; } export type INote = { diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 14e906eb9d..ea1f41aae2 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -23,11 +23,11 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res if (visibilityErr) return rej('invalid visibility'); // Get 'text' parameter - const [text, textErr] = $(params.text).optional.string().pipe(isValidText).$; + const [text = null, textErr] = $(params.text).optional.nullable.string().pipe(isValidText).$; if (textErr) return rej('invalid text'); // Get 'cw' parameter - const [cw, cwErr] = $(params.cw).optional.string().pipe(isValidCw).$; + const [cw, cwErr] = $(params.cw).optional.nullable.string().pipe(isValidCw).$; if (cwErr) return rej('invalid cw'); // Get 'viaMobile' parameter @@ -187,14 +187,14 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res const note = await create(user, { createdAt: new Date(), media: files, - poll: poll, - text: text, + poll, + text, reply, renote, - cw: cw, - tags: tags, - app: app, - viaMobile: viaMobile, + cw, + tags, + app, + viaMobile, visibility, geo }); diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 2380e64ce3..e5ad96898f 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -63,7 +63,7 @@ export default async (user: IUser, data: { renoteId: data.renote ? data.renote._id : null, text: data.text, poll: data.poll, - cw: data.cw, + cw: data.cw == null ? null : data.cw, tags, userId: user._id, viaMobile: data.viaMobile, -- cgit v1.2.3-freya From 3d9ac6387e4f698c15bdca0d8dbd1f0a2b02161f Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 25 Apr 2018 18:04:16 +0900 Subject: wip --- src/publishers/stream.ts | 5 ++ src/server/api/endpoints/users/lists/push.ts | 5 +- src/services/note/create.ts | 110 +++++++++++++++------------ 3 files changed, 71 insertions(+), 49 deletions(-) (limited to 'src/services/note/create.ts') diff --git a/src/publishers/stream.ts b/src/publishers/stream.ts index 2ecbfa0dd8..dcc03e39f1 100644 --- a/src/publishers/stream.ts +++ b/src/publishers/stream.ts @@ -25,6 +25,10 @@ class MisskeyEvent { this.publish(`note-stream:${noteId}`, type, typeof value === 'undefined' ? null : value); } + public publishUserListStream(listId: ID, type: string, value?: any): void { + this.publish(`user-list-stream:${listId}`, type, typeof value === 'undefined' ? null : value); + } + public publishMessagingStream(userId: ID, otherpartyId: ID, type: string, value?: any): void { this.publish(`messaging-stream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value); } @@ -69,6 +73,7 @@ export default ev.publishUserStream.bind(ev); export const publishLocalTimelineStream = ev.publishLocalTimelineStream.bind(ev); export const publishGlobalTimelineStream = ev.publishGlobalTimelineStream.bind(ev); export const publishDriveStream = ev.publishDriveStream.bind(ev); +export const publishUserListStream = ev.publishUserListStream.bind(ev); export const publishNoteStream = ev.publishNoteStream.bind(ev); export const publishMessagingStream = ev.publishMessagingStream.bind(ev); export const publishMessagingIndexStream = ev.publishMessagingIndexStream.bind(ev); diff --git a/src/server/api/endpoints/users/lists/push.ts b/src/server/api/endpoints/users/lists/push.ts index f21234775d..467c08efd4 100644 --- a/src/server/api/endpoints/users/lists/push.ts +++ b/src/server/api/endpoints/users/lists/push.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import ID from '../../../../../cafy-id'; import UserList from '../../../../../models/user-list'; -import User from '../../../../../models/user'; +import User, { pack as packUser } from '../../../../../models/user'; +import { publishUserListStream } from '../../../../../publishers/stream'; /** * Add a user to a user list @@ -45,4 +46,6 @@ module.exports = async (params, me) => new Promise(async (res, rej) => { }); res(); + + publishUserListStream(userList._id, 'userAdded', await packUser(user)); }); diff --git a/src/services/note/create.ts b/src/services/note/create.ts index e5ad96898f..4808edfda4 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -1,6 +1,6 @@ import Note, { pack, INote } from '../../models/note'; import User, { isLocalUser, IUser, isRemoteUser } from '../../models/user'; -import stream, { publishLocalTimelineStream, publishGlobalTimelineStream } from '../../publishers/stream'; +import stream, { publishLocalTimelineStream, publishGlobalTimelineStream, publishUserListStream } from '../../publishers/stream'; import Following from '../../models/following'; import { deliver } from '../../queue'; import renderNote from '../../remote/activitypub/renderer/note'; @@ -16,6 +16,7 @@ import pushSw from '../../publishers/push-sw'; import event from '../../publishers/stream'; import parse from '../../text/parse'; import { IApp } from '../../models/app'; +import UserList from '../../models/user-list'; export default async (user: IUser, data: { createdAt?: Date; @@ -110,60 +111,73 @@ export default async (user: IUser, data: { // タイムラインへの投稿 if (note.channelId == null) { - if (isLocalUser(user)) { - // Publish event to myself's stream - stream(note.userId, 'note', noteObj); - - // Publish note to local timeline stream - publishLocalTimelineStream(noteObj); - } - - // Publish note to global timeline stream - publishGlobalTimelineStream(noteObj); - - // Fetch all followers - const followers = await Following.find({ - followeeId: note.userId - }); - if (!silent) { - const render = async () => { - const content = data.renote && data.text == null - ? renderAnnounce(data.renote.uri ? data.renote.uri : await renderNote(data.renote)) - : renderCreate(await renderNote(note)); - return packAp(content); - }; - - // 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送 - if (data.reply && isLocalUser(user) && isRemoteUser(data.reply._user)) { - deliver(user, await render(), data.reply._user.inbox); - } + if (isLocalUser(user)) { + // Publish event to myself's stream + stream(note.userId, 'note', noteObj); - // 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送 - if (data.renote && isLocalUser(user) && isRemoteUser(data.renote._user)) { - deliver(user, await render(), data.renote._user.inbox); + // Publish note to local timeline stream + publishLocalTimelineStream(noteObj); } - Promise.all(followers.map(async following => { - const follower = following._follower; - - if (isLocalUser(follower)) { - // ストーキングしていない場合 - if (!following.stalk) { - // この投稿が返信ならスキップ - if (note.replyId && !note._reply.userId.equals(following.followerId) && !note._reply.userId.equals(note.userId)) return; + // Publish note to global timeline stream + publishGlobalTimelineStream(noteObj); + + // フォロワーに配信 + Following.find({ + followeeId: note.userId + }).then(followers => { + followers.map(async following => { + const follower = following._follower; + + if (isLocalUser(follower)) { + // ストーキングしていない場合 + if (!following.stalk) { + // この投稿が返信ならスキップ + if (note.replyId && !note._reply.userId.equals(following.followerId) && !note._reply.userId.equals(note.userId)) return; + } + + // Publish event to followers stream + stream(following.followerId, 'note', noteObj); + } else { + //#region AP配送 + // フォロワーがリモートユーザーかつ投稿者がローカルユーザーなら投稿を配信 + if (isLocalUser(user)) { + deliver(user, await render(), follower.inbox); + } + //#endergion } + }); + }); - // Publish event to followers stream - stream(following.followerId, 'note', noteObj); - } else { - // フォロワーがリモートユーザーかつ投稿者がローカルユーザーなら投稿を配信 - if (isLocalUser(user)) { - deliver(user, await render(), follower.inbox); - } - } - })); + // リストに配信 + UserList.find({ + userIds: note.userId + }).then(lists => { + lists.forEach(list => { + publishUserListStream(list._id, 'note', noteObj); + }); + }); + } + + //#region AP配送 + const render = async () => { + const content = data.renote && data.text == null + ? renderAnnounce(data.renote.uri ? data.renote.uri : await renderNote(data.renote)) + : renderCreate(await renderNote(note)); + return packAp(content); + }; + + // 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送 + if (data.reply && isLocalUser(user) && isRemoteUser(data.reply._user)) { + deliver(user, await render(), data.reply._user.inbox); + } + + // 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送 + if (data.renote && isLocalUser(user) && isRemoteUser(data.renote._user)) { + deliver(user, await render(), data.renote._user.inbox); } + //#endergion } // チャンネルへの投稿 -- cgit v1.2.3-freya From ec4ed8fb2d87f02ff67cfbf6b9f8e2975bc49e4f Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 29 Apr 2018 04:30:51 +0900 Subject: wip --- .../common/views/components/visibility-chooser.vue | 6 +-- src/models/note.ts | 53 +++++++++++++++++++++- src/server/api/endpoints/notes/create.ts | 16 ++++++- src/services/note/create.ts | 6 +++ 4 files changed, 74 insertions(+), 7 deletions(-) (limited to 'src/services/note/create.ts') diff --git a/src/client/app/common/views/components/visibility-chooser.vue b/src/client/app/common/views/components/visibility-chooser.vue index b6a8e9b661..dd36d32e74 100644 --- a/src/client/app/common/views/components/visibility-chooser.vue +++ b/src/client/app/common/views/components/visibility-chooser.vue @@ -22,11 +22,11 @@ 自分のフォロワーにのみ公開
-
+
%fa:envelope%
- メンション - 言及したユーザーにのみ公開 + ダイレクト + 指定したユーザーにのみ公開
diff --git a/src/models/note.ts b/src/models/note.ts index 2f95cbfd65..5c4ac86353 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -12,6 +12,7 @@ import NoteWatching, { deleteNoteWatching } from './note-watching'; import NoteReaction from './note-reaction'; import Favorite, { deleteFavorite } from './favorite'; import Notification, { deleteNotification } from './notification'; +import Following from './following'; const Note = db.get('notes'); @@ -51,10 +52,12 @@ export type INote = { * public ... 公開 * home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す * followers ... フォロワーのみ - * mentioned ... 言及したユーザーのみ + * specified ... visibleUserIds で指定したユーザーのみ * private ... 自分のみ */ - visibility: 'public' | 'home' | 'followers' | 'mentioned' | 'private'; + visibility: 'public' | 'home' | 'followers' | 'specified' | 'private'; + + visibleUserIds: mongo.ObjectID[]; geo: { coordinates: number[]; @@ -190,6 +193,52 @@ export const pack = async ( if (!_note) throw `invalid note arg ${note}`; + let hide = false; + + // visibility が private かつ投稿者のIDが自分のIDではなかったら非表示 + if (_note.visibility == 'private' && (meId == null || !meId.equals(_note.userId))) { + hide = true; + } + + // visibility が specified かつ自分が指定されていなかったら非表示 + if (_note.visibility == 'specified') { + if (meId == null) { + hide = true; + } else if (meId.equals(_note.userId)) { + hide = false; + } else { + // 指定されているかどうか + const specified = _note.visibleUserIds.test(id => id.equals(meId)); + + if (specified) { + hide = false; + } else { + hide = true; + } + } + } + + // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 + if (_note.visibility == 'followers') { + if (meId == null) { + hide = true; + } else if (meId.equals(_note.userId)) { + hide = false; + } else { + // フォロワーかどうか + const following = await Following.findOne({ + followeeId: _note.userId, + followerId: meId + }); + + if (following == null) { + hide = true; + } else { + hide = false; + } + } + } + const id = _note._id; // Rename _id to id diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index af4f365226..52c6068fd0 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -3,7 +3,7 @@ */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note'; -import { ILocalUser } from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import Channel, { IChannel } from '../../../../models/channel'; import DriveFile from '../../../../models/drive-file'; import create from '../../../../services/note/create'; @@ -14,9 +14,20 @@ import { IApp } from '../../../../models/app'; */ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { // Get 'visibility' parameter - const [visibility = 'public', visibilityErr] = $(params.visibility).optional.string().or(['public', 'unlisted', 'private', 'direct']).get(); + const [visibility = 'public', visibilityErr] = $(params.visibility).optional.string().or(['public', 'home', 'followers', 'specified', 'private']).get(); if (visibilityErr) return rej('invalid visibility'); + // Get 'visibleUserIds' parameter + const [visibleUserIds, visibleUserIdsErr] = $(params.visibleUserIds).optional.array($().type(ID)).unique().min(1).get(); + if (visibleUserIdsErr) return rej('invalid visibleUserIds'); + + let visibleUsers = []; + if (visibleUserIds !== undefined) { + visibleUsers = await Promise.all(visibleUserIds.map(id => User.findOne({ + _id: id + }))); + } + // Get 'text' parameter const [text = null, textErr] = $(params.text).optional.nullable.string().pipe(isValidText).get(); if (textErr) return rej('invalid text'); @@ -191,6 +202,7 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res app, viaMobile, visibility, + visibleUsers, geo }); diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 4808edfda4..e8070595cd 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -30,6 +30,7 @@ export default async (user: IUser, data: { tags?: string[]; cw?: string; visibility?: string; + visibleUsers?: IUser[]; uri?: string; app?: IApp; }, silent = false) => new Promise(async (res, rej) => { @@ -57,6 +58,10 @@ export default async (user: IUser, data: { }); } + if (data.visibleUsers) { + data.visibleUsers = data.visibleUsers.filter(x => x != null); + } + const insert: any = { createdAt: data.createdAt, mediaIds: data.media ? data.media.map(file => file._id) : [], @@ -71,6 +76,7 @@ export default async (user: IUser, data: { geo: data.geo || null, appId: data.app ? data.app._id : null, visibility: data.visibility, + visibleUserIds: data.visibleUsers ? data.visibleUsers.map(u => u._id) : [], // 以下非正規化データ _reply: data.reply ? { userId: data.reply.userId } : null, -- cgit v1.2.3-freya From 7797a0746a665673db60fae7ed82329153d21262 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 29 Apr 2018 05:28:34 +0900 Subject: wip --- .../app/desktop/views/components/post-form.vue | 30 ++++++++++++++++++++++ src/services/note/create.ts | 6 ++++- 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'src/services/note/create.ts') diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index d1d7076a30..a9ce1354da 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -6,6 +6,10 @@ @drop.stop="onDrop" >
+
+ {{ u | userName }}[x] + +ユーザーを追加 +