Merge remote-tracking branch 'mi-dev/master' into report
This commit is contained in:
commit
cb1586658e
567 changed files with 10281 additions and 3996 deletions
|
|
@ -11,7 +11,7 @@
|
|||
"decoratorMetadata": true
|
||||
},
|
||||
"experimental": {
|
||||
"keepImportAttributes": true
|
||||
"keepImportAssertions": true
|
||||
},
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
|
|
|
|||
37
packages/backend/migration/1702718871541-ffVisibility.js
Normal file
37
packages/backend/migration/1702718871541-ffVisibility.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class ffVisibility1702718871541 {
|
||||
constructor() {
|
||||
this.name = 'ffVisibility1702718871541';
|
||||
}
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_followingvisibility_enum" AS ENUM('public', 'followers', 'private')`);
|
||||
await queryRunner.query(`CREATE CAST ("public"."user_profile_ffvisibility_enum" AS "public"."user_profile_followingvisibility_enum") WITH INOUT AS ASSIGNMENT`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_followersVisibility_enum" AS ENUM('public', 'followers', 'private')`);
|
||||
await queryRunner.query(`CREATE CAST ("public"."user_profile_ffvisibility_enum" AS "public"."user_profile_followersVisibility_enum") WITH INOUT AS ASSIGNMENT`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "followingVisibility" "public"."user_profile_followingvisibility_enum" NOT NULL DEFAULT 'public'`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "followersVisibility" "public"."user_profile_followersVisibility_enum" NOT NULL DEFAULT 'public'`);
|
||||
await queryRunner.query(`UPDATE "user_profile" SET "followingVisibility" = "ffVisibility"`);
|
||||
await queryRunner.query(`UPDATE "user_profile" SET "followersVisibility" = "ffVisibility"`);
|
||||
await queryRunner.query(`DROP CAST ("public"."user_profile_ffvisibility_enum" AS "public"."user_profile_followersVisibility_enum")`);
|
||||
await queryRunner.query(`DROP CAST ("public"."user_profile_ffvisibility_enum" AS "public"."user_profile_followingvisibility_enum")`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "ffVisibility"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."user_profile_ffvisibility_enum"`);
|
||||
}
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_ffvisibility_enum" AS ENUM('public', 'followers', 'private')`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "ffVisibility" "public"."user_profile_ffvisibility_enum" NOT NULL DEFAULT 'public'`);
|
||||
|
||||
await queryRunner.query(`CREATE CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum") WITH INOUT AS ASSIGNMENT`);
|
||||
await queryRunner.query(`UPDATE "user_profile" SET "ffVisibility" = "followingVisibility"`);
|
||||
await queryRunner.query(`DROP CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum")`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followersVisibility"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followingVisibility"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."user_profile_followersVisibility_enum"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."user_profile_followingvisibility_enum"`);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class bannedEmailDomains1703209889304 {
|
||||
constructor() {
|
||||
this.name = 'bannedEmailDomains1703209889304';
|
||||
}
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "bannedEmailDomains" character varying(1024) array NOT NULL DEFAULT '{}'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "bannedEmailDomains"`);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.16.0"
|
||||
"node": ">=20.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./built/boot/entry.js",
|
||||
|
|
@ -16,7 +16,8 @@
|
|||
"watch:swc": "swc src -d built -D -w",
|
||||
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
|
||||
"watch": "node watch.mjs",
|
||||
"dev": "node ./built/boot/entry.js",
|
||||
"restart": "pnpm build && pnpm start",
|
||||
"dev": "nodemon -w src -e ts,js,mjs,cjs,json --exec \"cross-env NODE_ENV=development pnpm run restart\"",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"eslint": "eslint --quiet \"src/**/*.ts\"",
|
||||
"lint": "pnpm typecheck && pnpm eslint",
|
||||
|
|
@ -64,10 +65,10 @@
|
|||
"@bull-board/api": "5.10.2",
|
||||
"@bull-board/fastify": "5.10.2",
|
||||
"@bull-board/ui": "5.10.2",
|
||||
"@discordapp/twemoji": "14.1.2",
|
||||
"@discordapp/twemoji": "15.0.2",
|
||||
"@fastify/accepts": "4.3.0",
|
||||
"@fastify/cookie": "9.2.0",
|
||||
"@fastify/cors": "8.4.2",
|
||||
"@fastify/cors": "8.5.0",
|
||||
"@fastify/express": "2.3.0",
|
||||
"@fastify/http-proxy": "9.3.0",
|
||||
"@fastify/multipart": "8.0.0",
|
||||
|
|
@ -82,6 +83,7 @@
|
|||
"@smithy/node-http-handler": "2.1.10",
|
||||
"@swc/cli": "0.1.63",
|
||||
"@swc/core": "1.3.100",
|
||||
"@twemoji/parser": "15.0.0",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.12.0",
|
||||
"archiver": "6.0.1",
|
||||
|
|
@ -89,7 +91,7 @@
|
|||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "1.20.2",
|
||||
"bullmq": "4.15.2",
|
||||
"bullmq": "4.15.4",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.1",
|
||||
"chalk": "5.3.0",
|
||||
|
|
@ -106,7 +108,7 @@
|
|||
"file-type": "18.7.0",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"form-data": "4.0.0",
|
||||
"got": "13.0.0",
|
||||
"got": "14.0.0",
|
||||
"happy-dom": "10.0.3",
|
||||
"hpagent": "1.2.0",
|
||||
"http-link-header": "1.1.1",
|
||||
|
|
@ -120,8 +122,8 @@
|
|||
"jsonld": "8.3.2",
|
||||
"jsrsasign": "10.9.0",
|
||||
"meilisearch": "0.36.0",
|
||||
"mfm-js": "0.23.3",
|
||||
"microformats-parser": "1.5.2",
|
||||
"mfm-js": "0.24.0",
|
||||
"microformats-parser": "2.0.2",
|
||||
"mime-types": "2.1.35",
|
||||
"misskey-js": "workspace:*",
|
||||
"ms": "3.0.0-canary.1",
|
||||
|
|
@ -165,13 +167,12 @@
|
|||
"tmp": "0.2.1",
|
||||
"tsc-alias": "1.8.8",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"twemoji-parser": "14.0.0",
|
||||
"typeorm": "0.3.17",
|
||||
"typescript": "5.3.3",
|
||||
"ulid": "2.3.0",
|
||||
"vary": "1.1.2",
|
||||
"web-push": "3.6.6",
|
||||
"ws": "8.14.2",
|
||||
"ws": "8.15.1",
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -194,7 +195,7 @@
|
|||
"@types/jsrsasign": "10.5.12",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "20.10.4",
|
||||
"@types/node": "20.10.5",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/nodemailer": "6.4.14",
|
||||
"@types/oauth": "0.9.4",
|
||||
|
|
@ -217,15 +218,16 @@
|
|||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.3",
|
||||
"@types/ws": "8.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "6.13.2",
|
||||
"@typescript-eslint/parser": "6.13.2",
|
||||
"@typescript-eslint/eslint-plugin": "6.14.0",
|
||||
"@typescript-eslint/parser": "6.14.0",
|
||||
"aws-sdk-client-mock": "3.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.55.0",
|
||||
"eslint-plugin-import": "2.29.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"execa": "8.0.1",
|
||||
"jest": "29.7.0",
|
||||
"jest-mock": "29.7.0",
|
||||
"nodemon": "3.0.2",
|
||||
"simple-oauth2": "5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import { URLSearchParams } from 'node:url';
|
|||
import * as nodemailer from 'nodemailer';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { validate as validateEmail } from 'deep-email-validator';
|
||||
import { SubOutputFormat } from 'deep-email-validator/dist/output/output.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type Logger from '@/logger.js';
|
||||
|
|
@ -30,6 +30,7 @@ export class EmailService {
|
|||
|
||||
private metaService: MetaService,
|
||||
private loggerService: LoggerService,
|
||||
private utilityService: UtilityService,
|
||||
private httpRequestService: HttpRequestService,
|
||||
) {
|
||||
this.logger = this.loggerService.getLogger('email');
|
||||
|
|
@ -155,7 +156,7 @@ export class EmailService {
|
|||
@bindThis
|
||||
public async validateEmailForAccount(emailAddress: string): Promise<{
|
||||
available: boolean;
|
||||
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp';
|
||||
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned';
|
||||
}> {
|
||||
const meta = await this.metaService.fetch();
|
||||
|
||||
|
|
@ -164,32 +165,38 @@ export class EmailService {
|
|||
email: emailAddress,
|
||||
});
|
||||
|
||||
const verifymailApi = meta.enableVerifymailApi && meta.verifymailAuthKey != null;
|
||||
let validated;
|
||||
let validated: {
|
||||
valid: boolean,
|
||||
reason?: string | null,
|
||||
};
|
||||
|
||||
if (meta.enableActiveEmailValidation && meta.verifymailAuthKey) {
|
||||
if (verifymailApi) {
|
||||
if (meta.enableActiveEmailValidation) {
|
||||
if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) {
|
||||
validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey);
|
||||
} else {
|
||||
validated = meta.enableActiveEmailValidation ? await validateEmail({
|
||||
validated = await validateEmail({
|
||||
email: emailAddress,
|
||||
validateRegex: true,
|
||||
validateMx: true,
|
||||
validateTypo: false, // TLDを見ているみたいだけどclubとか弾かれるので
|
||||
validateDisposable: true, // 捨てアドかどうかチェック
|
||||
validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので
|
||||
}) : { valid: true, reason: null };
|
||||
});
|
||||
}
|
||||
} else {
|
||||
validated = { valid: true, reason: null };
|
||||
}
|
||||
|
||||
const available = exist === 0 && validated.valid;
|
||||
const emailDomain: string = emailAddress.split('@')[1];
|
||||
const isBanned = this.utilityService.isBlockedHost(meta.bannedEmailDomains, emailDomain);
|
||||
|
||||
const available = exist === 0 && validated.valid && !isBanned;
|
||||
|
||||
return {
|
||||
available,
|
||||
reason: available ? null :
|
||||
exist !== 0 ? 'used' :
|
||||
isBanned ? 'banned' :
|
||||
validated.reason === 'regex' ? 'format' :
|
||||
validated.reason === 'disposable' ? 'disposable' :
|
||||
validated.reason === 'mx' ? 'mx' :
|
||||
|
|
|
|||
|
|
@ -77,6 +77,17 @@ export class FeaturedService {
|
|||
return Array.from(ranking.keys());
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async removeFromRanking(name: string, windowRange: number, element: string): Promise<void> {
|
||||
const currentWindow = this.getCurrentWindow(windowRange);
|
||||
const previousWindow = currentWindow - 1;
|
||||
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
redisPipeline.zrem(`${name}:${currentWindow}`, element);
|
||||
redisPipeline.zrem(`${name}:${previousWindow}`, element);
|
||||
await redisPipeline.exec();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public updateGlobalNotesRanking(noteId: MiNote['id'], score = 1): Promise<void> {
|
||||
return this.updateRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
|
||||
|
|
@ -126,4 +137,9 @@ export class FeaturedService {
|
|||
public getHashtagsRanking(threshold: number): Promise<string[]> {
|
||||
return this.getRankingOf('featuredHashtagsRanking', HASHTAG_RANKING_WINDOW, threshold);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public removeHashtagsFromRanking(hashtag: string): Promise<void> {
|
||||
return this.removeFromRanking('featuredHashtagsRanking', HASHTAG_RANKING_WINDOW, hashtag);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|||
import { bindThis } from '@/decorators.js';
|
||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
|
||||
@Injectable()
|
||||
export class HashtagService {
|
||||
|
|
@ -29,6 +30,7 @@ export class HashtagService {
|
|||
private featuredService: FeaturedService,
|
||||
private idService: IdService,
|
||||
private metaService: MetaService,
|
||||
private utilityService: UtilityService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -161,6 +163,7 @@ export class HashtagService {
|
|||
const instance = await this.metaService.fetch();
|
||||
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
|
||||
if (hiddenTags.includes(hashtag)) return;
|
||||
if (this.utilityService.isSensitiveWordIncluded(hashtag, instance.sensitiveWords)) return;
|
||||
|
||||
// YYYYMMDDHHmm (10分間隔)
|
||||
const now = new Date();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { MiMeta } from '@/models/Meta.js';
|
|||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -25,6 +26,7 @@ export class MetaService implements OnApplicationShutdown {
|
|||
@Inject(DI.db)
|
||||
private db: DataSource,
|
||||
|
||||
private featuredService: FeaturedService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
//this.onMessage = this.onMessage.bind(this);
|
||||
|
|
@ -95,6 +97,8 @@ export class MetaService implements OnApplicationShutdown {
|
|||
|
||||
@bindThis
|
||||
public async update(data: Partial<MiMeta>): Promise<MiMeta> {
|
||||
let before: MiMeta | undefined;
|
||||
|
||||
const updated = await this.db.transaction(async transactionalEntityManager => {
|
||||
const metas = await transactionalEntityManager.find(MiMeta, {
|
||||
order: {
|
||||
|
|
@ -102,10 +106,10 @@ export class MetaService implements OnApplicationShutdown {
|
|||
},
|
||||
});
|
||||
|
||||
const meta = metas[0];
|
||||
before = metas[0];
|
||||
|
||||
if (meta) {
|
||||
await transactionalEntityManager.update(MiMeta, meta.id, data);
|
||||
if (before) {
|
||||
await transactionalEntityManager.update(MiMeta, before.id, data);
|
||||
|
||||
const metas = await transactionalEntityManager.find(MiMeta, {
|
||||
order: {
|
||||
|
|
@ -119,6 +123,21 @@ export class MetaService implements OnApplicationShutdown {
|
|||
}
|
||||
});
|
||||
|
||||
if (data.hiddenTags) {
|
||||
process.nextTick(() => {
|
||||
const hiddenTags = new Set<string>(data.hiddenTags);
|
||||
if (before) {
|
||||
for (const previousHiddenTag of before.hiddenTags) {
|
||||
hiddenTags.delete(previousHiddenTag);
|
||||
}
|
||||
}
|
||||
|
||||
for (const hiddenTag of hiddenTags) {
|
||||
this.featuredService.removeHashtagsFromRanking(hiddenTag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.globalEventService.publishInternalEvent('metaUpdated', updated);
|
||||
|
||||
return updated;
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
|
||||
if (data.visibility === 'public' && data.channel == null) {
|
||||
const sensitiveWords = meta.sensitiveWords;
|
||||
if (this.isSensitive(data, sensitiveWords)) {
|
||||
if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
|
||||
data.visibility = 'home';
|
||||
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
|
||||
data.visibility = 'home';
|
||||
|
|
@ -293,7 +293,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
// Check blocking
|
||||
if (data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)) {
|
||||
if (data.renote && !this.isQuote(data)) {
|
||||
if (data.renote.userHost === null) {
|
||||
if (data.renote.userId !== user.id) {
|
||||
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
|
||||
|
|
@ -622,7 +622,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
|
||||
// If it is renote
|
||||
if (data.renote) {
|
||||
const type = data.text ? 'quote' : 'renote';
|
||||
const type = this.isQuote(data) ? 'quote' : 'renote';
|
||||
|
||||
// Notify
|
||||
if (data.renote.userHost === null) {
|
||||
|
|
@ -705,28 +705,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private isSensitive(note: Option, sensitiveWord: string[]): boolean {
|
||||
if (sensitiveWord.length > 0) {
|
||||
const text = note.cw ?? note.text ?? '';
|
||||
if (text === '') return false;
|
||||
const matched = sensitiveWord.some(filter => {
|
||||
// represents RegExp
|
||||
const regexp = filter.match(/^\/(.+)\/(.*)$/);
|
||||
// This should never happen due to input sanitisation.
|
||||
if (!regexp) {
|
||||
const words = filter.split(' ');
|
||||
return words.every(keyword => text.includes(keyword));
|
||||
}
|
||||
try {
|
||||
return new RE2(regexp[1], regexp[2]).test(text);
|
||||
} catch (err) {
|
||||
// This should never happen due to input sanitisation.
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (matched) return true;
|
||||
}
|
||||
return false;
|
||||
private isQuote(note: Option): note is Option & { renote: MiNote } {
|
||||
// sync with misc/is-quote.ts
|
||||
return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
@ -794,7 +775,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
private async renderNoteOrRenoteActivity(data: Option, note: MiNote) {
|
||||
if (data.localOnly) return null;
|
||||
|
||||
const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)
|
||||
const content = data.renote && !this.isQuote(data)
|
||||
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
|
||||
: this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note);
|
||||
|
||||
|
|
@ -906,6 +887,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
// ダイレクトのとき、そのリストが対象外のユーザーの場合
|
||||
if (
|
||||
note.visibility === 'specified' &&
|
||||
note.userId !== userListMembership.userListUserId &&
|
||||
!note.visibleUserIds.some(v => v === userListMembership.userListUserId)
|
||||
) continue;
|
||||
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ export class ReactionService {
|
|||
reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
|
||||
|
||||
// センシティブ
|
||||
if ((note.reactionAcceptance === 'nonSensitiveOnly') && emoji.isSensitive) {
|
||||
if ((note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && emoji.isSensitive) {
|
||||
reaction = FALLBACK;
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,14 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import { In } from 'typeorm';
|
||||
import type { MiRole, MiRoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import type {
|
||||
MiRole,
|
||||
MiRoleAssignment,
|
||||
RoleAssignmentsRepository,
|
||||
RolesRepository,
|
||||
UsersRepository,
|
||||
} from '@/models/_.js';
|
||||
import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
|
@ -16,12 +23,13 @@ import { CacheService } from '@/core/CacheService.js';
|
|||
import type { RoleCondFormulaValue } from '@/models/Role.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
|
||||
|
||||
export type RolePolicies = {
|
||||
gtlAvailable: boolean;
|
||||
|
|
@ -47,6 +55,7 @@ export type RolePolicies = {
|
|||
userListLimit: number;
|
||||
userEachUserListsLimit: number;
|
||||
rateLimitFactor: number;
|
||||
avatarDecorationLimit: number;
|
||||
};
|
||||
|
||||
export const DEFAULT_POLICIES: RolePolicies = {
|
||||
|
|
@ -73,17 +82,21 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
|||
userListLimit: 10,
|
||||
userEachUserListsLimit: 50,
|
||||
rateLimitFactor: 1,
|
||||
avatarDecorationLimit: 1,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class RoleService implements OnApplicationShutdown {
|
||||
export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||
private rolesCache: MemorySingleCache<MiRole[]>;
|
||||
private roleAssignmentByUserIdCache: MemoryKVCache<MiRoleAssignment[]>;
|
||||
private notificationService: NotificationService;
|
||||
|
||||
public static AlreadyAssignedError = class extends Error {};
|
||||
public static NotAssignedError = class extends Error {};
|
||||
|
||||
constructor(
|
||||
private moduleRef: ModuleRef,
|
||||
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
|
|
@ -118,6 +131,10 @@ export class RoleService implements OnApplicationShutdown {
|
|||
this.redisForSub.on('message', this.onMessage);
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
this.notificationService = this.moduleRef.get(NotificationService.name);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async onMessage(_: string, data: string): Promise<void> {
|
||||
const obj = JSON.parse(data);
|
||||
|
|
@ -326,6 +343,7 @@ export class RoleService implements OnApplicationShutdown {
|
|||
userListLimit: calc('userListLimit', vs => Math.max(...vs)),
|
||||
userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)),
|
||||
rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)),
|
||||
avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -424,6 +442,12 @@ export class RoleService implements OnApplicationShutdown {
|
|||
|
||||
this.globalEventService.publishInternalEvent('userRoleAssigned', created);
|
||||
|
||||
if (role.isPublic) {
|
||||
this.notificationService.createNotification(userId, 'roleAssigned', {
|
||||
roleId: roleId,
|
||||
});
|
||||
}
|
||||
|
||||
if (moderator) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
||||
this.moderationLogService.log(moderator, 'assignRole', {
|
||||
|
|
|
|||
|
|
@ -3,30 +3,34 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import { Inject, Injectable, OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import type { UserListMembershipsRepository } from '@/models/_.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import type { MiUserList } from '@/models/UserList.js';
|
||||
import type { MiUserListMembership } from '@/models/UserListMembership.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { RedisKVCache } from '@/misc/cache.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
@Injectable()
|
||||
export class UserListService implements OnApplicationShutdown {
|
||||
export class UserListService implements OnApplicationShutdown, OnModuleInit {
|
||||
public static TooManyUsersError = class extends Error {};
|
||||
|
||||
public membersCache: RedisKVCache<Set<string>>;
|
||||
private roleService: RoleService;
|
||||
|
||||
constructor(
|
||||
private moduleRef: ModuleRef,
|
||||
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
|
|
@ -38,7 +42,6 @@ export class UserListService implements OnApplicationShutdown {
|
|||
|
||||
private userEntityService: UserEntityService,
|
||||
private idService: IdService,
|
||||
private roleService: RoleService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private proxyAccountService: ProxyAccountService,
|
||||
private queueService: QueueService,
|
||||
|
|
@ -54,6 +57,10 @@ export class UserListService implements OnApplicationShutdown {
|
|||
this.redisForSub.on('message', this.onMessage);
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
this.roleService = this.moduleRef.get(RoleService.name);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async onMessage(_: string, data: string): Promise<void> {
|
||||
const obj = JSON.parse(data);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { URL } from 'node:url';
|
||||
import { toASCII } from 'punycode';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import RE2 from 're2';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
|
@ -41,6 +42,33 @@ export class UtilityService {
|
|||
return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean {
|
||||
if (sensitiveWords.length === 0) return false;
|
||||
if (text === '') return false;
|
||||
|
||||
const regexpregexp = /^\/(.+)\/(.*)$/;
|
||||
|
||||
const matched = sensitiveWords.some(filter => {
|
||||
// represents RegExp
|
||||
const regexp = filter.match(regexpregexp);
|
||||
// This should never happen due to input sanitisation.
|
||||
if (!regexp) {
|
||||
const words = filter.split(' ');
|
||||
return words.every(keyword => text.includes(keyword));
|
||||
}
|
||||
try {
|
||||
// TODO: RE2インスタンスをキャッシュ
|
||||
return new RE2(regexp[1], regexp[2]).test(text);
|
||||
} catch (err) {
|
||||
// This should never happen due to input sanitisation.
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public extractDbHost(uri: string): string {
|
||||
const url = new URL(uri);
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ import type { Packed } from '@/misc/json-schema.js';
|
|||
import { bindThis } from '@/decorators.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { FilterUnionByProperty, notificationTypes } from '@/types.js';
|
||||
import { RoleEntityService } from './RoleEntityService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
||||
import type { UserEntityService } from './UserEntityService.js';
|
||||
import type { NoteEntityService } from './NoteEntityService.js';
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ const NOTE_REQUIRED_GROUPED_NOTIFICATION_TYPES = new Set(['note', 'mention', 're
|
|||
export class NotificationEntityService implements OnModuleInit {
|
||||
private userEntityService: UserEntityService;
|
||||
private noteEntityService: NoteEntityService;
|
||||
private customEmojiService: CustomEmojiService;
|
||||
private roleEntityService: RoleEntityService;
|
||||
|
||||
constructor(
|
||||
private moduleRef: ModuleRef,
|
||||
|
|
@ -43,14 +43,13 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
|
||||
//private userEntityService: UserEntityService,
|
||||
//private noteEntityService: NoteEntityService,
|
||||
//private customEmojiService: CustomEmojiService,
|
||||
) {
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
this.userEntityService = this.moduleRef.get('UserEntityService');
|
||||
this.noteEntityService = this.moduleRef.get('NoteEntityService');
|
||||
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
|
||||
this.roleEntityService = this.moduleRef.get('RoleEntityService');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
@ -81,6 +80,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
detail: false,
|
||||
})
|
||||
) : undefined;
|
||||
const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined;
|
||||
|
||||
return await awaitAll({
|
||||
id: notification.id,
|
||||
|
|
@ -92,6 +92,9 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
...(notification.type === 'reaction' ? {
|
||||
reaction: notification.reaction,
|
||||
} : {}),
|
||||
...(notification.type === 'roleAssigned' ? {
|
||||
role: role,
|
||||
} : {}),
|
||||
...(notification.type === 'achievementEarned' ? {
|
||||
achievement: notification.achievement,
|
||||
} : {}),
|
||||
|
|
@ -216,6 +219,8 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
});
|
||||
}
|
||||
|
||||
const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined;
|
||||
|
||||
return await awaitAll({
|
||||
id: notification.id,
|
||||
createdAt: new Date(notification.createdAt).toISOString(),
|
||||
|
|
@ -226,6 +231,9 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
...(notification.type === 'reaction' ? {
|
||||
reaction: notification.reaction,
|
||||
} : {}),
|
||||
...(notification.type === 'roleAssigned' ? {
|
||||
role: role,
|
||||
} : {}),
|
||||
...(notification.type === 'achievementEarned' ? {
|
||||
achievement: notification.achievement,
|
||||
} : {}),
|
||||
|
|
|
|||
|
|
@ -332,13 +332,13 @@ export class UserEntityService implements OnModuleInit {
|
|||
const profile = opts.detail ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null;
|
||||
|
||||
const followingCount = profile == null ? null :
|
||||
(profile.ffVisibility === 'public') || isMe ? user.followingCount :
|
||||
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
|
||||
(profile.followingVisibility === 'public') || isMe ? user.followingCount :
|
||||
(profile.followingVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
|
||||
null;
|
||||
|
||||
const followersCount = profile == null ? null :
|
||||
(profile.ffVisibility === 'public') || isMe ? user.followersCount :
|
||||
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
|
||||
(profile.followersVisibility === 'public') || isMe ? user.followersCount :
|
||||
(profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
|
||||
null;
|
||||
|
||||
const isModerator = isMe && opts.detail ? this.roleService.isModerator(user) : null;
|
||||
|
|
@ -362,6 +362,8 @@ export class UserEntityService implements OnModuleInit {
|
|||
id: ud.id,
|
||||
angle: ud.angle || undefined,
|
||||
flipH: ud.flipH || undefined,
|
||||
offsetX: ud.offsetX || undefined,
|
||||
offsetY: ud.offsetY || undefined,
|
||||
url: decorations.find(d => d.id === ud.id)!.url,
|
||||
}))) : [],
|
||||
isBot: user.isBot,
|
||||
|
|
@ -415,7 +417,8 @@ export class UserEntityService implements OnModuleInit {
|
|||
pinnedPageId: profile!.pinnedPageId,
|
||||
pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null,
|
||||
publicReactions: profile!.publicReactions,
|
||||
ffVisibility: profile!.ffVisibility,
|
||||
followersVisibility: profile!.followersVisibility,
|
||||
followingVisibility: profile!.followingVisibility,
|
||||
twoFactorEnabled: profile!.twoFactorEnabled,
|
||||
usePasswordLessLogin: profile!.usePasswordLessLogin,
|
||||
securityKeys: profile!.twoFactorEnabled
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const kinds = [
|
||||
'read:account',
|
||||
'write:account',
|
||||
'read:blocks',
|
||||
'write:blocks',
|
||||
'read:drive',
|
||||
'write:drive',
|
||||
'read:favorites',
|
||||
'write:favorites',
|
||||
'read:following',
|
||||
'write:following',
|
||||
'read:messaging',
|
||||
'write:messaging',
|
||||
'read:mutes',
|
||||
'write:mutes',
|
||||
'write:notes',
|
||||
'read:notifications',
|
||||
'write:notifications',
|
||||
'read:reactions',
|
||||
'write:reactions',
|
||||
'write:votes',
|
||||
'read:pages',
|
||||
'write:pages',
|
||||
'write:page-likes',
|
||||
'read:page-likes',
|
||||
'read:user-groups',
|
||||
'write:user-groups',
|
||||
'read:channels',
|
||||
'write:channels',
|
||||
'read:gallery',
|
||||
'write:gallery',
|
||||
'read:gallery-likes',
|
||||
'write:gallery-likes',
|
||||
];
|
||||
// IF YOU ADD KINDS(PERMISSIONS), YOU MUST ADD TRANSLATIONS (under _permissions).
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -7,5 +7,6 @@ import type { MiNote } from '@/models/Note.js';
|
|||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function(note: MiNote): boolean {
|
||||
// sync with NoteCreateService.isQuote
|
||||
return note.renoteId != null && (note.text != null || note.hasPoll || (note.fileIds != null && note.fileIds.length > 0));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import { packedFlashSchema } from '@/models/json-schema/flash.js';
|
|||
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
|
||||
import { packedSigninSchema } from '@/models/json-schema/signin.js';
|
||||
import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js';
|
||||
import { packedAdSchema } from '@/models/json-schema/ad.js';
|
||||
|
||||
export const refs = {
|
||||
UserLite: packedUserLiteSchema,
|
||||
|
|
@ -49,6 +50,7 @@ export const refs = {
|
|||
User: packedUserSchema,
|
||||
|
||||
UserList: packedUserListSchema,
|
||||
Ad: packedAdSchema,
|
||||
Announcement: packedAnnouncementSchema,
|
||||
App: packedAppSchema,
|
||||
Note: packedNoteSchema,
|
||||
|
|
|
|||
|
|
@ -495,6 +495,13 @@ export class MiMeta {
|
|||
})
|
||||
public manifestJsonOverride: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
array: true,
|
||||
default: '{}',
|
||||
})
|
||||
public bannedEmailDomains: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { notificationTypes } from '@/types.js';
|
||||
import { MiUser } from './User.js';
|
||||
import { MiNote } from './Note.js';
|
||||
import { MiFollowRequest } from './FollowRequest.js';
|
||||
import { MiAccessToken } from './AccessToken.js';
|
||||
import { MiRole } from './Role.js';
|
||||
|
||||
export type MiNotification = {
|
||||
type: 'note';
|
||||
|
|
@ -68,6 +67,11 @@ export type MiNotification = {
|
|||
id: string;
|
||||
createdAt: string;
|
||||
notifierId: MiUser['id'];
|
||||
} | {
|
||||
type: 'roleAssigned';
|
||||
id: string;
|
||||
createdAt: string;
|
||||
roleId: MiRole['id'];
|
||||
} | {
|
||||
type: 'achievementEarned';
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -143,8 +143,10 @@ export class MiUser {
|
|||
})
|
||||
public avatarDecorations: {
|
||||
id: string;
|
||||
angle: number;
|
||||
flipH: boolean;
|
||||
angle?: number;
|
||||
flipH?: boolean;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
}[];
|
||||
|
||||
@Index()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
|
||||
import { obsoleteNotificationTypes, ffVisibility, notificationTypes } from '@/types.js';
|
||||
import { obsoleteNotificationTypes, followingVisibilities, followersVisibilities, notificationTypes } from '@/types.js';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
import { MiPage } from './Page.js';
|
||||
|
|
@ -94,10 +94,16 @@ export class MiUserProfile {
|
|||
public publicReactions: boolean;
|
||||
|
||||
@Column('enum', {
|
||||
enum: ffVisibility,
|
||||
enum: followingVisibilities,
|
||||
default: 'public',
|
||||
})
|
||||
public ffVisibility: typeof ffVisibility[number];
|
||||
public followingVisibility: typeof followingVisibilities[number];
|
||||
|
||||
@Column('enum', {
|
||||
enum: followersVisibilities,
|
||||
default: 'public',
|
||||
})
|
||||
public followersVisibility: typeof followersVisibilities[number];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
|
|
|
|||
64
packages/backend/src/models/json-schema/ad.ts
Normal file
64
packages/backend/src/models/json-schema/ad.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const packedAdSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
format: 'id',
|
||||
example: 'xxxxxxxxxx',
|
||||
},
|
||||
expiresAt: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
startsAt: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
place: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
priority: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
ratio: {
|
||||
type: 'number',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
imageUrl: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
memo: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
dayOfWeek: {
|
||||
type: 'integer',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
@ -145,6 +145,7 @@ export const packedRoleSchema = {
|
|||
userEachUserListsLimit: rolePolicyValue,
|
||||
canManageAvatarDecorations: rolePolicyValue,
|
||||
canUseTranslator: rolePolicyValue,
|
||||
avatarDecorationLimit: rolePolicyValue,
|
||||
},
|
||||
},
|
||||
usersCount: {
|
||||
|
|
|
|||
|
|
@ -74,6 +74,14 @@ export const packedUserLiteSchema = {
|
|||
format: 'url',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
offsetX: {
|
||||
type: 'number',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
offsetY: {
|
||||
type: 'number',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -303,7 +311,12 @@ export const packedUserDetailedNotMeOnlySchema = {
|
|||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
ffVisibility: {
|
||||
followingVisibility: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: ['public', 'followers', 'private'],
|
||||
},
|
||||
followersVisibility: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: ['public', 'followers', 'private'],
|
||||
|
|
@ -541,9 +554,7 @@ export const packedMeDetailedOnlySchema = {
|
|||
mention: notificationRecieveConfig,
|
||||
reaction: notificationRecieveConfig,
|
||||
pollEnded: notificationRecieveConfig,
|
||||
achievementEarned: notificationRecieveConfig,
|
||||
receiveFollowRequest: notificationRecieveConfig,
|
||||
followRequestAccepted: notificationRecieveConfig,
|
||||
},
|
||||
},
|
||||
emailNotificationTypes: {
|
||||
|
|
@ -672,6 +683,10 @@ export const packedMeDetailedOnlySchema = {
|
|||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
avatarDecorationLimit: {
|
||||
type: 'number',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
//#region secrets
|
||||
|
|
|
|||
|
|
@ -153,8 +153,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
this.systemQueueWorker
|
||||
.on('active', (job) => systemLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => systemLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => systemLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('failed', (job, err) => systemLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => systemLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => systemLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
|
|
@ -191,8 +191,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
this.dbQueueWorker
|
||||
.on('active', (job) => dbLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => dbLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => dbLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('failed', (job, err) => dbLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => dbLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => dbLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
|
|
@ -215,8 +215,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
this.deliverQueueWorker
|
||||
.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
|
||||
.on('error', (err: Error) => deliverLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('failed', (job, err) => deliverLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
|
||||
.on('error', (err: Error) => deliverLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => deliverLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
|
|
@ -226,7 +226,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
autorun: false,
|
||||
concurrency: this.config.inboxJobConcurrency ?? 16,
|
||||
limiter: {
|
||||
max: this.config.inboxJobPerSec ?? 16,
|
||||
max: this.config.inboxJobPerSec ?? 32,
|
||||
duration: 1000,
|
||||
},
|
||||
settings: {
|
||||
|
|
@ -239,8 +239,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
this.inboxQueueWorker
|
||||
.on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
|
||||
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
|
||||
.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => inboxLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('failed', (job, err) => inboxLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => inboxLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => inboxLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
|
|
@ -263,8 +263,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
this.webhookDeliverQueueWorker
|
||||
.on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
|
||||
.on('error', (err: Error) => webhookLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('failed', (job, err) => webhookLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
|
||||
.on('error', (err: Error) => webhookLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => webhookLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
|
|
@ -292,8 +292,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
this.relationshipQueueWorker
|
||||
.on('active', (job) => relationshipLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => relationshipLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('failed', (job, err) => relationshipLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => relationshipLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => relationshipLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
|
|
@ -315,8 +315,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
this.objectStorageQueueWorker
|
||||
.on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => objectStorageLogger.error(`error ${err}`, { e: renderError(err) }))
|
||||
.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => objectStorageLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => objectStorageLogger.warn(`stalled id=${jobId}`));
|
||||
//#endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ export class ActivityPubServerService {
|
|||
return;
|
||||
}
|
||||
|
||||
const algo = match[1];
|
||||
const algo = match[1].toUpperCase();
|
||||
const digestValue = match[2];
|
||||
|
||||
if (algo !== 'SHA-256') {
|
||||
|
|
@ -195,11 +195,11 @@ export class ActivityPubServerService {
|
|||
//#region Check ff visibility
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
if (profile.ffVisibility === 'private') {
|
||||
if (profile.followersVisibility === 'private') {
|
||||
reply.code(403);
|
||||
reply.header('Cache-Control', 'public, max-age=30');
|
||||
return;
|
||||
} else if (profile.ffVisibility === 'followers') {
|
||||
} else if (profile.followersVisibility === 'followers') {
|
||||
reply.code(403);
|
||||
reply.header('Cache-Control', 'public, max-age=30');
|
||||
return;
|
||||
|
|
@ -287,11 +287,11 @@ export class ActivityPubServerService {
|
|||
//#region Check ff visibility
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
if (profile.ffVisibility === 'private') {
|
||||
if (profile.followingVisibility === 'private') {
|
||||
reply.code(403);
|
||||
reply.header('Cache-Control', 'public, max-age=30');
|
||||
return;
|
||||
} else if (profile.ffVisibility === 'followers') {
|
||||
} else if (profile.followingVisibility === 'followers') {
|
||||
reply.code(403);
|
||||
reply.header('Cache-Control', 'public, max-age=30');
|
||||
return;
|
||||
|
|
@ -493,8 +493,7 @@ export class ActivityPubServerService {
|
|||
|
||||
@bindThis
|
||||
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||
// addConstraintStrategy の型定義がおかしいため
|
||||
(fastify.addConstraintStrategy as any)({
|
||||
fastify.addConstraintStrategy({
|
||||
name: 'apOrHtml',
|
||||
storage() {
|
||||
const store = {} as any;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ export class FileServerService {
|
|||
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
reply.header('Content-Security-Policy', 'default-src \'none\'; img-src \'self\'; media-src \'self\'; style-src \'unsafe-inline\'');
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
reply.header('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,8 @@ export class ServerService implements OnApplicationShutdown {
|
|||
fastify.register(this.activityPubServerService.createServer);
|
||||
fastify.register(this.nodeinfoServerService.createServer);
|
||||
fastify.register(this.wellKnownServerService.createServer);
|
||||
fastify.register(this.oauth2ProviderService.createServer);
|
||||
fastify.register(this.oauth2ProviderService.createServer, { prefix: '/oauth' });
|
||||
fastify.register(this.oauth2ProviderService.createTokenServer, { prefix: '/oauth/token' });
|
||||
|
||||
fastify.get<{ Params: { path: string }; Querystring: { static?: any; badge?: any; }; }>('/emoji/:path(.*)', async (request, reply) => {
|
||||
const path = request.params.path;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import * as Acct from '@/misc/acct.js';
|
|||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { NodeinfoServerService } from './NodeinfoServerService.js';
|
||||
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
|
||||
import type { FindOptionsWhere } from 'typeorm';
|
||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ export class WellKnownServerService {
|
|||
|
||||
private nodeinfoServerService: NodeinfoServerService,
|
||||
private userEntityService: UserEntityService,
|
||||
private oauth2ProviderService: OAuth2ProviderService,
|
||||
) {
|
||||
//this.createServer = this.createServer.bind(this);
|
||||
}
|
||||
|
|
@ -87,6 +89,10 @@ export class WellKnownServerService {
|
|||
return { links: this.nodeinfoServerService.getLinks() };
|
||||
});
|
||||
|
||||
fastify.get('/.well-known/oauth-authorization-server', async () => {
|
||||
return this.oauth2ProviderService.generateRFC8414();
|
||||
});
|
||||
|
||||
/* TODO
|
||||
fastify.get('/.well-known/change-password', async (request, reply) => {
|
||||
});
|
||||
|
|
|
|||
|
|
@ -330,7 +330,8 @@ export class ApiCallService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
if (token && ep.meta.kind && !token.permission.some(p => p === ep.meta.kind)) {
|
||||
if (token && ((ep.meta.kind && !token.permission.some(p => p === ep.meta.kind))
|
||||
|| (!ep.meta.kind && (ep.meta.requireCredential || ep.meta.requireModerator || ep.meta.requireAdmin)))) {
|
||||
throw new ApiError({
|
||||
message: 'Your app does not have the necessary permissions to use this endpoint.',
|
||||
code: 'PERMISSION_DENIED',
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@ export class StreamingApiServerService {
|
|||
|
||||
try {
|
||||
[user, app] = await this.authenticateService.authenticate(token);
|
||||
|
||||
if (app !== null && !app.permission.some(p => p === 'read:account')) {
|
||||
throw new AuthenticationError('Your app does not have necessary permissions to use websocket API.');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof AuthenticationError) {
|
||||
socket.write([
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
import type { Schema } from '@/misc/json-schema.js';
|
||||
import { permissions } from 'misskey-js';
|
||||
import { RolePolicies } from '@/core/RoleService.js';
|
||||
|
||||
import * as ep___admin_meta from './endpoints/admin/meta.js';
|
||||
|
|
@ -724,7 +725,7 @@ const eps = [
|
|||
['retention', ep___retention],
|
||||
];
|
||||
|
||||
export interface IEndpointMeta {
|
||||
interface IEndpointMetaBase {
|
||||
readonly stability?: 'deprecated' | 'experimental' | 'stable';
|
||||
|
||||
readonly tags?: ReadonlyArray<string>;
|
||||
|
|
@ -823,6 +824,23 @@ export interface IEndpointMeta {
|
|||
readonly cacheSec?: number;
|
||||
}
|
||||
|
||||
export type IEndpointMeta = (Omit<IEndpointMetaBase, 'requireCrential' | 'requireModerator' | 'requireAdmin'> & {
|
||||
requireCredential?: false,
|
||||
requireAdmin?: false,
|
||||
requireModerator?: false,
|
||||
}) | (Omit<IEndpointMetaBase, 'secure'> & {
|
||||
secure: true,
|
||||
}) | (Omit<IEndpointMetaBase, 'requireCredential' | 'kind'> & {
|
||||
requireCredential: true,
|
||||
kind: (typeof permissions)[number],
|
||||
}) | (Omit<IEndpointMetaBase, 'requireModerator' | 'kind'> & {
|
||||
requireModerator: true,
|
||||
kind: (typeof permissions)[number],
|
||||
}) | (Omit<IEndpointMetaBase, 'requireAdmin' | 'kind'> & {
|
||||
requireAdmin: true,
|
||||
kind: (typeof permissions)[number],
|
||||
})
|
||||
|
||||
export interface IEndpoint {
|
||||
name: string;
|
||||
meta: IEndpointMeta;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:abuse-user-reports',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private userEntityService: UserEntityService,
|
||||
private signupService: SignupService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, _me) => {
|
||||
super(meta, paramDef, async (ps, _me, token) => {
|
||||
const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
|
||||
const noUsers = (await this.usersRepository.countBy({
|
||||
host: IsNull(),
|
||||
})) === 0;
|
||||
if (!noUsers && !me?.isRoot) throw new Error('access denied');
|
||||
if ((!noUsers && !me?.isRoot) || token !== null) throw new Error('access denied');
|
||||
|
||||
const { account, secret } = await this.signupService.signup({
|
||||
username: ps.username,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'write:admin:account',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'read:admin:account',
|
||||
|
||||
errors: {
|
||||
userNotFound: {
|
||||
|
|
@ -23,6 +24,11 @@ export const meta = {
|
|||
id: 'cb865949-8af5-4062-a88c-ef55e8786d1d',
|
||||
},
|
||||
},
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'User',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,13 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:ad',
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: 'Ad',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
@ -61,7 +68,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
ad: ad,
|
||||
});
|
||||
|
||||
return ad;
|
||||
return {
|
||||
id: ad.id,
|
||||
expiresAt: ad.expiresAt.toISOString(),
|
||||
startsAt: ad.startsAt.toISOString(),
|
||||
dayOfWeek: ad.dayOfWeek,
|
||||
url: ad.url,
|
||||
imageUrl: ad.imageUrl,
|
||||
priority: ad.priority,
|
||||
ratio: ad.ratio,
|
||||
place: ad.place,
|
||||
memo: ad.memo,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:ad',
|
||||
|
||||
errors: {
|
||||
noSuchAd: {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,18 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:ad',
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: 'Ad',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
@ -44,7 +56,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
const ads = await query.limit(ps.limit).getMany();
|
||||
|
||||
return ads;
|
||||
return ads.map(ad => ({
|
||||
id: ad.id,
|
||||
expiresAt: ad.expiresAt.toISOString(),
|
||||
startsAt: ad.startsAt.toISOString(),
|
||||
dayOfWeek: ad.dayOfWeek,
|
||||
url: ad.url,
|
||||
imageUrl: ad.imageUrl,
|
||||
memo: ad.memo,
|
||||
place: ad.place,
|
||||
priority: ad.priority,
|
||||
ratio: ad.ratio,
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:ad',
|
||||
|
||||
errors: {
|
||||
noSuchAd: {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:announcements',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:announcements',
|
||||
|
||||
errors: {
|
||||
noSuchAnnouncement: {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:announcements',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:announcements',
|
||||
|
||||
errors: {
|
||||
noSuchAnnouncement: {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'write:admin:avatar-decorations',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'write:admin:avatar-decorations',
|
||||
errors: {
|
||||
},
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'read:admin:avatar-decorations',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'write:admin:avatar-decorations',
|
||||
|
||||
errors: {
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'write:admin:delete-account',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'write:admin:delete-all-files-of-a-user',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:drive',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:drive',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:drive',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:drive',
|
||||
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
|
|
@ -29,6 +30,8 @@ export const meta = {
|
|||
id: 'f7a3462c-4e6e-4069-8421-b9bd4f4c3975',
|
||||
},
|
||||
},
|
||||
|
||||
ref: 'EmojiDetailed',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
|
||||
errors: {
|
||||
noSuchEmoji: {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
|
||||
errors: {
|
||||
noSuchEmoji: {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'read:admin:emoji',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'read:admin:emoji',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
|
||||
errors: {
|
||||
noSuchEmoji: {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:federation',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:federation',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:federation',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:federation',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,19 @@ import { DI } from '@/di-symbols.js';
|
|||
export const meta = {
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'read:admin:index-stats',
|
||||
|
||||
tags: ['admin'],
|
||||
res: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
tablename: { type: 'string' },
|
||||
indexname: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { DI } from '@/di-symbols.js';
|
|||
export const meta = {
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'read:admin:table-stats',
|
||||
|
||||
tags: ['admin'],
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,26 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:user-ips',
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
properties: {
|
||||
ip: { type: 'string' },
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
optional: false,
|
||||
nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:invite-codes',
|
||||
|
||||
errors: {
|
||||
invalidDateTime: {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:invite-codes',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'read:admin:meta',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
|
|
@ -143,6 +144,14 @@ export const meta = {
|
|||
type: 'string',
|
||||
},
|
||||
},
|
||||
bannedEmailDomains: {
|
||||
type: 'array',
|
||||
optional: true, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
preservedUsernames: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
|
|
@ -371,6 +380,10 @@ export const meta = {
|
|||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
shortName: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
objectStorageS3ForcePathStyle: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
|
@ -511,6 +524,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
|
||||
enableServerMachineStats: instance.enableServerMachineStats,
|
||||
enableIdenticonGeneration: instance.enableIdenticonGeneration,
|
||||
bannedEmailDomains: instance.bannedEmailDomains,
|
||||
policies: { ...DEFAULT_POLICIES, ...instance.policies },
|
||||
manifestJsonOverride: instance.manifestJsonOverride,
|
||||
enableFanoutTimeline: instance.enableFanoutTimeline,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:promo',
|
||||
|
||||
errors: {
|
||||
noSuchNote: {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:queue',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:queue',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:queue',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:queue',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:emoji',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:relays',
|
||||
|
||||
errors: {
|
||||
invalidUrl: {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:relays',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:relays',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:reset-password',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:resolve-abuse-user-report',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:roles',
|
||||
|
||||
errors: {
|
||||
noSuchRole: {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'write:admin:roles',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'write:admin:roles',
|
||||
|
||||
errors: {
|
||||
noSuchRole: {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:roles',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:roles',
|
||||
|
||||
errors: {
|
||||
noSuchRole: {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:roles',
|
||||
|
||||
errors: {
|
||||
noSuchRole: {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'write:admin:roles',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'write:admin:roles',
|
||||
|
||||
errors: {
|
||||
noSuchRole: {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export const meta = {
|
|||
|
||||
requireCredential: false,
|
||||
requireAdmin: true,
|
||||
kind: 'read:admin:roles',
|
||||
|
||||
errors: {
|
||||
noSuchRole: {
|
||||
|
|
@ -26,6 +27,20 @@ export const meta = {
|
|||
id: '224eff5e-2488-4b18-b3e7-f50d94421648',
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', format: 'misskey:id' },
|
||||
createdAt: { type: 'string', format: 'date-time' },
|
||||
user: { ref: 'UserDetailed' },
|
||||
expiresAt: { type: 'string', format: 'date-time', nullable: true },
|
||||
},
|
||||
required: ['id', 'createdAt', 'user'],
|
||||
},
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
@ -78,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
id: assign.id,
|
||||
createdAt: this.idService.parse(assign.id).date.toISOString(),
|
||||
user: await this.userEntityService.pack(assign.user!, me, { detail: true }),
|
||||
expiresAt: assign.expiresAt,
|
||||
expiresAt: assign.expiresAt?.toISOString() ?? null,
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const meta = {
|
|||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:send-email',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { DI } from '@/di-symbols.js';
|
|||
export const meta = {
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'read:admin:server-info',
|
||||
|
||||
tags: ['admin', 'meta'],
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue