merge: misskey 2025.5.0 (!1028)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1028 Approved-by: Hazelnoot <acomputerdog@gmail.com> Approved-by: Marie <github@yuugi.dev>
This commit is contained in:
commit
13d045d813
152 changed files with 1690 additions and 842 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ type TimelineOptions = {
|
|||
excludeReplies?: boolean;
|
||||
excludeBots?: boolean;
|
||||
excludePureRenotes: boolean;
|
||||
ignoreAuthorFromUserSuspension?: boolean;
|
||||
dbFallback: (untilId: string | null, sinceId: string | null, limit: number) => Promise<MiNote[]>,
|
||||
};
|
||||
|
||||
|
|
@ -145,6 +146,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をキャッシュする?
|
||||
|
|
|
|||
|
|
@ -47,29 +47,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;
|
||||
}
|
||||
|
|
@ -557,4 +564,26 @@ export class QueryService {
|
|||
|
||||
return q[join](`NOT EXISTS (${threadMutedQuery.getQuery()})`, threadMutedQuery.getParameters());
|
||||
}
|
||||
|
||||
// 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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -301,6 +301,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);
|
||||
|
||||
|
|
@ -368,11 +369,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;
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import { URL, domainToASCII } from 'node:url';
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import RE2 from 're2';
|
||||
import psl from 'psl';
|
||||
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 {
|
||||
|
|
@ -213,4 +215,20 @@ export class UtilityService {
|
|||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -442,10 +442,12 @@ export class WebhookTestService {
|
|||
private async toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Promise<Packed<'UserLite'>> {
|
||||
return {
|
||||
...user,
|
||||
createdAt: this.idService.parse(user.id).date.toISOString(),
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
username: user.username,
|
||||
host: user.host,
|
||||
description: 'dummy user',
|
||||
avatarUrl: user.avatarId == null ? null : user.avatarUrl,
|
||||
avatarBlurhash: user.avatarId == null ? null : user.avatarBlurhash,
|
||||
avatarDecorations: user.avatarDecorations.map(it => ({
|
||||
|
|
@ -456,8 +458,6 @@ export class WebhookTestService {
|
|||
offsetX: it.offsetX,
|
||||
offsetY: it.offsetY,
|
||||
})),
|
||||
createdAt: this.idService.parse(user.id).date.toISOString(),
|
||||
description: '',
|
||||
isBot: user.isBot,
|
||||
isCat: user.isCat,
|
||||
emojis: await this.customEmojiService.populateEmojis(user.emojis, user.host),
|
||||
|
|
|
|||
|
|
@ -35,6 +35,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,
|
||||
|
|
@ -45,8 +46,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: instance.isBlocked,
|
||||
softwareName: instance.softwareName,
|
||||
softwareVersion: instance.softwareVersion,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue