From 7815c68ea8080a87d1f89ba7d7e64503ae49eaec Mon Sep 17 00:00:00 2001 From: patrickkfkan Date: Sun, 29 Oct 2023 01:30:55 +0800 Subject: [PATCH] FanAPI: fetch by logged-in user if target empty --- src/lib/fan/FanAPI.ts | 39 ++++++++++++++++++++++++++++-------- src/lib/fan/FanInfoParser.ts | 27 +++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/lib/fan/FanAPI.ts b/src/lib/fan/FanAPI.ts index e0e74bd..33c96b9 100644 --- a/src/lib/fan/FanAPI.ts +++ b/src/lib/fan/FanAPI.ts @@ -17,12 +17,12 @@ import BaseAPIWithImageSupport, { BaseAPIWithImageSupportParams } from '../commo export { FanPageItemsResult, FanContinuationItemsResult }; export interface FanAPIGetInfoParams { - username: string; + username?: string; imageFormat: string | number | ImageFormat; } export interface FanAPIGetItemsParams { - target: string | FanItemsContinuation; + target?: string | FanItemsContinuation; imageFormat?: string | number | ImageFormat; } @@ -39,6 +39,13 @@ export interface FanAPIGetItemsFullParams extends FanAPIGetItemsParams { export default class FanAPI extends BaseAPIWithImageSupport { async getInfo(params: FanAPIGetInfoParams): Promise { + if (!params.username) { + const username = await this.getLoggedInFanUsername(); + return this.getInfo({ + ...params, + username + }); + } const imageConstants = await this.imageAPI.getConstants(); const fanPageUrl = FanAPI.getFanPageUrl(params.username); const opts = { @@ -94,6 +101,15 @@ export default class FanAPI extends BaseAPIWithImageSupport { */ protected async getItems(params: FanAPIGetItemsFullParams): Promise | FanContinuationItemsResult> { const { target, imageFormat, defaultImageFormat, continuationUrl } = params; + + if (!target) { + const username = await this.getLoggedInFanUsername(); + return this.getItems({ + ...params, + target: username + }); + } + const imageConstants = await this.imageAPI.getConstants(); const opts = { imageBaseUrl: imageConstants.baseUrl, @@ -101,7 +117,7 @@ export default class FanAPI extends BaseAPIWithImageSupport { }; if (!FanAPI.isContinuation(target)) { - const fanPageUrl = FanAPI.getFanPageUrl(target as string); + const fanPageUrl = FanAPI.getFanPageUrl(target); const html = await this.fetch(fanPageUrl); return params.parsePageFn(html, opts); } @@ -111,14 +127,13 @@ export default class FanAPI extends BaseAPIWithImageSupport { throw new FetchError('Unable to fetch fan contents: target is continuation token but continuation URL is missing.'); } - const continuation = target as FanItemsContinuation; const payload = { - fan_id: continuation.fanId, - older_than_token: continuation.token, + fan_id: target.fanId, + older_than_token: target.token, count: 20 }; const json = await this.fetch(continuationUrl, true, FetchMethod.POST, payload); - return params.parseContinuationFn(json, continuation, opts); + return params.parseContinuationFn(json, target, opts); } /** @@ -131,9 +146,17 @@ export default class FanAPI extends BaseAPIWithImageSupport { /** * @internal */ - protected static isContinuation(target: any) { + protected static isContinuation(target: any): target is FanItemsContinuation { return typeof target === 'object' && target.fanId && target.token; } + + /** + * @internal + */ + protected async getLoggedInFanUsername() { + const html = await this.fetch(URLS.SITE_URL); + return FanInfoParser.parseLoggedInFanUsername(html); + } } export class LimiterFanAPI extends FanAPI { diff --git a/src/lib/fan/FanInfoParser.ts b/src/lib/fan/FanInfoParser.ts index fc5fa1a..3119e87 100644 --- a/src/lib/fan/FanInfoParser.ts +++ b/src/lib/fan/FanInfoParser.ts @@ -49,4 +49,31 @@ export default class FanInfoParser { return result; } + + static parseLoggedInFanUsername(html: string) { + const $ = cheerioLoad(html); + const blob = decode($('#pagedata[data-blob]').attr('data-blob')); + let parsed; + try { + parsed = JSON.parse(blob); + } + catch (error: any) { + throw new ParseError('Failed to parse logged-in fan username: JSON error in data-blob.', html, error); + } + + const identitiesData = parsed.identities || {}; + const username = identitiesData.fan?.username; + if (!username || typeof username !== 'string') { + let reason; + if (identitiesData.fan === null) { + reason = 'check if valid cookie is set'; + } + else { + reason = 'invalid data'; + } + throw new ParseError(`Failed to parse logged-in fan username: ${reason}.`, html); + } + + return username; + } }