* 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:
syuilo 2020-08-18 22:44:21 +09:00 committed by GitHub
parent 122076e8ea
commit 9855405b89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 2191 additions and 184 deletions

View file

@ -28,6 +28,7 @@ export async function addNoteToAntenna(antenna: Antenna, note: Note, noteUser: U
select: ['muteeId']
});
// Copy
const _note: Note = {
...note
};

View file

@ -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';

View file

@ -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,

View file

@ -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();
}

View file

@ -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);
}