mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-19 13:35:30 +02:00
Merge branch 'develop' into copilot/add-user-mute-settings
This commit is contained in:
@@ -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()})} ---`);
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -5,18 +5,9 @@
|
||||
|
||||
import {
|
||||
FindOneOptions,
|
||||
InsertQueryBuilder,
|
||||
ObjectLiteral,
|
||||
QueryRunner,
|
||||
Repository,
|
||||
SelectQueryBuilder,
|
||||
} from 'typeorm';
|
||||
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js';
|
||||
import { RelationCountLoader } from 'typeorm/query-builder/relation-count/RelationCountLoader.js';
|
||||
import { RelationIdLoader } from 'typeorm/query-builder/relation-id/RelationIdLoader.js';
|
||||
import {
|
||||
RawSqlResultsToEntityTransformer,
|
||||
} from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js';
|
||||
import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js';
|
||||
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||
import { MiAccessToken } from '@/models/AccessToken.js';
|
||||
@@ -96,66 +87,12 @@ import { MiWebhook } from '@/models/Webhook.js';
|
||||
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
||||
|
||||
export interface MiRepository<T extends ObjectLiteral> {
|
||||
createTableColumnNames(this: Repository<T> & MiRepository<T>): string[];
|
||||
|
||||
insertOne(this: Repository<T> & MiRepository<T>, entity: QueryDeepPartialEntity<T>, findOptions?: Pick<FindOneOptions<T>, 'relations'>): Promise<T>;
|
||||
|
||||
insertOneImpl(this: Repository<T> & MiRepository<T>, entity: QueryDeepPartialEntity<T>, findOptions?: Pick<FindOneOptions<T>, 'relations'>, queryRunner?: QueryRunner): Promise<T>;
|
||||
|
||||
selectAliasColumnNames(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>, builder: SelectQueryBuilder<T>): void;
|
||||
}
|
||||
|
||||
export const miRepository = {
|
||||
createTableColumnNames() {
|
||||
return this.metadata.columns.filter(column => column.isSelect && !column.isVirtual).map(column => column.databaseName);
|
||||
},
|
||||
async insertOne(entity, findOptions?) {
|
||||
const opt = this.manager.connection.options as PostgresConnectionOptions;
|
||||
if (opt.replication) {
|
||||
const queryRunner = this.manager.connection.createQueryRunner('master');
|
||||
try {
|
||||
return this.insertOneImpl(entity, findOptions, queryRunner);
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
} else {
|
||||
return this.insertOneImpl(entity, findOptions);
|
||||
}
|
||||
},
|
||||
async insertOneImpl(entity, findOptions?, queryRunner?) {
|
||||
// ---- insert + returningの結果を共通テーブル式(CTE)に保持するクエリを生成 ----
|
||||
|
||||
const queryBuilder = this.createQueryBuilder().insert().values(entity);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const mainAlias = queryBuilder.expressionMap.mainAlias!;
|
||||
const name = mainAlias.name;
|
||||
mainAlias.name = 't';
|
||||
const columnNames = this.createTableColumnNames();
|
||||
queryBuilder.returning(columnNames.reduce((a, c) => `${a}, ${queryBuilder.escape(c)}`, '').slice(2));
|
||||
|
||||
// ---- 共通テーブル式(CTE)から結果を取得 ----
|
||||
const builder = this.createQueryBuilder(undefined, queryRunner).addCommonTableExpression(queryBuilder, 'cte', { columnNames });
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
builder.expressionMap.mainAlias!.tablePath = 'cte';
|
||||
this.selectAliasColumnNames(queryBuilder, builder);
|
||||
if (findOptions) {
|
||||
builder.setFindOptions(findOptions);
|
||||
}
|
||||
const raw = await builder.execute();
|
||||
mainAlias.name = name;
|
||||
const relationId = await new RelationIdLoader(builder.connection, this.queryRunner, builder.expressionMap.relationIdAttributes).load(raw);
|
||||
const relationCount = await new RelationCountLoader(builder.connection, this.queryRunner, builder.expressionMap.relationCountAttributes).load(raw);
|
||||
const result = new RawSqlResultsToEntityTransformer(builder.expressionMap, builder.connection.driver, relationId, relationCount, this.queryRunner).transform(raw, mainAlias);
|
||||
return result[0];
|
||||
},
|
||||
selectAliasColumnNames(queryBuilder, builder) {
|
||||
let selectOrAddSelect = (selection: string, selectionAliasName?: string) => {
|
||||
selectOrAddSelect = (selection, selectionAliasName) => builder.addSelect(selection, selectionAliasName);
|
||||
return builder.select(selection, selectionAliasName);
|
||||
};
|
||||
for (const columnName of this.createTableColumnNames()) {
|
||||
selectOrAddSelect(`${builder.alias}.${columnName}`, `${builder.alias}_${columnName}`);
|
||||
}
|
||||
return await this.insert(entity).then(x => this.findOneOrFail({ where: x.identifiers[0], ...findOptions }));
|
||||
},
|
||||
} satisfies MiRepository<ObjectLiteral>;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
// https://github.com/typeorm/typeorm/issues/2400
|
||||
import pg from 'pg';
|
||||
import { DataSource, Logger, type QueryRunner } from 'typeorm';
|
||||
import * as highlight from 'cli-highlight';
|
||||
import { entities as charts } from '@/core/chart/entities.js';
|
||||
import { Config } from '@/config.js';
|
||||
import MisskeyLogger from '@/logger.js';
|
||||
@@ -25,7 +24,7 @@ import { MiAuthSession } from '@/models/AuthSession.js';
|
||||
import { MiBlocking } from '@/models/Blocking.js';
|
||||
import { MiChannelFollowing } from '@/models/ChannelFollowing.js';
|
||||
import { MiChannelFavorite } from '@/models/ChannelFavorite.js';
|
||||
import { MiChannelMuting } from "@/models/ChannelMuting.js";
|
||||
import { MiChannelMuting } from '@/models/ChannelMuting.js';
|
||||
import { MiClip } from '@/models/Clip.js';
|
||||
import { MiClipNote } from '@/models/ClipNote.js';
|
||||
import { MiClipFavorite } from '@/models/ClipFavorite.js';
|
||||
@@ -101,12 +100,6 @@ export type LoggerProps = {
|
||||
printReplicationMode?: boolean,
|
||||
};
|
||||
|
||||
function highlightSql(sql: string) {
|
||||
return highlight.highlight(sql, {
|
||||
language: 'sql', ignoreIllegals: true,
|
||||
});
|
||||
}
|
||||
|
||||
function truncateSql(sql: string) {
|
||||
return sql.length > 100 ? `${sql.substring(0, 100)}...` : sql;
|
||||
}
|
||||
@@ -132,7 +125,7 @@ class MyCustomLogger implements Logger {
|
||||
modded = truncateSql(modded);
|
||||
}
|
||||
|
||||
return highlightSql(modded);
|
||||
return modded;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append('auth_key', this.serverSettings.deeplAuthKey);
|
||||
params.append('text', note.text);
|
||||
params.append('target_lang', targetLang);
|
||||
|
||||
@@ -104,6 +103,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
const res = await this.httpRequestService.send(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `DeepL-Auth-Key ${this.serverSettings.deeplAuthKey}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: 'application/json, */*',
|
||||
},
|
||||
|
||||
@@ -41,6 +41,10 @@ class ChannelChannel extends Channel {
|
||||
private async onNote(note: Packed<'Note'>) {
|
||||
if (note.channelId !== this.channelId) return;
|
||||
|
||||
if (note.user.requireSigninToViewContents && this.user == null) return;
|
||||
if (note.renote && note.renote.user.requireSigninToViewContents && this.user == null) return;
|
||||
if (note.reply && note.reply.user.requireSigninToViewContents && this.user == null) return;
|
||||
|
||||
if (this.isNoteMutedOrBlocked(note)) return;
|
||||
|
||||
if (this.user && isRenotePacked(note) && !isQuotePacked(note)) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
//#region Script
|
||||
async function importAppScript() {
|
||||
await import(CLIENT_ENTRY ? `/embed_vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/embed_vite/src/_boot_.ts')
|
||||
await import(CLIENT_ENTRY ? `/embed_vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/embed_vite/src/boot.ts')
|
||||
.catch(async e => {
|
||||
console.error(e);
|
||||
renderError('APP_IMPORT');
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user