summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordakkar <dakkar@thenautilus.net>2024-12-12 17:38:29 +0000
committerdakkar <dakkar@thenautilus.net>2024-12-12 17:38:29 +0000
commitb266a5f9f65332dd147d60e863ec7fc809f79920 (patch)
treecbbe19f77214d5ddf9cac1a2e642216480d329ab
parentupdate css variable references (diff)
parentmerge: Add "enable RSS" user privacy toggle (resolves #826) (!806) (diff)
downloadsharkey-b266a5f9f65332dd147d60e863ec7fc809f79920.tar.gz
sharkey-b266a5f9f65332dd147d60e863ec7fc809f79920.tar.bz2
sharkey-b266a5f9f65332dd147d60e863ec7fc809f79920.zip
Merge branch 'develop' into feature/2024.10
-rw-r--r--locales/index.d.ts8
-rw-r--r--packages/backend/migration/1733748798177-add_user_enableRss.js13
-rw-r--r--packages/backend/migration/1733754069260-alter_user_hideOnlineStatus_default_true.js11
-rw-r--r--packages/backend/src/core/SignupService.ts1
-rw-r--r--packages/backend/src/core/WebhookTestService.ts2
-rw-r--r--packages/backend/src/core/activitypub/ApRendererService.ts2
-rw-r--r--packages/backend/src/core/activitypub/misc/contexts.ts2
-rw-r--r--packages/backend/src/core/activitypub/models/ApPersonService.ts6
-rw-r--r--packages/backend/src/core/activitypub/type.ts2
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts1
-rw-r--r--packages/backend/src/models/User.ts15
-rw-r--r--packages/backend/src/models/json-schema/user.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/i/update.ts20
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts1
-rw-r--r--packages/frontend/src/components/MkFlashPreview.vue3
-rw-r--r--packages/frontend/src/components/MkPagePreview.vue3
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.Privacy.vue2
-rw-r--r--packages/frontend/src/pages/settings/privacy.vue6
-rw-r--r--packages/misskey-js/src/autogen/types.ts2
-rw-r--r--sharkey-locales/en-US.yml2
20 files changed, 93 insertions, 13 deletions
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 8775a1ee8d..5b0ae8b904 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -10978,6 +10978,14 @@ export interface Locale extends ILocale {
*/
"makeIndexableDescription": string;
/**
+ * Enable RSS feed
+ */
+ "enableRss": string;
+ /**
+ * Generate an RSS feed containing your basic profile details and public notes. Users can subscribe to the feed without a follow request or approval.
+ */
+ "enableRssDescription": string;
+ /**
* Require approval for new users
*/
"approvalRequiredForSignup": string;
diff --git a/packages/backend/migration/1733748798177-add_user_enableRss.js b/packages/backend/migration/1733748798177-add_user_enableRss.js
new file mode 100644
index 0000000000..64662ca7b8
--- /dev/null
+++ b/packages/backend/migration/1733748798177-add_user_enableRss.js
@@ -0,0 +1,13 @@
+export class AddUserEnableRss1733748798177 {
+ name = 'AddUserEnableRss1733748798177'
+
+ async up(queryRunner) {
+ // Disable by default, then specifically enable for all existing local users.
+ await queryRunner.query(`ALTER TABLE "user" ADD "enable_rss" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`UPDATE "user" SET "enable_rss" = true WHERE host IS NULL;`)
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "enable_rss"`);
+ }
+}
diff --git a/packages/backend/migration/1733754069260-alter_user_hideOnlineStatus_default_true.js b/packages/backend/migration/1733754069260-alter_user_hideOnlineStatus_default_true.js
new file mode 100644
index 0000000000..c0db48ceea
--- /dev/null
+++ b/packages/backend/migration/1733754069260-alter_user_hideOnlineStatus_default_true.js
@@ -0,0 +1,11 @@
+export class AlterUserHideOnlineStatusDefaultTrue1733754069260 {
+ name = 'AlterUserHideOnlineStatusDefaultTrue1733754069260'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "hideOnlineStatus" SET DEFAULT true`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "hideOnlineStatus" SET DEFAULT false`);
+ }
+}
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index a1e23a49c1..0ad448e95f 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -135,6 +135,7 @@ export class SignupService {
isRoot: isTheFirstUser,
approved: isTheFirstUser || (opts.approved ?? !this.meta.approvalRequiredForSignup),
signupReason: reason,
+ enableRss: false,
}));
await transactionalEntityManager.save(new MiUserKeypair({
diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts
index 1530d91532..dfe7a259c4 100644
--- a/packages/backend/src/core/WebhookTestService.ts
+++ b/packages/backend/src/core/WebhookTestService.ts
@@ -98,6 +98,7 @@ function generateDummyUser(override?: Partial<MiUser>): MiUser {
approved: true,
signupReason: null,
noindex: false,
+ enableRss: true,
...override,
};
}
@@ -214,6 +215,7 @@ function toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Packed<'
isAdmin: false,
isSystem: false,
isSilenced: user.isSilenced,
+ enableRss: true,
...override,
};
}
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 11c88c63a4..fb706a775f 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -531,8 +531,10 @@ export class ApRendererService {
discoverable: user.isExplorable,
publicKey: this.renderKey(user, keypair, '#main-key'),
isCat: user.isCat,
+ hideOnlineStatus: user.hideOnlineStatus,
noindex: user.noindex,
indexable: !user.noindex,
+ enableRss: user.enableRss,
speakAsCat: user.speakAsCat,
attachment: attachment.length ? attachment : undefined,
};
diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts
index 45ca495145..d7b6fc6589 100644
--- a/packages/backend/src/core/activitypub/misc/contexts.ts
+++ b/packages/backend/src/core/activitypub/misc/contexts.ts
@@ -567,8 +567,10 @@ const extension_context_definition = {
speakAsCat: 'firefish:speakAsCat',
// Sharkey
sharkey: 'https://joinsharkey.org/ns#',
+ hideOnlineStatus: 'sharkey:hideOnlineStatus',
backgroundUrl: 'sharkey:backgroundUrl',
listenbrainz: 'sharkey:listenbrainz',
+ enableRss: 'sharkey:enableRss',
// vcard
vcard: 'http://www.w3.org/2006/vcard/ns#',
} satisfies Context;
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index d32811c7e5..5c71dbc626 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -391,10 +391,13 @@ export class ApPersonService implements OnModuleInit {
lastFetchedAt: new Date(),
name: truncate(person.name, nameLength),
noindex: (person as any).noindex ?? false,
+ enableRss: person.enableRss === true,
isLocked: person.manuallyApprovesFollowers,
movedToUri: person.movedTo,
movedAt: person.movedTo ? new Date() : null,
alsoKnownAs: person.alsoKnownAs,
+ // We use "!== false" to handle incorrect types, missing / null values, and "default to true" logic.
+ hideOnlineStatus: person.hideOnlineStatus !== false,
isExplorable: person.discoverable,
username: person.preferredUsername,
approved: true,
@@ -593,9 +596,12 @@ export class ApPersonService implements OnModuleInit {
isCat: (person as any).isCat === true,
speakAsCat: (person as any).speakAsCat != null ? (person as any).speakAsCat === true : (person as any).isCat === true,
noindex: (person as any).noindex ?? false,
+ enableRss: person.enableRss === true,
isLocked: person.manuallyApprovesFollowers,
movedToUri: person.movedTo ?? null,
alsoKnownAs: person.alsoKnownAs ?? null,
+ // We use "!== false" to handle incorrect types, missing / null values, and "default to true" logic.
+ hideOnlineStatus: person.hideOnlineStatus !== false,
isExplorable: person.discoverable,
...(await this.resolveAvatarAndBanner(exist, person.icon, person.image, person.backgroundUrl).catch(() => ({}))),
} as Partial<MiRemoteUser> & Pick<MiRemoteUser, 'isBot' | 'isCat' | 'speakAsCat' | 'isLocked' | 'movedToUri' | 'alsoKnownAs' | 'isExplorable'>;
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index e61d89f9db..d67f8cf62e 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -218,7 +218,9 @@ export interface IActor extends IObject {
};
'vcard:bday'?: string;
'vcard:Address'?: string;
+ hideOnlineStatus?: boolean;
noindex?: boolean;
+ enableRss?: boolean;
listenbrainz?: string;
backgroundUrl?: string;
}
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 4f6b412609..6bfe865038 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -539,6 +539,7 @@ export class UserEntityService implements OnModuleInit {
isBot: user.isBot,
isCat: user.isCat,
noindex: user.noindex,
+ enableRss: user.enableRss,
isSilenced: user.isSilenced || this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote),
speakAsCat: user.speakAsCat ?? false,
approved: user.approved,
diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts
index a4481a1f81..3a825d36a7 100644
--- a/packages/backend/src/models/User.ts
+++ b/packages/backend/src/models/User.ts
@@ -32,7 +32,7 @@ export class MiUser {
public lastActiveDate: Date | null;
@Column('boolean', {
- default: false,
+ default: true,
})
public hideOnlineStatus: boolean;
@@ -160,7 +160,7 @@ export class MiUser {
length: 128, nullable: true,
})
public backgroundBlurhash: string | null;
-
+
@Column('jsonb', {
default: [],
})
@@ -328,6 +328,17 @@ export class MiUser {
})
public signupReason: string | null;
+ /**
+ * True if profile RSS feeds are enabled for this user.
+ * Enabled by default (opt-out) for existing users, to avoid breaking any existing feeds.
+ * Disabled by default (opt-in) for newly created users, for privacy.
+ */
+ @Column('boolean', {
+ name: 'enable_rss',
+ default: true,
+ })
+ public enableRss: boolean;
+
constructor(data: Partial<MiUser>) {
if (data == null) return;
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index 46aa06c392..f953008b3f 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -130,6 +130,10 @@ export const packedUserLiteSchema = {
type: 'boolean',
nullable: false, optional: false,
},
+ enableRss: {
+ type: 'boolean',
+ nullable: false, optional: false,
+ },
isBot: {
type: 'boolean',
nullable: false, optional: true,
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index 832bf1418d..fe28b50338 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -198,6 +198,7 @@ export const paramDef = {
requireSigninToViewContents: { type: 'boolean' },
makeNotesFollowersOnlyBefore: { type: 'integer', nullable: true },
makeNotesHiddenBefore: { type: 'integer', nullable: true },
+ enableRss: { type: 'boolean' },
isBot: { type: 'boolean' },
isCat: { type: 'boolean' },
speakAsCat: { type: 'boolean' },
@@ -352,6 +353,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus;
if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions;
if (typeof ps.noindex === 'boolean') updates.noindex = ps.noindex;
+ if (typeof ps.enableRss === 'boolean') updates.enableRss = ps.enableRss;
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;
@@ -619,12 +621,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// these two methods need to be kept in sync with
// `ApRendererService.renderPerson`
private userNeedsPublishing(oldUser: MiLocalUser, newUser: Partial<MiUser>): boolean {
- for (const field of ['avatarId', 'bannerId', 'backgroundId', 'isBot', 'username', 'name', 'isLocked', 'isExplorable', 'isCat', 'noindex', 'speakAsCat', 'movedToUri', 'alsoKnownAs'] as (keyof MiUser)[]) {
+ const basicFields: (keyof MiUser)[] = ['avatarId', 'bannerId', 'backgroundId', 'isBot', 'username', 'name', 'isLocked', 'isExplorable', 'isCat', 'noindex', 'speakAsCat', 'movedToUri', 'alsoKnownAs', 'hideOnlineStatus', 'enableRss'];
+ for (const field of basicFields) {
if ((field in newUser) && oldUser[field] !== newUser[field]) {
return true;
}
}
- for (const arrayField of ['emojis', 'tags'] as (keyof MiUser)[]) {
+
+ const arrayFields: (keyof MiUser)[] = ['emojis', 'tags'];
+ for (const arrayField of arrayFields) {
if ((arrayField in newUser) !== (arrayField in oldUser)) {
return true;
}
@@ -634,7 +639,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (!Array.isArray(oldArray) || !Array.isArray(newArray)) {
return true;
}
- if (oldArray.join("\0") !== newArray.join("\0")) {
+ if (oldArray.join('\0') !== newArray.join('\0')) {
return true;
}
}
@@ -642,12 +647,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
private profileNeedsPublishing(oldProfile: MiUserProfile, newProfile: Partial<MiUserProfile>): boolean {
- for (const field of ['description', 'followedMessage', 'birthday', 'location', 'listenbrainz'] as (keyof MiUserProfile)[]) {
+ const basicFields: (keyof MiUserProfile)[] = ['description', 'followedMessage', 'birthday', 'location', 'listenbrainz'];
+ for (const field of basicFields) {
if ((field in newProfile) && oldProfile[field] !== newProfile[field]) {
return true;
}
}
- for (const arrayField of ['fields'] as (keyof MiUserProfile)[]) {
+
+ const arrayFields: (keyof MiUserProfile)[] = ['fields'];
+ for (const arrayField of arrayFields) {
if ((arrayField in newProfile) !== (arrayField in oldProfile)) {
return true;
}
@@ -657,7 +665,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (!Array.isArray(oldArray) || !Array.isArray(newArray)) {
return true;
}
- if (oldArray.join("\0") !== newArray.join("\0")) {
+ if (oldArray.join('\0') !== newArray.join('\0')) {
return true;
}
}
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 2997121cfd..e59314bf55 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -530,6 +530,7 @@ export class ClientServerService {
usernameLower: username.toLowerCase(),
host: host ?? IsNull(),
isSuspended: false,
+ enableRss: true,
});
return user && await this.feedService.packFeed(user);
diff --git a/packages/frontend/src/components/MkFlashPreview.vue b/packages/frontend/src/components/MkFlashPreview.vue
index b7278ac742..0bb1450f87 100644
--- a/packages/frontend/src/components/MkFlashPreview.vue
+++ b/packages/frontend/src/components/MkFlashPreview.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</p>
<footer>
<img class="icon" :src="flash.user.avatarUrl"/>
- <p>{{ userName(flash.user) }}</p>
+ <MkUserName :key="flash.user.id" :user="flash.user"/>
</footer>
</article>
</MkA>
@@ -23,7 +23,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { } from 'vue';
import * as Misskey from 'misskey-js';
-import { userName } from '@/filters/user.js';
const props = defineProps<{
flash: Misskey.entities.Flash;
diff --git a/packages/frontend/src/components/MkPagePreview.vue b/packages/frontend/src/components/MkPagePreview.vue
index 35a37a1f7d..29fdc91a2b 100644
--- a/packages/frontend/src/components/MkPagePreview.vue
+++ b/packages/frontend/src/components/MkPagePreview.vue
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<p v-if="page.summary" :title="page.summary">{{ page.summary.length > 85 ? page.summary.slice(0, 85) + '…' : page.summary }}</p>
<footer>
<img v-if="page.user.avatarUrl" class="icon" :src="page.user.avatarUrl"/>
- <p>{{ userName(page.user) }}</p>
+ <MkUserName :key="page.user.id" :user="page.user"/>
</footer>
</article>
</MkA>
@@ -30,7 +30,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { } from 'vue';
import * as Misskey from 'misskey-js';
-import { userName } from '@/filters/user.js';
import MediaImage from '@/components/MkMediaImage.vue';
const props = defineProps<{
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
index bc998d6158..fb4a2b1c78 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
@@ -44,7 +44,7 @@ import MkFolder from '@/components/MkFolder.vue';
import { misskeyApi } from '@/scripts/misskey-api.js';
const isLocked = ref(false);
-const hideOnlineStatus = ref(false);
+const hideOnlineStatus = ref(true);
const noCrawle = ref(false);
watch([isLocked, hideOnlineStatus, noCrawle], () => {
diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue
index 78df0faf4d..790f9e44e2 100644
--- a/packages/frontend/src/pages/settings/privacy.vue
+++ b/packages/frontend/src/pages/settings/privacy.vue
@@ -43,6 +43,10 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.makeExplorable }}
<template #caption>{{ i18n.ts.makeExplorableDescription }}</template>
</MkSwitch>
+ <MkSwitch v-model="enableRss" @update:modelValue="save()">
+ {{ i18n.ts.enableRss }}
+ <template #caption>{{ i18n.ts.enableRssDescription }}</template>
+ </MkSwitch>
<FormSection>
<template #label>{{ i18n.ts.lockdown }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
@@ -180,6 +184,7 @@ const isLocked = ref($i.isLocked);
const autoAcceptFollowed = ref($i.autoAcceptFollowed);
const noCrawle = ref($i.noCrawle);
const noindex = ref($i.noindex);
+const enableRss = ref($i.enableRss);
const isExplorable = ref($i.isExplorable);
const requireSigninToViewContents = ref($i.requireSigninToViewContents ?? false);
const makeNotesFollowersOnlyBefore = ref($i.makeNotesFollowersOnlyBefore ?? null);
@@ -237,6 +242,7 @@ function save() {
autoAcceptFollowed: !!autoAcceptFollowed.value,
noCrawle: !!noCrawle.value,
noindex: !!noindex.value,
+ enableRss: !!enableRss.value,
isExplorable: !!isExplorable.value,
requireSigninToViewContents: !!requireSigninToViewContents.value,
makeNotesFollowersOnlyBefore: makeNotesFollowersOnlyBefore.value,
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 22e4a4a8c1..888e46e008 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -3931,6 +3931,7 @@ export type components = {
/** @default false */
isSystem?: boolean;
noindex: boolean;
+ enableRss: boolean;
isBot?: boolean;
isCat?: boolean;
speakAsCat?: boolean;
@@ -21482,6 +21483,7 @@ export type operations = {
requireSigninToViewContents?: boolean;
makeNotesFollowersOnlyBefore?: number | null;
makeNotesHiddenBefore?: number | null;
+ enableRss?: boolean;
isBot?: boolean;
isCat?: boolean;
speakAsCat?: boolean;
diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml
index f15646a156..4e462edda7 100644
--- a/sharkey-locales/en-US.yml
+++ b/sharkey-locales/en-US.yml
@@ -88,6 +88,8 @@ searchEngineCustomURIDescription: "The custom URI must be input in the format li
searchEngineCusomURI: "Custom URI"
makeIndexable: "Make public notes not indexable"
makeIndexableDescription: "Stop note search from indexing your public notes."
+enableRss: "Enable RSS feed"
+enableRssDescription: "Generate an RSS feed containing your basic profile details and public notes. Users can subscribe to the feed without a follow request or approval."
sendErrorReportsDescription: "When turned on, detailed error information will be shared with Sharkey when a problem occurs, helping to improve the quality of Sharkey.\nThis will include information such the version of your OS, what browser you're using, your activity in Sharkey, etc."
noInquiryUrlWarning: "Contact URL is not set."
misskeyUpdated: "Sharkey has been updated!"