Add "enable RSS" user privacy toggle

This commit is contained in:
Hazelnoot 2024-12-09 09:11:39 -05:00
parent 943c6414d8
commit fe37aa2ce8
15 changed files with 56 additions and 0 deletions

8
locales/index.d.ts vendored
View file

@ -10847,6 +10847,14 @@ export interface Locale extends ILocale {
* Stop note search from indexing your public notes. * Stop note search from indexing your public notes.
*/ */
"makeIndexableDescription": string; "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 * Require approval for new users
*/ */

View file

@ -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"`);
}
}

View file

@ -135,6 +135,7 @@ export class SignupService {
isRoot: isTheFirstUser, isRoot: isTheFirstUser,
approved: isTheFirstUser || (opts.approved ?? !this.meta.approvalRequiredForSignup), approved: isTheFirstUser || (opts.approved ?? !this.meta.approvalRequiredForSignup),
signupReason: reason, signupReason: reason,
enableRss: false,
})); }));
await transactionalEntityManager.save(new MiUserKeypair({ await transactionalEntityManager.save(new MiUserKeypair({

View file

@ -531,6 +531,7 @@ export class ApRendererService {
hideOnlineStatus: user.hideOnlineStatus, hideOnlineStatus: user.hideOnlineStatus,
noindex: user.noindex, noindex: user.noindex,
indexable: !user.noindex, indexable: !user.noindex,
enableRss: user.enableRss,
speakAsCat: user.speakAsCat, speakAsCat: user.speakAsCat,
attachment: attachment.length ? attachment : undefined, attachment: attachment.length ? attachment : undefined,
}; };

View file

@ -567,6 +567,7 @@ const extension_context_definition = {
hideOnlineStatus: 'sharkey:hideOnlineStatus', hideOnlineStatus: 'sharkey:hideOnlineStatus',
backgroundUrl: 'sharkey:backgroundUrl', backgroundUrl: 'sharkey:backgroundUrl',
listenbrainz: 'sharkey:listenbrainz', listenbrainz: 'sharkey:listenbrainz',
enableRss: 'sharkey:enableRss',
// vcard // vcard
vcard: 'http://www.w3.org/2006/vcard/ns#', vcard: 'http://www.w3.org/2006/vcard/ns#',
} satisfies Context; } satisfies Context;

View file

@ -385,6 +385,7 @@ export class ApPersonService implements OnModuleInit {
lastFetchedAt: new Date(), lastFetchedAt: new Date(),
name: truncate(person.name, nameLength), name: truncate(person.name, nameLength),
noindex: (person as any).noindex ?? false, noindex: (person as any).noindex ?? false,
enableRss: person.enableRss === true,
isLocked: person.manuallyApprovesFollowers, isLocked: person.manuallyApprovesFollowers,
movedToUri: person.movedTo, movedToUri: person.movedTo,
movedAt: person.movedTo ? new Date() : null, movedAt: person.movedTo ? new Date() : null,
@ -584,6 +585,7 @@ export class ApPersonService implements OnModuleInit {
isCat: (person as any).isCat === true, isCat: (person as any).isCat === true,
speakAsCat: (person as any).speakAsCat != null ? (person as any).speakAsCat === true : (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, noindex: (person as any).noindex ?? false,
enableRss: person.enableRss === true,
isLocked: person.manuallyApprovesFollowers, isLocked: person.manuallyApprovesFollowers,
movedToUri: person.movedTo ?? null, movedToUri: person.movedTo ?? null,
alsoKnownAs: person.alsoKnownAs ?? null, alsoKnownAs: person.alsoKnownAs ?? null,

View file

@ -217,6 +217,7 @@ export interface IActor extends IObject {
'vcard:Address'?: string; 'vcard:Address'?: string;
hideOnlineStatus?: boolean; hideOnlineStatus?: boolean;
noindex?: boolean; noindex?: boolean;
enableRss?: boolean;
listenbrainz?: string; listenbrainz?: string;
backgroundUrl?: string; backgroundUrl?: string;
} }

View file

@ -539,6 +539,7 @@ export class UserEntityService implements OnModuleInit {
isBot: user.isBot, isBot: user.isBot,
isCat: user.isCat, isCat: user.isCat,
noindex: user.noindex, noindex: user.noindex,
enableRss: user.enableRss,
isSilenced: user.isSilenced || this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote), isSilenced: user.isSilenced || this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote),
speakAsCat: user.speakAsCat ?? false, speakAsCat: user.speakAsCat ?? false,
approved: user.approved, approved: user.approved,

View file

@ -311,6 +311,17 @@ export class MiUser {
}) })
public signupReason: string | null; 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 = true;
constructor(data: Partial<MiUser>) { constructor(data: Partial<MiUser>) {
if (data == null) return; if (data == null) return;

View file

@ -130,6 +130,10 @@ export const packedUserLiteSchema = {
type: 'boolean', type: 'boolean',
nullable: false, optional: false, nullable: false, optional: false,
}, },
enableRss: {
type: 'boolean',
nullable: false, optional: false,
},
isBot: { isBot: {
type: 'boolean', type: 'boolean',
nullable: false, optional: true, nullable: false, optional: true,

View file

@ -187,6 +187,7 @@ export const paramDef = {
noCrawle: { type: 'boolean' }, noCrawle: { type: 'boolean' },
preventAiLearning: { type: 'boolean' }, preventAiLearning: { type: 'boolean' },
noindex: { type: 'boolean' }, noindex: { type: 'boolean' },
enableRss: { type: 'boolean' },
isBot: { type: 'boolean' }, isBot: { type: 'boolean' },
isCat: { type: 'boolean' }, isCat: { type: 'boolean' },
speakAsCat: { type: 'boolean' }, speakAsCat: { type: 'boolean' },
@ -337,6 +338,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus; if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus;
if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions; if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions;
if (typeof ps.noindex === 'boolean') updates.noindex = ps.noindex; 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.isBot === 'boolean') updates.isBot = ps.isBot;
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot; if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;

View file

@ -510,6 +510,7 @@ export class ClientServerService {
usernameLower: username.toLowerCase(), usernameLower: username.toLowerCase(),
host: host ?? IsNull(), host: host ?? IsNull(),
isSuspended: false, isSuspended: false,
enableRss: true,
}); });
return user && await this.feedService.packFeed(user); return user && await this.feedService.packFeed(user);

View file

@ -43,6 +43,10 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.makeExplorable }} {{ i18n.ts.makeExplorable }}
<template #caption>{{ i18n.ts.makeExplorableDescription }}</template> <template #caption>{{ i18n.ts.makeExplorableDescription }}</template>
</MkSwitch> </MkSwitch>
<MkSwitch v-model="enableRss" @update:modelValue="save()">
{{ i18n.ts.enableRss }}
<template #caption>{{ i18n.ts.enableRssDescription }}</template>
</MkSwitch>
<FormSection> <FormSection>
<div class="_gaps_m"> <div class="_gaps_m">
@ -89,6 +93,7 @@ const isLocked = ref($i.isLocked);
const autoAcceptFollowed = ref($i.autoAcceptFollowed); const autoAcceptFollowed = ref($i.autoAcceptFollowed);
const noCrawle = ref($i.noCrawle); const noCrawle = ref($i.noCrawle);
const noindex = ref($i.noindex); const noindex = ref($i.noindex);
const enableRss = ref($i.enableRss);
const isExplorable = ref($i.isExplorable); const isExplorable = ref($i.isExplorable);
const hideOnlineStatus = ref($i.hideOnlineStatus); const hideOnlineStatus = ref($i.hideOnlineStatus);
const publicReactions = ref($i.publicReactions); const publicReactions = ref($i.publicReactions);
@ -106,6 +111,7 @@ function save() {
autoAcceptFollowed: !!autoAcceptFollowed.value, autoAcceptFollowed: !!autoAcceptFollowed.value,
noCrawle: !!noCrawle.value, noCrawle: !!noCrawle.value,
noindex: !!noindex.value, noindex: !!noindex.value,
enableRss: !!enableRss.value,
isExplorable: !!isExplorable.value, isExplorable: !!isExplorable.value,
hideOnlineStatus: !!hideOnlineStatus.value, hideOnlineStatus: !!hideOnlineStatus.value,
publicReactions: !!publicReactions.value, publicReactions: !!publicReactions.value,

View file

@ -3913,6 +3913,7 @@ export type components = {
/** @default false */ /** @default false */
isSystem?: boolean; isSystem?: boolean;
noindex: boolean; noindex: boolean;
enableRss: boolean;
isBot?: boolean; isBot?: boolean;
isCat?: boolean; isCat?: boolean;
speakAsCat?: boolean; speakAsCat?: boolean;
@ -21320,6 +21321,7 @@ export type operations = {
noCrawle?: boolean; noCrawle?: boolean;
preventAiLearning?: boolean; preventAiLearning?: boolean;
noindex?: boolean; noindex?: boolean;
enableRss?: boolean;
isBot?: boolean; isBot?: boolean;
isCat?: boolean; isCat?: boolean;
speakAsCat?: boolean; speakAsCat?: boolean;

View file

@ -88,6 +88,8 @@ searchEngineCustomURIDescription: "The custom URI must be input in the format li
searchEngineCusomURI: "Custom URI" searchEngineCusomURI: "Custom URI"
makeIndexable: "Make public notes not indexable" makeIndexable: "Make public notes not indexable"
makeIndexableDescription: "Stop note search from indexing your public notes." 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." 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." noInquiryUrlWarning: "Contact URL is not set."
misskeyUpdated: "Sharkey has been updated!" misskeyUpdated: "Sharkey has been updated!"