diff --git a/CHANGELOG.md b/CHANGELOG.md index c815e65ab39069295ba6f82206d1e26d43e0bf4d..4d8c8ded3a9ceddc86fb249fcb61c3eaeab9d34c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## Unreleased ### General -- +- Feat: コンテンツã®è¡¨ç¤ºã«ãƒã‚°ã‚¤ãƒ³ã‚’å¿…é ˆã«ã§ãるよã†ã« ### Client - Enhance: Bull Dashboardã§Relationship Queueã®çŠ¶æ…‹ã‚‚確èªã§ãるよã†ã« diff --git a/locales/index.d.ts b/locales/index.d.ts index fb010d9353e2359bc7a1450f40f6141b32e72d6a..e00254030746d733745c32cfbdee81e5f2c7b2b6 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5190,6 +5190,32 @@ export interface Locale extends ILocale { * åå‰ã«ç¦æ¢ã•ã‚Œã¦ã„ã‚‹æ–‡å—列ãŒå«ã¾ã‚Œã¦ã„ã¾ã™ã€‚ã“ã®åå‰ã‚’使用ã—ãŸã„å ´åˆã¯ã€ã‚µãƒ¼ãƒãƒ¼ç®¡ç†è€…ã«ãŠå•ã„åˆã‚ã›ãã ã•ã„。 */ "yourNameContainsProhibitedWordsDescription": string; + /** + * 投稿者ã«ã‚ˆã‚Šã€è¡¨ç¤ºã«ã¯ãƒã‚°ã‚¤ãƒ³ãŒå¿…è¦ã¨è¨å®šã•ã‚Œã¦ã„ã¾ã™ + */ + "thisContentsAreMarkedAsSigninRequiredByAuthor": string; + /** + * ãƒãƒƒã‚¯ãƒ€ã‚¦ãƒ³ + */ + "lockdown": string; + "_accountSettings": { + /** + * コンテンツã®è¡¨ç¤ºã«ãƒã‚°ã‚¤ãƒ³ã‚’å¿…é ˆã«ã™ã‚‹ + */ + "requireSigninToViewContents": string; + /** + * ã‚ãªãŸãŒä½œæˆã—ãŸå…¨ã¦ã®ãƒŽãƒ¼ãƒˆãªã©ã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„を表示ã™ã‚‹ã®ã«ãƒã‚°ã‚¤ãƒ³ã‚’å¿…é ˆã«ã—ã¾ã™ã€‚クãƒãƒ¼ãƒ©ãƒ¼ã‹ã‚‰æƒ…å ±ã‚’åŽé›†ã•ã‚Œã‚‹ã®ã‚’防ã効果ãŒæœŸå¾…ã§ãã¾ã™ã€‚ + */ + "requireSigninToViewContentsDescription1": string; + /** + * URLプレビュー(OGP)ã€Webページã¸ã®åŸ‹ã‚è¾¼ã¿ã€ãƒŽãƒ¼ãƒˆã®å¼•ç”¨ã«å¯¾å¿œã—ã¦ã„ãªã„サーãƒãƒ¼ã‹ã‚‰ã®è¡¨ç¤ºã‚‚ä¸å¯ã«ãªã‚Šã¾ã™ã€‚ + */ + "requireSigninToViewContentsDescription2": string; + /** + * リモートサーãƒãƒ¼ã«é€£åˆã•ã‚ŒãŸã‚³ãƒ³ãƒ†ãƒ³ãƒ„ã§ã¯ã€ã“れらã®åˆ¶é™ãŒé©ç”¨ã•ã‚Œãªã„å ´åˆãŒã‚ã‚Šã¾ã™ã€‚ + */ + "requireSigninToViewContentsDescription3": string; + }; "_abuseUserReport": { /** * è»¢é€ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index c241a9e5602a6a614b82c84cd99d0d265e388890..f3f7e5c77f0ea0f9a1add704b9821a36384966ec 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1293,6 +1293,14 @@ prohibitedWordsForNameOfUser: "ç¦æ¢ãƒ¯ãƒ¼ãƒ‰ï¼ˆãƒ¦ãƒ¼ã‚¶ãƒ¼ã®åå‰ï¼‰" prohibitedWordsForNameOfUserDescription: "ã“ã®ãƒªã‚¹ãƒˆã«å«ã¾ã‚Œã‚‹æ–‡å—列ãŒãƒ¦ãƒ¼ã‚¶ãƒ¼ã®åå‰ã«å«ã¾ã‚Œã‚‹å ´åˆã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®åå‰ã®å¤‰æ›´ã‚’æ‹’å¦ã—ã¾ã™ã€‚モデレーター権é™ã‚’æŒã¤ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã“ã®åˆ¶é™ã®å½±éŸ¿ã‚’å—ã‘ã¾ã›ã‚“。" yourNameContainsProhibitedWords: "変更ã—よã†ã¨ã—ãŸåå‰ã«ç¦æ¢ã•ã‚ŒãŸæ–‡å—列ãŒå«ã¾ã‚Œã¦ã„ã¾ã™" yourNameContainsProhibitedWordsDescription: "åå‰ã«ç¦æ¢ã•ã‚Œã¦ã„ã‚‹æ–‡å—列ãŒå«ã¾ã‚Œã¦ã„ã¾ã™ã€‚ã“ã®åå‰ã‚’使用ã—ãŸã„å ´åˆã¯ã€ã‚µãƒ¼ãƒãƒ¼ç®¡ç†è€…ã«ãŠå•ã„åˆã‚ã›ãã ã•ã„。" +thisContentsAreMarkedAsSigninRequiredByAuthor: "投稿者ã«ã‚ˆã‚Šã€è¡¨ç¤ºã«ã¯ãƒã‚°ã‚¤ãƒ³ãŒå¿…è¦ã¨è¨å®šã•ã‚Œã¦ã„ã¾ã™" +lockdown: "ãƒãƒƒã‚¯ãƒ€ã‚¦ãƒ³" + +_accountSettings: + requireSigninToViewContents: "コンテンツã®è¡¨ç¤ºã«ãƒã‚°ã‚¤ãƒ³ã‚’å¿…é ˆã«ã™ã‚‹" + requireSigninToViewContentsDescription1: "ã‚ãªãŸãŒä½œæˆã—ãŸå…¨ã¦ã®ãƒŽãƒ¼ãƒˆãªã©ã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„を表示ã™ã‚‹ã®ã«ãƒã‚°ã‚¤ãƒ³ã‚’å¿…é ˆã«ã—ã¾ã™ã€‚クãƒãƒ¼ãƒ©ãƒ¼ã‹ã‚‰æƒ…å ±ã‚’åŽé›†ã•ã‚Œã‚‹ã®ã‚’防ã効果ãŒæœŸå¾…ã§ãã¾ã™ã€‚" + requireSigninToViewContentsDescription2: "URLプレビュー(OGP)ã€Webページã¸ã®åŸ‹ã‚è¾¼ã¿ã€ãƒŽãƒ¼ãƒˆã®å¼•ç”¨ã«å¯¾å¿œã—ã¦ã„ãªã„サーãƒãƒ¼ã‹ã‚‰ã®è¡¨ç¤ºã‚‚ä¸å¯ã«ãªã‚Šã¾ã™ã€‚" + requireSigninToViewContentsDescription3: "リモートサーãƒãƒ¼ã«é€£åˆã•ã‚ŒãŸã‚³ãƒ³ãƒ†ãƒ³ãƒ„ã§ã¯ã€ã“れらã®åˆ¶é™ãŒé©ç”¨ã•ã‚Œãªã„å ´åˆãŒã‚ã‚Šã¾ã™ã€‚" _abuseUserReport: forward: "転é€" diff --git a/packages/backend/migration/1729333924409-signinRequiredForShowContents.js b/packages/backend/migration/1729333924409-signinRequiredForShowContents.js new file mode 100644 index 0000000000000000000000000000000000000000..5d4d1fcce2fde4c5e7456ce8a53d9966a51e494d --- /dev/null +++ b/packages/backend/migration/1729333924409-signinRequiredForShowContents.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SigninRequiredForShowContents1729333924409 { + name = 'SigninRequiredForShowContents1729333924409' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "requireSigninToViewContents" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "requireSigninToViewContents"`); + } +} diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index 55c8a527050cb74c0b738994615f69c7e533d087..254d961040f0d2afd0ae0b47299ffea00665a2d1 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -83,6 +83,7 @@ function generateDummyUser(override?: Partial<MiUser>): MiUser { isExplorable: true, isHibernated: false, isDeleted: false, + requireSigninToViewContents: false, emojis: [], score: 0, host: null, diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index fba8947f032773cabb19e071dfbf08e229947d56..8235d7ba303d2054aa861e02fecbb1756f980c5f 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -495,6 +495,7 @@ export class ApRendererService { summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null, _misskey_summary: profile.description, _misskey_followedMessage: profile.followedMessage, + _misskey_requireSigninToViewContents: user.requireSigninToViewContents, 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 3dd85b9b86c58cee0ea753731bc40aed622800b5..447f7ef3db60f67dff2d2826992c687599d9dc2b 100644 --- a/packages/backend/src/core/activitypub/misc/contexts.ts +++ b/packages/backend/src/core/activitypub/misc/contexts.ts @@ -555,6 +555,7 @@ const extension_context_definition = { '_misskey_votes': 'misskey:_misskey_votes', '_misskey_summary': 'misskey:_misskey_summary', '_misskey_followedMessage': 'misskey:_misskey_followedMessage', + '_misskey_requireSigninToViewContents': 'misskey:_misskey_requireSigninToViewContents', '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 73281078e530be3345957bf31a7d841fed37a886..c7915ed94fe982b2acf3f2c6d4c9a5448d78fcae 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -356,6 +356,7 @@ export class ApPersonService implements OnModuleInit { tags, isBot, isCat: (person as any).isCat === true, + requireSigninToViewContents: (person as any).requireSigninToViewContents === true, emojis, })) as MiRemoteUser; diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts index 154965b9d592ee1838a0176f2b4503ae1278385a..8a860335faf339b0704cc7ad4674801acab31dcb 100644 --- a/packages/backend/src/core/activitypub/type.ts +++ b/packages/backend/src/core/activitypub/type.ts @@ -14,6 +14,7 @@ export interface IObject { summary?: string; _misskey_summary?: string; _misskey_followedMessage?: string | null; + _misskey_requireSigninToViewContents?: boolean; published?: string; cc?: ApObject; to?: ApObject; diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 3e1f094fce594f17bef152f49d3429d8aa2d4976..62016936a2633fa1e315369ca64e9da55d06f5cc 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -149,6 +149,10 @@ export class NoteEntityService implements OnModuleInit { } } + if (packedNote.user.requireSigninToViewContents && meId == null) { + hide = true; + } + if (hide) { packedNote.visibleUserIds = undefined; packedNote.fileIds = []; diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index c9939adf11371f45be8429da7280bfa50d2dfdec..747ffc780f5dcca52f401c44a04a2c00a0bfce91 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -490,6 +490,7 @@ export class UserEntityService implements OnModuleInit { }))) : [], isBot: user.isBot, isCat: user.isCat, + requireSigninToViewContents: user.requireSigninToViewContents === false ? undefined : true, 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 805a1e75aebddd3a7e31a683be3c1adc04d8ca51..6fcff77854d22f1fef17472360ab93882d9cfd4c 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -202,6 +202,11 @@ export class MiUser { }) public isHibernated: boolean; + @Column('boolean', { + default: false, + }) + public requireSigninToViewContents: boolean; + // アカウントãŒå‰Šé™¤ã•ã‚ŒãŸã‹ã©ã†ã‹ã®ãƒ•ãƒ©ã‚°ã ãŒã€å®Œå…¨ã«å‰Šé™¤ã•ã‚Œã‚‹éš›ã¯ç‰©ç†å‰Šé™¤ãªã®ã§å®Ÿè³ªå‰Šé™¤ã•ã‚Œã‚‹ã¾ã§ã®ã€Œå‰Šé™¤ãŒé€²è¡Œã—ã¦ã„ã‚‹ã‹ã©ã†ã‹ã€ã®ãƒ•ãƒ©ã‚° @Column('boolean', { default: false, diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 9cffd680f29b73dad42a1da1752f63cd729d7b27..817f8e9292433b1f78ad9e825ad05ddfc8960d51 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -115,6 +115,10 @@ export const packedUserLiteSchema = { type: 'boolean', nullable: false, optional: true, }, + requireSigninToViewContents: { + type: 'boolean', + nullable: false, optional: true, + }, instance: { type: 'object', nullable: false, optional: true, diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts index bff3ab96f33cda2e64ddf5fb05382e45764e9d5b..444e6db744a60b197e72634c04af88171fd96032 100644 --- a/packages/backend/src/server/api/GetterService.ts +++ b/packages/backend/src/server/api/GetterService.ts @@ -39,6 +39,17 @@ export class GetterService { return note; } + @bindThis + public async getNoteWithUser(noteId: MiNote['id']) { + const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user'] }); + + if (note == null) { + throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); + } + + return note; + } + /** * Get user for API processing */ diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 0b35005a87c175ff55f43fb4c904f72bb9d6c87c..6680c96f3f2dfe519205b4e3411b5241672cdf89 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -179,6 +179,7 @@ export const paramDef = { autoAcceptFollowed: { type: 'boolean' }, noCrawle: { type: 'boolean' }, preventAiLearning: { type: 'boolean' }, + requireSigninToViewContents: { type: 'boolean' }, isBot: { type: 'boolean' }, isCat: { type: 'boolean' }, injectFeaturedNote: { type: 'boolean' }, @@ -334,6 +335,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle; if (typeof ps.preventAiLearning === 'boolean') profileUpdates.preventAiLearning = ps.preventAiLearning; + if (typeof ps.requireSigninToViewContents === 'boolean') updates.requireSigninToViewContents = ps.requireSigninToViewContents; if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat; if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index adcda30a7d88ac16f19ad5b0a70fbf31e76d5f5a..11839bce36604ab89713830c81dd623ddc51d4b2 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -26,6 +26,12 @@ export const meta = { code: 'NO_SUCH_NOTE', id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d', }, + + signinRequired: { + message: 'Signin required.', + code: 'SIGNIN_REQUIRED', + id: '8e75455b-738c-471d-9f80-62693f33372e', + }, }, } as const; @@ -44,11 +50,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private getterService: GetterService, ) { super(meta, paramDef, async (ps, me) => { - const note = await this.getterService.getNote(ps.noteId).catch(err => { + const note = await this.getterService.getNoteWithUser(ps.noteId).catch(err => { if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); throw err; }); + if (note.user!.requireSigninToViewContents && me == null) { + throw new ApiError(meta.errors.signinRequired); + } + return await this.noteEntityService.pack(note, me, { detail: true, }); diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 7fc11ba369057146f4c5460f317e504c15b0d775..e9c334057e763730fcb11aa99fee1492233272d2 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -42,6 +42,12 @@ export const meta = { code: 'BOTH_WITH_REPLIES_AND_WITH_FILES', id: '91c8cb9f-36ed-46e7-9ca2-7df96ed6e222', }, + + signinRequired: { + message: 'Signin required.', + code: 'SIGNIN_REQUIRED', + id: 'd1588a9e-4b4d-4c07-807f-16f1486577a2', + }, }, } as const; diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index c9c29e42a8c8cb2a72b98604319502363925bb85..4860ef3e12c5adce07e52c0e4c74d5edd7ccb8b6 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -601,12 +601,15 @@ export class ClientServerService { fastify.get<{ Params: { note: string; } }>('/notes/:note', async (request, reply) => { vary(reply.raw, 'Accept'); - const note = await this.notesRepository.findOneBy({ - id: request.params.note, - visibility: In(['public', 'home']), + const note = await this.notesRepository.findOne({ + where: { + id: request.params.note, + visibility: In(['public', 'home']), + }, + relations: ['user'], }); - if (note) { + if (note && !note.user!.requireSigninToViewContents) { const _note = await this.noteEntityService.pack(note); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId }); reply.header('Cache-Control', 'public, max-age=15'); diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index ccea7cd453b15464c38dac43ac547ec39c229b1e..cc07175907901bd142504b9015dfbd0d613d21e9 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -37,13 +37,13 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onBeforeUnmount, onMounted, ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { host } from '@@/js/config.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { pleaseLogin } from '@/scripts/please-login.js'; -import { host } from '@@/js/config.js'; import { $i } from '@/account.js'; import { defaultStore } from '@/store.js'; @@ -80,7 +80,7 @@ function onFollowChange(user: Misskey.entities.UserDetailed) { } async function onClick() { - pleaseLogin(undefined, { type: 'web', path: `/@${props.user.username}@${props.user.host ?? host}` }); + pleaseLogin({ openOnRemote: { type: 'web', path: `/@${props.user.username}@${props.user.host ?? host}` } }); wait.value = true; diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 828ad2e872b6ee839aba93c10c01b9ddf905afb7..425c4992da0009c0575ac2cb47869726d87e3d31 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -419,7 +419,7 @@ if (!props.mock) { } function renote(viaKeyboard = false) { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); const { menu } = getRenoteMenu({ note: note.value, renoteButton, mock: props.mock }); @@ -429,7 +429,7 @@ function renote(viaKeyboard = false) { } function reply(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); if (props.mock) { return; } @@ -442,7 +442,7 @@ function reply(): void { } function react(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (appearNote.value.reactionAcceptance === 'likeOnly') { sound.playMisskeySfx('reaction'); @@ -563,7 +563,7 @@ function showRenoteMenu(): void { } if (isMyRenote) { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); os.popupMenu([ getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote), { type: 'divider' }, diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 6d53685651b7e973f8225a10617075c38d25f12d..e0473dce5e13ac7eef4bd9328bb9487d015d5cd8 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -207,6 +207,7 @@ import { computed, inject, onMounted, provide, ref, shallowRef } from 'vue'; import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; import { isLink } from '@@/js/is-link.js'; +import { host } from '@@/js/config.js'; import MkNoteSub from '@/components/MkNoteSub.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; @@ -230,7 +231,6 @@ import { reactionPicker } from '@/scripts/reaction-picker.js'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; import { $i } from '@/account.js'; import { i18n } from '@/i18n.js'; -import { host } from '@@/js/config.js'; import { getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/scripts/get-note-menu.js'; import { useNoteCapture } from '@/scripts/use-note-capture.js'; import { deepClone } from '@/scripts/clone.js'; @@ -404,7 +404,7 @@ if (appearNote.value.reactionAcceptance === 'likeOnly') { } function renote() { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); const { menu } = getRenoteMenu({ note: note.value, renoteButton }); @@ -412,7 +412,7 @@ function renote() { } function reply(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); os.post({ reply: appearNote.value, @@ -423,7 +423,7 @@ function reply(): void { } function react(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (appearNote.value.reactionAcceptance === 'likeOnly') { sound.playMisskeySfx('reaction'); @@ -499,7 +499,7 @@ async function clip(): Promise<void> { function showRenoteMenu(): void { if (!isMyRenote) return; - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); os.popupMenu([{ text: i18n.ts.unrenote, icon: 'ti ti-trash', diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 48913004e04c7ea440acab1f2f834ca2a54c2433..e70ac7ff1a00d787e774cdea1e6b6fbecdd2e534 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -29,14 +29,14 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { host } from '@@/js/config.js'; +import { useInterval } from '@@/js/use-interval.js'; import type { OpenOnRemoteOptions } from '@/scripts/please-login.js'; import { sum } from '@/scripts/array.js'; import { pleaseLogin } from '@/scripts/please-login.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { host } from '@@/js/config.js'; -import { useInterval } from '@@/js/use-interval.js'; const props = defineProps<{ noteId: string; @@ -85,7 +85,7 @@ if (props.poll.expiresAt) { const vote = async (id) => { if (props.readOnly || closed.value || isVoted.value) return; - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); const { canceled } = await os.confirm({ type: 'question', diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 4d41cf5bc0995a03a09b93312819796b0cda0a54..07d91a0644e9bfb92bc2ae03132aafcd75165854 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -688,14 +688,16 @@ export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> { } export function post(props: Record<string, any> = {}): Promise<void> { - pleaseLogin(undefined, (props.initialText || props.initialNote ? { - type: 'share', - params: { - text: props.initialText ?? props.initialNote.text, - visibility: props.initialVisibility ?? props.initialNote?.visibility, - localOnly: (props.initialLocalOnly || props.initialNote?.localOnly) ? '1' : '0', - }, - } : undefined)); + pleaseLogin({ + openOnRemote: (props.initialText || props.initialNote ? { + type: 'share', + params: { + text: props.initialText ?? props.initialNote.text, + visibility: props.initialVisibility ?? props.initialNote?.visibility, + localOnly: (props.initialLocalOnly || props.initialNote?.localOnly) ? '1' : '0', + }, + } : undefined), + }); showMovedDialog(); return new Promise(resolve => { diff --git a/packages/frontend/src/pages/not-found.vue b/packages/frontend/src/pages/not-found.vue index 93a792c42f8cd41c12cae6b54fcc964f97e077a2..6a2d01b6fa663e9b3fc0178c2dbd7bd019393494 100644 --- a/packages/frontend/src/pages/not-found.vue +++ b/packages/frontend/src/pages/not-found.vue @@ -24,7 +24,7 @@ const props = defineProps<{ }>(); if (props.showLoginPopup) { - pleaseLogin('/'); + pleaseLogin({ path: '/' }); } const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue index 448244204d043a340429a66a7cf31c0b5bf85e21..454ee3c6bcf3f241c372bd31cca525ee97cdd08f 100644 --- a/packages/frontend/src/pages/note.vue +++ b/packages/frontend/src/pages/note.vue @@ -61,6 +61,7 @@ import { i18n } from '@/i18n.js'; import { dateString } from '@/filters/date.js'; import MkClipPreview from '@/components/MkClipPreview.vue'; import { defaultStore } from '@/store.js'; +import { pleaseLogin } from '@/scripts/please-login.js'; const props = defineProps<{ noteId: string; @@ -128,6 +129,11 @@ function fetchNote() { }); } }).catch(err => { + if (err.id === '8e75455b-738c-471d-9f80-62693f33372e') { + pleaseLogin({ + message: i18n.ts.thisContentsAreMarkedAsSigninRequiredByAuthor, + }); + } error.value = err; }); } diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue index d418be624ef24026baba99ff18a08fb416c34a08..e277dfad71dd6c246b87bf81f04056014ff08881 100644 --- a/packages/frontend/src/pages/settings/privacy.vue +++ b/packages/frontend/src/pages/settings/privacy.vue @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #caption>{{ i18n.ts.noCrawleDescription }}</template> </MkSwitch> <MkSwitch v-model="preventAiLearning" @update:modelValue="save()"> - {{ i18n.ts.preventAiLearning }}<span class="_beta">{{ i18n.ts.beta }}</span> + {{ i18n.ts.preventAiLearning }} <template #caption>{{ i18n.ts.preventAiLearningDescription }}</template> </MkSwitch> <MkSwitch v-model="isExplorable" @update:modelValue="save()"> @@ -44,6 +44,21 @@ SPDX-License-Identifier: AGPL-3.0-only <template #caption>{{ i18n.ts.makeExplorableDescription }}</template> </MkSwitch> + <FormSection> + <template #label>{{ i18n.ts.lockdown }}</template> + + <div class="_gaps_m"> + <MkSwitch v-model="requireSigninToViewContents" @update:modelValue="save()"> + {{ i18n.ts._accountSettings.requireSigninToViewContents }}<span class="_beta">{{ i18n.ts.beta }}</span> + <template #caption> + <div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div> + <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div> + <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div> + </template> + </MkSwitch> + </div> + </FormSection> + <FormSection> <div class="_gaps_m"> <MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch> @@ -90,6 +105,7 @@ const autoAcceptFollowed = ref($i.autoAcceptFollowed); const noCrawle = ref($i.noCrawle); const preventAiLearning = ref($i.preventAiLearning); const isExplorable = ref($i.isExplorable); +const requireSigninToViewContents = ref($i.requireSigninToViewContents ?? false); const hideOnlineStatus = ref($i.hideOnlineStatus); const publicReactions = ref($i.publicReactions); const followingVisibility = ref($i.followingVisibility); @@ -107,6 +123,7 @@ function save() { noCrawle: !!noCrawle.value, preventAiLearning: !!preventAiLearning.value, isExplorable: !!isExplorable.value, + requireSigninToViewContents: !!requireSigninToViewContents.value, hideOnlineStatus: !!hideOnlineStatus.value, publicReactions: !!publicReactions.value, followingVisibility: followingVisibility.value, diff --git a/packages/frontend/src/scripts/please-login.ts b/packages/frontend/src/scripts/please-login.ts index 18f05bc7f42855bbbd87a6afab3b4f71870814a1..43dcf1193674ad514eaab082f7e6cd6c96ec16ec 100644 --- a/packages/frontend/src/scripts/please-login.ts +++ b/packages/frontend/src/scripts/please-login.ts @@ -44,17 +44,21 @@ export type OpenOnRemoteOptions = { params: Record<string, string>; }; -export function pleaseLogin(path?: string, openOnRemote?: OpenOnRemoteOptions) { +export function pleaseLogin(opts: { + path?: string; + message?: string; + openOnRemote?: OpenOnRemoteOptions; +} = {}) { if ($i) return; const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), { autoSet: true, - message: openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired, - openOnRemote, + message: opts.message ?? (opts.openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired), + openOnRemote: opts.openOnRemote, }, { cancelled: () => { - if (path) { - window.location.href = path; + if (opts.path) { + window.location.href = opts.path; } }, closed: () => dispose(), diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 698c08826a0677c5f5bb48dff436955e36b05188..8f84aa37ff80f2f69838b17c66978a7a41d9aecf 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -3736,6 +3736,7 @@ export type components = { }[]; isBot?: boolean; isCat?: boolean; + requireSigninToViewContents?: boolean; instance?: { name: string | null; softwareName: string | null; @@ -19844,6 +19845,7 @@ export type operations = { autoAcceptFollowed?: boolean; noCrawle?: boolean; preventAiLearning?: boolean; + requireSigninToViewContents?: boolean; isBot?: boolean; isCat?: boolean; injectFeaturedNote?: boolean;