From 01c3c6ca4b898a979422bb7ab1c26881a545f2cd Mon Sep 17 00:00:00 2001 From: Benjamin Altpeter Date: Thu, 13 Jun 2024 09:54:44 +0200 Subject: [PATCH 1/3] Make app metadata more generic to prepare for app details endpoint --- docs/README.md | 109 ++++++++++++++++++++++++++++++++---- src/common/data-format.ts | 65 +++++++++++++-------- src/endpoints/search.ts | 26 ++++++++- src/endpoints/top-charts.ts | 25 ++++++++- src/index.ts | 42 +++----------- 5 files changed, 193 insertions(+), 74 deletions(-) diff --git a/docs/README.md b/docs/README.md index fd2e9a2..19d1f67 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,6 +13,10 @@ parse-play - v2.2.0 ### Type Aliases - [AppMetadata](README.md#appmetadata) +- [AppMetadataFull](README.md#appmetadatafull) +- [AppMetadataProperty](README.md#appmetadataproperty) +- [AppMetadataPropertySearch](README.md#appmetadatapropertysearch) +- [AppMetadataPropertyTopCharts](README.md#appmetadatapropertytopcharts) - [CategoryId](README.md#categoryid) - [CountryCode](README.md#countrycode) - [DataSafetyLabel](README.md#datasafetylabel) @@ -37,6 +41,8 @@ parse-play - v2.2.0 - [dataSafetyLabelDataCategories](README.md#datasafetylabeldatacategories) - [dataSafetyLabelDataTypes](README.md#datasafetylabeldatatypes) - [dataSafetyLabelPurposes](README.md#datasafetylabelpurposes) +- [searchAppMetadataProperties](README.md#searchappmetadataproperties) +- [topChartsAppMetadataProperties](README.md#topchartsappmetadataproperties) ### Functions @@ -48,9 +54,28 @@ parse-play - v2.2.0 ### AppMetadata -Ƭ **AppMetadata**: `Object` +Ƭ **AppMetadata**<`P`\>: `Pick`<[`AppMetadataFull`](README.md#appmetadatafull), `P`\> -A single app and its associated metadata. +The metadata for a single app. The available properties depend on which endpoint this was fetched from. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `P` | extends [`AppMetadataProperty`](README.md#appmetadataproperty) | + +#### Defined in + +[common/data-format.ts:42](https://github.com/baltpeter/parse-play/blob/main/src/common/data-format.ts#L42) + +___ + +### AppMetadataFull + +Ƭ **AppMetadataFull**: `Object` + +The full metadata that can theoretically be fetched for an app. The individual endpoints will only return a subset of +this, see [AppMetadata](README.md#appmetadata). #### Type declaration @@ -74,7 +99,43 @@ A single app and its associated metadata. #### Defined in -[common/data-format.ts:6](https://github.com/baltpeter/parse-play/blob/main/src/common/data-format.ts#L6) +[common/data-format.ts:7](https://github.com/baltpeter/parse-play/blob/main/src/common/data-format.ts#L7) + +___ + +### AppMetadataProperty + +Ƭ **AppMetadataProperty**: keyof [`AppMetadataFull`](README.md#appmetadatafull) + +A property that can be present in the metadata of an app. + +#### Defined in + +[common/data-format.ts:40](https://github.com/baltpeter/parse-play/blob/main/src/common/data-format.ts#L40) + +___ + +### AppMetadataPropertySearch + +Ƭ **AppMetadataPropertySearch**: typeof [`searchAppMetadataProperties`](README.md#searchappmetadataproperties)[`number`] + +A property present in the metadata of each app in the search results. + +#### Defined in + +[endpoints/search.ts:42](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/search.ts#L42) + +___ + +### AppMetadataPropertyTopCharts + +Ƭ **AppMetadataPropertyTopCharts**: typeof [`topChartsAppMetadataProperties`](README.md#topchartsappmetadataproperties)[`number`] + +A property present in the metadata of each app in the top chart. + +#### Defined in + +[endpoints/top-charts.ts:49](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/top-charts.ts#L49) ___ @@ -297,25 +358,25 @@ ___ ### SearchAppsResults -Ƭ **SearchAppsResults**: [`AppMetadata`](README.md#appmetadata)[] +Ƭ **SearchAppsResults**: [`AppMetadata`](README.md#appmetadata)<[`AppMetadataPropertySearch`](README.md#appmetadatapropertysearch)\>[] A list of the search results. #### Defined in -[endpoints/search.ts:26](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/search.ts#L26) +[endpoints/search.ts:46](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/search.ts#L46) ___ ### TopChartsEntry -Ƭ **TopChartsEntry**: [`AppMetadata`](README.md#appmetadata) +Ƭ **TopChartsEntry**: [`AppMetadata`](README.md#appmetadata)<[`AppMetadataPropertyTopCharts`](README.md#appmetadatapropertytopcharts)\> A single app and its associated metadata on a top chart. #### Defined in -[endpoints/top-charts.ts:33](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/top-charts.ts#L33) +[endpoints/top-charts.ts:54](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/top-charts.ts#L54) ___ @@ -366,7 +427,7 @@ A list of the entries on the respective top chart. #### Defined in -[endpoints/top-charts.ts:37](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/top-charts.ts#L37) +[endpoints/top-charts.ts:58](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/top-charts.ts#L58) ## Variables @@ -410,6 +471,30 @@ Taken from the official documentation: @@ -510,7 +595,7 @@ An array of the top charts, in the same order as the requests. #### Defined in -[endpoints/top-charts.ts:110](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/top-charts.ts#L110) +[endpoints/top-charts.ts:131](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/top-charts.ts#L131) ___ @@ -537,7 +622,7 @@ The search results. #### Defined in -[endpoints/search.ts:131](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/search.ts#L131) +[endpoints/search.ts:151](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/search.ts#L151) ▸ **searchApps**(`requests`, `options`): `Promise`<([`SearchAppsResults`](README.md#searchappsresults) \| `undefined`)[]\> @@ -561,4 +646,4 @@ An array of the search results, in the same order as the requests. #### Defined in -[endpoints/search.ts:145](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/search.ts#L145) +[endpoints/search.ts:165](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/search.ts#L165) diff --git a/src/common/data-format.ts b/src/common/data-format.ts index 3772c46..0e7390b 100644 --- a/src/common/data-format.ts +++ b/src/common/data-format.ts @@ -1,9 +1,10 @@ import type { LanguageCode, CountryCode } from './consts'; /** - * A single app and its associated metadata. + * The full metadata that can theoretically be fetched for an app. The individual endpoints will only return a subset of + * this, see {@link AppMetadata}. */ -export type AppMetadata = { +export type AppMetadataFull = { /** The app's position in a list (top chart, search results). */ position: number; /** The app's bundle ID. */ @@ -35,30 +36,50 @@ export type AppMetadata = { /** A URL to the app's cover image. */ cover_image_url: string | undefined; }; +/** A property that can be present in the metadata of an app. */ +export type AppMetadataProperty = keyof AppMetadataFull; +/** The metadata for a single app. The available properties depend on which endpoint this was fetched from. */ +export type AppMetadata

= Pick; export const formatCurrency = ( value: number, currency: string, options: { language: LanguageCode; country: CountryCode } ) => new Intl.NumberFormat(`${options.language}-${options.country}`, { style: 'currency', currency }).format(value); -export const parseAppEntry = ( + +const appMetadataProperties: Record< + Exclude, + (d: any, options: { language: LanguageCode; country: CountryCode }) => any +> = { + app_id: (d) => d[0][0], + icon_url: (d) => d[1][3][2], + screenshot_urls: (d) => d[2].map((s: any) => s[3][2]), + name: (d) => d[3], + rating: (d) => d[4][1], + category: (d) => d[5], + price: (d, o) => (d[8] ? formatCurrency(d[8]?.[1][0][0] / 1e6, d[8]?.[1][0][1], o) : undefined), + buy_url: (d) => d[8]?.[6][5][2], + store_path: (d) => d[10][4][2], + trailer_url: (d) => d[12]?.[0][0][3][2], + description: (d) => d[13][1], + developer: (d) => d[14], + downloads: (d) => d[15], + cover_image_url: (d) => d[22][3]?.[2], +}; +export const parseAppEntry =

( entry: any, - idx: number, - options: { language: LanguageCode; country: CountryCode } -): AppMetadata => ({ - position: idx + 1, - app_id: entry[0][0], - icon_url: entry[1][3][2], - screenshot_urls: entry[2].map((s: any) => s[3][2]), - name: entry[3], - rating: entry[4][1], - category: entry[5], - price: entry[8] ? formatCurrency(entry[8]?.[1][0][0] / 1e6, entry[8]?.[1][0][1], options) : undefined, - buy_url: entry[8]?.[6][5][2], - store_path: entry[10][4][2], - trailer_url: entry[12]?.[0][0][3][2], - description: entry[13][1], - developer: entry[14], - downloads: entry[15], - cover_image_url: entry[22][3]?.[2], -}); + properties: P[] | readonly P[], + options: { language: LanguageCode; country: CountryCode; idx?: number } +): AppMetadata

=> { + const res: Record = {}; + + if (options.idx) res.position = options.idx + 1; + + for (const [property, getter] of Object.entries(appMetadataProperties).filter(([p]) => + properties.includes(p as P) + )) { + res[property] = getter(entry, options); + } + + return res as AppMetadata

; +}; diff --git a/src/endpoints/search.ts b/src/endpoints/search.ts index 12081ab..0242861 100644 --- a/src/endpoints/search.ts +++ b/src/endpoints/search.ts @@ -20,10 +20,30 @@ export type SearchAppsOptions = { language: LanguageCode; }; +/** The properties present in the metadata of each app in the search results. */ +export const searchAppMetadataProperties = [ + 'position', + 'app_id', + 'icon_url', + 'screenshot_urls', + 'name', + 'rating', + 'category', + 'price', + 'buy_url', + 'store_path', + 'trailer_url', + 'description', + 'developer', + 'downloads', + 'cover_image_url', +] as const; +/** A property present in the metadata of each app in the search results. */ +export type AppMetadataPropertySearch = typeof searchAppMetadataProperties[number]; /** * A list of the search results. */ -export type SearchAppsResults = AppMetadata[]; +export type SearchAppsResults = AppMetadata[]; export const searchAppsRequestPayload = (request: SearchAppsRequest): RequestPayload => [ 'lGYRle', @@ -77,7 +97,7 @@ export const parseSearchAppsPayload = (data: any, options: SearchAppsOptions): S assert(() => meta.length === 102 || meta.length === 101, 'Meta length.'); - return parseAppEntry(meta, hasFeaturedApp ? idx + 1 : idx, options); + return parseAppEntry(meta, searchAppMetadataProperties, { ...options, idx: hasFeaturedApp ? idx + 1 : idx }); }); if (hasFeaturedApp) { @@ -89,7 +109,7 @@ export const parseSearchAppsPayload = (data: any, options: SearchAppsOptions): S 'Featured entry inner meta length.' ); - const featuredEntryParsed: AppMetadata = { + const featuredEntryParsed = { position: 1, app_id: featuredEntry[16][11][0][0], icon_url: featuredEntry[16][2][95][0][3][2], diff --git a/src/endpoints/top-charts.ts b/src/endpoints/top-charts.ts index 4035d4d..5d7390c 100644 --- a/src/endpoints/top-charts.ts +++ b/src/endpoints/top-charts.ts @@ -27,10 +27,31 @@ export type TopChartsOptions = { language: LanguageCode; }; +/** The properties present in the metadata of each app in the top chart. */ +export const topChartsAppMetadataProperties = [ + 'position', + 'app_id', + 'icon_url', + 'screenshot_urls', + 'name', + 'rating', + 'category', + 'price', + 'buy_url', + 'store_path', + 'trailer_url', + 'description', + 'developer', + 'downloads', + 'cover_image_url', +] as const; +/** A property present in the metadata of each app in the top chart. */ +export type AppMetadataPropertyTopCharts = typeof topChartsAppMetadataProperties[number]; + /** * A single app and its associated metadata on a top chart. */ -export type TopChartsEntry = AppMetadata; +export type TopChartsEntry = AppMetadata; /** * A list of the entries on the respective top chart. */ @@ -78,7 +99,7 @@ export const parseTopChartPayload = (data: any, options: TopChartsOptions): TopC 'Weird second meta object only has category.' ); - return parseAppEntry(meta, idx, options); + return parseAppEntry(meta, topChartsAppMetadataProperties, { ...options, idx }); }); return parsed; diff --git a/src/index.ts b/src/index.ts index be8f346..c53a645 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,37 +1,9 @@ -export { fetchTopCharts, parseTopChartPayload, topChartsRequestPayload } from './endpoints/top-charts'; -export { - fetchDataSafetyLabels, - parseDataSafetyLabelPayload, - dataSafetyLabelsRequestPayload, -} from './endpoints/data-safety'; -export { searchApps, parseSearchAppsPayload, searchAppsRequestPayload } from './endpoints/search'; +export * from './endpoints/data-safety'; +export * from './endpoints/search'; +export * from './endpoints/top-charts'; -export { batchExecute } from './common/requests'; -export { - categories, - countries, - languages, - dataSafetyLabelDataCategories, - dataSafetyLabelDataTypes, - dataSafetyLabelPurposes, -} from './common/consts'; -export { parseAppEntry } from './common/data-format'; +export * from './common/consts'; -export type { TopChartsRequest, TopChartsOptions, TopChartsResult, TopChartsEntry } from './endpoints/top-charts'; -export type { - DataSafetyLabelRequest, - DataSafetyLabelsOptions, - DataSafetyLabel, - DataTypeDeclaration, - DataSafetyLabelSecurityPracticesDeclarations, -} from './endpoints/data-safety'; -export type { SearchAppsRequest, SearchAppsOptions, SearchAppsResults } from './endpoints/search'; -export type { - CategoryId, - CountryCode, - LanguageCode, - DataSafetyLabelDataCategory, - DataSafetyLabelDataType, - DataSafetyLabelPurpose, -} from './common/consts'; -export type { AppMetadata } from './common/data-format'; +export { parseAppEntry } from './common/data-format'; +export type { AppMetadata, AppMetadataFull, AppMetadataProperty } from './common/data-format'; +export { batchExecute } from './common/requests'; From 397a64a97e8662a60532ae89815af87cf8ced4f8 Mon Sep 17 00:00:00 2001 From: Benjamin Altpeter Date: Thu, 13 Jun 2024 12:18:48 +0200 Subject: [PATCH 2/3] Fixes #13: Fetch app details function --- README.md | 69 ++++++++- docs/README.md | 220 +++++++++++++++++++++++++++- src/common/data-format.ts | 82 ++++++++++- src/endpoints/app-details.ts | 269 +++++++++++++++++++++++++++++++++++ src/index.ts | 3 +- 5 files changed, 630 insertions(+), 13 deletions(-) create mode 100644 src/endpoints/app-details.ts diff --git a/README.md b/README.md index 5e29a40..abbaf17 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This library is able to fetch and parse data from undocumented internal API endpoints of the Google Play Store. Currently, it has the following features: * Fetch the **charts of the most popular apps**, including filtering by category and chart. -* Fetch apps' **data safety labels**. +* Fetch an app's **metadata** including **data safety labels**. * **Search** for apps. I'll extend the supported API endpoints over time, as per what I need for my projects. The focus will likely be on functions useful for research into mobile privacy and data protection. @@ -66,6 +66,73 @@ console.log(topCharts[1]?.[0]?.app_id, topCharts?.[1]?.[0]?.name); // com.MOBGam Note that despite us trying to fetch 1000 apps for the second chart, only 660 apps were returned. This is a server-side limit. +### Fetch app details + +The following example fetches the metadata of the Facebook app: + +```ts +import { fetchAppDetails } from 'parse-play'; + +(async () => { + const appDetails = await fetchAppDetails({ appId: 'com.facebook.katana' }, { language: 'EN', country: 'DE' }); + console.log(appDetails.name, 'costs', appDetails.price, 'and was last updated on', appDetails.updated_on); + // Facebook costs €0.00 and was last updated on 2024-06-13T04:58:13.000Z +})(); +``` + +Through this endpoint, you can also fetch an app's data safety labels: + +```ts +const appDetails = await fetchAppDetails({ appId: 'com.facebook.katana' }, { language: 'EN', country: 'DE' }); + +console.log('Data shared:', appDetails.data_shared); +console.log('Data collected:', appDetails.data_collected); +console.log('Security practices:', appDetails.security_practices); +console.log('Privacy policy URL:', appDetails.privacy_policy_url); +``` + +

+Data safety label response +The result looks like this: + +``` +Data shared: [ + { + category: 'Personal info', + type: 'Name', + purposes: [ 'Fraud prevention, security, and compliance' ], + optional: false + }, + // … +] +Data collected: [ + { + category: 'Personal info', + type: 'Name', + purposes: [ + 'App functionality', + 'Analytics', + 'Developer communications', + 'Advertising or marketing', + 'Fraud prevention, security, and compliance', + 'Personalization', + 'Account management' + ], + optional: false + }, + // … +] +Security practices: { + data_encrypted_in_transit: true, + can_request_data_deletion: true, + committed_to_play_families_policy: undefined, + independent_security_review: undefined +} +Privacy policy URL: https://www.facebook.com/about/privacy/ +``` +
+ + ### Fetch an app's data safety labels The following example fetches the data safety labels for TikTok in English: diff --git a/docs/README.md b/docs/README.md index 19d1f67..65333f6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,9 +12,13 @@ parse-play - v2.2.0 ### Type Aliases +- [AppDetailsOptions](README.md#appdetailsoptions) +- [AppDetailsRequest](README.md#appdetailsrequest) +- [AppDetailsResult](README.md#appdetailsresult) - [AppMetadata](README.md#appmetadata) - [AppMetadataFull](README.md#appmetadatafull) - [AppMetadataProperty](README.md#appmetadataproperty) +- [AppMetadataPropertyFetchAppDetails](README.md#appmetadatapropertyfetchappdetails) - [AppMetadataPropertySearch](README.md#appmetadatapropertysearch) - [AppMetadataPropertyTopCharts](README.md#appmetadatapropertytopcharts) - [CategoryId](README.md#categoryid) @@ -28,6 +32,7 @@ parse-play - v2.2.0 - [DataSafetyLabelsOptions](README.md#datasafetylabelsoptions) - [DataTypeDeclaration](README.md#datatypedeclaration) - [LanguageCode](README.md#languagecode) +- [PermissionGroup](README.md#permissiongroup) - [SearchAppsOptions](README.md#searchappsoptions) - [SearchAppsRequest](README.md#searchappsrequest) - [SearchAppsResults](README.md#searchappsresults) @@ -41,17 +46,69 @@ parse-play - v2.2.0 - [dataSafetyLabelDataCategories](README.md#datasafetylabeldatacategories) - [dataSafetyLabelDataTypes](README.md#datasafetylabeldatatypes) - [dataSafetyLabelPurposes](README.md#datasafetylabelpurposes) +- [fetchAppDetailsMetadataProperties](README.md#fetchappdetailsmetadataproperties) - [searchAppMetadataProperties](README.md#searchappmetadataproperties) - [topChartsAppMetadataProperties](README.md#topchartsappmetadataproperties) ### Functions +- [fetchAppDetails](README.md#fetchappdetails) - [fetchDataSafetyLabels](README.md#fetchdatasafetylabels) - [fetchTopCharts](README.md#fetchtopcharts) +- [parseAppEntry](README.md#parseappentry) - [searchApps](README.md#searchapps) ## Type Aliases +### AppDetailsOptions + +Ƭ **AppDetailsOptions**: `Object` + +Parameters for all fetch app details requests in a [fetchAppDetails](README.md#fetchappdetails) call. + +#### Type declaration + +| Name | Type | Description | +| :------ | :------ | :------ | +| `country` | [`CountryCode`](README.md#countrycode) | The country version of the Play Store to fetch from. | +| `language` | [`LanguageCode`](README.md#languagecode) | The language for descriptions, etc. | + +#### Defined in + +[endpoints/app-details.ts:13](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/app-details.ts#L13) + +___ + +### AppDetailsRequest + +Ƭ **AppDetailsRequest**: `Object` + +Parameters for a fetch app details request. + +#### Type declaration + +| Name | Type | Description | +| :------ | :------ | :------ | +| `appId` | `string` | The app ID. | + +#### Defined in + +[endpoints/app-details.ts:8](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/app-details.ts#L8) + +___ + +### AppDetailsResult + +Ƭ **AppDetailsResult**: [`AppMetadata`](README.md#appmetadata)<[`AppMetadataPropertyFetchAppDetails`](README.md#appmetadatapropertyfetchappdetails)\> + +The result of a fetch app details request. + +#### Defined in + +[endpoints/app-details.ts:58](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/app-details.ts#L58) + +___ + ### AppMetadata Ƭ **AppMetadata**<`P`\>: `Pick`<[`AppMetadataFull`](README.md#appmetadatafull), `P`\> @@ -66,7 +123,7 @@ The metadata for a single app. The available properties depend on which endpoint #### Defined in -[common/data-format.ts:42](https://github.com/baltpeter/parse-play/blob/main/src/common/data-format.ts#L42) +[common/data-format.ts:112](https://github.com/baltpeter/parse-play/blob/main/src/common/data-format.ts#L112) ___ @@ -83,23 +140,50 @@ this, see [AppMetadata](README.md#appmetadata). | :------ | :------ | :------ | | `app_id` | `string` | The app's bundle ID. | | `buy_url` | `string` \| `undefined` | A URL to the Play Store website to buy the app. | -| `category` | `string` | The app's category. | +| `category` | `string` | The app's main category. | +| `content_rating?` | { `icon_url`: `string` ; `interactive_elements?`: `string` ; `label`: `string` } | The app's content rating. | +| `content_rating.icon_url` | `string` | The URL to an icon for the content rating. | +| `content_rating.interactive_elements?` | `string` | A description of interactive elements in the app. | +| `content_rating.label` | `string` | The label for the content rating. | | `cover_image_url` | `string` \| `undefined` | A URL to the app's cover image. | +| `data_collected` | [`DataTypeDeclaration`](README.md#datatypedeclaration)[] \| `undefined` | An overview of the data the app may collect. | +| `data_shared` | [`DataTypeDeclaration`](README.md#datatypedeclaration)[] \| `undefined` | An overview of the data that the app may share with other companies or organizations. | | `description` | `string` | The app's description. | | `developer` | `string` | The app's developer. | -| `downloads` | `string` | The approximate download count of the app, as displayed on the Play Store website. | +| `developer_address` | `string` \| `undefined` | The developer's address. | +| `developer_email` | `string` | The developer's email address. | +| `developer_path` | `string` | The relative path of the developer's page on the Play Store website. | +| `developer_website_url` | `string` \| `undefined` | The URL to the developer's website. | +| `downloads` | `string` | The approximate download count of the app as a string, as displayed on the Play Store website. | +| `downloads_exact` | `number` | The exact download count of the app. | | `icon_url` | `string` | A URL to the app's icon. | +| `in_app_purchases?` | `string` | The cost of in-app purchases for the app. | | `name` | `string` | The app's name. | +| `offered_by` | `string` | The company distributing the app on the Play Store. | +| `permissions` | [`PermissionGroup`](README.md#permissiongroup)[] | The app's permissions, grouped by category. | | `position` | `number` | The app's position in a list (top chart, search results). | | `price` | `string` \| `undefined` | The app's price. Can be undefined for pre-release apps. | +| `privacy_policy_url` | `string` \| `undefined` | The URL to the app's privacy policy. | | `rating` | `number` \| `undefined` | The app's review rating. | +| `released_on?` | `Date` | The date when the app was first published. | +| `requires_android?` | {} | The app's required version of Android. | | `screenshot_urls` | `string`[] | URLs to screenshots of the app. | +| `security_practices` | [`DataSafetyLabelSecurityPracticesDeclarations`](README.md#datasafetylabelsecuritypracticesdeclarations) \| `undefined` | An overview of the app's security practices. | | `store_path` | `string` | The relative path of the app on the Play Store website. | +| `tags?` | { `id?`: `string` ; `name`: `string` ; `path`: `string` } | A list of the app's categories and related search terms. | +| `tags.id?` | `string` | A machine-readable ID for the tag. | +| `tags.name` | `string` | The name/label of the tag. | +| `tags.path` | `string` | The relative path of the category/search page on the Play Store website. | +| `top_chart_placement?` | { `label`: `string` ; `placement`: `string` } | The app's placement on a top chart. | +| `top_chart_placement.label` | `string` | The label for the placement. | +| `top_chart_placement.placement` | `string` | The app's position in the top chart. | | `trailer_url` | `string` \| `undefined` | A URL to a video trailer for the app. | +| `updated_on` | `Date` | The date when the app was last updated. | +| `version?` | `string` | The app's version. | #### Defined in -[common/data-format.ts:7](https://github.com/baltpeter/parse-play/blob/main/src/common/data-format.ts#L7) +[common/data-format.ts:20](https://github.com/baltpeter/parse-play/blob/main/src/common/data-format.ts#L20) ___ @@ -111,7 +195,19 @@ A property that can be present in the metadata of an app. #### Defined in -[common/data-format.ts:40](https://github.com/baltpeter/parse-play/blob/main/src/common/data-format.ts#L40) +[common/data-format.ts:110](https://github.com/baltpeter/parse-play/blob/main/src/common/data-format.ts#L110) + +___ + +### AppMetadataPropertyFetchAppDetails + +Ƭ **AppMetadataPropertyFetchAppDetails**: typeof [`fetchAppDetailsMetadataProperties`](README.md#fetchappdetailsmetadataproperties)[`number`] + +A property present when fetching app details. + +#### Defined in + +[endpoints/app-details.ts:56](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/app-details.ts#L56) ___ @@ -319,6 +415,27 @@ The language code of a language supported on the Play Store. ___ +### PermissionGroup + +Ƭ **PermissionGroup**: `Object` + +A group of related permissions the app has access to. + +#### Type declaration + +| Name | Type | Description | +| :------ | :------ | :------ | +| `icon_url?` | `string` | The URL to the group's icon. | +| `id?` | `string` | A machine-readable ID for the group. | +| `name?` | `string` | The name/label of the group. | +| `permissions` | `string`[] | The detailed permissions in this group the app has access to. | + +#### Defined in + +[common/data-format.ts:5](https://github.com/baltpeter/parse-play/blob/main/src/common/data-format.ts#L5) + +___ + ### SearchAppsOptions Ƭ **SearchAppsOptions**: `Object` @@ -473,6 +590,18 @@ Taken from the official documentation: + +Fetch the details/metadata of an app on the Google Play Store. + +This uses the Play Store's internal `batchexecute` endpoint with an RPC ID of `Ws7gDc`. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `request` | [`AppDetailsRequest`](README.md#appdetailsrequest) \| [[`AppDetailsRequest`](README.md#appdetailsrequest)] | The parameters of which app to fetch the details of. | +| `options` | [`AppDetailsOptions`](README.md#appdetailsoptions) | Language and country options. | + +#### Returns + +`Promise`<[`AppDetailsResult`](README.md#appdetailsresult)\> + +The app details. + +#### Defined in + +[endpoints/app-details.ts:243](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/app-details.ts#L243) + +▸ **fetchAppDetails**(`requests`, `options`): `Promise`<[`AppDetailsResult`](README.md#appdetailsresult)[]\> + +Same as [fetchAppDetails](README.md#fetchappdetails) but for fetching the details of multiple apps at once. The details are all fetched in +a single API request. + +**`see`** [fetchAppDetails](README.md#fetchappdetails) + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `requests` | [`AppDetailsRequest`](README.md#appdetailsrequest)[] | An array of fetch app details requests. | +| `options` | [`AppDetailsOptions`](README.md#appdetailsoptions) | The options for _all_ requests. | + +#### Returns + +`Promise`<[`AppDetailsResult`](README.md#appdetailsresult)[]\> + +An array of the app details, in the same order as the requests. + +#### Defined in + +[endpoints/app-details.ts:257](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/app-details.ts#L257) + +___ + ### fetchDataSafetyLabels ▸ **fetchDataSafetyLabels**(`request`, `options`): `Promise`<[`DataSafetyLabel`](README.md#datasafetylabel) \| `undefined`\> @@ -599,6 +779,36 @@ An array of the top charts, in the same order as the requests. ___ +### parseAppEntry + +▸ **parseAppEntry**<`P`\>(`entry`, `properties`, `options`): [`AppMetadata`](README.md#appmetadata)<`P`\> + +Parse an app entry in a search or top chart response. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `P` | extends keyof [`AppMetadataFull`](README.md#appmetadatafull) | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `entry` | `any` | +| `properties` | `P`[] \| readonly `P`[] | +| `options` | `Object` | + +#### Returns + +[`AppMetadata`](README.md#appmetadata)<`P`\> + +#### Defined in + +[common/data-format.ts:139](https://github.com/baltpeter/parse-play/blob/main/src/common/data-format.ts#L139) + +___ + ### searchApps ▸ **searchApps**(`request`, `options`): `Promise`<[`SearchAppsResults`](README.md#searchappsresults) \| `undefined`\> diff --git a/src/common/data-format.ts b/src/common/data-format.ts index 0e7390b..841ca9f 100644 --- a/src/common/data-format.ts +++ b/src/common/data-format.ts @@ -1,4 +1,17 @@ -import type { LanguageCode, CountryCode } from './consts'; +import { LanguageCode, CountryCode } from './consts'; +import type { DataTypeDeclaration, DataSafetyLabelSecurityPracticesDeclarations } from '../endpoints/data-safety'; + +/** A group of related permissions the app has access to. */ +export type PermissionGroup = { + /** A machine-readable ID for the group. */ + id?: string; + /** The name/label of the group. */ + name?: string; + /** The URL to the group's icon. */ + icon_url?: string; + /** The detailed permissions in this group the app has access to. */ + permissions: string[]; +}; /** * The full metadata that can theoretically be fetched for an app. The individual endpoints will only return a subset of @@ -17,7 +30,7 @@ export type AppMetadataFull = { name: string; /** The app's review rating. */ rating: number | undefined; - /** The app's category. */ + /** The app's main category. */ category: string; /** The app's price. Can be undefined for pre-release apps. */ price: string | undefined; @@ -31,10 +44,67 @@ export type AppMetadataFull = { description: string; /** The app's developer. */ developer: string; - /** The approximate download count of the app, as displayed on the Play Store website. */ + /** The relative path of the developer's page on the Play Store website. */ + developer_path: string; + /** The URL to the developer's website. */ + developer_website_url: string | undefined; + /** The developer's email address. */ + developer_email: string; + /** The developer's address. */ + developer_address: string | undefined; + /** The URL to the app's privacy policy. */ + privacy_policy_url: string | undefined; + /** An overview of the data that the app may share with other companies or organizations. */ + data_shared: DataTypeDeclaration[] | undefined; + /** An overview of the data the app may collect. */ + data_collected: DataTypeDeclaration[] | undefined; + /** An overview of the app's security practices. */ + security_practices: DataSafetyLabelSecurityPracticesDeclarations | undefined; + /** The approximate download count of the app as a string, as displayed on the Play Store website. */ downloads: string; + /** The exact download count of the app. */ + downloads_exact: number; /** A URL to the app's cover image. */ cover_image_url: string | undefined; + /** The app's permissions, grouped by category. */ + permissions: PermissionGroup[]; + /** The date when the app was last updated. */ + updated_on: Date; + /** The date when the app was first published. */ + released_on?: Date; + /** The cost of in-app purchases for the app. */ + in_app_purchases?: string; + /** The app's content rating. */ + content_rating?: { + /** The label for the content rating. */ + label: string; + /** The URL to an icon for the content rating. */ + icon_url: string; + /** A description of interactive elements in the app. */ + interactive_elements?: string; + }; + /** The app's placement on a top chart. */ + top_chart_placement?: { + /** The label for the placement. */ + label: string; + /** The app's position in the top chart. */ + placement: string; + }; + /** A list of the app's categories and related search terms. */ + tags?: { + /** A machine-readable ID for the tag. */ + id?: string; + /** The name/label of the tag. */ + name: string; + /** The relative path of the category/search page on the Play Store website. */ + path: string; + }; + /** The app's version. */ + version?: string; + /** The app's required version of Android. */ + requires_android?: { version: string; api_level: number }; + /** The company distributing the app on the Play Store. */ + offered_by: string; }; /** A property that can be present in the metadata of an app. */ export type AppMetadataProperty = keyof AppMetadataFull; @@ -47,9 +117,8 @@ export const formatCurrency = ( options: { language: LanguageCode; country: CountryCode } ) => new Intl.NumberFormat(`${options.language}-${options.country}`, { style: 'currency', currency }).format(value); -const appMetadataProperties: Record< - Exclude, - (d: any, options: { language: LanguageCode; country: CountryCode }) => any +const appMetadataProperties: Partial< + Record any> > = { app_id: (d) => d[0][0], icon_url: (d) => d[1][3][2], @@ -66,6 +135,7 @@ const appMetadataProperties: Record< downloads: (d) => d[15], cover_image_url: (d) => d[22][3]?.[2], }; +/** Parse an app entry in a search or top chart response. */ export const parseAppEntry =

( entry: any, properties: P[] | readonly P[], diff --git a/src/endpoints/app-details.ts b/src/endpoints/app-details.ts new file mode 100644 index 0000000..0305d31 --- /dev/null +++ b/src/endpoints/app-details.ts @@ -0,0 +1,269 @@ +import { batchExecute, RequestPayload } from '../common/requests'; +import { assert } from '../common/assert'; +import { formatCurrency, type AppMetadata } from '../common/data-format'; +import type { DataTypeDeclaration } from './data-safety'; +import { dataSafetyLabelPurposes, type LanguageCode, type CountryCode } from '../common/consts'; + +/** Parameters for a fetch app details request. */ +export type AppDetailsRequest = { + /** The app ID. */ + appId: string; +}; +/** Parameters for all fetch app details requests in a {@link fetchAppDetails} call. */ +export type AppDetailsOptions = { + /** The country version of the Play Store to fetch from. */ + country: CountryCode; + /** The language for descriptions, etc. */ + language: LanguageCode; +}; + +/** The properties present when fetching app details. */ +export const fetchAppDetailsMetadataProperties = [ + 'app_id', + 'name', + 'content_rating', + 'released_on', + 'downloads', + 'downloads_exact', + 'in_app_purchases', + 'offered_by', + 'rating', + 'price', + 'buy_url', + 'top_chart_placement', + 'developer', + 'developer_path', + 'developer_website_url', + 'developer_email', + 'developer_address', + 'description', + 'permissions', + 'screenshot_urls', + 'category', + 'icon_url', + 'cover_image_url', + 'privacy_policy_url', + 'trailer_url', + 'tags', + 'data_shared', + 'data_collected', + 'security_practices', + 'version', + 'requires_android', + 'updated_on', +] as const; +/** A property present when fetching app details. */ +export type AppMetadataPropertyFetchAppDetails = typeof fetchAppDetailsMetadataProperties[number]; +/** The result of a fetch app details request. */ +export type AppDetailsResult = AppMetadata; + +export const fetchAppDetailsRequestPayload = (request: AppDetailsRequest): RequestPayload => [ + 'Ws7gDc', + JSON.stringify([ + null, + null, + [ + [ + 1, 9, 10, 11, 13, 14, 19, 20, 38, 43, 47, 49, 52, 58, 59, 63, 69, 70, 73, 74, 75, 78, 79, 80, 91, 92, + 95, 96, 97, 100, 101, 103, 106, 112, 119, 129, 137, 138, 139, 141, 145, 146, 151, 155, 169, + ], + ], + [ + [ + null, + null, + null, + null, + null, + null, + null, + [null, 2], + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + [1], + ], + [null, [[[]]], null, null, [1]], + [null, [[[]]], null, [1]], + [null, [[[]]]], + null, + null, + null, + null, + [[[[]]]], + [[[[]]]], + ], + null, + [[request.appId, 7]], + ]), +]; + +const parseDslCategory = (c: any) => ({ + heading: c[1], + description: c[2][1], + icons: [c[0][3][2], c[0][10][2]], +}); +const parseDslSection = (d: any, idx: number): DataTypeDeclaration[] | undefined => + !d + ? undefined + : !d[idx][0] + ? [] + : (d[idx][0] as any[]).flatMap((r: any) => + (r[4] as any[]).map((d: any) => ({ + category: parseDslCategory(r[0]).heading, + type: d[0], + purposes: dataSafetyLabelPurposes.filter((p) => (d[2] as string).includes(p)), + optional: d[1], + })) + ); +const parseSecurityPractices = (data: any) => { + const securityPracticesEntries: any[] | undefined = data[137]?.[9]?.[2].map(parseDslCategory); + assert(() => { + if (!securityPracticesEntries) return true; + + const knownHeadings = new Set([ + 'Data is encrypted in transit', + 'You can request that data be deleted', + 'Data can’t be deleted', + 'Data isn’t encrypted', + 'Committed to follow the Play Families Policy', + 'Independent security review', + ]); + return securityPracticesEntries?.map((e) => e.heading).every((h) => knownHeadings.has(h)); + }, 'Only known security practice entries.'); + const hasSecurityPracticeAttribute = (positiveHeading?: string, negativeHeading?: string) => { + if (positiveHeading && securityPracticesEntries?.find((e) => e.heading === positiveHeading)) return true; + if (negativeHeading && securityPracticesEntries?.find((e) => e.heading === negativeHeading)) return false; + return undefined; + }; + + return securityPracticesEntries + ? { + data_encrypted_in_transit: hasSecurityPracticeAttribute( + 'Data is encrypted in transit', + 'Data isn’t encrypted' + ), + can_request_data_deletion: hasSecurityPracticeAttribute( + 'You can request that data be deleted', + 'Data can’t be deleted' + ), + committed_to_play_families_policy: hasSecurityPracticeAttribute( + 'Committed to follow the Play Families Policy' + ), + independent_security_review: hasSecurityPracticeAttribute('Independent security review'), + } + : undefined; +}; + +export const parseAppDetailsPayload = (payload: any, options: AppDetailsOptions): AppDetailsResult | undefined => { + if (!payload) return undefined; + assert( + () => payload.length === 3 && payload[0].flat(Infinity).length === 0 && payload[1].length === 40, + 'Expected inner data structure.' + ); + const data = payload[1][2]; + + return { + app_id: payload[1][11][0][0] || data[77][0], + name: data[0][0], + content_rating: data[9] + ? { label: data[9][0], icon_url: data[9][1][3][2], interactive_elements: data[9][3]?.[1] } + : undefined, + released_on: data[10] ? new Date(data[10][1][0] * 1000) : undefined, + downloads: data[13][0], + downloads_exact: data[13][2], + in_app_purchases: data[19]?.[0], + offered_by: data[37][0], + rating: data[51][0][1], + price: data[57][0][0][0][0] + ? formatCurrency(data[57][0][0][0][0]?.[1][0][0] / 1e6, data[57][0][0][0][0]?.[1][0][1], options) + : undefined, + buy_url: data[57][0][0][0][0][6][5][2], + top_chart_placement: data[58] ? { label: data[58][0], placement: data[58][2] } : undefined, + developer: data[68][0], + developer_path: data[68][1][4][2], + developer_website_url: data[69][0]?.[5][2], + developer_email: data[69][1][0], + developer_address: data[69][2]?.[0], + description: data[72][0][1], + permissions: [ + ...(data[74][2][0] || []), + ...(data[74][2][1] || []), + [undefined, undefined, data[74][2][2], undefined], + ] + .filter((g) => g[2]?.length > 0) + .map((g) => ({ + id: g[3]?.[0], + name: g[0], + icon_url: g[1]?.[3][2], + permissions: g[2].map((p: any) => p[1]), + })), + screenshot_urls: data[78][0].map((s: any) => s[3][2]), + category: data[79][0][0][0], + icon_url: data[95][0][3][2], + cover_image_url: data[96][0][3][2], + privacy_policy_url: data[99]?.[0][5][2], + trailer_url: data[100]?.[0][0][3][2], + tags: data[118] + ?.filter(Boolean) + .map((g: any) => (typeof g[0][0][0] === 'string' ? g[0] : g[0][0])) + .flat() + .map((t: any) => ({ id: t[2], name: t[0], path: t[1][4][2] })), + data_shared: parseDslSection(data[137][4], 0), + data_collected: parseDslSection(data[137][4], 1), + security_practices: parseSecurityPractices(data), + version: data[140][0][0]?.[0], + requires_android: data[140][1][1][0] + ? { version: data[140][1][1][0][0][1], api_level: data[140][1][1][0][0][0] } + : undefined, + updated_on: new Date(data[145][0][1][0] * 1000), + }; +}; + +/** + * Fetch the details/metadata of an app on the Google Play Store. + * + * This uses the Play Store's internal `batchexecute` endpoint with an RPC ID of `Ws7gDc`. + * + * @param request The parameters of which app to fetch the details of. + * @param options Language and country options. + * @returns The app details. + */ +export async function fetchAppDetails( + request: AppDetailsRequest | [AppDetailsRequest], + options: AppDetailsOptions +): Promise; +/** + * Same as {@link fetchAppDetails} but for fetching the details of multiple apps at once. The details are all fetched in + * a single API request. + * + * @see {@link fetchAppDetails} + * + * @param requests An array of fetch app details requests. + * @param options The options for _all_ requests. + * @returns An array of the app details, in the same order as the requests. + */ +export async function fetchAppDetails( + requests: AppDetailsRequest[], + options: AppDetailsOptions +): Promise; +export async function fetchAppDetails(requests: AppDetailsRequest | AppDetailsRequest[], options: AppDetailsOptions) { + const _requests = Array.isArray(requests) ? requests : [requests]; + const data = await batchExecute( + _requests.map((r) => fetchAppDetailsRequestPayload(r)), + { hl: options.language, gl: options.country } + ); + const res = data.map((d) => parseAppDetailsPayload(d, options)); + return _requests.length === 1 ? res[0] : res; +} diff --git a/src/index.ts b/src/index.ts index c53a645..91ab6d3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +export * from './endpoints/app-details'; export * from './endpoints/data-safety'; export * from './endpoints/search'; export * from './endpoints/top-charts'; @@ -5,5 +6,5 @@ export * from './endpoints/top-charts'; export * from './common/consts'; export { parseAppEntry } from './common/data-format'; -export type { AppMetadata, AppMetadataFull, AppMetadataProperty } from './common/data-format'; +export type { AppMetadata, AppMetadataFull, AppMetadataProperty, PermissionGroup } from './common/data-format'; export { batchExecute } from './common/requests'; From 0d86007a3732871e9bc5c6f81f868cb4626554e4 Mon Sep 17 00:00:00 2001 From: Benjamin Altpeter Date: Thu, 13 Jun 2024 12:25:00 +0200 Subject: [PATCH 3/3] Deprecate separate data safety label function --- README.md | 99 +++++++++++++++++++----------------- docs/README.md | 30 ++++++++--- src/endpoints/data-safety.ts | 16 ++++++ 3 files changed, 90 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index abbaf17..8e5f38c 100644 --- a/README.md +++ b/README.md @@ -132,9 +132,60 @@ Privacy policy URL: https://www.facebook.com/about/privacy/ ``` +### Search for apps + +The following example searches for the term "education": + +```ts +import { searchApps } from 'parse-play'; + +(async () => { + const searchResult = await searchApps({ searchTerm: 'education' }, { language: 'EN', country: 'DE' }); + console.dir(searchResult, { depth: null }); +})(); +``` + +

+Search apps response +The response looks like this: + +```ts +[ + { + position: 1, + app_id: 'de.easysoft.app.education', + icon_url: 'https://play-lh.googleusercontent.com/KZ19KJw8vrNy6gpRtyzLAGichfxShCU9L2kZdJbnKs6mrKblKqcWBvM5v9QdgEW-SGFR', + screenshot_urls: [ + 'https://play-lh.googleusercontent.com/Bh0sDOl-oOcOtmjKTIXL4eE_vIcDqntnrwqvoi9qylQjptmPnMtZyMkUxUh4JnC0hQ', + 'https://play-lh.googleusercontent.com/vlOZjzYHjRZEwBTWYVWxkWvXMEjtJGJ2tbJQJuNuB89wgXA-MVLM5MwaJOhRMdY7vA', + 'https://play-lh.googleusercontent.com/zEiBcIIuY6LP_BbNZQ5PxxilZMmkf6dOn2XsYCNET5GumPOktuhZPo438QiasoVv5g4l', + 'https://play-lh.googleusercontent.com/XP02HcK1hsyCUdrt9abKiy-KdF0ATB3W5jVVW5StHkxsmrlz22DFXfPbovZhyYjLiqI', + 'https://play-lh.googleusercontent.com/c3pmHB-DkHZ6j3g3LfmgWgdHlIK18jOt-2oFGkh9GTtQwY2aay7C9VO70XnZPX3qJas', + 'https://play-lh.googleusercontent.com/8Pj29QXYfhFlmPrMhNvgXdWeCj4X2n3vubIxoHGgd_w4h4MsE04TftKskB53BHp01XU', + 'https://play-lh.googleusercontent.com/mnyR06BYAQQ66ONQrYMluqALsdpKIV1_M2pKEIYurLlpEdRsE0Yu-AMsOmuPNYk-a8jP' + ], + name: 'easySoft App Education', + rating: 2.739726, + category: 'Business', + price: '€0.00', + buy_url: 'https://play.google.com/store/apps/details?id=de.easysoft.app.education&rdid=de.easysoft.app.education&feature=md&offerId', + store_path: '/store/apps/details?id=de.easysoft.app.education', + trailer_url: undefined, + description: 'With the easySoft App Education, […]', + developer: 'easySoft. GmbH', + downloads: '10,000+', + cover_image_url: 'https://play-lh.googleusercontent.com/mnyR06BYAQQ66ONQrYMluqALsdpKIV1_M2pKEIYurLlpEdRsE0Yu-AMsOmuPNYk-a8jP' + }, + // … +] +``` +
### Fetch an app's data safety labels +> [!WARNING] +> The separate function for fetching data safety labels is deprecated and will be removed in a future release. Instead, you can use [fetch an app's metadata](#fetch-app-details), which includes the data safety label. + The following example fetches the data safety labels for TikTok in English: ```ts @@ -189,54 +240,6 @@ The response looks like this: You can also request the labels for multiple apps at once by adding corresponding objects to the first parameter, they will all be fetched in a single API request. -### Search for apps - -The following example searches for the term "education": - -```ts -import { searchApps } from 'parse-play'; - -(async () => { - const searchResult = await searchApps({ searchTerm: 'education' }, { language: 'EN', country: 'DE' }); - console.dir(searchResult, { depth: null }); -})(); -``` - -
-Search apps response -The response looks like this: - -```ts -[ - { - position: 1, - app_id: 'de.easysoft.app.education', - icon_url: 'https://play-lh.googleusercontent.com/KZ19KJw8vrNy6gpRtyzLAGichfxShCU9L2kZdJbnKs6mrKblKqcWBvM5v9QdgEW-SGFR', - screenshot_urls: [ - 'https://play-lh.googleusercontent.com/Bh0sDOl-oOcOtmjKTIXL4eE_vIcDqntnrwqvoi9qylQjptmPnMtZyMkUxUh4JnC0hQ', - 'https://play-lh.googleusercontent.com/vlOZjzYHjRZEwBTWYVWxkWvXMEjtJGJ2tbJQJuNuB89wgXA-MVLM5MwaJOhRMdY7vA', - 'https://play-lh.googleusercontent.com/zEiBcIIuY6LP_BbNZQ5PxxilZMmkf6dOn2XsYCNET5GumPOktuhZPo438QiasoVv5g4l', - 'https://play-lh.googleusercontent.com/XP02HcK1hsyCUdrt9abKiy-KdF0ATB3W5jVVW5StHkxsmrlz22DFXfPbovZhyYjLiqI', - 'https://play-lh.googleusercontent.com/c3pmHB-DkHZ6j3g3LfmgWgdHlIK18jOt-2oFGkh9GTtQwY2aay7C9VO70XnZPX3qJas', - 'https://play-lh.googleusercontent.com/8Pj29QXYfhFlmPrMhNvgXdWeCj4X2n3vubIxoHGgd_w4h4MsE04TftKskB53BHp01XU', - 'https://play-lh.googleusercontent.com/mnyR06BYAQQ66ONQrYMluqALsdpKIV1_M2pKEIYurLlpEdRsE0Yu-AMsOmuPNYk-a8jP' - ], - name: 'easySoft App Education', - rating: 2.739726, - category: 'Business', - price: '€0.00', - buy_url: 'https://play.google.com/store/apps/details?id=de.easysoft.app.education&rdid=de.easysoft.app.education&feature=md&offerId', - store_path: '/store/apps/details?id=de.easysoft.app.education', - trailer_url: undefined, - description: 'With the easySoft App Education, […]', - developer: 'easySoft. GmbH', - downloads: '10,000+', - cover_image_url: 'https://play-lh.googleusercontent.com/mnyR06BYAQQ66ONQrYMluqALsdpKIV1_M2pKEIYurLlpEdRsE0Yu-AMsOmuPNYk-a8jP' - }, - // … -] -``` -
## License diff --git a/docs/README.md b/docs/README.md index 65333f6..1196e92 100644 --- a/docs/README.md +++ b/docs/README.md @@ -265,6 +265,10 @@ ___ An app's data safety label. +**`deprecated`** The separate function for fetching data safety labels is deprecated and will be removed in a future +release. Instead, you can use the [fetchAppDetails](README.md#fetchappdetails) function to fetch an app's metadata, which includes the +data safety label. + #### Type declaration | Name | Type | Description | @@ -285,7 +289,7 @@ An app's data safety label. #### Defined in -[endpoints/data-safety.ts:61](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L61) +[endpoints/data-safety.ts:69](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L69) ___ @@ -331,6 +335,10 @@ ___ Parameters for a single data safety label request. +**`deprecated`** The separate function for fetching data safety labels is deprecated and will be removed in a future +release. Instead, you can use the [fetchAppDetails](README.md#fetchappdetails) function to fetch an app's metadata, which includes the +data safety label. + #### Type declaration | Name | Type | Description | @@ -339,7 +347,7 @@ Parameters for a single data safety label request. #### Defined in -[endpoints/data-safety.ts:14](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L14) +[endpoints/data-safety.ts:18](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L18) ___ @@ -360,7 +368,7 @@ An app's declared security practices in a data safety label. #### Defined in -[endpoints/data-safety.ts:44](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L44) +[endpoints/data-safety.ts:48](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L48) ___ @@ -378,7 +386,7 @@ Parameters for all data safety label requests in a [fetchDataSafetyLabels](READM #### Defined in -[endpoints/data-safety.ts:23](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L23) +[endpoints/data-safety.ts:27](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L27) ___ @@ -399,7 +407,7 @@ An app's declaration for a single data type in a data safety label. #### Defined in -[endpoints/data-safety.ts:31](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L31) +[endpoints/data-safety.ts:35](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L35) ___ @@ -685,6 +693,10 @@ Fetch and parse the given app's data safety label from the Google Play Store. This uses the Play Store's internal `batchexecute` endpoint with an RPC ID of `Ws7gDc`. +**`deprecated`** The separate function for fetching data safety labels is deprecated and will be removed in a future +release. Instead, you can use the [fetchAppDetails](README.md#fetchappdetails) function to fetch an app's metadata, which includes the +data safety label. + #### Parameters | Name | Type | Description | @@ -700,13 +712,17 @@ The data safety label. #### Defined in -[endpoints/data-safety.ts:186](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L186) +[endpoints/data-safety.ts:198](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L198) ▸ **fetchDataSafetyLabels**(`requests`, `options`): `Promise`<([`DataSafetyLabel`](README.md#datasafetylabel) \| `undefined`)[]\> Same as [fetchDataSafetyLabels](README.md#fetchdatasafetylabels) but for fetching multiple data safety labels at once. The data safety labels are fetched in a single API request. +**`deprecated`** The separate function for fetching data safety labels is deprecated and will be removed in a future +release. Instead, you can use the [fetchAppDetails](README.md#fetchappdetails) function to fetch an app's metadata, which includes the +data safety label. + **`see`** [fetchDataSafetyLabels](README.md#fetchdatasafetylabels) #### Parameters @@ -724,7 +740,7 @@ An array of the data safety labels, in the same order as the requests. #### Defined in -[endpoints/data-safety.ts:200](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L200) +[endpoints/data-safety.ts:216](https://github.com/baltpeter/parse-play/blob/main/src/endpoints/data-safety.ts#L216) ___ diff --git a/src/endpoints/data-safety.ts b/src/endpoints/data-safety.ts index 24a8835..4f2afb6 100644 --- a/src/endpoints/data-safety.ts +++ b/src/endpoints/data-safety.ts @@ -10,6 +10,10 @@ import { assert } from '../common/assert'; /** * Parameters for a single data safety label request. + * + * @deprecated The separate function for fetching data safety labels is deprecated and will be removed in a future + * release. Instead, you can use the {@link fetchAppDetails} function to fetch an app's metadata, which includes the + * data safety label. */ export type DataSafetyLabelRequest = { /** @@ -57,6 +61,10 @@ export type DataSafetyLabelSecurityPracticesDeclarations = { }; /** * An app's data safety label. + * + * @deprecated The separate function for fetching data safety labels is deprecated and will be removed in a future + * release. Instead, you can use the {@link fetchAppDetails} function to fetch an app's metadata, which includes the + * data safety label. */ export type DataSafetyLabel = { /** The app's name. */ @@ -179,6 +187,10 @@ export const parseDataSafetyLabelPayload = (payload: any): DataSafetyLabel | und * * This uses the Play Store's internal `batchexecute` endpoint with an RPC ID of `Ws7gDc`. * + * @deprecated The separate function for fetching data safety labels is deprecated and will be removed in a future + * release. Instead, you can use the {@link fetchAppDetails} function to fetch an app's metadata, which includes the + * data safety label. + * * @param request The parameters for which app to fetch. * @param options Language options. * @returns The data safety label. @@ -191,6 +203,10 @@ export async function fetchDataSafetyLabels( * Same as {@link fetchDataSafetyLabels} but for fetching multiple data safety labels at once. The data safety labels * are fetched in a single API request. * + * @deprecated The separate function for fetching data safety labels is deprecated and will be removed in a future + * release. Instead, you can use the {@link fetchAppDetails} function to fetch an app's metadata, which includes the + * data safety label. + * * @see {@link fetchDataSafetyLabels} * * @param requests An array of data safety label requests.