summaryrefslogtreecommitdiff
path: root/src/services/note
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2020-08-18 22:44:21 +0900
committerGitHub <noreply@github.com>2020-08-18 22:44:21 +0900
commit9855405b8989713b81709fc1700e2ead97423467 (patch)
tree54254d2159378d1903e962f0fb37c799bb0f4464 /src/services/note
parentSign (request-target) Fix #6652 (#6656) (diff)
downloadmisskey-9855405b8989713b81709fc1700e2ead97423467.tar.gz
misskey-9855405b8989713b81709fc1700e2ead97423467.tar.bz2
misskey-9855405b8989713b81709fc1700e2ead97423467.zip
Channel (#6621)
* wip * wip * wip * wip * wip * wip * wip * wip * wop * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * add notes * wip * wip * wip * wip * sound * wip * add kick_gaba2 * wip
Diffstat (limited to 'src/services/note')
-rw-r--r--src/services/note/create.ts75
-rw-r--r--src/services/note/read.ts135
-rw-r--r--src/services/note/unread.ts35
3 files changed, 175 insertions, 70 deletions
diff --git a/src/services/note/create.ts b/src/services/note/create.ts
index 44ec5fda6f..a530b86f55 100644
--- a/src/services/note/create.ts
+++ b/src/services/note/create.ts
@@ -17,7 +17,7 @@ import extractMentions from '../../misc/extract-mentions';
import extractEmojis from '../../misc/extract-emojis';
import extractHashtags from '../../misc/extract-hashtags';
import { Note, IMentionedRemoteUsers } from '../../models/entities/note';
-import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings, MutedNotes } from '../../models';
+import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings, MutedNotes, Channels, ChannelFollowings } from '../../models';
import { DriveFile } from '../../models/entities/drive-file';
import { App } from '../../models/entities/app';
import { Not, getConnection, In } from 'typeorm';
@@ -33,6 +33,7 @@ import { checkWordMute } from '../../misc/check-word-mute';
import { addNoteToAntenna } from '../add-note-to-antenna';
import { countSameRenotes } from '../../misc/count-same-renotes';
import { deliverToRelays } from '../relay';
+import { Channel } from '../../models/entities/channel';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@@ -102,6 +103,7 @@ type Option = {
cw?: string | null;
visibility?: string;
visibleUsers?: User[] | null;
+ channel?: Channel | null;
apMentions?: User[] | null;
apHashtags?: string[] | null;
apEmojis?: string[] | null;
@@ -111,13 +113,31 @@ type Option = {
};
export default async (user: User, data: Option, silent = false) => new Promise<Note>(async (res, rej) => {
+ // チャンネル外にリプライしたら対象のスコープに合わせる
+ // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
+ if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
+ if (data.reply.channelId) {
+ data.channel = await Channels.findOne(data.reply.channelId);
+ } else {
+ data.channel = null;
+ }
+ }
+
+ // チャンネル内にリプライしたら対象のスコープに合わせる
+ // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
+ if (data.reply && (data.channel == null) && data.reply.channelId) {
+ data.channel = await Channels.findOne(data.reply.channelId);
+ }
+
if (data.createdAt == null) data.createdAt = new Date();
if (data.visibility == null) data.visibility = 'public';
if (data.viaMobile == null) data.viaMobile = false;
if (data.localOnly == null) data.localOnly = false;
+ if (data.channel != null) data.visibility = 'public';
+ if (data.channel != null) data.visibleUsers = [];
// サイレンス
- if (user.isSilenced && data.visibility === 'public') {
+ if (user.isSilenced && data.visibility === 'public' && data.channel == null) {
data.visibility = 'home';
}
@@ -142,12 +162,12 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
}
// ローカルのみをRenoteしたらローカルのみにする
- if (data.renote && data.renote.localOnly) {
+ if (data.renote && data.renote.localOnly && data.channel == null) {
data.localOnly = true;
}
// ローカルのみにリプライしたらローカルのみにする
- if (data.reply && data.reply.localOnly) {
+ if (data.reply && data.reply.localOnly && data.channel == null) {
data.localOnly = true;
}
@@ -255,6 +275,18 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
}
});
+ // Channel
+ if (note.channelId) {
+ ChannelFollowings.find({ followeeId: note.channelId }).then(followings => {
+ for (const following of followings) {
+ insertNoteUnread(following.followerId, note, {
+ isSpecified: false,
+ isMentioned: false,
+ });
+ }
+ });
+ }
+
if (data.reply) {
saveReply(data.reply, note);
}
@@ -273,11 +305,23 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
if (data.visibleUsers == null) throw new Error('invalid param');
for (const u of data.visibleUsers) {
- insertNoteUnread(u, note, true);
+ // ローカルユーザーのみ
+ if (!Users.isLocalUser(u)) continue;
+
+ insertNoteUnread(u.id, note, {
+ isSpecified: true,
+ isMentioned: false,
+ });
}
} else {
for (const u of mentionedUsers) {
- insertNoteUnread(u, note, false);
+ // ローカルユーザーのみ
+ if (!Users.isLocalUser(u)) continue;
+
+ insertNoteUnread(u.id, note, {
+ isSpecified: false,
+ isMentioned: true,
+ });
}
}
@@ -379,6 +423,24 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
//#endregion
}
+ if (data.channel) {
+ Channels.increment({ id: data.channel.id }, 'notesCount', 1);
+ Channels.update(data.channel.id, {
+ lastNotedAt: new Date(),
+ });
+
+ Notes.count({
+ userId: user.id,
+ channelId: data.channel.id,
+ }).then(count => {
+ // この処理が行われるのはノート作成後なので、ノートが一つしかなかったら最初の投稿だと判断できる
+ // TODO: とはいえノートを削除して何回も投稿すればその分だけインクリメントされる雑さもあるのでどうにかしたい
+ if (count === 1) {
+ Channels.increment({ id: data.channel.id }, 'usersCount', 1);
+ }
+ });
+ }
+
// Register to search database
index(note);
});
@@ -405,6 +467,7 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri
fileIds: data.files ? data.files.map(file => file.id) : [],
replyId: data.reply ? data.reply.id : null,
renoteId: data.renote ? data.renote.id : null,
+ channelId: data.channel ? data.channel.id : null,
name: data.name,
text: data.text,
hasPoll: data.poll != null,
diff --git a/src/services/note/read.ts b/src/services/note/read.ts
index 33fe4f1bf0..5a39ab30b7 100644
--- a/src/services/note/read.ts
+++ b/src/services/note/read.ts
@@ -2,71 +2,104 @@ import { publishMainStream } from '../stream';
import { Note } from '../../models/entities/note';
import { User } from '../../models/entities/user';
import { NoteUnreads, Antennas, AntennaNotes, Users } from '../../models';
-
-// TODO: 状態が変化していない場合は各種イベントを送信しない
+import { Not, IsNull } from 'typeorm';
/**
* Mark a note as read
*/
-export default (
+export default async function(
userId: User['id'],
noteId: Note['id']
-) => new Promise<any>(async (resolve, reject) => {
- // Remove document
- /*const res = */await NoteUnreads.delete({
- userId: userId,
- noteId: noteId
- });
+) {
+ async function careNoteUnreads() {
+ const exist = await NoteUnreads.findOne({
+ userId: userId,
+ noteId: noteId,
+ });
- // v11 TODO: https://github.com/typeorm/typeorm/issues/2415
- //if (res.affected === 0) {
- // return;
- //}
+ if (!exist) return;
- const [count1, count2] = await Promise.all([
- NoteUnreads.count({
+ // Remove the record
+ await NoteUnreads.delete({
userId: userId,
- isSpecified: false
- }),
- NoteUnreads.count({
- userId: userId,
- isSpecified: true
- })
- ]);
+ noteId: noteId,
+ });
- if (count1 === 0) {
- // 全て既読になったイベントを発行
- publishMainStream(userId, 'readAllUnreadMentions');
- }
+ if (exist.isMentioned) {
+ NoteUnreads.count({
+ userId: userId,
+ isMentioned: true
+ }).then(mentionsCount => {
+ if (mentionsCount === 0) {
+ // 全て既読になったイベントを発行
+ publishMainStream(userId, 'readAllUnreadMentions');
+ }
+ });
+ }
- if (count2 === 0) {
- // 全て既読になったイベントを発行
- publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
+ if (exist.isSpecified) {
+ NoteUnreads.count({
+ userId: userId,
+ isSpecified: true
+ }).then(specifiedCount => {
+ if (specifiedCount === 0) {
+ // 全て既読になったイベントを発行
+ publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
+ }
+ });
+ }
+
+ if (exist.noteChannelId) {
+ NoteUnreads.count({
+ userId: userId,
+ noteChannelId: Not(IsNull())
+ }).then(channelNoteCount => {
+ if (channelNoteCount === 0) {
+ // 全て既読になったイベントを発行
+ publishMainStream(userId, 'readAllChannels');
+ }
+ });
+ }
}
- const antennas = await Antennas.find({ userId });
+ async function careAntenna() {
+ const beforeUnread = await Users.getHasUnreadAntenna(userId);
+ if (!beforeUnread) return;
- await Promise.all(antennas.map(async antenna => {
- await AntennaNotes.update({
- antennaId: antenna.id,
- noteId: noteId
- }, {
- read: true
- });
+ const antennas = await Antennas.find({ userId });
- const count = await AntennaNotes.count({
- antennaId: antenna.id,
- read: false
- });
+ await Promise.all(antennas.map(async antenna => {
+ const countBefore = await AntennaNotes.count({
+ antennaId: antenna.id,
+ read: false
+ });
- if (count === 0) {
- publishMainStream(userId, 'readAntenna', antenna);
- }
- }));
+ if (countBefore === 0) return;
- Users.getHasUnreadAntenna(userId).then(unread => {
- if (!unread) {
- publishMainStream(userId, 'readAllAntennas');
- }
- });
-});
+ await AntennaNotes.update({
+ antennaId: antenna.id,
+ noteId: noteId
+ }, {
+ read: true
+ });
+
+ const countAfter = await AntennaNotes.count({
+ antennaId: antenna.id,
+ read: false
+ });
+
+ if (countAfter === 0) {
+ publishMainStream(userId, 'readAntenna', antenna);
+ }
+ }));
+
+ Users.getHasUnreadAntenna(userId).then(unread => {
+ if (!unread) {
+ publishMainStream(userId, 'readAllAntennas');
+ }
+ });
+ }
+
+ careNoteUnreads();
+ careAntenna();
+}
diff --git a/src/services/note/unread.ts b/src/services/note/unread.ts
index 549aa971a7..6fd9ee2cfe 100644
--- a/src/services/note/unread.ts
+++ b/src/services/note/unread.ts
@@ -1,16 +1,18 @@
import { Note } from '../../models/entities/note';
import { publishMainStream } from '../stream';
import { User } from '../../models/entities/user';
-import { Mutings, NoteUnreads, Users } from '../../models';
+import { Mutings, NoteUnreads } from '../../models';
import { genId } from '../../misc/gen-id';
-export default async function(user: User, note: Note, isSpecified = false) {
- // ローカルユーザーのみ
- if (!Users.isLocalUser(user)) return;
-
+export default async function(userId: User['id'], note: Note, params: {
+ // NOTE: isSpecifiedがtrueならisMentionedは必ずfalse
+ isSpecified: boolean;
+ isMentioned: boolean;
+}) {
//#region ミュートしているなら無視
+ // TODO: 現在の仕様ではChannelにミュートは適用されないのでよしなにケアする
const mute = await Mutings.find({
- muterId: user.id
+ muterId: userId
});
if (mute.map(m => m.muteeId).includes(note.userId)) return;
//#endregion
@@ -18,20 +20,27 @@ export default async function(user: User, note: Note, isSpecified = false) {
const unread = await NoteUnreads.save({
id: genId(),
noteId: note.id,
- userId: user.id,
- isSpecified,
- noteUserId: note.userId
+ userId: userId,
+ isSpecified: params.isSpecified,
+ isMentioned: params.isMentioned,
+ noteChannelId: note.channelId,
+ noteUserId: note.userId,
});
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
setTimeout(async () => {
const exist = await NoteUnreads.findOne(unread.id);
- if (exist == null) return;
- publishMainStream(user.id, 'unreadMention', note.id);
+ if (exist == null) return;
- if (isSpecified) {
- publishMainStream(user.id, 'unreadSpecifiedNote', note.id);
+ if (params.isMentioned) {
+ publishMainStream(userId, 'unreadMention', note.id);
+ }
+ if (params.isSpecified) {
+ publishMainStream(userId, 'unreadSpecifiedNote', note.id);
+ }
+ if (note.channelId) {
+ publishMainStream(userId, 'unreadChannel', note.id);
}
}, 2000);
}