summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-09-21 18:48:15 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2023-09-21 18:48:15 +0900
commite3f151e2307e4c0d7b9cdfc7deba2ff028adce03 (patch)
tree15ef05643d8fc99a724fa97f8af8f55530af1379 /packages
parent2023.9.0-beta.10 (diff)
downloadsharkey-e3f151e2307e4c0d7b9cdfc7deba2ff028adce03.tar.gz
sharkey-e3f151e2307e4c0d7b9cdfc7deba2ff028adce03.tar.bz2
sharkey-e3f151e2307e4c0d7b9cdfc7deba2ff028adce03.zip
feat: 指定したユーザーの投稿通知
Resolve #11499
Diffstat (limited to 'packages')
-rw-r--r--packages/backend/migration/1695288787870-following-notify.js13
-rw-r--r--packages/backend/src/core/NoteCreateService.ts19
-rw-r--r--packages/backend/src/core/entities/NotificationEntityService.ts2
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts14
-rw-r--r--packages/backend/src/models/Following.ts7
-rw-r--r--packages/backend/src/models/Notification.ts12
-rw-r--r--packages/backend/src/models/json-schema/user.ts4
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts4
-rw-r--r--packages/backend/src/server/api/endpoints.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/following/create.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/following/invalidate.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/following/update.ts107
-rw-r--r--packages/backend/src/types.ts17
-rw-r--r--packages/backend/test/e2e/users.ts2
-rw-r--r--packages/frontend/.storybook/fakes.ts1
-rw-r--r--packages/frontend/src/components/MkNotification.vue5
-rw-r--r--packages/frontend/src/const.ts2
-rw-r--r--packages/frontend/src/scripts/get-user-menu.ts18
-rw-r--r--packages/misskey-js/etc/misskey-js.api.md10
-rw-r--r--packages/misskey-js/src/consts.ts2
-rw-r--r--packages/misskey-js/src/entities.ts8
-rw-r--r--packages/sw/src/scripts/create-notification.ts7
22 files changed, 231 insertions, 31 deletions
diff --git a/packages/backend/migration/1695288787870-following-notify.js b/packages/backend/migration/1695288787870-following-notify.js
new file mode 100644
index 0000000000..e7e2194b15
--- /dev/null
+++ b/packages/backend/migration/1695288787870-following-notify.js
@@ -0,0 +1,13 @@
+export class FollowingNotify1695288787870 {
+ name = 'FollowingNotify1695288787870'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "following" ADD "notify" character varying(32)`);
+ await queryRunner.query(`CREATE INDEX "IDX_5108098457488634a4768e1d12" ON "following" ("notify") `);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_5108098457488634a4768e1d12"`);
+ await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "notify"`);
+ }
+}
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 4b0d818862..972319ddcf 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -14,7 +14,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf
import { extractHashtags } from '@/misc/extract-hashtags.js';
import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { MiNote } from '@/models/Note.js';
-import type { ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { ChannelsRepository, FollowingsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiApp } from '@/models/App.js';
import { concat } from '@/misc/prelude/array.js';
@@ -185,6 +185,9 @@ export class NoteCreateService implements OnApplicationShutdown {
@Inject(DI.noteThreadMutingsRepository)
private noteThreadMutingsRepository: NoteThreadMutingsRepository,
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
+
private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
private idService: IdService,
@@ -505,6 +508,20 @@ export class NoteCreateService implements OnApplicationShutdown {
this.saveReply(data.reply, note);
}
+ if (data.reply == null) {
+ this.followingsRepository.findBy({
+ followeeId: user.id,
+ notify: 'normal',
+ }).then(followings => {
+ for (const following of followings) {
+ this.notificationService.createNotification(following.followerId, 'note', {
+ notifierId: user.id,
+ noteId: note.id,
+ });
+ }
+ });
+ }
+
// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
if (data.renote && (await this.noteEntityService.countSameRenotes(user.id, data.renote.id, note.id) === 0)) {
if (!user.isBot) this.incRenoteCount(data.renote);
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index 75a2f62f6e..3ee7c91f3a 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -20,7 +20,7 @@ import type { CustomEmojiService } from '../CustomEmojiService.js';
import type { UserEntityService } from './UserEntityService.js';
import type { NoteEntityService } from './NoteEntityService.js';
-const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set(['mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded'] as (typeof notificationTypes[number])[]);
+const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set(['note', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded'] as (typeof notificationTypes[number])[]);
@Injectable()
export class NotificationEntityService implements OnModuleInit {
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 7bef410bf9..3dd64ce625 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -146,15 +146,14 @@ export class UserEntityService implements OnModuleInit {
@bindThis
public async getRelation(me: MiUser['id'], target: MiUser['id']) {
+ const following = await this.followingsRepository.findOneBy({
+ followerId: me,
+ followeeId: target,
+ });
return awaitAll({
id: target,
- isFollowing: this.followingsRepository.count({
- where: {
- followerId: me,
- followeeId: target,
- },
- take: 1,
- }).then(n => n > 0),
+ following,
+ isFollowing: following != null,
isFollowed: this.followingsRepository.count({
where: {
followerId: target,
@@ -486,6 +485,7 @@ export class UserEntityService implements OnModuleInit {
isBlocked: relation.isBlocked,
isMuted: relation.isMuted,
isRenoteMuted: relation.isRenoteMuted,
+ notify: relation.following?.notify ?? 'none',
} : {}),
} as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>;
diff --git a/packages/backend/src/models/Following.ts b/packages/backend/src/models/Following.ts
index 05b729412f..8c9f965fad 100644
--- a/packages/backend/src/models/Following.ts
+++ b/packages/backend/src/models/Following.ts
@@ -45,6 +45,13 @@ export class MiFollowing {
@JoinColumn()
public follower: MiUser | null;
+ @Index()
+ @Column('varchar', {
+ length: 32,
+ nullable: true,
+ })
+ public notify: 'normal' | null;
+
//#region Denormalized fields
@Index()
@Column('varchar', {
diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts
index fb7f67dfd8..c0a9df2e23 100644
--- a/packages/backend/src/models/Notification.ts
+++ b/packages/backend/src/models/Notification.ts
@@ -22,18 +22,6 @@ export type MiNotification = {
/**
* 通知の種類。
- * follow - フォローされた
- * mention - 投稿で自分が言及された
- * reply - 投稿に返信された
- * renote - 投稿がRenoteされた
- * quote - 投稿が引用Renoteされた
- * reaction - 投稿にリアクションされた
- * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
- * receiveFollowRequest - フォローリクエストされた
- * followRequestAccepted - 自分の送ったフォローリクエストが承認された
- * achievementEarned - 実績を獲得
- * app - アプリ通知
- * test - テスト通知(サーバー側)
*/
type: typeof notificationTypes[number];
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index 8d0e4e72ed..f15b225a30 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -273,6 +273,10 @@ export const packedUserDetailedNotMeOnlySchema = {
type: 'string',
nullable: false, optional: true,
},
+ notify: {
+ type: 'string',
+ nullable: false, optional: true,
+ },
//#endregion
},
} as const;
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 7b9fa6c3b0..41a11bfb19 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -160,6 +160,7 @@ import * as ep___federation_users from './endpoints/federation/users.js';
import * as ep___federation_stats from './endpoints/federation/stats.js';
import * as ep___following_create from './endpoints/following/create.js';
import * as ep___following_delete from './endpoints/following/delete.js';
+import * as ep___following_update from './endpoints/following/update.js';
import * as ep___following_invalidate from './endpoints/following/invalidate.js';
import * as ep___following_requests_accept from './endpoints/following/requests/accept.js';
import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js';
@@ -507,6 +508,7 @@ const $federation_users: Provider = { provide: 'ep:federation/users', useClass:
const $federation_stats: Provider = { provide: 'ep:federation/stats', useClass: ep___federation_stats.default };
const $following_create: Provider = { provide: 'ep:following/create', useClass: ep___following_create.default };
const $following_delete: Provider = { provide: 'ep:following/delete', useClass: ep___following_delete.default };
+const $following_update: Provider = { provide: 'ep:following/update', useClass: ep___following_update.default };
const $following_invalidate: Provider = { provide: 'ep:following/invalidate', useClass: ep___following_invalidate.default };
const $following_requests_accept: Provider = { provide: 'ep:following/requests/accept', useClass: ep___following_requests_accept.default };
const $following_requests_cancel: Provider = { provide: 'ep:following/requests/cancel', useClass: ep___following_requests_cancel.default };
@@ -858,6 +860,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$federation_stats,
$following_create,
$following_delete,
+ $following_update,
$following_invalidate,
$following_requests_accept,
$following_requests_cancel,
@@ -1203,6 +1206,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$federation_stats,
$following_create,
$following_delete,
+ $following_update,
$following_invalidate,
$following_requests_accept,
$following_requests_cancel,
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index a9cb7c341a..ab20a708ef 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -160,6 +160,7 @@ import * as ep___federation_users from './endpoints/federation/users.js';
import * as ep___federation_stats from './endpoints/federation/stats.js';
import * as ep___following_create from './endpoints/following/create.js';
import * as ep___following_delete from './endpoints/following/delete.js';
+import * as ep___following_update from './endpoints/following/update.js';
import * as ep___following_invalidate from './endpoints/following/invalidate.js';
import * as ep___following_requests_accept from './endpoints/following/requests/accept.js';
import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js';
@@ -505,6 +506,7 @@ const eps = [
['federation/stats', ep___federation_stats],
['following/create', ep___following_create],
['following/delete', ep___following_delete],
+ ['following/update', ep___following_update],
['following/invalidate', ep___following_invalidate],
['following/requests/accept', ep___following_requests_accept],
['following/requests/cancel', ep___following_requests_cancel],
diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts
index d80c853672..e0e7fed87a 100644
--- a/packages/backend/src/server/api/endpoints/following/create.ts
+++ b/packages/backend/src/server/api/endpoints/following/create.ts
@@ -19,7 +19,7 @@ export const meta = {
limit: {
duration: ms('1hour'),
- max: 50,
+ max: 100,
},
requireCredential: true,
diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts
index 147d16556f..53ef925b2f 100644
--- a/packages/backend/src/server/api/endpoints/following/invalidate.ts
+++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts
@@ -29,7 +29,7 @@ export const meta = {
noSuchUser: {
message: 'No such user.',
code: 'NO_SUCH_USER',
- id: '5b12c78d-2b28-4dca-99d2-f56139b42ff8',
+ id: 'b77e6ae6-a3e5-40da-9cc8-c240115479cc',
},
followerIsYourself: {
@@ -41,7 +41,7 @@ export const meta = {
notFollowing: {
message: 'The other use is not following you.',
code: 'NOT_FOLLOWING',
- id: '5dbf82f5-c92b-40b1-87d1-6c8c0741fd09',
+ id: '918faac3-074f-41ae-9c43-ed5d2946770d',
},
},
diff --git a/packages/backend/src/server/api/endpoints/following/update.ts b/packages/backend/src/server/api/endpoints/following/update.ts
new file mode 100644
index 0000000000..25f393e517
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/following/update.ts
@@ -0,0 +1,107 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import ms from 'ms';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { FollowingsRepository } from '@/models/_.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { UserFollowingService } from '@/core/UserFollowingService.js';
+import { DI } from '@/di-symbols.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['following', 'users'],
+
+ limit: {
+ duration: ms('1hour'),
+ max: 100,
+ },
+
+ requireCredential: true,
+
+ kind: 'write:following',
+
+ errors: {
+ noSuchUser: {
+ message: 'No such user.',
+ code: 'NO_SUCH_USER',
+ id: '14318698-f67e-492a-99da-5353a5ac52be',
+ },
+
+ followeeIsYourself: {
+ message: 'Followee is yourself.',
+ code: 'FOLLOWEE_IS_YOURSELF',
+ id: '4c4cbaf9-962a-463b-8418-a5e365dbf2eb',
+ },
+
+ notFollowing: {
+ message: 'You are not following that user.',
+ code: 'NOT_FOLLOWING',
+ id: 'b8dc75cf-1cb5-46c9-b14b-5f1ffbd782c9',
+ },
+ },
+
+ res: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'UserLite',
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ userId: { type: 'string', format: 'misskey:id' },
+ notify: { type: 'string', enum: ['normal', 'none'] },
+ },
+ required: ['userId', 'notify'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
+
+ private userEntityService: UserEntityService,
+ private getterService: GetterService,
+ private userFollowingService: UserFollowingService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const follower = me;
+
+ // Check if the follower is yourself
+ if (me.id === ps.userId) {
+ throw new ApiError(meta.errors.followeeIsYourself);
+ }
+
+ // Get followee
+ const followee = await this.getterService.getUser(ps.userId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
+
+ // Check not following
+ const exist = await this.followingsRepository.findOneBy({
+ followerId: follower.id,
+ followeeId: followee.id,
+ });
+
+ if (exist == null) {
+ throw new ApiError(meta.errors.notFollowing);
+ }
+
+ await this.followingsRepository.update({
+ id: exist.id,
+ }, {
+ notify: ps.notify === 'none' ? null : ps.notify,
+ });
+
+ return await this.userEntityService.pack(follower.id, me);
+ });
+ }
+}
diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts
index 024ba01e3b..0a28d88d08 100644
--- a/packages/backend/src/types.ts
+++ b/packages/backend/src/types.ts
@@ -3,7 +3,22 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app', 'test'] as const;
+/**
+ * note - 通知オンにしているユーザーが投稿した
+ * follow - フォローされた
+ * mention - 投稿で自分が言及された
+ * reply - 投稿に返信された
+ * renote - 投稿がRenoteされた
+ * quote - 投稿が引用Renoteされた
+ * reaction - 投稿にリアクションされた
+ * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
+ * receiveFollowRequest - フォローリクエストされた
+ * followRequestAccepted - 自分の送ったフォローリクエストが承認された
+ * achievementEarned - 実績を獲得
+ * app - アプリ通知
+ * test - テスト通知(サーバー側)
+ */
+export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app', 'test'] as const;
export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index 13cea0cfc2..e4ecd6e398 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -494,7 +494,7 @@ describe('ユーザー', () => {
{ parameters: (): object => ({ mutedWords: [] }) },
{ parameters: (): object => ({ mutedInstances: ['xxxx.xxxxx'] }) },
{ parameters: (): object => ({ mutedInstances: [] }) },
- { parameters: (): object => ({ mutingNotificationTypes: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] }) },
+ { parameters: (): object => ({ mutingNotificationTypes: ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] }) },
{ parameters: (): object => ({ mutingNotificationTypes: [] }) },
{ parameters: (): object => ({ emailNotificationTypes: ['mention', 'reply', 'quote', 'follow', 'receiveFollowRequest'] }) },
{ parameters: (): object => ({ emailNotificationTypes: [] }) },
diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts
index 2bda89196a..811c243926 100644
--- a/packages/frontend/.storybook/fakes.ts
+++ b/packages/frontend/.storybook/fakes.ts
@@ -120,6 +120,7 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi
updatedAt: null,
uri: null,
url: null,
+ notify: 'none',
};
}
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index f71834b099..99443a6409 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div ref="elRef" :class="$style.root">
<div :class="$style.head">
<MkAvatar v-if="notification.type === 'pollEnded'" :class="$style.icon" :user="notification.note.user" link preview/>
+ <MkAvatar v-else-if="notification.type === 'note'" :class="$style.icon" :user="notification.note.user" link preview/>
<MkAvatar v-else-if="notification.type === 'achievementEarned'" :class="$style.icon" :user="$i" link preview/>
<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
<MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user" link preview/>
@@ -47,6 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.tail">
<header :class="$style.header">
<span v-if="notification.type === 'pollEnded'">{{ i18n.ts._notification.pollEnded }}</span>
+ <span v-else-if="notification.type === 'note'">{{ i18n.ts._notification.newNote }}: {{ notification.note.user.name ?? notification.note.user.username }}</span>
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
<MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
@@ -73,6 +75,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA v-else-if="notification.type === 'quote'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/>
</MkA>
+ <MkA v-else-if="notification.type === 'note'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
+ <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/>
+ </MkA>
<MkA v-else-if="notification.type === 'pollEnded'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
<i class="ti ti-quote" :class="$style.quote"></i>
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/>
diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts
index 19bad95c87..15038b1063 100644
--- a/packages/frontend/src/const.ts
+++ b/packages/frontend/src/const.ts
@@ -54,7 +54,7 @@ https://github.com/sindresorhus/file-type/blob/main/core.js
https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
*/
-export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] as const;
+export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] as const;
export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
export const ROLE_POLICIES = [
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 1561daa6d9..128cbafb15 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -80,6 +80,15 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
});
}
+ async function toggleNotify() {
+ os.apiWithDialog('following/update', {
+ userId: user.id,
+ notify: user.notify === 'normal' ? 'none' : 'normal',
+ }).then(() => {
+ user.notify = user.notify === 'normal' ? 'none' : 'normal';
+ });
+ }
+
function reportAbuse() {
os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), {
user: user,
@@ -270,6 +279,15 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
}]);
}
+ // フォローしたとしても user.isFollowing はリアルタイム更新されないので不便なため
+ //if (user.isFollowing) {
+ menu = menu.concat([{
+ icon: user.notify === 'none' ? 'ti ti-bell' : 'ti ti-bell-off',
+ text: user.notify === 'none' ? i18n.ts.notifyNotes : i18n.ts.unnotifyNotes,
+ action: toggleNotify,
+ }]);
+ //}
+
menu = menu.concat([null, {
icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off',
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index fd2d0ced02..d72652bd92 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -2609,7 +2609,12 @@ type Notification_2 = {
userId: User['id'];
note: Note;
} | {
- type: 'pollVote';
+ type: 'note';
+ user: User;
+ userId: User['id'];
+ note: Note;
+} | {
+ type: 'pollEnded';
user: User;
userId: User['id'];
note: Note;
@@ -2640,7 +2645,7 @@ type Notification_2 = {
});
// @public (undocumented)
-export const notificationTypes: readonly ["follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app"];
+export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app"];
// @public (undocumented)
type OriginType = 'combined' | 'local' | 'remote';
@@ -2810,6 +2815,7 @@ type UserDetailed = UserLite & {
updatedAt: DateString | null;
uri: string | null;
url: string | null;
+ notify: 'normal' | 'none';
};
// @public (undocumented)
diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts
index 2b144ab4a4..6cf6dc07e7 100644
--- a/packages/misskey-js/src/consts.ts
+++ b/packages/misskey-js/src/consts.ts
@@ -1,4 +1,4 @@
-export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const;
+export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const;
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index 018210c96b..9a0114d71c 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -70,6 +70,7 @@ export type UserDetailed = UserLite & {
updatedAt: DateString | null;
uri: string | null;
url: string | null;
+ notify: 'normal' | 'none';
};
export type UserGroup = TODO;
@@ -233,7 +234,12 @@ export type Notification = {
userId: User['id'];
note: Note;
} | {
- type: 'pollVote';
+ type: 'note';
+ user: User;
+ userId: User['id'];
+ note: Note;
+} | {
+ type: 'pollEnded';
user: User;
userId: User['id'];
note: Note;
diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts
index 2b7f91a741..f33ab1c33c 100644
--- a/packages/sw/src/scripts/create-notification.ts
+++ b/packages/sw/src/scripts/create-notification.ts
@@ -134,6 +134,13 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
],
}];
+ case 'note':
+ return [t('_notification.newNote') + ': ' + getUserName(data.body.user), {
+ body: data.body.note.text ?? '',
+ icon: data.body.user.avatarUrl,
+ data,
+ }];
+
case 'reaction': {
let reaction = data.body.reaction;
let badge: string | undefined;