Fixed 267U.pre2

This commit is contained in:
2026-07-26 18:25:37 +09:00
parent 50bfaeafdf
commit 317d00a284
1286 changed files with 80222 additions and 1 deletions
@@ -0,0 +1,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;
});