diff options
| author | misskey-release-bot[bot] <157398866+misskey-release-bot[bot]@users.noreply.github.com> | 2025-05-07 02:46:42 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-07 02:46:42 +0000 |
| commit | 9ed0d5ccecb53c973cef1e762dd0fae9e04f9a5b (patch) | |
| tree | c41c3ee20b995c3a74a75d4005ab980d217a3727 /packages/backend/src/core | |
| parent | Merge pull request #15842 from misskey-dev/develop (diff) | |
| parent | Release: 2025.5.0 (diff) | |
| download | misskey-9ed0d5ccecb53c973cef1e762dd0fae9e04f9a5b.tar.gz misskey-9ed0d5ccecb53c973cef1e762dd0fae9e04f9a5b.tar.bz2 misskey-9ed0d5ccecb53c973cef1e762dd0fae9e04f9a5b.zip | |
Merge pull request #15933 from misskey-dev/develop
Release: 2025.5.0
Diffstat (limited to 'packages/backend/src/core')
| -rw-r--r-- | packages/backend/src/core/AchievementService.ts | 82 | ||||
| -rw-r--r-- | packages/backend/src/core/FanoutTimelineEndpointService.ts | 18 | ||||
| -rw-r--r-- | packages/backend/src/core/QueryService.ts | 61 | ||||
| -rw-r--r-- | packages/backend/src/core/SearchService.ts | 9 | ||||
| -rw-r--r-- | packages/backend/src/core/UtilityService.ts | 20 | ||||
| -rw-r--r-- | packages/backend/src/core/entities/InstanceEntityService.ts | 5 |
6 files changed, 94 insertions, 101 deletions
diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts index 4fc1193f32..8d2de89efd 100644 --- a/packages/backend/src/core/AchievementService.ts +++ b/packages/backend/src/core/AchievementService.ts @@ -9,87 +9,7 @@ import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { NotificationService } from '@/core/NotificationService.js'; - -export const ACHIEVEMENT_TYPES = [ - 'notes1', - 'notes10', - 'notes100', - 'notes500', - 'notes1000', - 'notes5000', - 'notes10000', - 'notes20000', - 'notes30000', - 'notes40000', - 'notes50000', - 'notes60000', - 'notes70000', - 'notes80000', - 'notes90000', - 'notes100000', - 'login3', - 'login7', - 'login15', - 'login30', - 'login60', - 'login100', - 'login200', - 'login300', - 'login400', - 'login500', - 'login600', - 'login700', - 'login800', - 'login900', - 'login1000', - 'passedSinceAccountCreated1', - 'passedSinceAccountCreated2', - 'passedSinceAccountCreated3', - 'loggedInOnBirthday', - 'loggedInOnNewYearsDay', - 'noteClipped1', - 'noteFavorited1', - 'myNoteFavorited1', - 'profileFilled', - 'markedAsCat', - 'following1', - 'following10', - 'following50', - 'following100', - 'following300', - 'followers1', - 'followers10', - 'followers50', - 'followers100', - 'followers300', - 'followers500', - 'followers1000', - 'collectAchievements30', - 'viewAchievements3min', - 'iLoveMisskey', - 'foundTreasure', - 'client30min', - 'client60min', - 'noteDeletedWithin1min', - 'postedAtLateNight', - 'postedAt0min0sec', - 'selfQuote', - 'htl20npm', - 'viewInstanceChart', - 'outputHelloWorldOnScratchpad', - 'open3windows', - 'driveFolderCircularReference', - 'reactWithoutRead', - 'clickedClickHere', - 'justPlainLucky', - 'setNameToSyuilo', - 'cookieClicked', - 'brainDiver', - 'smashTestNotificationButton', - 'tutorialCompleted', - 'bubbleGameExplodingHead', - 'bubbleGameDoubleExplodingHead', -] as const; +import { ACHIEVEMENT_TYPES } from '@/models/UserProfile.js'; @Injectable() export class AchievementService { diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts index 1ffeb4b3a4..6253f792ed 100644 --- a/packages/backend/src/core/FanoutTimelineEndpointService.ts +++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts @@ -36,6 +36,7 @@ type TimelineOptions = { excludeNoFiles?: boolean; excludeReplies?: boolean; excludePureRenotes: boolean; + ignoreAuthorFromUserSuspension?: boolean; dbFallback: (untilId: string | null, sinceId: string | null, limit: number) => Promise<MiNote[]>, }; @@ -139,6 +140,23 @@ export class FanoutTimelineEndpointService { }; } + { + const parentFilter = filter; + filter = (note) => { + const noteJoined = note as MiNote & { + renoteUser: MiUser | null; + replyUser: MiUser | null; + }; + if (!ps.ignoreAuthorFromUserSuspension) { + if (note.user!.isSuspended) return false; + } + if (note.userId !== note.renoteUserId && noteJoined.renoteUser?.isSuspended) return false; + if (note.userId !== note.replyUserId && noteJoined.replyUser?.isSuspended) return false; + + return parentFilter(note); + }; + } + const redisTimeline: MiNote[] = []; let readFromRedis = 0; let lastSuccessfulRate = 1; // rateをキャッシュする? diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index 119eb49c02..b9cef5b0ec 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -43,29 +43,36 @@ export class QueryService { ) { } - public makePaginationQuery<T extends ObjectLiteral>(q: SelectQueryBuilder<T>, sinceId?: string | null, untilId?: string | null, sinceDate?: number | null, untilDate?: number | null): SelectQueryBuilder<T> { + public makePaginationQuery<T extends ObjectLiteral>( + q: SelectQueryBuilder<T>, + sinceId?: string | null, + untilId?: string | null, + sinceDate?: number | null, + untilDate?: number | null, + targetColumn = 'id', + ): SelectQueryBuilder<T> { if (sinceId && untilId) { - q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); - q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); - q.orderBy(`${q.alias}.id`, 'DESC'); + q.andWhere(`${q.alias}.${targetColumn} > :sinceId`, { sinceId: sinceId }); + q.andWhere(`${q.alias}.${targetColumn} < :untilId`, { untilId: untilId }); + q.orderBy(`${q.alias}.${targetColumn}`, 'DESC'); } else if (sinceId) { - q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); - q.orderBy(`${q.alias}.id`, 'ASC'); + q.andWhere(`${q.alias}.${targetColumn} > :sinceId`, { sinceId: sinceId }); + q.orderBy(`${q.alias}.${targetColumn}`, 'ASC'); } else if (untilId) { - q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); - q.orderBy(`${q.alias}.id`, 'DESC'); + q.andWhere(`${q.alias}.${targetColumn} < :untilId`, { untilId: untilId }); + q.orderBy(`${q.alias}.${targetColumn}`, 'DESC'); } else if (sinceDate && untilDate) { - q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.gen(sinceDate) }); - q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.gen(untilDate) }); - q.orderBy(`${q.alias}.id`, 'DESC'); + q.andWhere(`${q.alias}.${targetColumn} > :sinceId`, { sinceId: this.idService.gen(sinceDate) }); + q.andWhere(`${q.alias}.${targetColumn} < :untilId`, { untilId: this.idService.gen(untilDate) }); + q.orderBy(`${q.alias}.${targetColumn}`, 'DESC'); } else if (sinceDate) { - q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.gen(sinceDate) }); - q.orderBy(`${q.alias}.id`, 'ASC'); + q.andWhere(`${q.alias}.${targetColumn} > :sinceId`, { sinceId: this.idService.gen(sinceDate) }); + q.orderBy(`${q.alias}.${targetColumn}`, 'ASC'); } else if (untilDate) { - q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.gen(untilDate) }); - q.orderBy(`${q.alias}.id`, 'DESC'); + q.andWhere(`${q.alias}.${targetColumn} < :untilId`, { untilId: this.idService.gen(untilDate) }); + q.orderBy(`${q.alias}.${targetColumn}`, 'DESC'); } else { - q.orderBy(`${q.alias}.id`, 'DESC'); + q.orderBy(`${q.alias}.${targetColumn}`, 'DESC'); } return q; } @@ -287,4 +294,26 @@ export class QueryService { .andWhere(instanceSuspension('renoteUser')); } } + + // Requirements: user replyUser renoteUser must be joined + @bindThis + public generateSuspendedUserQueryForNote(q: SelectQueryBuilder<any>, excludeAuthor?: boolean): void { + if (excludeAuthor) { + const brakets = (user: string) => new Brackets(qb => qb + .where(`note.${user}Id IS NULL`) + .orWhere(`user.id = ${user}.id`) + .orWhere(`${user}.isSuspended = FALSE`)); + q + .andWhere(brakets('replyUser')) + .andWhere(brakets('renoteUser')); + } else { + const brakets = (user: string) => new Brackets(qb => qb + .where(`note.${user}Id IS NULL`) + .orWhere(`${user}.isSuspended = FALSE`)); + q + .andWhere('user.isSuspended = FALSE') + .andWhere(brakets('replyUser')) + .andWhere(brakets('renoteUser')); + } + } } diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index d94281920e..20a776ded8 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -235,6 +235,7 @@ export class SearchService { this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); if (me) this.queryService.generateMutedUserQueryForNotes(query, me); if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); @@ -297,11 +298,17 @@ export class SearchService { ]) : [new Set<string>(), new Set<string>()]; - const query = this.notesRepository.createQueryBuilder('note'); + const query = this.notesRepository.createQueryBuilder('note') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); query.where('note.id IN (:...noteIds)', { noteIds: res.hits.map(x => x.id) }); this.queryService.generateBlockedHostQueryForNote(query); + this.queryService.generateSuspendedUserQueryForNote(query); const notes = (await query.getMany()).filter(note => { if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false; diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index 23fb928ac9..67ec6cc7b0 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -6,10 +6,12 @@ import { URL, domainToASCII } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import RE2 from 're2'; +import semver from 'semver'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; -import { MiMeta } from '@/models/Meta.js'; +import { MiMeta, SoftwareSuspension } from '@/models/Meta.js'; +import { MiInstance } from '@/models/Instance.js'; @Injectable() export class UtilityService { @@ -143,4 +145,20 @@ export class UtilityService { const host = this.extractDbHost(uri); return this.isFederationAllowedHost(host); } + + @bindThis + public isDeliverSuspendedSoftware(software: Pick<MiInstance, 'softwareName' | 'softwareVersion'>): SoftwareSuspension | undefined { + if (software.softwareName == null) return undefined; + if (software.softwareVersion == null) { + // software version is null; suspend iff versionRange is * + return this.meta.deliverSuspendedSoftware.find(x => + x.software === software.softwareName + && x.versionRange.trim() === '*'); + } else { + const softwareVersion = software.softwareVersion; + return this.meta.deliverSuspendedSoftware.find(x => + x.software === software.softwareName + && semver.satisfies(softwareVersion, x.versionRange, { includePrerelease: true })); + } + } } diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index 284537b986..3688cfb363 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -31,6 +31,7 @@ export class InstanceEntityService { me?: { id: MiUser['id']; } | null | undefined, ): Promise<Packed<'FederationInstance'>> { const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false; + const softwareSuspended = this.utilityService.isDeliverSuspendedSoftware(instance); return { id: instance.id, @@ -41,8 +42,8 @@ export class InstanceEntityService { followingCount: instance.followingCount, followersCount: instance.followersCount, isNotResponding: instance.isNotResponding, - isSuspended: instance.suspensionState !== 'none', - suspensionState: instance.suspensionState, + isSuspended: instance.suspensionState !== 'none' || Boolean(softwareSuspended), + suspensionState: instance.suspensionState === 'none' && softwareSuspended ? 'softwareSuspended' : instance.suspensionState, isBlocked: this.utilityService.isBlockedHost(this.meta.blockedHosts, instance.host), softwareName: instance.softwareName, softwareVersion: instance.softwareVersion, |