Fixed 267U.pre2

This commit is contained in:
2026-07-26 18:25:37 +09:00
parent 50bfaeafdf
commit 317d00a284
1286 changed files with 80222 additions and 1 deletions
@@ -0,0 +1,158 @@
import define from "../../define.js";
import { AbuseUserReports } from "../../../../models/index.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
nullable: false,
optional: false,
format: "id",
example: "xxxxxxxxxx"
},
createdAt: {
type: "string",
nullable: false,
optional: false,
format: "date-time"
},
comment: {
type: "string",
nullable: false,
optional: false
},
resolved: {
type: "boolean",
nullable: false,
optional: false,
example: false
},
reporterId: {
type: "string",
nullable: false,
optional: false,
format: "id"
},
targetUserId: {
type: "string",
nullable: false,
optional: false,
format: "id"
},
assigneeId: {
type: "string",
nullable: true,
optional: false,
format: "id"
},
reporter: {
type: "object",
nullable: false,
optional: false,
ref: "User"
},
targetUser: {
type: "object",
nullable: false,
optional: false,
ref: "User"
},
assignee: {
type: "object",
nullable: true,
optional: true,
ref: "User"
}
}
}
}
};
export const paramDef = {
type: "object",
properties: {
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 10
},
sinceId: {
type: "string",
format: "misskey:id"
},
untilId: {
type: "string",
format: "misskey:id"
},
state: {
type: "string",
nullable: true,
default: null
},
reporterOrigin: {
type: "string",
enum: [
"combined",
"local",
"remote"
],
default: "combined"
},
targetUserOrigin: {
type: "string",
enum: [
"combined",
"local",
"remote"
],
default: "combined"
},
forwarded: {
type: "boolean",
default: false
}
},
required: []
};
export default define(meta, paramDef, async (ps)=>{
const query = makePaginationQuery(AbuseUserReports.createQueryBuilder("report"), ps.sinceId, ps.untilId);
switch(ps.state){
case "resolved":
query.andWhere("report.resolved = TRUE");
break;
case "unresolved":
query.andWhere("report.resolved = FALSE");
break;
}
switch(ps.reporterOrigin){
case "local":
query.andWhere("report.reporterHost IS NULL");
break;
case "remote":
query.andWhere("report.reporterHost IS NOT NULL");
break;
}
switch(ps.targetUserOrigin){
case "local":
query.andWhere("report.targetUserHost IS NULL");
break;
case "remote":
query.andWhere("report.targetUserHost IS NOT NULL");
break;
}
const reports = await query.take(ps.limit).getMany();
return await AbuseUserReports.packMany(reports);
});
@@ -0,0 +1,53 @@
import define from "../../../define.js";
import { Users } from "../../../../../models/index.js";
import { signup } from "../../../common/signup.js";
import { IsNull } from "typeorm";
export const meta = {
tags: [
"admin"
],
res: {
type: "object",
optional: false,
nullable: false,
ref: "User",
properties: {
token: {
type: "string",
optional: false,
nullable: false
}
}
}
};
export const paramDef = {
type: "object",
properties: {
username: Users.localUsernameSchema,
password: Users.passwordSchema
},
required: [
"username",
"password"
]
};
export default define(meta, paramDef, async (ps, _me)=>{
const me = _me ? await Users.findOneByOrFail({
id: _me.id
}) : null;
const noUsers = await Users.countBy({
host: IsNull(),
isAdmin: true
}) === 0;
if (!(noUsers || me?.isAdmin)) throw new Error("access denied");
const { account, secret } = await signup({
username: ps.username,
password: ps.password
});
const res = await Users.pack(account, account, {
detail: true,
includeSecrets: true
});
res.token = secret;
return res;
});
@@ -0,0 +1,56 @@
import define from "../../../define.js";
import { Users } from "../../../../../models/index.js";
import { doPostSuspend } from "../../../../../services/suspend-user.js";
import { publishUserEvent } from "../../../../../services/stream.js";
import { createDeleteAccountJob } from "../../../../../queue/index.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const user = await Users.findOneBy({
id: ps.userId
});
if (user == null) {
throw new Error("user not found");
}
if (user.isAdmin) {
throw new Error("cannot suspend admin");
}
if (user.isModerator) {
throw new Error("cannot suspend moderator");
}
if (Users.isLocalUser(user)) {
// 物理削除する前にDelete activityを送信する
await doPostSuspend(user).catch((e)=>{});
createDeleteAccountJob(user, {
soft: false
});
} else {
createDeleteAccountJob(user, {
soft: true
});
}
await Users.update(user.id, {
isDeleted: true
});
if (Users.isLocalUser(user)) {
// Terminate streaming
publishUserEvent(user.id, "terminate", {});
}
});
@@ -0,0 +1,109 @@
import config from "../../../../../config/index.js";
import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
import define from "../../../define.js";
import { Metas } from "../../../../../models/index.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async (ps, me)=>{
const hostedConfig = config.isManagedHosting;
const hosted = hostedConfig != null && hostedConfig === true;
if (hosted) {
const set = {};
if (config.deepl.managed != null && config.deepl.managed === true) {
if (typeof config.deepl.authKey === "boolean") {
set.deeplAuthKey = config.deepl.authKey;
}
if (typeof config.deepl.isPro === "boolean") {
set.deeplIsPro = config.deepl.isPro;
}
}
if (config.libreTranslate.managed != null && config.libreTranslate.managed === true) {
if (typeof config.libreTranslate.apiUrl === "string") {
set.libreTranslateApiUrl = config.libreTranslate.apiUrl;
}
if (typeof config.libreTranslate.apiKey === "string") {
set.libreTranslateApiKey = config.libreTranslate.apiKey;
}
}
if (config.email.managed != null && config.email.managed === true) {
set.enableEmail = true;
if (typeof config.email.address === "string") {
set.email = config.email.address;
}
if (typeof config.email.host === "string") {
set.smtpHost = config.email.host;
}
if (typeof config.email.port === "number") {
set.smtpPort = config.email.port;
}
if (typeof config.email.user === "string") {
set.smtpUser = config.email.user;
}
if (typeof config.email.pass === "string") {
set.smtpPass = config.email.pass;
}
if (typeof config.email.useImplicitSslTls === "boolean") {
set.smtpSecure = config.email.useImplicitSslTls;
}
}
if (config.objectStorage.managed != null && config.objectStorage.managed === true) {
set.useObjectStorage = true;
if (typeof config.objectStorage.baseUrl === "string") {
set.objectStorageBaseUrl = config.objectStorage.baseUrl;
}
if (typeof config.objectStorage.bucket === "string") {
set.objectStorageBucket = config.objectStorage.bucket;
}
if (typeof config.objectStorage.prefix === "string") {
set.objectStoragePrefix = config.objectStorage.prefix;
}
if (typeof config.objectStorage.endpoint === "string") {
set.objectStorageEndpoint = config.objectStorage.endpoint;
}
if (typeof config.objectStorage.region === "string") {
set.objectStorageRegion = config.objectStorage.region;
}
if (typeof config.objectStorage.accessKey === "string") {
set.objectStorageAccessKey = config.objectStorage.accessKey;
}
if (typeof config.objectStorage.secretKey === "string") {
set.objectStorageSecretKey = config.objectStorage.secretKey;
}
if (typeof config.objectStorage.useSsl === "boolean") {
set.objectStorageUseSSL = config.objectStorage.useSsl;
}
if (typeof config.objectStorage.connnectOverProxy === "boolean") {
set.objectStorageUseProxy = config.objectStorage.connnectOverProxy;
}
if (typeof config.objectStorage.setPublicReadOnUpload === "boolean") {
set.objectStorageSetPublicRead = config.objectStorage.setPublicReadOnUpload;
}
if (typeof config.objectStorage.s3ForcePathStyle === "boolean") {
set.objectStorageS3ForcePathStyle = config.objectStorage.s3ForcePathStyle;
}
}
if (config.summalyProxyUrl !== undefined) {
set.summalyProxy = config.summalyProxyUrl;
}
const meta = await Metas.findOne({
where: {},
order: {
id: "DESC"
}
});
if (meta) await Metas.update(meta.id, set);
else await Metas.save(set);
insertModerationLog(me, "updateMeta");
}
return hosted;
});
@@ -0,0 +1,108 @@
import define from "../../../define.js";
import { Announcements } from "../../../../../models/index.js";
import { genId } from "../../../../../misc/gen-id.js";
import { publishBroadcastStream } from "../../../../../services/stream.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id",
example: "xxxxxxxxxx"
},
createdAt: {
type: "string",
optional: false,
nullable: false,
format: "date-time"
},
updatedAt: {
type: "string",
optional: false,
nullable: true,
format: "date-time"
},
title: {
type: "string",
optional: false,
nullable: false
},
text: {
type: "string",
optional: false,
nullable: false
},
imageUrl: {
type: "string",
optional: false,
nullable: true
},
showPopup: {
type: "boolean",
optional: true,
nullable: false
},
isGoodNews: {
type: "boolean",
optional: true,
nullable: false
}
}
}
};
export const paramDef = {
type: "object",
properties: {
title: {
type: "string",
minLength: 1
},
text: {
type: "string",
minLength: 1
},
imageUrl: {
type: "string",
nullable: true,
minLength: 1
},
showPopup: {
type: "boolean"
},
isGoodNews: {
type: "boolean"
}
},
required: [
"title",
"text",
"imageUrl"
]
};
export default define(meta, paramDef, async (ps)=>{
const announcement = await Announcements.insert({
id: genId(),
createdAt: new Date(),
updatedAt: null,
title: ps.title,
text: ps.text,
imageUrl: ps.imageUrl,
showPopup: ps.showPopup ?? false,
isGoodNews: ps.isGoodNews ?? false
}).then((x)=>Announcements.findOneByOrFail(x.identifiers[0]));
publishBroadcastStream("announcementAdded", announcement);
return Object.assign({}, announcement, {
createdAt: announcement.createdAt.toISOString(),
updatedAt: null
});
});
@@ -0,0 +1,38 @@
import define from "../../../define.js";
import { Announcements } from "../../../../../models/index.js";
import { ApiError } from "../../../error.js";
import { publishBroadcastStream } from "../../../../../services/stream.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
errors: {
noSuchAnnouncement: {
message: "No such announcement.",
code: "NO_SUCH_ANNOUNCEMENT",
id: "ecad8040-a276-4e85-bda9-015a708d291e"
}
}
};
export const paramDef = {
type: "object",
properties: {
id: {
type: "string",
format: "misskey:id"
}
},
required: [
"id"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const announcement = await Announcements.findOneBy({
id: ps.id
});
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
publishBroadcastStream("announcementDeleted", announcement.id);
await Announcements.delete(announcement.id);
});
@@ -0,0 +1,112 @@
import { Announcements, AnnouncementReads } from "../../../../../models/index.js";
import define from "../../../define.js";
import { makePaginationQuery } from "../../../common/make-pagination-query.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id",
example: "xxxxxxxxxx"
},
createdAt: {
type: "string",
optional: false,
nullable: false,
format: "date-time"
},
updatedAt: {
type: "string",
optional: false,
nullable: true,
format: "date-time"
},
text: {
type: "string",
optional: false,
nullable: false
},
title: {
type: "string",
optional: false,
nullable: false
},
imageUrl: {
type: "string",
optional: false,
nullable: true
},
reads: {
type: "number",
optional: false,
nullable: false
},
showPopup: {
type: "boolean",
optional: true,
nullable: false
},
isGoodNews: {
type: "boolean",
optional: true,
nullable: false
}
}
}
}
};
export const paramDef = {
type: "object",
properties: {
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 10
},
sinceId: {
type: "string",
format: "misskey:id"
},
untilId: {
type: "string",
format: "misskey:id"
}
},
required: []
};
export default define(meta, paramDef, async (ps)=>{
const query = makePaginationQuery(Announcements.createQueryBuilder("announcement"), ps.sinceId, ps.untilId);
const announcements = await query.take(ps.limit).getMany();
const reads = new Map();
for (const announcement of announcements){
reads.set(announcement, await AnnouncementReads.countBy({
announcementId: announcement.id
}));
}
return announcements.map((announcement)=>({
id: announcement.id,
createdAt: announcement.createdAt.toISOString(),
updatedAt: announcement.updatedAt?.toISOString() ?? null,
title: announcement.title,
text: announcement.text,
imageUrl: announcement.imageUrl,
reads: reads.get(announcement),
showPopup: announcement.showPopup,
isGoodNews: announcement.isGoodNews
}));
});
@@ -0,0 +1,65 @@
import define from "../../../define.js";
import { Announcements } from "../../../../../models/index.js";
import { ApiError } from "../../../error.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
errors: {
noSuchAnnouncement: {
message: "No such announcement.",
code: "NO_SUCH_ANNOUNCEMENT",
id: "d3aae5a7-6372-4cb4-b61c-f511ffc2d7cc"
}
}
};
export const paramDef = {
type: "object",
properties: {
id: {
type: "string",
format: "misskey:id"
},
title: {
type: "string",
minLength: 1
},
text: {
type: "string",
minLength: 1
},
imageUrl: {
type: "string",
nullable: true,
minLength: 1
},
showPopup: {
type: "boolean"
},
isGoodNews: {
type: "boolean"
}
},
required: [
"id",
"title",
"text",
"imageUrl"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const announcement = await Announcements.findOneBy({
id: ps.id
});
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
await Announcements.update(announcement.id, {
updatedAt: new Date(),
title: ps.title,
text: ps.text,
imageUrl: ps.imageUrl,
showPopup: ps.showPopup ?? false,
isGoodNews: ps.isGoodNews ?? false
});
});
@@ -0,0 +1,194 @@
import * as fs from "node:fs";
import { mkdir, rm, stat, writeFile } from "node:fs/promises";
import { spawn } from "node:child_process";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import archiver from "archiver";
import config from "../../../../config/index.js";
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
import { createTempDir } from "../../../../misc/create-temp.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true,
res: {
type: "object",
optional: false,
nullable: false,
properties: {
path: {
type: "string",
optional: false,
nullable: false
},
fileName: {
type: "string",
optional: false,
nullable: false
},
size: {
type: "number",
optional: false,
nullable: false
},
includedMedia: {
type: "boolean",
optional: false,
nullable: false
}
}
},
errors: {
backupFailed: {
message: "Failed to create backup.",
code: "BACKUP_FAILED",
id: "7498ab9f-4e1d-40d0-96d8-d8d1d82bd621"
}
}
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "../../../../../../..");
function timestamp() {
const now = new Date();
const pad = (value)=>value.toString().padStart(2, "0");
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
}
function run(command, args, env) {
return new Promise((resolvePromise, reject)=>{
const child = spawn(command, args, {
stdio: [
"ignore",
"ignore",
"pipe"
],
env
});
let stderr = "";
child.stderr.on("data", (chunk)=>{
stderr += chunk.toString();
});
child.on("error", reject);
child.on("exit", (code)=>{
if (code === 0) {
resolvePromise();
} else {
reject(new Error(`${command} exited with code ${code}: ${stderr.trim()}`));
}
});
});
}
function archiveDirectory(sourceDir, outFile) {
return new Promise((resolvePromise, reject)=>{
const output = fs.createWriteStream(outFile);
const archive = archiver("tar", {
gzip: true,
gzipOptions: {
level: 6
}
});
output.on("close", ()=>resolvePromise());
archive.on("error", reject);
archive.pipe(output);
archive.directory(sourceDir, false);
archive.finalize();
});
}
async function pathExists(path) {
return stat(path).then(()=>true).catch(()=>false);
}
export default define(meta, paramDef, async (_ps, me)=>{
const [workDir, cleanup] = await createTempDir();
const fileName = `iceshrimp-full-${timestamp()}.tar.gz`;
const outDir = resolve(process.env.ICESHRIMP_BACKUP_DIR ?? `${rootDir}/backups`);
const outFile = resolve(outDir, fileName);
const dbDumpPath = resolve(workDir, "database.dump");
const mediaDir = resolve(config.mediaDir);
let includedMedia = false;
try {
await mkdir(outDir, {
recursive: true
});
const env = {
...process.env,
PGHOST: config.db.host,
PGPORT: String(config.db.port),
PGDATABASE: config.db.db,
PGUSER: config.db.user,
PGPASSWORD: config.db.pass
};
await run("pg_dump", [
"--format=custom",
"--blobs",
"--no-owner",
"--file",
dbDumpPath,
config.db.db
], env);
const configDir = resolve(workDir, "config");
await mkdir(configDir, {
recursive: true
});
const configFiles = [
process.env.ICESHRIMP_CONFIG ? resolve(process.env.ICESHRIMP_CONFIG) : resolve(rootDir, ".config/default.yml"),
...process.env.ICESHRIMP_SECRETS ? [
resolve(process.env.ICESHRIMP_SECRETS)
] : []
];
for (const configFile of configFiles){
if (await pathExists(configFile)) {
fs.copyFileSync(configFile, resolve(configDir, configFile.split("/").pop()));
}
}
if (await pathExists(mediaDir) && !outDir.startsWith(mediaDir + "/") && outDir !== mediaDir) {
fs.cpSync(mediaDir, resolve(workDir, "files"), {
recursive: true,
dereference: false,
errorOnExist: false
});
includedMedia = true;
}
await writeFile(resolve(workDir, "manifest.json"), `${JSON.stringify({
type: "iceshrimp-full-backup",
version: config.version,
createdAt: new Date().toISOString(),
host: config.host,
database: config.db.db,
included: {
database: true,
config: true,
media: includedMedia
},
restore: "yarn full:restore <this archive>"
}, null, 2)}\n`, "utf8");
await archiveDirectory(workDir, outFile);
const outStat = await stat(outFile);
await insertModerationLog(me, "createBackup", {
path: outFile,
size: outStat.size,
includedMedia
});
return {
path: outFile,
fileName,
size: outStat.size,
includedMedia
};
} catch (e) {
await rm(outFile, {
force: true
}).catch(()=>{});
throw new ApiError(meta.errors.backupFailed, {
message: e instanceof Error ? e.message : String(e)
});
} finally{
cleanup();
}
});
@@ -0,0 +1,32 @@
import { Users } from "../../../../models/index.js";
import { deleteAccount } from "../../../../services/delete-account.js";
import define from "../../define.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true,
res: {}
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps)=>{
const user = await Users.findOneByOrFail({
id: ps.userId
});
if (user.isDeleted) {
return;
}
await deleteAccount(user);
});
@@ -0,0 +1,30 @@
import define from "../../define.js";
import { deleteFile } from "../../../../services/drive/delete-file.js";
import { DriveFiles } from "../../../../models/index.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const files = await DriveFiles.findBy({
userId: ps.userId
});
for (const file of files){
deleteFile(file);
}
});
@@ -0,0 +1,48 @@
import define from "../../define.js";
import { Users } from "../../../../models/index.js";
import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
import { publishInternalEvent } from "../../../../services/stream.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
},
overrideMb: {
type: "number",
nullable: true
}
},
required: [
"userId",
"overrideMb"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const user = await Users.findOneBy({
id: ps.userId
});
if (user == null) {
throw new Error("user not found");
}
if (!Users.isLocalUser(user)) {
throw new Error("user is not local user");
}
await Users.update(user.id, {
driveCapacityOverrideMb: ps.overrideMb
});
publishInternalEvent("localUserUpdated", {
id: user.id
});
insertModerationLog(me, "change-drive-capacity-override", {
targetId: user.id
});
});
@@ -0,0 +1,17 @@
import define from "../../../define.js";
import { createCleanRemoteFilesJob } from "../../../../../queue/index.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async (ps, me)=>{
createCleanRemoteFilesJob();
});
@@ -0,0 +1,24 @@
import { IsNull } from "typeorm";
import define from "../../../define.js";
import { deleteFile } from "../../../../../services/drive/delete-file.js";
import { DriveFiles } from "../../../../../models/index.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async (ps, me)=>{
const files = await DriveFiles.findBy({
userId: IsNull()
});
for (const file of files){
deleteFile(file);
}
});
@@ -0,0 +1,102 @@
import { DriveFiles } from "../../../../../models/index.js";
import define from "../../../define.js";
import { makePaginationQuery } from "../../../common/make-pagination-query.js";
export const meta = {
tags: [
"admin"
],
requireCredential: false,
requireModerator: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "DriveFile"
}
}
};
export const paramDef = {
type: "object",
properties: {
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 10
},
sinceId: {
type: "string",
format: "misskey:id"
},
untilId: {
type: "string",
format: "misskey:id"
},
userId: {
type: "string",
format: "misskey:id",
nullable: true
},
type: {
type: "string",
nullable: true,
pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1)
},
origin: {
type: "string",
enum: [
"combined",
"local",
"remote"
],
default: "local"
},
hostname: {
type: "string",
nullable: true,
default: null,
description: "The local host is represented with `null`."
}
},
required: []
};
export default define(meta, paramDef, async (ps, me)=>{
const query = makePaginationQuery(DriveFiles.createQueryBuilder("file"), ps.sinceId, ps.untilId);
if (ps.userId) {
query.andWhere("file.userId = :userId", {
userId: ps.userId
});
} else {
if (ps.origin === "local") {
query.andWhere("file.userHost IS NULL");
} else if (ps.origin === "remote") {
query.andWhere("file.userHost IS NOT NULL");
}
if (ps.hostname) {
query.andWhere("file.userHost = :hostname", {
hostname: ps.hostname
});
}
}
if (ps.type) {
if (ps.type.endsWith("/*")) {
query.andWhere("file.type like :type", {
type: `${ps.type.replace("/*", "/")}%`
});
} else {
query.andWhere("file.type = :type", {
type: ps.type
});
}
}
const files = await query.take(ps.limit).getMany();
return await DriveFiles.packMany(files, {
detail: true,
withUser: true,
self: true
});
});
@@ -0,0 +1,227 @@
import { DriveFiles } from "../../../../../models/index.js";
import define from "../../../define.js";
import { ApiError } from "../../../error.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
errors: {
noSuchFile: {
message: "No such file.",
code: "NO_SUCH_FILE",
id: "caf3ca38-c6e5-472e-a30c-b05377dcc240"
}
},
res: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id",
example: "xxxxxxxxxx"
},
createdAt: {
type: "string",
optional: false,
nullable: false,
format: "date-time"
},
userId: {
type: "string",
optional: false,
nullable: true,
format: "id",
example: "xxxxxxxxxx"
},
userHost: {
type: "string",
optional: false,
nullable: true,
description: "The local host is represented with `null`."
},
md5: {
type: "string",
optional: false,
nullable: false,
format: "md5",
example: "15eca7fba0480996e2245f5185bf39f2"
},
name: {
type: "string",
optional: false,
nullable: false,
example: "lenna.jpg"
},
type: {
type: "string",
optional: false,
nullable: false,
example: "image/jpeg"
},
size: {
type: "number",
optional: false,
nullable: false,
example: 51469
},
comment: {
type: "string",
optional: false,
nullable: true
},
blurhash: {
type: "string",
optional: false,
nullable: true
},
properties: {
type: "object",
optional: false,
nullable: false,
properties: {
width: {
type: "number",
optional: false,
nullable: false,
example: 1280
},
height: {
type: "number",
optional: false,
nullable: false,
example: 720
},
avgColor: {
type: "string",
optional: true,
nullable: false,
example: "rgb(40,65,87)"
}
}
},
storedInternal: {
type: "boolean",
optional: false,
nullable: true,
example: true
},
url: {
type: "string",
optional: false,
nullable: true,
format: "url"
},
thumbnailUrl: {
type: "string",
optional: false,
nullable: true,
format: "url"
},
webpublicUrl: {
type: "string",
optional: false,
nullable: true,
format: "url"
},
accessKey: {
type: "string",
optional: false,
nullable: false
},
thumbnailAccessKey: {
type: "string",
optional: false,
nullable: false
},
webpublicAccessKey: {
type: "string",
optional: false,
nullable: false
},
uri: {
type: "string",
optional: false,
nullable: true
},
src: {
type: "string",
optional: false,
nullable: true
},
folderId: {
type: "string",
optional: false,
nullable: true,
format: "id",
example: "xxxxxxxxxx"
},
isSensitive: {
type: "boolean",
optional: false,
nullable: false
},
isLink: {
type: "boolean",
optional: false,
nullable: false
}
}
}
};
export const paramDef = {
type: "object",
anyOf: [
{
properties: {
fileId: {
type: "string",
format: "misskey:id"
}
},
required: [
"fileId"
]
},
{
properties: {
url: {
type: "string"
}
},
required: [
"url"
]
}
]
};
export default define(meta, paramDef, async (ps, me)=>{
const file = ps.fileId ? await DriveFiles.findOneBy({
id: ps.fileId
}) : await DriveFiles.findOne({
where: [
{
url: ps.url
},
{
thumbnailUrl: ps.url
},
{
webpublicUrl: ps.url
}
]
});
if (file == null) {
throw new ApiError(meta.errors.noSuchFile);
}
if (!me.isAdmin) {
file.requestIp = undefined;
file.requestHeaders = undefined;
}
return file;
});
@@ -0,0 +1,49 @@
import define from "../../../define.js";
import { Emojis } from "../../../../../models/index.js";
import { In } from "typeorm";
import { db } from "../../../../../db/postgre.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
ids: {
type: "array",
items: {
type: "string",
format: "misskey:id"
}
},
aliases: {
type: "array",
items: {
type: "string"
}
}
},
required: [
"ids",
"aliases"
]
};
export default define(meta, paramDef, async (ps)=>{
const emojis = await Emojis.findBy({
id: In(ps.ids)
});
for (const emoji of emojis){
await Emojis.update(emoji.id, {
updatedAt: new Date(),
aliases: [
...new Set(emoji.aliases.concat(ps.aliases))
]
});
}
await db.queryResultCache.remove([
"meta_emojis"
]);
});
@@ -0,0 +1,70 @@
import define from "../../../define.js";
import { Emojis, DriveFiles } from "../../../../../models/index.js";
import { genId } from "../../../../../misc/gen-id.js";
import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
import { ApiError } from "../../../error.js";
import rndstr from "rndstr";
import { publishBroadcastStream } from "../../../../../services/stream.js";
import { db } from "../../../../../db/postgre.js";
import { getEmojiSize } from "../../../../../misc/emoji-meta.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
errors: {
noSuchFile: {
message: "No such file.",
code: "MO_SUCH_FILE",
id: "fc46b5a4-6b92-4c33-ac66-b806659bb5cf"
}
}
};
export const paramDef = {
type: "object",
properties: {
fileId: {
type: "string",
format: "misskey:id"
}
},
required: [
"fileId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const file = await DriveFiles.findOneBy({
id: ps.fileId
});
if (file == null) throw new ApiError(meta.errors.noSuchFile);
const name = file.name.split(".")[0].match(/^[a-z0-9_]+$/) ? file.name.split(".")[0] : `_${rndstr("a-z0-9", 8)}_`;
const size = await getEmojiSize(file.url);
const emoji = await Emojis.insert({
id: genId(),
updatedAt: new Date(),
name: name,
category: null,
host: null,
aliases: [],
originalUrl: file.url,
publicUrl: file.webpublicUrl ?? file.url,
type: file.webpublicType ?? file.type,
license: null,
glyph: file.type === "image/svg+xml",
width: size.width || null,
height: size.height || null
}).then((x)=>Emojis.findOneByOrFail(x.identifiers[0]));
await db.queryResultCache.remove([
"meta_emojis"
]);
publishBroadcastStream("emojiAdded", {
emoji: await Emojis.pack(emoji.id)
});
insertModerationLog(me, "addEmoji", {
emojiId: emoji.id
});
return {
id: emoji.id
};
});
@@ -0,0 +1,90 @@
import define from "../../../define.js";
import { Emojis } from "../../../../../models/index.js";
import { genId } from "../../../../../misc/gen-id.js";
import { ApiError } from "../../../error.js";
import { uploadFromUrl } from "../../../../../services/drive/upload-from-url.js";
import { publishBroadcastStream } from "../../../../../services/stream.js";
import { db } from "../../../../../db/postgre.js";
import { getEmojiSize } from "../../../../../misc/emoji-meta.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
errors: {
noSuchEmoji: {
message: "No such emoji.",
code: "NO_SUCH_EMOJI",
id: "e2785b66-dca3-4087-9cac-b93c541cc425"
}
},
res: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id"
}
}
}
};
export const paramDef = {
type: "object",
properties: {
emojiId: {
type: "string",
format: "misskey:id"
}
},
required: [
"emojiId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const emoji = await Emojis.findOneBy({
id: ps.emojiId
});
if (emoji == null) {
throw new ApiError(meta.errors.noSuchEmoji);
}
let driveFile;
try {
// Create file
driveFile = await uploadFromUrl({
url: emoji.originalUrl,
user: null,
force: true
});
} catch (e) {
throw new ApiError();
}
const size = await getEmojiSize(driveFile.url);
const copied = await Emojis.insert({
id: genId(),
updatedAt: new Date(),
name: emoji.name,
host: null,
aliases: [],
originalUrl: driveFile.url,
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
type: driveFile.webpublicType ?? driveFile.type,
license: emoji.license,
glyph: emoji.glyph,
width: size.width || null,
height: size.height || null
}).then((x)=>Emojis.findOneByOrFail(x.identifiers[0]));
await db.queryResultCache.remove([
"meta_emojis"
]);
publishBroadcastStream("emojiAdded", {
emoji: await Emojis.pack(copied.id)
});
return {
id: copied.id
};
});
@@ -0,0 +1,41 @@
import define from "../../../define.js";
import { Emojis } from "../../../../../models/index.js";
import { In } from "typeorm";
import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
import { db } from "../../../../../db/postgre.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
ids: {
type: "array",
items: {
type: "string",
format: "misskey:id"
}
}
},
required: [
"ids"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const emojis = await Emojis.findBy({
id: In(ps.ids)
});
for (const emoji of emojis){
await Emojis.delete(emoji.id);
await db.queryResultCache.remove([
"meta_emojis"
]);
insertModerationLog(me, "deleteEmoji", {
emoji: emoji
});
}
});
@@ -0,0 +1,44 @@
import define from "../../../define.js";
import { Emojis } from "../../../../../models/index.js";
import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
import { ApiError } from "../../../error.js";
import { db } from "../../../../../db/postgre.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
errors: {
noSuchEmoji: {
message: "No such emoji.",
code: "NO_SUCH_EMOJI",
id: "be83669b-773a-44b7-b1f8-e5e5170ac3c2"
}
}
};
export const paramDef = {
type: "object",
properties: {
id: {
type: "string",
format: "misskey:id"
}
},
required: [
"id"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const emoji = await Emojis.findOneBy({
id: ps.id
});
if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
await Emojis.delete(emoji.id);
await db.queryResultCache.remove([
"meta_emojis"
]);
insertModerationLog(me, "deleteEmoji", {
emoji: emoji
});
});
@@ -0,0 +1,22 @@
import define from "../../../define.js";
import { createImportCustomEmojisJob } from "../../../../../queue/index.js";
export const meta = {
secure: true,
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
fileId: {
type: "string",
format: "misskey:id"
}
},
required: [
"fileId"
]
};
export default define(meta, paramDef, async (ps, user)=>{
createImportCustomEmojisJob(user, ps.fileId);
});
@@ -0,0 +1,134 @@
import define from "../../../define.js";
import { Emojis } from "../../../../../models/index.js";
import { toPuny } from "../../../../../misc/convert-host.js";
import { makePaginationQuery } from "../../../common/make-pagination-query.js";
import { sqlLikeEscape } from "../../../../../misc/sql-like-escape.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id"
},
aliases: {
type: "array",
optional: false,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false
}
},
name: {
type: "string",
optional: false,
nullable: false
},
category: {
type: "string",
optional: false,
nullable: true
},
host: {
type: "string",
optional: false,
nullable: true,
description: "The local host is represented with `null`."
},
url: {
type: "string",
optional: false,
nullable: false
},
license: {
type: "string",
optional: false,
nullable: true
},
glyph: {
type: "boolean",
optional: false,
nullable: false
},
glyphUrl: {
type: "string",
optional: false,
nullable: true
},
width: {
type: "number",
optional: false,
nullable: true
},
height: {
type: "number",
optional: false,
nullable: true
}
}
}
}
};
export const paramDef = {
type: "object",
properties: {
query: {
type: "string",
nullable: true,
default: null
},
host: {
type: "string",
nullable: true,
default: null,
description: "Use `null` to represent the local host."
},
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 10
},
sinceId: {
type: "string",
format: "misskey:id"
},
untilId: {
type: "string",
format: "misskey:id"
}
},
required: []
};
export default define(meta, paramDef, async (ps)=>{
const q = makePaginationQuery(Emojis.createQueryBuilder("emoji"), ps.sinceId, ps.untilId);
if (ps.host == null) {
q.andWhere("emoji.host IS NOT NULL");
} else {
q.andWhere("emoji.host = :host", {
host: toPuny(ps.host)
});
}
if (ps.query) {
q.andWhere("emoji.name like :query", {
query: `%${sqlLikeEscape(ps.query)}%`
});
}
const emojis = await q.orderBy("emoji.id", "DESC").take(ps.limit).getMany();
return Emojis.packMany(emojis);
});
@@ -0,0 +1,123 @@
import define from "../../../define.js";
import { Emojis } from "../../../../../models/index.js";
import { makePaginationQuery } from "../../../common/make-pagination-query.js";
//import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id"
},
aliases: {
type: "array",
optional: false,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false
}
},
name: {
type: "string",
optional: false,
nullable: false
},
category: {
type: "string",
optional: false,
nullable: true
},
host: {
type: "null",
optional: false,
description: "The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files."
},
url: {
type: "string",
optional: false,
nullable: false
},
license: {
type: "string",
optional: false,
nullable: true
},
glyph: {
type: "boolean",
optional: false,
nullable: false
},
glyphUrl: {
type: "string",
optional: false,
nullable: true
},
width: {
type: "number",
optional: false,
nullable: true
},
height: {
type: "number",
optional: false,
nullable: true
}
}
}
}
};
export const paramDef = {
type: "object",
properties: {
query: {
type: "string",
nullable: true,
default: null
},
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 10
},
sinceId: {
type: "string",
format: "misskey:id"
},
untilId: {
type: "string",
format: "misskey:id"
}
},
required: []
};
export default define(meta, paramDef, async (ps)=>{
const q = makePaginationQuery(Emojis.createQueryBuilder("emoji"), ps.sinceId, ps.untilId).andWhere("emoji.host IS NULL");
let emojis;
if (ps.query) {
//q.andWhere('emoji.name ILIKE :q', { q: `%${sqlLikeEscape(ps.query)}%` });
//const emojis = await q.take(ps.limit).getMany();
emojis = await q.getMany();
emojis = emojis.filter((emoji)=>emoji.name.includes(ps.query) || emoji.aliases.some((a)=>a.includes(ps.query)) || emoji.category?.includes(ps.query));
emojis.splice(ps.limit + 1);
} else {
emojis = await q.take(ps.limit).getMany();
}
return Emojis.packMany(emojis);
});
@@ -0,0 +1,47 @@
import define from "../../../define.js";
import { Emojis } from "../../../../../models/index.js";
import { In } from "typeorm";
import { db } from "../../../../../db/postgre.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
ids: {
type: "array",
items: {
type: "string",
format: "misskey:id"
}
},
aliases: {
type: "array",
items: {
type: "string"
}
}
},
required: [
"ids",
"aliases"
]
};
export default define(meta, paramDef, async (ps)=>{
const emojis = await Emojis.findBy({
id: In(ps.ids)
});
for (const emoji of emojis){
await Emojis.update(emoji.id, {
updatedAt: new Date(),
aliases: emoji.aliases.filter((x)=>!ps.aliases.includes(x))
});
}
await db.queryResultCache.remove([
"meta_emojis"
]);
});
@@ -0,0 +1,44 @@
import define from "../../../define.js";
import { Emojis } from "../../../../../models/index.js";
import { In } from "typeorm";
import { db } from "../../../../../db/postgre.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
ids: {
type: "array",
items: {
type: "string",
format: "misskey:id"
}
},
aliases: {
type: "array",
items: {
type: "string"
}
}
},
required: [
"ids",
"aliases"
]
};
export default define(meta, paramDef, async (ps)=>{
await Emojis.update({
id: In(ps.ids)
}, {
updatedAt: new Date(),
aliases: ps.aliases
});
await db.queryResultCache.remove([
"meta_emojis"
]);
});
@@ -0,0 +1,42 @@
import define from "../../../define.js";
import { Emojis } from "../../../../../models/index.js";
import { In } from "typeorm";
import { db } from "../../../../../db/postgre.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
ids: {
type: "array",
items: {
type: "string",
format: "misskey:id"
}
},
category: {
type: "string",
nullable: true,
description: "Use `null` to reset the category."
}
},
required: [
"ids"
]
};
export default define(meta, paramDef, async (ps)=>{
await Emojis.update({
id: In(ps.ids)
}, {
updatedAt: new Date(),
category: ps.category
});
await db.queryResultCache.remove([
"meta_emojis"
]);
});
@@ -0,0 +1,42 @@
import define from "../../../define.js";
import { Emojis } from "../../../../../models/index.js";
import { In } from "typeorm";
import { db } from "../../../../../db/postgre.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
ids: {
type: "array",
items: {
type: "string",
format: "misskey:id"
}
},
license: {
type: "string",
nullable: true,
description: "Use `null` to reset the license."
}
},
required: [
"ids"
]
};
export default define(meta, paramDef, async (ps)=>{
await Emojis.update({
id: In(ps.ids)
}, {
updatedAt: new Date(),
license: ps.license
});
await db.queryResultCache.remove([
"meta_emojis"
]);
});
@@ -0,0 +1,72 @@
import define from "../../../define.js";
import { Emojis } from "../../../../../models/index.js";
import { ApiError } from "../../../error.js";
import { db } from "../../../../../db/postgre.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
errors: {
noSuchEmoji: {
message: "No such emoji.",
code: "NO_SUCH_EMOJI",
id: "684dec9d-a8c2-4364-9aa8-456c49cb1dc8"
}
}
};
export const paramDef = {
type: "object",
properties: {
id: {
type: "string",
format: "misskey:id"
},
name: {
type: "string"
},
category: {
type: "string",
nullable: true,
description: "Use `null` to reset the category."
},
aliases: {
type: "array",
items: {
type: "string"
}
},
license: {
type: "string",
nullable: true
},
glyph: {
type: "boolean"
}
},
required: [
"id",
"name",
"aliases"
]
};
export default define(meta, paramDef, async (ps)=>{
const emoji = await Emojis.findOneBy({
id: ps.id
});
if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
await Emojis.update(emoji.id, {
updatedAt: new Date(),
name: ps.name,
category: ps.category,
aliases: ps.aliases,
license: ps.license,
...typeof ps.glyph === "boolean" ? {
glyph: ps.glyph
} : {}
});
await db.queryResultCache.remove([
"meta_emojis"
]);
});
@@ -0,0 +1,29 @@
import define from "../../../define.js";
import { deleteFile } from "../../../../../services/drive/delete-file.js";
import { DriveFiles } from "../../../../../models/index.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
host: {
type: "string"
}
},
required: [
"host"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const files = await DriveFiles.findBy({
userHost: ps.host
});
for (const file of files){
deleteFile(file);
}
});
@@ -0,0 +1,31 @@
import define from "../../../define.js";
import { Instances } from "../../../../../models/index.js";
import { toPuny } from "../../../../../misc/convert-host.js";
import { fetchInstanceMetadata } from "../../../../../services/fetch-instance-metadata.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
host: {
type: "string"
}
},
required: [
"host"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const instance = await Instances.findOneBy({
host: toPuny(ps.host)
});
if (instance == null) {
throw new Error("instance not found");
}
fetchInstanceMetadata(instance, true);
});
@@ -0,0 +1,37 @@
import define from "../../../define.js";
import deleteFollowing from "../../../../../services/following/delete.js";
import { Followings, Users } from "../../../../../models/index.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
host: {
type: "string"
}
},
required: [
"host"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const followings = await Followings.findBy({
followerHost: ps.host
});
const pairs = await Promise.all(followings.map((f)=>Promise.all([
Users.findOneByOrFail({
id: f.followerId
}),
Users.findOneByOrFail({
id: f.followeeId
})
])));
for (const pair of pairs){
deleteFollowing(pair[0], pair[1]);
}
});
@@ -0,0 +1,38 @@
import define from "../../../define.js";
import { Instances } from "../../../../../models/index.js";
import { toPuny } from "../../../../../misc/convert-host.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
host: {
type: "string"
},
isSuspended: {
type: "boolean"
}
},
required: [
"host",
"isSuspended"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const instance = await Instances.findOneBy({
host: toPuny(ps.host)
});
if (instance == null) {
throw new Error("instance not found");
}
Instances.update({
host: toPuny(ps.host)
}, {
isSuspended: ps.isSuspended
});
});
@@ -0,0 +1,24 @@
import define from "../../define.js";
import { db } from "../../../../db/postgre.js";
export const meta = {
requireCredential: true,
requireModerator: true,
tags: [
"admin"
]
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async ()=>{
const stats = await db.query("SELECT * FROM pg_indexes;").then((recs)=>{
const res = [];
for (const rec of recs){
res.push(rec);
}
return res;
});
return stats;
});
@@ -0,0 +1,43 @@
import { db } from "../../../../db/postgre.js";
import define from "../../define.js";
export const meta = {
requireCredential: true,
requireModerator: true,
tags: [
"admin"
],
res: {
type: "object",
optional: false,
nullable: false,
example: {
migrations: {
count: 66,
size: 32768
}
}
}
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async ()=>{
const sizes = await db.query(`
SELECT relname AS "table", reltuples as "count", pg_total_relation_size(C.oid) AS "size"
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
AND C.relkind <> 'i'
AND nspname !~ '^pg_toast';`).then((recs)=>{
const res = {};
for (const rec of recs){
res[rec.table] = {
count: parseInt(rec.count, 10),
size: parseInt(rec.size, 10)
};
}
return res;
});
return sizes;
});
@@ -0,0 +1,36 @@
import { UserIps } from "../../../../models/index.js";
import define from "../../define.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const ips = await UserIps.find({
where: {
userId: ps.userId
},
order: {
createdAt: "DESC"
},
take: 30
});
return ips.map((x)=>({
ip: x.ip,
createdAt: x.createdAt.toISOString()
}));
});
@@ -0,0 +1,45 @@
import rndstr from "rndstr";
import define from "../../define.js";
import { RegistrationTickets } from "../../../../models/index.js";
import { genId } from "../../../../misc/gen-id.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "object",
optional: false,
nullable: false,
properties: {
code: {
type: "string",
optional: false,
nullable: false,
example: "2ERUA5VR",
maxLength: 8,
minLength: 8
}
}
}
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async ()=>{
const code = rndstr({
length: 8,
chars: "2-9A-HJ-NP-Z"
});
await RegistrationTickets.insert({
id: genId(),
createdAt: new Date(),
code
});
return {
code
};
});
@@ -0,0 +1,503 @@
import config from "../../../../config/index.js";
import { fetchMeta } from "../../../../misc/fetch-meta.js";
import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "../../../../const.js";
import define from "../../define.js";
export const meta = {
tags: [
"meta"
],
requireCredential: true,
requireAdmin: true,
res: {
type: "object",
optional: false,
nullable: false,
properties: {
driveCapacityPerLocalUserMb: {
type: "number",
optional: false,
nullable: false
},
driveCapacityPerRemoteUserMb: {
type: "number",
optional: false,
nullable: false
},
lua4frozenDatabaseCapacityMb: {
type: "number",
optional: false,
nullable: false
},
cacheRemoteFiles: {
type: "boolean",
optional: false,
nullable: false
},
emailRequiredForSignup: {
type: "boolean",
optional: false,
nullable: false
},
enableHcaptcha: {
type: "boolean",
optional: false,
nullable: false
},
hcaptchaSiteKey: {
type: "string",
optional: false,
nullable: true
},
enableRecaptcha: {
type: "boolean",
optional: false,
nullable: false
},
recaptchaSiteKey: {
type: "string",
optional: false,
nullable: true
},
swPublickey: {
type: "string",
optional: false,
nullable: true
},
mascotImageUrl: {
type: "string",
optional: false,
nullable: false,
default: "/twemoji/1f440.svg"
},
bannerUrl: {
type: "string",
optional: false,
nullable: false
},
errorImageUrl: {
type: "string",
optional: false,
nullable: false,
default: "/twemoji/1f480.svg"
},
iconUrl: {
type: "string",
optional: false,
nullable: true
},
maxNoteTextLength: {
type: "number",
optional: false,
nullable: false
},
maxCaptionTextLength: {
type: "number",
optional: false,
nullable: false
},
emojis: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id"
},
aliases: {
type: "array",
optional: false,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false
}
},
category: {
type: "string",
optional: false,
nullable: true
},
host: {
type: "string",
optional: false,
nullable: true
},
url: {
type: "string",
optional: false,
nullable: false,
format: "url"
}
}
}
},
enableEmail: {
type: "boolean",
optional: false,
nullable: false
},
enableGithubIntegration: {
type: "boolean",
optional: false,
nullable: false
},
enableDiscordIntegration: {
type: "boolean",
optional: false,
nullable: false
},
translatorAvailable: {
type: "boolean",
optional: false,
nullable: false
},
recommendedInstances: {
type: "array",
optional: true,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false
}
},
pinnedUsers: {
type: "array",
optional: true,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false
}
},
customMOTD: {
type: "array",
optional: true,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false
}
},
customSplashIcons: {
type: "array",
optional: true,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false
}
},
hiddenTags: {
type: "array",
optional: true,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false
}
},
blockedHosts: {
type: "array",
optional: true,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false
}
},
silencedHosts: {
type: "array",
optional: true,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false
}
},
allowedHosts: {
type: "array",
optional: true,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false
}
},
privateMode: {
type: "boolean",
optional: false,
nullable: false
},
secureMode: {
type: "boolean",
optional: false,
nullable: false
},
hcaptchaSecretKey: {
type: "string",
optional: true,
nullable: true
},
recaptchaSecretKey: {
type: "string",
optional: true,
nullable: true
},
summaryProxy: {
type: "string",
optional: true,
nullable: true
},
email: {
type: "string",
optional: true,
nullable: true
},
smtpSecure: {
type: "boolean",
optional: true,
nullable: false
},
smtpHost: {
type: "string",
optional: true,
nullable: true
},
smtpPort: {
type: "string",
optional: true,
nullable: true
},
smtpUser: {
type: "string",
optional: true,
nullable: true
},
smtpPass: {
type: "string",
optional: true,
nullable: true
},
swPrivateKey: {
type: "string",
optional: true,
nullable: true
},
useObjectStorage: {
type: "boolean",
optional: true,
nullable: false
},
objectStorageBaseUrl: {
type: "string",
optional: true,
nullable: true
},
objectStorageBucket: {
type: "string",
optional: true,
nullable: true
},
objectStoragePrefix: {
type: "string",
optional: true,
nullable: true
},
objectStorageEndpoint: {
type: "string",
optional: true,
nullable: true
},
objectStorageRegion: {
type: "string",
optional: true,
nullable: true
},
objectStoragePort: {
type: "number",
optional: true,
nullable: true
},
objectStorageAccessKey: {
type: "string",
optional: true,
nullable: true
},
objectStorageSecretKey: {
type: "string",
optional: true,
nullable: true
},
objectStorageUseSSL: {
type: "boolean",
optional: true,
nullable: false
},
objectStorageUseProxy: {
type: "boolean",
optional: true,
nullable: false
},
objectStorageSetPublicRead: {
type: "boolean",
optional: true,
nullable: false
},
enableIpLogging: {
type: "boolean",
optional: true,
nullable: false
},
enableActiveEmailValidation: {
type: "boolean",
optional: true,
nullable: false
},
defaultReaction: {
type: "string",
optional: false,
nullable: false
},
experimentalFeatures: {
type: "object",
optional: true,
nullable: true,
properties: {
postImports: {
type: "boolean"
}
}
},
enableServerMachineStats: {
type: "boolean",
optional: false,
nullable: false
},
enableIdenticonGeneration: {
type: "boolean",
optional: false,
nullable: false
},
donationLink: {
type: "string",
optional: true,
nullable: true
},
autofollowedAccount: {
type: "string",
optional: true,
nullable: true
}
}
}
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async (ps, me)=>{
const instance = await fetchMeta(true);
return {
maintainerName: instance.maintainerName,
maintainerEmail: instance.maintainerEmail,
version: config.version,
name: instance.name,
uri: config.url,
description: instance.description,
langs: instance.langs,
tosUrl: instance.ToSUrl,
repositoryUrl: instance.repositoryUrl,
feedbackUrl: instance.feedbackUrl,
disableRegistration: instance.disableRegistration,
disableLocalTimeline: instance.disableLocalTimeline,
disableRecommendedTimeline: instance.disableRecommendedTimeline,
disableGlobalTimeline: instance.disableGlobalTimeline,
driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
lua4frozenDatabaseCapacityMb: instance.lua4frozenDatabaseCapacityMb,
emailRequiredForSignup: instance.emailRequiredForSignup,
enableHcaptcha: instance.enableHcaptcha,
hcaptchaSiteKey: instance.hcaptchaSiteKey,
enableRecaptcha: instance.enableRecaptcha,
recaptchaSiteKey: instance.recaptchaSiteKey,
swPublickey: instance.swPublicKey,
themeColor: instance.themeColor,
mascotImageUrl: instance.mascotImageUrl,
bannerUrl: instance.bannerUrl,
errorImageUrl: instance.errorImageUrl,
iconUrl: instance.iconUrl,
backgroundImageUrl: instance.backgroundImageUrl,
logoImageUrl: instance.logoImageUrl,
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH,
defaultLightTheme: instance.defaultLightTheme,
defaultDarkTheme: instance.defaultDarkTheme,
enableEmail: instance.enableEmail,
translatorAvailable: instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
pinnedPages: instance.pinnedPages,
pinnedClipId: instance.pinnedClipId,
cacheRemoteFiles: instance.cacheRemoteFiles,
defaultReaction: instance.defaultReaction,
recommendedInstances: instance.recommendedInstances,
pinnedUsers: instance.pinnedUsers,
customMOTD: instance.customMOTD,
customSplashIcons: instance.customSplashIcons,
hiddenTags: instance.hiddenTags,
blockedHosts: instance.blockedHosts,
silencedHosts: instance.silencedHosts,
allowedHosts: instance.allowedHosts,
privateMode: instance.privateMode,
secureMode: instance.secureMode,
hcaptchaSecretKey: instance.hcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
summalyProxy: instance.summalyProxy,
email: instance.email,
smtpSecure: instance.smtpSecure,
smtpHost: instance.smtpHost,
smtpPort: instance.smtpPort,
smtpUser: instance.smtpUser,
smtpPass: instance.smtpPass,
swPrivateKey: instance.swPrivateKey,
useObjectStorage: instance.useObjectStorage,
objectStorageBaseUrl: instance.objectStorageBaseUrl,
objectStorageBucket: instance.objectStorageBucket,
objectStoragePrefix: instance.objectStoragePrefix,
objectStorageEndpoint: instance.objectStorageEndpoint,
objectStorageRegion: instance.objectStorageRegion,
objectStoragePort: instance.objectStoragePort,
objectStorageAccessKey: instance.objectStorageAccessKey,
objectStorageSecretKey: instance.objectStorageSecretKey,
objectStorageUseSSL: instance.objectStorageUseSSL,
objectStorageUseProxy: instance.objectStorageUseProxy,
objectStorageSetPublicRead: instance.objectStorageSetPublicRead,
objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
deeplAuthKey: instance.deeplAuthKey,
deeplIsPro: instance.deeplIsPro,
libreTranslateApiUrl: instance.libreTranslateApiUrl,
libreTranslateApiKey: instance.libreTranslateApiKey,
enableIpLogging: instance.enableIpLogging,
enableActiveEmailValidation: instance.enableActiveEmailValidation,
experimentalFeatures: instance.experimentalFeatures,
enableServerMachineStats: instance.enableServerMachineStats,
enableIdenticonGeneration: instance.enableIdenticonGeneration,
donationLink: instance.donationLink,
autofollowedAccount: instance.autofollowedAccount
};
});
@@ -0,0 +1,40 @@
import define from "../../../define.js";
import { Users } from "../../../../../models/index.js";
import { publishInternalEvent } from "../../../../../services/stream.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps)=>{
const user = await Users.findOneBy({
id: ps.userId
});
if (user == null) {
throw new Error("user not found");
}
if (user.isAdmin) {
throw new Error("cannot mark as moderator if admin user");
}
await Users.update(user.id, {
isModerator: true
});
publishInternalEvent("userChangeModeratorState", {
id: user.id,
isModerator: true
});
});
@@ -0,0 +1,37 @@
import define from "../../../define.js";
import { Users } from "../../../../../models/index.js";
import { publishInternalEvent } from "../../../../../services/stream.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps)=>{
const user = await Users.findOneBy({
id: ps.userId
});
if (user == null) {
throw new Error("user not found");
}
await Users.update(user.id, {
isModerator: false
});
publishInternalEvent("userChangeModeratorState", {
id: user.id,
isModerator: false
});
});
@@ -0,0 +1,59 @@
import define from "../../../define.js";
import { Plans } from "../../../../../models/index.js";
import { genId } from "../../../../../misc/gen-id.js";
import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
name: {
type: "string",
minLength: 1,
maxLength: 128
},
icon: {
type: "string",
minLength: 1,
maxLength: 64
},
description: {
type: "string",
maxLength: 512,
default: ""
}
},
required: [
"name",
"icon"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const name = ps.name.trim();
const icon = ps.icon.trim();
const description = ps.description.trim();
if (name === "") throw new Error("name is empty");
if (icon === "") throw new Error("icon is empty");
const exists = await Plans.findOneBy({
name
});
if (exists) throw new Error("plan name already exists");
const plan = await Plans.insert({
id: genId(),
createdAt: new Date(),
updatedAt: null,
name,
icon,
description
}).then((x)=>Plans.findOneByOrFail(x.identifiers[0]));
insertModerationLog(me, "createPlan", {
planId: plan.id,
name
});
return await Plans.pack(plan);
});
@@ -0,0 +1,32 @@
import define from "../../../define.js";
import { Plans } from "../../../../../models/index.js";
import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
planId: {
type: "string",
format: "misskey:id"
}
},
required: [
"planId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const plan = await Plans.findOneByOrFail({
id: ps.planId
});
await Plans.delete(plan.id);
insertModerationLog(me, "deletePlan", {
planId: plan.id,
name: plan.name
});
});
@@ -0,0 +1,22 @@
import define from "../../../define.js";
import { Plans } from "../../../../../models/index.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async ()=>{
const plans = await Plans.find({
order: {
createdAt: "ASC"
}
});
return await Plans.packMany(plans);
});
@@ -0,0 +1,64 @@
import define from "../../../define.js";
import { Plans } from "../../../../../models/index.js";
import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
planId: {
type: "string",
format: "misskey:id"
},
name: {
type: "string",
minLength: 1,
maxLength: 128
},
icon: {
type: "string",
minLength: 1,
maxLength: 64
},
description: {
type: "string",
maxLength: 512,
default: ""
}
},
required: [
"planId",
"name",
"icon"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const plan = await Plans.findOneByOrFail({
id: ps.planId
});
const name = ps.name.trim();
const icon = ps.icon.trim();
const description = ps.description.trim();
if (name === "") throw new Error("name is empty");
if (icon === "") throw new Error("icon is empty");
const exists = await Plans.findOneBy({
name
});
if (exists && exists.id !== plan.id) throw new Error("plan name already exists");
await Plans.update(plan.id, {
updatedAt: new Date(),
name,
icon,
description
});
insertModerationLog(me, "updatePlan", {
planId: plan.id,
name
});
return await Plans.pack(plan.id);
});
@@ -0,0 +1,79 @@
import define from "../../../define.js";
import { ApiError } from "../../../error.js";
import { Notes, PromoNotes } from "../../../../../models/index.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
errors: {
noSuchNote: {
message: "No such note.",
code: "NO_SUCH_NOTE",
id: "ee449fbe-af2a-453b-9cae-cf2fe7c895fc"
},
alreadyPromoted: {
message: "The note has already promoted.",
code: "ALREADY_PROMOTED",
id: "ae427aa2-7a41-484f-a18c-2c1104051604"
},
notAdService: {
message: "The note must have #AdService.",
code: "NOT_AD_SERVICE",
id: "970a4d57-7b67-4540-80ea-f210041724ef"
}
}
};
export const paramDef = {
type: "object",
properties: {
noteId: {
type: "string",
format: "misskey:id"
},
expiresAt: {
type: "integer"
},
credits: {
type: "integer",
minimum: 1,
maximum: 100000,
default: 1
}
},
required: [
"noteId"
]
};
export default define(meta, paramDef, async (ps, user)=>{
const note = await Notes.findOneBy({
id: ps.noteId
});
if (note == null) {
throw new ApiError(meta.errors.noSuchNote);
}
if (!note.tags.includes("adservice")) {
throw new ApiError(meta.errors.notAdService);
}
const expiresAt = ps.expiresAt ? new Date(ps.expiresAt) : new Date(Date.now() + 30 * 86400000);
const credits = ps.credits ?? 1;
const exist = await PromoNotes.findOneBy({
noteId: note.id
});
if (exist) {
await PromoNotes.update(note.id, {
expiresAt: exist.expiresAt.getTime() > expiresAt.getTime() ? exist.expiresAt : expiresAt,
totalCredits: exist.totalCredits + credits,
remainingCredits: exist.remainingCredits + credits
});
return;
}
await PromoNotes.insert({
noteId: note.id,
expiresAt,
totalCredits: credits,
remainingCredits: credits,
userId: note.userId
});
});
@@ -0,0 +1,52 @@
import rndstr from "rndstr";
import { Notes, PromoNotes } from "../../../../../models/index.js";
import define from "../../../define.js";
import { makePaginationQuery } from "../../../common/make-pagination-query.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 10
},
sinceId: {
type: "string",
format: "misskey:id"
},
untilId: {
type: "string",
format: "misskey:id"
}
},
required: []
};
export default define(meta, paramDef, async (ps, user)=>{
const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId).andWhere(`'adservice' = ANY(note.tags)`).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
const notes = await query.take(ps.limit).getMany();
if (notes.length === 0) return [];
const promos = await PromoNotes.findBy(notes.map((note)=>({
noteId: note.id
})));
return Promise.all(notes.map(async (note)=>{
const promo = promos.find((item)=>item.noteId === note.id);
const expiredCredits = promo && promo.expiresAt.getTime() <= Date.now() ? promo.remainingCredits : 0;
note._prId_ = rndstr("a-z0-9", 8);
return {
id: note.id,
note: await Notes.pack(note, user),
expiresAt: promo?.expiresAt.toISOString() ?? null,
totalCredits: promo?.totalCredits ?? 0,
remainingCredits: promo?.remainingCredits ?? 0,
expiredCredits
};
}));
});
@@ -0,0 +1,19 @@
import define from "../../../define.js";
import { destroy } from "../../../../../queue/index.js";
import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async (ps, me)=>{
destroy();
insertModerationLog(me, "clearQueue");
});
@@ -0,0 +1,60 @@
import { deliverQueue } from "../../../../../queue/queues.js";
import { URL } from "node:url";
import define from "../../../define.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "array",
optional: false,
nullable: false,
items: {
anyOf: [
{
type: "string"
},
{
type: "number"
}
]
}
},
example: [
[
"example.com",
12
]
]
}
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async (ps)=>{
const jobs = await deliverQueue.getJobs([
"delayed"
]);
const res = [];
for (const job of jobs){
const host = new URL(job.data.to).host;
if (res.find((x)=>x[0] === host)) {
res.find((x)=>x[0] === host)[1]++;
} else {
res.push([
host,
1
]);
}
}
res.sort((a, b)=>b[1] - a[1]);
return res;
});
@@ -0,0 +1,60 @@
import { URL } from "node:url";
import define from "../../../define.js";
import { inboxQueue } from "../../../../../queue/queues.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "array",
optional: false,
nullable: false,
items: {
anyOf: [
{
type: "string"
},
{
type: "number"
}
]
}
},
example: [
[
"example.com",
12
]
]
}
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async (ps)=>{
const jobs = await inboxQueue.getJobs([
"delayed"
]);
const res = [];
for (const job of jobs){
const host = new URL(job.data.signature.keyId).host;
if (res.find((x)=>x[0] === host)) {
res.find((x)=>x[0] === host)[1]++;
} else {
res.push([
host,
1
]);
}
}
res.sort((a, b)=>b[1] - a[1]);
return res;
});
@@ -0,0 +1,53 @@
import { deliverQueue, inboxQueue, dbQueue, objectStorageQueue } from "../../../../../queue/queues.js";
import define from "../../../define.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "object",
optional: false,
nullable: false,
properties: {
deliver: {
optional: false,
nullable: false,
ref: "QueueCount"
},
inbox: {
optional: false,
nullable: false,
ref: "QueueCount"
},
db: {
optional: false,
nullable: false,
ref: "QueueCount"
},
objectStorage: {
optional: false,
nullable: false,
ref: "QueueCount"
}
}
}
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async (ps)=>{
const deliverJobCounts = await deliverQueue.getJobCounts();
const inboxJobCounts = await inboxQueue.getJobCounts();
const dbJobCounts = await dbQueue.getJobCounts();
const objectStorageJobCounts = await objectStorageQueue.getJobCounts();
return {
deliver: deliverJobCounts,
inbox: inboxJobCounts,
db: dbJobCounts,
objectStorage: objectStorageJobCounts
};
});
@@ -0,0 +1,68 @@
import { URL } from "node:url";
import define from "../../../define.js";
import { addRelay } from "../../../../../services/relay.js";
import { ApiError } from "../../../error.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
errors: {
invalidUrl: {
message: "Invalid URL",
code: "INVALID_URL",
id: "fb8c92d3-d4e5-44e7-b3d4-800d5cef8b2c"
}
},
res: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id"
},
inbox: {
description: "URL of the inbox, must be a https scheme URL",
type: "string",
optional: false,
nullable: false,
format: "url"
},
status: {
type: "string",
optional: false,
nullable: false,
default: "requesting",
enum: [
"requesting",
"accepted",
"rejected"
]
}
}
}
};
export const paramDef = {
type: "object",
properties: {
inbox: {
type: "string"
}
},
required: [
"inbox"
]
};
export default define(meta, paramDef, async (ps, user)=>{
try {
if (new URL(ps.inbox).protocol !== "https:") throw new Error("https only");
} catch {
throw new ApiError(meta.errors.invalidUrl);
}
return await addRelay(ps.inbox);
});
@@ -0,0 +1,52 @@
import define from "../../../define.js";
import { listRelay } from "../../../../../services/relay.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id"
},
inbox: {
type: "string",
optional: false,
nullable: false,
format: "url"
},
status: {
type: "string",
optional: false,
nullable: false,
default: "requesting",
enum: [
"requesting",
"accepted",
"rejected"
]
}
}
}
}
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async (ps, user)=>{
return await listRelay();
});
@@ -0,0 +1,23 @@
import define from "../../../define.js";
import { removeRelay } from "../../../../../services/relay.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
inbox: {
type: "string"
}
},
required: [
"inbox"
]
};
export default define(meta, paramDef, async (ps, user)=>{
return await removeRelay(ps.inbox);
});
@@ -0,0 +1,61 @@
import define from "../../define.js";
// import bcrypt from "bcryptjs";
import rndstr from "rndstr";
import { Users, UserProfiles } from "../../../../models/index.js";
import { hashPassword } from "../../../../misc/password.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "object",
optional: false,
nullable: false,
properties: {
password: {
type: "string",
optional: false,
nullable: false,
minLength: 8,
maxLength: 8
}
}
}
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps)=>{
const user = await Users.findOneBy({
id: ps.userId
});
if (user == null) {
throw new Error("user not found");
}
if (user.isAdmin) {
throw new Error("cannot reset password of admin");
}
const passwd = rndstr("a-zA-Z0-9", 8);
// Generate hash of password
// const hash = bcrypt.hashSync(passwd);
const hash = await hashPassword(passwd);
await UserProfiles.update({
userId: user.id
}, {
password: hash
});
return {
password: passwd
};
});
@@ -0,0 +1,51 @@
import define from "../../define.js";
import { AbuseUserReports, Users } from "../../../../models/index.js";
import { getInstanceActor } from "../../../../services/instance-actor.js";
import { deliver } from "../../../../queue/index.js";
import { renderActivity } from "../../../../remote/activitypub/renderer/index.js";
import { renderFlag } from "../../../../remote/activitypub/renderer/flag.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
reportId: {
type: "string",
format: "misskey:id"
},
forward: {
type: "boolean",
default: false
}
},
required: [
"reportId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const report = await AbuseUserReports.findOneByOrFail({
id: ps.reportId
});
if (report == null) {
throw new Error("report not found");
}
if (ps.forward && report.targetUserHost != null) {
const actor = await getInstanceActor();
const targetUser = await Users.findOneByOrFail({
id: report.targetUserId
});
deliver(actor, renderActivity(renderFlag(actor, [
targetUser.uri
], report.comment)), targetUser.inbox);
}
await AbuseUserReports.update(report.id, {
resolved: true,
assigneeId: me.id,
forwarded: ps.forward && report.targetUserHost != null
});
});
@@ -0,0 +1,48 @@
import define from "../../define.js";
import { Users, VerifiedBadgeRequests } from "../../../../models/index.js";
import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
requestId: {
type: "string",
format: "misskey:id"
},
approve: {
type: "boolean"
}
},
required: [
"requestId",
"approve"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const request = await VerifiedBadgeRequests.findOneByOrFail({
id: ps.requestId
});
if (request.status !== "pending") {
throw new Error("request already resolved");
}
if (ps.approve) {
await Users.update(request.userId, {
isVerified: true
});
}
await VerifiedBadgeRequests.update(request.id, {
status: ps.approve ? "approved" : "rejected",
resolvedAt: new Date(),
resolverId: me.id
});
insertModerationLog(me, ps.approve ? "approveVerifiedBadgeRequest" : "rejectVerifiedBadgeRequest", {
targetId: request.userId,
requestId: request.id
});
});
@@ -0,0 +1,31 @@
import define from "../../define.js";
import { sendEmail } from "../../../../services/send-email.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
to: {
type: "string"
},
subject: {
type: "string"
},
text: {
type: "string"
}
},
required: [
"to",
"subject",
"text"
]
};
export default define(meta, paramDef, async (ps)=>{
await sendEmail(ps.to, ps.subject, ps.text, ps.text);
});
@@ -0,0 +1,70 @@
import * as sanitizeHtml from "sanitize-html";
import define from "../../define.js";
import { Users, UserProfiles } from "../../../../models/index.js";
import { ApiError } from "../../error.js";
import { sendEmail } from "../../../../services/send-email.js";
import { createNotification } from "../../../../services/create-notification.js";
import config from "../../../../config/index.js";
export const meta = {
tags: [
"users"
],
requireCredential: true,
requireModerator: true,
description: "Send a moderation notice.",
errors: {
noSuchUser: {
message: "No such user.",
code: "NO_SUCH_USER",
id: "1acefcb5-0959-43fd-9685-b48305736cb5"
},
noEmail: {
message: "No email for user.",
code: "NO_EMAIL",
id: "ac9d2d22-ef73-11ed-a05b-0242ac120003"
}
}
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
},
comment: {
type: "string",
minLength: 1,
maxLength: 2048
}
},
required: [
"userId",
"comment"
]
};
export default define(meta, paramDef, async (ps)=>{
const [user, profile] = await Promise.all([
Users.findOneBy({
id: ps.userId
}),
UserProfiles.findOneBy({
userId: ps.userId
})
]);
if (user == null || profile == null) {
throw new ApiError(meta.errors.noSuchUser);
}
createNotification(user.id, "app", {
customBody: ps.comment,
customHeader: "Moderation Notice",
customIcon: config?.images?.info
});
setImmediate(async ()=>{
const email = profile.email;
if (email == null) {
throw new ApiError(meta.errors.noEmail);
}
sendEmail(email, "Moderation notice", sanitizeHtml(ps.comment), sanitizeHtml(ps.comment));
});
});
@@ -0,0 +1,137 @@
import * as os from "node:os";
import si from "systeminformation";
import define from "../../define.js";
import { redisClient } from "../../../../db/redis.js";
import { db } from "../../../../db/postgre.js";
export const meta = {
requireCredential: true,
requireModerator: true,
tags: [
"admin",
"meta"
],
res: {
type: "object",
optional: false,
nullable: false,
properties: {
machine: {
type: "string",
optional: false,
nullable: false
},
os: {
type: "string",
optional: false,
nullable: false,
example: "linux"
},
node: {
type: "string",
optional: false,
nullable: false
},
psql: {
type: "string",
optional: false,
nullable: false
},
cpu: {
type: "object",
optional: false,
nullable: false,
properties: {
model: {
type: "string",
optional: false,
nullable: false
},
cores: {
type: "number",
optional: false,
nullable: false
}
}
},
mem: {
type: "object",
optional: false,
nullable: false,
properties: {
total: {
type: "number",
optional: false,
nullable: false,
format: "bytes"
}
}
},
fs: {
type: "object",
optional: false,
nullable: false,
properties: {
total: {
type: "number",
optional: false,
nullable: false,
format: "bytes"
},
used: {
type: "number",
optional: false,
nullable: false,
format: "bytes"
}
}
},
net: {
type: "object",
optional: false,
nullable: false,
properties: {
interface: {
type: "string",
optional: false,
nullable: false,
example: "eth0"
}
}
}
}
}
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async ()=>{
const memStats = await si.mem();
const fsStats = await si.fsSize();
const netInterface = await si.networkInterfaceDefault();
const redisServerInfo = await redisClient.info("Server");
const m = redisServerInfo.match(new RegExp("^redis_version:(.*)", "m"));
const redis_version = m?.[1];
return {
machine: os.hostname(),
os: os.platform(),
node: process.version,
psql: await db.query("SHOW server_version").then((x)=>x[0].server_version),
redis: redis_version,
cpu: {
model: os.cpus()[0].model,
cores: os.cpus().length
},
mem: {
total: memStats.total
},
fs: {
total: fsStats[0].size,
used: fsStats[0].used
},
net: {
interface: netInterface
}
};
});
@@ -0,0 +1,40 @@
import define from "../../define.js";
import { Users } from "../../../../models/index.js";
import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
},
isVerified: {
type: "boolean"
}
},
required: [
"userId",
"isVerified"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const user = await Users.findOneBy({
id: ps.userId
});
if (user == null) {
throw new Error("user not found");
}
await Users.update(user.id, {
isVerified: ps.isVerified
});
insertModerationLog(me, ps.isVerified ? "markAsVerified" : "unmarkAsVerified", {
targetId: user.id
});
});
@@ -0,0 +1,81 @@
import define from "../../define.js";
import { ModerationLogs } from "../../../../models/index.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id"
},
createdAt: {
type: "string",
optional: false,
nullable: false,
format: "date-time"
},
type: {
type: "string",
optional: false,
nullable: false
},
info: {
type: "object",
optional: false,
nullable: false
},
userId: {
type: "string",
optional: false,
nullable: false,
format: "id"
},
user: {
type: "object",
optional: false,
nullable: false,
ref: "UserDetailed"
}
}
}
}
};
export const paramDef = {
type: "object",
properties: {
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 10
},
sinceId: {
type: "string",
format: "misskey:id"
},
untilId: {
type: "string",
format: "misskey:id"
}
},
required: []
};
export default define(meta, paramDef, async (ps)=>{
const query = makePaginationQuery(ModerationLogs.createQueryBuilder("report"), ps.sinceId, ps.untilId);
const reports = await query.take(ps.limit).getMany();
return await ModerationLogs.packMany(reports);
});
@@ -0,0 +1,86 @@
import { Signins, UserProfiles, Users } from "../../../../models/index.js";
import define from "../../define.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "object",
nullable: false,
optional: false
}
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const [user, profile] = await Promise.all([
Users.findOneBy({
id: ps.userId
}),
UserProfiles.findOneBy({
userId: ps.userId
})
]);
if (user == null || profile == null) {
throw new Error("user not found");
}
const _me = await Users.findOneByOrFail({
id: me.id
});
if (_me.isModerator && !_me.isAdmin && user.isAdmin) {
throw new Error("cannot show info of admin");
}
if (!_me.isAdmin) {
return {
isModerator: user.isModerator,
isSilenced: user.isSilenced,
isSuspended: user.isSuspended,
moderationNote: profile.moderationNote
};
}
const maskedKeys = [
"accessToken",
"accessTokenSecret",
"refreshToken"
];
Object.keys(profile.integrations).forEach((integration)=>{
maskedKeys.forEach((key)=>profile.integrations[integration][key] = "<MASKED>");
});
const signins = await Signins.findBy({
userId: user.id
});
return {
email: profile.email,
emailVerified: profile.emailVerified,
autoAcceptFollowed: profile.autoAcceptFollowed,
noCrawle: profile.noCrawle,
preventAiLearning: profile.preventAiLearning,
alwaysMarkNsfw: profile.alwaysMarkNsfw,
carefulBot: profile.carefulBot,
injectFeaturedNote: profile.injectFeaturedNote,
receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
integrations: profile.integrations,
mutedWords: profile.mutedWords,
mutedInstances: profile.mutedInstances,
mutingNotificationTypes: profile.mutingNotificationTypes,
isModerator: user.isModerator,
isVerified: user.isVerified,
isSilenced: user.isSilenced,
isSuspended: user.isSuspended,
lastActiveDate: user.lastActiveDate,
moderationNote: profile.moderationNote,
signins
};
});
@@ -0,0 +1,161 @@
import { Users } from "../../../../models/index.js";
import define from "../../define.js";
import { sqlLikeEscape } from "../../../../misc/sql-like-escape.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true,
res: {
type: "array",
nullable: false,
optional: false,
items: {
type: "object",
nullable: false,
optional: false,
ref: "UserDetailed"
}
}
};
export const paramDef = {
type: "object",
properties: {
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 10
},
offset: {
type: "integer",
default: 0
},
sort: {
type: "string",
enum: [
"+follower",
"-follower",
"+createdAt",
"-createdAt",
"+updatedAt",
"-updatedAt"
]
},
state: {
type: "string",
enum: [
"all",
"alive",
"available",
"admin",
"moderator",
"adminOrModerator",
"silenced",
"suspended",
"verified"
],
default: "all"
},
origin: {
type: "string",
enum: [
"combined",
"local",
"remote"
],
default: "combined"
},
username: {
type: "string",
nullable: true,
default: null
},
hostname: {
type: "string",
nullable: true,
default: null,
description: "The local host is represented with `null`."
}
},
required: []
};
export default define(meta, paramDef, async (ps, me)=>{
const query = Users.createQueryBuilder("user");
switch(ps.state){
case "available":
query.where("user.isSuspended = FALSE");
break;
case "admin":
query.where("user.isAdmin = TRUE");
break;
case "moderator":
query.where("user.isModerator = TRUE");
break;
case "adminOrModerator":
query.where("user.isAdmin = TRUE OR user.isModerator = TRUE");
break;
case "alive":
query.where("user.updatedAt > :date", {
date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5)
});
break;
case "silenced":
query.where("user.isSilenced = TRUE");
break;
case "suspended":
query.where("user.isSuspended = TRUE");
break;
case "verified":
query.where("user.isVerified = TRUE");
break;
}
switch(ps.origin){
case "local":
query.andWhere("user.host IS NULL");
break;
case "remote":
query.andWhere("user.host IS NOT NULL");
break;
}
if (ps.username) {
query.andWhere("user.usernameLower like :username", {
username: `${sqlLikeEscape(ps.username.toLowerCase())}%`
});
}
if (ps.hostname) {
query.andWhere("user.host = :hostname", {
hostname: ps.hostname.toLowerCase()
});
}
switch(ps.sort){
case "+follower":
query.orderBy("user.followersCount", "DESC");
break;
case "-follower":
query.orderBy("user.followersCount", "ASC");
break;
case "+createdAt":
query.orderBy("user.createdAt", "DESC");
break;
case "-createdAt":
query.orderBy("user.createdAt", "ASC");
break;
case "+updatedAt":
query.orderBy("user.updatedAt", "DESC", "NULLS LAST");
break;
case "-updatedAt":
query.orderBy("user.updatedAt", "ASC", "NULLS FIRST");
break;
default:
query.orderBy("user.id", "ASC");
break;
}
query.take(ps.limit);
query.skip(ps.offset);
const users = await query.getMany();
return await Users.packMany(users, me, {
detail: true
});
});
@@ -0,0 +1,44 @@
import define from "../../define.js";
import { Users } from "../../../../models/index.js";
import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
import { publishInternalEvent } from "../../../../services/stream.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const user = await Users.findOneBy({
id: ps.userId
});
if (user == null) {
throw new Error("user not found");
}
if (user.isAdmin) {
throw new Error("cannot silence admin");
}
await Users.update(user.id, {
isSilenced: true
});
publishInternalEvent("userChangeSilencedState", {
id: user.id,
isSilenced: true
});
insertModerationLog(me, "silence", {
targetId: user.id
});
});
@@ -0,0 +1,76 @@
import define from "../../define.js";
import deleteFollowing from "../../../../services/following/delete.js";
import { Users, Followings, Notifications } from "../../../../models/index.js";
import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
import { doPostSuspend } from "../../../../services/suspend-user.js";
import { publishUserEvent } from "../../../../services/stream.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const user = await Users.findOneBy({
id: ps.userId
});
if (user == null) {
throw new Error("user not found");
}
if (user.isAdmin) {
throw new Error("cannot suspend admin");
}
if (user.isModerator) {
throw new Error("cannot suspend moderator");
}
await Users.update(user.id, {
isSuspended: true
});
insertModerationLog(me, "suspend", {
targetId: user.id
});
// Terminate streaming
if (Users.isLocalUser(user)) {
publishUserEvent(user.id, "terminate", {});
}
(async ()=>{
await doPostSuspend(user).catch((e)=>{});
await unFollowAll(user).catch((e)=>{});
await readAllNotify(user).catch((e)=>{});
})();
});
async function unFollowAll(follower) {
const followings = await Followings.findBy({
followerId: follower.id
});
for (const following of followings){
const followee = await Users.findOneBy({
id: following.followeeId
});
if (followee == null) {
throw new Error(`Cant find followee ${following.followeeId}`);
}
await deleteFollowing(follower, followee, true);
}
}
async function readAllNotify(notifier) {
await Notifications.update({
notifierId: notifier.id,
isRead: false
}, {
isRead: true
});
}
@@ -0,0 +1,41 @@
import define from "../../define.js";
import { Users } from "../../../../models/index.js";
import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
import { publishInternalEvent } from "../../../../services/stream.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const user = await Users.findOneBy({
id: ps.userId
});
if (user == null) {
throw new Error("user not found");
}
await Users.update(user.id, {
isSilenced: false
});
publishInternalEvent("userChangeSilencedState", {
id: user.id,
isSilenced: false
});
insertModerationLog(me, "unsilence", {
targetId: user.id
});
});
@@ -0,0 +1,38 @@
import define from "../../define.js";
import { Users } from "../../../../models/index.js";
import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
import { doPostUnsuspend } from "../../../../services/unsuspend-user.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const user = await Users.findOneBy({
id: ps.userId
});
if (user == null) {
throw new Error("user not found");
}
await Users.update(user.id, {
isSuspended: false
});
insertModerationLog(me, "unsuspend", {
targetId: user.id
});
doPostUnsuspend(user);
});
@@ -0,0 +1,671 @@
import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
import define from "../../define.js";
import { Metas } from "../../../../models/index.js";
import { Users } from "../../../../models/index.js";
import { IsNull } from "typeorm";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
disableRegistration: {
type: "boolean",
nullable: true
},
disableLocalTimeline: {
type: "boolean",
nullable: true
},
disableRecommendedTimeline: {
type: "boolean",
nullable: true
},
disableGlobalTimeline: {
type: "boolean",
nullable: true
},
defaultReaction: {
type: "string",
nullable: true
},
recommendedInstances: {
type: "array",
nullable: true,
items: {
type: "string"
}
},
pinnedUsers: {
type: "array",
nullable: true,
items: {
type: "string"
}
},
customMOTD: {
type: "array",
nullable: true,
items: {
type: "string"
}
},
customSplashIcons: {
type: "array",
nullable: true,
items: {
type: "string"
}
},
hiddenTags: {
type: "array",
nullable: true,
items: {
type: "string"
}
},
blockedHosts: {
type: "array",
nullable: true,
items: {
type: "string"
}
},
silencedHosts: {
type: "array",
nullable: true,
items: {
type: "string"
}
},
allowedHosts: {
type: "array",
nullable: true,
items: {
type: "string"
}
},
secureMode: {
type: "boolean",
nullable: true
},
privateMode: {
type: "boolean",
nullable: true
},
themeColor: {
type: "string",
nullable: true,
pattern: "^#[0-9a-fA-F]{6}$"
},
mascotImageUrl: {
type: "string",
nullable: true
},
bannerUrl: {
type: "string",
nullable: true
},
logoImageUrl: {
type: "string",
nullable: true
},
errorImageUrl: {
type: "string",
nullable: true
},
iconUrl: {
type: "string",
nullable: true
},
backgroundImageUrl: {
type: "string",
nullable: true
},
name: {
type: "string",
nullable: true
},
description: {
type: "string",
nullable: true
},
defaultLightTheme: {
type: "string",
nullable: true
},
defaultDarkTheme: {
type: "string",
nullable: true
},
localDriveCapacityMb: {
type: "integer"
},
remoteDriveCapacityMb: {
type: "integer"
},
lua4frozenDatabaseCapacityMb: {
type: "integer"
},
cacheRemoteFiles: {
type: "boolean"
},
emailRequiredForSignup: {
type: "boolean"
},
enableHcaptcha: {
type: "boolean"
},
hcaptchaSiteKey: {
type: "string",
nullable: true
},
hcaptchaSecretKey: {
type: "string",
nullable: true
},
enableRecaptcha: {
type: "boolean"
},
recaptchaSiteKey: {
type: "string",
nullable: true
},
recaptchaSecretKey: {
type: "string",
nullable: true
},
maintainerName: {
type: "string",
nullable: true
},
maintainerEmail: {
type: "string",
nullable: true
},
pinnedPages: {
type: "array",
items: {
type: "string"
}
},
pinnedClipId: {
type: "string",
format: "misskey:id",
nullable: true
},
langs: {
type: "array",
items: {
type: "string"
}
},
summalyProxy: {
type: "string",
nullable: true
},
deeplAuthKey: {
type: "string",
nullable: true
},
deeplIsPro: {
type: "boolean"
},
libreTranslateApiUrl: {
type: "string",
nullable: true
},
libreTranslateApiKey: {
type: "string",
nullable: true
},
enableGithubIntegration: {
type: "boolean"
},
githubClientId: {
type: "string",
nullable: true
},
githubClientSecret: {
type: "string",
nullable: true
},
enableDiscordIntegration: {
type: "boolean"
},
discordClientId: {
type: "string",
nullable: true
},
discordClientSecret: {
type: "string",
nullable: true
},
enableEmail: {
type: "boolean"
},
email: {
type: "string",
nullable: true
},
smtpSecure: {
type: "boolean"
},
smtpHost: {
type: "string",
nullable: true
},
smtpPort: {
type: "integer",
nullable: true
},
smtpUser: {
type: "string",
nullable: true
},
smtpPass: {
type: "string",
nullable: true
},
tosUrl: {
type: "string",
nullable: true
},
repositoryUrl: {
type: "string"
},
feedbackUrl: {
type: "string"
},
useObjectStorage: {
type: "boolean"
},
objectStorageBaseUrl: {
type: "string",
nullable: true
},
objectStorageBucket: {
type: "string",
nullable: true
},
objectStoragePrefix: {
type: "string",
nullable: true
},
objectStorageEndpoint: {
type: "string",
nullable: true
},
objectStorageRegion: {
type: "string",
nullable: true
},
objectStoragePort: {
type: "integer",
nullable: true
},
objectStorageAccessKey: {
type: "string",
nullable: true
},
objectStorageSecretKey: {
type: "string",
nullable: true
},
objectStorageUseSSL: {
type: "boolean"
},
objectStorageUseProxy: {
type: "boolean"
},
objectStorageSetPublicRead: {
type: "boolean"
},
objectStorageS3ForcePathStyle: {
type: "boolean"
},
enableIpLogging: {
type: "boolean"
},
enableActiveEmailValidation: {
type: "boolean"
},
experimentalFeatures: {
type: "object",
nullable: true,
properties: {
postImports: {
type: "boolean"
}
}
},
enableServerMachineStats: {
type: "boolean"
},
enableIdenticonGeneration: {
type: "boolean"
},
donationLink: {
type: "string",
nullable: true
},
autofollowedAccount: {
type: "string",
nullable: true
}
},
required: []
};
export default define(meta, paramDef, async (ps, me)=>{
const set = {};
if (typeof ps.disableRegistration === "boolean") {
set.disableRegistration = ps.disableRegistration;
}
if (typeof ps.disableLocalTimeline === "boolean") {
set.disableLocalTimeline = ps.disableLocalTimeline;
}
if (typeof ps.disableRecommendedTimeline === "boolean") {
set.disableRecommendedTimeline = ps.disableRecommendedTimeline;
}
if (typeof ps.disableGlobalTimeline === "boolean") {
set.disableGlobalTimeline = ps.disableGlobalTimeline;
}
if (typeof ps.defaultReaction === "string") {
set.defaultReaction = ps.defaultReaction;
}
if (Array.isArray(ps.pinnedUsers)) {
set.pinnedUsers = ps.pinnedUsers.filter(Boolean);
}
if (Array.isArray(ps.customMOTD)) {
set.customMOTD = ps.customMOTD.filter(Boolean);
}
if (Array.isArray(ps.customSplashIcons)) {
set.customSplashIcons = ps.customSplashIcons.filter(Boolean);
}
if (Array.isArray(ps.recommendedInstances)) {
set.recommendedInstances = ps.recommendedInstances.filter(Boolean);
if (set.recommendedInstances?.length > 0) {
set.recommendedInstances.forEach((instance, index)=>{
if (/^https?:\/\//i.test(instance)) {
set.recommendedInstances[index] = instance.replace(/^https?:\/\//i, "").replace(/\/$/, "");
}
});
}
}
if (Array.isArray(ps.hiddenTags)) {
set.hiddenTags = ps.hiddenTags.filter(Boolean);
}
if (Array.isArray(ps.blockedHosts)) {
let lastValue = "";
set.blockedHosts = ps.blockedHosts.sort().filter((h)=>{
const lv = lastValue;
lastValue = h;
return h !== "" && h !== lv;
});
}
if (Array.isArray(ps.silencedHosts)) {
let lastValue = "";
set.silencedHosts = ps.silencedHosts.sort().filter((h)=>{
const lv = lastValue;
lastValue = h;
return h !== "" && h !== lv;
});
}
if (ps.themeColor !== undefined) {
set.themeColor = ps.themeColor;
}
if (Array.isArray(ps.allowedHosts)) {
set.allowedHosts = ps.allowedHosts.filter(Boolean);
}
if (typeof ps.privateMode === "boolean") {
set.privateMode = ps.privateMode;
}
if (typeof ps.secureMode === "boolean") {
set.secureMode = ps.secureMode;
}
if (ps.mascotImageUrl !== undefined) {
set.mascotImageUrl = ps.mascotImageUrl;
}
if (ps.bannerUrl !== undefined) {
set.bannerUrl = ps.bannerUrl;
}
if (ps.logoImageUrl !== undefined) {
set.logoImageUrl = ps.logoImageUrl;
}
if (ps.iconUrl !== undefined) {
set.iconUrl = ps.iconUrl;
}
if (ps.backgroundImageUrl !== undefined) {
set.backgroundImageUrl = ps.backgroundImageUrl;
}
if (ps.logoImageUrl !== undefined) {
set.logoImageUrl = ps.logoImageUrl;
}
if (ps.name !== undefined) {
set.name = ps.name;
}
if (ps.description !== undefined) {
set.description = ps.description;
}
if (ps.defaultLightTheme !== undefined) {
set.defaultLightTheme = ps.defaultLightTheme;
}
if (ps.defaultDarkTheme !== undefined) {
set.defaultDarkTheme = ps.defaultDarkTheme;
}
if (ps.localDriveCapacityMb !== undefined) {
set.localDriveCapacityMb = ps.localDriveCapacityMb;
}
if (ps.remoteDriveCapacityMb !== undefined) {
set.remoteDriveCapacityMb = ps.remoteDriveCapacityMb;
}
if (ps.lua4frozenDatabaseCapacityMb !== undefined) {
set.lua4frozenDatabaseCapacityMb = ps.lua4frozenDatabaseCapacityMb;
}
if (ps.cacheRemoteFiles !== undefined) {
set.cacheRemoteFiles = ps.cacheRemoteFiles;
}
if (ps.emailRequiredForSignup !== undefined) {
set.emailRequiredForSignup = ps.emailRequiredForSignup;
}
if (ps.enableHcaptcha !== undefined) {
set.enableHcaptcha = ps.enableHcaptcha;
}
if (ps.hcaptchaSiteKey !== undefined) {
set.hcaptchaSiteKey = ps.hcaptchaSiteKey;
}
if (ps.hcaptchaSecretKey !== undefined) {
set.hcaptchaSecretKey = ps.hcaptchaSecretKey;
}
if (ps.enableRecaptcha !== undefined) {
set.enableRecaptcha = ps.enableRecaptcha;
}
if (ps.recaptchaSiteKey !== undefined) {
set.recaptchaSiteKey = ps.recaptchaSiteKey;
}
if (ps.recaptchaSecretKey !== undefined) {
set.recaptchaSecretKey = ps.recaptchaSecretKey;
}
if (ps.maintainerName !== undefined) {
set.maintainerName = ps.maintainerName;
}
if (ps.maintainerEmail !== undefined) {
set.maintainerEmail = ps.maintainerEmail;
}
if (Array.isArray(ps.langs)) {
set.langs = ps.langs.filter(Boolean);
}
if (Array.isArray(ps.pinnedPages)) {
set.pinnedPages = ps.pinnedPages.filter(Boolean);
}
if (ps.pinnedClipId !== undefined) {
set.pinnedClipId = ps.pinnedClipId;
}
if (ps.summalyProxy !== undefined) {
set.summalyProxy = ps.summalyProxy;
}
if (ps.enableGithubIntegration !== undefined) {
set.enableGithubIntegration = ps.enableGithubIntegration;
}
if (ps.githubClientId !== undefined) {
set.githubClientId = ps.githubClientId;
}
if (ps.githubClientSecret !== undefined) {
set.githubClientSecret = ps.githubClientSecret;
}
if (ps.enableDiscordIntegration !== undefined) {
set.enableDiscordIntegration = ps.enableDiscordIntegration;
}
if (ps.discordClientId !== undefined) {
set.discordClientId = ps.discordClientId;
}
if (ps.discordClientSecret !== undefined) {
set.discordClientSecret = ps.discordClientSecret;
}
if (ps.enableEmail !== undefined) {
set.enableEmail = ps.enableEmail;
}
if (ps.email !== undefined) {
set.email = ps.email;
}
if (ps.smtpSecure !== undefined) {
set.smtpSecure = ps.smtpSecure;
}
if (ps.smtpHost !== undefined) {
set.smtpHost = ps.smtpHost;
}
if (ps.smtpPort !== undefined) {
set.smtpPort = ps.smtpPort;
}
if (ps.smtpUser !== undefined) {
set.smtpUser = ps.smtpUser;
}
if (ps.smtpPass !== undefined) {
set.smtpPass = ps.smtpPass;
}
if (ps.errorImageUrl !== undefined) {
set.errorImageUrl = ps.errorImageUrl;
}
if (ps.tosUrl !== undefined) {
set.ToSUrl = ps.tosUrl;
}
if (ps.repositoryUrl !== undefined) {
set.repositoryUrl = ps.repositoryUrl;
}
if (ps.feedbackUrl !== undefined) {
set.feedbackUrl = ps.feedbackUrl;
}
if (ps.useObjectStorage !== undefined) {
set.useObjectStorage = ps.useObjectStorage;
}
if (ps.objectStorageBaseUrl !== undefined) {
set.objectStorageBaseUrl = ps.objectStorageBaseUrl;
}
if (ps.objectStorageBucket !== undefined) {
set.objectStorageBucket = ps.objectStorageBucket;
}
if (ps.objectStoragePrefix !== undefined) {
set.objectStoragePrefix = ps.objectStoragePrefix;
}
if (ps.objectStorageEndpoint !== undefined) {
set.objectStorageEndpoint = ps.objectStorageEndpoint;
}
if (ps.objectStorageRegion !== undefined) {
set.objectStorageRegion = ps.objectStorageRegion;
}
if (ps.objectStoragePort !== undefined) {
set.objectStoragePort = ps.objectStoragePort;
}
if (ps.objectStorageAccessKey !== undefined) {
set.objectStorageAccessKey = ps.objectStorageAccessKey;
}
if (ps.objectStorageSecretKey !== undefined) {
set.objectStorageSecretKey = ps.objectStorageSecretKey;
}
if (ps.objectStorageUseSSL !== undefined) {
set.objectStorageUseSSL = ps.objectStorageUseSSL;
}
if (ps.objectStorageUseProxy !== undefined) {
set.objectStorageUseProxy = ps.objectStorageUseProxy;
}
if (ps.objectStorageSetPublicRead !== undefined) {
set.objectStorageSetPublicRead = ps.objectStorageSetPublicRead;
}
if (ps.objectStorageS3ForcePathStyle !== undefined) {
set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle;
}
if (ps.deeplAuthKey !== undefined) {
if (ps.deeplAuthKey === "") {
set.deeplAuthKey = null;
} else {
set.deeplAuthKey = ps.deeplAuthKey;
}
}
if (ps.deeplIsPro !== undefined) {
set.deeplIsPro = ps.deeplIsPro;
}
if (ps.libreTranslateApiUrl !== undefined) {
if (ps.libreTranslateApiUrl === "") {
set.libreTranslateApiUrl = null;
} else {
set.libreTranslateApiUrl = ps.libreTranslateApiUrl;
}
}
if (ps.libreTranslateApiKey !== undefined) {
if (ps.libreTranslateApiKey === "") {
set.libreTranslateApiKey = null;
} else {
set.libreTranslateApiKey = ps.libreTranslateApiKey;
}
}
if (ps.enableIpLogging !== undefined) {
set.enableIpLogging = ps.enableIpLogging;
}
if (ps.enableActiveEmailValidation !== undefined) {
set.enableActiveEmailValidation = ps.enableActiveEmailValidation;
}
if (ps.experimentalFeatures !== undefined) {
set.experimentalFeatures = ps.experimentalFeatures || undefined;
}
if (ps.enableServerMachineStats !== undefined) {
set.enableServerMachineStats = ps.enableServerMachineStats;
}
if (ps.enableIdenticonGeneration !== undefined) {
set.enableIdenticonGeneration = ps.enableIdenticonGeneration;
}
if (ps.donationLink !== undefined) {
set.donationLink = ps.donationLink;
if (set.donationLink && !/^https?:\/\//i.test(set.donationLink)) {
set.donationLink = `https://${set.donationLink}`;
}
}
if (ps.autofollowedAccount !== undefined) {
if (ps.autofollowedAccount === null) {
set.autofollowedAccount = null;
} else {
// Verify account exists and is a local account
const user = await Users.findOneBy({
username: ps.autofollowedAccount,
host: IsNull()
});
if (user !== null) {
set.autofollowedAccount = user.username;
} else {
set.autofollowedAccount = null;
}
}
}
const meta = await Metas.findOne({
where: {},
order: {
id: "DESC"
}
});
if (meta) await Metas.update(meta.id, set);
else await Metas.save(set);
insertModerationLog(me, "updateMeta");
});
@@ -0,0 +1,38 @@
import { UserProfiles, Users } from "../../../../models/index.js";
import define from "../../define.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
},
text: {
type: "string"
}
},
required: [
"userId",
"text"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const user = await Users.findOneBy({
id: ps.userId
});
if (user == null) {
throw new Error("user not found");
}
await UserProfiles.update({
userId: user.id
}, {
moderationNote: ps.text
});
});
@@ -0,0 +1,51 @@
import define from "../../../define.js";
import { Plans, UserPlans, Users } from "../../../../../models/index.js";
import { genId } from "../../../../../misc/gen-id.js";
import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
},
planId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId",
"planId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
await Users.findOneByOrFail({
id: ps.userId
});
await Plans.findOneByOrFail({
id: ps.planId
});
const exists = await UserPlans.findOneBy({
userId: ps.userId,
planId: ps.planId
});
if (exists) return;
await UserPlans.insert({
id: genId(),
createdAt: new Date(),
userId: ps.userId,
planId: ps.planId
});
insertModerationLog(me, "addUserPlan", {
targetId: ps.userId,
planId: ps.planId
});
});
@@ -0,0 +1,38 @@
import define from "../../../define.js";
import { Plans, UserPlans, Users } from "../../../../../models/index.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps)=>{
await Users.findOneByOrFail({
id: ps.userId
});
const joins = await UserPlans.find({
where: {
userId: ps.userId
},
relations: [
"plan"
],
order: {
createdAt: "ASC"
}
});
return await Plans.packMany(joins.map((join)=>join.plan).filter((plan)=>plan != null));
});
@@ -0,0 +1,40 @@
import define from "../../../define.js";
import { UserPlans, Users } from "../../../../../models/index.js";
import { insertModerationLog } from "../../../../../services/insert-moderation-log.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
},
planId: {
type: "string",
format: "misskey:id"
}
},
required: [
"userId",
"planId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
await Users.findOneByOrFail({
id: ps.userId
});
await UserPlans.delete({
userId: ps.userId,
planId: ps.planId
});
insertModerationLog(me, "removeUserPlan", {
targetId: ps.userId,
planId: ps.planId
});
});
@@ -0,0 +1,36 @@
import define from "../../define.js";
import { insertModerationLog } from "../../../../services/insert-moderation-log.js";
import { db } from "../../../../db/postgre.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireModerator: true
};
export const paramDef = {
type: "object",
properties: {
full: {
type: "boolean"
},
analyze: {
type: "boolean"
}
},
required: [
"full",
"analyze"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const params = [];
if (ps.full) {
params.push("FULL");
}
if (ps.analyze) {
params.push("ANALYZE");
}
db.query(`VACUUM ${params.join(" ")}`);
insertModerationLog(me, "vacuum", ps);
});
@@ -0,0 +1,50 @@
import define from "../../define.js";
import { VerifiedBadgeRequests } from "../../../../models/index.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js";
export const meta = {
tags: [
"admin"
],
requireCredential: true,
requireAdmin: true
};
export const paramDef = {
type: "object",
properties: {
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 10
},
sinceId: {
type: "string",
format: "misskey:id"
},
untilId: {
type: "string",
format: "misskey:id"
},
state: {
type: "string",
enum: [
"all",
"pending",
"approved",
"rejected"
],
default: "pending"
}
},
required: []
};
export default define(meta, paramDef, async (ps)=>{
const query = makePaginationQuery(VerifiedBadgeRequests.createQueryBuilder("request"), ps.sinceId, ps.untilId);
if (ps.state !== "all") {
query.andWhere("request.status = :status", {
status: ps.state
});
}
const requests = await query.take(ps.limit).getMany();
return await VerifiedBadgeRequests.packMany(requests);
});