diff --git a/locales/en-US.yml b/locales/en-US.yml index 2e6cb32e31..85e865e2f7 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -164,6 +164,8 @@ flagAsBot: "Mark this account as a bot" flagAsBotDescription: "Enable this option if this account is controlled by a program. If enabled, it will act as a flag for other developers to prevent endless interaction chains with other bots and adjust Misskey's internal systems to treat this account as a bot." flagAsCat: "Mark this account as a cat" flagAsCatDescription: "Enable this option to mark this account as a cat." +flagSpeakAsCat: "Speak as a cat" +flagSpeakAsCatDescription: "Your posts will get nyanified when in cat mode." flagShowTimelineReplies: "Show replies in timeline" flagShowTimelineRepliesDescription: "Shows replies of users to notes of other users in the timeline if turned on." autoAcceptFollowed: "Automatically approve follow requests from users you're following" diff --git a/packages/backend/migration/1696386694000-speakAsCat.js b/packages/backend/migration/1696386694000-speakAsCat.js new file mode 100644 index 0000000000..d68b9401bf --- /dev/null +++ b/packages/backend/migration/1696386694000-speakAsCat.js @@ -0,0 +1,12 @@ +export class SpeakAsCat1696386694000 { + name = "SpeakAsCat1696386694000"; + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "speakAsCat" boolean NOT NULL DEFAULT true`); + await queryRunner.query(`COMMENT ON COLUMN "user"."speakAsCat" IS 'Whether to speak as a cat if chosen.'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "speakAsCat"`); + } +} diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 43e4ddbf32..155aee39a9 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -501,6 +501,7 @@ export class ApRendererService { discoverable: user.isExplorable, publicKey: this.renderKey(user, keypair, '#main-key'), isCat: user.isCat, + speakAsCat: user.speakAsCat, attachment: attachment.length ? attachment : undefined, }; @@ -646,6 +647,9 @@ export class ApRendererService { '_misskey_reaction': 'misskey:_misskey_reaction', '_misskey_votes': 'misskey:_misskey_votes', 'isCat': 'misskey:isCat', + // Firefish + firefish: "https://joinfirefish.org/ns#", + speakAsCat: "firefish:speakAsCat", // 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 2469583e5a..bbb362646d 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -326,6 +326,7 @@ export class ApPersonService implements OnModuleInit { tags, isBot, isCat: (person as any).isCat === true, + speakAsCat: (person as any).speakAsCat != null ? (person as any).speakAsCat === true : (person as any).isCat === true, emojis, })) as MiRemoteUser; @@ -460,12 +461,13 @@ export class ApPersonService implements OnModuleInit { tags, isBot: getApType(object) === 'Service', isCat: (person as any).isCat === true, + speakAsCat: (person as any).speakAsCat != null ? (person as any).speakAsCat === true : (person as any).isCat === true, isLocked: person.manuallyApprovesFollowers, movedToUri: person.movedTo ?? null, alsoKnownAs: person.alsoKnownAs ?? null, isExplorable: person.discoverable, ...(await this.resolveAvatarAndBanner(exist, person.icon, person.image).catch(() => ({}))), - } as Partial & Pick; + } as Partial & Pick; const moving = ((): boolean => { // 移行先がない→ある diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 026f84bb39..22da6fadd4 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -364,7 +364,7 @@ export class NoteEntityService implements OnModuleInit { } : {}), }); - if (packed.user.isCat && packed.text) { + if (packed.user.isCat && packed.user.speakAsCat && packed.text) { const tokens = packed.text ? mfm.parse(packed.text) : []; function nyaizeNode(node: mfm.MfmNode) { if (node.type === 'quote') return; diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index cdd1182f6d..714459d76b 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -352,6 +352,7 @@ export class UserEntityService implements OnModuleInit { createdAt: user.createdAt.toISOString(), isBot: user.isBot ?? falsy, isCat: user.isCat ?? falsy, + speakAsCat: user.speakAsCat ?? falsy, instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? { name: instance.name, softwareName: instance.softwareName, diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 8f0122a90c..08c8243421 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -174,6 +174,12 @@ export class MiUser { }) public isCat: boolean; + @Column('boolean', { + default: true, + comment: 'Whether the User speaks in nya.', + }) + public speakAsCat: boolean; + @Column('boolean', { default: false, comment: 'Whether the User is the root.', diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 79b14bb65f..a8fb34acb1 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -55,6 +55,10 @@ export const packedUserLiteSchema = { type: 'boolean', nullable: false, optional: true, }, + speakAsCat: { + type: 'boolean', + nullable: false, optional: true, + }, onlineStatus: { type: 'string', format: 'url', diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index e5bf27d227..93897b9c8f 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -156,6 +156,7 @@ export const paramDef = { preventAiLearning: { type: 'boolean' }, isBot: { type: 'boolean' }, isCat: { type: 'boolean' }, + speakAsCat: { type: 'boolean' }, injectFeaturedNote: { type: 'boolean' }, receiveAnnouncementEmail: { type: 'boolean' }, alwaysMarkNsfw: { type: 'boolean' }, @@ -259,6 +260,7 @@ export default class extends Endpoint { // eslint- if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle; if (typeof ps.preventAiLearning === 'boolean') profileUpdates.preventAiLearning = ps.preventAiLearning; if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat; + if (typeof ps.speakAsCat === 'boolean') updates.speakAsCat = ps.speakAsCat; if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; if (typeof ps.alwaysMarkNsfw === 'boolean') { diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index 0f5d5f7344..93fbdaaa32 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -70,6 +70,7 @@ describe('ユーザー', () => { avatarBlurhash: user.avatarBlurhash, isBot: user.isBot, isCat: user.isCat, + speakAsCat: user.speakAsCat, instance: user.instance, emojis: user.emojis, onlineStatus: user.onlineStatus, @@ -350,6 +351,7 @@ describe('ユーザー', () => { assert.strictEqual(response.avatarBlurhash, null); assert.strictEqual(response.isBot, false); assert.strictEqual(response.isCat, false); + assert.strictEqual(response.speakAsCat, false); assert.strictEqual(response.instance, undefined); assert.deepStrictEqual(response.emojis, {}); assert.strictEqual(response.onlineStatus, 'unknown'); @@ -481,6 +483,8 @@ describe('ユーザー', () => { { parameters: (): object => ({ isBot: false }) }, { parameters: (): object => ({ isCat: true }) }, { parameters: (): object => ({ isCat: false }) }, + { parameters: (): object => ({ speakAsCat: true }) }, + { parameters: (): object => ({ speakAsCat: false }) }, { parameters: (): object => ({ injectFeaturedNote: true }) }, { parameters: (): object => ({ injectFeaturedNote: false }) }, { parameters: (): object => ({ receiveAnnouncementEmail: true }) }, diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts index 811c243926..47f9a53dad 100644 --- a/packages/frontend/.storybook/fakes.ts +++ b/packages/frontend/.storybook/fakes.ts @@ -99,6 +99,7 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi isBlocking: false, isBot: false, isCat: false, + speakAsCat: false, isFollowed: false, isFollowing: false, isLocked: false, diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index b6377d8b9b..904ed03ee2 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -93,6 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.flagAsCat }} + {{ i18n.ts.flagSpeakAsCat }} {{ i18n.ts.flagAsBot }}
@@ -141,6 +142,7 @@ const profile = reactive({ lang: $i.lang, isBot: $i.isBot, isCat: $i.isCat, + speakAsCat: $i.speakAsCat, }); watch(() => profile, () => { @@ -190,6 +192,7 @@ function save() { lang: profile.lang || null, isBot: !!profile.isBot, isCat: !!profile.isCat, + speakAsCat: !!profile.speakAsCat, }); claimAchievement('profileFilled'); if (profile.name === 'syuilo' || profile.name === 'しゅいろ') { diff --git a/packages/megalodon/src/misskey/entities/userDetail.ts b/packages/megalodon/src/misskey/entities/userDetail.ts index bf0e3c2c29..0a59278605 100644 --- a/packages/megalodon/src/misskey/entities/userDetail.ts +++ b/packages/megalodon/src/misskey/entities/userDetail.ts @@ -13,6 +13,7 @@ namespace MisskeyEntity { isModerator: boolean isBot: boolean isCat: boolean + speakAsCat: boolean emojis: Array | { [key: string]: string } createdAt: string bannerUrl: string diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index f0fc47c207..5fb80c6a3f 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1541,6 +1541,7 @@ export type Endpoints = { noCrawle?: boolean; isBot?: boolean; isCat?: boolean; + speakAsCat?: boolean; injectFeaturedNote?: boolean; receiveAnnouncementEmail?: boolean; alwaysMarkNsfw?: boolean; @@ -2915,6 +2916,7 @@ type UserDetailed = UserLite & { isBlocking: boolean; isBot: boolean; isCat: boolean; + speakAsCat: boolean; isFollowed: boolean; isFollowing: boolean; isLocked: boolean; diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts index 7a8b6872dc..b3837369ec 100644 --- a/packages/misskey-js/src/api.types.ts +++ b/packages/misskey-js/src/api.types.ts @@ -426,6 +426,7 @@ export type Endpoints = { noCrawle?: boolean; isBot?: boolean; isCat?: boolean; + speakAsCat?: boolean; injectFeaturedNote?: boolean; receiveAnnouncementEmail?: boolean; alwaysMarkNsfw?: boolean; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 301d4bc3ac..59df4582de 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -50,6 +50,7 @@ export type UserDetailed = UserLite & { isBlocking: boolean; isBot: boolean; isCat: boolean; + speakAsCat: boolean; isFollowed: boolean; isFollowing: boolean; isLocked: boolean;