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
This commit is contained in:
parent
122076e8ea
commit
9855405b89
70 changed files with 2191 additions and 184 deletions
|
@ -28,6 +28,7 @@ export async function addNoteToAntenna(antenna: Antenna, note: Note, noteUser: U
|
|||
select: ['muteeId']
|
||||
});
|
||||
|
||||
// Copy
|
||||
const _note: Note = {
|
||||
...note
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@ import { IdentifiableError } from '../../misc/identifiable-error';
|
|||
import { User } from '../../models/entities/user';
|
||||
import { Note } from '../../models/entities/note';
|
||||
import { Notes, UserNotePinings, Users } from '../../models';
|
||||
import { UserNotePining } from '../../models/entities/user-note-pinings';
|
||||
import { UserNotePining } from '../../models/entities/user-note-pining';
|
||||
import { genId } from '../../misc/gen-id';
|
||||
import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
|
||||
import { deliverToRelays } from '../relay';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
||||
// v11 TODO: https://github.com/typeorm/typeorm/issues/2415
|
||||
//if (res.affected === 0) {
|
||||
// return;
|
||||
//}
|
||||
|
||||
const [count1, count2] = await Promise.all([
|
||||
NoteUnreads.count({
|
||||
) {
|
||||
async function careNoteUnreads() {
|
||||
const exist = await NoteUnreads.findOne({
|
||||
userId: userId,
|
||||
isSpecified: false
|
||||
}),
|
||||
NoteUnreads.count({
|
||||
userId: userId,
|
||||
isSpecified: true
|
||||
})
|
||||
]);
|
||||
|
||||
if (count1 === 0) {
|
||||
// 全て既読になったイベントを発行
|
||||
publishMainStream(userId, 'readAllUnreadMentions');
|
||||
}
|
||||
|
||||
if (count2 === 0) {
|
||||
// 全て既読になったイベントを発行
|
||||
publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
|
||||
}
|
||||
|
||||
const antennas = await Antennas.find({ userId });
|
||||
|
||||
await Promise.all(antennas.map(async antenna => {
|
||||
await AntennaNotes.update({
|
||||
antennaId: antenna.id,
|
||||
noteId: noteId
|
||||
}, {
|
||||
read: true
|
||||
noteId: noteId,
|
||||
});
|
||||
|
||||
const count = await AntennaNotes.count({
|
||||
antennaId: antenna.id,
|
||||
read: false
|
||||
if (!exist) return;
|
||||
|
||||
// Remove the record
|
||||
await NoteUnreads.delete({
|
||||
userId: userId,
|
||||
noteId: noteId,
|
||||
});
|
||||
|
||||
if (count === 0) {
|
||||
publishMainStream(userId, 'readAntenna', antenna);
|
||||
if (exist.isMentioned) {
|
||||
NoteUnreads.count({
|
||||
userId: userId,
|
||||
isMentioned: true
|
||||
}).then(mentionsCount => {
|
||||
if (mentionsCount === 0) {
|
||||
// 全て既読になったイベントを発行
|
||||
publishMainStream(userId, 'readAllUnreadMentions');
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
Users.getHasUnreadAntenna(userId).then(unread => {
|
||||
if (!unread) {
|
||||
publishMainStream(userId, 'readAllAntennas');
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function careAntenna() {
|
||||
const beforeUnread = await Users.getHasUnreadAntenna(userId);
|
||||
if (!beforeUnread) return;
|
||||
|
||||
const antennas = await Antennas.find({ userId });
|
||||
|
||||
await Promise.all(antennas.map(async antenna => {
|
||||
const countBefore = await AntennaNotes.count({
|
||||
antennaId: antenna.id,
|
||||
read: false
|
||||
});
|
||||
|
||||
if (countBefore === 0) return;
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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 (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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue