Fixed 267U.pre2
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { Clips } from "../../../../models/index.js";
|
||||
import define from "../../define.js";
|
||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users",
|
||||
"clips"
|
||||
],
|
||||
requireCredentialPrivateMode: true,
|
||||
description: "Show all clips this user owns.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "Clip"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
},
|
||||
sinceId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
untilId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const query = makePaginationQuery(Clips.createQueryBuilder("clip"), ps.sinceId, ps.untilId).andWhere("clip.userId = :userId", {
|
||||
userId: ps.userId
|
||||
}).andWhere("clip.isPublic = true");
|
||||
const clips = await query.take(ps.limit).getMany();
|
||||
return await Clips.packMany(clips);
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
import { IsNull } from "typeorm";
|
||||
import { Users, Followings, UserProfiles } from "../../../../models/index.js";
|
||||
import { toPunyNullable } from "../../../../misc/convert-host.js";
|
||||
import define from "../../define.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users"
|
||||
],
|
||||
requireCredential: false,
|
||||
requireCredentialPrivateMode: true,
|
||||
description: "Show everyone that follows this user.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "Following"
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "27fa5435-88ab-43de-9360-387de88727cd"
|
||||
},
|
||||
forbidden: {
|
||||
message: "Forbidden.",
|
||||
code: "FORBIDDEN",
|
||||
id: "3c6a84db-d619-26af-ca14-06232a21df8a"
|
||||
},
|
||||
nullFollowers: {
|
||||
message: "No followers found.",
|
||||
code: "NULL_FOLLOWERS",
|
||||
id: "174a6507-a6c2-4925-8e5d-92fd08aedc9e"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
sinceId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
untilId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
}
|
||||
},
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userId"
|
||||
]
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
username: {
|
||||
type: "string"
|
||||
},
|
||||
host: {
|
||||
type: "string",
|
||||
nullable: true,
|
||||
description: "The local host is represented with `null`."
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"username",
|
||||
"host"
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const user = await Users.findOneBy(ps.userId != null ? {
|
||||
id: ps.userId
|
||||
} : {
|
||||
usernameLower: ps.username.toLowerCase(),
|
||||
host: toPunyNullable(ps.host) ?? IsNull()
|
||||
});
|
||||
if (user == null) {
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
}
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
if (profile.ffVisibility === "private") {
|
||||
if (me == null || me.id !== user.id) {
|
||||
throw new ApiError(meta.errors.forbidden);
|
||||
}
|
||||
} else if (profile.ffVisibility === "followers") {
|
||||
if (me == null) {
|
||||
throw new ApiError(meta.errors.forbidden);
|
||||
} else if (me.id !== user.id) {
|
||||
const isFollowed = await Followings.exist({
|
||||
where: {
|
||||
followeeId: user.id,
|
||||
followerId: me.id
|
||||
}
|
||||
});
|
||||
if (!isFollowed) {
|
||||
throw new ApiError(meta.errors.nullFollowers);
|
||||
}
|
||||
}
|
||||
}
|
||||
const query = makePaginationQuery(Followings.createQueryBuilder("following"), ps.sinceId, ps.untilId).andWhere("following.followeeId = :userId", {
|
||||
userId: user.id
|
||||
}).innerJoinAndSelect("following.follower", "follower");
|
||||
const followings = await query.take(ps.limit).getMany();
|
||||
return await Followings.packMany(followings, me, {
|
||||
populateFollower: true
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
import { IsNull } from "typeorm";
|
||||
import { Users, Followings, UserProfiles } from "../../../../models/index.js";
|
||||
import { toPunyNullable } from "../../../../misc/convert-host.js";
|
||||
import define from "../../define.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users"
|
||||
],
|
||||
requireCredential: false,
|
||||
requireCredentialPrivateMode: true,
|
||||
description: "Show everyone that this user is following.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "Following"
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "63e4aba4-4156-4e53-be25-c9559e42d71b"
|
||||
},
|
||||
forbidden: {
|
||||
message: "Forbidden.",
|
||||
code: "FORBIDDEN",
|
||||
id: "f6cdb0df-c19f-ec5c-7dbb-0ba84a1f92ba"
|
||||
},
|
||||
cannot_find: {
|
||||
message: "Cannot find the following.",
|
||||
code: "CANNOT_FIND",
|
||||
id: "7a55f0d7-8e06-4a7e-9c77-ee7d59b25a82"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
sinceId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
untilId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
}
|
||||
},
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userId"
|
||||
]
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
username: {
|
||||
type: "string"
|
||||
},
|
||||
host: {
|
||||
type: "string",
|
||||
nullable: true,
|
||||
description: "The local host is represented with `null`."
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"username",
|
||||
"host"
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const user = await Users.findOneBy(ps.userId != null ? {
|
||||
id: ps.userId
|
||||
} : {
|
||||
usernameLower: ps.username.toLowerCase(),
|
||||
host: toPunyNullable(ps.host) ?? IsNull()
|
||||
});
|
||||
if (user == null) {
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
}
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: user.id
|
||||
});
|
||||
if (profile.ffVisibility === "private") {
|
||||
if (me == null || me.id !== user.id) {
|
||||
throw new ApiError(meta.errors.forbidden);
|
||||
}
|
||||
} else if (profile.ffVisibility === "followers") {
|
||||
if (me == null) {
|
||||
throw new ApiError(meta.errors.forbidden);
|
||||
} else if (me.id !== user.id) {
|
||||
const isFollowing = await Followings.exist({
|
||||
where: {
|
||||
followeeId: user.id,
|
||||
followerId: me.id
|
||||
}
|
||||
});
|
||||
if (!isFollowing) {
|
||||
throw new ApiError(meta.errors.cannot_find);
|
||||
}
|
||||
}
|
||||
}
|
||||
const query = makePaginationQuery(Followings.createQueryBuilder("following"), ps.sinceId, ps.untilId).andWhere("following.followerId = :userId", {
|
||||
userId: user.id
|
||||
}).innerJoinAndSelect("following.followee", "followee");
|
||||
const followings = await query.take(ps.limit).getMany();
|
||||
return await Followings.packMany(followings, me, {
|
||||
populateFollowee: true
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import define from "../../../define.js";
|
||||
import { GalleryPosts } from "../../../../../models/index.js";
|
||||
import { makePaginationQuery } from "../../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users",
|
||||
"gallery"
|
||||
],
|
||||
requireCredentialPrivateMode: true,
|
||||
description: "Show all gallery posts by the given user.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "GalleryPost"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
},
|
||||
sinceId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
untilId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const query = makePaginationQuery(GalleryPosts.createQueryBuilder("post"), ps.sinceId, ps.untilId).andWhere("post.userId = :userId", {
|
||||
userId: ps.userId
|
||||
});
|
||||
const posts = await query.take(ps.limit).getMany();
|
||||
return await GalleryPosts.packMany(posts, user);
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Not, In, IsNull } from "typeorm";
|
||||
import { maximum } from "../../../../prelude/array.js";
|
||||
import { Notes, Users } from "../../../../models/index.js";
|
||||
import define from "../../define.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { getUser } from "../../common/getters.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users"
|
||||
],
|
||||
requireCredential: false,
|
||||
requireCredentialPrivateMode: true,
|
||||
description: "Get a list of other users that the specified user frequently replies to.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
properties: {
|
||||
user: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserDetailed"
|
||||
},
|
||||
weight: {
|
||||
type: "number",
|
||||
optional: false,
|
||||
nullable: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "e6965129-7b2a-40a4-bae2-cd84cd434822"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
// Lookup user
|
||||
const user = await getUser(ps.userId).catch((e)=>{
|
||||
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
// Fetch recent notes
|
||||
const recentNotes = await Notes.find({
|
||||
where: {
|
||||
userId: user.id,
|
||||
replyId: Not(IsNull())
|
||||
},
|
||||
order: {
|
||||
id: -1
|
||||
},
|
||||
take: 1000,
|
||||
select: [
|
||||
"replyId"
|
||||
]
|
||||
});
|
||||
// 投稿が少なかったら中断
|
||||
if (recentNotes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
// TODO ミュートを考慮
|
||||
const replyTargetNotes = await Notes.find({
|
||||
where: {
|
||||
id: In(recentNotes.map((p)=>p.replyId))
|
||||
},
|
||||
select: [
|
||||
"userId"
|
||||
]
|
||||
});
|
||||
const repliedUsers = {};
|
||||
// Extract replies from recent notes
|
||||
for (const userId of replyTargetNotes.map((x)=>x.userId.toString())){
|
||||
if (repliedUsers[userId]) {
|
||||
repliedUsers[userId]++;
|
||||
} else {
|
||||
repliedUsers[userId] = 1;
|
||||
}
|
||||
}
|
||||
// Calc peak
|
||||
const peak = maximum(Object.values(repliedUsers));
|
||||
// Sort replies by frequency
|
||||
const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b)=>repliedUsers[b] - repliedUsers[a]);
|
||||
// Extract top replied users
|
||||
const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
|
||||
// Make replies object (includes weights)
|
||||
const repliesObj = await Promise.all(topRepliedUsers.map(async (user)=>({
|
||||
user: await Users.pack(user, me, {
|
||||
detail: true
|
||||
}),
|
||||
weight: repliedUsers[user] / peak
|
||||
})));
|
||||
return repliesObj;
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { UserGroups, UserGroupJoinings } from "../../../../../models/index.js";
|
||||
import { genId } from "../../../../../misc/gen-id.js";
|
||||
import define from "../../../define.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:user-groups",
|
||||
description: "Create a new group.",
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserGroup"
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 100
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"name"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const userGroup = await UserGroups.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
name: ps.name
|
||||
}).then((x)=>UserGroups.findOneByOrFail(x.identifiers[0]));
|
||||
// Push the owner
|
||||
await UserGroupJoinings.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
userGroupId: userGroup.id
|
||||
});
|
||||
return await UserGroups.pack(userGroup);
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { UserGroups } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:user-groups",
|
||||
description: "Delete an existing group.",
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: "No such group.",
|
||||
code: "NO_SUCH_GROUP",
|
||||
id: "63dbd64c-cd77-413f-8e08-61781e210b38"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
groupId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"groupId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const userGroup = await UserGroups.findOneBy({
|
||||
id: ps.groupId,
|
||||
userId: user.id
|
||||
});
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
await UserGroups.delete(userGroup.id);
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
import define from "../../../../define.js";
|
||||
import { ApiError } from "../../../../error.js";
|
||||
import { DriveFiles, UserEmojis, UserGroups } from "../../../../../../models/index.js";
|
||||
import { genId } from "../../../../../../misc/gen-id.js";
|
||||
import { getEmojiSize } from "../../../../../../misc/emoji-meta.js";
|
||||
import { clearGroupEmojiCache } 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: [
|
||||
"groups"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:user-groups",
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: "No such group.",
|
||||
code: "NO_SUCH_GROUP",
|
||||
id: "ab752eba-3f77-4746-b805-1457719f648d"
|
||||
},
|
||||
noSuchFile: {
|
||||
message: "No such file.",
|
||||
code: "NO_SUCH_FILE",
|
||||
id: "be2812bf-55c0-4fd9-8d1b-813578fc81f9"
|
||||
},
|
||||
alreadyExists: {
|
||||
message: "Group emoji already exists.",
|
||||
code: "ALREADY_EXISTS",
|
||||
id: "7957d717-e009-47c8-b32d-37a13afed4db"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
groupId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
pattern: "^[a-z0-9_]{1,64}$"
|
||||
},
|
||||
fileId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
glyph: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"groupId",
|
||||
"name",
|
||||
"fileId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const group = await UserGroups.findOneBy({
|
||||
id: ps.groupId,
|
||||
userId: me.id
|
||||
});
|
||||
if (!group) throw new ApiError(meta.errors.noSuchGroup);
|
||||
const file = await DriveFiles.findOneBy({
|
||||
id: ps.fileId,
|
||||
userId: me.id
|
||||
});
|
||||
if (!file || !file.type.startsWith("image/")) throw new ApiError(meta.errors.noSuchFile);
|
||||
const exists = await UserEmojis.findOneBy({
|
||||
name: ps.name,
|
||||
userGroupId: group.id
|
||||
});
|
||||
if (exists) throw new ApiError(meta.errors.alreadyExists);
|
||||
const size = await getEmojiSize(file.url).catch(()=>({
|
||||
width: null,
|
||||
height: null
|
||||
}));
|
||||
const emoji = await UserEmojis.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
name: ps.name,
|
||||
userId: null,
|
||||
userGroupId: group.id,
|
||||
originalUrl: file.url,
|
||||
publicUrl: file.webpublicUrl ?? file.url,
|
||||
type: normalizeMimeType(file.webpublicType ?? file.type),
|
||||
glyph: ps.glyph,
|
||||
width: size.width || null,
|
||||
height: size.height || null
|
||||
}).then((x)=>UserEmojis.findOneByOrFail(x.identifiers[0]));
|
||||
await clearGroupEmojiCache(emoji.name, group.username);
|
||||
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,43 @@
|
||||
import define from "../../../../define.js";
|
||||
import { ApiError } from "../../../../error.js";
|
||||
import { UserEmojis, UserGroups } from "../../../../../../models/index.js";
|
||||
import { clearGroupEmojiCache } from "../../../../../../misc/populate-emojis.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:user-groups",
|
||||
errors: {
|
||||
noSuchEmoji: {
|
||||
message: "No such group emoji.",
|
||||
code: "NO_SUCH_GROUP_EMOJI",
|
||||
id: "e22d8bd7-d9c5-4470-8ef0-9a9095985b3f"
|
||||
}
|
||||
}
|
||||
};
|
||||
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
|
||||
});
|
||||
if (!emoji?.userGroupId) throw new ApiError(meta.errors.noSuchEmoji);
|
||||
const group = await UserGroups.findOneBy({
|
||||
id: emoji.userGroupId,
|
||||
userId: me.id
|
||||
});
|
||||
if (!group) throw new ApiError(meta.errors.noSuchEmoji);
|
||||
await UserEmojis.delete(emoji.id);
|
||||
await clearGroupEmojiCache(emoji.name, group.username);
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import define from "../../../../define.js";
|
||||
import { ApiError } from "../../../../error.js";
|
||||
import { UserEmojis, UserGroupJoinings, UserGroups } from "../../../../../../models/index.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:user-groups",
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: "No such group.",
|
||||
code: "NO_SUCH_GROUP",
|
||||
id: "e9f78109-710a-44fd-8ab2-51582f65dd97"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
groupId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"groupId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const group = await UserGroups.findOneBy({
|
||||
id: ps.groupId
|
||||
});
|
||||
if (!group) throw new ApiError(meta.errors.noSuchGroup);
|
||||
const member = await UserGroupJoinings.findOneBy({
|
||||
userGroupId: group.id,
|
||||
userId: me.id
|
||||
});
|
||||
if (group.userId !== me.id && !member) throw new ApiError(meta.errors.noSuchGroup);
|
||||
const emojis = await UserEmojis.find({
|
||||
where: {
|
||||
userGroupId: group.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,52 @@
|
||||
import { UserGroupJoinings, UserGroupInvitations } from "../../../../../../models/index.js";
|
||||
import { genId } from "../../../../../../misc/gen-id.js";
|
||||
import { ApiError } from "../../../../error.js";
|
||||
import define from "../../../../define.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups",
|
||||
"users"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:user-groups",
|
||||
description: "Join a group the authenticated user has been invited to.",
|
||||
errors: {
|
||||
noSuchInvitation: {
|
||||
message: "No such invitation.",
|
||||
code: "NO_SUCH_INVITATION",
|
||||
id: "98c11eca-c890-4f42-9806-c8c8303ebb5e"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
invitationId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"invitationId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
// Fetch the invitation
|
||||
const invitation = await UserGroupInvitations.findOneBy({
|
||||
id: ps.invitationId
|
||||
});
|
||||
if (invitation == null) {
|
||||
throw new ApiError(meta.errors.noSuchInvitation);
|
||||
}
|
||||
if (invitation.userId !== user.id) {
|
||||
throw new ApiError(meta.errors.noSuchInvitation);
|
||||
}
|
||||
// Push the user
|
||||
await UserGroupJoinings.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
userGroupId: invitation.userGroupId
|
||||
});
|
||||
UserGroupInvitations.delete(invitation.id);
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { UserGroupInvitations } from "../../../../../../models/index.js";
|
||||
import define from "../../../../define.js";
|
||||
import { ApiError } from "../../../../error.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups",
|
||||
"users"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:user-groups",
|
||||
description: "Delete an existing group invitation for the authenticated user without joining the group.",
|
||||
errors: {
|
||||
noSuchInvitation: {
|
||||
message: "No such invitation.",
|
||||
code: "NO_SUCH_INVITATION",
|
||||
id: "ad7471d4-2cd9-44b4-ac68-e7136b4ce656"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
invitationId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"invitationId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
// Fetch the invitation
|
||||
const invitation = await UserGroupInvitations.findOneBy({
|
||||
id: ps.invitationId
|
||||
});
|
||||
if (invitation == null) {
|
||||
throw new ApiError(meta.errors.noSuchInvitation);
|
||||
}
|
||||
if (invitation.userId !== user.id) {
|
||||
throw new ApiError(meta.errors.noSuchInvitation);
|
||||
}
|
||||
await UserGroupInvitations.delete(invitation.id);
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
import { UserGroups, UserGroupJoinings, UserGroupInvitations } from "../../../../../models/index.js";
|
||||
import { genId } from "../../../../../misc/gen-id.js";
|
||||
import { createNotification } from "../../../../../services/create-notification.js";
|
||||
import { getUser } from "../../../common/getters.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import define from "../../../define.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups",
|
||||
"users"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:user-groups",
|
||||
description: "Invite a user to an existing group.",
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: "No such group.",
|
||||
code: "NO_SUCH_GROUP",
|
||||
id: "583f8bc0-8eee-4b78-9299-1e14fc91e409"
|
||||
},
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "da52de61-002c-475b-90e1-ba64f9cf13a8"
|
||||
},
|
||||
alreadyAdded: {
|
||||
message: "That user has already been added to that group.",
|
||||
code: "ALREADY_ADDED",
|
||||
id: "7e35c6a0-39b2-4488-aea6-6ee20bd5da2c"
|
||||
},
|
||||
alreadyInvited: {
|
||||
message: "That user has already been invited to that group.",
|
||||
code: "ALREADY_INVITED",
|
||||
id: "ee0f58b4-b529-4d13-b761-b9a3e69f97e6"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
groupId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"groupId",
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
// Fetch the group
|
||||
const userGroup = await UserGroups.findOneBy({
|
||||
id: ps.groupId,
|
||||
userId: me.id
|
||||
});
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
// Fetch the user
|
||||
const user = await getUser(ps.userId).catch((e)=>{
|
||||
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
const joining = await UserGroupJoinings.findOneBy({
|
||||
userGroupId: userGroup.id,
|
||||
userId: user.id
|
||||
});
|
||||
if (joining) {
|
||||
throw new ApiError(meta.errors.alreadyAdded);
|
||||
}
|
||||
const existInvitation = await UserGroupInvitations.findOneBy({
|
||||
userGroupId: userGroup.id,
|
||||
userId: user.id
|
||||
});
|
||||
if (existInvitation) {
|
||||
throw new ApiError(meta.errors.alreadyInvited);
|
||||
}
|
||||
const invitation = await UserGroupInvitations.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
userGroupId: userGroup.id
|
||||
}).then((x)=>UserGroupInvitations.findOneByOrFail(x.identifiers[0]));
|
||||
// 通知を作成
|
||||
createNotification(user.id, "groupInvited", {
|
||||
notifierId: me.id,
|
||||
userGroupInvitationId: invitation.id
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Not, In } from "typeorm";
|
||||
import { UserGroups, UserGroupJoinings } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups",
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:user-groups",
|
||||
description: "List the groups that the authenticated user is a member of.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserGroup"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const ownedGroups = await UserGroups.findBy({
|
||||
userId: me.id
|
||||
});
|
||||
const joinings = await UserGroupJoinings.findBy({
|
||||
userId: me.id,
|
||||
...ownedGroups.length > 0 ? {
|
||||
userGroupId: Not(In(ownedGroups.map((x)=>x.id)))
|
||||
} : {}
|
||||
});
|
||||
return await Promise.all(joinings.map((x)=>UserGroups.pack(x.userGroupId)));
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import { UserGroups, UserGroupJoinings } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups",
|
||||
"users"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:user-groups",
|
||||
description: "Leave a group. The owner of a group can not leave. They must transfer ownership or delete the group instead.",
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: "No such group.",
|
||||
code: "NO_SUCH_GROUP",
|
||||
id: "62780270-1f67-5dc0-daca-3eb510612e31"
|
||||
},
|
||||
youAreOwner: {
|
||||
message: "Your are the owner.",
|
||||
code: "YOU_ARE_OWNER",
|
||||
id: "b6d6e0c2-ef8a-9bb8-653d-79f4a3107c69"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
groupId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"groupId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
// Fetch the group
|
||||
const userGroup = await UserGroups.findOneBy({
|
||||
id: ps.groupId
|
||||
});
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
if (me.id === userGroup.userId) {
|
||||
throw new ApiError(meta.errors.youAreOwner);
|
||||
}
|
||||
await UserGroupJoinings.delete({
|
||||
userGroupId: userGroup.id,
|
||||
userId: me.id
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { UserGroups } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups",
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:user-groups",
|
||||
description: "List the groups that the authenticated user is the owner of.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserGroup"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const userGroups = await UserGroups.findBy({
|
||||
userId: me.id
|
||||
});
|
||||
return await Promise.all(userGroups.map((x)=>UserGroups.pack(x)));
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { UserGroups, UserGroupJoinings } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { getUser } from "../../../common/getters.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups",
|
||||
"users"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:user-groups",
|
||||
description: "Removes a specified user from a group. The owner can not be removed.",
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: "No such group.",
|
||||
code: "NO_SUCH_GROUP",
|
||||
id: "4662487c-05b1-4b78-86e5-fd46998aba74"
|
||||
},
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "0b5cc374-3681-41da-861e-8bc1146f7a55"
|
||||
},
|
||||
isOwner: {
|
||||
message: "The user is the owner.",
|
||||
code: "IS_OWNER",
|
||||
id: "1546eed5-4414-4dea-81c1-b0aec4f6d2af"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
groupId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"groupId",
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
// Fetch the group
|
||||
const userGroup = await UserGroups.findOneBy({
|
||||
id: ps.groupId,
|
||||
userId: me.id
|
||||
});
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
// Fetch the user
|
||||
const user = await getUser(ps.userId).catch((e)=>{
|
||||
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
if (user.id === userGroup.userId) {
|
||||
throw new ApiError(meta.errors.isOwner);
|
||||
}
|
||||
// Pull the user
|
||||
await UserGroupJoinings.delete({
|
||||
userGroupId: userGroup.id,
|
||||
userId: user.id
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { UserGroups } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups"
|
||||
],
|
||||
requireCredential: false,
|
||||
requireCredentialPrivateMode: true,
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserGroup"
|
||||
},
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: "No such group.",
|
||||
code: "NO_SUCH_GROUP",
|
||||
id: "e2eb1dcc-d778-4a5f-b3c1-f7a0c7c3f9ad"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
username: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 64
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"username"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps)=>{
|
||||
const group = await UserGroups.findOneBy({
|
||||
username: ps.username.toLowerCase(),
|
||||
isPrivate: false
|
||||
});
|
||||
if (!group) throw new ApiError(meta.errors.noSuchGroup);
|
||||
return UserGroups.pack(group);
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { UserGroups, UserGroupJoinings } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups",
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:user-groups",
|
||||
description: "Show the properties of a group.",
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserGroup"
|
||||
},
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: "No such group.",
|
||||
code: "NO_SUCH_GROUP",
|
||||
id: "ea04751e-9b7e-487b-a509-330fb6bd6b9b"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
groupId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"groupId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
// Fetch the group
|
||||
const userGroup = await UserGroups.findOneBy({
|
||||
id: ps.groupId
|
||||
});
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
const joining = await UserGroupJoinings.findOneBy({
|
||||
userId: me.id,
|
||||
userGroupId: userGroup.id
|
||||
});
|
||||
if (joining == null && userGroup.userId !== me.id) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
return await UserGroups.pack(userGroup);
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import { UserGroups, UserGroupJoinings } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { getUser } from "../../../common/getters.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups",
|
||||
"users"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:user-groups",
|
||||
description: "Transfer ownership of a group from the authenticated user to another user.",
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserGroup"
|
||||
},
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: "No such group.",
|
||||
code: "NO_SUCH_GROUP",
|
||||
id: "8e31d36b-2f88-4ccd-a438-e2d78a9162db"
|
||||
},
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "711f7ebb-bbb9-4dfa-b540-b27809fed5e9"
|
||||
},
|
||||
noSuchGroupMember: {
|
||||
message: "No such group member.",
|
||||
code: "NO_SUCH_GROUP_MEMBER",
|
||||
id: "d31bebee-196d-42c2-9a3e-9474d4be6cc4"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
groupId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"groupId",
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
// Fetch the group
|
||||
const userGroup = await UserGroups.findOneBy({
|
||||
id: ps.groupId,
|
||||
userId: me.id
|
||||
});
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
// Fetch the user
|
||||
const user = await getUser(ps.userId).catch((e)=>{
|
||||
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
const joining = await UserGroupJoinings.findOneBy({
|
||||
userGroupId: userGroup.id,
|
||||
userId: user.id
|
||||
});
|
||||
if (joining == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroupMember);
|
||||
}
|
||||
await UserGroups.update(userGroup.id, {
|
||||
userId: ps.userId
|
||||
});
|
||||
return await UserGroups.pack(userGroup.id);
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
import { DriveFiles, UserGroups } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"groups"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:user-groups",
|
||||
description: "Update the properties of a group.",
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserGroup"
|
||||
},
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: "No such group.",
|
||||
code: "NO_SUCH_GROUP",
|
||||
id: "9081cda3-7a9e-4fac-a6ce-908d70f282f6"
|
||||
},
|
||||
noSuchFile: {
|
||||
message: "No such file.",
|
||||
code: "NO_SUCH_FILE",
|
||||
id: "45861e5e-75d4-4353-b8a9-a72fd2b4c62f"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
groupId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 100
|
||||
},
|
||||
username: {
|
||||
type: "string",
|
||||
pattern: "^[a-zA-Z0-9_]{1,64}$",
|
||||
nullable: true
|
||||
},
|
||||
allowCalls: {
|
||||
type: "boolean"
|
||||
},
|
||||
iconFileId: {
|
||||
type: "string",
|
||||
format: "misskey:id",
|
||||
nullable: true
|
||||
},
|
||||
symbolFileId: {
|
||||
type: "string",
|
||||
format: "misskey:id",
|
||||
nullable: true
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"groupId",
|
||||
"name"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
// Fetch the group
|
||||
const userGroup = await UserGroups.findOneBy({
|
||||
id: ps.groupId,
|
||||
userId: me.id
|
||||
});
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
const updates = {
|
||||
name: ps.name
|
||||
};
|
||||
if (ps.username !== undefined) updates.username = ps.username?.toLowerCase() ?? null;
|
||||
if (typeof ps.allowCalls === "boolean") updates.allowCalls = ps.allowCalls;
|
||||
if (ps.iconFileId !== undefined) {
|
||||
if (ps.iconFileId) {
|
||||
const file = await DriveFiles.findOneBy({
|
||||
id: ps.iconFileId,
|
||||
userId: me.id
|
||||
});
|
||||
if (!file) throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
updates.iconFileId = ps.iconFileId;
|
||||
}
|
||||
if (ps.symbolFileId !== undefined) {
|
||||
if (ps.symbolFileId) {
|
||||
const file = await DriveFiles.findOneBy({
|
||||
id: ps.symbolFileId,
|
||||
userId: me.id
|
||||
});
|
||||
if (!file) throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
updates.symbolFileId = ps.symbolFileId;
|
||||
}
|
||||
await UserGroups.update(userGroup.id, updates);
|
||||
return await UserGroups.pack(userGroup.id);
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { UserLists } from "../../../../../models/index.js";
|
||||
import { genId } from "../../../../../misc/gen-id.js";
|
||||
import define from "../../../define.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"lists"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
description: "Create a new list of users.",
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserList"
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 100
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"name"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const userList = await UserLists.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
name: ps.name
|
||||
}).then((x)=>UserLists.findOneByOrFail(x.identifiers[0]));
|
||||
return await UserLists.pack(userList);
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { UserLists } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"lists"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
description: "Delete all lists of users.",
|
||||
errors: {
|
||||
noSuchList: {
|
||||
message: "No such list.",
|
||||
code: "NO_SUCH_LIST",
|
||||
id: "78436795-db79-42f5-b1e2-55ea2cf19166"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object"
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
while(await UserLists.findOneBy({
|
||||
userId: user.id
|
||||
}) != null){
|
||||
const userList = await UserLists.findOneBy({
|
||||
userId: user.id
|
||||
});
|
||||
if (userList == null) {
|
||||
throw new ApiError(meta.errors.noSuchList);
|
||||
}
|
||||
await UserLists.delete(userList.id);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { UserLists } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"lists"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
description: "Delete an existing list of users.",
|
||||
errors: {
|
||||
noSuchList: {
|
||||
message: "No such list.",
|
||||
code: "NO_SUCH_LIST",
|
||||
id: "78436795-db79-42f5-b1e2-55ea2cf19166"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
listId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"listId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const userList = await UserLists.findOneBy({
|
||||
id: ps.listId,
|
||||
userId: user.id
|
||||
});
|
||||
if (userList == null) {
|
||||
throw new ApiError(meta.errors.noSuchList);
|
||||
}
|
||||
await UserLists.delete(userList.id);
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { UserLists } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"lists",
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:account",
|
||||
description: "Show all lists that the authenticated user has created.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserList"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const userLists = await UserLists.findBy({
|
||||
userId: me.id
|
||||
});
|
||||
return await Promise.all(userLists.map((x)=>UserLists.pack(x)));
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { UserLists } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { getUser } from "../../../common/getters.js";
|
||||
import { pullUserFromUserList } from "../../../../../services/user-list/pull.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"lists",
|
||||
"users"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
description: "Remove a user from a list.",
|
||||
errors: {
|
||||
noSuchList: {
|
||||
message: "No such list.",
|
||||
code: "NO_SUCH_LIST",
|
||||
id: "7f44670e-ab16-43b8-b4c1-ccd2ee89cc02"
|
||||
},
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "588e7f72-c744-4a61-b180-d354e912bda2"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
listId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"listId",
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
// Fetch the list
|
||||
const userList = await UserLists.findOneBy({
|
||||
id: ps.listId,
|
||||
userId: me.id
|
||||
});
|
||||
if (userList == null) {
|
||||
throw new ApiError(meta.errors.noSuchList);
|
||||
}
|
||||
// Fetch the user
|
||||
const user = await getUser(ps.userId).catch((e)=>{
|
||||
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
// Pull the user
|
||||
await pullUserFromUserList(user, userList);
|
||||
});
|
||||
@@ -0,0 +1,105 @@
|
||||
import { pushUserToUserList } from "../../../../../services/user-list/push.js";
|
||||
import { UserLists, UserListJoinings, Blockings, Followings } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { getUser } from "../../../common/getters.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"lists",
|
||||
"users"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
description: "Add a user to an existing list.",
|
||||
errors: {
|
||||
noSuchList: {
|
||||
message: "No such list.",
|
||||
code: "NO_SUCH_LIST",
|
||||
id: "2214501d-ac96-4049-b717-91e42272a711"
|
||||
},
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "a89abd3d-f0bc-4cce-beb1-2f446f4f1e6a"
|
||||
},
|
||||
alreadyAdded: {
|
||||
message: "That user has already been added to that list.",
|
||||
code: "ALREADY_ADDED",
|
||||
id: "1de7c884-1595-49e9-857e-61f12f4d4fc5"
|
||||
},
|
||||
youHaveBeenBlocked: {
|
||||
message: "You cannot push this user because you have been blocked by this user.",
|
||||
code: "YOU_HAVE_BEEN_BLOCKED",
|
||||
id: "990232c5-3f9d-4d83-9f3f-ef27b6332a4b"
|
||||
},
|
||||
notFollowing: {
|
||||
message: "You cannot push this user because you are not following this user.",
|
||||
code: "NOT_FOLLOWING",
|
||||
id: "0a2e4d73-fe61-41fb-822c-d365ec81ba2a"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
listId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"listId",
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
// Fetch the list
|
||||
const userList = await UserLists.findOneBy({
|
||||
id: ps.listId,
|
||||
userId: me.id
|
||||
});
|
||||
if (!userList) {
|
||||
throw new ApiError(meta.errors.noSuchList);
|
||||
}
|
||||
// Fetch the user
|
||||
const user = await getUser(ps.userId).catch((e)=>{
|
||||
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
// Check blocking and following status
|
||||
if (user.id !== me.id) {
|
||||
const isBlocked = await Blockings.exist({
|
||||
where: {
|
||||
blockerId: user.id,
|
||||
blockeeId: me.id
|
||||
}
|
||||
});
|
||||
const isFollowed = await Followings.exist({
|
||||
where: {
|
||||
followerId: me.id,
|
||||
followeeId: user.id
|
||||
}
|
||||
});
|
||||
if (isBlocked) {
|
||||
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||
}
|
||||
if (!isFollowed) {
|
||||
throw new ApiError(meta.errors.notFollowing);
|
||||
}
|
||||
}
|
||||
const exist = await UserListJoinings.exist({
|
||||
where: {
|
||||
userListId: ps.listId,
|
||||
userId: user.id
|
||||
}
|
||||
});
|
||||
if (exist) {
|
||||
throw new ApiError(meta.errors.alreadyAdded);
|
||||
}
|
||||
// Push the user
|
||||
await pushUserToUserList(user, userList);
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { UserLists } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"lists",
|
||||
"account"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:account",
|
||||
description: "Show the properties of a list.",
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserList"
|
||||
},
|
||||
errors: {
|
||||
noSuchList: {
|
||||
message: "No such list.",
|
||||
code: "NO_SUCH_LIST",
|
||||
id: "7bc05c21-1d7a-41ae-88f1-66820f4dc686"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
listId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"listId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
// Fetch the list
|
||||
const userList = await UserLists.findOneBy({
|
||||
id: ps.listId,
|
||||
userId: me.id
|
||||
});
|
||||
if (!userList) {
|
||||
throw new ApiError(meta.errors.noSuchList);
|
||||
}
|
||||
return await UserLists.pack(userList);
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { UserListJoinings, UserLists } from "../../../../../models/index.js";
|
||||
import define from "../../../define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { publishUserEvent } from "../../../../../services/stream.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"lists"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "write:account",
|
||||
description: "Update the properties of a list.",
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserList"
|
||||
},
|
||||
errors: {
|
||||
noSuchList: {
|
||||
message: "No such list.",
|
||||
code: "NO_SUCH_LIST",
|
||||
id: "796666fe-3dff-4d39-becb-8a5932c1d5b7"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
listId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 100
|
||||
},
|
||||
hideFromHomeTl: {
|
||||
type: "boolean",
|
||||
nullable: true
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"listId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
// Fetch the list
|
||||
const userList = await UserLists.findOneBy({
|
||||
id: ps.listId,
|
||||
userId: user.id
|
||||
});
|
||||
if (userList == null) {
|
||||
throw new ApiError(meta.errors.noSuchList);
|
||||
}
|
||||
const partial = {
|
||||
name: ps.name ?? undefined,
|
||||
hideFromHomeTl: ps.hideFromHomeTl ?? undefined
|
||||
};
|
||||
if (Object.keys(partial).length > 0) await UserLists.update(userList.id, partial);
|
||||
if (ps.hideFromHomeTl != null) {
|
||||
UserListJoinings.findBy({
|
||||
userListId: ps.listId
|
||||
}).then((members)=>{
|
||||
for (const member of members){
|
||||
publishUserEvent(userList.userId, ps.hideFromHomeTl ? "userHidden" : "userUnhidden", member.userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
return await UserLists.pack(userList.id);
|
||||
});
|
||||
@@ -0,0 +1,140 @@
|
||||
import { Brackets } from "typeorm";
|
||||
import { Notes } from "../../../../models/index.js";
|
||||
import define from "../../define.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { getUser } from "../../common/getters.js";
|
||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||
import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
|
||||
import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
|
||||
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users",
|
||||
"notes"
|
||||
],
|
||||
requireCredentialPrivateMode: true,
|
||||
description: "Show all notes that this user created.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "Note"
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "27e494ba-2ac2-48e8-893b-10d4d8c2387b"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
includeReplies: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
},
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
},
|
||||
sinceId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
untilId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
sinceDate: {
|
||||
type: "integer"
|
||||
},
|
||||
untilDate: {
|
||||
type: "integer"
|
||||
},
|
||||
includeMyRenotes: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
},
|
||||
withFiles: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
},
|
||||
fileType: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
excludeNsfw: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
// Lookup user
|
||||
const user = await getUser(ps.userId).catch((e)=>{
|
||||
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
//#region Construct query
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate).andWhere("note.userId = :userId", {
|
||||
userId: user.id
|
||||
}).innerJoinAndSelect("note.user", "user").leftJoinAndSelect("note.reply", "reply").leftJoinAndSelect("note.renote", "renote").leftJoinAndSelect("reply.user", "replyUser").leftJoinAndSelect("renote.user", "renoteUser");
|
||||
generateVisibilityQuery(query, me);
|
||||
if (me) {
|
||||
generateMutedUserQuery(query, me, user);
|
||||
generateBlockedUserQuery(query, me);
|
||||
}
|
||||
if (ps.withFiles) {
|
||||
query.andWhere("note.fileIds != '{}'");
|
||||
}
|
||||
if (ps.fileType != null) {
|
||||
query.andWhere("note.fileIds != '{}'");
|
||||
query.andWhere(new Brackets((qb)=>{
|
||||
for (const type of ps.fileType){
|
||||
const i = ps.fileType.indexOf(type);
|
||||
qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, {
|
||||
[`type${i}`]: type
|
||||
});
|
||||
}
|
||||
}));
|
||||
if (ps.excludeNsfw) {
|
||||
query.andWhere("note.cw IS NULL");
|
||||
query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)');
|
||||
}
|
||||
}
|
||||
if (!ps.includeReplies) {
|
||||
query.andWhere("note.replyId IS NULL");
|
||||
}
|
||||
if (ps.includeMyRenotes === false) {
|
||||
query.andWhere(new Brackets((qb)=>{
|
||||
qb.orWhere("note.userId != :userId", {
|
||||
userId: user.id
|
||||
});
|
||||
qb.orWhere("note.renoteId IS NULL");
|
||||
qb.orWhere("note.text IS NOT NULL");
|
||||
qb.orWhere("note.fileIds != '{}'");
|
||||
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
|
||||
}));
|
||||
}
|
||||
//#endregion
|
||||
const timeline = await query.take(ps.limit).getMany();
|
||||
return await Notes.packMany(timeline, me);
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Pages } from "../../../../models/index.js";
|
||||
import define from "../../define.js";
|
||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users",
|
||||
"pages"
|
||||
],
|
||||
requireCredentialPrivateMode: true,
|
||||
description: "Show all pages this user created.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "Page"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
},
|
||||
sinceId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
untilId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, user)=>{
|
||||
const query = makePaginationQuery(Pages.createQueryBuilder("page"), ps.sinceId, ps.untilId).andWhere("page.userId = :userId", {
|
||||
userId: ps.userId
|
||||
}).andWhere("page.visibility = 'public'").andWhere("page.isPublic = true");
|
||||
const pages = await query.take(ps.limit).getMany();
|
||||
return await Pages.packMany(pages);
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
import { NoteReactions, UserProfiles } from "../../../../models/index.js";
|
||||
import define from "../../define.js";
|
||||
import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
||||
import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users",
|
||||
"reactions"
|
||||
],
|
||||
requireCredential: false,
|
||||
requireCredentialPrivateMode: true,
|
||||
description: "Show all reactions this user made.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "NoteReaction"
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
reactionsNotPublic: {
|
||||
message: "Reactions of the user is not public.",
|
||||
code: "REACTIONS_NOT_PUBLIC",
|
||||
id: "673a7dd2-6924-1093-e0c0-e68456ceae5c"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
},
|
||||
sinceId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
untilId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
sinceDate: {
|
||||
type: "integer"
|
||||
},
|
||||
untilDate: {
|
||||
type: "integer"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const profile = await UserProfiles.findOneByOrFail({
|
||||
userId: ps.userId
|
||||
});
|
||||
if (me.id !== ps.userId && !profile.publicReactions) {
|
||||
throw new ApiError(meta.errors.reactionsNotPublic);
|
||||
}
|
||||
const query = makePaginationQuery(NoteReactions.createQueryBuilder("reaction"), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate).andWhere("reaction.userId = :userId", {
|
||||
userId: ps.userId
|
||||
}).leftJoinAndSelect("reaction.note", "note");
|
||||
generateVisibilityQuery(query, me);
|
||||
const reactions = await query.take(ps.limit).getMany();
|
||||
return await NoteReactions.packMany(reactions, me, {
|
||||
withNote: true
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Users, Followings } from "../../../../models/index.js";
|
||||
import define from "../../define.js";
|
||||
import { generateMutedUserQueryForUsers } from "../../common/generate-muted-user-query.js";
|
||||
import { generateBlockedUserQuery, generateBlockQueryForUsers } from "../../common/generate-block-query.js";
|
||||
import { DAY } from "../../../../const.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users"
|
||||
],
|
||||
requireCredential: true,
|
||||
kind: "read:account",
|
||||
description: "Show users that the authenticated user might be interested to follow.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "UserDetailed"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
},
|
||||
offset: {
|
||||
type: "integer",
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
required: []
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const query = Users.createQueryBuilder("user").where("user.isLocked = FALSE").andWhere("user.isExplorable = TRUE").andWhere("user.host IS NULL").andWhere("user.updatedAt >= :date", {
|
||||
date: new Date(Date.now() - 7 * DAY)
|
||||
}).andWhere("user.id != :meId", {
|
||||
meId: me.id
|
||||
}).orderBy("user.followersCount", "DESC");
|
||||
generateMutedUserQueryForUsers(query, me);
|
||||
generateBlockQueryForUsers(query, me);
|
||||
generateBlockedUserQuery(query, me);
|
||||
const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :followerId", {
|
||||
followerId: me.id
|
||||
});
|
||||
query.andWhere(`user.id NOT IN (${followingQuery.getQuery()})`);
|
||||
query.setParameters(followingQuery.getParameters());
|
||||
const users = await query.take(ps.limit).skip(ps.offset).getMany();
|
||||
return await Users.packMany(users, me, {
|
||||
detail: true
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
import { Users } from "../../../../models/index.js";
|
||||
import define from "../../define.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users"
|
||||
],
|
||||
requireCredential: true,
|
||||
description: "Show the different kinds of relations between the authenticated user and the specified user(s).",
|
||||
res: {
|
||||
optional: false,
|
||||
nullable: false,
|
||||
oneOf: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
format: "id"
|
||||
},
|
||||
isFollowing: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
hasPendingFollowRequestFromYou: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
hasPendingFollowRequestToYou: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
isFollowed: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
isBlocking: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
isBlocked: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
isMuted: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
isRenoteMuted: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
format: "id"
|
||||
},
|
||||
isFollowing: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
hasPendingFollowRequestFromYou: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
hasPendingFollowRequestToYou: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
isFollowed: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
isBlocking: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
isBlocked: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
isMuted: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
isRenoteMuted: {
|
||||
type: "boolean",
|
||||
optional: false,
|
||||
nullable: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: {
|
||||
anyOf: [
|
||||
{
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
{
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const ids = Array.isArray(ps.userId) ? ps.userId : [
|
||||
ps.userId
|
||||
];
|
||||
const relations = await Promise.all(ids.map((id)=>Users.getRelation(me.id, id)));
|
||||
return Array.isArray(ps.userId) ? relations : relations[0];
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import * as sanitizeHtml from "sanitize-html";
|
||||
import { publishAdminStream } from "../../../../services/stream.js";
|
||||
import { AbuseUserReports, Users } from "../../../../models/index.js";
|
||||
import { genId } from "../../../../misc/gen-id.js";
|
||||
import { sendEmail } from "../../../../services/send-email.js";
|
||||
import { fetchMeta } from "../../../../misc/fetch-meta.js";
|
||||
import { getUser } from "../../common/getters.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import define from "../../define.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users"
|
||||
],
|
||||
requireCredential: true,
|
||||
description: "File a report.",
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "1acefcb5-0959-43fd-9685-b48305736cb5"
|
||||
},
|
||||
cannotReportYourself: {
|
||||
message: "Cannot report yourself.",
|
||||
code: "CANNOT_REPORT_YOURSELF",
|
||||
id: "1e13149e-b1e8-43cf-902e-c01dbfcb202f"
|
||||
},
|
||||
cannotReportAdmin: {
|
||||
message: "Cannot report the admin.",
|
||||
code: "CANNOT_REPORT_THE_ADMIN",
|
||||
id: "35e166f5-05fb-4f87-a2d5-adb42676d48f"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
},
|
||||
comment: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 2048
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userId",
|
||||
"comment"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
// Lookup user
|
||||
const user = await getUser(ps.userId).catch((e)=>{
|
||||
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff") throw new ApiError(meta.errors.noSuchUser);
|
||||
throw e;
|
||||
});
|
||||
if (user.id === me.id) {
|
||||
throw new ApiError(meta.errors.cannotReportYourself);
|
||||
}
|
||||
if (user.isAdmin) {
|
||||
throw new ApiError(meta.errors.cannotReportAdmin);
|
||||
}
|
||||
const report = await AbuseUserReports.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
targetUserId: user.id,
|
||||
targetUserHost: user.host,
|
||||
reporterId: me.id,
|
||||
reporterHost: null,
|
||||
comment: ps.comment
|
||||
}).then((x)=>AbuseUserReports.findOneByOrFail(x.identifiers[0]));
|
||||
// Publish event to moderators
|
||||
setImmediate(async ()=>{
|
||||
const moderators = await Users.find({
|
||||
where: [
|
||||
{
|
||||
isAdmin: true
|
||||
},
|
||||
{
|
||||
isModerator: true
|
||||
}
|
||||
]
|
||||
});
|
||||
for (const moderator of moderators){
|
||||
publishAdminStream(moderator.id, "newAbuseUserReport", {
|
||||
id: report.id,
|
||||
targetUserId: report.targetUserId,
|
||||
reporterId: report.reporterId,
|
||||
comment: report.comment
|
||||
});
|
||||
}
|
||||
const meta = await fetchMeta();
|
||||
if (meta.email) {
|
||||
sendEmail(meta.email, "New abuse report", sanitizeHtml(ps.comment), sanitizeHtml(ps.comment));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
import { Brackets } from "typeorm";
|
||||
import { Followings, Users } from "../../../../models/index.js";
|
||||
import define from "../../define.js";
|
||||
import { sqlLikeEscape } from "../../../../misc/sql-like-escape.js";
|
||||
import { generateMinorBadgeUserVisibilityQuery } from "../../common/generate-minor-badge-visibility-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users"
|
||||
],
|
||||
requireCredential: false,
|
||||
requireCredentialPrivateMode: true,
|
||||
description: "Search for a user by username and/or host.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "User"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
username: {
|
||||
type: "string",
|
||||
nullable: true
|
||||
},
|
||||
host: {
|
||||
type: "string",
|
||||
nullable: true
|
||||
},
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
},
|
||||
maxDaysSinceLastActive: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 1000,
|
||||
nullable: true
|
||||
},
|
||||
detail: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
}
|
||||
},
|
||||
anyOf: [
|
||||
{
|
||||
required: [
|
||||
"username"
|
||||
]
|
||||
},
|
||||
{
|
||||
required: [
|
||||
"host"
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
// TODO: avatar,bannerをJOINしたいけどエラーになる
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const activeThreshold = ps.maxDaysSinceLastActive ? new Date(Date.now() - 1000 * 60 * 60 * 24 * ps.maxDaysSinceLastActive) : null;
|
||||
if (ps.host) {
|
||||
const q = Users.createQueryBuilder("user").where("user.isSuspended = FALSE").andWhere("user.host LIKE :host", {
|
||||
host: `${sqlLikeEscape(ps.host.toLowerCase())}%`
|
||||
});
|
||||
if (ps.username) {
|
||||
q.andWhere("user.usernameLower LIKE :username", {
|
||||
username: `${sqlLikeEscape(ps.username.toLowerCase())}%`
|
||||
});
|
||||
}
|
||||
q.andWhere("user.updatedAt IS NOT NULL");
|
||||
generateMinorBadgeUserVisibilityQuery(q, me);
|
||||
q.orderBy("user.updatedAt", "DESC");
|
||||
const users = await q.take(ps.limit).getMany();
|
||||
return await Users.packMany(users, me, {
|
||||
detail: ps.detail
|
||||
});
|
||||
} else if (ps.username) {
|
||||
let users = [];
|
||||
if (me) {
|
||||
const followingQuery = Followings.createQueryBuilder("following").select("following.followeeId").where("following.followerId = :followerId", {
|
||||
followerId: me.id
|
||||
});
|
||||
const query = Users.createQueryBuilder("user").where(`user.id IN (${followingQuery.getQuery()})`).andWhere("user.isSuspended = FALSE").andWhere("user.usernameLower LIKE :username", {
|
||||
username: `${sqlLikeEscape(ps.username.toLowerCase())}%`
|
||||
});
|
||||
if (activeThreshold) {
|
||||
query.andWhere(new Brackets((qb)=>{
|
||||
qb.where("user.updatedAt IS NULL").orWhere("user.updatedAt > :activeThreshold", {
|
||||
activeThreshold: activeThreshold
|
||||
});
|
||||
}));
|
||||
}
|
||||
generateMinorBadgeUserVisibilityQuery(query, me);
|
||||
query.setParameters(followingQuery.getParameters());
|
||||
users = await query.orderBy("user.usernameLower", "ASC").take(ps.limit).getMany();
|
||||
if (users.length < ps.limit) {
|
||||
const otherQuery = await Users.createQueryBuilder("user").where(`user.id NOT IN (${followingQuery.getQuery()})`).andWhere("user.isSuspended = FALSE").andWhere("user.usernameLower LIKE :username", {
|
||||
username: `${sqlLikeEscape(ps.username.toLowerCase())}%`
|
||||
}).andWhere("user.updatedAt IS NOT NULL");
|
||||
generateMinorBadgeUserVisibilityQuery(otherQuery, me);
|
||||
otherQuery.setParameters(followingQuery.getParameters());
|
||||
const otherUsers = await otherQuery.orderBy("user.updatedAt", "DESC").take(ps.limit - users.length).getMany();
|
||||
users = users.concat(otherUsers);
|
||||
}
|
||||
} else {
|
||||
users = await Users.createQueryBuilder("user").where("user.isSuspended = FALSE").andWhere("user.usernameLower LIKE :username", {
|
||||
username: `${sqlLikeEscape(ps.username.toLowerCase())}%`
|
||||
}).andWhere("user.updatedAt IS NOT NULL").andWhere("NOT ('E' = ANY(user.\"minorBadges\"))").orderBy("user.updatedAt", "DESC").take(ps.limit - users.length).getMany();
|
||||
}
|
||||
return await Users.packMany(users, me, {
|
||||
detail: !!ps.detail
|
||||
});
|
||||
}
|
||||
return [];
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
import { Brackets } from "typeorm";
|
||||
import { UserProfiles, Users } from "../../../../models/index.js";
|
||||
import define from "../../define.js";
|
||||
import { sqlLikeEscape } from "../../../../misc/sql-like-escape.js";
|
||||
import { generateMinorBadgeUserVisibilityQuery } from "../../common/generate-minor-badge-visibility-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users"
|
||||
],
|
||||
requireCredential: false,
|
||||
requireCredentialPrivateMode: true,
|
||||
description: "Search for users.",
|
||||
res: {
|
||||
type: "array",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
items: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
ref: "User"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string"
|
||||
},
|
||||
offset: {
|
||||
type: "integer",
|
||||
default: 0
|
||||
},
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
},
|
||||
origin: {
|
||||
type: "string",
|
||||
enum: [
|
||||
"local",
|
||||
"remote",
|
||||
"combined"
|
||||
],
|
||||
default: "combined"
|
||||
},
|
||||
detail: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"query"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const activeThreshold = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30); // 30日
|
||||
const isUsername = ps.query.startsWith("@");
|
||||
let users = [];
|
||||
if (isUsername) {
|
||||
const usernameQuery = Users.createQueryBuilder("user").where("user.usernameLower LIKE :username", {
|
||||
username: `${sqlLikeEscape(ps.query.replace("@", "").toLowerCase())}%`
|
||||
}).andWhere(new Brackets((qb)=>{
|
||||
qb.where("user.updatedAt IS NULL").orWhere("user.updatedAt > :activeThreshold", {
|
||||
activeThreshold: activeThreshold
|
||||
});
|
||||
})).andWhere("user.isSuspended = FALSE");
|
||||
if (ps.origin === "local") {
|
||||
usernameQuery.andWhere("user.host IS NULL");
|
||||
} else if (ps.origin === "remote") {
|
||||
usernameQuery.andWhere("user.host IS NOT NULL");
|
||||
}
|
||||
generateMinorBadgeUserVisibilityQuery(usernameQuery, me);
|
||||
users = await usernameQuery.orderBy("user.updatedAt", "DESC", "NULLS LAST").take(ps.limit).skip(ps.offset).getMany();
|
||||
} else {
|
||||
const nameQuery = Users.createQueryBuilder("user").where(new Brackets((qb)=>{
|
||||
qb.where("user.name ILIKE :query", {
|
||||
query: `%${sqlLikeEscape(ps.query)}%`
|
||||
});
|
||||
// Also search username if it qualifies as username
|
||||
if (Users.validateLocalUsername(ps.query)) {
|
||||
qb.orWhere("user.usernameLower LIKE :username", {
|
||||
username: `%${sqlLikeEscape(ps.query.toLowerCase())}%`
|
||||
});
|
||||
}
|
||||
})).andWhere(new Brackets((qb)=>{
|
||||
qb.where("user.updatedAt IS NULL").orWhere("user.updatedAt > :activeThreshold", {
|
||||
activeThreshold: activeThreshold
|
||||
});
|
||||
})).andWhere("user.isSuspended = FALSE");
|
||||
if (ps.origin === "local") {
|
||||
nameQuery.andWhere("user.host IS NULL");
|
||||
} else if (ps.origin === "remote") {
|
||||
nameQuery.andWhere("user.host IS NOT NULL");
|
||||
}
|
||||
generateMinorBadgeUserVisibilityQuery(nameQuery, me);
|
||||
users = await nameQuery.orderBy("user.updatedAt", "DESC", "NULLS LAST").take(ps.limit).skip(ps.offset).getMany();
|
||||
if (users.length < ps.limit) {
|
||||
const profQuery = UserProfiles.createQueryBuilder("prof").select("prof.userId").where("prof.description ILIKE :query", {
|
||||
query: `%${sqlLikeEscape(ps.query)}%`
|
||||
});
|
||||
if (ps.origin === "local") {
|
||||
profQuery.andWhere("prof.userHost IS NULL");
|
||||
} else if (ps.origin === "remote") {
|
||||
profQuery.andWhere("prof.userHost IS NOT NULL");
|
||||
}
|
||||
const query = Users.createQueryBuilder("user").where(`user.id IN (${profQuery.getQuery()})`).andWhere(new Brackets((qb)=>{
|
||||
qb.where("user.updatedAt IS NULL").orWhere("user.updatedAt > :activeThreshold", {
|
||||
activeThreshold: activeThreshold
|
||||
});
|
||||
})).andWhere("user.isSuspended = FALSE").setParameters(profQuery.getParameters());
|
||||
generateMinorBadgeUserVisibilityQuery(query, me);
|
||||
users = users.concat(await query.orderBy("user.updatedAt", "DESC", "NULLS LAST").take(ps.limit).skip(ps.offset).getMany());
|
||||
}
|
||||
}
|
||||
return await Users.packMany(users, me, {
|
||||
detail: ps.detail
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,154 @@
|
||||
import { In, IsNull } from "typeorm";
|
||||
import { resolveUser } from "../../../../remote/resolve-user.js";
|
||||
import { Users } from "../../../../models/index.js";
|
||||
import define from "../../define.js";
|
||||
import { apiLogger } from "../../logger.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
import { fetchMeta } from "../../../../misc/fetch-meta.js";
|
||||
import { shouldHideEUsersFor } from "../../common/generate-minor-badge-visibility-query.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users"
|
||||
],
|
||||
// TODO: determine if should allow this in private mode or to create a new endpoint just for 2fa
|
||||
requireCredential: false,
|
||||
requireCredentialPrivateMode: false,
|
||||
description: "Show the properties of a user.",
|
||||
res: {
|
||||
optional: false,
|
||||
nullable: false,
|
||||
oneOf: [
|
||||
{
|
||||
type: "object",
|
||||
ref: "UserDetailed"
|
||||
},
|
||||
{
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
ref: "UserDetailed"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
errors: {
|
||||
failedToResolveRemoteUser: {
|
||||
message: "Failed to resolve remote user.",
|
||||
code: "FAILED_TO_RESOLVE_REMOTE_USER",
|
||||
id: "ef7b9be4-9cba-4e6f-ab41-90ed171c7d3c",
|
||||
kind: "server"
|
||||
},
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "4362f8dc-731f-4ad8-a694-be5a88922a24"
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
userId: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userId"
|
||||
]
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
userIds: {
|
||||
type: "array",
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userIds"
|
||||
]
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
username: {
|
||||
type: "string"
|
||||
},
|
||||
host: {
|
||||
type: "string",
|
||||
nullable: true,
|
||||
description: "The local host is represented with `null`."
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"username"
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
let user;
|
||||
const isAdminOrModerator = me && (me.isAdmin || me.isModerator);
|
||||
if (ps.userIds) {
|
||||
if (ps.userIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const isUrl = ps.userIds[0].startsWith("http");
|
||||
let users;
|
||||
if (isUrl) {
|
||||
users = await Users.findBy(isAdminOrModerator ? {
|
||||
uri: In(ps.userIds)
|
||||
} : {
|
||||
uri: In(ps.userIds),
|
||||
isSuspended: false
|
||||
});
|
||||
} else {
|
||||
users = await Users.findBy(isAdminOrModerator ? {
|
||||
id: In(ps.userIds)
|
||||
} : {
|
||||
id: In(ps.userIds),
|
||||
isSuspended: false
|
||||
});
|
||||
}
|
||||
// リクエストされた通りに並べ替え
|
||||
const _users = [];
|
||||
for (const id of ps.userIds){
|
||||
const res = users.find((x)=>isUrl ? x.uri === id : x.id === id);
|
||||
if (res && !(shouldHideEUsersFor(me) && res.id !== me.id && (res.minorBadges ?? []).includes("E"))) _users.push(res);
|
||||
}
|
||||
return await Promise.all(_users.map((u)=>Users.pack(u, me, {
|
||||
detail: true
|
||||
})));
|
||||
} else {
|
||||
// Lookup user
|
||||
if (typeof ps.host === "string" && typeof ps.username === "string") {
|
||||
user = await resolveUser(ps.username, ps.host).catch((e)=>{
|
||||
apiLogger.warn(`failed to resolve remote user: ${e}`);
|
||||
throw new ApiError(meta.errors.failedToResolveRemoteUser);
|
||||
});
|
||||
} else {
|
||||
const q = ps.userId != null ? ps.userId.startsWith("http") ? {
|
||||
uri: ps.userId
|
||||
} : {
|
||||
id: ps.userId
|
||||
} : {
|
||||
usernameLower: ps.username.toLowerCase(),
|
||||
host: IsNull()
|
||||
};
|
||||
user = await Users.findOneBy(q);
|
||||
}
|
||||
if (user == null || !isAdminOrModerator && user.isSuspended || shouldHideEUsersFor(me) && user.id !== me.id && (user.minorBadges ?? []).includes("E")) {
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
}
|
||||
// apiLogger.debug(`packed (detailed): ${JSON.stringify(await Users.pack(user, me, {detail: true}))}`);
|
||||
// apiLogger.debug(`packed (private): ${JSON.stringify(await Users.pack(user, me, {detail: true, isPrivateMode: true}))}`);
|
||||
const serverMeta = await fetchMeta();
|
||||
return await Users.pack(user, me, {
|
||||
detail: true,
|
||||
isPrivateMode: me !== null ? false : serverMeta.privateMode
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,204 @@
|
||||
import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from "../../../../models/index.js";
|
||||
import { awaitAll } from "../../../../prelude/await-all.js";
|
||||
import define from "../../define.js";
|
||||
import { ApiError } from "../../error.js";
|
||||
export const meta = {
|
||||
tags: [
|
||||
"users"
|
||||
],
|
||||
requireCredential: false,
|
||||
requireCredentialPrivateMode: true,
|
||||
description: "Show statistics about a user.",
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: "No such user.",
|
||||
code: "NO_SUCH_USER",
|
||||
id: "9e638e45-3b25-4ef7-8f95-07e8498f1819"
|
||||
}
|
||||
},
|
||||
res: {
|
||||
type: "object",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
properties: {
|
||||
notesCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
repliesCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
renotesCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
repliedCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
renotedCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
pollVotesCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
pollVotedCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
localFollowingCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
remoteFollowingCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
localFollowersCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
remoteFollowersCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
followingCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
followersCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
sentReactionsCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
receivedReactionsCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
noteFavoritesCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
pageLikesCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
pageLikedCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
driveFilesCount: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false
|
||||
},
|
||||
driveUsage: {
|
||||
type: "integer",
|
||||
optional: false,
|
||||
nullable: false,
|
||||
description: "Drive usage in bytes"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: {
|
||||
type: "string",
|
||||
format: "misskey:id"
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"userId"
|
||||
]
|
||||
};
|
||||
export default define(meta, paramDef, async (ps, me)=>{
|
||||
const user = await Users.findOneBy({
|
||||
id: ps.userId
|
||||
});
|
||||
if (user == null) {
|
||||
throw new ApiError(meta.errors.noSuchUser);
|
||||
}
|
||||
const result = await awaitAll({
|
||||
notesCount: Notes.createQueryBuilder("note").where("note.userId = :userId", {
|
||||
userId: user.id
|
||||
}).getCount(),
|
||||
repliesCount: Notes.createQueryBuilder("note").where("note.userId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("note.replyId IS NOT NULL").getCount(),
|
||||
renotesCount: Notes.createQueryBuilder("note").where("note.userId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("note.renoteId IS NOT NULL").getCount(),
|
||||
repliedCount: Notes.createQueryBuilder("note").where("note.replyUserId = :userId", {
|
||||
userId: user.id
|
||||
}).getCount(),
|
||||
renotedCount: Notes.createQueryBuilder("note").where("note.renoteUserId = :userId", {
|
||||
userId: user.id
|
||||
}).getCount(),
|
||||
pollVotesCount: PollVotes.createQueryBuilder("vote").where("vote.userId = :userId", {
|
||||
userId: user.id
|
||||
}).getCount(),
|
||||
pollVotedCount: PollVotes.createQueryBuilder("vote").innerJoin("vote.note", "note").where("note.userId = :userId", {
|
||||
userId: user.id
|
||||
}).getCount(),
|
||||
localFollowingCount: Followings.createQueryBuilder("following").where("following.followerId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("following.followeeHost IS NULL").getCount(),
|
||||
remoteFollowingCount: Followings.createQueryBuilder("following").where("following.followerId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("following.followeeHost IS NOT NULL").getCount(),
|
||||
localFollowersCount: Followings.createQueryBuilder("following").where("following.followeeId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("following.followerHost IS NULL").getCount(),
|
||||
remoteFollowersCount: Followings.createQueryBuilder("following").where("following.followeeId = :userId", {
|
||||
userId: user.id
|
||||
}).andWhere("following.followerHost IS NOT NULL").getCount(),
|
||||
sentReactionsCount: NoteReactions.createQueryBuilder("reaction").where("reaction.userId = :userId", {
|
||||
userId: user.id
|
||||
}).getCount(),
|
||||
receivedReactionsCount: NoteReactions.createQueryBuilder("reaction").innerJoin("reaction.note", "note").where("note.userId = :userId", {
|
||||
userId: user.id
|
||||
}).getCount(),
|
||||
noteFavoritesCount: NoteFavorites.createQueryBuilder("favorite").where("favorite.userId = :userId", {
|
||||
userId: user.id
|
||||
}).getCount(),
|
||||
pageLikesCount: PageLikes.createQueryBuilder("like").where("like.userId = :userId", {
|
||||
userId: user.id
|
||||
}).getCount(),
|
||||
pageLikedCount: PageLikes.createQueryBuilder("like").innerJoin("like.page", "page").where("page.userId = :userId", {
|
||||
userId: user.id
|
||||
}).getCount(),
|
||||
driveFilesCount: DriveFiles.createQueryBuilder("file").where("file.userId = :userId", {
|
||||
userId: user.id
|
||||
}).getCount(),
|
||||
driveUsage: DriveFiles.calcDriveUsageOf(user)
|
||||
});
|
||||
result.followingCount = result.localFollowingCount + result.remoteFollowingCount;
|
||||
result.followersCount = result.localFollowersCount + result.remoteFollowersCount;
|
||||
return result;
|
||||
});
|
||||
Reference in New Issue
Block a user