Fixed 267U.pre2
This commit is contained in:
@@ -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);
|
||||
}
|
||||
});
|
||||
+31
@@ -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
Reference in New Issue
Block a user