diff --git a/CHANGELOG_KAKUREGA.md b/CHANGELOG_KAKUREGA.md index b061f5e99d8d..89a600ebdc90 100644 --- a/CHANGELOG_KAKUREGA.md +++ b/CHANGELOG_KAKUREGA.md @@ -1,3 +1,10 @@ +## 1.34.1 +Release: 2024/04/11 +Base: 2024.3.1 + +### 修正 +- 連携サービスの定期更新でプロセス数分のリクエストが発生してしまう問題を修正 + ## 1.34.0 Release: 2024/03/20 Base: 2024.3.1 diff --git a/locales/index.d.ts b/locales/index.d.ts index d8b58acd7445..6d1a56535641 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1485,7 +1485,7 @@ export interface Locale extends ILocale { */ "requestedRefresh": string; /** - * 最新の情報が反映されるまで最大5分ほどかかります。 + * 最新の情報が反映されるまで最大1時間程度かかる場合があります。反映されない場合は5~10分ほどお待ちいただいた上で、再度リクエストをお試しください。 */ "requestedRefreshDetails": string; /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1e28e681bd08..c0fc642cec25 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -367,7 +367,7 @@ connectService: "接続する" disconnectService: "切断する" requestRefresh: "更新をリクエストする" requestedRefresh: "更新をリクエストしました" -requestedRefreshDetails: "最新の情報が反映されるまで最大5分ほどかかります。" +requestedRefreshDetails: "最新の情報が反映されるまで最大1時間程度かかる場合があります。反映されない場合は5~10分ほどお待ちいただいた上で、再度リクエストをお試しください。" enableLocalTimeline: "ローカルタイムラインを有効にする" enableGlobalTimeline: "グローバルタイムラインを有効にする" disablingTimelinesInfo: "これらのタイムラインを無効化しても、利便性のため管理者およびモデレーターは引き続き利用することができます。" diff --git a/package.json b/package.json index 4785f22240c3..fa09ecde8c34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.3.1-kakurega.1.34.0", + "version": "2024.3.1-kakurega.1.34.1", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 28ef63ed2bb7..25b7b3ec7518 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -21,6 +21,8 @@ import type { MiPage } from '@/models/Page.js'; import type { MiWebhook } from '@/models/Webhook.js'; import type { MiMeta } from '@/models/Meta.js'; import { MiAvatarDecoration, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js'; +import type { PatreonMembers } from '@/core/integrations/PatreonManagementService.js'; +import type { FanboxMembers } from '@/core/integrations/FanboxManagementService.js'; import type { Packed } from '@/misc/json-schema.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; @@ -209,6 +211,8 @@ type SerializedAll = { [K in keyof T]: Serialized; }; +type MapToRecord = T extends Map ? Record : never; + export interface InternalEventTypes { userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; }; userChangeDeletedState: { id: MiUser['id']; isDeleted: MiUser['isDeleted']; }; @@ -244,6 +248,10 @@ export interface InternalEventTypes { unmute: { muterId: MiUser['id']; muteeId: MiUser['id']; }; userListMemberAdded: { userListId: MiUserList['id']; memberId: MiUser['id']; }; userListMemberRemoved: { userListId: MiUserList['id']; memberId: MiUser['id']; }; + patreonMembersUpdated: MapToRecord; + patreonMembersUpdating: never; + fanboxMembersUpdated: MapToRecord; + fanboxMembersUpdating: never; } // name/messages(spec) pairs dictionary diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index b7f792c77106..04ccf19bbe36 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -69,6 +69,19 @@ export class QueueService { repeat: { pattern: '*/5 * * * *' }, removeOnComplete: true, }); + + this.systemQueue.add('integrationDaemon', { + }, { + repeat: { every: 1000 * 60 * 60 }, + removeOnComplete: true, + }); + + // 初回実行 + this.systemQueue.add('integrationDaemon', { + }, { + delay: 1000 * 10, + removeOnComplete: true, + }) } @bindThis diff --git a/packages/backend/src/core/integrations/FanboxManagementService.ts b/packages/backend/src/core/integrations/FanboxManagementService.ts index b9790f2054dc..7dbdfe138b31 100644 --- a/packages/backend/src/core/integrations/FanboxManagementService.ts +++ b/packages/backend/src/core/integrations/FanboxManagementService.ts @@ -1,6 +1,6 @@ import { Injectable, Inject } from '@nestjs/common'; import { Not } from 'typeorm'; -import fetch from 'node-fetch'; +import Redis from 'ioredis'; import { MetaService } from '@/core/MetaService.js'; import type { UserProfilesRepository, MiUser } from '@/models/_.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; @@ -8,77 +8,95 @@ import { StatusError } from '@/misc/status-error.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { GlobalEventService, GlobalEvents } from '@/core/GlobalEventService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +type SupporterUser = { + username: string, + name: string, + avatarUrl: string, +} + type FanboxMember = { amounts: number, - user: MiUser, - isHideFromSupporterPage: boolean, + hide: boolean, + user: SupporterUser, } +export type FanboxMembers = Map; + @Injectable() export class FanboxManagementService implements OnApplicationShutdown { - private intervalId: NodeJS.Timeout; - private timeoutId: NodeJS.Timeout; + private cache: FanboxMembers = new Map(); private logger: Logger; - private cache: Record = {}; private cacheLastUpdate = 0; - private isWaitingToUpdate = false; - private isUpdating = false; constructor( + @Inject(DI.redisForSub) + private redisForSub: Redis.Redis, + @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, private metaService: MetaService, private httpRequestService: HttpRequestService, private loggerService: LoggerService, + private globalEventService: GlobalEventService, + private userEntityService: UserEntityService, ) { this.logger = this.loggerService.getLogger('fanbox-manager'); - this.start(); + this.redisForSub.on('message', this.onMessage); } @bindThis - private async start(): Promise { - this.updateCache(); + private async onMessage(_: string, data: string): Promise { + const obj = JSON.parse(data); - // 1時間おきにキャッシュを更新する - this.intervalId = setInterval(this.updateCache, 1000 * 60 * 60); + if (obj.channel === 'internal') { + const { type, body } = obj.message as GlobalEvents['internal']['payload']; + + switch (type) { + case 'fanboxMembersUpdated': { + this.cache = new Map(Object.entries(body)); + this.cacheLastUpdate = Date.now(); + break; + } + + case 'fanboxMembersUpdating': { + this.cacheLastUpdate = Date.now(); + break; + } + + default: break; + } + } } @bindThis public amountsValue(user: MiUser): number { - const target = this.cache[user.id]; + const target = this.cache.get(user.id); return target?.amounts ?? 0; } @bindThis - public getFanboxUsers() { + public get() { return this.cache; } @bindThis - public requestUpdateCache(): void { - // 最後に更新してから5分経過していなければキューに追加 - if (Date.now() - this.cacheLastUpdate < 1000 * 60 * 5 && !this.isWaitingToUpdate) { - this.isWaitingToUpdate = true; - const waitTime = this.cacheLastUpdate + (1000 * 60 * 5) - Date.now(); - this.logger.debug(`Refresh request received. next refresh is after ${waitTime}ms`); - this.timeoutId = setTimeout(this.updateCache, waitTime); - return; - } + public async update() { + // 最後に更新してから5分経過していなければ更新しない + if (Date.now() - this.cacheLastUpdate < 1000 * 60 * 5) return; - this.updateCache(); - } + // 排他的に更新したいのでイベントを発行 + this.globalEventService.publishInternalEvent('fanboxMembersUpdating'); - @bindThis - private async updateCache(): Promise { - if (this.isUpdating) return; const meta = await this.metaService.fetch(); if (!meta.enableFanboxIntegration) return; - this.isUpdating = true; + const usersList: FanboxMembers = new Map(); try { const members = await this.fetchUsers(); @@ -93,30 +111,31 @@ export class FanboxManagementService implements OnApplicationShutdown { }, }); - const usersList = {} as Record; - - for (const user of users) { - const pixivId = user.integrations.fanbox?.id; + for (const userProfile of users) { + const pixivId = userProfile.integrations.fanbox?.id; const amounts = pixivId ? members[pixivId] : null; - if (!amounts) continue; - usersList[user.userId] = { + if (!amounts || !userProfile.user) continue; + const user = userProfile.user; + + usersList.set(user.id, { amounts, - user: user.user as MiUser, - isHideFromSupporterPage: user.hideFromSupporterPage, - }; + hide: userProfile.hideFromSupporterPage, + user: { + username: user.username, + name: user.name ?? user.username, + avatarUrl: user.avatar?.url ?? this.userEntityService.getIdenticonUrl(user), + }, + }); } this.logger.info(`Found ${Object.keys(usersList).length} fanbox supporter(s)`); - this.cache = usersList; this.logger.info('Cache updated.'); } catch (err: any) { this.logger.error('Failed to update fanbox supporter cache'); this.logger.error(err); } - this.cacheLastUpdate = Date.now(); - this.isWaitingToUpdate = false; - this.isUpdating = false; + this.globalEventService.publishInternalEvent('fanboxMembersUpdated', Object.fromEntries(usersList)); } @bindThis @@ -164,7 +183,6 @@ export class FanboxManagementService implements OnApplicationShutdown { } async onApplicationShutdown(): Promise { - clearInterval(this.intervalId); - clearTimeout(this.timeoutId); + this.redisForSub.off('message', this.onMessage); } } diff --git a/packages/backend/src/core/integrations/PatreonManagementService.ts b/packages/backend/src/core/integrations/PatreonManagementService.ts index cadb8531aa64..27826d547676 100644 --- a/packages/backend/src/core/integrations/PatreonManagementService.ts +++ b/packages/backend/src/core/integrations/PatreonManagementService.ts @@ -1,5 +1,6 @@ import { Injectable, Inject } from '@nestjs/common'; import { Not } from 'typeorm'; +import Redis from 'ioredis'; import { OAuth2 } from 'oauth'; import { MetaService } from '@/core/MetaService.js'; import type { UserProfilesRepository, MiUser, RoleAssignmentsRepository } from '@/models/_.js'; @@ -8,27 +9,36 @@ import { StatusError } from '@/misc/status-error.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { GlobalEventService, GlobalEvents } from '@/core/GlobalEventService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +type SupporterUser = { + username: string, + name: string, + avatarUrl: string, +} + type PatreonMember = { amounts: number, - user: MiUser, + hide: boolean, isPatreon: boolean, - isHideFromSupporterPage: boolean, + user: SupporterUser, } +export type PatreonMembers = Map; + @Injectable() export class PatreonManagementService implements OnApplicationShutdown { - private intervalId: NodeJS.Timeout; - private timeoutId: NodeJS.Timeout; + private cache: PatreonMembers = new Map(); private logger: Logger; - private cache: Record = {}; private cacheLastUpdate = 0; - private isWaitingToUpdate = false; - private isUpdating = false; constructor( + @Inject(DI.redisForSub) + private redisForSub: Redis.Redis, + @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, @@ -38,58 +48,65 @@ export class PatreonManagementService implements OnApplicationShutdown { private metaService: MetaService, private httpRequestService: HttpRequestService, private loggerService: LoggerService, + private globalEventService: GlobalEventService, + private userEntityService: UserEntityService, ) { this.logger = this.loggerService.getLogger('patreon-manager'); - this.start(); + this.redisForSub.on('message', this.onMessage); } @bindThis - private async start(): Promise { - this.updateCache(); + private async onMessage(_: string, data: string): Promise { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message as GlobalEvents['internal']['payload']; + + switch (type) { + case 'patreonMembersUpdated': { + this.cache = new Map(Object.entries(body)); + this.cacheLastUpdate = Date.now(); + break; + } - // 1時間おきにキャッシュを更新する - this.intervalId = setInterval(this.updateCache, 1000 * 60 * 60); + case 'patreonMembersUpdating': { + this.cacheLastUpdate = Date.now(); + break; + } + + default: break; + } + } } @bindThis public amountsValue(user: MiUser): number { - const target = this.cache[user.id]; + const target = this.cache.get(user.id); if (!target) return 0; return target.isPatreon ? target.amounts : 0; } @bindThis - public getPatreonUsers(): Record { + public get() { return this.cache; } @bindThis - public requestUpdateCache(): void { - // 最後に更新してから5分経過していなければキューに追加 - if (Date.now() - this.cacheLastUpdate < 1000 * 60 * 5 && !this.isWaitingToUpdate) { - this.isWaitingToUpdate = true; - const waitTime = this.cacheLastUpdate + (1000 * 60 * 5) - Date.now(); - this.logger.debug(`Refresh request received. next refresh is after ${waitTime}ms`); - this.timeoutId = setTimeout(this.updateCache, waitTime); - return; - } + public async update() { + // 最後に更新してから5分経過していなければ更新しない + if (Date.now() - this.cacheLastUpdate < 1000 * 60 * 5) return; - this.updateCache(); - } + // 排他的に更新したいのでイベントを発行 + this.globalEventService.publishInternalEvent('patreonMembersUpdating'); - @bindThis - private async updateCache(): Promise { - if (this.isUpdating) return; const meta = await this.metaService.fetch(); - if (!meta.enableSupporterPage) return; - - this.isUpdating = true; + if (!meta.enablePatreonIntegration) return; - const usersList = {} as Record; + const usersList: PatreonMembers = new Map(); try { - const members = meta.enablePatreonIntegration ? await this.fetchUsers() : {}; + const members = await this.fetchUsers(); const users = await this.userProfilesRepository.find({ where: { integrations: Not('{}'), @@ -101,16 +118,22 @@ export class PatreonManagementService implements OnApplicationShutdown { }, }); - for (const user of users) { - const patreonId = user.integrations.patreon?.id; + for (const userProfile of users) { + const patreonId = userProfile.integrations.patreon?.id; const amounts = patreonId ? members[patreonId] : null; - if (!amounts) continue; - usersList[user.userId] = { + if (!amounts || !userProfile.user) continue; + const user = userProfile.user; + + usersList.set(user.id, { amounts, - user: user.user as MiUser, isPatreon: true, - isHideFromSupporterPage: user.hideFromSupporterPage, - }; + hide: userProfile.hideFromSupporterPage, + user: { + username: user.username, + name: user.name ?? user.username, + avatarUrl: user.avatar?.url ?? this.userEntityService.getIdenticonUrl(user), + }, + }); } this.logger.info(`Found ${Object.keys(usersList).length} patreon(s)`); @@ -137,21 +160,20 @@ export class PatreonManagementService implements OnApplicationShutdown { const user = assign.user as MiUser; const userProfile = await this.userProfilesRepository.findOneBy({ userId: user.id }); - usersList[user.id] = { - user, + usersList.set(user.id, { amounts: isNaN(roleAmounts) ? 500 : roleAmounts, isPatreon: false, - isHideFromSupporterPage: userProfile?.hideFromSupporterPage ?? false, - }; + hide: userProfile?.hideFromSupporterPage ?? false, + user: { + username: user.username, + name: user.name ?? user.username, + avatarUrl: user.avatar?.url ?? this.userEntityService.getIdenticonUrl(user), + }, + }); }); } - this.cache = usersList; - this.cacheLastUpdate = Date.now(); - this.isWaitingToUpdate = false; - this.isUpdating = false; - - this.logger.info('Cache updated.'); + this.globalEventService.publishInternalEvent('patreonMembersUpdated', Object.fromEntries(usersList)); } @bindThis @@ -257,7 +279,6 @@ export class PatreonManagementService implements OnApplicationShutdown { } async onApplicationShutdown(): Promise { - clearInterval(this.intervalId); - clearTimeout(this.timeoutId); + this.redisForSub.off('message', this.onMessage); } } diff --git a/packages/backend/src/models/json-schema/channel.ts b/packages/backend/src/models/json-schema/channel.ts index 8d462777ed4a..e44b71ef56b1 100644 --- a/packages/backend/src/models/json-schema/channel.ts +++ b/packages/backend/src/models/json-schema/channel.ts @@ -91,7 +91,7 @@ export const packedChannelSchema = { }, announcement: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, }, } as const; diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index e9f159b8126a..1a478b18f351 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -39,6 +39,7 @@ import { TickChartsProcessorService } from './processors/TickChartsProcessorServ import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js'; import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js'; import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js'; +import { IntegrationDaemonProcessorService } from './processors/IntegrationDaemonProcessorService.js'; @Module({ imports: [ @@ -78,6 +79,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor DeliverProcessorService, InboxProcessorService, AggregateRetentionProcessorService, + IntegrationDaemonProcessorService, QueueProcessorService, ], exports: [ diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 6ec4d01fd069..dd6dea551b58 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -40,6 +40,7 @@ import { CleanChartsProcessorService } from './processors/CleanChartsProcessorSe import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js'; +import { IntegrationDaemonProcessorService } from './processors/IntegrationDaemonProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; import { QUEUE, baseQueueOptions } from './const.js'; @@ -116,6 +117,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private resyncChartsProcessorService: ResyncChartsProcessorService, private cleanChartsProcessorService: CleanChartsProcessorService, private aggregateRetentionProcessorService: AggregateRetentionProcessorService, + private integrationDaemonProcessorService: IntegrationDaemonProcessorService, private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService, private cleanProcessorService: CleanProcessorService, ) { @@ -146,6 +148,7 @@ export class QueueProcessorService implements OnApplicationShutdown { case 'aggregateRetention': return this.aggregateRetentionProcessorService.process(); case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process(); case 'clean': return this.cleanProcessorService.process(); + case 'integrationDaemon': return this.integrationDaemonProcessorService.process(); default: throw new Error(`unrecognized job type ${job.name} for system`); } }, { diff --git a/packages/backend/src/queue/processors/IntegrationDaemonProcessorService.ts b/packages/backend/src/queue/processors/IntegrationDaemonProcessorService.ts new file mode 100644 index 000000000000..beb1079e113d --- /dev/null +++ b/packages/backend/src/queue/processors/IntegrationDaemonProcessorService.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@nestjs/common'; +import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import { MetaService } from '@/core/MetaService.js'; +import { PatreonManagementService } from '@/core/integrations/PatreonManagementService.js'; +import { FanboxManagementService } from '@/core/integrations/FanboxManagementService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; + +@Injectable() +export class IntegrationDaemonProcessorService { + private logger: Logger; + + constructor( + private queueLoggerService: QueueLoggerService, + private metaService: MetaService, + private patreonManagementService: PatreonManagementService, + private fanboxManagementService: FanboxManagementService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('integration-daemon'); + } + + @bindThis + public async process(): Promise { + const meta = await this.metaService.fetch(); + + if (meta.enableFanboxIntegration) { + this.fanboxManagementService.update(); + } + + if (meta.enablePatreonIntegration) { + this.patreonManagementService.update(); + } + } +} diff --git a/packages/backend/src/server/api/endpoints/integrations/fanbox/request-refresh.ts b/packages/backend/src/server/api/endpoints/integrations/fanbox/request-refresh.ts index 83051b9c82dd..37aeb6662d49 100644 --- a/packages/backend/src/server/api/endpoints/integrations/fanbox/request-refresh.ts +++ b/packages/backend/src/server/api/endpoints/integrations/fanbox/request-refresh.ts @@ -26,7 +26,7 @@ export default class extends Endpoint { private fanboxManagementService: FanboxManagementService, ) { super(meta, paramDef, async (ps, me) => { - this.fanboxManagementService.requestUpdateCache(); + this.fanboxManagementService.update(); }); } } diff --git a/packages/backend/src/server/api/endpoints/integrations/patreon/request-refresh.ts b/packages/backend/src/server/api/endpoints/integrations/patreon/request-refresh.ts index e2e95b49e79b..9b85e057e887 100644 --- a/packages/backend/src/server/api/endpoints/integrations/patreon/request-refresh.ts +++ b/packages/backend/src/server/api/endpoints/integrations/patreon/request-refresh.ts @@ -26,7 +26,7 @@ export default class extends Endpoint { private patreonManagementService: PatreonManagementService, ) { super(meta, paramDef, async (ps, me) => { - this.patreonManagementService.requestUpdateCache(); + this.patreonManagementService.update(); }); } } diff --git a/packages/backend/src/server/api/endpoints/supporter-list.ts b/packages/backend/src/server/api/endpoints/supporter-list.ts index 288cbeaafebf..0b1b61b8f51f 100644 --- a/packages/backend/src/server/api/endpoints/supporter-list.ts +++ b/packages/backend/src/server/api/endpoints/supporter-list.ts @@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PatreonManagementService } from '@/core/integrations/PatreonManagementService.js'; import { FanboxManagementService } from '@/core/integrations/FanboxManagementService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { MetaService } from '@/core/MetaService.js'; export const meta = { @@ -34,35 +33,32 @@ type SupporterUser = { @Injectable() export default class extends Endpoint { constructor( - private userEntityService: UserEntityService, private patreonManagementService: PatreonManagementService, private fanboxManagementService: FanboxManagementService, private metaService: MetaService, ) { super(meta, paramDef, async (ps, me) => { - const patreons = this.patreonManagementService.getPatreonUsers(); - const supporters = this.fanboxManagementService.getFanboxUsers(); + const patreons = this.patreonManagementService.get(); + const supporters = this.fanboxManagementService.get(); const users: Map = new Map(); const meta = await this.metaService.fetch(); if (!meta.enableSupporterPage || !meta.supporterNameThreshold || !meta.supporterNameWithIconThreshold) return []; - for (const patreon of Object.values(patreons)) { - if (patreon.amounts < meta.supporterNameThreshold || patreon.isHideFromSupporterPage) continue; - users.set(patreon.user.id, { - username: patreon.user.username, - name: patreon.user.name ?? patreon.user.username, - avatarUrl: patreon.user.avatarUrl ?? this.userEntityService.getIdenticonUrl(patreon.user), - withIcon: meta.supporterNameWithIconThreshold <= patreon.amounts, + for (const patreon of patreons.values()) { + if (patreon.amounts < meta.supporterNameThreshold || patreon.hide) continue; + const user = patreon.user; + users.set(user.username, { + ...patreon.user, + withIcon: meta.supporterNameWithIconThreshold <= patreon.amounts }); } - for (const supporter of Object.values(supporters)) { - if (supporter.amounts < meta.supporterNameThreshold || supporter.isHideFromSupporterPage) continue; - users.set(supporter.user.id, { - username: supporter.user.username, - name: supporter.user.name ?? supporter.user.username, - avatarUrl: supporter.user.avatarUrl ?? this.userEntityService.getIdenticonUrl(supporter.user), + for (const supporter of supporters.values()) { + if (supporter.amounts < meta.supporterNameThreshold || supporter.hide) continue; + const user = supporter.user; + users.set(user.username, { + ...supporter.user, withIcon: meta.supporterNameWithIconThreshold <= supporter.amounts, }); }