1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-16 05:05:32 +02:00

Merge branch 'develop' into fetch-outbox

This commit is contained in:
Kagami Sascha Rosylight
2023-08-06 00:04:26 +02:00
1698 changed files with 17392 additions and 4601 deletions

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
declare module 'hcaptcha' {
interface IVerifyResponse {
success: boolean;

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
declare module '@peertube/http-signature' {
import type { IncomingMessage, ClientRequest } from 'node:http';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
declare module 'os-utils' {
type FreeCommandCallback = (usedmem: number) => void;

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
declare module '*/package.json' {
interface IRepository {
type: string;

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
declare module 'probe-image-size' {
import type { ReadStream } from 'node:fs';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
declare module 'redis-lock' {
import type Redis from 'ioredis';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { setTimeout } from 'node:timers/promises';
import { Global, Inject, Module } from '@nestjs/common';
import * as Redis from 'ioredis';
@@ -41,14 +46,7 @@ const $meilisearch: Provider = {
const $redis: Provider = {
provide: DI.redis,
useFactory: (config: Config) => {
return new Redis.Redis({
port: config.redis.port,
host: config.redis.host,
family: config.redis.family == null ? 0 : config.redis.family,
password: config.redis.pass,
keyPrefix: `${config.redis.prefix}:`,
db: config.redis.db ?? 0,
});
return new Redis.Redis(config.redis);
},
inject: [DI.config],
};
@@ -56,14 +54,7 @@ const $redis: Provider = {
const $redisForPub: Provider = {
provide: DI.redisForPub,
useFactory: (config: Config) => {
const redis = new Redis.Redis({
port: config.redisForPubsub.port,
host: config.redisForPubsub.host,
family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family,
password: config.redisForPubsub.pass,
keyPrefix: `${config.redisForPubsub.prefix}:`,
db: config.redisForPubsub.db ?? 0,
});
const redis = new Redis.Redis(config.redisForPubsub);
return redis;
},
inject: [DI.config],
@@ -72,14 +63,7 @@ const $redisForPub: Provider = {
const $redisForSub: Provider = {
provide: DI.redisForSub,
useFactory: (config: Config) => {
const redis = new Redis.Redis({
port: config.redisForPubsub.port,
host: config.redisForPubsub.host,
family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family,
password: config.redisForPubsub.pass,
keyPrefix: `${config.redisForPubsub.prefix}:`,
db: config.redisForPubsub.db ?? 0,
});
const redis = new Redis.Redis(config.redisForPubsub);
redis.subscribe(config.host);
return redis;
},

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Module } from '@nestjs/common';
import { ServerModule } from '@/server/ServerModule.js';
import { GlobalModule } from '@/GlobalModule.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { LoggerService } from '@nestjs/common';
import Logger from '@/logger.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { NestFactory } from '@nestjs/core';
import { ChartManagementService } from '@/core/chart/ChartManagementService.js';
import { QueueProcessorService } from '@/queue/QueueProcessorService.js';

View File

@@ -1,3 +1,7 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* Misskey Entry Point!

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
@@ -5,7 +10,6 @@ import * as os from 'node:os';
import cluster from 'node:cluster';
import chalk from 'chalk';
import chalkTemplate from 'chalk-template';
import semver from 'semver';
import Logger from '@/logger.js';
import { loadConfig } from '@/config.js';
import type { Config } from '@/config.js';
@@ -78,7 +82,7 @@ export async function masterMain() {
await spawnWorkers(config.clusterLimit);
}
bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, null, true);
bootLogger.succ(config.socket ? `Now listening on socket ${config.socket} on ${config.url}` : `Now listening on port ${config.port} on ${config.url}`, null, true);
}
function showEnvironment(): void {

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import cluster from 'node:cluster';
import { envOption } from '@/env.js';
import { jobQueue, server } from './common.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* Config loader
*/
@@ -6,6 +11,16 @@ import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
import * as yaml from 'js-yaml';
import type { RedisOptions } from 'ioredis';
type RedisOptionsSource = Partial<RedisOptions> & {
host: string;
port: number;
family?: number;
pass: string;
db?: number;
prefix?: string;
};
/**
* ユーザーが設定する必要のある情報
@@ -14,7 +29,9 @@ export type Source = {
repository_url?: string;
feedback_url?: string;
url: string;
port: number;
port?: number;
socket?: string;
chmodSocket?: string;
disableHsts?: boolean;
db: {
host: string;
@@ -33,30 +50,9 @@ export type Source = {
user: string;
pass: string;
}[];
redis: {
host: string;
port: number;
family?: number;
pass: string;
db?: number;
prefix?: string;
};
redisForPubsub?: {
host: string;
port: number;
family?: number;
pass: string;
db?: number;
prefix?: string;
};
redisForJobQueue?: {
host: string;
port: number;
family?: number;
pass: string;
db?: number;
prefix?: string;
};
redis: RedisOptionsSource;
redisForPubsub?: RedisOptionsSource;
redisForJobQueue?: RedisOptionsSource;
meilisearch?: {
host: string;
port: string;
@@ -118,8 +114,9 @@ export type Mixin = {
mediaProxy: string;
externalMediaProxyEnabled: boolean;
videoThumbnailGenerator: string | null;
redisForPubsub: NonNullable<Source['redisForPubsub']>;
redisForJobQueue: NonNullable<Source['redisForJobQueue']>;
redis: RedisOptions & RedisOptionsSource;
redisForPubsub: RedisOptions & RedisOptionsSource;
redisForJobQueue: RedisOptions & RedisOptionsSource;
};
export type Config = Source & Mixin;
@@ -181,9 +178,9 @@ export function loadConfig() {
config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
: null;
if (!config.redis.prefix) config.redis.prefix = mixin.host;
if (config.redisForPubsub == null) config.redisForPubsub = config.redis;
if (config.redisForJobQueue == null) config.redisForJobQueue = config.redis;
mixin.redis = convertRedisOptions(config.redis, mixin.host);
mixin.redisForPubsub = config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, mixin.host) : mixin.redis;
mixin.redisForJobQueue = config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, mixin.host) : mixin.redis;
return Object.assign(config, mixin);
}
@@ -195,3 +192,14 @@ function tryCreateUrl(url: string) {
throw new Error(`url="${url}" is not a valid URL.`);
}
}
function convertRedisOptions(options: RedisOptionsSource, host: string): RedisOptions & RedisOptionsSource {
return {
...options,
password: options.pass,
prefix: options.prefix ?? host,
family: options.family ?? 0,
keyPrefix: `${options.prefix ?? host}:`,
db: options.db ?? 0,
};
}

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const MAX_NOTE_TEXT_LENGTH = 3000;
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min

View File

@@ -1,13 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { IsNull, In, MoreThan, Not } from 'typeorm';
import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, Muting, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/index.js';
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/index.js';
import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
import type { User } from '@/models/entities/User.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
@@ -27,9 +30,6 @@ import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
@Injectable()
export class AccountMoveService {
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,

View File

@@ -1,7 +1,11 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import type { User } from '@/models/entities/User.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { RelayService } from '@/core/RelayService.js';
@@ -12,9 +16,6 @@ import { bindThis } from '@/decorators.js';
@Injectable()
export class AccountUpdateService {
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,

View File

@@ -1,5 +1,10 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import type { UserProfilesRepository, UsersRepository } from '@/models/index.js';
import type { UserProfilesRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
@@ -85,9 +90,6 @@ export const ACHIEVEMENT_TYPES = [
@Injectable()
export class AchievementService {
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,

View File

@@ -1,12 +1,15 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { Inject, Injectable } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import * as nsfw from 'nsfwjs';
import si from 'systeminformation';
import { Mutex } from 'async-mutex';
import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
const _filename = fileURLToPath(import.meta.url);
@@ -21,8 +24,6 @@ export class AiService {
private modelLoadMutex: Mutex = new Mutex();
constructor(
@Inject(DI.config)
private config: Config,
) {
}

View File

@@ -1,18 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import type { Antenna } from '@/models/entities/Antenna.js';
import type { Note } from '@/models/entities/Note.js';
import type { User } from '@/models/entities/User.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js';
import { IdService } from '@/core/IdService.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { PushNotificationService } from '@/core/PushNotificationService.js';
import * as Acct from '@/misc/acct.js';
import type { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js';
import type { MutingsRepository, NotesRepository, AntennasRepository, UserListJoiningsRepository } from '@/models/index.js';
import type { AntennasRepository, UserListJoiningsRepository } from '@/models/index.js';
import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { StreamMessages } from '@/server/api/stream/types.js';
@@ -30,12 +30,6 @@ export class AntennaService implements OnApplicationShutdown {
@Inject(DI.redisForSub)
private redisForSub: Redis.Redis,
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.antennasRepository)
private antennasRepository: AntennasRepository,
@@ -43,11 +37,7 @@ export class AntennaService implements OnApplicationShutdown {
private userListJoiningsRepository: UserListJoiningsRepository,
private utilityService: UtilityService,
private idService: IdService,
private globalEventService: GlobalEventService,
private pushNotificationService: PushNotificationService,
private noteEntityService: NoteEntityService,
private antennaEntityService: AntennaEntityService,
) {
this.antennasFetched = false;
this.antennas = [];

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { promisify } from 'node:util';
import { Inject, Injectable } from '@nestjs/common';
import redisLock from 'redis-lock';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, UserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Module } from '@nestjs/common';
import { AccountMoveService } from './AccountMoveService.js';
import { AccountUpdateService } from './AccountUpdateService.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import bcrypt from 'bcryptjs';
@@ -33,7 +38,7 @@ export class CreateSystemUserService {
// Generate secret
const secret = generateNativeUserToken();
const keyPair = await genRsaKeyPair(4096);
const keyPair = await genRsaKeyPair();
let account!: User;

View File

@@ -1,5 +1,10 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { DataSource, In, IsNull } from 'typeorm';
import { In, IsNull } from 'typeorm';
import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
import { IdService } from '@/core/IdService.js';
@@ -11,7 +16,6 @@ import type { EmojisRepository, Role } from '@/models/index.js';
import { bindThis } from '@/decorators.js';
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
import { UtilityService } from '@/core/UtilityService.js';
import type { Config } from '@/config.js';
import { query } from '@/misc/prelude/url.js';
import type { Serialized } from '@/server/api/stream/types.js';
@@ -26,12 +30,6 @@ export class CustomEmojiService implements OnApplicationShutdown {
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.config)
private config: Config,
@Inject(DI.db)
private db: DataSource,
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,

View File

@@ -1,8 +1,12 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository } from '@/models/index.js';
import { QueueService } from '@/core/QueueService.js';
import { UserSuspendService } from '@/core/UserSuspendService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
@@ -14,7 +18,6 @@ export class DeleteAccountService {
private userSuspendService: UserSuspendService,
private queueService: QueueService,
private globalEventService: GlobalEventService,
) {
}

View File

@@ -1,6 +1,10 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as fs from 'node:fs';
import * as stream from 'node:stream';
import * as util from 'node:util';
import * as stream from 'node:stream/promises';
import { Inject, Injectable } from '@nestjs/common';
import ipaddr from 'ipaddr.js';
import chalk from 'chalk';
@@ -14,7 +18,6 @@ import { StatusError } from '@/misc/status-error.js';
import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js';
const pipeline = util.promisify(stream.pipeline);
import { bindThis } from '@/decorators.js';
@Injectable()
@@ -102,7 +105,7 @@ export class DownloadService {
});
try {
await pipeline(req, fs.createWriteStream(path));
await stream.pipeline(req, fs.createWriteStream(path));
} catch (e) {
if (e instanceof Got.HTTPError) {
throw new StatusError(`${e.response.statusCode} ${e.response.statusMessage}`, e.response.statusCode, e.response.statusMessage);
@@ -129,7 +132,7 @@ export class DownloadService {
// write content at URL to temp file
await this.downloadUrl(url, path);
const text = await util.promisify(fs.readFile)(path, 'utf8');
const text = await fs.promises.readFile(path, 'utf8');
return text;
} finally {

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { randomUUID } from 'node:crypto';
import * as fs from 'node:fs';
import { Inject, Injectable } from '@nestjs/common';
@@ -327,7 +332,7 @@ export class DriveService {
this.registerLogger.debug('web image not created (not an required image)');
}
} catch (err) {
this.registerLogger.warn('web image not created (an error occured)', err as Error);
this.registerLogger.warn('web image not created (an error occurred)', err as Error);
}
} else {
if (satisfyWebpublic) this.registerLogger.info('web image not created (original satisfies webpublic)');
@@ -346,7 +351,7 @@ export class DriveService {
thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 422);
}
} catch (err) {
this.registerLogger.warn('thumbnail not created (an error occured)', err as Error);
this.registerLogger.warn('thumbnail not created (an error occurred)', err as Error);
}
// #endregion thumbnail
@@ -569,9 +574,7 @@ export class DriveService {
file.maybePorn = info.porn;
file.isSensitive = user
? this.userEntityService.isLocalUser(user) && profile!.alwaysMarkNsfw ? true :
(sensitive !== null && sensitive !== undefined)
? sensitive
: false
sensitive ?? false
: false;
if (info.sensitive && profile!.autoSensitive) file.isSensitive = true;

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as nodemailer from 'nodemailer';
import { Inject, Injectable } from '@nestjs/common';
import { validate as validateEmail } from 'deep-email-validator';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as Redis from 'ioredis';
import type { InstancesRepository } from '@/models/index.js';

View File

@@ -1,7 +1,13 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import { JSDOM } from 'jsdom';
import tinycolor from 'tinycolor2';
import * as Redis from 'ioredis';
import type { Instance } from '@/models/entities/Instance.js';
import type Logger from '@/logger.js';
import { DI } from '@/di-symbols.js';
@@ -10,7 +16,6 @@ import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import type { DOMWindow } from 'jsdom';
import * as Redis from 'ioredis';
type NodeInfo = {
openRegistrations?: unknown;
@@ -70,9 +75,9 @@ export class FetchInstanceMetadataService {
return;
}
}
this.logger.info(`Fetching metadata of ${instance.host} ...`);
const [info, dom, manifest] = await Promise.all([
this.fetchNodeinfo(instance).catch(() => null),
this.fetchDom(instance).catch(() => null),
@@ -103,7 +108,7 @@ export class FetchInstanceMetadataService {
if (name) updates.name = name;
if (description) updates.description = description;
if (icon || favicon) updates.iconUrl = (icon && !icon.includes('data:image/png;base64')) ? icon : favicon;
if (icon ?? favicon) updates.iconUrl = (icon && !icon.includes('data:image/png;base64')) ? icon : favicon;
if (favicon) updates.faviconUrl = favicon;
if (themeColor) updates.themeColor = themeColor;

View File

@@ -1,8 +1,12 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as fs from 'node:fs';
import * as crypto from 'node:crypto';
import { join } from 'node:path';
import * as stream from 'node:stream';
import * as util from 'node:util';
import * as stream from 'node:stream/promises';
import { Injectable } from '@nestjs/common';
import { FSWatcher } from 'chokidar';
import * as fileType from 'file-type';
@@ -16,8 +20,6 @@ import { createTempDir } from '@/misc/create-temp.js';
import { AiService } from '@/core/AiService.js';
import { bindThis } from '@/decorators.js';
const pipeline = util.promisify(stream.pipeline);
export type FileInfo = {
size: number;
md5: string;
@@ -371,8 +373,7 @@ export class FileInfoService {
*/
@bindThis
public async getFileSize(path: string): Promise<number> {
const getStat = util.promisify(fs.stat);
return (await getStat(path)).size;
return (await fs.promises.stat(path)).size;
}
/**
@@ -381,7 +382,7 @@ export class FileInfoService {
@bindThis
private async calcHash(path: string): Promise<string> {
const hash = crypto.createHash('md5').setEncoding('hex');
await pipeline(fs.createReadStream(path), hash);
await stream.pipeline(fs.createReadStream(path), hash);
return hash.read();
}

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import type { User } from '@/models/entities/User.js';

View File

@@ -1,19 +1,21 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { User } from '@/models/entities/User.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { IdService } from '@/core/IdService.js';
import type { Hashtag } from '@/models/entities/Hashtag.js';
import type { HashtagsRepository, UsersRepository } from '@/models/index.js';
import type { HashtagsRepository } from '@/models/index.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
@Injectable()
export class HashtagService {
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.hashtagsRepository)
private hashtagsRepository: HashtagsRepository,

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as http from 'node:http';
import * as https from 'node:https';
import * as net from 'node:net';
@@ -88,7 +93,7 @@ export class HttpRequestService {
*/
@bindThis
public getAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent {
if (bypassProxy || (this.config.proxyBypassHosts || []).includes(url.hostname)) {
if (bypassProxy || (this.config.proxyBypassHosts ?? []).includes(url.hostname)) {
return url.protocol === 'http:' ? this.http : this.https;
} else {
return url.protocol === 'http:' ? this.httpAgent : this.httpsAgent;

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { ulid } from 'ulid';
import { DI } from '@/di-symbols.js';

View File

@@ -1,7 +1,10 @@
import { Inject, Injectable } from '@nestjs/common';
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import sharp from 'sharp';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
export type IImage = {
data: Buffer;
@@ -45,8 +48,6 @@ import { Readable } from 'node:stream';
@Injectable()
export class ImageProcessingService {
constructor(
@Inject(DI.config)
private config: Config,
) {
}

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { IsNull } from 'typeorm';
import type { LocalUser } from '@/models/entities/User.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as fs from 'node:fs';
import * as Path from 'node:path';
import { fileURLToPath } from 'node:url';

View File

@@ -1,6 +1,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import type { KEYWORD } from 'color-convert/conversions.js';
@@ -8,8 +11,6 @@ import type { KEYWORD } from 'color-convert/conversions.js';
@Injectable()
export class LoggerService {
constructor(
@Inject(DI.config)
private config: Config,
) {
}

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
import * as Redis from 'ioredis';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import * as parse5 from 'parse5';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { ModerationLogsRepository } from '@/models/index.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { setImmediate } from 'node:timers/promises';
import * as mfm from 'mfm-js';
import { In, DataSource } from 'typeorm';
@@ -9,7 +14,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf
import { extractHashtags } from '@/misc/extract-hashtags.js';
import type { IMentionedRemoteUsers } from '@/models/entities/Note.js';
import { Note } from '@/models/entities/Note.js';
import type { ChannelFollowingsRepository, ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import type { ChannelsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { App } from '@/models/entities/App.js';
import { concat } from '@/misc/prelude/array.js';
@@ -177,9 +182,6 @@ export class NoteCreateService implements OnApplicationShutdown {
@Inject(DI.channelsRepository)
private channelsRepository: ChannelsRepository,
@Inject(DI.channelFollowingsRepository)
private channelFollowingsRepository: ChannelFollowingsRepository,
@Inject(DI.noteThreadMutingsRepository)
private noteThreadMutingsRepository: NoteThreadMutingsRepository,
@@ -362,7 +364,7 @@ export class NoteCreateService implements OnApplicationShutdown {
name: data.name,
text: data.text,
hasPoll: data.poll != null,
cw: data.cw == null ? null : data.cw,
cw: data.cw ?? null,
tags: tags.map(tag => normalizeForSearch(tag)),
emojis,
userId: user.id,
@@ -397,7 +399,7 @@ export class NoteCreateService implements OnApplicationShutdown {
const url = profile != null ? profile.url : null;
return {
uri: u.uri,
url: url == null ? undefined : url,
url: url ?? undefined,
username: u.username,
host: u.host,
} as IMentionedRemoteUsers[0];
@@ -574,7 +576,7 @@ export class NoteCreateService implements OnApplicationShutdown {
where: {
userId: data.reply.userId,
threadId: data.reply.threadId ?? data.reply.id,
}
},
});
if (!isThreadMuted) {

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Brackets, In } from 'typeorm';
import { Injectable, Inject } from '@nestjs/common';
import type { User, LocalUser, RemoteUser } from '@/models/entities/User.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { NotesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { setTimeout } from 'node:timers/promises';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { In } from 'typeorm';

View File

@@ -1,12 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { setTimeout } from 'node:timers/promises';
import * as Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { MutingsRepository, UserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import type { UsersRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js';
import type { Notification } from '@/models/entities/Notification.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { PushNotificationService } from '@/core/PushNotificationService.js';
@@ -25,14 +29,7 @@ export class NotificationService implements OnApplicationShutdown {
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
private notificationEntityService: NotificationEntityService,
private userEntityService: UserEntityService,
private idService: IdService,
private globalEventService: GlobalEventService,
private pushNotificationService: PushNotificationService,

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { NotesRepository, UsersRepository, PollsRepository, PollVotesRepository, User } from '@/models/index.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository } from '@/models/index.js';
import type { LocalUser } from '@/models/entities/User.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import push from 'web-push';
import * as Redis from 'ioredis';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Brackets, ObjectLiteral } from 'typeorm';
import { DI } from '@/di-symbols.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { setTimeout } from 'node:timers/promises';
import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
import * as Bull from 'bullmq';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import type { IActivity } from '@/core/activitypub/type.js';
@@ -108,7 +113,7 @@ export class QueueService {
removeOnFail: true,
};
await this.deliverQueue.addBulk(Array.from(inboxes.entries()).map(d => ({
await this.deliverQueue.addBulk(Array.from(inboxes.entries(), d => ({
name: d[0],
data: {
user,

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/index.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { IsNull } from 'typeorm';
import type { LocalUser, User } from '@/models/entities/User.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import type Logger from '@/logger.js';
import { LoggerService } from '@/core/LoggerService.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import chalk from 'chalk';
@@ -8,8 +13,9 @@ import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
import type { Config } from '@/config.js';
import type Logger from '@/logger.js';
import { UtilityService } from '@/core/UtilityService.js';
import { WebfingerService } from '@/core/WebfingerService.js';
import { ILink, WebfingerService } from '@/core/WebfingerService.js';
import { RemoteLoggerService } from '@/core/RemoteLoggerService.js';
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { bindThis } from '@/decorators.js';
@@ -27,6 +33,7 @@ export class RemoteUserResolveService {
private utilityService: UtilityService,
private webfingerService: WebfingerService,
private remoteLoggerService: RemoteLoggerService,
private apDbResolverService: ApDbResolverService,
private apPersonService: ApPersonService,
) {
this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user');
@@ -67,6 +74,22 @@ export class RemoteUserResolveService {
if (user == null) {
const self = await this.resolveSelf(acctLower);
if (self.href.startsWith(this.config.url)) {
const local = this.apDbResolverService.parseUri(self.href);
if (local.local && local.type === 'users') {
// the LR points to local
return (await this.apDbResolverService
.getUserFromApId(self.href)
.then((u) => {
if (u == null) {
throw new Error('local user not found');
} else {
return u;
}
})) as LocalUser;
}
}
this.logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`);
return await this.apPersonService.createPerson(self.href);
}
@@ -119,7 +142,7 @@ export class RemoteUserResolveService {
}
@bindThis
private async resolveSelf(acctLower: string) {
private async resolveSelf(acctLower: string): Promise<ILink> {
this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
const finger = await this.webfingerService.webfinger(acctLower).catch(err => {
this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`);

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { In } from 'typeorm';
@@ -220,14 +225,19 @@ export class RoleService implements OnApplicationShutdown {
}
@bindThis
public async getUserRoles(userId: User['id']) {
public async getUserAssigns(userId: User['id']) {
const now = Date.now();
let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
// 期限切れのロールを除外
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
const assignedRoleIds = assigns.map(x => x.roleId);
return assigns;
}
@bindThis
public async getUserRoles(userId: User['id']) {
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id));
const assigns = await this.getUserAssigns(userId);
const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id));
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula));
return [...assignedRoles, ...matchedCondRoles];

View File

@@ -1,12 +1,15 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { URL } from 'node:url';
import * as http from 'node:http';
import * as https from 'node:https';
import { Inject, Injectable } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { Upload } from '@aws-sdk/lib-storage';
import { NodeHttpHandler, NodeHttpHandlerOptions } from '@aws-sdk/node-http-handler';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type { Meta } from '@/models/entities/Meta.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
@@ -15,9 +18,6 @@ import type { DeleteObjectCommandInput, PutObjectCommandInput } from '@aws-sdk/c
@Injectable()
export class S3Service {
constructor(
@Inject(DI.config)
private config: Config,
private httpRequestService: HttpRequestService,
) {
}

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';

View File

@@ -1,10 +1,14 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { generateKeyPair } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import bcrypt from 'bcryptjs';
import { DataSource, IsNull } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { UsedUsernamesRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import { User } from '@/models/entities/User.js';
import { UserProfile } from '@/models/entities/UserProfile.js';
import { IdService } from '@/core/IdService.js';
@@ -23,9 +27,6 @@ export class SignupService {
@Inject(DI.db)
private db: DataSource,
@Inject(DI.config)
private config: Config,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -92,7 +93,7 @@ export class SignupService {
const keyPair = await new Promise<string[]>((res, rej) =>
generateKeyPair('rsa', {
modulusLength: 4096,
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem',

View File

@@ -1,8 +1,12 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as crypto from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import * as jsrsasign from 'jsrsasign';
import { DI } from '@/di-symbols.js';
import type { UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
@@ -110,9 +114,6 @@ export class TwoFactorAuthenticationService {
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
) {
}

View File

@@ -1,3 +1,7 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';

View File

@@ -1,5 +1,11 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { IsNull } from 'typeorm';
import type { LocalUser, PartialLocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { QueueService } from '@/core/QueueService.js';
@@ -21,9 +27,8 @@ import { UserBlockingService } from '@/core/UserBlockingService.js';
import { MetaService } from '@/core/MetaService.js';
import { CacheService } from '@/core/CacheService.js';
import type { Config } from '@/config.js';
import Logger from '../logger.js';
import { IsNull } from 'typeorm';
import { AccountMoveService } from '@/core/AccountMoveService.js';
import Logger from '../logger.js';
const logger = new Logger('following/create');
@@ -322,7 +327,7 @@ export class UserFollowingService implements OnModuleInit {
where: {
followerId: follower.id,
followeeId: followee.id,
}
},
});
if (following === null || !following.follower || !following.followee) {
@@ -412,8 +417,8 @@ export class UserFollowingService implements OnModuleInit {
followerId: user.id,
followee: {
movedToUri: IsNull(),
}
}
},
},
});
const nonMovedFollowers = await this.followingsRepository.count({
relations: {
@@ -423,8 +428,8 @@ export class UserFollowingService implements OnModuleInit {
followeeId: user.id,
follower: {
movedToUri: IsNull(),
}
}
},
},
});
await this.usersRepository.update(
{ id: user.id },
@@ -646,7 +651,7 @@ export class UserFollowingService implements OnModuleInit {
where: {
followeeId: followee.id,
followerId: follower.id,
}
},
});
if (!following || !following.followee || !following.follower) return;

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as Redis from 'ioredis';
import type { User } from '@/models/entities/User.js';

View File

@@ -1,10 +1,14 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import type { UserListJoiningsRepository, UsersRepository } from '@/models/index.js';
import type { UserListJoiningsRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js';
import type { UserList } from '@/models/entities/UserList.js';
import type { UserListJoining } from '@/models/entities/UserListJoining.js';
import { IdService } from '@/core/IdService.js';
import { UserFollowingService } from '@/core/UserFollowingService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -18,15 +22,11 @@ export class UserListService {
public static TooManyUsersError = class extends Error {};
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.userListJoiningsRepository)
private userListJoiningsRepository: UserListJoiningsRepository,
private userEntityService: UserEntityService,
private idService: IdService,
private userFollowingService: UserFollowingService,
private roleService: RoleService,
private globalEventService: GlobalEventService,
private proxyAccountService: ProxyAccountService,

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import type { MutingsRepository, Muting } from '@/models/index.js';

View File

@@ -1,11 +1,15 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Not, IsNull } from 'typeorm';
import type { FollowingsRepository, UsersRepository } from '@/models/index.js';
import type { FollowingsRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js';
import { QueueService } from '@/core/QueueService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
@@ -13,12 +17,6 @@ import { bindThis } from '@/decorators.js';
@Injectable()
export class UserSuspendService {
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { URL } from 'node:url';
import { toASCII } from 'punycode';
import { Inject, Injectable } from '@nestjs/common';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import FFmpeg from 'fluent-ffmpeg';
import { DI } from '@/di-symbols.js';
@@ -52,7 +57,7 @@ export class VideoProcessingService {
query({
thumbnail: '1',
url,
})
}),
);
}
}

View File

@@ -1,17 +1,20 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { Injectable } from '@nestjs/common';
import { query as urlQuery } from '@/misc/prelude/url.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
type ILink = {
export type ILink = {
href: string;
rel?: string;
};
type IWebFinger = {
export type IWebFinger = {
links: ILink[];
subject: string;
};
@@ -22,9 +25,6 @@ const mRegex = /^([^@]+)@(.*)/;
@Injectable()
export class WebfingerService {
constructor(
@Inject(DI.config)
private config: Config,
private httpRequestService: HttpRequestService,
) {
}

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import type { WebhooksRepository } from '@/models/index.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit';
import type { RemoteUser, User } from '@/models/entities/User.js';
@@ -95,7 +100,7 @@ export class ApAudienceService {
private isPublic(id: string): boolean {
return [
'https://www.w3.org/ns/activitystreams#Public',
'as#Public',
'as:Public',
'Public',
].includes(id);
}

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';

View File

@@ -1,8 +1,12 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { IsNull, Not } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { FollowingsRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import type { FollowingsRepository } from '@/models/index.js';
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
import { QueueService } from '@/core/QueueService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -147,12 +151,6 @@ class DeliverManager {
@Injectable()
export class ApDeliverManagerService {
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
@@ -13,11 +18,9 @@ import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js';
import { AppLockService } from '@/core/AppLockService.js';
import type Logger from '@/logger.js';
import { MetaService } from '@/core/MetaService.js';
import { AccountMoveService } from '@/core/AccountMoveService.js';
import { IdService } from '@/core/IdService.js';
import { StatusError } from '@/misc/status-error.js';
import { UtilityService } from '@/core/UtilityService.js';
import { CacheService } from '@/core/CacheService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { QueueService } from '@/core/QueueService.js';
@@ -78,8 +81,6 @@ export class ApInboxService {
private apNoteService: ApNoteService,
private apPersonService: ApPersonService,
private apQuestionService: ApQuestionService,
private accountMoveService: AccountMoveService,
private cacheService: CacheService,
private queueService: QueueService,
) {
this.logger = this.apLoggerService.logger;

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import type Logger from '@/logger.js';
import { RemoteLoggerService } from '@/core/RemoteLoggerService.js';

View File

@@ -1,7 +1,10 @@
import { Inject, Injectable } from '@nestjs/common';
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import * as mfm from 'mfm-js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { MfmService } from '@/core/MfmService.js';
import type { Note } from '@/models/entities/Note.js';
import { bindThis } from '@/decorators.js';
@@ -11,9 +14,6 @@ import type { IObject } from './type.js';
@Injectable()
export class ApMfmService {
constructor(
@Inject(DI.config)
private config: Config,
private mfmService: MfmService,
) {
}

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { createPublicKey, randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
@@ -18,7 +23,7 @@ import { MfmService } from '@/core/MfmService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import type { UserKeypair } from '@/models/entities/UserKeypair.js';
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, EmojisRepository, PollsRepository } from '@/models/index.js';
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository } from '@/models/index.js';
import { bindThis } from '@/decorators.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { isNotNull } from '@/misc/is-not-null.js';
@@ -44,9 +49,6 @@ export class ApRendererService {
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
@Inject(DI.pollsRepository)
private pollsRepository: PollsRepository,

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as crypto from 'node:crypto';
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
import { InstanceActorService } from '@/core/InstanceActorService.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as crypto from 'node:crypto';
import { Injectable } from '@nestjs/common';
import { HttpRequestService } from '@/core/HttpRequestService.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { JsonLd } from 'jsonld/jsonld-spec.js';
/* eslint:disable:quotemark indent */

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { DriveFilesRepository } from '@/models/index.js';

View File

@@ -1,22 +1,21 @@
import { Inject, Injectable } from '@nestjs/common';
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit';
import { DI } from '@/di-symbols.js';
import type { User } from '@/models/index.js';
import type { Config } from '@/config.js';
import { toArray, unique } from '@/misc/prelude/array.js';
import { bindThis } from '@/decorators.js';
import { isMention } from '../type.js';
import { ApResolverService, Resolver } from '../ApResolverService.js';
import { Resolver } from '../ApResolverService.js';
import { ApPersonService } from './ApPersonService.js';
import type { IObject, IApMention } from '../type.js';
@Injectable()
export class ApMentionService {
constructor(
@Inject(DI.config)
private config: Config,
private apResolverService: ApResolverService,
private apPersonService: ApPersonService,
) {
}

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit';
import { In } from 'typeorm';
@@ -195,7 +200,7 @@ export class ApNoteService {
// 引用
let quote: Note | undefined | null = null;
if (note._misskey_quote || note.quoteUrl) {
if (note._misskey_quote ?? note.quoteUrl) {
const tryResolveNote = async (uri: string): Promise<
| { status: 'ok'; res: Note }
| { status: 'permerror' | 'temperror' }

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit';
import { DataSource } from 'typeorm';
@@ -223,6 +228,23 @@ export class ApPersonService implements OnModuleInit {
return null;
}
private async resolveAvatarAndBanner(user: RemoteUser, icon: any, image: any): Promise<Pick<RemoteUser, 'avatarId' | 'bannerId' | 'avatarUrl' | 'bannerUrl' | 'avatarBlurhash' | 'bannerBlurhash'>> {
const [avatar, banner] = await Promise.all([icon, image].map(img => {
if (img == null) return null;
if (user == null) throw new Error('failed to create user: user is null');
return this.apImageService.resolveImage(user, img).catch(() => null);
}));
return {
avatarId: avatar?.id ?? null,
bannerId: banner?.id ?? null,
avatarUrl: avatar ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null,
bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null,
avatarBlurhash: avatar?.blurhash ?? null,
bannerBlurhash: banner?.blurhash ?? null,
};
}
/**
* Personを作成します。
*/
@@ -262,6 +284,16 @@ export class ApPersonService implements OnModuleInit {
// Create user
let user: RemoteUser | null = null;
//#region カスタム絵文字取得
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host)
.then(_emojis => _emojis.map(emoji => emoji.name))
.catch(err => {
this.logger.error('error occurred while fetching user emojis', { stack: err });
return [];
});
//#endregion
try {
// Start transaction
await this.db.transaction(async transactionalEntityManager => {
@@ -288,6 +320,7 @@ export class ApPersonService implements OnModuleInit {
tags,
isBot,
isCat: (person as any).isCat === true,
emojis,
})) as RemoteUser;
await transactionalEntityManager.save(new UserProfile({
@@ -324,6 +357,9 @@ export class ApPersonService implements OnModuleInit {
if (user == null) throw new Error('failed to create user: user is null');
// Register to the cache
this.cacheService.uriPersonCache.set(user.uri, user);
// Register host
this.federatedInstanceService.fetch(host).then(async i => {
this.instancesRepository.increment({ id: i.id }, 'usersCount', 1);
@@ -339,45 +375,16 @@ export class ApPersonService implements OnModuleInit {
this.hashtagService.updateUsertags(user, tags);
//#region アバターとヘッダー画像をフェッチ
const [avatar, banner] = await Promise.all([person.icon, person.image].map(img => {
if (img == null) return null;
if (user == null) throw new Error('failed to create user: user is null');
return this.apImageService.resolveImage(user, img).catch(() => null);
}));
try {
const updates = await this.resolveAvatarAndBanner(user, person.icon, person.image);
await this.usersRepository.update(user.id, updates);
user = { ...user, ...updates };
const avatarId = avatar?.id ?? null;
const bannerId = banner?.id ?? null;
const avatarUrl = avatar ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null;
const bannerUrl = banner ? this.driveFileEntityService.getPublicUrl(banner) : null;
const avatarBlurhash = avatar?.blurhash ?? null;
const bannerBlurhash = banner?.blurhash ?? null;
await this.usersRepository.update(user.id, {
avatarId,
bannerId,
avatarUrl,
bannerUrl,
avatarBlurhash,
bannerBlurhash,
});
user.avatarId = avatarId;
user.bannerId = bannerId;
user.avatarUrl = avatarUrl;
user.bannerUrl = bannerUrl;
user.avatarBlurhash = avatarBlurhash;
user.bannerBlurhash = bannerBlurhash;
//#endregion
//#region カスタム絵文字取得
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => {
this.logger.info(`extractEmojis: ${err}`);
return [];
});
const emojiNames = emojis.map(emoji => emoji.name);
await this.usersRepository.update(user.id, { emojis: emojiNames });
// Register to the cache
this.cacheService.uriPersonCache.set(user.uri, user);
} catch (err) {
this.logger.error('error occurred while fetching user avatar/banner', { stack: err });
}
//#endregion
await Promise.allSettled([
@@ -406,7 +413,7 @@ export class ApPersonService implements OnModuleInit {
if (uri.startsWith(`${this.config.url}/`)) return;
//#region このサーバーに既に登録されているか
const exist = await this.usersRepository.findOneBy({ uri }) as RemoteUser | null;
const exist = await this.fetchPerson(uri) as RemoteUser | null;
if (exist === null) return;
//#endregion
@@ -419,12 +426,6 @@ export class ApPersonService implements OnModuleInit {
this.logger.info(`Updating the Person: ${person.id}`);
// アバターとヘッダー画像をフェッチ
const [avatar, banner] = await Promise.all([person.icon, person.image].map(img => {
if (img == null) return null;
return this.apImageService.resolveImage(exist, img).catch(() => null);
}));
// カスタム絵文字取得
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => {
this.logger.info(`extractEmojis: ${e}`);
@@ -460,6 +461,7 @@ export class ApPersonService implements OnModuleInit {
movedToUri: person.movedTo ?? null,
alsoKnownAs: person.alsoKnownAs ?? null,
isExplorable: person.discoverable,
...(await this.resolveAvatarAndBanner(exist, person.icon, person.image).catch(() => ({}))),
} as Partial<RemoteUser> & Pick<RemoteUser, 'isBot' | 'isCat' | 'isLocked' | 'movedToUri' | 'alsoKnownAs' | 'isExplorable'>;
const moving = ((): boolean => {
@@ -482,18 +484,6 @@ export class ApPersonService implements OnModuleInit {
if (moving) updates.movedAt = new Date();
if (avatar) {
updates.avatarId = avatar.id;
updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar');
updates.avatarBlurhash = avatar.blurhash;
}
if (banner) {
updates.bannerId = banner.id;
updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner);
updates.bannerBlurhash = banner.blurhash;
}
// Update user
await this.usersRepository.update(exist.id, updates);

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { NotesRepository, PollsRepository } from '@/models/index.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export type IIcon = {
type: string;
mediaType?: string;

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export type IIdentifier = {
type: string;
name: string;

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { toArray } from '@/misc/prelude/array.js';
import { isHashtag } from '../type.js';
import type { IObject, IApHashtag } from '../type.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export type Obj = { [x: string]: any };
export type ApObject = IObject | string | (IObject | string)[];

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import type Logger from '@/logger.js';
import { LoggerService } from '@/core/LoggerService.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { AppLockService } from '@/core/AppLockService.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { AppLockService } from '@/core/AppLockService.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import type { DriveFile } from '@/models/entities/DriveFile.js';

View File

@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import Chart from '../../core.js';
export const name = 'activeUsers';

Some files were not shown because too many files have changed in this diff Show More