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);
});
@@ -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: [
"meta"
],
requireCredential: false,
requireCredentialPrivateMode: 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
},
isRead: {
type: "boolean",
optional: true,
nullable: false
},
showPopup: {
type: "boolean",
optional: false,
nullable: false
},
isGoodNews: {
type: "boolean",
optional: false,
nullable: false
}
}
}
}
};
export const paramDef = {
type: "object",
properties: {
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 10
},
withUnreads: {
type: "boolean",
default: false
},
sinceId: {
type: "string",
format: "misskey:id"
},
untilId: {
type: "string",
format: "misskey:id"
}
},
required: []
};
export default define(meta, paramDef, async (ps, user)=>{
const query = makePaginationQuery(Announcements.createQueryBuilder("announcement"), ps.sinceId, ps.untilId);
const announcements = await query.take(ps.limit).getMany();
if (user) {
const reads = (await AnnouncementReads.findBy({
userId: user.id
})).map((x)=>x.announcementId);
for (const announcement of announcements){
announcement.isRead = reads.includes(announcement.id);
}
}
return (ps.withUnreads ? announcements.filter((a)=>!a.isRead) : announcements).map((a)=>({
...a,
createdAt: a.createdAt.toISOString(),
updatedAt: a.updatedAt?.toISOString() ?? null
}));
});
@@ -0,0 +1,173 @@
import define from "../../define.js";
import { genId } from "../../../../misc/gen-id.js";
import { Antennas, UserLists, UserGroupJoinings } from "../../../../models/index.js";
import { ApiError } from "../../error.js";
import { publishInternalEvent } from "../../../../services/stream.js";
export const meta = {
tags: [
"antennas"
],
requireCredential: true,
kind: "write:account",
errors: {
noSuchUserList: {
message: "No such user list.",
code: "NO_SUCH_USER_LIST",
id: "95063e93-a283-4b8b-9aa5-bcdb8df69a7f"
},
noSuchUserGroup: {
message: "No such user group.",
code: "NO_SUCH_USER_GROUP",
id: "aa3c0b9a-8cae-47c0-92ac-202ce5906682"
},
tooManyAntennas: {
message: "Too many antennas.",
code: "TOO_MANY_ANTENNAS",
id: "c3a5a51e-04d4-11ee-be56-0242ac120002"
},
noKeywords: {
message: "No keywords.",
code: "NO_KEYWORDS",
id: "aa975b74-1ddb-11ee-be56-0242ac120002"
}
},
res: {
type: "object",
optional: false,
nullable: false,
ref: "Antenna"
}
};
export const paramDef = {
type: "object",
properties: {
name: {
type: "string",
minLength: 1,
maxLength: 100
},
src: {
type: "string",
enum: [
"home",
"all",
"users",
"list",
"group",
"instances"
]
},
userListId: {
type: "string",
format: "misskey:id",
nullable: true
},
userGroupId: {
type: "string",
format: "misskey:id",
nullable: true
},
keywords: {
type: "array",
items: {
type: "array",
items: {
type: "string"
}
}
},
excludeKeywords: {
type: "array",
items: {
type: "array",
items: {
type: "string"
}
}
},
users: {
type: "array",
items: {
type: "string"
}
},
instances: {
type: "array",
items: {
type: "string"
}
},
caseSensitive: {
type: "boolean"
},
withReplies: {
type: "boolean"
},
withFile: {
type: "boolean"
},
notify: {
type: "boolean"
}
},
required: [
"name",
"src",
"keywords",
"excludeKeywords",
"users",
"instances",
"caseSensitive",
"withReplies",
"withFile",
"notify"
]
};
export default define(meta, paramDef, async (ps, user)=>{
if (user.movedToUri != null) throw new ApiError(meta.errors.noSuchUserGroup);
if (ps.keywords.length === 0) throw new ApiError(meta.errors.noKeywords);
let userList;
let userGroupJoining;
const antennas = await Antennas.findBy({
userId: user.id
});
if (antennas.length > 5 && !user.isAdmin) {
throw new ApiError(meta.errors.tooManyAntennas);
}
if (ps.src === "list" && ps.userListId) {
userList = await UserLists.findOneBy({
id: ps.userListId,
userId: user.id
});
if (userList == null) {
throw new ApiError(meta.errors.noSuchUserList);
}
} else if (ps.src === "group" && ps.userGroupId) {
userGroupJoining = await UserGroupJoinings.findOneBy({
userGroupId: ps.userGroupId,
userId: user.id
});
if (userGroupJoining == null) {
throw new ApiError(meta.errors.noSuchUserGroup);
}
}
const antenna = await Antennas.insert({
id: genId(),
createdAt: new Date(),
userId: user.id,
name: ps.name,
src: ps.src,
userListId: userList ? userList.id : null,
userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null,
keywords: ps.keywords,
excludeKeywords: ps.excludeKeywords,
users: ps.users,
instances: ps.instances,
caseSensitive: ps.caseSensitive,
withReplies: ps.withReplies,
withFile: ps.withFile,
notify: ps.notify
}).then((x)=>Antennas.findOneByOrFail(x.identifiers[0]));
publishInternalEvent("antennaCreated", antenna);
return await Antennas.pack(antenna);
});
@@ -0,0 +1,41 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { Antennas } from "../../../../models/index.js";
import { publishInternalEvent } from "../../../../services/stream.js";
export const meta = {
tags: [
"antennas"
],
requireCredential: true,
kind: "write:account",
errors: {
noSuchAntenna: {
message: "No such antenna.",
code: "NO_SUCH_ANTENNA",
id: "b34dcf9d-348f-44bb-99d0-6c9314cfe2df"
}
}
};
export const paramDef = {
type: "object",
properties: {
antennaId: {
type: "string",
format: "misskey:id"
}
},
required: [
"antennaId"
]
};
export default define(meta, paramDef, async (ps, user)=>{
const antenna = await Antennas.findOneBy({
id: ps.antennaId,
userId: user.id
});
if (antenna == null) {
throw new ApiError(meta.errors.noSuchAntenna);
}
await Antennas.delete(antenna.id);
publishInternalEvent("antennaDeleted", antenna);
});
@@ -0,0 +1,32 @@
import define from "../../define.js";
import { Antennas } from "../../../../models/index.js";
export const meta = {
tags: [
"antennas",
"account"
],
requireCredential: true,
kind: "read:account",
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "Antenna"
}
}
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async (ps, me)=>{
const antennas = await Antennas.findBy({
userId: me.id
});
return await Promise.all(antennas.map((x)=>Antennas.pack(x)));
});
@@ -0,0 +1,41 @@
import define from "../../define.js";
import { Antennas } from "../../../../models/index.js";
export const meta = {
tags: [
"antennas",
"account"
],
requireCredential: true,
kind: "write:account"
};
export const paramDef = {
type: "object",
properties: {
antennaId: {
type: "string",
format: "misskey:id"
}
},
required: [
"antennaId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const antenna = await Antennas.findOneBy({
userId: me.id,
id: ps.antennaId
});
if (!antenna) {
return null;
}
// await AntennaNotes.update(
// {
// antennaId: antenna.id,
// read: false,
// },
// {
// read: true,
// },
// );
return true;
});
@@ -0,0 +1,125 @@
import define from "../../define.js";
import readNote from "../../../../services/note/read.js";
import { Antennas, Notes } from "../../../../models/index.js";
import { redisClient } from "../../../../db/redis.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js";
import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
import { ApiError } from "../../error.js";
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
import { generateExcludeMemorietQuery } from "../../common/generate-exclude-memoriet-query.js";
export const meta = {
tags: [
"antennas",
"account",
"notes"
],
requireCredential: true,
kind: "read:account",
errors: {
noSuchAntenna: {
message: "No such antenna.",
code: "NO_SUCH_ANTENNA",
id: "850926e0-fd3b-49b6-b69a-b28a5dbd82fe"
}
},
res: {
type: "object",
optional: false,
nullable: false,
properties: {
pagination: {
type: "string",
nullable: false,
optional: false
},
notes: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "Note"
}
}
}
}
};
export const paramDef = {
type: "object",
properties: {
antennaId: {
type: "string",
format: "misskey:id"
},
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 10
},
pagination: {
type: "string",
default: "+"
}
},
required: [
"antennaId"
]
};
export default define(meta, paramDef, async (ps, user)=>{
const antenna = await Antennas.findOneBy({
id: ps.antennaId,
userId: user.id
});
let pagination = ps.pagination || "+";
if (antenna == null) {
throw new ApiError(meta.errors.noSuchAntenna);
}
let notes = [];
let paginationMap = [];
while(notes.length < ps.limit && pagination !== "-1"){
// exclusive range
if (pagination != "+" && !pagination.startsWith("(")) pagination = `(${pagination}`;
const noteIdsRes = await redisClient.xrevrange(`antennaTimeline:${antenna.id}`, pagination, "-", "COUNT", ps.limit - notes.length);
const noteIds = noteIdsRes.map((x)=>x[1][1]);
if (noteIds.length === 0) {
pagination = "-1";
break;
}
const query = makePaginationQuery(Notes.createQueryBuilder("note")).where("note.id IN (:...noteIds)", {
noteIds: noteIds
}).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser").andWhere("note.visibility != 'home'");
generateVisibilityQuery(query, user);
generateMutedUserQuery(query, user);
generateBlockedUserQuery(query, user);
generateExcludeMemorietQuery(query);
pagination = noteIdsRes[noteIdsRes.length - 1][0];
paginationMap = paginationMap.concat(noteIdsRes.map((x)=>[
x[1][1],
x[0]
]));
notes = notes.concat(await query.take(ps.limit - notes.length).getMany());
}
if (notes.length === 0) {
return {
pagination: "-1",
notes: []
};
} else {
readNote(user.id, notes);
}
const packedNotes = (await Notes.packMany(notes, user)).sort((a, b)=>paginationMap.findIndex((p)=>p[0] == a.id) - paginationMap.findIndex((p)=>p[0] == b.id));
if (notes.length < ps.limit) {
pagination = "-1";
} else {
// I'm so sorry, FIXME: rewrite pagination system
pagination = paginationMap.find((p)=>p[0] == packedNotes[packedNotes.length - (packedNotes.length > 1 ? 2 : 1)].id)[1];
}
return {
pagination: pagination,
notes: packedNotes
};
});
@@ -0,0 +1,47 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { Antennas } from "../../../../models/index.js";
export const meta = {
tags: [
"antennas",
"account"
],
requireCredential: true,
kind: "read:account",
errors: {
noSuchAntenna: {
message: "No such antenna.",
code: "NO_SUCH_ANTENNA",
id: "c06569fb-b025-4f23-b22d-1fcd20d2816b"
}
},
res: {
type: "object",
optional: false,
nullable: false,
ref: "Antenna"
}
};
export const paramDef = {
type: "object",
properties: {
antennaId: {
type: "string",
format: "misskey:id"
}
},
required: [
"antennaId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
// Fetch the antenna
const antenna = await Antennas.findOneBy({
id: ps.antennaId,
userId: me.id
});
if (antenna == null) {
throw new ApiError(meta.errors.noSuchAntenna);
}
return await Antennas.pack(antenna);
});
@@ -0,0 +1,171 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { Antennas, UserLists, UserGroupJoinings } from "../../../../models/index.js";
import { publishInternalEvent } from "../../../../services/stream.js";
export const meta = {
tags: [
"antennas"
],
requireCredential: true,
kind: "write:account",
errors: {
noSuchAntenna: {
message: "No such antenna.",
code: "NO_SUCH_ANTENNA",
id: "10c673ac-8852-48eb-aa1f-f5b67f069290"
},
noSuchUserList: {
message: "No such user list.",
code: "NO_SUCH_USER_LIST",
id: "1c6b35c9-943e-48c2-81e4-2844989407f7"
},
noSuchUserGroup: {
message: "No such user group.",
code: "NO_SUCH_USER_GROUP",
id: "109ed789-b6eb-456e-b8a9-6059d567d385"
}
},
res: {
type: "object",
optional: false,
nullable: false,
ref: "Antenna"
}
};
export const paramDef = {
type: "object",
properties: {
antennaId: {
type: "string",
format: "misskey:id"
},
name: {
type: "string",
minLength: 1,
maxLength: 100
},
src: {
type: "string",
enum: [
"home",
"all",
"users",
"list",
"group",
"instances"
]
},
userListId: {
type: "string",
format: "misskey:id",
nullable: true
},
userGroupId: {
type: "string",
format: "misskey:id",
nullable: true
},
keywords: {
type: "array",
items: {
type: "array",
items: {
type: "string"
}
}
},
excludeKeywords: {
type: "array",
items: {
type: "array",
items: {
type: "string"
}
}
},
users: {
type: "array",
items: {
type: "string"
}
},
instances: {
type: "array",
items: {
type: "string"
}
},
caseSensitive: {
type: "boolean"
},
withReplies: {
type: "boolean"
},
withFile: {
type: "boolean"
},
notify: {
type: "boolean"
}
},
required: [
"antennaId",
"name",
"src",
"keywords",
"excludeKeywords",
"users",
"instances",
"caseSensitive",
"withReplies",
"withFile",
"notify"
]
};
export default define(meta, paramDef, async (ps, user)=>{
// Fetch the antenna
const antenna = await Antennas.findOneBy({
id: ps.antennaId,
userId: user.id
});
if (antenna == null) {
throw new ApiError(meta.errors.noSuchAntenna);
}
let userList;
let userGroupJoining;
if (ps.src === "list" && ps.userListId) {
userList = await UserLists.findOneBy({
id: ps.userListId,
userId: user.id
});
if (userList == null) {
throw new ApiError(meta.errors.noSuchUserList);
}
} else if (ps.src === "group" && ps.userGroupId) {
userGroupJoining = await UserGroupJoinings.findOneBy({
userGroupId: ps.userGroupId,
userId: user.id
});
if (userGroupJoining == null) {
throw new ApiError(meta.errors.noSuchUserGroup);
}
}
await Antennas.update(antenna.id, {
name: ps.name,
src: ps.src,
userListId: userList ? userList.id : null,
userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null,
keywords: ps.keywords,
excludeKeywords: ps.excludeKeywords,
users: ps.users,
instances: ps.instances,
caseSensitive: ps.caseSensitive,
withReplies: ps.withReplies,
withFile: ps.withFile,
notify: ps.notify
});
publishInternalEvent("antennaUpdated", await Antennas.findOneByOrFail({
id: antenna.id
}));
return await Antennas.pack(antenna.id);
});
@@ -0,0 +1,36 @@
import define from "../../define.js";
import Resolver from "../../../../remote/activitypub/resolver.js";
import { HOUR } from "../../../../const.js";
export const meta = {
tags: [
"federation"
],
requireCredential: true,
requireAdmin: true,
limit: {
duration: HOUR,
max: 30
},
errors: {},
res: {
type: "object",
optional: false,
nullable: false
}
};
export const paramDef = {
type: "object",
properties: {
uri: {
type: "string"
}
},
required: [
"uri"
]
};
export default define(meta, paramDef, async (ps)=>{
const resolver = new Resolver();
const object = await resolver.resolve(ps.uri);
return object;
});
@@ -0,0 +1,157 @@
import define from "../../define.js";
import { createPerson } from "../../../../remote/activitypub/models/person.js";
import { createNote } from "../../../../remote/activitypub/models/note.js";
import DbResolver from "../../../../remote/activitypub/db-resolver.js";
import Resolver from "../../../../remote/activitypub/resolver.js";
import { ApiError } from "../../error.js";
import { extractDbHost } from "../../../../misc/convert-host.js";
import { Users, Notes } from "../../../../models/index.js";
import { isActor, isPost, getApId } from "../../../../remote/activitypub/type.js";
import { MINUTE } from "../../../../const.js";
import { shouldBlockInstance } from "../../../../misc/should-block-instance.js";
import { updateQuestion } from "../../../../remote/activitypub/models/question.js";
import { populatePoll } from "../../../../models/repositories/note.js";
import { redisClient } from "../../../../db/redis.js";
export const meta = {
tags: [
"federation"
],
requireCredential: true,
limit: {
duration: MINUTE,
max: 10
},
errors: {
noSuchObject: {
message: "No such object.",
code: "NO_SUCH_OBJECT",
id: "dc94d745-1262-4e63-a17d-fecaa57efc82"
}
},
res: {
optional: false,
nullable: false,
oneOf: [
{
type: "object",
properties: {
type: {
type: "string",
optional: false,
nullable: false,
enum: [
"User"
]
},
object: {
type: "object",
optional: false,
nullable: false,
ref: "UserDetailedNotMe"
}
}
},
{
type: "object",
properties: {
type: {
type: "string",
optional: false,
nullable: false,
enum: [
"Note"
]
},
object: {
type: "object",
optional: false,
nullable: false,
ref: "Note"
}
}
}
]
}
};
export const paramDef = {
type: "object",
properties: {
uri: {
type: "string"
}
},
required: [
"uri"
]
};
export default define(meta, paramDef, async (ps, me)=>{
const object = await fetchAny(ps.uri, me);
if (object) {
return object;
} else {
throw new ApiError(meta.errors.noSuchObject);
}
});
/***
* Resolve User or Note from URI
*/ async function fetchAny(uri, me) {
// Wait if blocked.
if (await shouldBlockInstance(extractDbHost(uri))) return null;
const dbResolver = new DbResolver();
const resolver = new Resolver();
resolver.setUser(me);
const [user, note] = await Promise.all([
dbResolver.getUserFromApId(uri),
dbResolver.getNoteFromApId(uri)
]);
let local = await mergePack(me, user, note);
if (local) {
if (local.type === "Note" && note?.uri && note.hasPoll) {
// Update questions if the stored (remote) note contains the poll
const key = `pollFetched:${note.uri}`;
if (await redisClient.exists(key) === 0) {
if (await updateQuestion(note.uri, resolver)) {
local.object.poll = await populatePoll(note, me?.id ?? null);
}
// Allow fetching the poll again after 1 minute
await redisClient.set(key, 1, "EX", 60);
}
}
return local;
}
// fetching Object once from remote
const object = await resolver.resolve(uri);
// /@user If a URI other than the id is specified,
// the URI is determined here
if (uri !== object.id) {
local = await mergePack(me, ...await Promise.all([
dbResolver.getUserFromApId(getApId(object)),
dbResolver.getNoteFromApId(getApId(object))
]));
if (local != null) return local;
}
return await mergePack(me, isActor(object) ? await createPerson(getApId(object), resolver.reset()) : null, isPost(object) ? await createNote(getApId(object), resolver.reset(), true) : null);
}
async function mergePack(me, user, note) {
if (user != null) {
return {
type: "User",
object: await Users.pack(user, me, {
detail: true
})
};
} else if (note != null) {
try {
const object = await Notes.pack(note, me, {
detail: true
});
return {
type: "Note",
object
};
} catch (e) {
return null;
}
}
return null;
}
@@ -0,0 +1,69 @@
import define from "../../define.js";
import { Apps } from "../../../../models/index.js";
import { genId } from "../../../../misc/gen-id.js";
import { unique } from "../../../../prelude/array.js";
import { secureRndstr } from "../../../../misc/secure-rndstr.js";
export const meta = {
tags: [
"app"
],
requireCredential: false,
res: {
type: "object",
optional: false,
nullable: false,
ref: "App"
}
};
export const paramDef = {
type: "object",
properties: {
name: {
type: "string"
},
description: {
type: "string"
},
permission: {
type: "array",
uniqueItems: true,
items: {
type: "string"
}
},
callbackUrl: {
type: "string",
nullable: true
}
},
required: [
"name",
"description",
"permission"
]
};
export default define(meta, paramDef, async (ps, user)=>{
if (user?.movedToUri != null) return await Apps.pack("", null, {
detail: true,
includeSecret: true
});
// Generate secret
const secret = secureRndstr(32);
// for backward compatibility
const permission = unique(ps.permission.map((v)=>v.replace(/^(.+)(\/|-)(read|write)$/, "$3:$1")));
// Create account
const app = await Apps.insert({
id: genId(),
createdAt: new Date(),
userId: user ? user.id : null,
name: ps.name,
description: ps.description,
permission,
callbackUrl: ps.callbackUrl,
secret: secret
}).then((x)=>Apps.findOneByOrFail(x.identifiers[0]));
return await Apps.pack(app, null, {
detail: true,
includeSecret: true
});
});
@@ -0,0 +1,47 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { Apps } from "../../../../models/index.js";
export const meta = {
tags: [
"app"
],
errors: {
noSuchApp: {
message: "No such app.",
code: "NO_SUCH_APP",
id: "dce83913-2dc6-4093-8a7b-71dbb11718a3"
}
},
res: {
type: "object",
optional: false,
nullable: false,
ref: "App"
}
};
export const paramDef = {
type: "object",
properties: {
appId: {
type: "string",
format: "misskey:id"
}
},
required: [
"appId"
]
};
export default define(meta, paramDef, async (ps, user, token)=>{
const isSecure = user != null && token == null;
// Lookup app
const ap = await Apps.findOneBy({
id: ps.appId
});
if (ap == null) {
throw new ApiError(meta.errors.noSuchApp);
}
return await Apps.pack(ap, user, {
detail: true,
includeSecret: isSecure && ap.userId === user.id
});
});
@@ -0,0 +1,74 @@
import * as crypto from "node:crypto";
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { AuthSessions, AccessTokens, Apps } from "../../../../models/index.js";
import { genId } from "../../../../misc/gen-id.js";
import { secureRndstr } from "../../../../misc/secure-rndstr.js";
export const meta = {
tags: [
"auth"
],
requireCredential: true,
secure: true,
errors: {
noSuchSession: {
message: "No such session.",
code: "NO_SUCH_SESSION",
id: "9c72d8de-391a-43c1-9d06-08d29efde8df"
}
}
};
export const paramDef = {
type: "object",
properties: {
token: {
type: "string"
}
},
required: [
"token"
]
};
export default define(meta, paramDef, async (ps, user)=>{
// Fetch token
const session = await AuthSessions.findOneBy({
token: ps.token
});
if (session == null) {
throw new ApiError(meta.errors.noSuchSession);
}
// Generate access token
const accessToken = secureRndstr(32);
// Fetch exist access token
const exist = await AccessTokens.exist({
where: {
appId: session.appId,
userId: user.id
}
});
if (!exist) {
// Lookup app
const app = await Apps.findOneByOrFail({
id: session.appId
});
// Generate Hash
const sha256 = crypto.createHash("sha256");
sha256.update(accessToken + app.secret);
const hash = sha256.digest("hex");
const now = new Date();
// Insert access token doc
await AccessTokens.insert({
id: genId(),
createdAt: now,
lastUsedAt: now,
appId: session.appId,
userId: user.id,
token: accessToken,
hash: hash
});
}
// Update session
await AuthSessions.update(session.id, {
userId: user.id
});
});
@@ -0,0 +1,70 @@
import { v4 as uuid } from "uuid";
import config from "../../../../../config/index.js";
import define from "../../../define.js";
import { ApiError } from "../../../error.js";
import { Apps, AuthSessions } from "../../../../../models/index.js";
import { genId } from "../../../../../misc/gen-id.js";
export const meta = {
tags: [
"auth"
],
requireCredential: false,
res: {
type: "object",
optional: false,
nullable: false,
properties: {
token: {
type: "string",
optional: false,
nullable: false
},
url: {
type: "string",
optional: false,
nullable: false,
format: "url"
}
}
},
errors: {
noSuchApp: {
message: "No such app.",
code: "NO_SUCH_APP",
id: "92f93e63-428e-4f2f-a5a4-39e1407fe998"
}
}
};
export const paramDef = {
type: "object",
properties: {
appSecret: {
type: "string"
}
},
required: [
"appSecret"
]
};
export default define(meta, paramDef, async (ps)=>{
// Lookup app
const app = await Apps.findOneBy({
secret: ps.appSecret
});
if (app == null) {
throw new ApiError(meta.errors.noSuchApp);
}
// Generate token
const token = uuid();
// Create session token document
const doc = await AuthSessions.insert({
id: genId(),
createdAt: new Date(),
appId: app.id,
token: token
}).then((x)=>AuthSessions.findOneByOrFail(x.identifiers[0]));
return {
token: doc.token,
url: `${config.authUrl}/${doc.token}`
};
});
@@ -0,0 +1,61 @@
import define from "../../../define.js";
import { ApiError } from "../../../error.js";
import { AuthSessions } from "../../../../../models/index.js";
export const meta = {
tags: [
"auth"
],
requireCredential: false,
errors: {
noSuchSession: {
message: "No such session.",
code: "NO_SUCH_SESSION",
id: "bd72c97d-eba7-4adb-a467-f171b8847250"
}
},
res: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id"
},
app: {
type: "object",
optional: false,
nullable: false,
ref: "App"
},
token: {
type: "string",
optional: false,
nullable: false
}
}
}
};
export const paramDef = {
type: "object",
properties: {
token: {
type: "string"
}
},
required: [
"token"
]
};
export default define(meta, paramDef, async (ps, user)=>{
// Lookup session
const session = await AuthSessions.findOneBy({
token: ps.token
});
if (session == null) {
throw new ApiError(meta.errors.noSuchSession);
}
return await AuthSessions.pack(session, user);
});
@@ -0,0 +1,92 @@
import define from "../../../define.js";
import { ApiError } from "../../../error.js";
import { Apps, AuthSessions, AccessTokens, Users } from "../../../../../models/index.js";
export const meta = {
tags: [
"auth"
],
requireCredential: false,
res: {
type: "object",
optional: false,
nullable: false,
properties: {
accessToken: {
type: "string",
optional: false,
nullable: false
},
user: {
type: "object",
optional: false,
nullable: false,
ref: "UserDetailedNotMe"
}
}
},
errors: {
noSuchApp: {
message: "No such app.",
code: "NO_SUCH_APP",
id: "fcab192a-2c5a-43b7-8ad8-9b7054d8d40d"
},
noSuchSession: {
message: "No such session.",
code: "NO_SUCH_SESSION",
id: "5b5a1503-8bc8-4bd0-8054-dc189e8cdcb3"
},
pendingSession: {
message: "This session is not completed yet.",
code: "PENDING_SESSION",
id: "8c8a4145-02cc-4cca-8e66-29ba60445a8e"
}
}
};
export const paramDef = {
type: "object",
properties: {
appSecret: {
type: "string"
},
token: {
type: "string"
}
},
required: [
"appSecret",
"token"
]
};
export default define(meta, paramDef, async (ps)=>{
// Lookup app
const app = await Apps.findOneBy({
secret: ps.appSecret
});
if (app == null) {
throw new ApiError(meta.errors.noSuchApp);
}
// Fetch token
const session = await AuthSessions.findOneBy({
token: ps.token,
appId: app.id
});
if (session == null) {
throw new ApiError(meta.errors.noSuchSession);
}
if (session.userId == null) {
throw new ApiError(meta.errors.pendingSession);
}
// Lookup access token
const accessToken = await AccessTokens.findOneByOrFail({
appId: app.id,
userId: session.userId
});
// Delete session
AuthSessions.delete(session.id);
return {
accessToken: accessToken.token,
user: await Users.pack(session.userId, null, {
detail: true
})
};
});
@@ -0,0 +1,77 @@
import { Bites } from "../../../../models/index.js";
import define from "../../define.js";
import { createBite } from "../../../../services/create-bite.js";
import { MINUTE } from "../../../../const.js";
import { ApiError } from "../../error.js";
export const meta = {
tags: [
"bites"
],
requireCredential: true,
limit: {
duration: MINUTE,
max: 30
},
res: {
type: "object",
optional: false,
nullable: false,
ref: "Bite"
},
errors: {
noSuchNote: {
message: "No such note.",
code: "NO_SUCH_NOTE",
id: "7a80aef8-e4ca-43c2-a997-a6e9b0198374"
},
bitesDisabled: {
message: "User doesn't allow bites.",
code: "BITES_DISABLED",
id: "a8cfcada-42e1-4ef4-b15e-6fcd903458b7"
},
bitesFollowersOnly: {
message: "User only lets followers bite them.",
code: "BITES_FOLLOWERS_ONLY",
id: "26a2ed34-a1df-408c-9f75-d7459380fb60"
},
youHaveBeenBlocked: {
message: "You cannot bite because you have been blocked by this user.",
code: "YOU_HAVE_BEEN_BLOCKED",
id: "c15a5199-7422-4968-941a-2a462c478f7d"
}
}
};
export const paramDef = {
type: "object",
properties: {
targetType: {
type: "string",
enum: [
"user",
"bite",
"note"
]
},
targetId: {
type: "string",
format: "misskey:id"
}
},
required: [
"targetType",
"targetId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
let biteId;
try {
biteId = await createBite(me, ps.targetType, ps.targetId);
} catch (err) {
if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24") throw new ApiError(meta.errors.noSuchNote);
if (err.id === "f82d8d34-beaf-42f3-9135-477d32288213") throw new ApiError(meta.errors.youHaveBeenBlocked);
if (err.id === "35363f14-f489-45e2-81a9-558450710dfe") throw new ApiError(meta.errors.bitesFollowersOnly);
if (err.id === "92ce0141-760d-4163-a7a2-73b349e3d133") throw new ApiError(meta.errors.bitesDisabled);
throw err;
}
return await Bites.pack(biteId, me);
});
@@ -0,0 +1,28 @@
import { Bites } from "../../../../models/index.js";
import define from "../../define.js";
export const meta = {
tags: [
"bites"
],
res: {
type: "object",
optional: false,
nullable: false,
ref: "Bite"
}
};
export const paramDef = {
type: "object",
properties: {
biteId: {
type: "string",
format: "misskey:id"
}
},
required: [
"biteId"
]
};
export default define(meta, paramDef, async (ps, me)=>{
return await Bites.pack(ps.biteId, me);
});
@@ -0,0 +1,104 @@
import create from "../../../../services/blocking/create.js";
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { getUser } from "../../common/getters.js";
import { Blockings, NoteWatchings, Users } from "../../../../models/index.js";
import { HOUR } from "../../../../const.js";
import { getGroupActor } from "../../common/get-group-actor.js";
export const meta = {
tags: [
"account"
],
limit: {
duration: HOUR,
max: 100
},
requireCredential: true,
kind: "write:blocks",
errors: {
noSuchUser: {
message: "No such user.",
code: "NO_SUCH_USER",
id: "7cc4f851-e2f1-4621-9633-ec9e1d00c01e"
},
blockeeIsYourself: {
message: "Blockee is yourself.",
code: "BLOCKEE_IS_YOURSELF",
id: "88b19138-f28d-42c0-8499-6a31bbd0fdc6"
},
alreadyBlocking: {
message: "You are already blocking that user.",
code: "ALREADY_BLOCKING",
id: "787fed64-acb9-464a-82eb-afbd745b9614"
},
noSuchGroup: {
message: "No such group.",
code: "NO_SUCH_GROUP",
id: "8193cd43-8319-4383-9591-a093cbb4aa3a"
}
},
res: {
type: "object",
optional: false,
nullable: false,
ref: "UserDetailedNotMe"
}
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
},
groupId: {
type: "string",
format: "misskey:id",
nullable: true
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps, user)=>{
const blocker = await Users.findOneByOrFail({
id: user.id
});
const group = await getGroupActor(ps.groupId, user);
if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
// 自分自身
if (group == null && user.id === ps.userId) {
throw new ApiError(meta.errors.blockeeIsYourself);
}
// Get blockee
const blockee = await getUser(ps.userId).catch((e)=>{
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
throw e;
});
// Check if already blocking
const exist = await Blockings.exist({
where: {
blockeeId: blockee.id,
...group ? {
groupId: group.id
} : {
blockerId: blocker.id,
groupId: null
}
}
});
if (exist) {
throw new ApiError(meta.errors.alreadyBlocking);
}
await create(blocker, blockee, group?.id ?? null);
if (group == null) {
NoteWatchings.delete({
userId: blocker.id,
noteUserId: blockee.id
});
}
return await Users.pack(blockee.id, blocker, {
detail: true
});
});
@@ -0,0 +1,99 @@
import deleteBlocking from "../../../../services/blocking/delete.js";
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { getUser } from "../../common/getters.js";
import { Blockings, Users } from "../../../../models/index.js";
import { HOUR } from "../../../../const.js";
import { getGroupActor } from "../../common/get-group-actor.js";
export const meta = {
tags: [
"account"
],
limit: {
duration: HOUR,
max: 100
},
requireCredential: true,
kind: "write:blocks",
errors: {
noSuchUser: {
message: "No such user.",
code: "NO_SUCH_USER",
id: "8621d8bf-c358-4303-a066-5ea78610eb3f"
},
blockeeIsYourself: {
message: "Blockee is yourself.",
code: "BLOCKEE_IS_YOURSELF",
id: "06f6fac6-524b-473c-a354-e97a40ae6eac"
},
notBlocking: {
message: "You are not blocking that user.",
code: "NOT_BLOCKING",
id: "291b2efa-60c6-45c0-9f6a-045c8f9b02cd"
},
noSuchGroup: {
message: "No such group.",
code: "NO_SUCH_GROUP",
id: "b797376f-f8cc-405b-a1d6-250b84cc80c2"
}
},
res: {
type: "object",
optional: false,
nullable: false,
ref: "UserDetailedNotMe"
}
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
},
groupId: {
type: "string",
format: "misskey:id",
nullable: true
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps, user)=>{
const blocker = await Users.findOneByOrFail({
id: user.id
});
const group = await getGroupActor(ps.groupId, user);
if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
// Check if the blockee is yourself
if (group == null && user.id === ps.userId) {
throw new ApiError(meta.errors.blockeeIsYourself);
}
// Get blockee
const blockee = await getUser(ps.userId).catch((e)=>{
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
throw e;
});
// Check not blocking
const exist = await Blockings.exist({
where: {
blockeeId: blockee.id,
...group ? {
groupId: group.id
} : {
blockerId: blocker.id,
groupId: null
}
}
});
if (!exist) {
throw new ApiError(meta.errors.notBlocking);
}
// Delete blocking
await deleteBlocking(blocker, blockee, group?.id ?? null);
return await Users.pack(blockee.id, blocker, {
detail: true
});
});
@@ -0,0 +1,72 @@
import define from "../../define.js";
import { Blockings } from "../../../../models/index.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js";
import { ApiError } from "../../error.js";
import { getGroupActor } from "../../common/get-group-actor.js";
export const meta = {
tags: [
"account"
],
requireCredential: true,
kind: "read:blocks",
errors: {
noSuchGroup: {
message: "No such group.",
code: "NO_SUCH_GROUP",
id: "b31c1162-4adf-4b4b-b3e3-3f2fb8ffcd5f"
}
},
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "Blocking"
}
}
};
export const paramDef = {
type: "object",
properties: {
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 30
},
sinceId: {
type: "string",
format: "misskey:id"
},
untilId: {
type: "string",
format: "misskey:id"
},
groupId: {
type: "string",
format: "misskey:id",
nullable: true
}
},
required: []
};
export default define(meta, paramDef, async (ps, me)=>{
const group = await getGroupActor(ps.groupId, me);
if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
const query = makePaginationQuery(Blockings.createQueryBuilder("blocking"), ps.sinceId, ps.untilId);
if (group) {
query.andWhere("blocking.groupId = :groupId", {
groupId: group.id
});
} else {
query.andWhere("blocking.blockerId = :meId", {
meId: me.id
});
query.andWhere("blocking.groupId IS NULL");
}
const blockings = await query.take(ps.limit).getMany();
return await Blockings.packMany(blockings, me);
});
@@ -0,0 +1,103 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { getUser } from "../../common/getters.js";
import { CallBlockings, Users } from "../../../../models/index.js";
import { HOUR } from "../../../../const.js";
import { genId } from "../../../../misc/gen-id.js";
import { getGroupActor } from "../../common/get-group-actor.js";
export const meta = {
tags: [
"account"
],
limit: {
duration: HOUR,
max: 100
},
requireCredential: true,
kind: "write:blocks",
errors: {
noSuchUser: {
message: "No such user.",
code: "NO_SUCH_USER",
id: "e476b7c0-03fd-44d3-8de2-efc43b15b7e0"
},
blockeeIsYourself: {
message: "Blockee is yourself.",
code: "BLOCKEE_IS_YOURSELF",
id: "ee4c68c6-2a3d-4e13-8842-d4e7a341f109"
},
alreadyBlocking: {
message: "You are already rejecting calls from that user.",
code: "ALREADY_CALL_BLOCKING",
id: "9601ea36-97cd-4232-b2d8-326e42e15df4"
},
noSuchGroup: {
message: "No such group.",
code: "NO_SUCH_GROUP",
id: "1d7a15c6-41e5-4e5c-9ce5-a59b83a3b1ee"
}
},
res: {
type: "object",
optional: false,
nullable: false,
ref: "UserDetailedNotMe"
}
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
},
groupId: {
type: "string",
format: "misskey:id",
nullable: true
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps, user)=>{
const blocker = await Users.findOneByOrFail({
id: user.id
});
const group = await getGroupActor(ps.groupId, user);
if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
if (group == null && user.id === ps.userId) {
throw new ApiError(meta.errors.blockeeIsYourself);
}
const blockee = await getUser(ps.userId).catch((e)=>{
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
throw e;
});
const exist = await CallBlockings.exist({
where: {
blockeeId: blockee.id,
...group ? {
groupId: group.id
} : {
blockerId: blocker.id,
groupId: null
}
}
});
if (exist) {
throw new ApiError(meta.errors.alreadyBlocking);
}
await CallBlockings.insert({
id: genId(),
createdAt: new Date(),
blockerId: blocker.id,
blockeeId: blockee.id,
groupId: group?.id ?? null,
blocker,
blockee
});
return await Users.pack(blockee.id, blocker, {
detail: true
});
});
@@ -0,0 +1,92 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { getUser } from "../../common/getters.js";
import { CallBlockings, Users } from "../../../../models/index.js";
import { HOUR } from "../../../../const.js";
import { getGroupActor } from "../../common/get-group-actor.js";
export const meta = {
tags: [
"account"
],
limit: {
duration: HOUR,
max: 100
},
requireCredential: true,
kind: "write:blocks",
errors: {
noSuchUser: {
message: "No such user.",
code: "NO_SUCH_USER",
id: "84d4f8bd-c78f-4b64-a542-76daefbd2338"
},
blockeeIsYourself: {
message: "Blockee is yourself.",
code: "BLOCKEE_IS_YOURSELF",
id: "8b347b91-c674-45af-8f4f-1fbbbcbd6f08"
},
notBlocking: {
message: "You are not rejecting calls from that user.",
code: "NOT_CALL_BLOCKING",
id: "7bd363a2-278f-46f8-a03a-ee1220302a3c"
},
noSuchGroup: {
message: "No such group.",
code: "NO_SUCH_GROUP",
id: "27f63aa2-58cc-418a-8e9b-83d13f46048f"
}
},
res: {
type: "object",
optional: false,
nullable: false,
ref: "UserDetailedNotMe"
}
};
export const paramDef = {
type: "object",
properties: {
userId: {
type: "string",
format: "misskey:id"
},
groupId: {
type: "string",
format: "misskey:id",
nullable: true
}
},
required: [
"userId"
]
};
export default define(meta, paramDef, async (ps, user)=>{
const blocker = await Users.findOneByOrFail({
id: user.id
});
const group = await getGroupActor(ps.groupId, user);
if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
if (group == null && user.id === ps.userId) {
throw new ApiError(meta.errors.blockeeIsYourself);
}
const blockee = await getUser(ps.userId).catch((e)=>{
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
throw e;
});
const blocking = await CallBlockings.findOneBy({
blockeeId: blockee.id,
...group ? {
groupId: group.id
} : {
blockerId: blocker.id,
groupId: null
}
});
if (!blocking) {
throw new ApiError(meta.errors.notBlocking);
}
await CallBlockings.delete(blocking.id);
return await Users.pack(blockee.id, blocker, {
detail: true
});
});
@@ -0,0 +1,71 @@
import define from "../../define.js";
import { CallBlockings } from "../../../../models/index.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js";
import { ApiError } from "../../error.js";
import { getGroupActor } from "../../common/get-group-actor.js";
export const meta = {
tags: [
"account"
],
requireCredential: true,
kind: "read:blocks",
errors: {
noSuchGroup: {
message: "No such group.",
code: "NO_SUCH_GROUP",
id: "a863670f-a52a-4bf7-8ddf-d64a83a22dda"
}
},
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false
}
}
};
export const paramDef = {
type: "object",
properties: {
limit: {
type: "integer",
minimum: 1,
maximum: 100,
default: 30
},
sinceId: {
type: "string",
format: "misskey:id"
},
untilId: {
type: "string",
format: "misskey:id"
},
groupId: {
type: "string",
format: "misskey:id",
nullable: true
}
},
required: []
};
export default define(meta, paramDef, async (ps, me)=>{
const group = await getGroupActor(ps.groupId, me);
if (ps.groupId != null && group == null) throw new ApiError(meta.errors.noSuchGroup);
const query = makePaginationQuery(CallBlockings.createQueryBuilder("call_blocking"), ps.sinceId, ps.untilId);
if (group) {
query.andWhere("call_blocking.groupId = :groupId", {
groupId: group.id
});
} else {
query.andWhere("call_blocking.blockerId = :meId", {
meId: me.id
});
query.andWhere("call_blocking.groupId IS NULL");
}
const blockings = await query.take(ps.limit).getMany();
return await CallBlockings.packMany(blockings, me);
});
@@ -0,0 +1,69 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { Channels, DriveFiles } from "../../../../models/index.js";
import { genId } from "../../../../misc/gen-id.js";
export const meta = {
tags: [
"channels"
],
requireCredential: true,
kind: "write:channels",
res: {
type: "object",
optional: false,
nullable: false,
ref: "Channel"
},
errors: {
noSuchFile: {
message: "No such file.",
code: "NO_SUCH_FILE",
id: "cd1e9f3e-5a12-4ab4-96f6-5d0a2cc32050"
}
}
};
export const paramDef = {
type: "object",
properties: {
name: {
type: "string",
minLength: 1,
maxLength: 128
},
description: {
type: "string",
nullable: true,
minLength: 1,
maxLength: 2048
},
bannerId: {
type: "string",
format: "misskey:id",
nullable: true
}
},
required: [
"name"
]
};
export default define(meta, paramDef, async (ps, user)=>{
let banner = null;
if (ps.bannerId != null) {
banner = await DriveFiles.findOneBy({
id: ps.bannerId,
userId: user.id
});
if (banner == null) {
throw new ApiError(meta.errors.noSuchFile);
}
}
const channel = await Channels.insert({
id: genId(),
createdAt: new Date(),
userId: user.id,
name: ps.name,
description: ps.description || null,
bannerId: banner ? banner.id : null
}).then((x)=>Channels.findOneByOrFail(x.identifiers[0]));
return await Channels.pack(channel, user);
});
@@ -0,0 +1,29 @@
import define from "../../define.js";
import { Channels } from "../../../../models/index.js";
export const meta = {
tags: [
"channels"
],
requireCredential: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "Channel"
}
}
};
export const paramDef = {
type: "object",
properties: {},
required: []
};
export default define(meta, paramDef, async (ps, me)=>{
const query = Channels.createQueryBuilder("channel").where("channel.lastNotedAt IS NOT NULL").orderBy("channel.lastNotedAt", "DESC");
const channels = await query.take(10).getMany();
return await Promise.all(channels.map((x)=>Channels.pack(x, me)));
});

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