1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-19 22:55:29 +02:00

Merge branch 'develop' into renovate/major-backend-update-dependencies

This commit is contained in:
kakkokari-gtyih
2025-11-29 19:07:57 +09:00
55 changed files with 2582 additions and 2829 deletions

View File

@@ -41,7 +41,7 @@ function greet() {
//#endregion
console.log(' Misskey is an open-source decentralized microblogging platform.');
console.log(chalk.rgb(255, 136, 0)(' If you like Misskey, please donate to support development. https://www.patreon.com/syuilo'));
console.log(chalk.rgb(255, 136, 0)(' If you like Misskey, please consider donating to support dev. https://misskey-hub.net/docs/donate/'));
console.log('');
console.log(chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`);

View File

@@ -7,11 +7,11 @@ import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { Injectable } from '@nestjs/common';
import * as nsfw from 'nsfwjs';
import si from 'systeminformation';
import { Mutex } from 'async-mutex';
import fetch from 'node-fetch';
import { bindThis } from '@/decorators.js';
import type { NSFWJS, PredictionType } from 'nsfwjs';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@@ -21,7 +21,7 @@ let isSupportedCpu: undefined | boolean = undefined;
@Injectable()
export class AiService {
private model: nsfw.NSFWJS;
private model: NSFWJS;
private modelLoadMutex: Mutex = new Mutex();
constructor(
@@ -29,7 +29,7 @@ export class AiService {
}
@bindThis
public async detectSensitive(source: string | Buffer): Promise<nsfw.PredictionType[] | null> {
public async detectSensitive(source: string | Buffer): Promise<PredictionType[] | null> {
try {
if (isSupportedCpu === undefined) {
isSupportedCpu = await this.computeIsSupportedCpu();
@@ -44,6 +44,7 @@ export class AiService {
tf.env().global.fetch = fetch;
if (this.model == null) {
const nsfw = await import('nsfwjs');
await this.modelLoadMutex.runExclusive(async () => {
if (this.model == null) {
this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });

View File

@@ -5,7 +5,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueryService } from '@/core/QueryService.js';
import type { ClipFavoritesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
@@ -31,11 +30,6 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
sinceDate: { type: 'integer' },
untilDate: { type: 'integer' },
},
required: [],
} as const;
@@ -46,16 +40,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.clipFavoritesRepository)
private clipFavoritesRepository: ClipFavoritesRepository,
private queryService: QueryService,
private clipEntityService: ClipEntityService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.clipFavoritesRepository.createQueryBuilder('favorite'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
const query = this.clipFavoritesRepository.createQueryBuilder('favorite')
.andWhere('favorite.userId = :meId', { meId: me.id })
.leftJoinAndSelect('favorite.clip', 'clip');
const favorites = await query
.limit(ps.limit)
.getMany();
return this.clipEntityService.packMany(favorites.map(x => x.clip!), me);

View File

@@ -295,8 +295,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.chatScope !== undefined) updates.chatScope = ps.chatScope;
function checkMuteWordCount(mutedWords: (string[] | string)[], limit: number) {
// TODO: ちゃんと数える
const length = JSON.stringify(mutedWords).length;
const count = (arr: (string[] | string)[]) => {
let length = 0;
for (const item of arr) {
if (typeof item === 'string') {
length += item.length;
} else if (Array.isArray(item)) {
for (const subItem of item) {
length += subItem.length;
}
}
}
return length;
};
const length = count(mutedWords);
if (length > limit) {
throw new ApiError(meta.errors.tooManyMutedWords);
}

View File

@@ -135,6 +135,18 @@ export const meta = {
code: 'CANNOT_RENOTE_TO_EXTERNAL',
id: 'ed1952ac-2d26-4957-8b30-2deda76bedf7',
},
scheduledAtRequired: {
message: 'scheduledAt is required when isActuallyScheduled is true.',
code: 'SCHEDULED_AT_REQUIRED',
id: '15e28a55-e74c-4d65-89b7-8880cdaaa87d',
},
scheduledAtMustBeInFuture: {
message: 'scheduledAt must be in the future.',
code: 'SCHEDULED_AT_MUST_BE_IN_FUTURE',
id: 'e4bed6c9-017e-4934-aed0-01c22cc60ec1',
},
},
limit: {
@@ -252,6 +264,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility);
case 'c3275f19-4558-4c59-83e1-4f684b5fab66':
throw new ApiError(meta.errors.tooManyScheduledNotes);
case '94a89a43-3591-400a-9c17-dd166e71fdfa':
throw new ApiError(meta.errors.scheduledAtRequired);
case 'b34d0c1b-996f-4e34-a428-c636d98df457':
throw new ApiError(meta.errors.scheduledAtMustBeInFuture);
default:
throw err;
}

View File

@@ -165,6 +165,18 @@ export const meta = {
code: 'TOO_MANY_SCHEDULED_NOTES',
id: '02f5df79-08ae-4a33-8524-f1503c8f6212',
},
scheduledAtRequired: {
message: 'scheduledAt is required when isActuallyScheduled is true.',
code: 'SCHEDULED_AT_REQUIRED',
id: 'fe9737d5-cc41-498c-af9d-149207307530',
},
scheduledAtMustBeInFuture: {
message: 'scheduledAt must be in the future.',
code: 'SCHEDULED_AT_MUST_BE_IN_FUTURE',
id: 'ed1a6673-d0d1-4364-aaae-9bf3f139cbc5',
},
},
limit: {
@@ -295,6 +307,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.containsTooManyMentions);
case 'bacdf856-5c51-4159-b88a-804fa5103be5':
throw new ApiError(meta.errors.tooManyScheduledNotes);
case '94a89a43-3591-400a-9c17-dd166e71fdfa':
throw new ApiError(meta.errors.scheduledAtRequired);
case 'b34d0c1b-996f-4e34-a428-c636d98df457':
throw new ApiError(meta.errors.scheduledAtMustBeInFuture);
default:
throw err;
}

View File

@@ -50,7 +50,7 @@ import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntitySer
import { FeedService } from './FeedService.js';
import { UrlPreviewService } from './UrlPreviewService.js';
import { ClientLoggerService } from './ClientLoggerService.js';
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
import type { FastifyError, FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@@ -918,7 +918,7 @@ export class ClientServerService {
return await renderBase(reply);
});
fastify.setErrorHandler(async (error, request, reply) => {
fastify.setErrorHandler<FastifyError>(async (error, request, reply) => {
const errId = randomUUID();
this.clientLoggerService.logger.error(`Internal error occurred in ${request.routeOptions.url}: ${error.message}`, {
path: request.routeOptions.url,

View File

@@ -28,7 +28,7 @@ html
meta(name='theme-color-orig' content= themeColor || '#86b300')
meta(property='og:site_name' content= instanceName || 'Misskey')
meta(property='instance_url' content= instanceUrl)
meta(name='viewport' content='width=device-width, initial-scale=1')
meta(name='viewport' content='width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover')
meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no')
link(rel='icon' href= icon || '/favicon.ico')
link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png')