Skip to content

Commit

Permalink
feat: フォローされた際のメッセージを設定できるようにする
Browse files Browse the repository at this point in the history
Resolve #14425
  • Loading branch information
syuilo committed Aug 18, 2024
1 parent 3cd5f86 commit 42cf8b2
Show file tree
Hide file tree
Showing 21 changed files with 121 additions and 28 deletions.
12 changes: 12 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8629,6 +8629,18 @@ export interface Locale extends ILocale {
* 最大{max}つまでデコレーションを付けられます。
*/
"avatarDecorationMax": ParameterizedString<"max">;
/**
* フォローされた時のメッセージ
*/
"followedMessage": string;
/**
* フォローされた時に相手に表示するメッセージを設定できます。
*/
"followedMessageDescription": string;
/**
* フォローを承認制にしている場合、フォローリクエストを許可した時に表示されます。
*/
"followedMessageDescriptionForLockedAccount": string;
};
"_exportOrImport": {
/**
Expand Down
3 changes: 3 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2273,6 +2273,9 @@ _profile:
changeBanner: "バナー画像を変更"
verifiedLinkDescription: "内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。"
avatarDecorationMax: "最大{max}つまでデコレーションを付けられます。"
followedMessage: "フォローされた時のメッセージ"
followedMessageDescription: "フォローされた時に相手に表示するメッセージを設定できます。"
followedMessageDescriptionForLockedAccount: "フォローを承認制にしている場合、フォローリクエストを許可した時に表示されます。"

_exportOrImport:
allNotes: "全てのノート"
Expand Down
16 changes: 16 additions & 0 deletions packages/backend/migration/1723944246767-followedMessage.js
Original file line number Diff line number Diff line change
@@ -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"');
}
}
15 changes: 9 additions & 6 deletions packages/backend/src/core/UserFollowingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,16 +277,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([
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/core/activitypub/ApRendererService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/core/activitypub/misc/contexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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#',
Expand Down
12 changes: 7 additions & 5 deletions packages/backend/src/core/activitypub/models/ApPersonService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,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';

Expand Down Expand Up @@ -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}/);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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}/);
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/core/activitypub/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
Expand Down Expand Up @@ -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,
} : {}),
Expand Down Expand Up @@ -229,7 +232,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;
},
Expand Down
3 changes: 2 additions & 1 deletion packages/backend/src/core/entities/UserEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ export class UserEntityService implements OnModuleInit {
name: r.name,
iconUrl: r.iconUrl,
displayOrder: r.displayOrder,
}))
})),
) : undefined,

...(isDetailed ? {
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/models/Notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type MiNotification = {
id: string;
createdAt: string;
notifierId: MiUser['id'];
message: string | null;
} | {
type: 'roleAssigned';
id: string;
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
8 changes: 8 additions & 0 deletions packages/backend/src/models/UserProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
})
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/models/json-schema/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ export const packedNotificationSchema = {
optional: false, nullable: false,
format: 'id',
},
message: {
type: 'string',
optional: false, nullable: true,
},
},
}, {
type: 'object',
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/models/json-schema/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,10 @@ export const packedMeDetailedOnlySchema = {
nullable: true, optional: false,
format: 'id',
},
followedMessage: {
type: 'string',
nullable: true, optional: false,
},
isModerator: {
type: 'boolean',
nullable: true, optional: false,
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/server/api/endpoints/admin/show-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions packages/backend/src/server/api/endpoints/i/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion packages/backend/test/e2e/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('ユーザー', () => {
// エンティティとしてのユーザーを主眼においたテストを記述する
Expand Down Expand Up @@ -114,6 +114,7 @@ describe('ユーザー', () => {
...userDetailedNotMe(user),
avatarId: user.avatarId,
bannerId: user.bannerId,
followedMessage: user.followedMessage,
isModerator: user.isModerator,
isAdmin: user.isAdmin,
injectFeaturedNote: user.injectFeaturedNote,
Expand Down Expand Up @@ -350,6 +351,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);
Expand Down Expand Up @@ -413,6 +415,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' }) },
Expand Down
9 changes: 8 additions & 1 deletion packages/frontend/src/components/MkNotification.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-else-if="notification.type === 'follow'">
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
</template>
<span v-else-if="notification.type === 'followRequestAccepted'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
<template v-else-if="notification.type === 'followRequestAccepted'">
<div :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</div>
<div v-if="notification.message" :class="$style.text" style="opacity: 0.6; font-style: oblique;">
<i class="ti ti-quote" :class="$style.quote"></i>
<span>{{ notification.message }}</span>
<i class="ti ti-quote" :class="$style.quote"></i>
</div>
</template>
<template v-else-if="notification.type === 'receiveFollowRequest'">
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}</span>
<div v-if="full && !followRequestDone" :class="$style.followRequestCommands">
Expand Down
27 changes: 19 additions & 8 deletions packages/frontend/src/pages/settings/profile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
</FormSlot>

<MkFolder>
<template #label>{{ i18n.ts.advancedSettings }}</template>

<div class="_gaps_m">
<MkSwitch v-model="profile.isCat">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></MkSwitch>
<MkSwitch v-model="profile.isBot">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></MkSwitch>
</div>
</MkFolder>
<MkInput v-model="profile.followedMessage" :max="200" manualSave :mfmPreview="false">
<template #label>{{ i18n.ts._profile.followedMessage }}</template>
<template #caption>
<div>{{ i18n.ts._profile.followedMessageDescription }}</div>
<div>{{ i18n.ts._profile.followedMessageDescriptionForLockedAccount }}</div>
</template>
</MkInput>

<MkSelect v-model="reactionAcceptance">
<template #label>{{ i18n.ts.reactionAcceptance }}</template>
Expand All @@ -105,6 +104,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<option value="nonSensitiveOnlyForLocalLikeOnlyForRemote">{{ i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }}</option>
<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
</MkSelect>

<MkFolder>
<template #label>{{ i18n.ts.advancedSettings }}</template>

<div class="_gaps_m">
<MkSwitch v-model="profile.isCat">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></MkSwitch>
<MkSwitch v-model="profile.isBot">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></MkSwitch>
</div>
</MkFolder>
</div>
</template>

Expand Down Expand Up @@ -138,6 +146,7 @@ const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAccep
const profile = reactive({
name: $i.name,
description: $i.description,
followedMessage: $i.followedMessage,
location: $i.location,
birthday: $i.birthday,
lang: $i.lang,
Expand Down Expand Up @@ -185,6 +194,8 @@ function save() {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
description: profile.description || null,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
followedMessage: profile.followedMessage || null,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
location: profile.location || null,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
birthday: profile.birthday || null,
Expand Down
Loading

0 comments on commit 42cf8b2

Please sign in to comment.