merge: upstream

This commit is contained in:
Mar0xy 2023-11-01 12:39:07 +01:00
commit 8388b18df9
No known key found for this signature in database
GPG key ID: 56569BBE47D2C828
37 changed files with 1040 additions and 897 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -66,17 +66,17 @@
"@discordapp/twemoji": "14.1.2",
"@fastify/accepts": "4.2.0",
"@fastify/cookie": "9.1.0",
"@fastify/cors": "8.4.0",
"@fastify/cors": "8.4.1",
"@fastify/express": "2.3.0",
"@fastify/http-proxy": "9.2.1",
"@fastify/multipart": "8.0.0",
"@fastify/static": "6.11.2",
"@fastify/static": "6.12.0",
"@fastify/view": "8.2.0",
"@nestjs/common": "10.2.7",
"@nestjs/core": "10.2.7",
"@nestjs/testing": "10.2.7",
"@peertube/http-signature": "1.7.0",
"@simplewebauthn/server": "8.3.4",
"@simplewebauthn/server": "8.3.5",
"@sinonjs/fake-timers": "11.2.2",
"@swc/cli": "0.1.62",
"@swc/core": "1.3.95",
@ -88,7 +88,7 @@
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"body-parser": "1.20.2",
"bullmq": "4.12.6",
"bullmq": "4.12.7",
"cacheable-lookup": "7.0.0",
"cbor": "9.0.1",
"chalk": "5.3.0",
@ -141,7 +141,7 @@
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
"pug": "3.0.2",
"punycode": "2.3.0",
"punycode": "2.3.1",
"pureimage": "0.3.17",
"qrcode": "1.5.3",
"random-seed": "0.3.0",
@ -179,7 +179,7 @@
"@simplewebauthn/typescript-types": "8.3.4",
"@swc/jest": "0.2.29",
"@types/accepts": "1.3.6",
"@types/archiver": "5.3.4",
"@types/archiver": "6.0.0",
"@types/bcryptjs": "2.4.5",
"@types/body-parser": "1.19.4",
"@types/cbor": "6.0.0",
@ -187,14 +187,14 @@
"@types/content-disposition": "0.5.7",
"@types/fluent-ffmpeg": "2.1.23",
"@types/http-link-header": "1.0.4",
"@types/jest": "29.5.6",
"@types/jest": "29.5.7",
"@types/js-yaml": "4.0.8",
"@types/jsdom": "21.1.4",
"@types/jsonld": "1.5.11",
"@types/jsrsasign": "10.5.11",
"@types/mime-types": "2.1.3",
"@types/ms": "0.7.33",
"@types/node": "20.8.9",
"@types/node": "20.8.10",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.13",
"@types/oauth": "0.9.3",
@ -218,8 +218,8 @@
"@types/web-push": "3.6.2",
"@types/uuid": "^9.0.4",
"@types/ws": "8.5.8",
"@typescript-eslint/eslint-plugin": "6.9.0",
"@typescript-eslint/parser": "6.9.0",
"@typescript-eslint/eslint-plugin": "6.9.1",
"@typescript-eslint/parser": "6.9.1",
"aws-sdk-client-mock": "3.0.0",
"cross-env": "7.0.3",
"eslint": "8.52.0",

View file

@ -258,7 +258,7 @@ export function loadConfig(): Config {
clientEntry: clientManifest['src/_boot_.ts'],
clientManifestExists: clientManifestExists,
perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 300,
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
pidFile: config.pidFile,
};

View file

@ -144,7 +144,9 @@ export class NotificationService implements OnApplicationShutdown {
this.globalEventService.publishMainStream(notifieeId, 'notification', packed);
// 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する
setTimeout(2000, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => {
// テスト通知の場合は即時発行
const interval = notification.type === 'test' ? 0 : 2000;
setTimeout(interval, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => {
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`);
if (latestReadNotificationId && (latestReadNotificationId >= (await redisIdPromise)!)) return;

View file

@ -5,7 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { ChannelFavoritesRepository, ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository, NotesRepository } from '@/models/_.js';
import type { ChannelFavoritesRepository, ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NotesRepository } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
import type { } from '@/models/Blocking.js';
import type { MiUser } from '@/models/User.js';
@ -31,9 +31,6 @@ export class ChannelEntityService {
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.noteUnreadsRepository)
private noteUnreadsRepository: NoteUnreadsRepository,
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
@ -54,13 +51,6 @@ export class ChannelEntityService {
const banner = channel.bannerId ? await this.driveFilesRepository.findOneBy({ id: channel.bannerId }) : null;
const hasUnreadNote = meId ? await this.noteUnreadsRepository.exist({
where: {
noteChannelId: channel.id,
userId: meId,
},
}) : undefined;
const isFollowing = meId ? await this.channelFollowingsRepository.exist({
where: {
followerId: meId,
@ -99,7 +89,7 @@ export class ChannelEntityService {
...(me ? {
isFollowing,
isFavorited,
hasUnreadNote,
hasUnreadNote: false, // 後方互換性のため
} : {}),
...(detailed ? {

View file

@ -15,6 +15,7 @@ import { awaitAll } from '@/misc/prelude/await-all.js';
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js';
import { birthdaySchema, listenbrainzSchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/User.js';
import { MiNotification } from '@/models/Notification.js';
import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, UserNotePiningsRepository, UserProfilesRepository, AnnouncementReadsRepository, AnnouncementsRepository, MiUserProfile, RenoteMutingsRepository, UserMemoRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
@ -236,17 +237,34 @@ export class UserEntityService implements OnModuleInit {
}
@bindThis
public async getHasUnreadNotification(userId: MiUser['id']): Promise<boolean> {
public async getNotificationsInfo(userId: MiUser['id']): Promise<{
hasUnread: boolean;
unreadCount: number;
}> {
const response = {
hasUnread: false,
unreadCount: 0,
};
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`);
const latestNotificationIdsRes = await this.redisClient.xrevrange(
`notificationTimeline:${userId}`,
'+',
'-',
'COUNT', 1);
const latestNotificationId = latestNotificationIdsRes[0]?.[0];
if (!latestReadNotificationId) {
response.unreadCount = await this.redisClient.xlen(`notificationTimeline:${userId}`);
} else {
const latestNotificationIdsRes = await this.redisClient.xrevrange(
`notificationTimeline:${userId}`,
'+',
latestReadNotificationId,
);
return latestNotificationId != null && (latestReadNotificationId == null || latestReadNotificationId < latestNotificationId);
response.unreadCount = (latestNotificationIdsRes.length - 1 >= 0) ? latestNotificationIdsRes.length - 1 : 0;
}
if (response.unreadCount > 0) {
response.hasUnread = true;
}
return response;
}
@bindThis
@ -361,6 +379,8 @@ export class UserEntityService implements OnModuleInit {
})) : null;
const checkHost = user.host == null ? this.config.host : user.host;
const notificationsInfo = isMe && opts.detail ? await this.getNotificationsInfo(user.id) : null;
const packed = {
id: user.id,
@ -486,8 +506,9 @@ export class UserEntityService implements OnModuleInit {
unreadAnnouncements,
hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
hasUnreadChannel: false, // 後方互換性のため
hasUnreadNotification: this.getHasUnreadNotification(user.id),
hasUnreadNotification: notificationsInfo?.hasUnread, // 後方互換性のため
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
unreadNotificationsCount: notificationsInfo?.unreadCount,
mutedWords: profile!.mutedWords,
mutedInstances: profile!.mutedInstances,
mutingNotificationTypes: [], // 後方互換性のため

View file

@ -423,6 +423,10 @@ export const packedMeDetailedOnlySchema = {
type: 'boolean',
nullable: false, optional: false,
},
unreadNotificationsCount: {
type: 'number',
nullable: false, optional: false,
},
mutedWords: {
type: 'array',
nullable: false, optional: false,

View file

@ -720,7 +720,7 @@ describe('クリップ', () => {
test('を追加できる。', async () => {
await addNote({ clipId: aliceClip.id, noteId: aliceNote.id });
const res = await show({ clipId: aliceClip.id });
assert.strictEqual(res.lastClippedAt, new Date(res.lastClippedAt ?? '').toISOString());
assert.strictEqual(res.lastClippedAt, res.lastClippedAt ? new Date(res.lastClippedAt).toISOString() : null);
assert.deepStrictEqual((await notes({ clipId: aliceClip.id })).map(x => x.id), [aliceNote.id]);
// 他人の非公開ノートも突っ込める

View file

@ -167,6 +167,7 @@ describe('ユーザー', () => {
hasUnreadAntenna: user.hasUnreadAntenna,
hasUnreadChannel: user.hasUnreadChannel,
hasUnreadNotification: user.hasUnreadNotification,
unreadNotificationsCount: user.unreadNotificationsCount,
hasPendingReceivedFollowRequest: user.hasPendingReceivedFollowRequest,
unreadAnnouncements: user.unreadAnnouncements,
mutedWords: user.mutedWords,
@ -420,6 +421,7 @@ describe('ユーザー', () => {
assert.strictEqual(response.hasUnreadAntenna, false);
assert.strictEqual(response.hasUnreadChannel, false);
assert.strictEqual(response.hasUnreadNotification, false);
assert.strictEqual(response.unreadNotificationsCount, 0);
assert.strictEqual(response.hasPendingReceivedFollowRequest, false);
assert.deepStrictEqual(response.unreadAnnouncements, []);
assert.deepStrictEqual(response.mutedWords, []);