Fixed 267U.pre2
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { publishMainStream } from "../../../../../services/stream.js";
|
||||
import * as OTPAuth from "otpauth";
|
||||
import define from "../../../define.js";
|
||||
import { Users, UserProfiles } from "../../../../../models/index.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
token: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"token"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const token = ps.token.replace(/\s/g, "");
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
if (profile.twoFactorTempSecret == null) {
|
||||
throw new Error("二段階認証の設定が開始されていません");
|
||||
}
|
||||
const delta = OTPAuth.TOTP.validate({
|
||||
secret: OTPAuth.Secret.fromBase32(profile.twoFactorTempSecret),
|
||||
digits: 6,
|
||||
token,
|
||||
window: 1
|
||||
});
|
||||
if (delta === null) {
|
||||
throw new Error("not verified");
|
||||
}
|
||||
await UserProfiles.update(user.id, {
|
||||
twoFactorSecret: profile.twoFactorTempSecret,
|
||||
twoFactorEnabled: true
|
||||
});
|
||||
const iObj = await Users.pack(user.id, user, {
|
||||
detail: true,
|
||||
includeSecrets: true
|
||||
});
|
||||
publishMainStream(user.id, "meUpdated", iObj);
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
import * as cbor from "cbor";
|
||||
import define from "../../../define.js";
|
||||
import { UserProfiles, UserSecurityKeys, AttestationChallenges, Users } from "../../../../../models/index.js";
|
||||
import config from "../../../../../config/index.js";
|
||||
import { procedures, hash } from "../../../2fa.js";
|
||||
import { publishMainStream } from "../../../../../services/stream.js";
|
||||
import { comparePassword } from "../../../../../misc/password.js";
|
||||
const rpIdHashReal = hash(Buffer.from(config.hostname, "utf-8"));
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
clientDataJSON: {
|
||||
type: "string"
|
||||
},
|
||||
attestationObject: {
|
||||
type: "string"
|
||||
},
|
||||
password: {
|
||||
type: "string"
|
||||
},
|
||||
challengeId: {
|
||||
type: "string"
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 30
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"clientDataJSON",
|
||||
"attestationObject",
|
||||
"password",
|
||||
"challengeId",
|
||||
"name"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
// Compare password
|
||||
const same = await comparePassword(ps.password, profile.password);
|
||||
if (!same) {
|
||||
throw new Error("incorrect password");
|
||||
}
|
||||
if (!profile.twoFactorEnabled) {
|
||||
throw new Error("2fa not enabled");
|
||||
}
|
||||
const clientData = JSON.parse(ps.clientDataJSON);
|
||||
if (clientData.type !== "webauthn.create") {
|
||||
throw new Error("not a creation attestation");
|
||||
}
|
||||
if (clientData.origin !== `${config.scheme}://${config.host}`) {
|
||||
throw new Error("origin mismatch");
|
||||
}
|
||||
const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, "utf-8"));
|
||||
const attestation = await cbor.decodeFirst(ps.attestationObject);
|
||||
const rpIdHash = attestation.authData.slice(0, 32);
|
||||
if (!rpIdHashReal.equals(rpIdHash)) {
|
||||
throw new Error("rpIdHash mismatch");
|
||||
}
|
||||
const flags = attestation.authData[32];
|
||||
if (!(flags & 1)) {
|
||||
throw new Error("user not present");
|
||||
}
|
||||
const authData = Buffer.from(attestation.authData);
|
||||
const credentialIdLength = authData.readUInt16BE(53);
|
||||
const credentialId = authData.slice(55, 55 + credentialIdLength);
|
||||
const publicKeyData = authData.slice(55 + credentialIdLength);
|
||||
const publicKey = await cbor.decodeFirst(publicKeyData);
|
||||
if (publicKey.get(3) !== -7) {
|
||||
throw new Error("alg mismatch");
|
||||
}
|
||||
if (!procedures[attestation.fmt]) {
|
||||
throw new Error("unsupported fmt");
|
||||
}
|
||||
const verificationData = procedures[attestation.fmt].verify({
|
||||
attStmt: attestation.attStmt,
|
||||
authenticatorData: authData,
|
||||
clientDataHash: clientDataJSONHash,
|
||||
credentialId,
|
||||
publicKey,
|
||||
rpIdHash
|
||||
});
|
||||
if (!verificationData.valid) throw new Error("signature invalid");
|
||||
const attestationChallenge = await AttestationChallenges.findOneBy({
|
||||
userId: user.id,
|
||||
id: ps.challengeId,
|
||||
registrationChallenge: true,
|
||||
challenge: hash(clientData.challenge).toString("hex")
|
||||
});
|
||||
if (!attestationChallenge) {
|
||||
throw new Error("non-existent challenge");
|
||||
}
|
||||
await AttestationChallenges.delete({
|
||||
userId: user.id,
|
||||
id: ps.challengeId
|
||||
});
|
||||
// Expired challenge (> 5min old)
|
||||
if (new Date().getTime() - attestationChallenge.createdAt.getTime() >= 5 * 60 * 1000) {
|
||||
throw new Error("expired challenge");
|
||||
}
|
||||
const credentialIdString = credentialId.toString("hex");
|
||||
await UserSecurityKeys.insert({
|
||||
userId: user.id,
|
||||
id: credentialIdString,
|
||||
lastUsed: new Date(),
|
||||
name: ps.name,
|
||||
publicKey: verificationData.publicKey.toString("hex")
|
||||
});
|
||||
// Publish meUpdated event
|
||||
publishMainStream(user.id, "meUpdated", await Users.pack(user.id, user, {
|
||||
detail: true,
|
||||
includeSecrets: true
|
||||
}));
|
||||
return {
|
||||
id: credentialIdString,
|
||||
name: ps.name
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import define from "../../../define.js";
|
||||
import { Users, UserProfiles, UserSecurityKeys } from "../../../../../models/index.js";
|
||||
import { publishMainStream } from "../../../../../services/stream.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true,
|
||||
errors: {
|
||||
noKey: {
|
||||
message: "No security key.",
|
||||
code: "NO_SECURITY_KEY",
|
||||
id: "f9c54d7f-d4c2-4d3c-9a8g-a70daac86512"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
value: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"value"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
if (ps.value === true) {
|
||||
// セキュリティキーがなければパスワードレスを有効にはできない
|
||||
const keyCount = await UserSecurityKeys.count({
|
||||
where: {
|
||||
userId: user.id
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
lastUsed: true
|
||||
}
|
||||
});
|
||||
if (keyCount === 0) {
|
||||
await UserProfiles.update(user.id, {
|
||||
usePasswordLessLogin: false
|
||||
});
|
||||
throw new ApiError(meta.errors.noKey);
|
||||
}
|
||||
}
|
||||
await UserProfiles.update(user.id, {
|
||||
usePasswordLessLogin: ps.value
|
||||
});
|
||||
const iObj = await Users.pack(user.id, user, {
|
||||
detail: true,
|
||||
includeSecrets: true
|
||||
});
|
||||
publishMainStream(user.id, "meUpdated", iObj);
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import define from "../../../define.js";
|
||||
import { UserProfiles, AttestationChallenges } from "../../../../../models/index.js";
|
||||
import { promisify } from "node:util";
|
||||
import * as crypto from "node:crypto";
|
||||
import { genId } from "../../../../../misc/gen-id.js";
|
||||
import { hash } from "../../../2fa.js";
|
||||
import { comparePassword } from "../../../../../misc/password.js";
|
||||
const randomBytes = promisify(crypto.randomBytes);
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
password: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"password"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
// Compare password
|
||||
const same = await comparePassword(ps.password, profile.password);
|
||||
if (!same) {
|
||||
throw new Error("incorrect password");
|
||||
}
|
||||
// if (!profile.twoFactorEnabled) {
|
||||
// throw new Error("2fa not enabled");
|
||||
// }
|
||||
// 32 byte challenge
|
||||
const entropy = await randomBytes(32);
|
||||
const challenge = entropy.toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
||||
const challengeId = genId();
|
||||
await AttestationChallenges.insert({
|
||||
userId: user.id,
|
||||
id: challengeId,
|
||||
challenge: hash(Buffer.from(challenge, "utf-8")).toString("hex"),
|
||||
createdAt: new Date(),
|
||||
registrationChallenge: true
|
||||
});
|
||||
return {
|
||||
challengeId,
|
||||
challenge
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import * as OTPAuth from "otpauth";
|
||||
import * as QRCode from "qrcode";
|
||||
import config from "../../../../../config/index.js";
|
||||
import { UserProfiles } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { comparePassword } from "../../../../../misc/password.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
password: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"password"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
// Compare password
|
||||
const same = await comparePassword(ps.password, profile.password);
|
||||
if (!same) {
|
||||
throw new Error("incorrect password");
|
||||
}
|
||||
// Generate user's secret key
|
||||
const secret = new OTPAuth.Secret();
|
||||
await UserProfiles.update(user.id, {
|
||||
twoFactorTempSecret: secret.base32
|
||||
});
|
||||
// Get the data URL of the authenticator URL
|
||||
const totp = new OTPAuth.TOTP({
|
||||
secret,
|
||||
digits: 6,
|
||||
label: user.username,
|
||||
issuer: config.host
|
||||
});
|
||||
const url = totp.toString();
|
||||
const qr = await QRCode.toDataURL(url);
|
||||
return {
|
||||
qr,
|
||||
url,
|
||||
secret: secret.base32,
|
||||
label: user.username,
|
||||
issuer: config.host
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { comparePassword } from "../../../../../misc/password.js";
|
||||
import define from "../../../define.js";
|
||||
import { UserProfiles, UserSecurityKeys, Users } from "../../../../../models/index.js";
|
||||
import { publishMainStream } from "../../../../../services/stream.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
password: {
|
||||
type: "string"
|
||||
},
|
||||
credentialId: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"password",
|
||||
"credentialId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
// Compare password
|
||||
const same = await comparePassword(ps.password, profile.password);
|
||||
if (!same) {
|
||||
throw new Error("incorrect password");
|
||||
}
|
||||
// Make sure we only delete the user's own creds
|
||||
await UserSecurityKeys.delete({
|
||||
userId: user.id,
|
||||
id: ps.credentialId
|
||||
});
|
||||
// 使われているキーがなくなったらパスワードレスログインをやめる
|
||||
const keyCount = await UserSecurityKeys.count({
|
||||
where: {
|
||||
userId: user.id
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
lastUsed: true
|
||||
}
|
||||
});
|
||||
if (keyCount === 0) {
|
||||
await UserProfiles.update(me.id, {
|
||||
usePasswordLessLogin: false
|
||||
});
|
||||
}
|
||||
// Publish meUpdated event
|
||||
publishMainStream(user.id, "meUpdated", await Users.pack(user.id, user, {
|
||||
detail: true,
|
||||
includeSecrets: true
|
||||
}));
|
||||
return {};
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { publishMainStream } from "../../../../../services/stream.js";
|
||||
import define from "../../../define.js";
|
||||
import { Users, UserProfiles } from "../../../../../models/index.js";
|
||||
import { comparePassword } from "../../../../../misc/password.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
password: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"password"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
// Compare password
|
||||
const same = await comparePassword(ps.password, profile.password);
|
||||
if (!same) {
|
||||
throw new Error("incorrect password");
|
||||
}
|
||||
await UserProfiles.update(user.id, {
|
||||
twoFactorSecret: null,
|
||||
twoFactorEnabled: false,
|
||||
usePasswordLessLogin: false
|
||||
});
|
||||
const iObj = await Users.pack(user.id, user, {
|
||||
detail: true,
|
||||
includeSecrets: true
|
||||
});
|
||||
publishMainStream(user.id, "meUpdated", iObj);
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { publishMainStream } from "../../../../../services/stream.js";
|
||||
import define from "../../../define.js";
|
||||
import { Users, UserSecurityKeys } from "../../../../../models/index.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true,
|
||||
errors: {
|
||||
noSuchKey: {
|
||||
message: "No such key.",
|
||||
code: "NO_SUCH_KEY",
|
||||
id: "f9c5467f-d492-4d3c-9a8g-a70dacc86512"
|
||||
},
|
||||
accessDenied: {
|
||||
message: "You do not have edit privilege of the channel.",
|
||||
code: "ACCESS_DENIED",
|
||||
id: "1fb7cb09-d46a-4fff-b8df-057708cce513"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 30
|
||||
},
|
||||
credentialId: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"name",
|
||||
"credentialId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const key = await UserSecurityKeys.findOneBy({
|
||||
id: ps.credentialId
|
||||
});
|
||||
if (key == null) {
|
||||
throw new ApiError(meta.errors.noSuchKey);
|
||||
}
|
||||
if (key.userId !== user.id) {
|
||||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
await UserSecurityKeys.update(key.id, {
|
||||
name: ps.name
|
||||
});
|
||||
const iObj = await Users.pack(user.id, user, {
|
||||
detail: true,
|
||||
includeSecrets: true
|
||||
});
|
||||
publishMainStream(user.id, "meUpdated", iObj);
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import define from "../../define.js";
|
||||
import { AccessTokens } from "../../../../models/index.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
sort: {
|
||||
type: "string",
|
||||
enum: [
|
||||
"+createdAt",
|
||||
"-createdAt",
|
||||
"+lastUsedAt",
|
||||
"-lastUsedAt"
|
||||
]
|
||||
}
|
||||
},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const query = AccessTokens.createQueryBuilder("token").where("token.userId = :userId", {
|
||||
userId: user.id
|
||||
});
|
||||
switch(ps.sort){
|
||||
case "+createdAt":
|
||||
query.orderBy("token.createdAt", "DESC");
|
||||
break;
|
||||
case "-createdAt":
|
||||
query.orderBy("token.createdAt", "ASC");
|
||||
break;
|
||||
case "+lastUsedAt":
|
||||
query.orderBy("token.lastUsedAt", "DESC");
|
||||
break;
|
||||
case "-lastUsedAt":
|
||||
query.orderBy("token.lastUsedAt", "ASC");
|
||||
break;
|
||||
default:
|
||||
query.orderBy("token.id", "ASC");
|
||||
break;
|
||||
}
|
||||
const tokens = await query.getMany();
|
||||
return await Promise.all(tokens.map((token)=>({
|
||||
id: token.id,
|
||||
name: token.name,
|
||||
createdAt: token.createdAt,
|
||||
lastUsedAt: token.lastUsedAt,
|
||||
permission: token.permission
|
||||
})));
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import define from "../../define.js";
|
||||
import { AccessTokens, Apps } from "../../../../models/index.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
},
|
||||
offset: {
|
||||
type: "integer",
|
||||
default: 0
|
||||
},
|
||||
sort: {
|
||||
type: "string",
|
||||
enum: [
|
||||
"desc",
|
||||
"asc"
|
||||
],
|
||||
default: "desc"
|
||||
}
|
||||
},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
// Get tokens
|
||||
const tokens = await AccessTokens.find({
|
||||
where: {
|
||||
userId: user.id
|
||||
},
|
||||
take: ps.limit,
|
||||
skip: ps.offset,
|
||||
order: {
|
||||
id: ps.sort === "asc" ? 1 : -1
|
||||
}
|
||||
});
|
||||
return await Promise.all(tokens.map((token)=>Apps.pack(token.appId, user, {
|
||||
detail: true
|
||||
})));
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import define from "../../define.js";
|
||||
import { UserProfiles } from "../../../../models/index.js";
|
||||
import { hashPassword, comparePassword } from "../../../../misc/password.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
currentPassword: {
|
||||
type: "string"
|
||||
},
|
||||
newPassword: {
|
||||
type: "string",
|
||||
minLength: 1
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"currentPassword",
|
||||
"newPassword"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
// Compare password
|
||||
const same = await comparePassword(ps.currentPassword, profile.password);
|
||||
if (!same) {
|
||||
throw new Error("incorrect password");
|
||||
}
|
||||
// Generate hash of password
|
||||
const hash = await hashPassword(ps.newPassword);
|
||||
await UserProfiles.update(user.id, {
|
||||
password: hash
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { UserProfiles, Users } from "../../../../models/index.js";
|
||||
import { deleteAccount } from "../../../../services/delete-account.js";
|
||||
import define from "../../define.js";
|
||||
import { comparePassword } from "../../../../misc/password.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
password: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"password"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
const userDetailed = await Users.findOneByOrFail({
|
||||
id: user.id
|
||||
});
|
||||
if (userDetailed.isDeleted) {
|
||||
return;
|
||||
}
|
||||
// Compare password
|
||||
const same = await comparePassword(ps.password, profile.password);
|
||||
if (!same) {
|
||||
throw new Error("incorrect password");
|
||||
}
|
||||
await deleteAccount(user);
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import define from "../../define.js";
|
||||
import { createExportBlockingJob } from "../../../../queue/index.js";
|
||||
import { HOUR } from "../../../../const.js";
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: HOUR,
|
||||
max: 1
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
createExportBlockingJob(user);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import define from "../../define.js";
|
||||
import { createExportFollowingJob } from "../../../../queue/index.js";
|
||||
import { HOUR } from "../../../../const.js";
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: HOUR,
|
||||
max: 1
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
excludeMuting: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
},
|
||||
excludeInactive: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
}
|
||||
},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
createExportFollowingJob(user, ps.excludeMuting, ps.excludeInactive);
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import define from "../../define.js";
|
||||
import { createExportMuteJob } from "../../../../queue/index.js";
|
||||
import { HOUR } from "../../../../const.js";
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: HOUR,
|
||||
max: 1
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
createExportMuteJob(user);
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import define from "../../define.js";
|
||||
import { createExportNotesJob } from "../../../../queue/index.js";
|
||||
import { DAY } from "../../../../const.js";
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: DAY,
|
||||
max: 1
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
createExportNotesJob(user);
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import define from "../../define.js";
|
||||
import { createExportUserListsJob } from "../../../../queue/index.js";
|
||||
import { MINUTE } from "../../../../const.js";
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: MINUTE,
|
||||
max: 1
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
createExportUserListsJob(user);
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import define from "../../define.js";
|
||||
import { NoteFavorites } from "../../../../models/index.js";
|
||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account",
|
||||
"notes",
|
||||
"favorites"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:favorites",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "NoteFavorite"
|
||||
}
|
||||
}
|
||||
};
|
||||
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(NoteFavorites.createQueryBuilder("favorite"), ps.sinceId, ps.untilId).andWhere("favorite.userId = :meId", {
|
||||
meId: user.id
|
||||
}).leftJoinAndSelect("favorite.note", "note");
|
||||
const favorites = await query.take(ps.limit).getMany();
|
||||
return await NoteFavorites.packMany(favorites, user);
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import define from "../../../define.js";
|
||||
import { GalleryLikes } from "../../../../../models/index.js";
|
||||
import { makePaginationQuery } from "../../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account",
|
||||
"gallery"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:gallery-likes",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
format: "id"
|
||||
},
|
||||
post: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "GalleryPost"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
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(GalleryLikes.createQueryBuilder("like"), ps.sinceId, ps.untilId).andWhere("like.userId = :meId", {
|
||||
meId: user.id
|
||||
}).leftJoinAndSelect("like.post", "post");
|
||||
const likes = await query.take(ps.limit).getMany();
|
||||
return await GalleryLikes.packMany(likes, user);
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { GalleryPosts } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { makePaginationQuery } from "../../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account",
|
||||
"gallery"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:gallery",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "GalleryPost"
|
||||
}
|
||||
}
|
||||
};
|
||||
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(GalleryPosts.createQueryBuilder("post"), ps.sinceId, ps.untilId).andWhere("post.userId = :meId", {
|
||||
meId: user.id
|
||||
});
|
||||
const posts = await query.take(ps.limit).getMany();
|
||||
return await GalleryPosts.packMany(posts, user);
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import define from "../../define.js";
|
||||
import { createImportBlockingJob } from "../../../../queue/index.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { DriveFiles } from "../../../../models/index.js";
|
||||
import { HOUR } from "../../../../const.js";
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: HOUR,
|
||||
max: 1
|
||||
},
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: "No such file.",
|
||||
code: "NO_SUCH_FILE",
|
||||
id: "ebb53e5f-6574-9c0c-0b92-7ca6def56d7e"
|
||||
},
|
||||
unexpectedFileType: {
|
||||
message: "We need csv file.",
|
||||
code: "UNEXPECTED_FILE_TYPE",
|
||||
id: "b6fab7d6-d945-d67c-dfdb-32da1cd12cfe"
|
||||
},
|
||||
tooBigFile: {
|
||||
message: "That file is too big.",
|
||||
code: "TOO_BIG_FILE",
|
||||
id: "b7fbf0b1-aeef-3b21-29ef-fadd4cb72ccf"
|
||||
},
|
||||
emptyFile: {
|
||||
message: "That file is empty.",
|
||||
code: "EMPTY_FILE",
|
||||
id: "6f3a4dcc-f060-a707-4950-806fbdbe60d6"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
fileId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"fileId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const file = await DriveFiles.findOneBy({
|
||||
id: ps.fileId
|
||||
});
|
||||
if (file == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
|
||||
if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
|
||||
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
|
||||
createImportBlockingJob(user, file.id);
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import define from "../../define.js";
|
||||
import { createImportFollowingJob } from "../../../../queue/index.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { DriveFiles } from "../../../../models/index.js";
|
||||
import { HOUR } from "../../../../const.js";
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duratition: HOUR,
|
||||
max: 1
|
||||
},
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: "No such file.",
|
||||
code: "NO_SUCH_FILE",
|
||||
id: "b98644cf-a5ac-4277-a502-0b8054a709a3"
|
||||
},
|
||||
unexpectedFileType: {
|
||||
message: "Must be a CSV or JSON file.",
|
||||
code: "UNEXPECTED_FILE_TYPE",
|
||||
id: "660f3599-bce0-4f95-9dde-311fd841c183"
|
||||
},
|
||||
tooBigFile: {
|
||||
message: "That file is too big.",
|
||||
code: "TOO_BIG_FILE",
|
||||
id: "dee9d4ed-ad07-43ed-8b34-b2856398bc60"
|
||||
},
|
||||
emptyFile: {
|
||||
message: "That file is empty.",
|
||||
code: "EMPTY_FILE",
|
||||
id: "31a1b42c-06f7-42ae-8a38-a661c5c9f691"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
fileId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"fileId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const file = await DriveFiles.findOneBy({
|
||||
id: ps.fileId
|
||||
});
|
||||
if (file == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
|
||||
if (file.size > 2_000_000) throw new ApiError(meta.errors.tooBigFile);
|
||||
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
|
||||
createImportFollowingJob(user, file.id);
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import define from "../../define.js";
|
||||
import { createImportMutingJob } from "../../../../queue/index.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { DriveFiles } from "../../../../models/index.js";
|
||||
import { HOUR } from "../../../../const.js";
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: HOUR,
|
||||
max: 1
|
||||
},
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: "No such file.",
|
||||
code: "NO_SUCH_FILE",
|
||||
id: "e674141e-bd2a-ba85-e616-aefb187c9c2a"
|
||||
},
|
||||
unexpectedFileType: {
|
||||
message: "We need csv file.",
|
||||
code: "UNEXPECTED_FILE_TYPE",
|
||||
id: "568c6e42-c86c-ba09-c004-517f83f9f1a8"
|
||||
},
|
||||
tooBigFile: {
|
||||
message: "That file is too big.",
|
||||
code: "TOO_BIG_FILE",
|
||||
id: "9b4ada6d-d7f7-0472-0713-4f558bd1ec9c"
|
||||
},
|
||||
emptyFile: {
|
||||
message: "That file is empty.",
|
||||
code: "EMPTY_FILE",
|
||||
id: "d2f12af1-e7b4-feac-86a3-519548f2728e"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
fileId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"fileId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const file = await DriveFiles.findOneBy({
|
||||
id: ps.fileId
|
||||
});
|
||||
if (file == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
|
||||
if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
|
||||
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
|
||||
createImportMutingJob(user, file.id);
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import define from "../../define.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { DAY } from "../../../../const.js";
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: DAY * 30,
|
||||
max: 2
|
||||
},
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: "No such file.",
|
||||
code: "NO_SUCH_FILE",
|
||||
id: "e674141e-bd2a-ba85-e616-aefb187c9c2a"
|
||||
},
|
||||
emptyFile: {
|
||||
message: "That file is empty.",
|
||||
code: "EMPTY_FILE",
|
||||
id: "d2f12af1-e7b4-feac-86a3-519548f2728e"
|
||||
},
|
||||
importsDisabled: {
|
||||
message: "Post imports are disabled for security reasons.",
|
||||
code: "IMPORTS_DISABLED",
|
||||
id: " bc9227e4-fb82-11ed-be56-0242ac120002"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
fileId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
signatureCheck: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"fileId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
throw new ApiError(meta.errors.importsDisabled);
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import define from "../../define.js";
|
||||
import { createImportUserListsJob } from "../../../../queue/index.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { DriveFiles } from "../../../../models/index.js";
|
||||
import { HOUR } from "../../../../const.js";
|
||||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: HOUR,
|
||||
max: 1
|
||||
},
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: "No such file.",
|
||||
code: "NO_SUCH_FILE",
|
||||
id: "ea9cc34f-c415-4bc6-a6fe-28ac40357049"
|
||||
},
|
||||
unexpectedFileType: {
|
||||
message: "We need csv file.",
|
||||
code: "UNEXPECTED_FILE_TYPE",
|
||||
id: "a3c9edda-dd9b-4596-be6a-150ef813745c"
|
||||
},
|
||||
tooBigFile: {
|
||||
message: "That file is too big.",
|
||||
code: "TOO_BIG_FILE",
|
||||
id: "ae6e7a22-971b-4b52-b2be-fc0b9b121fe9"
|
||||
},
|
||||
emptyFile: {
|
||||
message: "That file is empty.",
|
||||
code: "EMPTY_FILE",
|
||||
id: "99efe367-ce6e-4d44-93f8-5fae7b040356"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
fileId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"fileId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const file = await DriveFiles.findOneBy({
|
||||
id: ps.fileId
|
||||
});
|
||||
if (file == null) throw new ApiError(meta.errors.noSuchFile);
|
||||
//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
|
||||
if (file.size > 30000) throw new ApiError(meta.errors.tooBigFile);
|
||||
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
|
||||
createImportUserListsJob(user, file.id);
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Users } from "../../../../models/index.js";
|
||||
import { resolveUser } from "../../../../remote/resolve-user.js";
|
||||
import acceptAllFollowRequests from "../../../../services/following/requests/accept-all.js";
|
||||
import { publishToFollowers } from "../../../../services/i/update.js";
|
||||
import { publishMainStream } from "../../../../services/stream.js";
|
||||
import { DAY } from "../../../../const.js";
|
||||
import { apiLogger } from "../../logger.js";
|
||||
import define from "../../define.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { parse } from "../../../../misc/acct.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users"
|
||||
],
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: DAY,
|
||||
max: 30
|
||||
},
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "fcd2eef9-a9b2-4c4f-8624-038099e90aa5"
|
||||
},
|
||||
notRemote: {
|
||||
message: "User is not remote. You can only migrate to other instances.",
|
||||
code: "NOT_REMOTE",
|
||||
id: "4362f8dc-731f-4ad8-a694-be2a88922a24"
|
||||
},
|
||||
uriNull: {
|
||||
message: "User ActivityPup URI is null.",
|
||||
code: "URI_NULL",
|
||||
id: "bf326f31-d430-4f97-9933-5d61e4d48a23"
|
||||
},
|
||||
alreadyMoved: {
|
||||
message: "You have already moved your account.",
|
||||
code: "ALREADY_MOVED",
|
||||
id: "56f20ec9-fd06-4fa5-841b-edd6d7d4fa31"
|
||||
},
|
||||
yourself: {
|
||||
message: "You can't set yourself as your own alias.",
|
||||
code: "FORBIDDEN_TO_SET_YOURSELF",
|
||||
id: "25c90186-4ab0-49c8-9bba-a1fa6c202ba4"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
alsoKnownAs: {
|
||||
type: "array",
|
||||
maxItems: 10,
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"alsoKnownAs"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
if (!ps.alsoKnownAs) throw new ApiError(meta.errors.noSuchUser);
|
||||
if (user.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
|
||||
const newAka = new Set();
|
||||
for (const line of ps.alsoKnownAs){
|
||||
if (!line) throw new ApiError(meta.errors.noSuchUser);
|
||||
const { username, host } = parse(line);
|
||||
const aka = await resolveUser(username, host).catch((e)=>{
|
||||
apiLogger.warn(`failed to resolve remote user: ${e}`);
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
});
|
||||
if (aka.id === user.id) throw new ApiError(meta.errors.yourself);
|
||||
if (!aka.uri) throw new ApiError(meta.errors.uriNull);
|
||||
newAka.add(aka.uri);
|
||||
}
|
||||
const updates = {
|
||||
alsoKnownAs: newAka.size > 0 ? Array.from(newAka) : null
|
||||
};
|
||||
await Users.update(user.id, updates);
|
||||
const iObj = await Users.pack(user.id, user, {
|
||||
detail: true,
|
||||
includeSecrets: true
|
||||
});
|
||||
// Publish meUpdated event
|
||||
publishMainStream(user.id, "meUpdated", iObj);
|
||||
if (user.isLocked === false) {
|
||||
acceptAllFollowRequests(user);
|
||||
}
|
||||
publishToFollowers(user.id);
|
||||
return iObj;
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
import { resolveUser } from "../../../../remote/resolve-user.js";
|
||||
import { DAY } from "../../../../const.js";
|
||||
import DeliverManager from "../../../../remote/activitypub/deliver-manager.js";
|
||||
import { renderActivity } from "../../../../remote/activitypub/renderer/index.js";
|
||||
import define from "../../define.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { apiLogger } from "../../logger.js";
|
||||
import deleteFollowing from "../../../../services/following/delete.js";
|
||||
import create from "../../../../services/following/create.js";
|
||||
import { getUser } from "../../common/getters.js";
|
||||
import { Followings, Users } from "../../../../models/index.js";
|
||||
import config from "../../../../config/index.js";
|
||||
import { publishMainStream } from "../../../../services/stream.js";
|
||||
import { parse } from "../../../../misc/acct.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users"
|
||||
],
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: DAY,
|
||||
max: 5
|
||||
},
|
||||
errors: {
|
||||
noSuchMoveTarget: {
|
||||
message: "No such move target.",
|
||||
code: "NO_SUCH_MOVE_TARGET",
|
||||
id: "b5c90186-4ab0-49c8-9bba-a1f76c202ba4"
|
||||
},
|
||||
remoteAccountForbids: {
|
||||
message: "Remote account doesn't have proper 'Known As' alias. Did you remember to set it?",
|
||||
code: "REMOTE_ACCOUNT_FORBIDS",
|
||||
id: "b5c90186-4ab0-49c8-9bba-a1f766282ba4"
|
||||
},
|
||||
notRemote: {
|
||||
message: "User is not remote. You can only migrate to other instances.",
|
||||
code: "NOT_REMOTE",
|
||||
id: "4362f8dc-731f-4ad8-a694-be2a88922a24"
|
||||
},
|
||||
adminForbidden: {
|
||||
message: "Admins cant migrate.",
|
||||
code: "NOT_ADMIN_FORBIDDEN",
|
||||
id: "4362e8dc-731f-4ad8-a694-be2a88922a24"
|
||||
},
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "fcd2eef9-a9b2-4c4f-8624-038099e90aa5"
|
||||
},
|
||||
uriNull: {
|
||||
message: "User ActivityPup URI is null.",
|
||||
code: "URI_NULL",
|
||||
id: "bf326f31-d430-4f97-9933-5d61e4d48a23"
|
||||
},
|
||||
localUriNull: {
|
||||
message: "Local User ActivityPup URI is null.",
|
||||
code: "URI_NULL",
|
||||
id: "95ba11b9-90e8-43a5-ba16-7acc1ab32e71"
|
||||
},
|
||||
alreadyMoved: {
|
||||
message: "Account was already moved to another account.",
|
||||
code: "ALREADY_MOVED",
|
||||
id: "b234a14e-9ebe-4581-8000-074b3c215962"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
moveToAccount: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"moveToAccount"
|
||||
]
|
||||
};
|
||||
function moveActivity(toUrl, fromUrl) {
|
||||
const activity = {
|
||||
id: null,
|
||||
actor: fromUrl,
|
||||
type: "Move",
|
||||
object: fromUrl,
|
||||
target: toUrl
|
||||
};
|
||||
return renderActivity(activity);
|
||||
}
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchMoveTarget);
|
||||
if (user.isAdmin) throw new ApiError(meta.errors.adminForbidden);
|
||||
if (user.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
|
||||
const { username, host } = parse(ps.moveToAccount);
|
||||
if (!host) throw new ApiError(meta.errors.notRemote);
|
||||
const moveTo = await resolveUser(username, host).catch((e)=>{
|
||||
apiLogger.warn(`failed to resolve remote user: ${e}`);
|
||||
throw new ApiError(meta.errors.noSuchMoveTarget);
|
||||
});
|
||||
let fromUrl = user.uri;
|
||||
if (!fromUrl) {
|
||||
fromUrl = `${config.url}/users/${user.id}`;
|
||||
}
|
||||
let toUrl = moveTo.uri;
|
||||
if (!toUrl) {
|
||||
throw new ApiError(meta.errors.uriNull);
|
||||
}
|
||||
let allowed = false;
|
||||
moveTo.alsoKnownAs?.forEach((element)=>{
|
||||
if (fromUrl.includes(element)) allowed = true;
|
||||
});
|
||||
if (!(allowed && toUrl && fromUrl)) throw new ApiError(meta.errors.remoteAccountForbids);
|
||||
const updates = {};
|
||||
if (!toUrl) toUrl = "";
|
||||
updates.movedToUri = toUrl;
|
||||
updates.alsoKnownAs = user.alsoKnownAs?.concat(toUrl) ?? [
|
||||
toUrl
|
||||
];
|
||||
await Users.update(user.id, updates);
|
||||
const iObj = await Users.pack(user.id, user, {
|
||||
detail: true,
|
||||
includeSecrets: true
|
||||
});
|
||||
const moveAct = moveActivity(toUrl, fromUrl);
|
||||
const dm = new DeliverManager(user, moveAct);
|
||||
dm.addFollowersRecipe();
|
||||
dm.execute();
|
||||
// Publish meUpdated event
|
||||
publishMainStream(user.id, "meUpdated", iObj);
|
||||
const followings = await Followings.findBy({
|
||||
followeeId: user.id
|
||||
});
|
||||
followings.forEach(async (following)=>{
|
||||
//if follower is local
|
||||
if (!following.followerHost) {
|
||||
const follower = await getUser(following.followerId).catch((e)=>{
|
||||
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
await deleteFollowing(follower, user);
|
||||
try {
|
||||
await create(follower, moveTo);
|
||||
} catch (e) {
|
||||
/* empty */ }
|
||||
}
|
||||
});
|
||||
return iObj;
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
import { Brackets } from "typeorm";
|
||||
import { Notifications, Followings, Mutings, Users, UserProfiles } from "../../../../models/index.js";
|
||||
import { notificationTypes } from "../../../../types.js";
|
||||
import read from "../../../../services/note/read.js";
|
||||
import { readNotification } from "../../common/read-notification.js";
|
||||
import define from "../../define.js";
|
||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account",
|
||||
"notifications"
|
||||
],
|
||||
requireCredential: true,
|
||||
limit: {
|
||||
duration: 60000,
|
||||
max: 15
|
||||
},
|
||||
kind: "read:notifications",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "Notification"
|
||||
}
|
||||
}
|
||||
};
|
||||
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"
|
||||
},
|
||||
following: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
},
|
||||
unreadOnly: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
},
|
||||
markAsRead: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
},
|
||||
includeTypes: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
enum: notificationTypes
|
||||
}
|
||||
},
|
||||
excludeTypes: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
enum: notificationTypes
|
||||
}
|
||||
}
|
||||
},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
// includeTypes が空の場合はクエリしない
|
||||
if (ps.includeTypes && ps.includeTypes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
// excludeTypes に全指定されている場合はクエリしない
|
||||
if (notificationTypes.every((type)=>ps.excludeTypes?.includes(type))) {
|
||||
return [];
|
||||
}
|
||||
const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :followerId", {
|
||||
followerId: user.id
|
||||
});
|
||||
const mutingQuery = Mutings.createQueryBuilder("muting").select("muting.muteeId").where("muting.muterId = :muterId", {
|
||||
muterId: user.id
|
||||
});
|
||||
const mutingInstanceQuery = UserProfiles.createQueryBuilder("user_profile").select("user_profile.mutedInstances").where("user_profile.userId = :muterId", {
|
||||
muterId: user.id
|
||||
});
|
||||
const suspendedQuery = Users.createQueryBuilder("users").select("users.id").where("users.isSuspended = TRUE");
|
||||
const query = makePaginationQuery(Notifications.createQueryBuilder("notification"), ps.sinceId, ps.untilId).andWhere("notification.notifieeId = :meId", {
|
||||
meId: user.id
|
||||
}).leftJoinAndSelect("notification.notifier", "notifier").leftJoinAndSelect("notification.note", "note").leftJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
|
||||
// muted users
|
||||
query.andWhere(new Brackets((qb)=>{
|
||||
qb.where(`notification.notifierId NOT IN (${mutingQuery.getQuery()})`).orWhere("notification.notifierId IS NULL");
|
||||
}));
|
||||
query.setParameters(mutingQuery.getParameters());
|
||||
// muted instances
|
||||
query.andWhere(new Brackets((qb)=>{
|
||||
qb.andWhere("notifier.host IS NULL").orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`);
|
||||
}));
|
||||
query.setParameters(mutingInstanceQuery.getParameters());
|
||||
// suspended users
|
||||
query.andWhere(new Brackets((qb)=>{
|
||||
qb.where(`notification.notifierId NOT IN (${suspendedQuery.getQuery()})`).orWhere("notification.notifierId IS NULL");
|
||||
}));
|
||||
if (ps.following) {
|
||||
query.andWhere(`((notification.notifierId IN (${followingQuery.getQuery()})) OR (notification.notifierId = :meId))`, {
|
||||
meId: user.id
|
||||
});
|
||||
query.setParameters(followingQuery.getParameters());
|
||||
}
|
||||
if (ps.includeTypes && ps.includeTypes.length > 0) {
|
||||
query.andWhere("notification.type IN (:...includeTypes)", {
|
||||
includeTypes: ps.includeTypes
|
||||
});
|
||||
} else if (ps.excludeTypes && ps.excludeTypes.length > 0) {
|
||||
query.andWhere("notification.type NOT IN (:...excludeTypes)", {
|
||||
excludeTypes: ps.excludeTypes
|
||||
});
|
||||
}
|
||||
if (ps.unreadOnly) {
|
||||
query.andWhere("notification.isRead = false");
|
||||
}
|
||||
const notifications = await query.take(ps.limit).getMany();
|
||||
// Mark all as read
|
||||
if (notifications.length > 0 && ps.markAsRead) {
|
||||
readNotification(user.id, notifications.map((x)=>x.id));
|
||||
}
|
||||
const notes = notifications.filter((notification)=>[
|
||||
"mention",
|
||||
"reply",
|
||||
"quote"
|
||||
].includes(notification.type)).map((notification)=>notification.note);
|
||||
if (notes.length > 0) {
|
||||
read(user.id, notes);
|
||||
}
|
||||
return await Notifications.packMany(notifications, user.id);
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { PageLikes } from "../../../../models/index.js";
|
||||
import define from "../../define.js";
|
||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account",
|
||||
"pages"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:page-likes",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
format: "id"
|
||||
},
|
||||
page: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "Page"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
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(PageLikes.createQueryBuilder("like"), ps.sinceId, ps.untilId).andWhere("like.userId = :meId", {
|
||||
meId: user.id
|
||||
}).leftJoinAndSelect("like.page", "page");
|
||||
const likes = await query.take(ps.limit).getMany();
|
||||
return PageLikes.packMany(likes, user);
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Pages } from "../../../../models/index.js";
|
||||
import define from "../../define.js";
|
||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account",
|
||||
"pages"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:pages",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "Page"
|
||||
}
|
||||
}
|
||||
};
|
||||
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(Pages.createQueryBuilder("page"), ps.sinceId, ps.untilId).andWhere("page.userId = :meId", {
|
||||
meId: user.id
|
||||
});
|
||||
const pages = await query.take(ps.limit).getMany();
|
||||
return await Pages.packMany(pages);
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { addPinned } from "../../../../services/i/pin.js";
|
||||
import define from "../../define.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { Users } from "../../../../models/index.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account",
|
||||
"notes"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
errors: {
|
||||
noSuchNote: {
|
||||
message: "No such note.",
|
||||
code: "NO_SUCH_NOTE",
|
||||
id: "56734f8b-3928-431e-bf80-6ff87df40cb3"
|
||||
},
|
||||
pinLimitExceeded: {
|
||||
message: "You can not pin notes any more.",
|
||||
code: "PIN_LIMIT_EXCEEDED",
|
||||
id: "72dab508-c64d-498f-8740-a8eec1ba385a"
|
||||
},
|
||||
alreadyPinned: {
|
||||
message: "That note has already been pinned.",
|
||||
code: "ALREADY_PINNED",
|
||||
id: "8b18c2b7-68fe-4edb-9892-c0cbaeb6c913"
|
||||
}
|
||||
},
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "MeDetailed"
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
noteId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"noteId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
await addPinned(user, ps.noteId).catch((e)=>{
|
||||
if (e.id === "70c4e51f-5bea-449c-a030-53bee3cce202") throw new ApiError(meta.errors.noSuchNote);
|
||||
if (e.id === "15a018eb-58e5-4da1-93be-330fcc5e4e1a") throw new ApiError(meta.errors.pinLimitExceeded);
|
||||
if (e.id === "23f0cf4e-59a3-4276-a91d-61a5891c1514") throw new ApiError(meta.errors.alreadyPinned);
|
||||
throw e;
|
||||
});
|
||||
return await Users.pack(user.id, user, {
|
||||
detail: true
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { publishMainStream } from "../../../../services/stream.js";
|
||||
import define from "../../define.js";
|
||||
import { MessagingMessages, UserGroupJoinings } from "../../../../models/index.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account",
|
||||
"messaging"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account"
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
// Update documents
|
||||
await MessagingMessages.update({
|
||||
recipientId: user.id,
|
||||
isRead: false
|
||||
}, {
|
||||
isRead: true
|
||||
});
|
||||
const joinings = await UserGroupJoinings.findBy({
|
||||
userId: user.id
|
||||
});
|
||||
await Promise.all(joinings.map((j)=>MessagingMessages.createQueryBuilder().update().set({
|
||||
reads: ()=>`array_append("reads", '${user.id}')`
|
||||
}).where("groupId = :groupId", {
|
||||
groupId: j.userGroupId
|
||||
}).andWhere("userId != :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("NOT (:userId = ANY(reads))", {
|
||||
userId: user.id
|
||||
}).execute()));
|
||||
publishMainStream(user.id, "readAllMessagingMessages");
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { publishMainStream } from "../../../../services/stream.js";
|
||||
import define from "../../define.js";
|
||||
import { NoteUnreads } from "../../../../models/index.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account"
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
// Remove documents
|
||||
await NoteUnreads.delete({
|
||||
userId: user.id
|
||||
});
|
||||
// 全て既読になったイベントを発行
|
||||
publishMainStream(user.id, "readAllUnreadMentions");
|
||||
publishMainStream(user.id, "readAllUnreadSpecifiedNotes");
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import define from "../../define.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { genId } from "../../../../misc/gen-id.js";
|
||||
import { AnnouncementReads, Announcements, Users } from "../../../../models/index.js";
|
||||
import { publishMainStream } from "../../../../services/stream.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
errors: {
|
||||
noSuchAnnouncement: {
|
||||
message: "No such announcement.",
|
||||
code: "NO_SUCH_ANNOUNCEMENT",
|
||||
id: "184663db-df88-4bc2-8b52-fb85f0681939"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
announcementId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"announcementId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
// Check if announcement exists
|
||||
const exist = await Announcements.exist({
|
||||
where: {
|
||||
id: ps.announcementId
|
||||
}
|
||||
});
|
||||
if (!exist) {
|
||||
throw new ApiError(meta.errors.noSuchAnnouncement);
|
||||
}
|
||||
// Check if already read
|
||||
const read = await AnnouncementReads.exist({
|
||||
where: {
|
||||
announcementId: ps.announcementId,
|
||||
userId: user.id
|
||||
}
|
||||
});
|
||||
if (read) {
|
||||
return;
|
||||
}
|
||||
// Create read
|
||||
await AnnouncementReads.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
announcementId: ps.announcementId,
|
||||
userId: user.id
|
||||
});
|
||||
if (!await Users.getHasUnreadAnnouncement(user.id)) {
|
||||
publishMainStream(user.id, "readAllAnnouncements");
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { publishInternalEvent, publishMainStream, publishUserEvent } from "../../../../services/stream.js";
|
||||
import generateUserToken from "../../common/generate-native-user-token.js";
|
||||
import define from "../../define.js";
|
||||
import { Users, UserProfiles } from "../../../../models/index.js";
|
||||
import { comparePassword } from "../../../../misc/password.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
password: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"password"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const freshUser = await Users.findOneByOrFail({
|
||||
id: user.id
|
||||
});
|
||||
const oldToken = freshUser.token;
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
// Compare password
|
||||
const same = await comparePassword(ps.password, profile.password);
|
||||
if (!same) {
|
||||
throw new Error("incorrect password");
|
||||
}
|
||||
const newToken = generateUserToken();
|
||||
await Users.update(user.id, {
|
||||
token: newToken
|
||||
});
|
||||
// Publish event
|
||||
publishInternalEvent("userTokenRegenerated", {
|
||||
id: user.id,
|
||||
oldToken,
|
||||
newToken
|
||||
});
|
||||
publishMainStream(user.id, "myTokenRegenerated");
|
||||
// Terminate streaming
|
||||
setTimeout(()=>{
|
||||
publishUserEvent(user.id, "terminate", {});
|
||||
}, 5000);
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import define from "../../../define.js";
|
||||
import { RegistryItems } from "../../../../../models/index.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
scope: {
|
||||
type: "array",
|
||||
default: [],
|
||||
items: {
|
||||
type: "string",
|
||||
pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
|
||||
}
|
||||
}
|
||||
},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const query = RegistryItems.createQueryBuilder("item").where("item.domain IS NULL").andWhere("item.userId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("item.scope = :scope", {
|
||||
scope: ps.scope
|
||||
});
|
||||
const items = await query.getMany();
|
||||
const res = {};
|
||||
for (const item of items){
|
||||
res[item.key] = item.value;
|
||||
}
|
||||
return res;
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import define from "../../../define.js";
|
||||
import { RegistryItems } from "../../../../../models/index.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true,
|
||||
errors: {
|
||||
noSuchKey: {
|
||||
message: "No such key.",
|
||||
code: "NO_SUCH_KEY",
|
||||
id: "97a1e8e7-c0f7-47d2-957a-92e61256e01a"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
key: {
|
||||
type: "string"
|
||||
},
|
||||
scope: {
|
||||
type: "array",
|
||||
default: [],
|
||||
items: {
|
||||
type: "string",
|
||||
pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
|
||||
}
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"key"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const query = RegistryItems.createQueryBuilder("item").where("item.domain IS NULL").andWhere("item.userId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("item.key = :key", {
|
||||
key: ps.key
|
||||
}).andWhere("item.scope = :scope", {
|
||||
scope: ps.scope
|
||||
});
|
||||
const item = await query.getOne();
|
||||
if (item == null) {
|
||||
throw new ApiError(meta.errors.noSuchKey);
|
||||
}
|
||||
return {
|
||||
updatedAt: item.updatedAt,
|
||||
value: item.value
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import define from "../../../define.js";
|
||||
import { RegistryItems } from "../../../../../models/index.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true,
|
||||
errors: {
|
||||
noSuchKey: {
|
||||
message: "No such key.",
|
||||
code: "NO_SUCH_KEY",
|
||||
id: "ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
key: {
|
||||
type: "string"
|
||||
},
|
||||
scope: {
|
||||
type: "array",
|
||||
default: [],
|
||||
items: {
|
||||
type: "string",
|
||||
pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
|
||||
}
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"key"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const query = RegistryItems.createQueryBuilder("item").where("item.domain IS NULL").andWhere("item.userId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("item.key = :key", {
|
||||
key: ps.key
|
||||
}).andWhere("item.scope = :scope", {
|
||||
scope: ps.scope
|
||||
});
|
||||
const item = await query.getOne();
|
||||
if (item == null) {
|
||||
throw new ApiError(meta.errors.noSuchKey);
|
||||
}
|
||||
return item.value;
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import define from "../../../define.js";
|
||||
import { RegistryItems } from "../../../../../models/index.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
scope: {
|
||||
type: "array",
|
||||
default: [],
|
||||
items: {
|
||||
type: "string",
|
||||
pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
|
||||
}
|
||||
}
|
||||
},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const query = RegistryItems.createQueryBuilder("item").where("item.domain IS NULL").andWhere("item.userId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("item.scope = :scope", {
|
||||
scope: ps.scope
|
||||
});
|
||||
const items = await query.getMany();
|
||||
const res = {};
|
||||
for (const item of items){
|
||||
const type = typeof item.value;
|
||||
res[item.key] = item.value === null ? "null" : Array.isArray(item.value) ? "array" : type === "number" ? "number" : type === "string" ? "string" : type === "boolean" ? "boolean" : type === "object" ? "object" : null;
|
||||
}
|
||||
return res;
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import define from "../../../define.js";
|
||||
import { RegistryItems } from "../../../../../models/index.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
scope: {
|
||||
type: "array",
|
||||
default: [],
|
||||
items: {
|
||||
type: "string",
|
||||
pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
|
||||
}
|
||||
}
|
||||
},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const query = RegistryItems.createQueryBuilder("item").select("item.key").where("item.domain IS NULL").andWhere("item.userId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("item.scope = :scope", {
|
||||
scope: ps.scope
|
||||
});
|
||||
const items = await query.getMany();
|
||||
return items.map((x)=>x.key);
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import define from "../../../define.js";
|
||||
import { RegistryItems } from "../../../../../models/index.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true,
|
||||
errors: {
|
||||
noSuchKey: {
|
||||
message: "No such key.",
|
||||
code: "NO_SUCH_KEY",
|
||||
id: "1fac4e8a-a6cd-4e39-a4a5-3a7e11f1b019"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
key: {
|
||||
type: "string"
|
||||
},
|
||||
scope: {
|
||||
type: "array",
|
||||
default: [],
|
||||
items: {
|
||||
type: "string",
|
||||
pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
|
||||
}
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"key"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const query = RegistryItems.createQueryBuilder("item").where("item.domain IS NULL").andWhere("item.userId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("item.key = :key", {
|
||||
key: ps.key
|
||||
}).andWhere("item.scope = :scope", {
|
||||
scope: ps.scope
|
||||
});
|
||||
const item = await query.getOne();
|
||||
if (item == null) {
|
||||
throw new ApiError(meta.errors.noSuchKey);
|
||||
}
|
||||
await RegistryItems.remove(item);
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import define from "../../../define.js";
|
||||
import { RegistryItems } from "../../../../../models/index.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const query = RegistryItems.createQueryBuilder("item").select("item.scope").where("item.domain IS NULL").andWhere("item.userId = :userId", {
|
||||
userId: user.id
|
||||
});
|
||||
const items = await query.getMany();
|
||||
const res = [];
|
||||
for (const item of items){
|
||||
if (res.some((scope)=>scope.join(".") === item.scope.join("."))) continue;
|
||||
res.push(item.scope);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { publishMainStream } from "../../../../../services/stream.js";
|
||||
import define from "../../../define.js";
|
||||
import { RegistryItems } from "../../../../../models/index.js";
|
||||
import { genId } from "../../../../../misc/gen-id.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
key: {
|
||||
type: "string",
|
||||
minLength: 1
|
||||
},
|
||||
value: {},
|
||||
scope: {
|
||||
type: "array",
|
||||
default: [],
|
||||
items: {
|
||||
type: "string",
|
||||
pattern: /^[a-zA-Z0-9_]+$/.toString().slice(1, -1)
|
||||
}
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"key",
|
||||
"value"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const query = RegistryItems.createQueryBuilder("item").where("item.domain IS NULL").andWhere("item.userId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("item.key = :key", {
|
||||
key: ps.key
|
||||
}).andWhere("item.scope = :scope", {
|
||||
scope: ps.scope
|
||||
});
|
||||
const existingItem = await query.getOne();
|
||||
if (existingItem) {
|
||||
await RegistryItems.update(existingItem.id, {
|
||||
updatedAt: new Date(),
|
||||
value: ps.value
|
||||
});
|
||||
} else {
|
||||
await RegistryItems.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
userId: user.id,
|
||||
domain: null,
|
||||
scope: ps.scope,
|
||||
key: ps.key,
|
||||
value: ps.value
|
||||
});
|
||||
}
|
||||
// TODO: サードパーティアプリが傍受出来てしまうのでどうにかする
|
||||
publishMainStream(user.id, "registryUpdated", {
|
||||
scope: ps.scope,
|
||||
key: ps.key,
|
||||
value: ps.value
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import define from "../../define.js";
|
||||
import { VerifiedBadgeRequests, Users } from "../../../../models/index.js";
|
||||
import { genId } from "../../../../misc/gen-id.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account"
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
comment: {
|
||||
type: "string",
|
||||
maxLength: 2048,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const me = await Users.findOneByOrFail({
|
||||
id: user.id
|
||||
});
|
||||
if (me.isVerified) {
|
||||
throw new Error("already verified");
|
||||
}
|
||||
const existing = await VerifiedBadgeRequests.findOneBy({
|
||||
userId: user.id,
|
||||
status: "pending"
|
||||
});
|
||||
if (existing != null) {
|
||||
return await VerifiedBadgeRequests.pack(existing);
|
||||
}
|
||||
const request = await VerifiedBadgeRequests.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
resolvedAt: null,
|
||||
userId: user.id,
|
||||
resolverId: null,
|
||||
status: "pending",
|
||||
comment: ps.comment ?? ""
|
||||
}).then((x)=>VerifiedBadgeRequests.findOneByOrFail(x.identifiers[0]));
|
||||
return await VerifiedBadgeRequests.pack(request);
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import define from "../../define.js";
|
||||
import { AccessTokens } from "../../../../models/index.js";
|
||||
import { publishUserEvent } from "../../../../services/stream.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
tokenId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"tokenId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const exist = await AccessTokens.exist({
|
||||
where: {
|
||||
id: ps.tokenId
|
||||
}
|
||||
});
|
||||
if (exist) {
|
||||
await AccessTokens.delete({
|
||||
id: ps.tokenId,
|
||||
userId: user.id
|
||||
});
|
||||
// Terminate streaming
|
||||
publishUserEvent(user.id, "terminate");
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import define from "../../define.js";
|
||||
import { Signins } from "../../../../models/index.js";
|
||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: 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(Signins.createQueryBuilder("signin"), ps.sinceId, ps.untilId).andWhere("signin.userId = :meId", {
|
||||
meId: user.id
|
||||
});
|
||||
const history = await query.take(ps.limit).getMany();
|
||||
return await Promise.all(history.map((record)=>Signins.pack(record)));
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { removePinned } from "../../../../services/i/pin.js";
|
||||
import define from "../../define.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { Users } from "../../../../models/index.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account",
|
||||
"notes"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
errors: {
|
||||
noSuchNote: {
|
||||
message: "No such note.",
|
||||
code: "NO_SUCH_NOTE",
|
||||
id: "454170ce-9d63-4a43-9da1-ea10afe81e21"
|
||||
}
|
||||
},
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "MeDetailed"
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
noteId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"noteId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
await removePinned(user, ps.noteId).catch((e)=>{
|
||||
if (e.id === "b302d4cf-c050-400a-bbb3-be208681f40c") throw new ApiError(meta.errors.noSuchNote);
|
||||
throw e;
|
||||
});
|
||||
return await Users.pack(user.id, user, {
|
||||
detail: true
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { publishMainStream } from "../../../../services/stream.js";
|
||||
import define from "../../define.js";
|
||||
import rndstr from "rndstr";
|
||||
import config from "../../../../config/index.js";
|
||||
import { Users, UserProfiles } from "../../../../models/index.js";
|
||||
import { sendEmail } from "../../../../services/send-email.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { validateEmailForAccount } from "../../../../services/validate-email-for-account.js";
|
||||
import { HOUR } from "../../../../const.js";
|
||||
import { comparePassword } from "../../../../misc/password.js";
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
secure: true,
|
||||
limit: {
|
||||
duration: HOUR,
|
||||
max: 3
|
||||
},
|
||||
errors: {
|
||||
incorrectPassword: {
|
||||
message: "Incorrect password.",
|
||||
code: "INCORRECT_PASSWORD",
|
||||
id: "e54c1d7e-e7d6-4103-86b6-0a95069b4ad3"
|
||||
},
|
||||
unavailable: {
|
||||
message: "Unavailable email address.",
|
||||
code: "UNAVAILABLE",
|
||||
id: "a2defefb-f220-8849-0af6-17f816099323"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
password: {
|
||||
type: "string"
|
||||
},
|
||||
email: {
|
||||
type: "string",
|
||||
nullable: true
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"password"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
// Compare password
|
||||
const same = await comparePassword(ps.password, profile.password);
|
||||
if (!same) {
|
||||
throw new ApiError(meta.errors.incorrectPassword);
|
||||
}
|
||||
if (ps.email != null) {
|
||||
const available = await validateEmailForAccount(ps.email);
|
||||
if (!available) {
|
||||
throw new ApiError(meta.errors.unavailable);
|
||||
}
|
||||
}
|
||||
await UserProfiles.update(user.id, {
|
||||
email: ps.email,
|
||||
emailVerified: false,
|
||||
emailVerifyCode: null
|
||||
});
|
||||
const iObj = await Users.pack(user.id, user, {
|
||||
detail: true,
|
||||
includeSecrets: true
|
||||
});
|
||||
// Publish meUpdated event
|
||||
publishMainStream(user.id, "meUpdated", iObj);
|
||||
if (ps.email != null) {
|
||||
const code = rndstr("a-z0-9", 16);
|
||||
await UserProfiles.update(user.id, {
|
||||
emailVerifyCode: code
|
||||
});
|
||||
const link = `${config.url}/verify-email/${code}`;
|
||||
sendEmail(ps.email, "Email verification", `To verify email, please click this link:<br><a href="${link}">${link}</a>`, `To verify email, please click this link: ${link}`);
|
||||
}
|
||||
return iObj;
|
||||
});
|
||||
@@ -0,0 +1,349 @@
|
||||
import RE2 from "re2";
|
||||
import { updateUserProfileData } from "../../../../services/i/update.js";
|
||||
import { Users, DriveFiles, UserProfiles, Pages } from "../../../../models/index.js";
|
||||
import { notificationTypes } from "../../../../types.js";
|
||||
import { langmap } from "../../../../misc/langmap.js";
|
||||
import { verifyLink } from "../../../../services/fetch-rel-me.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import define from "../../define.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
errors: {
|
||||
noSuchAvatar: {
|
||||
message: "No such avatar file.",
|
||||
code: "NO_SUCH_AVATAR",
|
||||
id: "539f3a45-f215-4f81-a9a8-31293640207f"
|
||||
},
|
||||
noSuchBanner: {
|
||||
message: "No such banner file.",
|
||||
code: "NO_SUCH_BANNER",
|
||||
id: "0d8f5629-f210-41c2-9433-735831a58595"
|
||||
},
|
||||
avatarNotAnImage: {
|
||||
message: "The file specified as an avatar is not an image.",
|
||||
code: "AVATAR_NOT_AN_IMAGE",
|
||||
id: "f419f9f8-2f4d-46b1-9fb4-49d3a2fd7191"
|
||||
},
|
||||
bannerNotAnImage: {
|
||||
message: "The file specified as a banner is not an image.",
|
||||
code: "BANNER_NOT_AN_IMAGE",
|
||||
id: "75aedb19-2afd-4e6d-87fc-67941256fa60"
|
||||
},
|
||||
noSuchPage: {
|
||||
message: "No such page.",
|
||||
code: "NO_SUCH_PAGE",
|
||||
id: "8e01b590-7eb9-431b-a239-860e086c408e"
|
||||
},
|
||||
invalidRegexp: {
|
||||
message: "Invalid Regular Expression.",
|
||||
code: "INVALID_REGEXP",
|
||||
id: "0d786918-10df-41cd-8f33-8dec7d9a89a5"
|
||||
},
|
||||
invalidFieldName: {
|
||||
message: "Invalid field name.",
|
||||
code: "INVALID_FIELD_NAME",
|
||||
id: "8f81972e-8b53-4d30-b0d2-efb026dda673"
|
||||
},
|
||||
invalidFieldValue: {
|
||||
message: "Invalid field value.",
|
||||
code: "INVALID_FIELD_VALUE",
|
||||
id: "aede7444-244b-11ee-be56-0242ac120002"
|
||||
}
|
||||
},
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "MeDetailed"
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
...Users.nameSchema,
|
||||
nullable: true
|
||||
},
|
||||
description: {
|
||||
...Users.descriptionSchema,
|
||||
nullable: true
|
||||
},
|
||||
location: {
|
||||
...Users.locationSchema,
|
||||
nullable: true
|
||||
},
|
||||
birthday: {
|
||||
...Users.birthdaySchema,
|
||||
nullable: true
|
||||
},
|
||||
lang: {
|
||||
type: "string",
|
||||
enum: [
|
||||
null,
|
||||
...Object.keys(langmap)
|
||||
],
|
||||
nullable: true
|
||||
},
|
||||
avatarId: {
|
||||
type: "string",
|
||||
format: "misskey:id",
|
||||
nullable: true
|
||||
},
|
||||
bannerId: {
|
||||
type: "string",
|
||||
format: "misskey:id",
|
||||
nullable: true
|
||||
},
|
||||
fields: {
|
||||
type: "array",
|
||||
minItems: 0,
|
||||
maxItems: 16,
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string"
|
||||
},
|
||||
value: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"name",
|
||||
"value"
|
||||
]
|
||||
}
|
||||
},
|
||||
isLocked: {
|
||||
type: "boolean"
|
||||
},
|
||||
isExplorable: {
|
||||
type: "boolean"
|
||||
},
|
||||
hideOnlineStatus: {
|
||||
type: "boolean"
|
||||
},
|
||||
publicReactions: {
|
||||
type: "boolean"
|
||||
},
|
||||
allowCalls: {
|
||||
type: "boolean"
|
||||
},
|
||||
symbolFileId: {
|
||||
type: "string",
|
||||
format: "misskey:id",
|
||||
nullable: true
|
||||
},
|
||||
carefulBot: {
|
||||
type: "boolean"
|
||||
},
|
||||
autoAcceptFollowed: {
|
||||
type: "boolean"
|
||||
},
|
||||
noCrawle: {
|
||||
type: "boolean"
|
||||
},
|
||||
preventAiLearning: {
|
||||
type: "boolean"
|
||||
},
|
||||
isBot: {
|
||||
type: "boolean"
|
||||
},
|
||||
isCat: {
|
||||
type: "boolean"
|
||||
},
|
||||
speakAsCat: {
|
||||
type: "boolean"
|
||||
},
|
||||
minorBadges: {
|
||||
type: "array",
|
||||
uniqueItems: true,
|
||||
maxItems: 1,
|
||||
items: {
|
||||
type: "string",
|
||||
enum: [
|
||||
"K",
|
||||
"T",
|
||||
"E"
|
||||
]
|
||||
}
|
||||
},
|
||||
injectFeaturedNote: {
|
||||
type: "boolean"
|
||||
},
|
||||
receiveAnnouncementEmail: {
|
||||
type: "boolean"
|
||||
},
|
||||
alwaysMarkNsfw: {
|
||||
type: "boolean"
|
||||
},
|
||||
ffVisibility: {
|
||||
type: "string",
|
||||
enum: [
|
||||
"public",
|
||||
"followers",
|
||||
"private"
|
||||
]
|
||||
},
|
||||
pinnedPageId: {
|
||||
type: "string",
|
||||
format: "misskey:id",
|
||||
nullable: true
|
||||
},
|
||||
mutedWords: {
|
||||
type: "array"
|
||||
},
|
||||
mutedInstances: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
mutingNotificationTypes: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
enum: notificationTypes
|
||||
}
|
||||
},
|
||||
emailNotificationTypes: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
pronouns: {
|
||||
type: "object",
|
||||
nullable: true
|
||||
},
|
||||
canBite: {
|
||||
type: "string",
|
||||
enum: [
|
||||
"anyone",
|
||||
"followers",
|
||||
"nobody"
|
||||
],
|
||||
nullable: true
|
||||
}
|
||||
}
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, _user, token)=>{
|
||||
const user = await Users.findOneByOrFail({
|
||||
id: _user.id
|
||||
});
|
||||
const isSecure = token == null;
|
||||
const updates = {};
|
||||
const profileUpdates = {};
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
if (ps.name !== undefined) updates.name = ps.name;
|
||||
if (ps.description !== undefined) profileUpdates.description = ps.description;
|
||||
if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
|
||||
if (ps.location !== undefined) profileUpdates.location = ps.location;
|
||||
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
|
||||
if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility;
|
||||
if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
|
||||
if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
|
||||
if (ps.mutedWords !== undefined) {
|
||||
// validate regular expression syntax
|
||||
ps.mutedWords.filter((x)=>!Array.isArray(x)).forEach((x)=>{
|
||||
const regexp = x.match(/^\/(.+)\/(.*)$/);
|
||||
if (!regexp) throw new ApiError(meta.errors.invalidRegexp);
|
||||
try {
|
||||
new RE2(regexp[1], regexp[2]);
|
||||
} catch (err) {
|
||||
throw new ApiError(meta.errors.invalidRegexp);
|
||||
}
|
||||
});
|
||||
profileUpdates.mutedWords = ps.mutedWords;
|
||||
profileUpdates.enableWordMute = ps.mutedWords.length > 0;
|
||||
}
|
||||
if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances;
|
||||
if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes;
|
||||
if (typeof ps.isLocked === "boolean") updates.isLocked = ps.isLocked;
|
||||
if (typeof ps.isExplorable === "boolean") updates.isExplorable = ps.isExplorable;
|
||||
if (typeof ps.hideOnlineStatus === "boolean") updates.hideOnlineStatus = ps.hideOnlineStatus;
|
||||
if (typeof ps.publicReactions === "boolean") profileUpdates.publicReactions = ps.publicReactions;
|
||||
if (typeof ps.allowCalls === "boolean") profileUpdates.allowCalls = ps.allowCalls;
|
||||
if (ps.symbolFileId !== undefined) profileUpdates.symbolFileId = ps.symbolFileId;
|
||||
if (typeof ps.isBot === "boolean") updates.isBot = ps.isBot;
|
||||
if (ps.minorBadges !== undefined) updates.minorBadges = ps.minorBadges.slice(0, 1);
|
||||
if (typeof ps.carefulBot === "boolean") profileUpdates.carefulBot = ps.carefulBot;
|
||||
if (typeof ps.autoAcceptFollowed === "boolean") profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
||||
if (typeof ps.noCrawle === "boolean") profileUpdates.noCrawle = ps.noCrawle;
|
||||
if (typeof ps.preventAiLearning === "boolean") profileUpdates.preventAiLearning = ps.preventAiLearning;
|
||||
if (typeof ps.isCat === "boolean") updates.isCat = ps.isCat;
|
||||
if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat;
|
||||
if (typeof ps.injectFeaturedNote === "boolean") profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
||||
if (typeof ps.receiveAnnouncementEmail === "boolean") profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
|
||||
if (typeof ps.alwaysMarkNsfw === "boolean") profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
|
||||
if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
|
||||
if (typeof ps.pronouns === "object") {
|
||||
if (ps.pronouns === null) ps.pronouns = {};
|
||||
for (const key of Object.keys(ps.pronouns)){
|
||||
if (key.length !== 2 || typeof ps.pronouns[key] !== "string" || ps.pronouns[key].length === 0) {
|
||||
delete ps.pronouns[key];
|
||||
}
|
||||
}
|
||||
profileUpdates.pronouns = ps.pronouns;
|
||||
}
|
||||
const avatar = ps.avatarId ? await DriveFiles.findOneBy({
|
||||
id: ps.avatarId
|
||||
}) : null;
|
||||
const banner = ps.bannerId ? await DriveFiles.findOneBy({
|
||||
id: ps.bannerId
|
||||
}) : null;
|
||||
if (ps.avatarId) {
|
||||
if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
|
||||
if (!avatar.type.startsWith("image/")) throw new ApiError(meta.errors.avatarNotAnImage);
|
||||
updates.avatarUrl = DriveFiles.getDatabasePrefetchUrl(avatar, true);
|
||||
updates.avatarBlurhash = avatar.blurhash;
|
||||
}
|
||||
if (ps.bannerId) {
|
||||
if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
|
||||
if (!banner.type.startsWith("image/")) throw new ApiError(meta.errors.bannerNotAnImage);
|
||||
updates.bannerUrl = DriveFiles.getDatabasePrefetchUrl(banner, false);
|
||||
updates.bannerBlurhash = banner.blurhash;
|
||||
}
|
||||
if (ps.pinnedPageId) {
|
||||
const page = await Pages.findOneBy({
|
||||
id: ps.pinnedPageId
|
||||
});
|
||||
if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage);
|
||||
profileUpdates.pinnedPageId = page.id;
|
||||
} else if (ps.pinnedPageId === null) {
|
||||
profileUpdates.pinnedPageId = null;
|
||||
}
|
||||
if (ps.fields) {
|
||||
for (const field of ps.fields){
|
||||
if (!field || field.name === "" || field.value === "") {
|
||||
continue;
|
||||
}
|
||||
if (typeof field.name !== "string" || field.name === "") {
|
||||
throw new ApiError(meta.errors.invalidFieldName);
|
||||
}
|
||||
if (typeof field.value !== "string" || field.value === "") {
|
||||
throw new ApiError(meta.errors.invalidFieldValue);
|
||||
}
|
||||
if (field.value.startsWith("http")) {
|
||||
field.verified = await verifyLink(field.value, user.username);
|
||||
}
|
||||
}
|
||||
profileUpdates.fields = ps.fields.filter((x)=>Object.keys(x).length !== 0).map((x)=>{
|
||||
return {
|
||||
name: x.name,
|
||||
value: x.value,
|
||||
verified: x.verified
|
||||
};
|
||||
});
|
||||
}
|
||||
if (ps.canBite) {
|
||||
updates.canBite = ps.canBite;
|
||||
}
|
||||
return updateUserProfileData(user, profile, updates, profileUpdates, isSecure);
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { DriveFiles, UserEmojis } from "../../../../../models/index.js";
|
||||
import { genId } from "../../../../../misc/gen-id.js";
|
||||
import { getEmojiSize } from "../../../../../misc/emoji-meta.js";
|
||||
import { clearUserEmojiCache } from "../../../../../misc/populate-emojis.js";
|
||||
function normalizeMimeType(type) {
|
||||
const mime = type?.split(";")[0]?.trim().toLowerCase();
|
||||
return mime && mime.length <= 64 ? mime : null;
|
||||
}
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: "No such file.",
|
||||
code: "NO_SUCH_FILE",
|
||||
id: "7be656a7-cb71-41a8-9904-e8b6481e61dd"
|
||||
},
|
||||
notImage: {
|
||||
message: "The file is not an image.",
|
||||
code: "NOT_IMAGE",
|
||||
id: "64cfbb24-2628-4217-873d-09e22957cf49"
|
||||
},
|
||||
alreadyExists: {
|
||||
message: "User emoji already exists.",
|
||||
code: "ALREADY_EXISTS",
|
||||
id: "259b6fdb-c603-4244-81cb-7218b967ab62"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
pattern: "^[a-z0-9_]{1,64}$"
|
||||
},
|
||||
fileId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
glyph: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"name",
|
||||
"fileId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const file = await DriveFiles.findOneBy({
|
||||
id: ps.fileId,
|
||||
userId: me.id
|
||||
});
|
||||
if (!file) throw new ApiError(meta.errors.noSuchFile);
|
||||
if (!file.type.startsWith("image/")) throw new ApiError(meta.errors.notImage);
|
||||
const exists = await UserEmojis.findOneBy({
|
||||
name: ps.name,
|
||||
userId: me.id
|
||||
});
|
||||
if (exists) throw new ApiError(meta.errors.alreadyExists);
|
||||
const size = await getEmojiSize(file.url).catch(()=>({
|
||||
width: null,
|
||||
height: null
|
||||
}));
|
||||
const type = normalizeMimeType(file.webpublicType ?? file.type);
|
||||
const emoji = await UserEmojis.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
name: ps.name,
|
||||
userId: me.id,
|
||||
userGroupId: null,
|
||||
originalUrl: file.url,
|
||||
publicUrl: file.webpublicUrl ?? file.url,
|
||||
type,
|
||||
glyph: ps.glyph,
|
||||
width: size.width || null,
|
||||
height: size.height || null
|
||||
}).then((x)=>UserEmojis.findOneByOrFail(x.identifiers[0]));
|
||||
await clearUserEmojiCache(emoji.name, me.username, me.host);
|
||||
return {
|
||||
id: emoji.id,
|
||||
name: emoji.name,
|
||||
url: emoji.publicUrl || emoji.originalUrl,
|
||||
glyph: emoji.glyph,
|
||||
glyphUrl: emoji.glyph ? emoji.originalUrl : null,
|
||||
width: emoji.width,
|
||||
height: emoji.height
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { UserEmojis } from "../../../../../models/index.js";
|
||||
import { clearUserEmojiCache } from "../../../../../misc/populate-emojis.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
errors: {
|
||||
noSuchEmoji: {
|
||||
message: "No such user emoji.",
|
||||
code: "NO_SUCH_USER_EMOJI",
|
||||
id: "a44ed453-7bec-4ae0-940e-2eb3e80e7b62"
|
||||
}
|
||||
}
|
||||
};
|
||||
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 UserEmojis.findOneBy({
|
||||
id: ps.id,
|
||||
userId: me.id
|
||||
});
|
||||
if (!emoji) throw new ApiError(meta.errors.noSuchEmoji);
|
||||
await UserEmojis.delete(emoji.id);
|
||||
await clearUserEmojiCache(emoji.name, me.username, me.host);
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import define from "../../../define.js";
|
||||
import { UserEmojis } from "../../../../../models/index.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:account"
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (_ps, me)=>{
|
||||
const emojis = await UserEmojis.find({
|
||||
where: {
|
||||
userId: me.id
|
||||
},
|
||||
order: {
|
||||
createdAt: "DESC"
|
||||
}
|
||||
});
|
||||
return emojis.map((emoji)=>({
|
||||
id: emoji.id,
|
||||
name: emoji.name,
|
||||
url: emoji.publicUrl || emoji.originalUrl,
|
||||
glyph: emoji.glyph,
|
||||
glyphUrl: emoji.glyph ? emoji.originalUrl : null,
|
||||
width: emoji.width,
|
||||
height: emoji.height
|
||||
}));
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import define from "../../define.js";
|
||||
import { UserGroupInvitations } from "../../../../models/index.js";
|
||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"account",
|
||||
"groups"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:user-groups",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
format: "id"
|
||||
},
|
||||
group: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserGroup"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
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(UserGroupInvitations.createQueryBuilder("invitation"), ps.sinceId, ps.untilId).andWhere("invitation.userId = :meId", {
|
||||
meId: user.id
|
||||
}).leftJoinAndSelect("invitation.userGroup", "user_group");
|
||||
const invitations = await query.take(ps.limit).getMany();
|
||||
return await UserGroupInvitations.packMany(invitations);
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import define from "../../../define.js";
|
||||
import { genId } from "../../../../../misc/gen-id.js";
|
||||
import { Webhooks } from "../../../../../models/index.js";
|
||||
import { publishInternalEvent } from "../../../../../services/stream.js";
|
||||
import { webhookEventTypes } from "../../../../../models/entities/webhook.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"webhooks"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account"
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 100
|
||||
},
|
||||
url: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 1024
|
||||
},
|
||||
secret: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 1024
|
||||
},
|
||||
on: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
enum: webhookEventTypes
|
||||
}
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"name",
|
||||
"url",
|
||||
"secret",
|
||||
"on"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const webhook = await Webhooks.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
name: ps.name,
|
||||
url: ps.url,
|
||||
secret: ps.secret,
|
||||
on: ps.on
|
||||
}).then((x)=>Webhooks.findOneByOrFail(x.identifiers[0]));
|
||||
publishInternalEvent("webhookCreated", webhook);
|
||||
return webhook;
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { Webhooks } from "../../../../../models/index.js";
|
||||
import { publishInternalEvent } from "../../../../../services/stream.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"webhooks"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
errors: {
|
||||
noSuchWebhook: {
|
||||
message: "No such webhook.",
|
||||
code: "NO_SUCH_WEBHOOK",
|
||||
id: "bae73e5a-5522-4965-ae19-3a8688e71d82"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
webhookId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"webhookId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const webhook = await Webhooks.findOneBy({
|
||||
id: ps.webhookId,
|
||||
userId: user.id
|
||||
});
|
||||
if (webhook == null) {
|
||||
throw new ApiError(meta.errors.noSuchWebhook);
|
||||
}
|
||||
await Webhooks.delete(webhook.id);
|
||||
publishInternalEvent("webhookDeleted", webhook);
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import define from "../../../define.js";
|
||||
import { Webhooks } from "../../../../../models/index.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"webhooks",
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:account"
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const webhooks = await Webhooks.findBy({
|
||||
userId: me.id
|
||||
});
|
||||
return webhooks;
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { Webhooks } from "../../../../../models/index.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"webhooks"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:account",
|
||||
errors: {
|
||||
noSuchWebhook: {
|
||||
message: "No such webhook.",
|
||||
code: "NO_SUCH_WEBHOOK",
|
||||
id: "50f614d9-3047-4f7e-90d8-ad6b2d5fb098"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
webhookId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"webhookId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const webhook = await Webhooks.findOneBy({
|
||||
id: ps.webhookId,
|
||||
userId: user.id
|
||||
});
|
||||
if (webhook == null) {
|
||||
throw new ApiError(meta.errors.noSuchWebhook);
|
||||
}
|
||||
return webhook;
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { Webhooks } from "../../../../../models/index.js";
|
||||
import { publishInternalEvent } from "../../../../../services/stream.js";
|
||||
import { webhookEventTypes } from "../../../../../models/entities/webhook.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"webhooks"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
errors: {
|
||||
noSuchWebhook: {
|
||||
message: "No such webhook.",
|
||||
code: "NO_SUCH_WEBHOOK",
|
||||
id: "fb0fea69-da18-45b1-828d-bd4fd1612518"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
webhookId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 100
|
||||
},
|
||||
url: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 1024
|
||||
},
|
||||
secret: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 1024
|
||||
},
|
||||
on: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
enum: webhookEventTypes
|
||||
}
|
||||
},
|
||||
active: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"webhookId",
|
||||
"name",
|
||||
"url",
|
||||
"secret",
|
||||
"on",
|
||||
"active"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const webhook = await Webhooks.findOneBy({
|
||||
id: ps.webhookId,
|
||||
userId: user.id
|
||||
});
|
||||
if (webhook == null) {
|
||||
throw new ApiError(meta.errors.noSuchWebhook);
|
||||
}
|
||||
await Webhooks.update(webhook.id, {
|
||||
name: ps.name,
|
||||
url: ps.url,
|
||||
secret: ps.secret,
|
||||
on: ps.on,
|
||||
active: ps.active
|
||||
});
|
||||
publishInternalEvent("webhookUpdated", webhook);
|
||||
});
|
||||
Reference in New Issue
Block a user