fix(route): apple app store (#20427)
This commit is contained in:
parent
9e3ca7471c
commit
13e7784944
|
|
@ -1,7 +1,7 @@
|
|||
import { Route, ViewType } from '@/types';
|
||||
import got from '@/utils/got';
|
||||
import { load } from 'cheerio';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { appstoreBearerToken } from './utils';
|
||||
|
||||
const platformIds = {
|
||||
osx: 'macOS',
|
||||
|
|
@ -64,7 +64,7 @@ export const route: Route = {
|
|||
handler,
|
||||
description: `
|
||||
::: tip
|
||||
For example, the URL of [GarageBand](https://apps.apple.com/us/app/messages/id408709785) in the App Store is \`https://apps.apple.com/us/app/messages/id408709785\`. In this case, the \`App Store Country\` parameter for the route is \`us\`, and the \`App id\` parameter is \`id1146560473\`. So the route should be [\`/apple/apps/update/us/id408709785\`](https://rsshub.app/apple/apps/update/us/id408709785).
|
||||
For example, the URL of [GarageBand](https://apps.apple.com/us/app/garageband/id408709785) in the App Store is \`https://apps.apple.com/us/app/garageband/id408709785\`. In this case, the \`App Store Country\` parameter for the route is \`us\`, and the \`App id\` parameter is \`id408709785\`. So the route should be [\`/apple/apps/update/us/id408709785\`](https://rsshub.app/apple/apps/update/us/id408709785).
|
||||
:::`,
|
||||
};
|
||||
|
||||
|
|
@ -85,12 +85,30 @@ async function handler(ctx) {
|
|||
const rootUrl = 'https://apps.apple.com';
|
||||
const currentUrl = new URL(`${country}/app/${id}`, rootUrl).href;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
const bearer = await appstoreBearerToken();
|
||||
|
||||
const $ = load(response);
|
||||
const response = await ofetch(`https://amp-api-edge.apps.apple.com/v1/catalog/${country}/apps/${id.replace('id', '')}`, {
|
||||
headers: {
|
||||
authorization: `Bearer ${bearer}`,
|
||||
origin: 'https://apps.apple.com',
|
||||
},
|
||||
query: {
|
||||
platform: 'iphone',
|
||||
additionalPlatforms: 'appletv,ipad,iphone,mac,realityDevice,watch',
|
||||
extend: 'accessibility,accessibilityDetails,ageRating,backgroundAssetsInfo,backgroundAssetsInfoWithOptional,customArtwork,customDeepLink,customIconArtwork,customPromotionalText,customScreenshotsByType,customVideoPreviewsByType,description,expectedReleaseDateDisplayFormat,fileSizeByDevice,gameDisplayName,iconArtwork,installSizeByDeviceInBytes,messagesScreenshots,miniGamesDeepLink,minimumOSVersion,privacy,privacyDetails,privacyPolicyUrl,remoteControllerRequirement,requirementsByDeviceFamily,supportURLForLanguage,supportedGameCenterFeatures,supportsFunCamera,supportsSharePlay,versionHistory,websiteUrl',
|
||||
'extend[app-events]': 'description,productArtwork,productVideo',
|
||||
include: 'alternate-apps,app-bundles,customers-also-bought-apps,developer,developer-other-apps,merchandised-in-apps,related-editorial-items,reviews,top-in-apps',
|
||||
'include[apps]': 'app-events',
|
||||
'availableIn[app-events]': 'future',
|
||||
'sparseLimit[apps:customers-also-bought-apps]': 40,
|
||||
'sparseLimit[apps:developer-other-apps]': 40,
|
||||
'sparseLimit[apps:related-editorial-items]': 40,
|
||||
'limit[reviews]': 8,
|
||||
l: 'en-US',
|
||||
},
|
||||
});
|
||||
|
||||
const appData = JSON.parse(Object.values(JSON.parse($('script#shoebox-media-api-cache-apps').text()))[0]);
|
||||
const attributes = appData.d[0].attributes;
|
||||
const attributes = response.data[0].attributes;
|
||||
|
||||
const appName = attributes.name;
|
||||
const artistName = attributes.artistName;
|
||||
|
|
@ -99,6 +117,7 @@ async function handler(ctx) {
|
|||
let items = [];
|
||||
let title = '';
|
||||
let description = '';
|
||||
let image = '';
|
||||
|
||||
if (platformId && Object.hasOwn(platformAttributes, platformId)) {
|
||||
platform = Object.hasOwn(platformIds, platformId) ? platformIds[platformId] : platformId;
|
||||
|
|
@ -108,6 +127,7 @@ async function handler(ctx) {
|
|||
items = platformAttribute.versionHistory;
|
||||
title = `${appName}${platform ? ` for ${platform} ` : ' '}`;
|
||||
description = platformAttribute.description.standard;
|
||||
image = platformAttribute.iconArtwork?.url?.replace('{w}x{h}{c}.{f}', '3000x3000bb.webp');
|
||||
} else {
|
||||
title = appName;
|
||||
for (const pid of Object.keys(platformAttributes)) {
|
||||
|
|
@ -119,7 +139,8 @@ async function handler(ctx) {
|
|||
platformId: pid,
|
||||
})),
|
||||
];
|
||||
description += platformAttribute.description.standard;
|
||||
description = platformAttribute.description.standard;
|
||||
image = platformAttribute.iconArtwork?.url?.replace('{w}x{h}{c}.{f}', '3000x3000bb.webp');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,21 +158,13 @@ async function handler(ctx) {
|
|||
};
|
||||
});
|
||||
|
||||
const icon = new URL('favicon.ico', rootUrl).href;
|
||||
|
||||
ctx.set('json', {
|
||||
appData,
|
||||
});
|
||||
|
||||
return {
|
||||
item: items,
|
||||
title: `${title} - Apple App Store`,
|
||||
link: currentUrl,
|
||||
description: description?.replaceAll('\n', ' '),
|
||||
language: $('html').prop('lang'),
|
||||
image: $('meta[property="og:image"]').prop('content'),
|
||||
icon,
|
||||
logo: icon,
|
||||
image,
|
||||
logo: image,
|
||||
subtitle: appName,
|
||||
author: artistName,
|
||||
allowEmpty: true,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import ofetch from '@/utils/ofetch';
|
||||
import cache from '@/utils/cache';
|
||||
import { load } from 'cheerio';
|
||||
import { config } from '@/config';
|
||||
|
||||
// App Store and Podcast use different bearer tokens
|
||||
export const appstoreBearerToken = () =>
|
||||
cache.tryGet(
|
||||
'apple:podcast:bearer',
|
||||
async () => {
|
||||
const baseUrl = 'https://apps.apple.com';
|
||||
const response = await ofetch(`${baseUrl}/us/iphone/today`);
|
||||
const $ = load(response);
|
||||
|
||||
const moduleAddress = new URL($('head script[type="module"]').attr('src'), baseUrl).href;
|
||||
const modulesResponse = await ofetch(moduleAddress, {
|
||||
parseResponse: (txt) => txt,
|
||||
});
|
||||
const bearerToken = modulesResponse.match(/="(eyJhbGci.*?)"/)[1];
|
||||
|
||||
return bearerToken as string;
|
||||
},
|
||||
config.cache.contentExpire,
|
||||
false
|
||||
);
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { Route } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { load } from 'cheerio';
|
||||
import { appstoreBearerToken } from '@/routes/apple/utils';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/iap/:country/:id',
|
||||
|
|
@ -23,14 +24,6 @@ export const route: Route = {
|
|||
handler,
|
||||
};
|
||||
|
||||
const getMediaApiToken = (metaContent) => {
|
||||
if (!metaContent) {
|
||||
throw new Error('Empty web experience config meta content');
|
||||
}
|
||||
const config = JSON.parse(decodeURIComponent(metaContent));
|
||||
return config.MEDIA_API.token;
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const country = ctx.req.param('country');
|
||||
const id = ctx.req.param('id');
|
||||
|
|
@ -39,18 +32,18 @@ async function handler(ctx) {
|
|||
const res = await ofetch(link);
|
||||
const $ = load(res);
|
||||
const lang = $('html').attr('lang');
|
||||
const mediaToken = getMediaApiToken($('meta[name="web-experience-app/config/environment"]').attr('content'));
|
||||
const mediaToken = await appstoreBearerToken();
|
||||
|
||||
const apiResponse = await ofetch(`https://amp-api-edge.apps.apple.com/v1/catalog/${country}/apps/${id.replace('id', '')}`, {
|
||||
headers: {
|
||||
authorization: `Bearer ${mediaToken}`,
|
||||
origin: 'https://apps.apple.com',
|
||||
},
|
||||
query: {
|
||||
platform: 'web',
|
||||
include: 'merchandised-in-apps,top-in-apps,eula',
|
||||
l: lang,
|
||||
},
|
||||
headers: {
|
||||
authorization: `Bearer ${mediaToken}`,
|
||||
origin: 'https://apps.apple.com',
|
||||
},
|
||||
});
|
||||
|
||||
const appData = apiResponse.data[0];
|
||||
|
|
|
|||
Loading…
Reference in New Issue