Merge branch 'develop' into feature/2024.10

This commit is contained in:
dakkar 2024-12-12 17:38:29 +00:00
commit b266a5f9f6
20 changed files with 93 additions and 13 deletions

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

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

View file

@ -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({

View file

@ -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,
};
}

View file

@ -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,
};

View file

@ -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;

View file

@ -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'>;

View file

@ -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;
}

View file

@ -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,

View file

@ -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;

View file

@ -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,

View file

@ -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;
}
}

View file

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