diff --git a/locales/en-US.yml b/locales/en-US.yml index f90dcc0e28..a44447a25f 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -2153,6 +2153,7 @@ _widgets: chooseList: "Select a list" clicker: "Clicker" birthdayFollowings: "Users who celebrate their birthday today" + birthdaySoon: "Users who will celebrate their birthday soon" _cw: hide: "Hide" show: "Show content" diff --git a/locales/index.d.ts b/locales/index.d.ts index e523dbcee5..70beffa0cf 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -8396,6 +8396,10 @@ export interface Locale extends ILocale { * 今日誕生日のユーザー */ "birthdayFollowings": string; + /** + * もうすぐ誕生日のユーザー + */ + "birthdaySoon": string; }; "_cw": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 34b11a8b01..ec4f688eb5 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2207,6 +2207,7 @@ _widgets: chooseList: "リストを選択" clicker: "クリッカー" birthdayFollowings: "今日誕生日のユーザー" + birthdaySoon: "もうすぐ誕生日のユーザー" _cw: hide: "隠す" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 09bb3e32b0..a736dcc23c 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -2131,6 +2131,7 @@ _widgets: chooseList: "리스트 선택" clicker: "클리커" birthdayFollowings: "오늘이 생일인 사용자" + birthdaySoon: "곧 생일인 사용자" _cw: hide: "숨기기" show: "더 보기" diff --git a/packages/backend/migration/1711478468155-birthday-index.js b/packages/backend/migration/1711478468155-birthday-index.js new file mode 100644 index 0000000000..7954e3cd2b --- /dev/null +++ b/packages/backend/migration/1711478468155-birthday-index.js @@ -0,0 +1,15 @@ +export class BirthdayIndex1711478468155 { + name = 'BirthdayIndex1711478468155' + + async up(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_de22cd2b445eee31ae51cdbe99"`); + await queryRunner.query(`CREATE OR REPLACE FUNCTION get_birthday_date(birthday TEXT) RETURNS SMALLINT AS $$ BEGIN RETURN CAST((SUBSTR(birthday, 6, 2) || SUBSTR(birthday, 9, 2)) AS SMALLINT); END; $$ LANGUAGE plpgsql IMMUTABLE;`); + await queryRunner.query(`CREATE INDEX "IDX_USERPROFILE_BIRTHDAY_DATE" ON "user_profile" (get_birthday_date("birthday"))`); + } + + async down(queryRunner) { + await queryRunner.query(`CREATE INDEX "IDX_de22cd2b445eee31ae51cdbe99" ON "user_profile" (substr("birthday", 6, 5))`); + await queryRunner.query(`DROP INDEX "public"."IDX_USERPROFILE_BIRTHDAY_DATE"`); + await queryRunner.query(`DROP FUNCTION IF EXISTS get_birthday_date(birthday TEXT)`); + } +} diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 225824f93e..892529f7c3 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -642,8 +642,8 @@ export class UserEntityService implements OnModuleInit { // -- 特に前提条件のない値群を取得 - const profilesMap = await this.userProfilesRepository.findBy({ userId: In(_userIds) }) - .then(profiles => new Map(profiles.map(p => [p.userId, p]))); + const profilesMap = (options?.schema !== 'UserLite') ? await this.userProfilesRepository.findBy({ userId: In(_userIds) }) + .then(profiles => new Map(profiles.map(p => [p.userId, p]))) : undefined; // -- 実行者の有無や指定スキーマの種別によって要否が異なる値群を取得 @@ -680,7 +680,7 @@ export class UserEntityService implements OnModuleInit { } } - return (await Promise.allSettled(_users.map(u => this.pack(u, me, { ...options, userProfile: profilesMap.get(u.id), userRelations: userRelations, userMemos: userMemos, pinNotes: pinNotes })))) + return (await Promise.allSettled(_users.map(u => this.pack(u, me, { ...options, userProfile: profilesMap?.get(u.id), userRelations: userRelations, userMemos: userMemos, pinNotes: pinNotes })))) .filter(result => result.status === 'fulfilled') .map(result => (result as PromiseFulfilledResult>).value); } diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 313d2cd042..7842f0e28d 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -348,6 +348,7 @@ import * as ep___users_clips from './endpoints/users/clips.js'; import * as ep___users_followers from './endpoints/users/followers.js'; import * as ep___users_following from './endpoints/users/following.js'; import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js'; +import * as ep___users_getFollowingBirthdayUsers from './endpoints/users/get-following-birthday-users.js'; import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js'; import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js'; import * as ep___users_lists_create from './endpoints/users/lists/create.js'; @@ -733,6 +734,7 @@ const $users_clips: Provider = { provide: 'ep:users/clips', useClass: ep___users const $users_followers: Provider = { provide: 'ep:users/followers', useClass: ep___users_followers.default }; const $users_following: Provider = { provide: 'ep:users/following', useClass: ep___users_following.default }; const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default }; +const $users_getFollowingBirthdayUsers: Provider = { provide: 'ep:users/get-following-birthday-users', useClass: ep___users_getFollowingBirthdayUsers.default }; const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default }; const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', useClass: ep___users_featuredNotes.default }; const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default }; @@ -1122,6 +1124,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $users_followers, $users_following, $users_gallery_posts, + $users_getFollowingBirthdayUsers, $users_getFrequentlyRepliedUsers, $users_featuredNotes, $users_lists_create, @@ -1503,6 +1506,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $users_followers, $users_following, $users_gallery_posts, + $users_getFollowingBirthdayUsers, $users_getFrequentlyRepliedUsers, $users_featuredNotes, $users_lists_create, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 4160490e62..be8c4f0803 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -348,6 +348,7 @@ import * as ep___users_clips from './endpoints/users/clips.js'; import * as ep___users_followers from './endpoints/users/followers.js'; import * as ep___users_following from './endpoints/users/following.js'; import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js'; +import * as ep___users_getFollowingBirthdayUsers from './endpoints/users/get-following-birthday-users.js'; import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js'; import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js'; import * as ep___users_lists_create from './endpoints/users/lists/create.js'; @@ -731,6 +732,7 @@ const eps = [ ['users/followers', ep___users_followers], ['users/following', ep___users_following], ['users/gallery/posts', ep___users_gallery_posts], + ['users/get-following-birthday-users', ep___users_getFollowingBirthdayUsers], ['users/get-frequently-replied-users', ep___users_getFrequentlyRepliedUsers], ['users/featured-notes', ep___users_featuredNotes], ['users/lists/create', ep___users_lists_create], diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 6b3389f0b2..084d278095 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -67,7 +67,10 @@ export const paramDef = { description: 'The local host is represented with `null`.', }, - birthday: { ...birthdaySchema, nullable: true }, + birthday: { + ...birthdaySchema, nullable: true, + description: '@deprecated use get-following-birthday-users instead.', + }, }, anyOf: [ { required: ['userId'] }, @@ -126,14 +129,15 @@ export default class extends Endpoint { // eslint- .andWhere('following.followerId = :userId', { userId: user.id }) .innerJoinAndSelect('following.followee', 'followee'); + // @deprecated use get-following-birthday-users instead. if (ps.birthday) { - try { - const birthday = ps.birthday.substring(5, 10); - const birthdayUserQuery = this.userProfilesRepository.createQueryBuilder('user_profile'); - birthdayUserQuery.select('user_profile.userId') - .where(`SUBSTR(user_profile.birthday, 6, 5) = '${birthday}'`); + query.innerJoin(this.userProfilesRepository.metadata.targetName, 'followeeProfile', 'followeeProfile.userId = following.followeeId'); - query.andWhere(`following.followeeId IN (${ birthdayUserQuery.getQuery() })`); + try { + const birthday = ps.birthday.split('-'); + birthday.shift(); // 年の部分を削除 + // なぜか get_birthday_date() = :birthday だとインデックスが効かないので、BETWEEN で対応 + query.andWhere('get_birthday_date(followeeProfile.birthday) BETWEEN :birthday AND :birthday', { birthday: parseInt(birthday.join('')) }); } catch (err) { throw new ApiError(meta.errors.birthdayInvalid); } diff --git a/packages/backend/src/server/api/endpoints/users/get-following-birthday-users.ts b/packages/backend/src/server/api/endpoints/users/get-following-birthday-users.ts new file mode 100644 index 0000000000..84357bca30 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/get-following-birthday-users.ts @@ -0,0 +1,130 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { + FollowingsRepository, + UserProfilesRepository, +} from '@/models/_.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import type { Packed } from '@/misc/json-schema.js'; + +export const meta = { + tags: ['users'], + + requireCredential: true, + kind: 'read:account', + + description: 'Find users who have a birthday on the specified range.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + birthday: { + type: 'string', format: 'date-time', + optional: false, nullable: false, + }, + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + }, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + offset: { type: 'integer', default: 0 }, + birthday: { + type: 'object', + properties: { + month: { type: 'integer', minimum: 1, maximum: 12 }, + day: { type: 'integer', minimum: 1, maximum: 31 }, + begin: { + type: 'object', + properties: { + month: { type: 'integer', minimum: 1, maximum: 12 }, + day: { type: 'integer', minimum: 1, maximum: 31 }, + }, + required: ['month', 'day'], + }, + end: { + type: 'object', + properties: { + month: { type: 'integer', minimum: 1, maximum: 12 }, + day: { type: 'integer', minimum: 1, maximum: 31 }, + }, + required: ['month', 'day'], + }, + }, + anyOf: [ + { required: ['month', 'day'] }, + { required: ['begin', 'end'] }, + ], + }, + }, + required: ['birthday'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.followingsRepository + .createQueryBuilder('following') + .andWhere('following.followerId = :userId', { userId: me.id }) + .innerJoin(this.userProfilesRepository.metadata.targetName, 'followeeProfile', 'followeeProfile.userId = following.followeeId'); + + if (Object.hasOwn(ps.birthday, 'begin') && Object.hasOwn(ps.birthday, 'end')) { + const { begin, end } = ps.birthday as { begin: { month: number; day: number }; end: { month: number; day: number }; }; + query.andWhere('get_birthday_date(followeeProfile.birthday) BETWEEN :begin AND :end', { begin: begin.month * 100 + begin.day, end: end.month * 100 + end.day }); + } else { + const { month, day } = ps.birthday as { month: number; day: number }; + // なぜか get_birthday_date() = :birthday だとインデックスが効かないので、BETWEEN で対応 + query.andWhere('get_birthday_date(followeeProfile.birthday) BETWEEN :birthday AND :birthday', { birthday: month * 100 + day }); + } + + query.select('following.followeeId', 'user_id'); + query.addSelect('get_birthday_date(followeeProfile.birthday)', 'birthday_date'); + query.orderBy('birthday_date', 'ASC'); + + const birthdayUsers = await query + .offset(ps.offset).limit(ps.limit) + .getRawMany<{ birthday_date: number; user_id: string }>(); + + const users = new Map>(( + await this.userEntityService.packMany( + birthdayUsers.map(u => u.user_id), + me, + { schema: 'UserLite' }, + ) + ).map(u => [u.id, u])); + + return birthdayUsers + .map(item => { + const birthday = new Date(); + birthday.setMonth(Math.floor(item.birthday_date / 100) - 1); + birthday.setDate(item.birthday_date % 100); + birthday.setHours(0, 0, 0, 0); + if (birthday.getTime() < Date.now()) birthday.setFullYear(new Date().getFullYear() + 1); + return { birthday: birthday.toISOString(), user: users.get(item.user_id) }; + }) + .filter(item => item.user !== undefined) + .map(item => item as { birthday: string; user: Packed<'UserLite'> }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 53e2e4b220..ff18c91877 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -27,10 +27,21 @@ export const meta = { res: { optional: false, nullable: false, oneOf: [ + { + type: 'object', + ref: 'UserLite', + }, { type: 'object', ref: 'UserDetailed', }, + { + type: 'array', + items: { + type: 'object', + ref: 'UserLite', + }, + }, { type: 'array', items: { @@ -71,6 +82,7 @@ export const paramDef = { nullable: true, description: 'The local host is represented with `null`.', }, + detailed: { type: 'boolean', default: true }, }, anyOf: [ { required: ['userId'] }, @@ -117,7 +129,7 @@ export default class extends Endpoint { // eslint- } return await this.userEntityService.packMany(_users, me, { - schema: 'UserDetailed', + schema: ps.detailed ? 'UserDetailed' : 'UserLite', }); } else { // Lookup user @@ -147,7 +159,7 @@ export default class extends Endpoint { // eslint- } return await this.userEntityService.pack(user, me, { - schema: 'UserDetailed', + schema: ps.detailed ? 'UserDetailed' : 'UserLite', }); } }); diff --git a/packages/frontend/src/components/MkAvatars.vue b/packages/frontend/src/components/MkAvatars.vue index 8236d0ddb9..fe15453138 100644 --- a/packages/frontend/src/components/MkAvatars.vue +++ b/packages/frontend/src/components/MkAvatars.vue @@ -29,6 +29,7 @@ const users = ref([]); onMounted(async () => { users.value = await misskeyApi('users/show', { userIds: props.userIds, + detailed: false, }) as unknown as Misskey.entities.UserLite[]; }); diff --git a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue index 49fd103d37..1247a02afe 100644 --- a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue +++ b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue @@ -4,43 +4,76 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 1a3ad3bdff..3289b4f7cf 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1686,6 +1686,8 @@ declare namespace entities { UsersFollowingResponse, UsersGalleryPostsRequest, UsersGalleryPostsResponse, + UsersGetFollowingBirthdayUsersRequest, + UsersGetFollowingBirthdayUsersResponse, UsersGetFrequentlyRepliedUsersRequest, UsersGetFrequentlyRepliedUsersResponse, UsersFeaturedNotesRequest, @@ -3091,6 +3093,12 @@ type UsersGalleryPostsRequest = operations['users___gallery___posts']['requestBo // @public (undocumented) type UsersGalleryPostsResponse = operations['users___gallery___posts']['responses']['200']['content']['application/json']; +// @public (undocumented) +type UsersGetFollowingBirthdayUsersRequest = operations['users___get-following-birthday-users']['requestBody']['content']['application/json']; + +// @public (undocumented) +type UsersGetFollowingBirthdayUsersResponse = operations['users___get-following-birthday-users']['responses']['200']['content']['application/json']; + // @public (undocumented) type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-replied-users']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 053474b00b..35d746190f 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -3804,6 +3804,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * Find users who have a birthday on the specified range. + * + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * Get a list of other users that the specified user frequently replies to. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index 9d9a990835..2dd65df9a4 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -506,6 +506,8 @@ import type { UsersFollowingResponse, UsersGalleryPostsRequest, UsersGalleryPostsResponse, + UsersGetFollowingBirthdayUsersRequest, + UsersGetFollowingBirthdayUsersResponse, UsersGetFrequentlyRepliedUsersRequest, UsersGetFrequentlyRepliedUsersResponse, UsersFeaturedNotesRequest, @@ -916,6 +918,7 @@ export type Endpoints = { 'users/followers': { req: UsersFollowersRequest; res: UsersFollowersResponse }; 'users/following': { req: UsersFollowingRequest; res: UsersFollowingResponse }; 'users/gallery/posts': { req: UsersGalleryPostsRequest; res: UsersGalleryPostsResponse }; + 'users/get-following-birthday-users': { req: UsersGetFollowingBirthdayUsersRequest; res: UsersGetFollowingBirthdayUsersResponse }; 'users/get-frequently-replied-users': { req: UsersGetFrequentlyRepliedUsersRequest; res: UsersGetFrequentlyRepliedUsersResponse }; 'users/featured-notes': { req: UsersFeaturedNotesRequest; res: UsersFeaturedNotesResponse }; 'users/lists/create': { req: UsersListsCreateRequest; res: UsersListsCreateResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 87c8b8def7..2c9afc650d 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -509,6 +509,8 @@ export type UsersFollowingRequest = operations['users___following']['requestBody export type UsersFollowingResponse = operations['users___following']['responses']['200']['content']['application/json']; export type UsersGalleryPostsRequest = operations['users___gallery___posts']['requestBody']['content']['application/json']; export type UsersGalleryPostsResponse = operations['users___gallery___posts']['responses']['200']['content']['application/json']; +export type UsersGetFollowingBirthdayUsersRequest = operations['users___get-following-birthday-users']['requestBody']['content']['application/json']; +export type UsersGetFollowingBirthdayUsersResponse = operations['users___get-following-birthday-users']['responses']['200']['content']['application/json']; export type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-replied-users']['requestBody']['content']['application/json']; export type UsersGetFrequentlyRepliedUsersResponse = operations['users___get-frequently-replied-users']['responses']['200']['content']['application/json']; export type UsersFeaturedNotesRequest = operations['users___featured-notes']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 94f60821eb..83250ce4ae 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -3276,6 +3276,15 @@ export type paths = { */ post: operations['users___gallery___posts']; }; + '/users/get-following-birthday-users': { + /** + * users/get-following-birthday-users + * @description Find users who have a birthday on the specified range. + * + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + post: operations['users___get-following-birthday-users']; + }; '/users/get-frequently-replied-users': { /** * users/get-frequently-replied-users @@ -25425,6 +25434,7 @@ export type operations = { username?: string; /** @description The local host is represented with `null`. */ host?: string | null; + /** @description @deprecated use get-following-birthday-users instead. */ birthday?: string | null; }; }; @@ -25528,6 +25538,78 @@ export type operations = { }; }; }; + /** + * users/get-following-birthday-users + * @description Find users who have a birthday on the specified range. + * + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + 'users___get-following-birthday-users': { + requestBody: { + content: { + 'application/json': { + /** @default 10 */ + limit?: number; + /** @default 0 */ + offset?: number; + birthday: { + month?: number; + day?: number; + begin?: { + month: number; + day: number; + }; + end?: { + month: number; + day: number; + }; + }; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + /** Format: date-time */ + birthday: string; + user: components['schemas']['UserLite']; + }[]; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; /** * users/get-frequently-replied-users * @description Get a list of other users that the specified user frequently replies to. @@ -26896,6 +26978,8 @@ export type operations = { username?: string; /** @description The local host is represented with `null`. */ host?: string | null; + /** @default true */ + detailed?: boolean; }; }; }; @@ -26903,7 +26987,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserDetailed'] | components['schemas']['UserDetailed'][]; + 'application/json': components['schemas']['UserLite'] | components['schemas']['UserDetailed'] | components['schemas']['UserLite'][] | components['schemas']['UserDetailed'][]; }; }; /** @description Client error */