Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release: v2024.3.1-kakurega.1.34.1 #142

Merged
merged 4 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG_KAKUREGA.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1485,7 +1485,7 @@ export interface Locale extends ILocale {
*/
"requestedRefresh": string;
/**
* 最新の情報が反映されるまで最大5分ほどかかります
* 最新の情報が反映されるまで最大1時間程度かかる場合があります。反映されない場合は5~10分ほどお待ちいただいた上で、再度リクエストをお試しください
*/
"requestedRefreshDetails": string;
/**
Expand Down
2 changes: 1 addition & 1 deletion locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ connectService: "接続する"
disconnectService: "切断する"
requestRefresh: "更新をリクエストする"
requestedRefresh: "更新をリクエストしました"
requestedRefreshDetails: "最新の情報が反映されるまで最大5分ほどかかります。"
requestedRefreshDetails: "最新の情報が反映されるまで最大1時間程度かかる場合があります。反映されない場合は5~10分ほどお待ちいただいた上で、再度リクエストをお試しください。"
enableLocalTimeline: "ローカルタイムラインを有効にする"
enableGlobalTimeline: "グローバルタイムラインを有効にする"
disablingTimelinesInfo: "これらのタイムラインを無効化しても、利便性のため管理者およびモデレーターは引き続き利用することができます。"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/core/GlobalEventService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -209,6 +211,8 @@ type SerializedAll<T> = {
[K in keyof T]: Serialized<T[K]>;
};

type MapToRecord<T> = T extends Map<string, infer V> ? Record<string, V> : never;

export interface InternalEventTypes {
userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; };
userChangeDeletedState: { id: MiUser['id']; isDeleted: MiUser['isDeleted']; };
Expand Down Expand Up @@ -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<PatreonMembers>;
patreonMembersUpdating: never;
fanboxMembersUpdated: MapToRecord<FanboxMembers>;
fanboxMembersUpdating: never;
}

// name/messages(spec) pairs dictionary
Expand Down
13 changes: 13 additions & 0 deletions packages/backend/src/core/QueueService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
108 changes: 63 additions & 45 deletions packages/backend/src/core/integrations/FanboxManagementService.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,102 @@
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';
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<string, FanboxMember>;

@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<string, FanboxMember> = {};
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<void> {
this.updateCache();
private async onMessage(_: string, data: string): Promise<void> {
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<void> {
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();
Expand All @@ -93,30 +111,31 @@ export class FanboxManagementService implements OnApplicationShutdown {
},
});

const usersList = {} as Record<string, FanboxMember>;

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
Expand Down Expand Up @@ -164,7 +183,6 @@ export class FanboxManagementService implements OnApplicationShutdown {
}

async onApplicationShutdown(): Promise<void> {
clearInterval(this.intervalId);
clearTimeout(this.timeoutId);
this.redisForSub.off('message', this.onMessage);
}
}
Loading
Loading