diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index aa8e2bd16..04dec100f 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -655,6 +655,8 @@ useSystemFont: "システムのデフォルトのフォントを使う" clips: "クリップ" experimentalFeatures: "実験的機能" developer: "開発者" +makeExplorable: "アカウントを見つけやすくする" +makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らなくなります。" _aboutMisskey: about: "Misskeyはsyuiloによって2014年から開発されている、オープンソースのソフトウェアです。" diff --git a/migration/1607353487793-isExplorable.ts b/migration/1607353487793-isExplorable.ts new file mode 100644 index 000000000..034f8c384 --- /dev/null +++ b/migration/1607353487793-isExplorable.ts @@ -0,0 +1,18 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class isExplorable1607353487793 implements MigrationInterface { + name = 'isExplorable1607353487793' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user" ADD "isExplorable" boolean NOT NULL DEFAULT true`); + await queryRunner.query(`COMMENT ON COLUMN "user"."isExplorable" IS 'Whether the User is explorable.'`); + await queryRunner.query(`CREATE INDEX "IDX_d5a1b83c7cab66f167e6888188" ON "user" ("isExplorable") `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_d5a1b83c7cab66f167e6888188"`); + await queryRunner.query(`COMMENT ON COLUMN "user"."isExplorable" IS 'Whether the User is explorable.'`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isExplorable"`); + } + +} diff --git a/src/client/pages/settings/privacy.vue b/src/client/pages/settings/privacy.vue index 09db07750..d5242a5f5 100644 --- a/src/client/pages/settings/privacy.vue +++ b/src/client/pages/settings/privacy.vue @@ -9,6 +9,10 @@ {{ $t('noCrawle') }} + + {{ $t('makeExplorable') }} + + {{ $t('rememberNoteVisibility') }} @@ -51,6 +55,7 @@ export default defineComponent({ isLocked: false, autoAcceptFollowed: false, noCrawle: false, + isExplorable: false, } }, @@ -75,6 +80,7 @@ export default defineComponent({ this.isLocked = this.$store.state.i.isLocked; this.autoAcceptFollowed = this.$store.state.i.autoAcceptFollowed; this.noCrawle = this.$store.state.i.noCrawle; + this.isExplorable = this.$store.state.i.isExplorable; }, mounted() { @@ -87,6 +93,7 @@ export default defineComponent({ isLocked: !!this.isLocked, autoAcceptFollowed: !!this.autoAcceptFollowed, noCrawle: !!this.noCrawle, + isExplorable: !!this.isExplorable, }); } } diff --git a/src/models/entities/user.ts b/src/models/entities/user.ts index fee5906a3..ba2062fdb 100644 --- a/src/models/entities/user.ts +++ b/src/models/entities/user.ts @@ -157,6 +157,13 @@ export class User { }) public isModerator: boolean; + @Index() + @Column('boolean', { + default: true, + comment: 'Whether the User is explorable.' + }) + public isExplorable: boolean; + @Column('varchar', { length: 128, array: true, default: '{}' }) diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 87f50b448..29facf523 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -240,6 +240,7 @@ export class UserRepository extends Repository { carefulBot: profile!.carefulBot, autoAcceptFollowed: profile!.autoAcceptFollowed, noCrawle: profile!.noCrawle, + isExplorable: user.isExplorable, hasUnreadSpecifiedNotes: NoteUnreads.count({ where: { userId: user.id, isSpecified: true }, take: 1 diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 9f6392174..f0a312b21 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -153,6 +153,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise; if (avatar) { diff --git a/src/remote/activitypub/renderer/index.ts b/src/remote/activitypub/renderer/index.ts index cf0fd8d85..a34febff2 100644 --- a/src/remote/activitypub/renderer/index.ts +++ b/src/remote/activitypub/renderer/index.ts @@ -38,6 +38,7 @@ export const attachLdSignature = async (activity: any, user: ILocalUser): Promis toot: 'http://joinmastodon.org/ns#', Emoji: 'toot:Emoji', featured: 'toot:featured', + discoverable: 'toot:discoverable', // schema schema: 'http://schema.org#', PropertyValue: 'schema:PropertyValue', diff --git a/src/remote/activitypub/renderer/person.ts b/src/remote/activitypub/renderer/person.ts index 87dca19ac..4462f8831 100644 --- a/src/remote/activitypub/renderer/person.ts +++ b/src/remote/activitypub/renderer/person.ts @@ -70,6 +70,7 @@ export async function renderPerson(user: ILocalUser) { image: banner ? renderImage(banner) : null, tag, manuallyApprovesFollowers: user.isLocked, + discoverable: !!user.isExplorable, publicKey: renderKey(user, keypair, `#main-key`), isCat: user.isCat, attachment: attachment.length ? attachment : undefined diff --git a/src/remote/activitypub/type.ts b/src/remote/activitypub/type.ts index 5c01c24b5..db866ae67 100644 --- a/src/remote/activitypub/type.ts +++ b/src/remote/activitypub/type.ts @@ -135,6 +135,7 @@ export interface IPerson extends IObject { name?: string; preferredUsername?: string; manuallyApprovesFollowers?: boolean; + discoverable?: boolean; inbox?: string; sharedInbox?: string; // 後方互換性のため publicKey: { diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 087267120..8ac427cd5 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -92,6 +92,10 @@ export const meta = { } }, + isExplorable: { + validator: $.optional.bool, + }, + carefulBot: { validator: $.optional.bool, desc: { @@ -208,6 +212,7 @@ export default define(meta, async (ps, user, token) => { } if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes as typeof notificationTypes[number][]; if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked; + if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable; if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot; if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot; if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts index 9d7991b40..1c1647257 100644 --- a/src/server/api/endpoints/users.ts +++ b/src/server/api/endpoints/users.ts @@ -64,12 +64,13 @@ export const meta = { export default define(meta, async (ps, me) => { const query = Users.createQueryBuilder('user'); + query.where('user.isExplorable = TRUE'); switch (ps.state) { - case 'admin': query.where('user.isAdmin = TRUE'); break; - case 'moderator': query.where('user.isModerator = TRUE'); break; - case 'adminOrModerator': query.where('user.isAdmin = TRUE OR isModerator = TRUE'); break; - case 'alive': query.where('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; + case 'admin': query.andWhere('user.isAdmin = TRUE'); break; + case 'moderator': query.andWhere('user.isModerator = TRUE'); break; + case 'adminOrModerator': query.andWhere('user.isAdmin = TRUE OR isModerator = TRUE'); break; + case 'alive': query.andWhere('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; } switch (ps.origin) { diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index 1b59624aa..7f80b5275 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -42,6 +42,7 @@ export const meta = { export default define(meta, async (ps, me) => { const query = Users.createQueryBuilder('user') .where('user.isLocked = FALSE') + .andWhere('user.isExplorable = TRUE') .andWhere('user.host IS NULL') .andWhere('user.updatedAt >= :date', { date: new Date(Date.now() - ms('7days')) }) .andWhere('user.id != :meId', { meId: me.id }) diff --git a/src/services/create-system-user.ts b/src/services/create-system-user.ts index 7f59efb44..3c44c7427 100644 --- a/src/services/create-system-user.ts +++ b/src/services/create-system-user.ts @@ -34,6 +34,7 @@ export async function createSystemUser(username: string) { token: secret, isAdmin: false, isLocked: true, + isExplorable: false, isBot: true, }));