summaryrefslogtreecommitdiff
path: root/packages/backend
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2024-09-28 09:55:21 +0900
committerGitHub <noreply@github.com>2024-09-28 09:55:21 +0900
commit28e9d4e483902771ddd20018f9e48b2cd0ea0673 (patch)
treee015d5ca2ed11b865078fffd7e66c8ffd38b4175 /packages/backend
parent:art: (diff)
downloadsharkey-28e9d4e483902771ddd20018f9e48b2cd0ea0673.tar.gz
sharkey-28e9d4e483902771ddd20018f9e48b2cd0ea0673.tar.bz2
sharkey-28e9d4e483902771ddd20018f9e48b2cd0ea0673.zip
feat: フォローされた際のメッセージを設定できるようにする (#14430)
* feat: フォローされた際のメッセージを設定できるようにする Resolve #14425 * Update CHANGELOG.md * 既にフォローしているユーザーのメッセージも見れるように * Update packages/frontend/src/components/MkNotification.vue Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> * fix indent * Update users.ts * wip * Update users.ts --------- Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Diffstat (limited to 'packages/backend')
-rw-r--r--packages/backend/migration/1723944246767-followedMessage.js16
-rw-r--r--packages/backend/src/core/UserFollowingService.ts15
-rw-r--r--packages/backend/src/core/activitypub/ApRendererService.ts1
-rw-r--r--packages/backend/src/core/activitypub/misc/contexts.ts1
-rw-r--r--packages/backend/src/core/activitypub/models/ApPersonService.ts12
-rw-r--r--packages/backend/src/core/activitypub/type.ts1
-rw-r--r--packages/backend/src/core/entities/NotificationEntityService.ts7
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts4
-rw-r--r--packages/backend/src/models/Notification.ts1
-rw-r--r--packages/backend/src/models/User.ts1
-rw-r--r--packages/backend/src/models/UserProfile.ts8
-rw-r--r--packages/backend/src/models/json-schema/notification.ts4
-rw-r--r--packages/backend/src/models/json-schema/user.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/admin/show-user.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/i/update.ts5
-rw-r--r--packages/backend/test/e2e/users.ts7
16 files changed, 79 insertions, 17 deletions
diff --git a/packages/backend/migration/1723944246767-followedMessage.js b/packages/backend/migration/1723944246767-followedMessage.js
new file mode 100644
index 0000000000..fc9ad1cb85
--- /dev/null
+++ b/packages/backend/migration/1723944246767-followedMessage.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class FollowedMessage1723944246767 {
+ name = 'FollowedMessage1723944246767';
+
+ async up(queryRunner) {
+ await queryRunner.query('ALTER TABLE "user_profile" ADD "followedMessage" character varying(256)');
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query('ALTER TABLE "user_profile" DROP COLUMN "followedMessage"');
+ }
+}
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 3f1c6b7125..77e7b60bea 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -275,16 +275,19 @@ export class UserFollowingService implements OnModuleInit {
followeeId: followee.id,
followerId: follower.id,
});
-
- // 通知を作成
- if (follower.host === null) {
- this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
- }, followee.id);
- }
}
if (alreadyFollowed) return;
+ // 通知を作成
+ if (follower.host === null) {
+ const profile = await this.cacheService.userProfileCache.fetch(followee.id);
+
+ this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
+ message: profile.followedMessage,
+ }, followee.id);
+ }
+
this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id });
const [followeeUser, followerUser] = await Promise.all([
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 98e944f347..fba8947f03 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -494,6 +494,7 @@ export class ApRendererService {
name: user.name,
summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null,
_misskey_summary: profile.description,
+ _misskey_followedMessage: profile.followedMessage,
icon: avatar ? this.renderImage(avatar) : null,
image: banner ? this.renderImage(banner) : null,
tag,
diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts
index feb8c42c56..3dd85b9b86 100644
--- a/packages/backend/src/core/activitypub/misc/contexts.ts
+++ b/packages/backend/src/core/activitypub/misc/contexts.ts
@@ -554,6 +554,7 @@ const extension_context_definition = {
'_misskey_reaction': 'misskey:_misskey_reaction',
'_misskey_votes': 'misskey:_misskey_votes',
'_misskey_summary': 'misskey:_misskey_summary',
+ '_misskey_followedMessage': 'misskey:_misskey_followedMessage',
'isCat': 'misskey:isCat',
// vcard
vcard: 'http://www.w3.org/2006/vcard/ns#',
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index 39c18e5e15..e042a85782 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -45,7 +45,7 @@ import type { ApNoteService } from './ApNoteService.js';
import type { ApMfmService } from '../ApMfmService.js';
import type { ApResolverService, Resolver } from '../ApResolverService.js';
import type { ApLoggerService } from '../ApLoggerService.js';
-// eslint-disable-next-line @typescript-eslint/consistent-type-imports
+
import type { ApImageService } from './ApImageService.js';
import type { IActor, ICollection, IObject, IOrderedCollection } from '../type.js';
@@ -307,8 +307,8 @@ export class ApPersonService implements OnModuleInit {
this.logger.error('error occurred while fetching following/followers collection', { stack: err });
}
return 'private';
- })
- )
+ }),
+ ),
);
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
@@ -370,6 +370,7 @@ export class ApPersonService implements OnModuleInit {
await transactionalEntityManager.save(new MiUserProfile({
userId: user.id,
description: _description,
+ followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null,
url,
fields,
followingVisibility,
@@ -494,8 +495,8 @@ export class ApPersonService implements OnModuleInit {
return undefined;
}
return 'private';
- })
- )
+ }),
+ ),
);
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
@@ -566,6 +567,7 @@ export class ApPersonService implements OnModuleInit {
url,
fields,
description: _description,
+ followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null,
followingVisibility,
followersVisibility,
birthday: bday?.[0] ?? null,
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index 16812b7a4d..154965b9d5 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -13,6 +13,7 @@ export interface IObject {
name?: string | null;
summary?: string;
_misskey_summary?: string;
+ _misskey_followedMessage?: string | null;
published?: string;
cc?: ApObject;
to?: ApObject;
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index 1b61a6195d..dff6968f9c 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -59,7 +59,7 @@ export class NotificationEntityService implements OnModuleInit {
async #packInternal <T extends MiNotification | MiGroupedNotification> (
src: T,
meId: MiUser['id'],
- // eslint-disable-next-line @typescript-eslint/ban-types
+
options: {
checkValidNotifier?: boolean;
},
@@ -159,6 +159,9 @@ export class NotificationEntityService implements OnModuleInit {
...(notification.type === 'roleAssigned' ? {
role: role,
} : {}),
+ ...(notification.type === 'followRequestAccepted' ? {
+ message: notification.message,
+ } : {}),
...(notification.type === 'achievementEarned' ? {
achievement: notification.achievement,
} : {}),
@@ -233,7 +236,7 @@ export class NotificationEntityService implements OnModuleInit {
public async pack(
src: MiNotification | MiGroupedNotification,
meId: MiUser['id'],
- // eslint-disable-next-line @typescript-eslint/ban-types
+
options: {
checkValidNotifier?: boolean;
},
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 9bf568bc90..69e2d6fc89 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -508,7 +508,7 @@ export class UserEntityService implements OnModuleInit {
name: r.name,
iconUrl: r.iconUrl,
displayOrder: r.displayOrder,
- }))
+ })),
) : undefined,
...(isDetailed ? {
@@ -567,6 +567,7 @@ export class UserEntityService implements OnModuleInit {
...(isDetailed && isMe ? {
avatarId: user.avatarId,
bannerId: user.bannerId,
+ followedMessage: profile!.followedMessage,
isModerator: isModerator,
isAdmin: isAdmin,
injectFeaturedNote: profile!.injectFeaturedNote,
@@ -635,6 +636,7 @@ export class UserEntityService implements OnModuleInit {
isRenoteMuted: relation.isRenoteMuted,
notify: relation.following?.notify ?? 'none',
withReplies: relation.following?.withReplies ?? false,
+ followedMessage: relation.isFollowing ? profile!.followedMessage : undefined,
} : {}),
} as Promiseable<Packed<S>>;
diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts
index 2c5b75f577..c1d3d42134 100644
--- a/packages/backend/src/models/Notification.ts
+++ b/packages/backend/src/models/Notification.ts
@@ -69,6 +69,7 @@ export type MiNotification = {
id: string;
createdAt: string;
notifierId: MiUser['id'];
+ message: string | null;
} | {
type: 'roleAssigned';
id: string;
diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts
index 9e2d7a3444..21362235ab 100644
--- a/packages/backend/src/models/User.ts
+++ b/packages/backend/src/models/User.ts
@@ -289,5 +289,6 @@ export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toStr
export const passwordSchema = { type: 'string', minLength: 1 } as const;
export const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
export const descriptionSchema = { type: 'string', minLength: 1, maxLength: 1500 } as const;
+export const followedMessageSchema = { type: 'string', minLength: 1, maxLength: 256 } as const;
export const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
export const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const;
diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts
index 7dbe0b3717..5544555296 100644
--- a/packages/backend/src/models/UserProfile.ts
+++ b/packages/backend/src/models/UserProfile.ts
@@ -42,6 +42,14 @@ export class MiUserProfile {
})
public description: string | null;
+ // フォローされた際のメッセージ
+ @Column('varchar', {
+ length: 256, nullable: true,
+ })
+ public followedMessage: string | null;
+
+ // TODO: 鍵アカウントの場合の、フォローリクエスト受信時のメッセージも設定できるようにする
+
@Column('jsonb', {
default: [],
})
diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts
index bbec2e397f..2645010491 100644
--- a/packages/backend/src/models/json-schema/notification.ts
+++ b/packages/backend/src/models/json-schema/notification.ts
@@ -267,6 +267,10 @@ export const packedNotificationSchema = {
optional: false, nullable: false,
format: 'id',
},
+ message: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
},
}, {
type: 'object',
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index 947a9317d7..16c8a5a097 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -370,6 +370,10 @@ export const packedUserDetailedNotMeOnlySchema = {
ref: 'RoleLite',
},
},
+ followedMessage: {
+ type: 'string',
+ nullable: true, optional: true,
+ },
memo: {
type: 'string',
nullable: true, optional: false,
@@ -437,6 +441,10 @@ export const packedMeDetailedOnlySchema = {
nullable: true, optional: false,
format: 'id',
},
+ followedMessage: {
+ type: 'string',
+ nullable: true, optional: false,
+ },
isModerator: {
type: 'boolean',
nullable: true, optional: false,
diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts
index 5a1c05f41a..655bd32bce 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -31,6 +31,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ followedMessage: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
autoAcceptFollowed: {
type: 'boolean',
optional: false, nullable: false,
@@ -226,6 +230,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return {
email: profile.email,
emailVerified: profile.emailVerified,
+ followedMessage: profile.followedMessage,
autoAcceptFollowed: profile.autoAcceptFollowed,
noCrawle: profile.noCrawle,
preventAiLearning: profile.preventAiLearning,
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index a1e2fa5e4c..798bd98cf1 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -13,9 +13,8 @@ import { extractHashtags } from '@/misc/extract-hashtags.js';
import * as Acct from '@/misc/acct.js';
import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/_.js';
import type { MiLocalUser, MiUser } from '@/models/User.js';
-import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js';
+import { birthdaySchema, descriptionSchema, followedMessageSchema, locationSchema, nameSchema } from '@/models/User.js';
import type { MiUserProfile } from '@/models/UserProfile.js';
-import { notificationTypes } from '@/types.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { langmap } from '@/misc/langmap.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
@@ -134,6 +133,7 @@ export const paramDef = {
properties: {
name: { ...nameSchema, nullable: true },
description: { ...descriptionSchema, nullable: true },
+ followedMessage: { ...followedMessageSchema, nullable: true },
location: { ...locationSchema, nullable: true },
birthday: { ...birthdaySchema, nullable: true },
lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true },
@@ -267,6 +267,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
}
if (ps.description !== undefined) profileUpdates.description = ps.description;
+ if (ps.followedMessage !== undefined) profileUpdates.followedMessage = ps.followedMessage;
if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
if (ps.location !== undefined) profileUpdates.location = ps.location;
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index 61fd759932..8ebe9af792 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -7,9 +7,9 @@ process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import { inspect } from 'node:util';
-import { DEFAULT_POLICIES } from '@/core/RoleService.js';
import { api, post, role, signup, successfulApiCall, uploadFile } from '../utils.js';
import type * as misskey from 'misskey-js';
+import { DEFAULT_POLICIES } from '@/core/RoleService.js';
describe('ユーザー', () => {
// エンティティとしてのユーザーを主眼においたテストを記述する
@@ -105,6 +105,7 @@ describe('ユーザー', () => {
isRenoteMuted: user.isRenoteMuted ?? false,
notify: user.notify ?? 'none',
withReplies: user.withReplies ?? false,
+ followedMessage: user.isFollowing ? (user.followedMessage ?? null) : undefined,
});
};
@@ -114,6 +115,7 @@ describe('ユーザー', () => {
...userDetailedNotMe(user),
avatarId: user.avatarId,
bannerId: user.bannerId,
+ followedMessage: user.followedMessage,
isModerator: user.isModerator,
isAdmin: user.isAdmin,
injectFeaturedNote: user.injectFeaturedNote,
@@ -350,6 +352,7 @@ describe('ユーザー', () => {
// MeDetailedOnly
assert.strictEqual(response.avatarId, null);
assert.strictEqual(response.bannerId, null);
+ assert.strictEqual(response.followedMessage, null);
assert.strictEqual(response.isModerator, false);
assert.strictEqual(response.isAdmin, false);
assert.strictEqual(response.injectFeaturedNote, true);
@@ -413,6 +416,8 @@ describe('ユーザー', () => {
{ parameters: () => ({ description: 'x'.repeat(1500) }) },
{ parameters: () => ({ description: 'x' }) },
{ parameters: () => ({ description: 'My description' }) },
+ { parameters: () => ({ followedMessage: null }) },
+ { parameters: () => ({ followedMessage: 'Thank you' }) },
{ parameters: () => ({ location: null }) },
{ parameters: () => ({ location: 'x'.repeat(50) }) },
{ parameters: () => ({ location: 'x' }) },