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 []; });