diff options
Diffstat (limited to 'src/server/api')
42 files changed, 318 insertions, 88 deletions
diff --git a/src/server/api/common/read-messaging-message.ts b/src/server/api/common/read-messaging-message.ts index 075e369832..63080d22a4 100644 --- a/src/server/api/common/read-messaging-message.ts +++ b/src/server/api/common/read-messaging-message.ts @@ -1,4 +1,5 @@ import * as mongo from 'mongodb'; +import isObjectId from '../../../misc/is-objectid'; import Message from '../../../models/messaging-message'; import { IMessagingMessage as IMessage } from '../../../models/messaging-message'; import { publishMainStream } from '../../../stream'; @@ -15,21 +16,21 @@ export default ( message: string | string[] | IMessage | IMessage[] | mongo.ObjectID | mongo.ObjectID[] ) => new Promise<any>(async (resolve, reject) => { - const userId = mongo.ObjectID.prototype.isPrototypeOf(user) + const userId = isObjectId(user) ? user : new mongo.ObjectID(user); - const otherpartyId = mongo.ObjectID.prototype.isPrototypeOf(otherparty) + const otherpartyId = isObjectId(otherparty) ? otherparty : new mongo.ObjectID(otherparty); const ids: mongo.ObjectID[] = Array.isArray(message) - ? mongo.ObjectID.prototype.isPrototypeOf(message[0]) + ? isObjectId(message[0]) ? (message as mongo.ObjectID[]) : typeof message[0] === 'string' ? (message as string[]).map(m => new mongo.ObjectID(m)) : (message as IMessage[]).map(m => m._id) - : mongo.ObjectID.prototype.isPrototypeOf(message) + : isObjectId(message) ? [(message as mongo.ObjectID)] : typeof message === 'string' ? [new mongo.ObjectID(message)] diff --git a/src/server/api/common/read-notification.ts b/src/server/api/common/read-notification.ts index 2d58ada4ce..27d3f1be32 100644 --- a/src/server/api/common/read-notification.ts +++ b/src/server/api/common/read-notification.ts @@ -1,4 +1,5 @@ import * as mongo from 'mongodb'; +import isObjectId from '../../../misc/is-objectid'; import { default as Notification, INotification } from '../../../models/notification'; import { publishMainStream } from '../../../stream'; import Mute from '../../../models/mute'; @@ -12,17 +13,17 @@ export default ( message: string | string[] | INotification | INotification[] | mongo.ObjectID | mongo.ObjectID[] ) => new Promise<any>(async (resolve, reject) => { - const userId = mongo.ObjectID.prototype.isPrototypeOf(user) + const userId = isObjectId(user) ? user : new mongo.ObjectID(user); const ids: mongo.ObjectID[] = Array.isArray(message) - ? mongo.ObjectID.prototype.isPrototypeOf(message[0]) + ? isObjectId(message[0]) ? (message as mongo.ObjectID[]) : typeof message[0] === 'string' ? (message as string[]).map(m => new mongo.ObjectID(m)) : (message as INotification[]).map(m => m._id) - : mongo.ObjectID.prototype.isPrototypeOf(message) + : isObjectId(message) ? [(message as mongo.ObjectID)] : typeof message === 'string' ? [new mongo.ObjectID(message)] diff --git a/src/server/api/common/signin.ts b/src/server/api/common/signin.ts index 44e1336f27..8d44b377fe 100644 --- a/src/server/api/common/signin.ts +++ b/src/server/api/common/signin.ts @@ -8,7 +8,9 @@ export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) { ctx.cookies.set('i', user.token, { path: '/', domain: config.hostname, - secure: config.url.startsWith('https'), + // SEE: https://github.com/koajs/koa/issues/974 + //secure: config.url.startsWith('https'), + secure: false, httpOnly: false, expires: new Date(Date.now() + expires), maxAge: expires diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts index 1f390d01aa..6cbd4ef87e 100644 --- a/src/server/api/endpoints/ap/show.ts +++ b/src/server/api/endpoints/ap/show.ts @@ -24,15 +24,15 @@ export const meta = { }, }; -export default (params: any) => new Promise(async (res, rej) => { +export default async (params: any) => { const [ps, psErr] = getParams(meta, params); - if (psErr) return rej(psErr); + if (psErr) throw psErr; const object = await fetchAny(ps.uri); - if (object !== null) return res(object); + if (object !== null) return object; - return rej('object not found'); -}); + throw new Error('object not found'); +}; /*** * URIからUserかNoteを解決する diff --git a/src/server/api/endpoints/drive/files/check_existence.ts b/src/server/api/endpoints/drive/files/check_existence.ts index 73d75b7caf..a024701655 100644 --- a/src/server/api/endpoints/drive/files/check_existence.ts +++ b/src/server/api/endpoints/drive/files/check_existence.ts @@ -27,7 +27,8 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = const file = await DriveFile.findOne({ md5: md5, - 'metadata.userId': user._id + 'metadata.userId': user._id, + 'metadata.deletedAt': { $exists: false } }); if (file === null) { diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts index 718fb8c2d7..e35c5e3ca2 100644 --- a/src/server/api/endpoints/drive/files/show.ts +++ b/src/server/api/endpoints/drive/files/show.ts @@ -22,7 +22,8 @@ export default async (params: any, user: ILocalUser) => { const file = await DriveFile .findOne({ _id: fileId, - 'metadata.userId': user._id + 'metadata.userId': user._id, + 'metadata.deletedAt': { $exists: false } }); if (file === null) { diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts index 3c7932c341..9b8c53caf7 100644 --- a/src/server/api/endpoints/drive/files/update.ts +++ b/src/server/api/endpoints/drive/files/update.ts @@ -100,8 +100,10 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = }).then(notes => { notes.forEach(note => { note._files[note._files.findIndex(f => f._id.equals(file._id))] = file; - Note.findOneAndUpdate({ _id: note._id }, { - _files: note._files + Note.update({ _id: note._id }, { + $set: { + _files: note._files + } }); }); }); diff --git a/src/server/api/endpoints/i/read_all_unread_notes.ts b/src/server/api/endpoints/i/read_all_unread_notes.ts new file mode 100644 index 0000000000..fae98b9816 --- /dev/null +++ b/src/server/api/endpoints/i/read_all_unread_notes.ts @@ -0,0 +1,36 @@ +import User, { ILocalUser } from '../../../../models/user'; +import { publishMainStream } from '../../../../stream'; +import NoteUnread from '../../../../models/note-unread'; + +export const meta = { + desc: { + 'ja-JP': '未読の投稿をすべて既読にします。' + }, + + requireCredential: true, + + kind: 'account-write', + + params: { + } +}; + +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + // Remove documents + await NoteUnread.remove({ + userId: user._id + }); + + User.update({ _id: user._id }, { + $set: { + hasUnreadMentions: false, + hasUnreadSpecifiedNotes: false + } + }); + + // 全て既読になったイベントを発行 + publishMainStream(user._id, 'readAllUnreadMentions'); + publishMainStream(user._id, 'readAllUnreadSpecifiedNotes'); + + res(); +}); diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 548ce5cadb..7b8431f0ee 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -67,6 +67,12 @@ export const meta = { } }), + carefulBot: $.bool.optional.note({ + desc: { + 'ja-JP': 'Botからのフォローを承認制にするか' + } + }), + isBot: $.bool.optional.note({ desc: { 'ja-JP': 'Botか否か' @@ -95,7 +101,7 @@ export const meta = { export default async (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { const [ps, psErr] = getParams(meta, params); - if (psErr) throw psErr; + if (psErr) return rej(psErr); const isSecure = user != null && app == null; @@ -110,6 +116,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a if (ps.wallpaperId !== undefined) updates.wallpaperId = ps.wallpaperId; if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked; if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot; + if (typeof ps.carefulBot == 'boolean') updates.carefulBot = ps.carefulBot; if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat; if (typeof ps.autoWatch == 'boolean') updates['settings.autoWatch'] = ps.autoWatch; if (typeof ps.alwaysMarkNsfw == 'boolean') updates['settings.alwaysMarkNsfw'] = ps.alwaysMarkNsfw; diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index c76d7f2e8f..ecd5817581 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -1,6 +1,3 @@ -/** - * Module dependencies - */ import * as os from 'os'; import config from '../../../config'; import Meta from '../../../models/meta'; @@ -9,9 +6,17 @@ import { ILocalUser } from '../../../models/user'; const pkg = require('../../../../package.json'); const client = require('../../../../built/client/meta.json'); -/** - * Show core info - */ +export const meta = { + desc: { + 'ja-JP': 'インスタンス情報を取得します。', + 'en-US': 'Get the information of this instance.' + }, + + requireCredential: false, + + params: {}, +}; + export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { const meta: any = (await Meta.findOne()) || {}; @@ -28,10 +33,12 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => machine: os.hostname(), os: os.platform(), node: process.version, + cpu: { model: os.cpus()[0].model, cores: os.cpus().length }, + broadcasts: meta.broadcasts || [], disableRegistration: meta.disableRegistration, disableLocalTimeline: meta.disableLocalTimeline, @@ -40,6 +47,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => swPublickey: config.sw ? config.sw.public_key : null, hidedTags: (me && me.isAdmin) ? meta.hidedTags : undefined, bannerUrl: meta.bannerUrl, + features: { registration: !meta.disableRegistration, localTimeLine: !meta.disableLocalTimeline, @@ -47,7 +55,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => recaptcha: config.recaptcha ? true : false, objectStorage: config.drive && config.drive.storage === 'minio', twitter: config.twitter ? true : false, - serviceWorker: config.sw ? true : false + serviceWorker: config.sw ? true : false, + userRecommendation: config.user_recommendation ? config.user_recommendation : {} } }); }); diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 8362143bb2..8a6c848943 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -58,6 +58,8 @@ export default async (params: any, user: ILocalUser) => { }; const query = { + deletedAt: null, + // public only visibility: 'public', diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 14b4432b33..b2ea9c60ac 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -129,6 +129,8 @@ export default async (params: any, user: ILocalUser) => { const query = { $and: [{ + deletedAt: null, + $or: [{ // フォローしている人の投稿 $or: followQuery diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 8ab07d8ea7..510564129c 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -71,6 +71,8 @@ export default async (params: any, user: ILocalUser) => { }; const query = { + deletedAt: null, + // public only visibility: 'public', diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 592a94263d..27b6245eb6 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -45,6 +45,8 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = // Construct query const query = { + deletedAt: null, + $or: [{ mentions: user._id }, { diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 44a504eb18..31a4978407 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -132,6 +132,8 @@ export default async (params: any, user: ILocalUser) => { const query = { $and: [{ + deletedAt: null, + // フォローしている人の投稿 $or: followQuery, diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index 6758b4eb73..7dddc4834e 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -137,6 +137,8 @@ export default async (params: any, user: ILocalUser) => { const query = { $and: [{ + deletedAt: null, + // リストに入っている人のタイムラインへの投稿 $or: listQuery, diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index 1bfe832c51..0b2fc06b04 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -99,6 +99,12 @@ export const meta = { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } }), + + fileType: $.arr($.str).optional.note({ + desc: { + 'ja-JP': '指定された種類のファイルが添付された投稿のみを取得します' + } + }), } }; @@ -136,7 +142,9 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => }; const query = { - userId: user._id + deletedAt: null, + userId: user._id, + visibility: { $in: ['public', 'home'] } } as any; if (ps.sinceId) { @@ -171,6 +179,14 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => $ne: [] }; } + + if (ps.fileType) { + query.fileIds = { $exists: true, $ne: [] }; + + query['_files.contentType'] = { + $in: ps.fileType + }; + } //#endregion // Issue query diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index e0a5cb9e36..df85343f0f 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -3,6 +3,8 @@ import $ from 'cafy'; import User, { pack, ILocalUser } from '../../../../models/user'; import { getFriendIds } from '../../common/get-friends'; import Mute from '../../../../models/mute'; +import * as request from 'request'; +import config from '../../../../config'; export const meta = { desc: { @@ -15,44 +17,70 @@ export const meta = { }; export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); - if (limitErr) return rej('invalid limit param'); + if (config.user_recommendation && config.user_recommendation.external) { + const userName = me.username; + const hostName = config.hostname; + const limit = params.limit; + const offset = params.offset; + const timeout = config.user_recommendation.timeout; + const engine = config.user_recommendation.engine; + const url = engine + .replace('{{host}}', hostName) + .replace('{{user}}', userName) + .replace('{{limit}}', limit) + .replace('{{offset}}', offset); - // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); - if (offsetErr) return rej('invalid offset param'); + request({ + url: url, + proxy: config.proxy, + timeout: timeout, + json: true, + followRedirect: true, + followAllRedirects: true + }, (error: any, response: any, body: any) => { + if (!error && response.statusCode == 200) { + res(body); + } else { + res([]); + } + }); + } else { + // Get 'limit' parameter + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); + if (limitErr) return rej('invalid limit param'); + + // Get 'offset' parameter + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); + if (offsetErr) return rej('invalid offset param'); - // ID list of the user itself and other users who the user follows - const followingIds = await getFriendIds(me._id); + // ID list of the user itself and other users who the user follows + const followingIds = await getFriendIds(me._id); - // ミュートしているユーザーを取得 - const mutedUserIds = (await Mute.find({ - muterId: me._id - })).map(m => m.muteeId); + // ミュートしているユーザーを取得 + const mutedUserIds = (await Mute.find({ + muterId: me._id + })).map(m => m.muteeId); - const users = await User - .find({ - _id: { - $nin: followingIds.concat(mutedUserIds) - }, - isLocked: false, - $or: [{ + const users = await User + .find({ + _id: { + $nin: followingIds.concat(mutedUserIds) + }, + isLocked: { $ne: true }, lastUsedAt: { $gte: new Date(Date.now() - ms('7days')) - } - }, { + }, host: null - }] - }, { - limit: limit, - skip: offset, - sort: { - followersCount: -1 - } - }); + }, { + limit: limit, + skip: offset, + sort: { + followersCount: -1 + } + }); - // Serialize - res(await Promise.all(users.map(async user => - await pack(user, me, { detail: true })))); + // Serialize + res(await Promise.all(users.map(async user => + await pack(user, me, { detail: true })))); + } }); diff --git a/src/server/api/index.ts b/src/server/api/index.ts index a8f6455d9a..33e98f650a 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -46,6 +46,8 @@ router.post('/signin', require('./private/signin').default); router.use(require('./service/github').routes()); router.use(require('./service/twitter').routes()); +router.use(require('./mastodon').routes()); + // Return 404 for unknown API router.all('*', async ctx => { ctx.status = 404; @@ -54,4 +56,4 @@ router.all('*', async ctx => { // Register router app.use(router.routes()); -module.exports = app; +export default app; diff --git a/src/server/api/limitter.ts b/src/server/api/limitter.ts index 20a18a7098..abf7627ab8 100644 --- a/src/server/api/limitter.ts +++ b/src/server/api/limitter.ts @@ -8,6 +8,12 @@ import { IUser } from '../../models/user'; const log = debug('misskey:limitter'); export default (endpoint: IEndpoint, user: IUser) => new Promise((ok, reject) => { + // Redisがインストールされてない場合は常に許可 + if (limiterDB == null) { + ok(); + return; + } + const limitation = endpoint.meta.limit; const key = limitation.hasOwnProperty('key') diff --git a/src/server/api/mastodon.ts b/src/server/api/mastodon.ts new file mode 100644 index 0000000000..f2ce1c384f --- /dev/null +++ b/src/server/api/mastodon.ts @@ -0,0 +1,14 @@ +import * as Router from 'koa-router'; +import User from '../../models/user'; +import { toASCII } from 'punycode'; + +// Init router +const router = new Router(); + +router.get('/v1/instance/peers', async ctx => { + const peers = await User.distinct('host', { host: { $ne: null } }) as any as string[]; + const punyCodes = peers.map(peer => toASCII(peer)); + ctx.body = punyCodes; +}); + +module.exports = router; diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index 0e44c2ddd6..0a0f9ae6f9 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -12,9 +12,8 @@ export default async (ctx: Koa.Context) => { ctx.set('Access-Control-Allow-Credentials', 'true'); const body = ctx.request.body as any; - // See: https://github.com/syuilo/misskey/issues/2384 - const username = body['username'] || body['x']; - const password = body['password'] || body['y']; + const username = body['username']; + const password = body['password']; const token = body['token']; if (typeof username != 'string') { diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index e3e8f044b5..13ca16eb9f 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -132,6 +132,12 @@ export default async (ctx: Koa.Context) => { updateUserStats(account, true); - // Response - ctx.body = await pack(account); + const res = await pack(account, account, { + detail: true, + includeSecrets: true + }); + + res.token = secret; + + ctx.body = res; }; diff --git a/src/server/api/service/github.ts b/src/server/api/service/github.ts index c8d588eaaf..ac18cf90ae 100644 --- a/src/server/api/service/github.ts +++ b/src/server/api/service/github.ts @@ -63,6 +63,7 @@ handler.on('status', event => { // Fetch parent status request({ url: `${parent.url}/statuses`, + proxy: config.proxy, headers: { 'User-Agent': 'misskey' } diff --git a/src/server/api/service/twitter.ts b/src/server/api/service/twitter.ts index f71e588628..6c3cdaa138 100644 --- a/src/server/api/service/twitter.ts +++ b/src/server/api/service/twitter.ts @@ -55,7 +55,7 @@ router.get('/disconnect/twitter', async ctx => { })); }); -if (config.twitter == null) { +if (config.twitter == null || redis == null) { router.get('/connect/twitter', ctx => { ctx.body = '現在Twitterへ接続できません (このインスタンスではTwitterはサポートされていません)'; }); diff --git a/src/server/api/stream/channel.ts b/src/server/api/stream/channel.ts index e2726060dc..75914964cb 100644 --- a/src/server/api/stream/channel.ts +++ b/src/server/api/stream/channel.ts @@ -7,6 +7,8 @@ import Connection from '.'; export default abstract class Channel { protected connection: Connection; public id: string; + public abstract readonly chName: string; + public static readonly shouldShare: boolean; protected get user() { return this.connection.user; diff --git a/src/server/api/stream/channels/drive.ts b/src/server/api/stream/channels/drive.ts index 807fc93cd0..7425a620ff 100644 --- a/src/server/api/stream/channels/drive.ts +++ b/src/server/api/stream/channels/drive.ts @@ -2,6 +2,9 @@ import autobind from 'autobind-decorator'; import Channel from '../channel'; export default class extends Channel { + public readonly chName = 'drive'; + public static shouldShare = true; + @autobind public async init(params: any) { // Subscribe drive stream diff --git a/src/server/api/stream/channels/games/reversi-game.ts b/src/server/api/stream/channels/games/reversi-game.ts index 11f1fb1feb..5dc9ca0608 100644 --- a/src/server/api/stream/channels/games/reversi-game.ts +++ b/src/server/api/stream/channels/games/reversi-game.ts @@ -1,5 +1,6 @@ import autobind from 'autobind-decorator'; import * as CRC32 from 'crc-32'; +import * as mongo from 'mongodb'; import ReversiGame, { pack } from '../../../../../models/games/reversi/game'; import { publishReversiGameStream } from '../../../../../stream'; import Reversi from '../../../../../games/reversi/core'; @@ -7,11 +8,14 @@ import * as maps from '../../../../../games/reversi/maps'; import Channel from '../../channel'; export default class extends Channel { - private gameId: string; + public readonly chName = 'gamesReversiGame'; + public static shouldShare = false; + + private gameId: mongo.ObjectID; @autobind public async init(params: any) { - this.gameId = params.gameId as string; + this.gameId = new mongo.ObjectID(params.gameId as string); // Subscribe game stream this.subscriber.on(`reversiGameStream:${this.gameId}`, data => { @@ -23,10 +27,10 @@ export default class extends Channel { public onMessage(type: string, body: any) { switch (type) { case 'accept': this.accept(true); break; - case 'cancel-accept': this.accept(false); break; - case 'update-settings': this.updateSettings(body.settings); break; - case 'init-form': this.initForm(body); break; - case 'update-form': this.updateForm(body.id, body.value); break; + case 'cancelAccept': this.accept(false); break; + case 'updateSettings': this.updateSettings(body.settings); break; + case 'initForm': this.initForm(body); break; + case 'updateForm': this.updateForm(body.id, body.value); break; case 'message': this.message(body); break; case 'set': this.set(body.pos); break; case 'check': this.check(body.crc32); break; diff --git a/src/server/api/stream/channels/games/reversi.ts b/src/server/api/stream/channels/games/reversi.ts index d75025c944..51cb264d98 100644 --- a/src/server/api/stream/channels/games/reversi.ts +++ b/src/server/api/stream/channels/games/reversi.ts @@ -5,6 +5,9 @@ import { publishMainStream } from '../../../../../stream'; import Channel from '../../channel'; export default class extends Channel { + public readonly chName = 'gamesReversi'; + public static shouldShare = true; + @autobind public async init(params: any) { // Subscribe reversi stream diff --git a/src/server/api/stream/channels/global-timeline.ts b/src/server/api/stream/channels/global-timeline.ts index ab0fe5d094..e39ea269a6 100644 --- a/src/server/api/stream/channels/global-timeline.ts +++ b/src/server/api/stream/channels/global-timeline.ts @@ -5,6 +5,9 @@ import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; export default class extends Channel { + public readonly chName = 'globalTimeline'; + public static shouldShare = true; + private mutedUserIds: string[] = []; @autobind diff --git a/src/server/api/stream/channels/hashtag.ts b/src/server/api/stream/channels/hashtag.ts index 652b0caa5b..cf652c166c 100644 --- a/src/server/api/stream/channels/hashtag.ts +++ b/src/server/api/stream/channels/hashtag.ts @@ -5,6 +5,9 @@ import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; export default class extends Channel { + public readonly chName = 'hashtag'; + public static shouldShare = false; + @autobind public async init(params: any) { const mute = this.user ? await Mute.find({ muterId: this.user._id }) : null; diff --git a/src/server/api/stream/channels/home-timeline.ts b/src/server/api/stream/channels/home-timeline.ts index 4c674e75ef..3fa887f1e5 100644 --- a/src/server/api/stream/channels/home-timeline.ts +++ b/src/server/api/stream/channels/home-timeline.ts @@ -5,6 +5,9 @@ import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; export default class extends Channel { + public readonly chName = 'homeTimeline'; + public static shouldShare = true; + private mutedUserIds: string[] = []; @autobind diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts index 0b12ab3a8f..d72545e4c8 100644 --- a/src/server/api/stream/channels/hybrid-timeline.ts +++ b/src/server/api/stream/channels/hybrid-timeline.ts @@ -5,6 +5,9 @@ import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; export default class extends Channel { + public readonly chName = 'hybridTimeline'; + public static shouldShare = true; + private mutedUserIds: string[] = []; @autobind diff --git a/src/server/api/stream/channels/local-timeline.ts b/src/server/api/stream/channels/local-timeline.ts index 769ec6392f..0ba0b1b195 100644 --- a/src/server/api/stream/channels/local-timeline.ts +++ b/src/server/api/stream/channels/local-timeline.ts @@ -5,6 +5,9 @@ import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; export default class extends Channel { + public readonly chName = 'localTimeline'; + public static shouldShare = true; + private mutedUserIds: string[] = []; @autobind diff --git a/src/server/api/stream/channels/main.ts b/src/server/api/stream/channels/main.ts index fd0984c833..7d5462c092 100644 --- a/src/server/api/stream/channels/main.ts +++ b/src/server/api/stream/channels/main.ts @@ -3,6 +3,9 @@ import Mute from '../../../../models/mute'; import Channel from '../channel'; export default class extends Channel { + public readonly chName = 'main'; + public static shouldShare = true; + @autobind public async init(params: any) { const mute = await Mute.find({ muterId: this.user._id }); diff --git a/src/server/api/stream/channels/messaging-index.ts b/src/server/api/stream/channels/messaging-index.ts index 6e87cca7f4..0211d702cf 100644 --- a/src/server/api/stream/channels/messaging-index.ts +++ b/src/server/api/stream/channels/messaging-index.ts @@ -2,6 +2,9 @@ import autobind from 'autobind-decorator'; import Channel from '../channel'; export default class extends Channel { + public readonly chName = 'messagingIndex'; + public static shouldShare = true; + @autobind public async init(params: any) { // Subscribe messaging index stream diff --git a/src/server/api/stream/channels/messaging.ts b/src/server/api/stream/channels/messaging.ts index e1a78c8678..ab04a332cf 100644 --- a/src/server/api/stream/channels/messaging.ts +++ b/src/server/api/stream/channels/messaging.ts @@ -3,6 +3,9 @@ import read from '../../common/read-messaging-message'; import Channel from '../channel'; export default class extends Channel { + public readonly chName = 'messaging'; + public static shouldShare = false; + private otherpartyId: string; @autobind diff --git a/src/server/api/stream/channels/notes-stats.ts b/src/server/api/stream/channels/notes-stats.ts index cc68d9886d..2282f8bc70 100644 --- a/src/server/api/stream/channels/notes-stats.ts +++ b/src/server/api/stream/channels/notes-stats.ts @@ -5,6 +5,9 @@ import Channel from '../channel'; const ev = new Xev(); export default class extends Channel { + public readonly chName = 'notesStats'; + public static shouldShare = true; + @autobind public async init(params: any) { ev.addListener('notesStats', this.onStats); diff --git a/src/server/api/stream/channels/server-stats.ts b/src/server/api/stream/channels/server-stats.ts index 28a566e8ae..912dcf5305 100644 --- a/src/server/api/stream/channels/server-stats.ts +++ b/src/server/api/stream/channels/server-stats.ts @@ -5,6 +5,9 @@ import Channel from '../channel'; const ev = new Xev(); export default class extends Channel { + public readonly chName = 'serverStats'; + public static shouldShare = true; + @autobind public async init(params: any) { ev.addListener('serverStats', this.onStats); diff --git a/src/server/api/stream/channels/user-list.ts b/src/server/api/stream/channels/user-list.ts index 4ace308923..b526a5f986 100644 --- a/src/server/api/stream/channels/user-list.ts +++ b/src/server/api/stream/channels/user-list.ts @@ -2,6 +2,9 @@ import autobind from 'autobind-decorator'; import Channel from '../channel'; export default class extends Channel { + public readonly chName = 'userList'; + public static shouldShare = false; + @autobind public async init(params: any) { const listId = params.listId as string; diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts index ef6397fcd9..e376b7a29e 100644 --- a/src/server/api/stream/index.ts +++ b/src/server/api/stream/index.ts @@ -1,6 +1,5 @@ import autobind from 'autobind-decorator'; import * as websocket from 'websocket'; -import Xev from 'xev'; import * as debug from 'debug'; import User, { IUser } from '../../../models/user'; @@ -11,6 +10,7 @@ import readNote from '../../../services/note/read'; import Channel from './channel'; import channels from './channels'; +import { EventEmitter } from 'events'; const log = debug('misskey'); @@ -21,14 +21,14 @@ export default class Connection { public user?: IUser; public app: IApp; private wsConnection: websocket.connection; - public subscriber: Xev; + public subscriber: EventEmitter; private channels: Channel[] = []; private subscribingNotes: any = {}; public sendMessageToWsOverride: any = null; // 後方互換性のため constructor( wsConnection: websocket.connection, - subscriber: Xev, + subscriber: EventEmitter, user: IUser, app: IApp ) { @@ -58,6 +58,7 @@ export default class Connection { case 'connect': this.onChannelConnectRequested(body); break; case 'disconnect': this.onChannelDisconnectRequested(body); break; case 'channel': this.onChannelMessageRequested(body); break; + case 'ch': this.onChannelMessageRequested(body); break; // alias } } @@ -145,9 +146,9 @@ export default class Connection { */ @autobind private onChannelConnectRequested(payload: any) { - const { channel, id, params } = payload; + const { channel, id, params, pong } = payload; log(`CH CONNECT: ${id} ${channel} by @${this.user.username}`); - this.connectChannel(id, params, (channels as any)[channel]); + this.connectChannel(id, params, channel, pong); } /** @@ -176,10 +177,21 @@ export default class Connection { * チャンネルに接続 */ @autobind - public connectChannel(id: string, params: any, channelClass: { new(id: string, connection: Connection): Channel }) { - const channel = new channelClass(id, this); - this.channels.push(channel); - channel.init(params); + public connectChannel(id: string, params: any, channel: string, pong = false) { + // 共有可能チャンネルに接続しようとしていて、かつそのチャンネルに既に接続していたら無意味なので無視 + if ((channels as any)[channel].shouldShare && this.channels.some(c => c.chName === channel)) { + return; + } + + const ch: Channel = new (channels as any)[channel](id, this); + this.channels.push(ch); + ch.init(params); + + if (pong) { + this.sendMessageToWs('connected', { + id: id + }); + } } /** diff --git a/src/server/api/streaming.ts b/src/server/api/streaming.ts index c8c4a8a294..8c0e6f6372 100644 --- a/src/server/api/streaming.ts +++ b/src/server/api/streaming.ts @@ -1,11 +1,13 @@ import * as http from 'http'; import * as websocket from 'websocket'; +import * as redis from 'redis'; import Xev from 'xev'; import MainStreamConnection from './stream'; import { ParsedUrlQuery } from 'querystring'; import authenticate from './authenticate'; -import channels from './stream/channels'; +import { EventEmitter } from 'events'; +import config from '../../config'; module.exports = (server: http.Server) => { // Init websocket server @@ -16,11 +18,34 @@ module.exports = (server: http.Server) => { ws.on('request', async (request) => { const connection = request.accept(); - const ev = new Xev(); - const q = request.resourceURL.query as ParsedUrlQuery; const [user, app] = await authenticate(q.i as string); + let ev: EventEmitter; + + if (config.redis) { + // Connect to Redis + const subscriber = redis.createClient( + config.redis.port, config.redis.host); + + subscriber.subscribe('misskey'); + + ev = new EventEmitter(); + + subscriber.on('message', async (_, data) => { + const obj = JSON.parse(data); + + ev.emit(obj.channel, obj.message); + }); + + connection.once('close', () => { + subscriber.unsubscribe(); + subscriber.quit(); + }); + } else { + ev = new Xev(); + } + const main = new MainStreamConnection(connection, ev, user, app); // 後方互換性のため @@ -39,11 +64,15 @@ module.exports = (server: http.Server) => { })); }; - main.connectChannel(Math.random().toString(), null, - request.resourceURL.pathname === '/' ? channels.homeTimeline : - request.resourceURL.pathname === '/local-timeline' ? channels.localTimeline : - request.resourceURL.pathname === '/hybrid-timeline' ? channels.hybridTimeline : - request.resourceURL.pathname === '/global-timeline' ? channels.globalTimeline : null); + main.connectChannel(Math.random().toString().substr(2, 8), null, + request.resourceURL.pathname === '/' ? 'homeTimeline' : + request.resourceURL.pathname === '/local-timeline' ? 'localTimeline' : + request.resourceURL.pathname === '/hybrid-timeline' ? 'hybridTimeline' : + request.resourceURL.pathname === '/global-timeline' ? 'globalTimeline' : null); + + if (request.resourceURL.pathname === '/') { + main.connectChannel(Math.random().toString().substr(2, 8), null, 'main'); + } } connection.once('close', () => { |