merge: Fix regressions from recent block/mute fixes (!1239)

View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1239

Approved-by: dakkar <dakkar@thenautilus.net>
Approved-by: Marie <github@yuugi.dev>
This commit is contained in:
Hazelnoot 2025-11-15 13:38:52 -05:00
commit 911f90f95a
17 changed files with 127 additions and 59 deletions

View file

@ -116,7 +116,10 @@ export class FanoutTimelineEndpointService {
const parentFilter = filter;
filter = (note, populated) => {
const { accessible, silence } = this.noteVisibilityService.checkNoteVisibility(populated, me, { data, filters: { includeSilencedAuthor: ps.ignoreAuthorFromUserSilence } });
const { accessible, silence } = this.noteVisibilityService.checkNoteVisibility(populated, me, { data, filters: {
includeSilencedAuthor: ps.ignoreAuthorFromUserSilence,
includeReplies: true, // Include replies because we check them elsewhere
} });
if (!accessible || silence) return false;
return parentFilter(note, populated);

View file

@ -3,8 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { MiUser } from '@/models/User.js';
import type { MiNote } from '@/models/Note.js';
import type { MiUser } from '@/models/User.js';
export function isReply(note: any, viewerId?: MiUser['id'] | undefined | null): boolean {
return note.replyId && note.replyUserId !== note.userId && note.replyUserId !== viewerId;
// Should really be named "isReplyToOther"
export function isReply(note: MiNote, viewerId?: MiUser['id'] | undefined | null): boolean {
return note.replyId != null && note.replyUserId !== note.userId && note.replyUserId !== viewerId;
}

View file

@ -3,9 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import webpush from 'web-push';
const { generateVAPIDKeys } = webpush;
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
@ -15,6 +14,21 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'write:admin:meta',
res: {
type: 'object',
optional: false, nullable: false,
properties: {
public: {
type: 'string',
optional: false, nullable: false,
},
private: {
type: 'string',
optional: false, nullable: false,
},
},
},
} as const;
export const paramDef = {
@ -28,8 +42,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const keys = await generateVAPIDKeys();
super(meta, paramDef, async () => {
const keys = webpush.generateVAPIDKeys();
// TODO add moderation log

View file

@ -489,6 +489,10 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
about: {
type: 'string',
optional: false, nullable: true,
},
disableRegistration: {
type: 'boolean',
optional: false, nullable: false,

View file

@ -10,7 +10,7 @@ import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
import { CacheService } from '@/core/CacheService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { InternalEventService } from '@/global/InternalEventService.js';
export const meta = {
tags: ['admin'],
@ -36,7 +36,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private readonly cacheService: CacheService,
private readonly moderationLogService: ModerationLogService,
private readonly roleService: RoleService,
private readonly globalEventService: GlobalEventService,
private readonly internalEventService: InternalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.cacheService.findUserById(ps.userId);
@ -47,11 +47,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (user.isSilenced) return;
await this.usersRepository.update(user.id, {
await this.usersRepository.update({ id: user.id }, {
isSilenced: true,
});
this.globalEventService.publishInternalEvent(user.host == null ? 'localUserUpdated' : 'remoteUserUpdated', {
await this.internalEventService.emit(user.host == null ? 'localUserUpdated' : 'remoteUserUpdated', {
id: user.id,
});

View file

@ -9,7 +9,7 @@ import type { UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { CacheService } from '@/core/CacheService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { InternalEventService } from '@/global/InternalEventService.js';
export const meta = {
tags: ['admin'],
@ -34,18 +34,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private readonly usersRepository: UsersRepository,
private readonly cacheService: CacheService,
private readonly moderationLogService: ModerationLogService,
private readonly globalEventService: GlobalEventService,
private readonly internalEventService: InternalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.cacheService.findUserById(ps.userId);
if (!user.isSilenced) return;
await this.usersRepository.update(user.id, {
await this.usersRepository.update({ id: user.id }, {
isSilenced: false,
});
this.globalEventService.publishInternalEvent(user.host == null ? 'localUserUpdated' : 'remoteUserUpdated', {
await this.internalEventService.emit(user.host == null ? 'localUserUpdated' : 'remoteUserUpdated', {
id: user.id,
});

View file

@ -8,6 +8,8 @@ import type { UserProfilesRepository, UsersRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { InternalEventService } from '@/global/InternalEventService.js';
import { CacheService } from '@/core/CacheService.js';
export const meta = {
tags: ['admin'],
@ -34,23 +36,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
private readonly internalEventService: InternalEventService,
private readonly cacheService: CacheService,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
if (user == null) {
throw new Error('user not found');
}
const currentProfile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
const [user, currentProfile] = await Promise.all([
this.cacheService.findUserById(ps.userId),
this.cacheService.userProfileCache.fetch(ps.userId),
]);
await this.userProfilesRepository.update({ userId: user.id }, {
moderationNote: ps.text,
});
await this.internalEventService.emit('updateUserProfile', { userId: user.id });
this.moderationLogService.log(me, 'updateUserNote', {
await this.moderationLogService.log(me, 'updateUserNote', {
userId: user.id,
userUsername: user.username,
userHost: user.host,

View file

@ -28,10 +28,11 @@ export const meta = {
},
},
// 2 calls per second
// 20 calls, then 4 per second
limit: {
duration: 1000,
max: 2,
type: 'bucket',
size: 20,
dripRate: 250,
},
} as const;

View file

@ -51,10 +51,11 @@ export const meta = {
},
},
// 5 calls per second
// Up to 20 calls, then 4/second
limit: {
duration: 1000,
max: 5,
type: 'bucket',
size: 20,
dripRate: 250,
},
} as const;
@ -63,7 +64,6 @@ export const paramDef = {
properties: {
userId: { type: 'string', format: 'misskey:id' },
withReplies: { type: 'boolean', default: false },
withRepliesToSelf: { type: 'boolean', default: true },
withQuotes: { type: 'boolean', default: true },
withRenotes: { type: 'boolean', default: true },
withBots: { type: 'boolean', default: true },
@ -122,8 +122,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
withQuotes: ps.withQuotes,
withBots: ps.withBots,
withNonPublic: ps.withNonPublic,
withRepliesToOthers: ps.withReplies,
withRepliesToSelf: ps.withRepliesToSelf,
withReplies: ps.withReplies,
}, me);
return await this.noteEntityService.packMany(timeline, me);
@ -146,15 +145,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
ignoreAuthorFromInstanceBlock: true,
ignoreAuthorFromUserSuspension: true,
ignoreAuthorFromUserSilence: true,
excludeReplies: ps.withChannelNotes && !ps.withReplies, // userTimelineWithChannel may include replies
excludeNoFiles: ps.withChannelNotes && ps.withFiles, // userTimelineWithChannel may include notes without files
excludeReplies: !ps.withChannelNotes && !ps.withReplies, // userTimelineWithChannel may include replies
excludeNoFiles: !ps.withChannelNotes && ps.withFiles, // userTimelineWithChannel may include notes without files
excludePureRenotes: !ps.withRenotes,
excludeBots: !ps.withBots,
noteFilter: note => {
if (note.channel?.isSensitive && !isSelf) return false;
// These are handled by DB fallback, but we duplicate them here in case a timeline was already populated with notes
if (!ps.withRepliesToSelf && note.reply?.userId === note.userId) return false;
if (!ps.withQuotes && isRenote(note) && isQuote(note)) return false;
if (!ps.withNonPublic && note.visibility !== 'public') return false;
@ -171,8 +167,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
withQuotes: ps.withQuotes,
withBots: ps.withBots,
withNonPublic: ps.withNonPublic,
withRepliesToOthers: ps.withReplies,
withRepliesToSelf: ps.withRepliesToSelf,
withReplies: ps.withReplies,
}, me),
});
@ -191,8 +186,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
withQuotes: boolean,
withBots: boolean,
withNonPublic: boolean,
withRepliesToOthers: boolean,
withRepliesToSelf: boolean,
withReplies: boolean,
}, me: MiLocalUser | null) {
const isSelf = me && (me.id === ps.userId);
@ -236,12 +230,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.queryService.andIsNotQuote(query, 'note');
}
if (!ps.withRepliesToOthers && !ps.withRepliesToSelf) {
query.andWhere('reply.id IS NULL');
} else if (!ps.withRepliesToOthers) {
if (!ps.withReplies) {
this.queryService.generateExcludedRepliesQueryForNotes(query, me);
} else if (!ps.withRepliesToSelf) {
query.andWhere('(reply.id IS NULL OR reply."userId" != note."userId")');
}
if (!ps.withNonPublic) {