Fixed 267U.pre2
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
import Channel from "../channel.js";
|
||||
export default class extends Channel {
|
||||
chName = "admin";
|
||||
static shouldShare = true;
|
||||
static requireCredential = true;
|
||||
async init(params) {
|
||||
// Subscribe admin stream
|
||||
this.subscriber.on(`adminStream:${this.user.id}`, (data)=>{
|
||||
this.send(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import Channel from "../channel.js";
|
||||
import { Notes } from "../../../../models/index.js";
|
||||
import { isUserRelated } from "../../../../misc/is-user-related.js";
|
||||
import { IdentifiableError } from "../../../../misc/identifiable-error.js";
|
||||
export default class extends Channel {
|
||||
chName = "antenna";
|
||||
static shouldShare = false;
|
||||
static requireCredential = false;
|
||||
antennaId;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onEvent = this.onEvent.bind(this);
|
||||
}
|
||||
async init(params) {
|
||||
this.antennaId = params.antennaId;
|
||||
// Subscribe stream
|
||||
this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent);
|
||||
}
|
||||
async onEvent(data) {
|
||||
if (data.type === "note") {
|
||||
try {
|
||||
const note = await Notes.pack(data.body.id, this.user, {
|
||||
detail: true
|
||||
});
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.muting)) return;
|
||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.blocking)) return;
|
||||
if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
|
||||
this.connection.cacheNote(note);
|
||||
this.send("note", note);
|
||||
} catch (e) {
|
||||
if (e instanceof IdentifiableError && e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") {
|
||||
// skip: note not visible to user
|
||||
return;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.send(data.type, data.body);
|
||||
}
|
||||
}
|
||||
dispose() {
|
||||
// Unsubscribe events
|
||||
this.subscriber.off(`antennaStream:${this.antennaId}`, this.onEvent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import Channel from "../channel.js";
|
||||
import { Users } from "../../../../models/index.js";
|
||||
import { isUserRelated } from "../../../../misc/is-user-related.js";
|
||||
export default class extends Channel {
|
||||
chName = "channel";
|
||||
static shouldShare = false;
|
||||
static requireCredential = false;
|
||||
channelId;
|
||||
typers = new Map();
|
||||
emitTypersIntervalId;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onNote = this.withPackedNote(this.onNote.bind(this));
|
||||
this.emitTypers = this.emitTypers.bind(this);
|
||||
}
|
||||
async init(params) {
|
||||
this.channelId = params.channelId;
|
||||
// Subscribe stream
|
||||
this.subscriber.on("notesStream", this.onNote);
|
||||
this.subscriber.on(`channelStream:${this.channelId}`, this.onEvent);
|
||||
this.emitTypersIntervalId = setInterval(this.emitTypers, 5000);
|
||||
}
|
||||
async onNote(note) {
|
||||
if (note.visibility === "hidden") return;
|
||||
if (note.channelId !== this.channelId) return;
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.muting)) return;
|
||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.blocking)) return;
|
||||
if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
|
||||
this.connection.cacheNote(note);
|
||||
this.send("note", note);
|
||||
}
|
||||
onEvent(data) {
|
||||
if (data.type === "typing") {
|
||||
const id = data.body;
|
||||
const begin = !this.typers.has(id);
|
||||
this.typers.set(id, new Date());
|
||||
if (begin) {
|
||||
this.emitTypers();
|
||||
}
|
||||
}
|
||||
}
|
||||
async emitTypers() {
|
||||
const now = new Date();
|
||||
// Remove not typing users
|
||||
for (const [userId, date] of Object.entries(this.typers)){
|
||||
if (now.getTime() - date.getTime() > 5000) this.typers.delete(userId);
|
||||
}
|
||||
const userIds = Array.from(this.typers.keys());
|
||||
const users = await Users.packMany(userIds, null, {
|
||||
detail: false
|
||||
});
|
||||
this.send({
|
||||
type: "typers",
|
||||
body: users
|
||||
});
|
||||
}
|
||||
dispose() {
|
||||
// Unsubscribe events
|
||||
this.subscriber.off("notesStream", this.onNote);
|
||||
this.subscriber.off(`channelStream:${this.channelId}`, this.onEvent);
|
||||
clearInterval(this.emitTypersIntervalId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import Channel from "../channel.js";
|
||||
export default class extends Channel {
|
||||
chName = "drive";
|
||||
static shouldShare = true;
|
||||
static requireCredential = true;
|
||||
async init(params) {
|
||||
// Subscribe drive stream
|
||||
this.subscriber.on(`driveStream:${this.user.id}`, (data)=>{
|
||||
this.send(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import Channel from "../channel.js";
|
||||
import { fetchMeta } from "../../../../misc/fetch-meta.js";
|
||||
import { isInstanceMuted } from "../../../../misc/is-instance-muted.js";
|
||||
import { isUserRelated } from "../../../../misc/is-user-related.js";
|
||||
import { isFiltered } from "../../../../misc/is-filtered.js";
|
||||
export default class extends Channel {
|
||||
chName = "globalTimeline";
|
||||
static shouldShare = true;
|
||||
static requireCredential = false;
|
||||
withReplies;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onNote = this.withPackedNote(this.onNote.bind(this));
|
||||
}
|
||||
async init(params) {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.disableGlobalTimeline) {
|
||||
if (this.user == null || !(this.user.isAdmin || this.user.isModerator)) return;
|
||||
}
|
||||
this.withReplies = params.withReplies;
|
||||
// Subscribe events
|
||||
this.subscriber.on("notesStream", this.onNote);
|
||||
}
|
||||
async onNote(note) {
|
||||
if (note.visibility !== "public") return;
|
||||
if (note.channelId != null) return;
|
||||
// 関係ない返信は除外
|
||||
if (note.reply && !this.withReplies) {
|
||||
const reply = note.reply;
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
|
||||
}
|
||||
// Ignore notes from instances the user has muted
|
||||
if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return;
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.muting)) return;
|
||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.blocking)) return;
|
||||
if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
||||
if (this.userProfile && await isFiltered(note, this.user, this.userProfile)) return;
|
||||
this.connection.cacheNote(note);
|
||||
this.send("note", note);
|
||||
}
|
||||
dispose() {
|
||||
// Unsubscribe events
|
||||
this.subscriber.off("notesStream", this.onNote);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import Channel from "../channel.js";
|
||||
import { isUserRelated } from "../../../../misc/is-user-related.js";
|
||||
import { isInstanceMuted } from "../../../../misc/is-instance-muted.js";
|
||||
import { isFiltered } from "../../../../misc/is-filtered.js";
|
||||
export default class extends Channel {
|
||||
chName = "homeTimeline";
|
||||
static shouldShare = true;
|
||||
static requireCredential = true;
|
||||
withReplies;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onNote = this.withPackedNote(this.onNote.bind(this));
|
||||
}
|
||||
async init(params) {
|
||||
this.withReplies = params.withReplies;
|
||||
// Subscribe events
|
||||
this.subscriber.on("notesStream", this.onNote);
|
||||
}
|
||||
async onNote(note) {
|
||||
if (note.visibility === "hidden") return;
|
||||
if (note.channelId) {
|
||||
if (!this.followingChannels.has(note.channelId)) return;
|
||||
} else {
|
||||
// その投稿のユーザーをフォローしていなかったら弾く
|
||||
if (this.user.id !== note.userId && !this.following.has(note.userId)) return;
|
||||
}
|
||||
// Ignore notes from instances the user has muted
|
||||
if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return;
|
||||
// 関係ない返信は除外
|
||||
if (note.reply && !this.withReplies) {
|
||||
const reply = note.reply;
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
|
||||
}
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.muting)) return;
|
||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.blocking)) return;
|
||||
// Members of lists with hideFromHome set
|
||||
if (note.userId !== this.user.id && isUserRelated(note, this.hidden)) return;
|
||||
if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
||||
if (this.userProfile && await isFiltered(note, this.user, this.userProfile)) return;
|
||||
this.connection.cacheNote(note);
|
||||
this.send("note", note);
|
||||
}
|
||||
dispose() {
|
||||
// Unsubscribe events
|
||||
this.subscriber.off("notesStream", this.onNote);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import Channel from "../channel.js";
|
||||
import { fetchMeta } from "../../../../misc/fetch-meta.js";
|
||||
import { isUserRelated } from "../../../../misc/is-user-related.js";
|
||||
import { isInstanceMuted } from "../../../../misc/is-instance-muted.js";
|
||||
import { isFiltered } from "../../../../misc/is-filtered.js";
|
||||
export default class extends Channel {
|
||||
chName = "hybridTimeline";
|
||||
static shouldShare = true;
|
||||
static requireCredential = true;
|
||||
withReplies;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onNote = this.withPackedNote(this.onNote.bind(this));
|
||||
}
|
||||
async init(params) {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return;
|
||||
this.withReplies = params.withReplies;
|
||||
// Subscribe events
|
||||
this.subscriber.on("notesStream", this.onNote);
|
||||
}
|
||||
async onNote(note) {
|
||||
if (note.visibility === "hidden") return;
|
||||
// チャンネルの投稿ではなく、自分自身の投稿 または
|
||||
// チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または
|
||||
// チャンネルの投稿ではなく、全体公開のローカルの投稿 または
|
||||
// フォローしているチャンネルの投稿 の場合だけ
|
||||
if (!(note.channelId == null && this.user.id === note.userId || note.channelId == null && this.following.has(note.userId) || note.channelId == null && note.user.host == null && note.visibility === "public" || note.channelId != null && this.followingChannels.has(note.channelId))) return;
|
||||
// Ignore notes from instances the user has muted
|
||||
if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return;
|
||||
// 関係ない返信は除外
|
||||
if (note.reply && !this.withReplies) {
|
||||
const reply = note.reply;
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
|
||||
}
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.muting)) return;
|
||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.blocking)) return;
|
||||
// Members of lists with hideFromHome set
|
||||
if (note.userId !== this.user.id && isUserRelated(note, this.hidden)) return;
|
||||
if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
||||
if (this.userProfile && await isFiltered(note, this.user, this.userProfile)) return;
|
||||
this.connection.cacheNote(note);
|
||||
this.send("note", note);
|
||||
}
|
||||
dispose() {
|
||||
// Unsubscribe events
|
||||
this.subscriber.off("notesStream", this.onNote);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import main from "./main.js";
|
||||
import homeTimeline from "./home-timeline.js";
|
||||
import localTimeline from "./local-timeline.js";
|
||||
import hybridTimeline from "./hybrid-timeline.js";
|
||||
import recommendedTimeline from "./recommended-timeline.js";
|
||||
import globalTimeline from "./global-timeline.js";
|
||||
import serverStats from "./server-stats.js";
|
||||
import queueStats from "./queue-stats.js";
|
||||
import userList from "./user-list.js";
|
||||
import antenna from "./antenna.js";
|
||||
import messaging from "./messaging.js";
|
||||
import messagingIndex from "./messaging-index.js";
|
||||
import drive from "./drive.js";
|
||||
import channel from "./channel.js";
|
||||
import admin from "./admin.js";
|
||||
import reversi from "./reversi.js";
|
||||
import reversiGame from "./reversi-game.js";
|
||||
import shogi from "./shogi.js";
|
||||
import shogiGame from "./shogi-game.js";
|
||||
export default {
|
||||
main,
|
||||
homeTimeline,
|
||||
localTimeline,
|
||||
recommendedTimeline,
|
||||
hybridTimeline,
|
||||
globalTimeline,
|
||||
serverStats,
|
||||
queueStats,
|
||||
userList,
|
||||
antenna,
|
||||
messaging,
|
||||
messagingIndex,
|
||||
drive,
|
||||
channel,
|
||||
admin,
|
||||
reversi,
|
||||
reversiGame,
|
||||
shogi,
|
||||
shogiGame
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import Channel from "../channel.js";
|
||||
import { fetchMeta } from "../../../../misc/fetch-meta.js";
|
||||
import { isUserRelated } from "../../../../misc/is-user-related.js";
|
||||
import { isFiltered } from "../../../../misc/is-filtered.js";
|
||||
export default class extends Channel {
|
||||
chName = "localTimeline";
|
||||
static shouldShare = true;
|
||||
static requireCredential = false;
|
||||
withReplies;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onNote = this.withPackedNote(this.onNote.bind(this));
|
||||
}
|
||||
async init(params) {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.disableLocalTimeline) {
|
||||
if (this.user == null || !(this.user.isAdmin || this.user.isModerator)) return;
|
||||
}
|
||||
this.withReplies = params.withReplies;
|
||||
// Subscribe events
|
||||
this.subscriber.on("notesStream", this.onNote);
|
||||
}
|
||||
async onNote(note) {
|
||||
if (note.user.host !== null) return;
|
||||
if (note.visibility !== "public") return;
|
||||
if (note.channelId != null && !this.followingChannels.has(note.channelId)) return;
|
||||
// 関係ない返信は除外
|
||||
if (note.reply && !this.withReplies) {
|
||||
const reply = note.reply;
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
|
||||
}
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.muting)) return;
|
||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.blocking)) return;
|
||||
if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
||||
if (this.userProfile && await isFiltered(note, this.user, this.userProfile)) return;
|
||||
this.connection.cacheNote(note);
|
||||
this.send("note", note);
|
||||
}
|
||||
dispose() {
|
||||
// Unsubscribe events
|
||||
this.subscriber.off("notesStream", this.onNote);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import Channel from "../channel.js";
|
||||
import { isInstanceMuted, isUserFromMutedInstance } from "../../../../misc/is-instance-muted.js";
|
||||
export default class extends Channel {
|
||||
chName = "main";
|
||||
static shouldShare = true;
|
||||
static requireCredential = true;
|
||||
async init(params) {
|
||||
// Subscribe main stream channel
|
||||
this.subscriber.on(`mainStream:${this.user.id}`, async (data)=>{
|
||||
switch(data.type){
|
||||
case "notification":
|
||||
{
|
||||
// Ignore notifications from instances the user has muted
|
||||
if (isUserFromMutedInstance(data.body, new Set(this.userProfile?.mutedInstances ?? []))) return;
|
||||
if (data.body.userId && this.muting.has(data.body.userId)) return;
|
||||
break;
|
||||
}
|
||||
case "mention":
|
||||
{
|
||||
if (isInstanceMuted(data.body, new Set(this.userProfile?.mutedInstances ?? []))) return;
|
||||
if (this.muting.has(data.body.userId)) return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.send(data.type, data.body);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import Channel from "../channel.js";
|
||||
export default class extends Channel {
|
||||
chName = "messagingIndex";
|
||||
static shouldShare = true;
|
||||
static requireCredential = true;
|
||||
async init(params) {
|
||||
// Subscribe messaging index stream
|
||||
this.subscriber.on(`messagingIndexStream:${this.user.id}`, (data)=>{
|
||||
this.send(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivity } from "../../common/read-messaging-message.js";
|
||||
import Channel from "../channel.js";
|
||||
import { UserGroupJoinings, Users, MessagingMessages } from "../../../../models/index.js";
|
||||
export default class extends Channel {
|
||||
chName = "messaging";
|
||||
static shouldShare = false;
|
||||
static requireCredential = true;
|
||||
otherpartyId;
|
||||
otherparty;
|
||||
groupId;
|
||||
subCh;
|
||||
typers = new Map();
|
||||
emitTypersIntervalId;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onEvent = this.onEvent.bind(this);
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
this.emitTypers = this.emitTypers.bind(this);
|
||||
}
|
||||
async init(params) {
|
||||
this.otherpartyId = params.otherparty;
|
||||
this.otherparty = this.otherpartyId ? await Users.findOneByOrFail({
|
||||
id: this.otherpartyId
|
||||
}) : null;
|
||||
this.groupId = params.group;
|
||||
// Check joining
|
||||
if (this.groupId) {
|
||||
const joining = await UserGroupJoinings.findOneBy({
|
||||
userId: this.user.id,
|
||||
userGroupId: this.groupId
|
||||
});
|
||||
if (joining == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.emitTypersIntervalId = setInterval(this.emitTypers, 5000);
|
||||
this.subCh = this.otherpartyId ? `messagingStream:${this.user.id}-${this.otherpartyId}` : `messagingStream:${this.groupId}`;
|
||||
// Subscribe messaging stream
|
||||
this.subscriber.on(this.subCh, this.onEvent);
|
||||
}
|
||||
onEvent(data) {
|
||||
if (data.type === "typing") {
|
||||
const id = data.body;
|
||||
const begin = !this.typers.has(id);
|
||||
this.typers.set(id, new Date());
|
||||
if (begin) {
|
||||
this.emitTypers();
|
||||
}
|
||||
} else {
|
||||
this.send(data);
|
||||
}
|
||||
}
|
||||
onMessage(type, body) {
|
||||
switch(type){
|
||||
case "read":
|
||||
if (this.otherpartyId) {
|
||||
readUserMessagingMessage(this.user.id, this.otherpartyId, [
|
||||
body.id
|
||||
]);
|
||||
// リモートユーザーからのメッセージだったら既読配信
|
||||
if (Users.isLocalUser(this.user) && Users.isRemoteUser(this.otherparty)) {
|
||||
MessagingMessages.findOneBy({
|
||||
id: body.id
|
||||
}).then((message)=>{
|
||||
if (message) deliverReadActivity(this.user, this.otherparty, message);
|
||||
});
|
||||
}
|
||||
} else if (this.groupId) {
|
||||
readGroupMessagingMessage(this.user.id, this.groupId, [
|
||||
body.id
|
||||
]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
async emitTypers() {
|
||||
const now = new Date();
|
||||
// Remove not typing users
|
||||
for (const [userId, date] of this.typers.entries()){
|
||||
if (now.getTime() - date.getTime() > 5000) this.typers.delete(userId);
|
||||
}
|
||||
const userIds = Array.from(this.typers.keys());
|
||||
const users = await Users.packMany(userIds, null, {
|
||||
detail: false
|
||||
});
|
||||
this.send({
|
||||
type: "typers",
|
||||
body: users
|
||||
});
|
||||
}
|
||||
dispose() {
|
||||
this.subscriber.off(this.subCh, this.onEvent);
|
||||
clearInterval(this.emitTypersIntervalId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import Xev from "xev";
|
||||
import Channel from "../channel.js";
|
||||
const ev = new Xev();
|
||||
export default class extends Channel {
|
||||
chName = "queueStats";
|
||||
static shouldShare = true;
|
||||
static requireCredential = false;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onStats = this.onStats.bind(this);
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
}
|
||||
async init(params) {
|
||||
ev.addListener("queueStats", this.onStats);
|
||||
}
|
||||
onStats(stats) {
|
||||
this.send("stats", stats);
|
||||
}
|
||||
onMessage(type, body) {
|
||||
switch(type){
|
||||
case "requestLog":
|
||||
ev.once(`queueStatsLog:${body.id}`, (statsLog)=>{
|
||||
this.send("statsLog", statsLog);
|
||||
});
|
||||
ev.emit("requestQueueStatsLog", {
|
||||
id: body.id,
|
||||
length: body.length
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispose() {
|
||||
ev.removeListener("queueStats", this.onStats);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import Channel from "../channel.js";
|
||||
import { fetchMeta } from "../../../../misc/fetch-meta.js";
|
||||
import { isUserRelated } from "../../../../misc/is-user-related.js";
|
||||
import { isInstanceMuted } from "../../../../misc/is-instance-muted.js";
|
||||
import { isFiltered } from "../../../../misc/is-filtered.js";
|
||||
export default class extends Channel {
|
||||
chName = "recommendedTimeline";
|
||||
static shouldShare = true;
|
||||
static requireCredential = true;
|
||||
withReplies;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onNote = this.withPackedNote(this.onNote.bind(this));
|
||||
}
|
||||
async init(params) {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.disableRecommendedTimeline && !this.user.isAdmin && !this.user.isModerator) return;
|
||||
this.withReplies = params.withReplies;
|
||||
// Subscribe events
|
||||
this.subscriber.on("notesStream", this.onNote);
|
||||
}
|
||||
async onNote(note) {
|
||||
if (note.visibility === "hidden") return;
|
||||
// チャンネルの投稿ではなく、自分自身の投稿 または
|
||||
// チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または
|
||||
// チャンネルの投稿ではなく、全体公開のローカルの投稿 または
|
||||
// フォローしているチャンネルの投稿 の場合だけ
|
||||
const meta = await fetchMeta();
|
||||
if (!(note.user.host != null && meta.recommendedInstances.includes(note.user.host) && note.visibility === "public")) return;
|
||||
// Ignore notes from instances the user has muted
|
||||
if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return;
|
||||
// 関係ない返信は除外
|
||||
if (note.reply && !this.withReplies) {
|
||||
const reply = note.reply;
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
|
||||
}
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.muting)) return;
|
||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.blocking)) return;
|
||||
if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
||||
if (this.userProfile && await isFiltered(note, this.user, this.userProfile)) return;
|
||||
this.connection.cacheNote(note);
|
||||
this.send("note", note);
|
||||
}
|
||||
dispose() {
|
||||
// Unsubscribe events
|
||||
this.subscriber.off("notesStream", this.onNote);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import Channel from "../channel.js";
|
||||
import { cancelGame, checkTimeout, gameReady, isValidUpdateKey, isValidUpdateValue, putStone, updateSettings } from "../../../../services/reversi/index.js";
|
||||
export default class extends Channel {
|
||||
chName = "reversiGame";
|
||||
static shouldShare = false;
|
||||
static requireCredential = false;
|
||||
gameId = null;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onEvent = this.onEvent.bind(this);
|
||||
}
|
||||
async init(params) {
|
||||
if (typeof params.gameId !== "string") return;
|
||||
this.gameId = params.gameId;
|
||||
this.subscriber.on(`reversiGameStream:${this.gameId}`, this.onEvent);
|
||||
}
|
||||
onEvent(data) {
|
||||
this.send(data);
|
||||
}
|
||||
onMessage(type, body) {
|
||||
if (!this.gameId) return;
|
||||
switch(type){
|
||||
case "ready":
|
||||
if (this.user && typeof body === "boolean") gameReady(this.gameId, this.user, body);
|
||||
break;
|
||||
case "updateSettings":
|
||||
if (this.user && body && isValidUpdateKey(body.key) && isValidUpdateValue(body.key, body.value)) {
|
||||
updateSettings(this.gameId, this.user, body.key, body.value);
|
||||
}
|
||||
break;
|
||||
case "cancel":
|
||||
if (this.user) cancelGame(this.gameId, this.user);
|
||||
break;
|
||||
case "putStone":
|
||||
if (this.user && body && typeof body.pos === "number") {
|
||||
putStone(this.gameId, this.user, body.pos, typeof body.id === "string" ? body.id : null);
|
||||
}
|
||||
break;
|
||||
case "claimTimeIsUp":
|
||||
checkTimeout(this.gameId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispose() {
|
||||
if (this.gameId) {
|
||||
this.subscriber.off(`reversiGameStream:${this.gameId}`, this.onEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import Channel from "../channel.js";
|
||||
export default class extends Channel {
|
||||
chName = "reversi";
|
||||
static shouldShare = true;
|
||||
static requireCredential = true;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onEvent = this.onEvent.bind(this);
|
||||
}
|
||||
async init(params) {
|
||||
this.subscriber.on(`reversiStream:${this.user.id}`, this.onEvent);
|
||||
}
|
||||
onEvent(data) {
|
||||
this.send(data);
|
||||
}
|
||||
dispose() {
|
||||
this.subscriber.off(`reversiStream:${this.user.id}`, this.onEvent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import Xev from "xev";
|
||||
import Channel from "../channel.js";
|
||||
const ev = new Xev();
|
||||
export default class extends Channel {
|
||||
chName = "serverStats";
|
||||
static shouldShare = true;
|
||||
static requireCredential = false;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onStats = this.onStats.bind(this);
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
}
|
||||
async init(params) {
|
||||
ev.addListener("serverStats", this.onStats);
|
||||
}
|
||||
onStats(stats) {
|
||||
this.send("stats", stats);
|
||||
}
|
||||
onMessage(type, body) {
|
||||
switch(type){
|
||||
case "requestLog":
|
||||
ev.once(`serverStatsLog:${body.id}`, (statsLog)=>{
|
||||
this.send("statsLog", statsLog);
|
||||
});
|
||||
ev.emit("requestServerStatsLog", {
|
||||
id: body.id,
|
||||
length: body.length
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispose() {
|
||||
ev.removeListener("serverStats", this.onStats);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import Channel from "../channel.js";
|
||||
import { cancelGame, gameReady, putMove } from "../../../../services/shogi/index.js";
|
||||
export default class extends Channel {
|
||||
chName = "shogiGame";
|
||||
static shouldShare = false;
|
||||
static requireCredential = false;
|
||||
gameId = null;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onEvent = this.onEvent.bind(this);
|
||||
}
|
||||
async init(params) {
|
||||
if (typeof params.gameId !== "string") return;
|
||||
this.gameId = params.gameId;
|
||||
this.subscriber.on(`shogiGameStream:${this.gameId}`, this.onEvent);
|
||||
}
|
||||
onEvent(data) {
|
||||
this.send(data);
|
||||
}
|
||||
onMessage(type, body) {
|
||||
if (!this.gameId) return;
|
||||
switch(type){
|
||||
case "ready":
|
||||
if (this.user && typeof body === "boolean") gameReady(this.gameId, this.user, body);
|
||||
break;
|
||||
case "cancel":
|
||||
if (this.user) cancelGame(this.gameId, this.user);
|
||||
break;
|
||||
case "move":
|
||||
if (this.user && body && typeof body.usi === "string") {
|
||||
putMove(this.gameId, this.user, body.usi, typeof body.id === "string" ? body.id : null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispose() {
|
||||
if (this.gameId) {
|
||||
this.subscriber.off(`shogiGameStream:${this.gameId}`, this.onEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import Channel from "../channel.js";
|
||||
export default class extends Channel {
|
||||
chName = "shogi";
|
||||
static shouldShare = true;
|
||||
static requireCredential = true;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.onEvent = this.onEvent.bind(this);
|
||||
}
|
||||
async init(params) {
|
||||
this.subscriber.on(`shogiStream:${this.user.id}`, this.onEvent);
|
||||
}
|
||||
onEvent(data) {
|
||||
this.send(data);
|
||||
}
|
||||
dispose() {
|
||||
this.subscriber.off(`shogiStream:${this.user.id}`, this.onEvent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import Channel from "../channel.js";
|
||||
import { UserListJoinings, UserLists } from "../../../../models/index.js";
|
||||
import { isUserRelated } from "../../../../misc/is-user-related.js";
|
||||
export default class extends Channel {
|
||||
chName = "userList";
|
||||
static shouldShare = false;
|
||||
static requireCredential = false;
|
||||
listId;
|
||||
listUsers = [];
|
||||
listUsersClock;
|
||||
constructor(id, connection){
|
||||
super(id, connection);
|
||||
this.updateListUsers = this.updateListUsers.bind(this);
|
||||
this.onNote = this.withPackedNote(this.onNote.bind(this));
|
||||
}
|
||||
async init(params) {
|
||||
this.listId = params.listId;
|
||||
// Check existence and owner
|
||||
const exist = await UserLists.exist({
|
||||
where: {
|
||||
id: this.listId,
|
||||
userId: this.user.id
|
||||
}
|
||||
});
|
||||
if (!exist) return;
|
||||
// Subscribe stream
|
||||
this.subscriber.on(`userListStream:${this.listId}`, this.send);
|
||||
this.subscriber.on("notesStream", this.onNote);
|
||||
this.updateListUsers();
|
||||
this.listUsersClock = setInterval(this.updateListUsers, 5000);
|
||||
}
|
||||
async updateListUsers() {
|
||||
const users = await UserListJoinings.find({
|
||||
where: {
|
||||
userListId: this.listId
|
||||
},
|
||||
select: [
|
||||
"userId"
|
||||
]
|
||||
});
|
||||
this.listUsers = users.map((x)=>x.userId);
|
||||
}
|
||||
async onNote(note) {
|
||||
if (note.visibility === "hidden") return;
|
||||
if (!this.listUsers.includes(note.userId)) return;
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.muting)) return;
|
||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.blocking)) return;
|
||||
if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
|
||||
this.send("note", note);
|
||||
}
|
||||
dispose() {
|
||||
// Unsubscribe events
|
||||
this.subscriber.off(`userListStream:${this.listId}`, this.send);
|
||||
this.subscriber.off("notesStream", this.onNote);
|
||||
clearInterval(this.listUsersClock);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user