diff --git a/CHANGELOG.md b/CHANGELOG.md index bb6f1f9865b5..0b204c476651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - 2023.9.2で導入されたノート編集機能はクオリティの高い実装が困難であることが判明したため撤回されました ### General +- Feat: プライバシーポリシー・運営者情報(Impressum)の指定が可能になりました + - プライバシーポリシーはサーバー登録時に同意確認が入ります - Enhance: モデレーションログ機能の強化 - Enhance: 依存関係の更新 - Fix: ダイレクト投稿をリノートできてしまう diff --git a/locales/index.d.ts b/locales/index.d.ts index f5ccc327a2d3..194d4f4ccd8e 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1131,6 +1131,15 @@ export interface Locale { "notificationRecieveConfig": string; "mutualFollow": string; "fileAttachedOnly": string; + "showRepliesToOthersInTimeline": string; + "hideRepliesToOthersInTimeline": string; + "externalServices": string; + "impressum": string; + "impressumUrl": string; + "impressumDescription": string; + "privacyPolicy": string; + "privacyPolicyUrl": string; + "tosAndPrivacyPolicy": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index e244ae013630..677e6b82d633 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1128,6 +1128,15 @@ edited: "編集済み" notificationRecieveConfig: "通知の受信設定" mutualFollow: "相互フォロー" fileAttachedOnly: "ファイル付きのみ" +showRepliesToOthersInTimeline: "TLに他の人への返信を含める" +hideRepliesToOthersInTimeline: "TLに他の人への返信を含めない" +externalServices: "外部サービス" +impressum: "運営者情報" +impressumUrl: "運営者情報URL" +impressumDescription: "ドイツなどの一部の国と地域では表示が義務付けられています(Impressum)。" +privacyPolicy: "プライバシーポリシー" +privacyPolicyUrl: "プライバシーポリシーURL" +tosAndPrivacyPolicy: "利用規約・プライバシーポリシー" _announcement: forExistingUsers: "既存ユーザーのみ" diff --git a/packages/backend/migration/1696003580220-AddSomeUrls.js b/packages/backend/migration/1696003580220-AddSomeUrls.js new file mode 100644 index 000000000000..683aa5eeed57 --- /dev/null +++ b/packages/backend/migration/1696003580220-AddSomeUrls.js @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AddSomeUrls1696003580220 { + name = 'AddSomeUrls1696003580220' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "impressumUrl" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "privacyPolicyUrl" character varying(1024)`); + } + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "impressumUrl"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "privacyPolicyUrl"`); + } +} diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index e69bef8e9845..9ae2d1c28e62 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -335,6 +335,18 @@ export class MiMeta { }) public feedbackUrl: string | null; + @Column('varchar', { + length: 1024, + nullable: true, + }) + public impressumUrl: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public privacyPolicyUrl: string | null; + @Column('varchar', { length: 8192, nullable: true, diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts index 79f130dabe4a..dd2b7882a221 100644 --- a/packages/backend/src/server/NodeinfoServerService.ts +++ b/packages/backend/src/server/NodeinfoServerService.ts @@ -102,6 +102,8 @@ export class NodeinfoServerService { }, langs: meta.langs, tosUrl: meta.termsOfServiceUrl, + privacyPolicyUrl: meta.privacyPolicyUrl, + impressumUrl: meta.impressumUrl, repositoryUrl: meta.repositoryUrl, feedbackUrl: meta.feedbackUrl, disableRegistration: meta.disableRegistration, diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index c3ba07cdd086..238197bee4f4 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -328,6 +328,8 @@ export default class extends Endpoint { // eslint- tosUrl: instance.termsOfServiceUrl, repositoryUrl: instance.repositoryUrl, feedbackUrl: instance.feedbackUrl, + impressumUrl: instance.impressumUrl, + privacyPolicyUrl: instance.privacyPolicyUrl, disableRegistration: instance.disableRegistration, emailRequiredForSignup: instance.emailRequiredForSignup, enableHcaptcha: instance.enableHcaptcha, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index ea6ebdd1fe1a..b14eb62566d2 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -86,6 +86,8 @@ export const paramDef = { tosUrl: { type: 'string', nullable: true }, repositoryUrl: { type: 'string' }, feedbackUrl: { type: 'string' }, + impressumUrl: { type: 'string' }, + privacyPolicyUrl: { type: 'string' }, useObjectStorage: { type: 'boolean' }, objectStorageBaseUrl: { type: 'string', nullable: true }, objectStorageBucket: { type: 'string', nullable: true }, @@ -341,6 +343,14 @@ export default class extends Endpoint { // eslint- set.feedbackUrl = ps.feedbackUrl; } + if (ps.impressumUrl !== undefined) { + set.impressumUrl = ps.impressumUrl; + } + + if (ps.privacyPolicyUrl !== undefined) { + set.privacyPolicyUrl = ps.privacyPolicyUrl; + } + if (ps.useObjectStorage !== undefined) { set.useObjectStorage = ps.useObjectStorage; } diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index fa6486ed1834..ee8002bfba6f 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -299,6 +299,8 @@ export default class extends Endpoint { // eslint- tosUrl: instance.termsOfServiceUrl, repositoryUrl: instance.repositoryUrl, feedbackUrl: instance.feedbackUrl, + impressumUrl: instance.impressumUrl, + privacyPolicyUrl: instance.privacyPolicyUrl, disableRegistration: instance.disableRegistration, emailRequiredForSignup: instance.emailRequiredForSignup, enableHcaptcha: instance.enableHcaptcha, diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue index aa4a184d7b88..76163ab68b05 100644 --- a/packages/frontend/src/components/MkSignupDialog.rules.vue +++ b/packages/frontend/src/components/MkSignupDialog.rules.vue @@ -30,13 +30,15 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.agree }} - - - - - {{ i18n.ts.termsOfService }} - - {{ i18n.ts.agree }} + + + + + + {{ i18n.ts.agree }} @@ -70,14 +72,15 @@ import MkInfo from '@/components/MkInfo.vue'; import * as os from '@/os.js'; const availableServerRules = instance.serverRules.length > 0; -const availableTos = instance.tosUrl != null; +const availableTos = instance.tosUrl != null && instance.tosUrl !== ''; +const availablePrivacyPolicy = instance.privacyPolicyUrl != null && instance.privacyPolicyUrl !== ''; const agreeServerRules = ref(false); -const agreeTos = ref(false); +const agreeTosAndPrivacyPolicy = ref(false); const agreeNote = ref(false); const agreed = computed(() => { - return (!availableServerRules || agreeServerRules.value) && (!availableTos || agreeTos.value) && agreeNote.value; + return (!availableServerRules || agreeServerRules.value) && ((!availableTos && !availablePrivacyPolicy) || agreeTosAndPrivacyPolicy.value) && agreeNote.value; }); const emit = defineEmits<{ @@ -85,6 +88,18 @@ const emit = defineEmits<{ (ev: 'done'): void; }>(); +const tosPrivacyPolicyLabel = computed(() => { + if (availableTos && availablePrivacyPolicy) { + return i18n.ts.tosAndPrivacyPolicy; + } else if (availableTos) { + return i18n.ts.termsOfService; + } else if (availablePrivacyPolicy) { + return i18n.ts.privacyPolicy; + } else { + return ""; + } +}); + async function updateAgreeServerRules(v: boolean) { if (v) { const confirm = await os.confirm({ @@ -99,17 +114,19 @@ async function updateAgreeServerRules(v: boolean) { } } -async function updateAgreeTos(v: boolean) { +async function updateAgreeTosAndPrivacyPolicy(v: boolean) { if (v) { const confirm = await os.confirm({ type: 'question', title: i18n.ts.doYouAgree, - text: i18n.t('iHaveReadXCarefullyAndAgree', { x: i18n.ts.termsOfService }), + text: i18n.t('iHaveReadXCarefullyAndAgree', { + x: tosPrivacyPolicyLabel.value, + }), }); if (confirm.canceled) return; - agreeTos.value = true; + agreeTosAndPrivacyPolicy.value = true; } else { - agreeTos.value = false; + agreeTosAndPrivacyPolicy.value = false; } } diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue index e4520bbb2d4a..40493a5d067a 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.vue @@ -104,7 +104,25 @@ function showMenu(ev) { action: () => { os.pageWindow('/about-misskey'); }, - }, null, { + }, null, (instance.impressumUrl) ? { + text: i18n.ts.impressum, + icon: 'ti ti-file-invoice', + action: () => { + window.open(instance.impressumUrl, '_blank'); + }, + } : undefined, (instance.tosUrl) ? { + text: i18n.ts.termsOfService, + icon: 'ti ti-notebook', + action: () => { + window.open(instance.tosUrl, '_blank'); + }, + } : undefined, (instance.privacyPolicyUrl) ? { + text: i18n.ts.privacyPolicy, + icon: 'ti ti-shield-lock', + action: () => { + window.open(instance.privacyPolicyUrl, '_blank'); + }, + } : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : null, { text: i18n.ts.help, icon: 'ti ti-help-circle', action: () => { diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue index 02768b077406..ee4043f9a500 100644 --- a/packages/frontend/src/pages/about.vue +++ b/packages/frontend/src/pages/about.vue @@ -46,14 +46,18 @@ SPDX-License-Identifier: AGPL-3.0-only - - - -
    -
  1. -
-
- {{ i18n.ts.termsOfService }} + {{ i18n.ts.impressum }} + diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue index 46f92729e816..8b160635f71f 100644 --- a/packages/frontend/src/pages/admin/moderation.vue +++ b/packages/frontend/src/pages/admin/moderation.vue @@ -25,6 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + @@ -69,6 +74,7 @@ let emailRequiredForSignup: boolean = $ref(false); let sensitiveWords: string = $ref(''); let preservedUsernames: string = $ref(''); let tosUrl: string | null = $ref(null); +let privacyPolicyUrl: string | null = $ref(null); async function init() { const meta = await os.api('admin/meta'); @@ -77,6 +83,7 @@ async function init() { sensitiveWords = meta.sensitiveWords.join('\n'); preservedUsernames = meta.preservedUsernames.join('\n'); tosUrl = meta.tosUrl; + privacyPolicyUrl = meta.privacyPolicyUrl; } function save() { @@ -84,6 +91,7 @@ function save() { disableRegistration: !enableRegistration, emailRequiredForSignup, tosUrl, + privacyPolicyUrl, sensitiveWords: sensitiveWords.split('\n'), preservedUsernames: preservedUsernames.split('\n'), }).then(() => { diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index f93678d72802..559a2e8ba218 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -34,6 +34,12 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + @@ -127,6 +133,7 @@ let shortName: string | null = $ref(null); let description: string | null = $ref(null); let maintainerName: string | null = $ref(null); let maintainerEmail: string | null = $ref(null); +let impressumUrl: string | null = $ref(null); let pinnedUsers: string = $ref(''); let cacheRemoteFiles: boolean = $ref(false); let cacheRemoteSensitiveFiles: boolean = $ref(false); @@ -143,6 +150,7 @@ async function init(): Promise { description = meta.description; maintainerName = meta.maintainerName; maintainerEmail = meta.maintainerEmail; + impressumUrl = meta.impressumUrl; pinnedUsers = meta.pinnedUsers.join('\n'); cacheRemoteFiles = meta.cacheRemoteFiles; cacheRemoteSensitiveFiles = meta.cacheRemoteSensitiveFiles; @@ -160,6 +168,7 @@ function save(): void { description, maintainerName, maintainerEmail, + impressumUrl, pinnedUsers: pinnedUsers.split('\n'), cacheRemoteFiles, cacheRemoteSensitiveFiles, diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index ca4a71a67f0f..e075e05db367 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -68,7 +68,25 @@ export function openInstanceMenu(ev: MouseEvent) { text: i18n.ts.manageCustomEmojis, icon: 'ti ti-icons', } : undefined], - }, null, { + }, null, (instance.impressumUrl) ? { + text: i18n.ts.impressum, + icon: 'ti ti-file-invoice', + action: () => { + window.open(instance.impressumUrl, '_blank'); + }, + } : undefined, (instance.tosUrl) ? { + text: i18n.ts.termsOfService, + icon: 'ti ti-notebook', + action: () => { + window.open(instance.tosUrl, '_blank'); + }, + } : undefined, (instance.privacyPolicyUrl) ? { + text: i18n.ts.privacyPolicy, + icon: 'ti ti-shield-lock', + action: () => { + window.open(instance.privacyPolicyUrl, '_blank'); + }, + } : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : null, { text: i18n.ts.help, icon: 'ti ti-help-circle', action: () => { diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 4f53d0a7dddf..f8caafa8a256 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2412,6 +2412,8 @@ type LiteInstanceMetadata = { tosUrl: string | null; repositoryUrl: string; feedbackUrl: string; + impressumUrl: string | null; + privacyPolicyUrl: string | null; disableRegistration: boolean; disableLocalTimeline: boolean; disableGlobalTimeline: boolean; @@ -2982,7 +2984,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u // src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts // src/api.types.ts:631:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts // src/entities.ts:107:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts -// src/entities.ts:594:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts +// src/entities.ts:596:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 8097618c3332..b60056e21a20 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -322,6 +322,8 @@ export type LiteInstanceMetadata = { tosUrl: string | null; repositoryUrl: string; feedbackUrl: string; + impressumUrl: string | null; + privacyPolicyUrl: string | null; disableRegistration: boolean; disableLocalTimeline: boolean; disableGlobalTimeline: boolean;