summaryrefslogtreecommitdiff
path: root/src/server/api/stream
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/api/stream')
-rw-r--r--src/server/api/stream/channel.ts8
-rw-r--r--src/server/api/stream/channels/admin.ts2
-rw-r--r--src/server/api/stream/channels/drive.ts2
-rw-r--r--src/server/api/stream/channels/games/reversi-game.ts168
-rw-r--r--src/server/api/stream/channels/games/reversi.ts13
-rw-r--r--src/server/api/stream/channels/global-timeline.ts18
-rw-r--r--src/server/api/stream/channels/hashtag.ts48
-rw-r--r--src/server/api/stream/channels/home-timeline.ts45
-rw-r--r--src/server/api/stream/channels/hybrid-timeline.ts55
-rw-r--r--src/server/api/stream/channels/index.ts4
-rw-r--r--src/server/api/stream/channels/local-timeline.ts45
-rw-r--r--src/server/api/stream/channels/main.ts9
-rw-r--r--src/server/api/stream/channels/messaging-index.ts2
-rw-r--r--src/server/api/stream/channels/messaging.ts4
-rw-r--r--src/server/api/stream/channels/social-timeline.ts64
-rw-r--r--src/server/api/stream/channels/user-list.ts72
-rw-r--r--src/server/api/stream/index.ts64
17 files changed, 368 insertions, 255 deletions
diff --git a/src/server/api/stream/channel.ts b/src/server/api/stream/channel.ts
index bdbe4605cf..18fa651820 100644
--- a/src/server/api/stream/channel.ts
+++ b/src/server/api/stream/channel.ts
@@ -15,6 +15,14 @@ export default abstract class Channel {
return this.connection.user;
}
+ protected get following() {
+ return this.connection.following;
+ }
+
+ protected get muting() {
+ return this.connection.muting;
+ }
+
protected get subscriber() {
return this.connection.subscriber;
}
diff --git a/src/server/api/stream/channels/admin.ts b/src/server/api/stream/channels/admin.ts
index 6bcd1a7e0b..e2eba10f78 100644
--- a/src/server/api/stream/channels/admin.ts
+++ b/src/server/api/stream/channels/admin.ts
@@ -9,7 +9,7 @@ export default class extends Channel {
@autobind
public async init(params: any) {
// Subscribe admin stream
- this.subscriber.on(`adminStream:${this.user._id}`, data => {
+ this.subscriber.on(`adminStream:${this.user.id}`, data => {
this.send(data);
});
}
diff --git a/src/server/api/stream/channels/drive.ts b/src/server/api/stream/channels/drive.ts
index 391c4b5c32..671aad4366 100644
--- a/src/server/api/stream/channels/drive.ts
+++ b/src/server/api/stream/channels/drive.ts
@@ -9,7 +9,7 @@ export default class extends Channel {
@autobind
public async init(params: any) {
// Subscribe drive stream
- this.subscriber.on(`driveStream:${this.user._id}`, data => {
+ this.subscriber.on(`driveStream:${this.user.id}`, data => {
this.send(data);
});
}
diff --git a/src/server/api/stream/channels/games/reversi-game.ts b/src/server/api/stream/channels/games/reversi-game.ts
index 87df9e194c..158f108c4e 100644
--- a/src/server/api/stream/channels/games/reversi-game.ts
+++ b/src/server/api/stream/channels/games/reversi-game.ts
@@ -1,22 +1,22 @@
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 '../../../../../services/stream';
import Reversi from '../../../../../games/reversi/core';
import * as maps from '../../../../../games/reversi/maps';
import Channel from '../../channel';
+import { ReversiGame } from '../../../../../models/entities/games/reversi/game';
+import { ReversiGames } from '../../../../../models';
export default class extends Channel {
public readonly chName = 'gamesReversiGame';
public static shouldShare = false;
public static requireCredential = false;
- private gameId: mongo.ObjectID;
+ private gameId: ReversiGame['id'];
@autobind
public async init(params: any) {
- this.gameId = new mongo.ObjectID(params.gameId as string);
+ this.gameId = params.gameId;
// Subscribe game stream
this.subscriber.on(`reversiGameStream:${this.gameId}`, data => {
@@ -29,7 +29,7 @@ export default class extends Channel {
switch (type) {
case 'accept': this.accept(true); break;
case 'cancelAccept': this.accept(false); break;
- case 'updateSettings': this.updateSettings(body.settings); break;
+ case 'updateSettings': this.updateSettings(body.key, body.value); break;
case 'initForm': this.initForm(body); break;
case 'updateForm': this.updateForm(body.id, body.value); break;
case 'message': this.message(body); break;
@@ -39,54 +39,55 @@ export default class extends Channel {
}
@autobind
- private async updateSettings(settings: any) {
- const game = await ReversiGame.findOne({ _id: this.gameId });
+ private async updateSettings(key: string, value: any) {
+ const game = await ReversiGames.findOne(this.gameId);
if (game.isStarted) return;
- if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return;
- if (game.user1Id.equals(this.user._id) && game.user1Accepted) return;
- if (game.user2Id.equals(this.user._id) && game.user2Accepted) return;
+ if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return;
+ if ((game.user1Id === this.user.id) && game.user1Accepted) return;
+ if ((game.user2Id === this.user.id) && game.user2Accepted) return;
- await ReversiGame.update({ _id: this.gameId }, {
- $set: {
- settings
- }
+ if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return;
+
+ await ReversiGames.update({ id: this.gameId }, {
+ [key]: value
});
- publishReversiGameStream(this.gameId, 'updateSettings', settings);
+ publishReversiGameStream(this.gameId, 'updateSettings', {
+ key: key,
+ value: value
+ });
}
@autobind
private async initForm(form: any) {
- const game = await ReversiGame.findOne({ _id: this.gameId });
+ const game = await ReversiGames.findOne(this.gameId);
if (game.isStarted) return;
- if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return;
+ if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return;
- const set = game.user1Id.equals(this.user._id) ? {
+ const set = game.user1Id === this.user.id ? {
form1: form
} : {
- form2: form
- };
+ form2: form
+ };
- await ReversiGame.update({ _id: this.gameId }, {
- $set: set
- });
+ await ReversiGames.update({ id: this.gameId }, set);
publishReversiGameStream(this.gameId, 'initForm', {
- userId: this.user._id,
+ userId: this.user.id,
form
});
}
@autobind
private async updateForm(id: string, value: any) {
- const game = await ReversiGame.findOne({ _id: this.gameId });
+ const game = await ReversiGames.findOne({ id: this.gameId });
if (game.isStarted) return;
- if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return;
+ if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return;
- const form = game.user1Id.equals(this.user._id) ? game.form2 : game.form1;
+ const form = game.user1Id === this.user.id ? game.form2 : game.form1;
const item = form.find((i: any) => i.id == id);
@@ -94,18 +95,16 @@ export default class extends Channel {
item.value = value;
- const set = game.user1Id.equals(this.user._id) ? {
+ const set = game.user1Id === this.user.id ? {
form2: form
} : {
form1: form
};
- await ReversiGame.update({ _id: this.gameId }, {
- $set: set
- });
+ await ReversiGames.update({ id: this.gameId }, set);
publishReversiGameStream(this.gameId, 'updateForm', {
- userId: this.user._id,
+ userId: this.user.id,
id,
value
});
@@ -115,24 +114,22 @@ export default class extends Channel {
private async message(message: any) {
message.id = Math.random();
publishReversiGameStream(this.gameId, 'message', {
- userId: this.user._id,
+ userId: this.user.id,
message
});
}
@autobind
private async accept(accept: boolean) {
- const game = await ReversiGame.findOne({ _id: this.gameId });
+ const game = await ReversiGames.findOne(this.gameId);
if (game.isStarted) return;
let bothAccepted = false;
- if (game.user1Id.equals(this.user._id)) {
- await ReversiGame.update({ _id: this.gameId }, {
- $set: {
- user1Accepted: accept
- }
+ if (game.user1Id === this.user.id) {
+ await ReversiGames.update({ id: this.gameId }, {
+ user1Accepted: accept
});
publishReversiGameStream(this.gameId, 'changeAccepts', {
@@ -141,11 +138,9 @@ export default class extends Channel {
});
if (accept && game.user2Accepted) bothAccepted = true;
- } else if (game.user2Id.equals(this.user._id)) {
- await ReversiGame.update({ _id: this.gameId }, {
- $set: {
- user2Accepted: accept
- }
+ } else if (game.user2Id === this.user.id) {
+ await ReversiGames.update({ id: this.gameId }, {
+ user2Accepted: accept
});
publishReversiGameStream(this.gameId, 'changeAccepts', {
@@ -161,15 +156,15 @@ export default class extends Channel {
if (bothAccepted) {
// 3秒後、まだacceptされていたらゲーム開始
setTimeout(async () => {
- const freshGame = await ReversiGame.findOne({ _id: this.gameId });
+ const freshGame = await ReversiGames.findOne(this.gameId);
if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return;
if (!freshGame.user1Accepted || !freshGame.user2Accepted) return;
let bw: number;
- if (freshGame.settings.bw == 'random') {
+ if (freshGame.bw == 'random') {
bw = Math.random() > 0.5 ? 1 : 2;
} else {
- bw = freshGame.settings.bw as number;
+ bw = parseInt(freshGame.bw, 10);
}
function getRandomMap() {
@@ -178,22 +173,20 @@ export default class extends Channel {
return Object.values(maps)[rnd].data;
}
- const map = freshGame.settings.map != null ? freshGame.settings.map : getRandomMap();
+ const map = freshGame.map != null ? freshGame.map : getRandomMap();
- await ReversiGame.update({ _id: this.gameId }, {
- $set: {
- startedAt: new Date(),
- isStarted: true,
- black: bw,
- 'settings.map': map
- }
+ await ReversiGames.update({ id: this.gameId }, {
+ startedAt: new Date(),
+ isStarted: true,
+ black: bw,
+ map: map
});
//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
const o = new Reversi(map, {
- isLlotheo: freshGame.settings.isLlotheo,
- canPutEverywhere: freshGame.settings.canPutEverywhere,
- loopedBoard: freshGame.settings.loopedBoard
+ isLlotheo: freshGame.isLlotheo,
+ canPutEverywhere: freshGame.canPutEverywhere,
+ loopedBoard: freshGame.loopedBoard
});
if (o.isEnded) {
@@ -206,23 +199,22 @@ export default class extends Channel {
winner = null;
}
- await ReversiGame.update({
- _id: this.gameId
+ await ReversiGames.update({
+ id: this.gameId
}, {
- $set: {
- isEnded: true,
- winnerId: winner
- }
- });
+ isEnded: true,
+ winnerId: winner
+ });
publishReversiGameStream(this.gameId, 'ended', {
winnerId: winner,
- game: await pack(this.gameId, this.user)
+ game: await ReversiGames.pack(this.gameId, this.user)
});
}
//#endregion
- publishReversiGameStream(this.gameId, 'started', await pack(this.gameId, this.user));
+ publishReversiGameStream(this.gameId, 'started',
+ await ReversiGames.pack(this.gameId, this.user));
}, 3000);
}
}
@@ -230,16 +222,16 @@ export default class extends Channel {
// 石を打つ
@autobind
private async set(pos: number) {
- const game = await ReversiGame.findOne({ _id: this.gameId });
+ const game = await ReversiGames.findOne(this.gameId);
if (!game.isStarted) return;
if (game.isEnded) return;
- if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return;
+ if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return;
- const o = new Reversi(game.settings.map, {
- isLlotheo: game.settings.isLlotheo,
- canPutEverywhere: game.settings.canPutEverywhere,
- loopedBoard: game.settings.loopedBoard
+ const o = new Reversi(game.map, {
+ isLlotheo: game.isLlotheo,
+ canPutEverywhere: game.canPutEverywhere,
+ loopedBoard: game.loopedBoard
});
for (const log of game.logs) {
@@ -247,7 +239,7 @@ export default class extends Channel {
}
const myColor =
- (game.user1Id.equals(this.user._id) && game.black == 1) || (game.user2Id.equals(this.user._id) && game.black == 2)
+ ((game.user1Id === this.user.id) && game.black == 1) || ((game.user2Id === this.user.id) && game.black == 2)
? true
: false;
@@ -271,20 +263,18 @@ export default class extends Channel {
pos
};
- const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString());
+ const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()).toString();
- await ReversiGame.update({
- _id: this.gameId
+ game.logs.push(log);
+
+ await ReversiGames.update({
+ id: this.gameId
}, {
- $set: {
- crc32,
- isEnded: o.isEnded,
- winnerId: winner
- },
- $push: {
- logs: log
- }
- });
+ crc32,
+ isEnded: o.isEnded,
+ winnerId: winner,
+ logs: game.logs
+ });
publishReversiGameStream(this.gameId, 'set', Object.assign(log, {
next: o.turn
@@ -293,14 +283,14 @@ export default class extends Channel {
if (o.isEnded) {
publishReversiGameStream(this.gameId, 'ended', {
winnerId: winner,
- game: await pack(this.gameId, this.user)
+ game: await ReversiGames.pack(this.gameId, this.user)
});
}
}
@autobind
private async check(crc32: string) {
- const game = await ReversiGame.findOne({ _id: this.gameId });
+ const game = await ReversiGames.findOne(this.gameId);
if (!game.isStarted) return;
@@ -308,7 +298,7 @@ export default class extends Channel {
if (game.crc32 == null) return;
if (crc32 !== game.crc32) {
- this.send('rescue', await pack(game, this.user));
+ this.send('rescue', await ReversiGames.pack(game, this.user));
}
}
}
diff --git a/src/server/api/stream/channels/games/reversi.ts b/src/server/api/stream/channels/games/reversi.ts
index 1b1ad187a3..0498e5e017 100644
--- a/src/server/api/stream/channels/games/reversi.ts
+++ b/src/server/api/stream/channels/games/reversi.ts
@@ -1,8 +1,7 @@
import autobind from 'autobind-decorator';
-import * as mongo from 'mongodb';
-import Matching, { pack } from '../../../../../models/games/reversi/matching';
import { publishMainStream } from '../../../../../services/stream';
import Channel from '../../channel';
+import { ReversiMatchings } from '../../../../../models';
export default class extends Channel {
public readonly chName = 'gamesReversi';
@@ -12,7 +11,7 @@ export default class extends Channel {
@autobind
public async init(params: any) {
// Subscribe reversi stream
- this.subscriber.on(`reversiStream:${this.user._id}`, data => {
+ this.subscriber.on(`reversiStream:${this.user.id}`, data => {
this.send(data);
});
}
@@ -22,12 +21,12 @@ export default class extends Channel {
switch (type) {
case 'ping':
if (body.id == null) return;
- const matching = await Matching.findOne({
- parentId: this.user._id,
- childId: new mongo.ObjectID(body.id)
+ const matching = await ReversiMatchings.findOne({
+ parentId: this.user.id,
+ childId: body.id
});
if (matching == null) return;
- publishMainStream(matching.childId, 'reversiInvited', await pack(matching, matching.childId));
+ publishMainStream(matching.childId, 'reversiInvited', await ReversiMatchings.pack(matching, matching.childId));
break;
}
}
diff --git a/src/server/api/stream/channels/global-timeline.ts b/src/server/api/stream/channels/global-timeline.ts
index b3689d47f5..bfb7697ba7 100644
--- a/src/server/api/stream/channels/global-timeline.ts
+++ b/src/server/api/stream/channels/global-timeline.ts
@@ -1,17 +1,14 @@
import autobind from 'autobind-decorator';
-import Mute from '../../../../models/mute';
-import { pack } from '../../../../models/note';
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
import Channel from '../channel';
import fetchMeta from '../../../../misc/fetch-meta';
+import { Notes } from '../../../../models';
export default class extends Channel {
public readonly chName = 'globalTimeline';
public static shouldShare = true;
public static requireCredential = false;
- private mutedUserIds: string[] = [];
-
@autobind
public async init(params: any) {
const meta = await fetchMeta();
@@ -20,29 +17,26 @@ export default class extends Channel {
}
// Subscribe events
- this.subscriber.on('globalTimeline', this.onNote);
-
- const mute = await Mute.find({ muterId: this.user._id });
- this.mutedUserIds = mute.map(m => m.muteeId.toString());
+ this.subscriber.on('notesStream', this.onNote);
}
@autobind
private async onNote(note: any) {
// リプライなら再pack
if (note.replyId != null) {
- note.reply = await pack(note.replyId, this.user, {
+ note.reply = await Notes.pack(note.replyId, this.user, {
detail: true
});
}
// Renoteなら再pack
if (note.renoteId != null) {
- note.renote = await pack(note.renoteId, this.user, {
+ note.renote = await Notes.pack(note.renoteId, this.user, {
detail: true
});
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (shouldMuteThisNote(note, this.mutedUserIds)) return;
+ if (shouldMuteThisNote(note, this.muting)) return;
this.send('note', note);
}
@@ -50,6 +44,6 @@ export default class extends Channel {
@autobind
public dispose() {
// Unsubscribe events
- this.subscriber.off('globalTimeline', this.onNote);
+ this.subscriber.off('notesStream', this.onNote);
}
}
diff --git a/src/server/api/stream/channels/hashtag.ts b/src/server/api/stream/channels/hashtag.ts
index 586ce02f06..36c56c7ab6 100644
--- a/src/server/api/stream/channels/hashtag.ts
+++ b/src/server/api/stream/channels/hashtag.ts
@@ -1,40 +1,46 @@
import autobind from 'autobind-decorator';
-import Mute from '../../../../models/mute';
-import { pack } from '../../../../models/note';
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
import Channel from '../channel';
+import { Notes } from '../../../../models';
export default class extends Channel {
public readonly chName = 'hashtag';
public static shouldShare = false;
public static requireCredential = false;
+ private q: string[][];
@autobind
public async init(params: any) {
- const mute = this.user ? await Mute.find({ muterId: this.user._id }) : null;
- const mutedUserIds = mute ? mute.map(m => m.muteeId.toString()) : [];
+ this.q = params.q;
- const q: string[][] = params.q;
-
- if (q == null) return;
+ if (this.q == null) return;
// Subscribe stream
- this.subscriber.on('hashtag', async note => {
- const noteTags = note.tags.map((t: string) => t.toLowerCase());
- const matched = q.some(tags => tags.every(tag => noteTags.includes(tag.toLowerCase())));
- if (!matched) return;
+ this.subscriber.on('notesStream', this.onNote);
+ }
+
+ @autobind
+ private async onNote(note: any) {
+ const noteTags = note.tags.map((t: string) => t.toLowerCase());
+ const matched = this.q.some(tags => tags.every(tag => noteTags.includes(tag.toLowerCase())));
+ if (!matched) return;
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await pack(note.renoteId, this.user, {
- detail: true
- });
- }
+ // Renoteなら再pack
+ if (note.renoteId != null) {
+ note.renote = await Notes.pack(note.renoteId, this.user, {
+ detail: true
+ });
+ }
- // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (shouldMuteThisNote(note, mutedUserIds)) return;
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (shouldMuteThisNote(note, this.muting)) return;
- this.send('note', note);
- });
+ this.send('note', note);
+ }
+
+ @autobind
+ public dispose() {
+ // Unsubscribe events
+ this.subscriber.off('notesStream', this.onNote);
}
}
diff --git a/src/server/api/stream/channels/home-timeline.ts b/src/server/api/stream/channels/home-timeline.ts
index 3c0b238720..2cece0947f 100644
--- a/src/server/api/stream/channels/home-timeline.ts
+++ b/src/server/api/stream/channels/home-timeline.ts
@@ -1,42 +1,49 @@
import autobind from 'autobind-decorator';
-import Mute from '../../../../models/mute';
-import { pack } from '../../../../models/note';
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
import Channel from '../channel';
+import { Notes } from '../../../../models';
export default class extends Channel {
public readonly chName = 'homeTimeline';
public static shouldShare = true;
public static requireCredential = true;
- private mutedUserIds: string[] = [];
-
@autobind
public async init(params: any) {
// Subscribe events
- this.subscriber.on(`homeTimeline:${this.user._id}`, this.onNote);
-
- const mute = await Mute.find({ muterId: this.user._id });
- this.mutedUserIds = mute.map(m => m.muteeId.toString());
+ this.subscriber.on('notesStream', this.onNote);
}
@autobind
private async onNote(note: any) {
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await pack(note.replyId, this.user, {
- detail: true
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await pack(note.renoteId, this.user, {
+ // その投稿のユーザーをフォローしていなかったら弾く
+ if (this.user.id !== note.userId && !this.following.includes(note.userId)) return;
+
+ if (['followers', 'specified'].includes(note.visibility)) {
+ note = await Notes.pack(note.id, this.user, {
detail: true
});
+
+ if (note.isHidden) {
+ return;
+ }
+ } else {
+ // リプライなら再pack
+ if (note.replyId != null) {
+ note.reply = await Notes.pack(note.replyId, this.user, {
+ detail: true
+ });
+ }
+ // Renoteなら再pack
+ if (note.renoteId != null) {
+ note.renote = await Notes.pack(note.renoteId, this.user, {
+ detail: true
+ });
+ }
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (shouldMuteThisNote(note, this.mutedUserIds)) return;
+ if (shouldMuteThisNote(note, this.muting)) return;
this.send('note', note);
}
@@ -44,6 +51,6 @@ export default class extends Channel {
@autobind
public dispose() {
// Unsubscribe events
- this.subscriber.off(`homeTimeline:${this.user._id}`, this.onNote);
+ this.subscriber.off('notesStream', this.onNote);
}
}
diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts
deleted file mode 100644
index 35ef17b56b..0000000000
--- a/src/server/api/stream/channels/hybrid-timeline.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import autobind from 'autobind-decorator';
-import Mute from '../../../../models/mute';
-import { pack } from '../../../../models/note';
-import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
-import Channel from '../channel';
-import fetchMeta from '../../../../misc/fetch-meta';
-
-export default class extends Channel {
- public readonly chName = 'hybridTimeline';
- public static shouldShare = true;
- public static requireCredential = true;
-
- private mutedUserIds: string[] = [];
-
- @autobind
- public async init(params: any) {
- const meta = await fetchMeta();
- if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return;
-
- // Subscribe events
- this.subscriber.on('hybridTimeline', this.onNewNote);
- this.subscriber.on(`hybridTimeline:${this.user._id}`, this.onNewNote);
-
- const mute = await Mute.find({ muterId: this.user._id });
- this.mutedUserIds = mute.map(m => m.muteeId.toString());
- }
-
- @autobind
- private async onNewNote(note: any) {
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await pack(note.replyId, this.user, {
- detail: true
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await pack(note.renoteId, this.user, {
- detail: true
- });
- }
-
- // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (shouldMuteThisNote(note, this.mutedUserIds)) return;
-
- this.send('note', note);
- }
-
- @autobind
- public dispose() {
- // Unsubscribe events
- this.subscriber.off('hybridTimeline', this.onNewNote);
- this.subscriber.off(`hybridTimeline:${this.user._id}`, this.onNewNote);
- }
-}
diff --git a/src/server/api/stream/channels/index.ts b/src/server/api/stream/channels/index.ts
index 4527fb1e46..199ab0a809 100644
--- a/src/server/api/stream/channels/index.ts
+++ b/src/server/api/stream/channels/index.ts
@@ -1,7 +1,7 @@
import main from './main';
import homeTimeline from './home-timeline';
import localTimeline from './local-timeline';
-import hybridTimeline from './hybrid-timeline';
+import socialTimeline from './social-timeline';
import globalTimeline from './global-timeline';
import notesStats from './notes-stats';
import serverStats from './server-stats';
@@ -20,7 +20,7 @@ export default {
main,
homeTimeline,
localTimeline,
- hybridTimeline,
+ socialTimeline,
globalTimeline,
notesStats,
serverStats,
diff --git a/src/server/api/stream/channels/local-timeline.ts b/src/server/api/stream/channels/local-timeline.ts
index 3402023192..4aec2d66b4 100644
--- a/src/server/api/stream/channels/local-timeline.ts
+++ b/src/server/api/stream/channels/local-timeline.ts
@@ -1,17 +1,14 @@
import autobind from 'autobind-decorator';
-import Mute from '../../../../models/mute';
-import { pack } from '../../../../models/note';
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
import Channel from '../channel';
import fetchMeta from '../../../../misc/fetch-meta';
+import { Notes } from '../../../../models';
export default class extends Channel {
public readonly chName = 'localTimeline';
public static shouldShare = true;
public static requireCredential = false;
- private mutedUserIds: string[] = [];
-
@autobind
public async init(params: any) {
const meta = await fetchMeta();
@@ -20,29 +17,39 @@ export default class extends Channel {
}
// Subscribe events
- this.subscriber.on('localTimeline', this.onNote);
-
- const mute = this.user ? await Mute.find({ muterId: this.user._id }) : null;
- this.mutedUserIds = mute ? mute.map(m => m.muteeId.toString()) : [];
+ this.subscriber.on('notesStream', this.onNote);
}
@autobind
private async onNote(note: any) {
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await pack(note.replyId, this.user, {
- detail: true
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await pack(note.renoteId, this.user, {
+ if (note.user.host !== null) return;
+ if (note.visibility === 'home') return;
+
+ if (['followers', 'specified'].includes(note.visibility)) {
+ note = await Notes.pack(note.id, this.user, {
detail: true
});
+
+ if (note.isHidden) {
+ return;
+ }
+ } else {
+ // リプライなら再pack
+ if (note.replyId != null) {
+ note.reply = await Notes.pack(note.replyId, this.user, {
+ detail: true
+ });
+ }
+ // Renoteなら再pack
+ if (note.renoteId != null) {
+ note.renote = await Notes.pack(note.renoteId, this.user, {
+ detail: true
+ });
+ }
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (shouldMuteThisNote(note, this.mutedUserIds)) return;
+ if (shouldMuteThisNote(note, this.muting)) return;
this.send('note', note);
}
@@ -50,6 +57,6 @@ export default class extends Channel {
@autobind
public dispose() {
// Unsubscribe events
- this.subscriber.off('localTimeline', this.onNote);
+ this.subscriber.off('notesStream', this.onNote);
}
}
diff --git a/src/server/api/stream/channels/main.ts b/src/server/api/stream/channels/main.ts
index 175d914fa5..0d9bf3149d 100644
--- a/src/server/api/stream/channels/main.ts
+++ b/src/server/api/stream/channels/main.ts
@@ -1,6 +1,6 @@
import autobind from 'autobind-decorator';
-import Mute from '../../../../models/mute';
import Channel from '../channel';
+import { Mutings } from '../../../../models';
export default class extends Channel {
public readonly chName = 'main';
@@ -9,16 +9,15 @@ export default class extends Channel {
@autobind
public async init(params: any) {
- const mute = await Mute.find({ muterId: this.user._id });
- const mutedUserIds = mute.map(m => m.muteeId.toString());
+ const mute = await Mutings.find({ muterId: this.user.id });
// Subscribe main stream channel
- this.subscriber.on(`mainStream:${this.user._id}`, async data => {
+ this.subscriber.on(`mainStream:${this.user.id}`, async data => {
const { type, body } = data;
switch (type) {
case 'notification': {
- if (mutedUserIds.includes(body.userId)) return;
+ if (mute.map(m => m.muteeId).includes(body.userId)) return;
if (body.note && body.note.isHidden) return;
break;
}
diff --git a/src/server/api/stream/channels/messaging-index.ts b/src/server/api/stream/channels/messaging-index.ts
index 148ff7f935..648badc1dc 100644
--- a/src/server/api/stream/channels/messaging-index.ts
+++ b/src/server/api/stream/channels/messaging-index.ts
@@ -9,7 +9,7 @@ export default class extends Channel {
@autobind
public async init(params: any) {
// Subscribe messaging index stream
- this.subscriber.on(`messagingIndexStream:${this.user._id}`, data => {
+ this.subscriber.on(`messagingIndexStream:${this.user.id}`, data => {
this.send(data);
});
}
diff --git a/src/server/api/stream/channels/messaging.ts b/src/server/api/stream/channels/messaging.ts
index 0d81b4e45c..b81fbb9d4c 100644
--- a/src/server/api/stream/channels/messaging.ts
+++ b/src/server/api/stream/channels/messaging.ts
@@ -14,7 +14,7 @@ export default class extends Channel {
this.otherpartyId = params.otherparty as string;
// Subscribe messaging stream
- this.subscriber.on(`messagingStream:${this.user._id}-${this.otherpartyId}`, data => {
+ this.subscriber.on(`messagingStream:${this.user.id}-${this.otherpartyId}`, data => {
this.send(data);
});
}
@@ -23,7 +23,7 @@ export default class extends Channel {
public onMessage(type: string, body: any) {
switch (type) {
case 'read':
- read(this.user._id, this.otherpartyId, body.id);
+ read(this.user.id, this.otherpartyId, body.id);
break;
}
}
diff --git a/src/server/api/stream/channels/social-timeline.ts b/src/server/api/stream/channels/social-timeline.ts
new file mode 100644
index 0000000000..1d76eed297
--- /dev/null
+++ b/src/server/api/stream/channels/social-timeline.ts
@@ -0,0 +1,64 @@
+import autobind from 'autobind-decorator';
+import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
+import Channel from '../channel';
+import fetchMeta from '../../../../misc/fetch-meta';
+import { Notes } from '../../../../models';
+
+export default class extends Channel {
+ public readonly chName = 'socialTimeline';
+ public static shouldShare = true;
+ public static requireCredential = true;
+
+ @autobind
+ public async init(params: any) {
+ const meta = await fetchMeta();
+ if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return;
+
+ // Subscribe events
+ this.subscriber.on('notesStream', this.onNote);
+ }
+
+ @autobind
+ private async onNote(note: any) {
+ // 自分自身の投稿 または その投稿のユーザーをフォローしている または ローカルの投稿 の場合だけ
+ if (!(
+ this.user.id === note.userId ||
+ this.following.includes(note.userId) ||
+ note.user.host === null
+ )) return;
+
+ if (['followers', 'specified'].includes(note.visibility)) {
+ note = await Notes.pack(note.id, this.user, {
+ detail: true
+ });
+
+ if (note.isHidden) {
+ return;
+ }
+ } else {
+ // リプライなら再pack
+ if (note.replyId != null) {
+ note.reply = await Notes.pack(note.replyId, this.user, {
+ detail: true
+ });
+ }
+ // Renoteなら再pack
+ if (note.renoteId != null) {
+ note.renote = await Notes.pack(note.renoteId, this.user, {
+ detail: true
+ });
+ }
+ }
+
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (shouldMuteThisNote(note, this.muting)) return;
+
+ this.send('note', note);
+ }
+
+ @autobind
+ public dispose() {
+ // Unsubscribe events
+ this.subscriber.off('notesStream', this.onNote);
+ }
+}
diff --git a/src/server/api/stream/channels/user-list.ts b/src/server/api/stream/channels/user-list.ts
index 5debf41770..f5434b8f08 100644
--- a/src/server/api/stream/channels/user-list.ts
+++ b/src/server/api/stream/channels/user-list.ts
@@ -1,23 +1,81 @@
import autobind from 'autobind-decorator';
import Channel from '../channel';
-import { pack } from '../../../../models/note';
+import { Notes, UserListJoinings } from '../../../../models';
+import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
+import { User } from '../../../../models/entities/user';
export default class extends Channel {
public readonly chName = 'userList';
public static shouldShare = false;
public static requireCredential = false;
+ private listId: string;
+ public listUsers: User['id'][] = [];
+ private listUsersClock: NodeJS.Timer;
@autobind
public async init(params: any) {
- const listId = params.listId as string;
+ this.listId = params.listId as string;
// Subscribe stream
- this.subscriber.on(`userListStream:${listId}`, async data => {
- // 再パック
- if (data.type == 'note') data.body = await pack(data.body.id, this.user, {
+ this.subscriber.on(`userListStream:${this.listId}`, this.send);
+
+ this.subscriber.on('notesStream', this.onNote);
+
+ this.updateListUsers();
+ this.listUsersClock = setInterval(this.updateListUsers, 5000);
+ }
+
+ @autobind
+ private async updateListUsers() {
+ const users = await UserListJoinings.find({
+ where: {
+ userListId: this.listId,
+ },
+ select: ['userId']
+ });
+
+ this.listUsers = users.map(x => x.userId);
+ }
+
+ @autobind
+ private async onNote(note: any) {
+ if (!this.listUsers.includes(note.userId)) return;
+
+ if (['followers', 'specified'].includes(note.visibility)) {
+ note = await Notes.pack(note.id, this.user, {
detail: true
});
- this.send(data);
- });
+
+ if (note.isHidden) {
+ return;
+ }
+ } else {
+ // リプライなら再pack
+ if (note.replyId != null) {
+ note.reply = await Notes.pack(note.replyId, this.user, {
+ detail: true
+ });
+ }
+ // Renoteなら再pack
+ if (note.renoteId != null) {
+ note.renote = await Notes.pack(note.renoteId, this.user, {
+ detail: true
+ });
+ }
+ }
+
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (shouldMuteThisNote(note, this.muting)) return;
+
+ this.send('note', note);
+ }
+
+ @autobind
+ public dispose() {
+ // Unsubscribe events
+ this.subscriber.off(`userListStream:${this.listId}`, this.send);
+ this.subscriber.off('notesStream', this.onNote);
+
+ clearInterval(this.listUsersClock);
}
}
diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts
index 22f7646cb9..abbd91ec81 100644
--- a/src/server/api/stream/index.ts
+++ b/src/server/api/stream/index.ts
@@ -1,33 +1,35 @@
import autobind from 'autobind-decorator';
import * as websocket from 'websocket';
-
-import User, { IUser } from '../../../models/user';
-import readNotification from '../common/read-notification';
+import { readNotification } from '../common/read-notification';
import call from '../call';
-import { IApp } from '../../../models/app';
import readNote from '../../../services/note/read';
-
import Channel from './channel';
import channels from './channels';
import { EventEmitter } from 'events';
+import { User } from '../../../models/entities/user';
+import { App } from '../../../models/entities/app';
+import { Users, Followings, Mutings } from '../../../models';
/**
* Main stream connection
*/
export default class Connection {
- public user?: IUser;
- public app: IApp;
+ public user?: User;
+ public following: User['id'][] = [];
+ public muting: User['id'][] = [];
+ public app: App;
private wsConnection: websocket.connection;
public subscriber: EventEmitter;
private channels: Channel[] = [];
private subscribingNotes: any = {};
- public sendMessageToWsOverride: any = null; // 後方互換性のため
+ private followingClock: NodeJS.Timer;
+ private mutingClock: NodeJS.Timer;
constructor(
wsConnection: websocket.connection,
subscriber: EventEmitter,
- user: IUser,
- app: IApp
+ user: User,
+ app: App
) {
this.wsConnection = wsConnection;
this.user = user;
@@ -35,6 +37,14 @@ export default class Connection {
this.subscriber = subscriber;
this.wsConnection.on('message', this.onWsConnectionMessage);
+
+ if (this.user) {
+ this.updateFollowing();
+ this.followingClock = setInterval(this.updateFollowing, 5000);
+
+ this.updateMuting();
+ this.mutingClock = setInterval(this.updateMuting, 5000);
+ }
}
/**
@@ -64,7 +74,7 @@ export default class Connection {
@autobind
private async onApiRequest(payload: any) {
// 新鮮なデータを利用するためにユーザーをフェッチ
- const user = this.user ? await User.findOne({ _id: this.user._id }) : null;
+ const user = this.user ? await Users.findOne(this.user.id) : null;
const endpoint = payload.endpoint || payload.ep; // alias
@@ -79,7 +89,7 @@ export default class Connection {
@autobind
private onReadNotification(payload: any) {
if (!payload.id) return;
- readNotification(this.user._id, payload.id);
+ readNotification(this.user.id, [payload.id]);
}
/**
@@ -100,7 +110,7 @@ export default class Connection {
}
if (payload.read) {
- readNote(this.user._id, payload.id);
+ readNote(this.user.id, payload.id);
}
}
@@ -150,7 +160,6 @@ export default class Connection {
*/
@autobind
public sendMessageToWs(type: string, payload: any) {
- if (this.sendMessageToWsOverride) return this.sendMessageToWsOverride(type, payload); // 後方互換性のため
this.wsConnection.send(JSON.stringify({
type: type,
body: payload
@@ -208,6 +217,30 @@ export default class Connection {
}
}
+ @autobind
+ private async updateFollowing() {
+ const followings = await Followings.find({
+ where: {
+ followerId: this.user.id
+ },
+ select: ['followeeId']
+ });
+
+ this.following = followings.map(x => x.followeeId);
+ }
+
+ @autobind
+ private async updateMuting() {
+ const mutings = await Mutings.find({
+ where: {
+ muterId: this.user.id
+ },
+ select: ['muteeId']
+ });
+
+ this.muting = mutings.map(x => x.muteeId);
+ }
+
/**
* ストリームが切れたとき
*/
@@ -216,5 +249,8 @@ export default class Connection {
for (const c of this.channels.filter(c => c.dispose)) {
c.dispose();
}
+
+ if (this.followingClock) clearInterval(this.followingClock);
+ if (this.mutingClock) clearInterval(this.mutingClock);
}
}