Retrieves a list of apps that results of searching by the given term on app store or iTunes store - ios

I am using Node.js module to scrape application data from the iTunes/Mac App Store. My purpose is find then app ranking in search result for particular keyword search.
https://github.com/facundoolano/app-store-scraper
Till today, This module was giving proper app ranking on search results by keyword but suddenly It completely shows different search result than app store in iPhone or Mac.
Does anyone know that There are any changes from apple side?
Module uses below apple url to find the search result.
const BASE_URL = 'https://itunes.apple.com/WebObjects/MZStore.woa/wa/search?clientApplication=Software&media=software&term=';
Does any changes in above URL?

Here is the official api documentation for itunes search api - https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/
Here is an example below for the search you are trying to do:
curl -X GET \
'https://itunes.apple.com/search?clientApplication=Software&media=software&term=Misfits%20Emoji' \
-H 'Accept: */*' \
-H 'Accept-Encoding: gzip, deflate' \
-H 'Cache-Control: no-cache' \
-H 'Connection: keep-alive' \
-H 'Host: itunes.apple.com' \
-H 'Postman-Token: 06880666-0bb8-4b43-80ed-53e4300444cd,004c0b3c-dffd-4b3d-90e3-e36167e2a127' \
-H 'User-Agent: PostmanRuntime/7.20.1' \
-H 'cache-control: no-cache'
For the example, I have used this application to search https://apps.apple.com/us/app/misfits-emoji/id1172889389 and it is the first item returned in the response.
Response:
{
"resultCount": 1,
"results": [{
"artistViewUrl": "https://apps.apple.com/us/developer/fan-si-inc/id932552952?uo=4",
"artworkUrl60": "https://is2-ssl.mzstatic.com/image/thumb/Purple71/v4/f5/ac/81/f5ac813b-2c60-5739-023d-fca28a4b5fb5/source/60x60bb.jpg",
"artworkUrl100": "https://is2-ssl.mzstatic.com/image/thumb/Purple71/v4/f5/ac/81/f5ac813b-2c60-5739-023d-fca28a4b5fb5/source/100x100bb.jpg",
"screenshotUrls": ["https://is2-ssl.mzstatic.com/image/thumb/Purple71/v4/95/8b/4c/958b4c46-b4d8-f035-d723-ef764131a1f9/pr_source.png/392x696bb.png", "https://is3-ssl.mzstatic.com/image/thumb/Purple71/v4/ab/c0/a2/abc0a2f7-30c3-6f4f-2fa9-f2ccf4b15912/pr_source.png/392x696bb.png"],
"ipadScreenshotUrls": [],
"appletvScreenshotUrls": [],
"artworkUrl512": "https://is2-ssl.mzstatic.com/image/thumb/Purple71/v4/f5/ac/81/f5ac813b-2c60-5739-023d-fca28a4b5fb5/source/512x512bb.jpg",
"isGameCenterEnabled": false,
"advisories": [],
"supportedDevices": ["iPad2Wifi-iPad2Wifi", "iPad23G-iPad23G", "iPhone4S-iPhone4S", "iPadThirdGen-iPadThirdGen", "iPadThirdGen4G-iPadThirdGen4G", "iPhone5-iPhone5", "iPodTouchFifthGen-iPodTouchFifthGen", "iPadFourthGen-iPadFourthGen", "iPadFourthGen4G-iPadFourthGen4G", "iPadMini-iPadMini", "iPadMini4G-iPadMini4G", "iPhone5c-iPhone5c", "iPhone5s-iPhone5s", "iPadAir-iPadAir", "iPadAirCellular-iPadAirCellular", "iPadMiniRetina-iPadMiniRetina", "iPadMiniRetinaCellular-iPadMiniRetinaCellular", "iPhone6-iPhone6", "iPhone6Plus-iPhone6Plus", "iPadAir2-iPadAir2", "iPadAir2Cellular-iPadAir2Cellular", "iPadMini3-iPadMini3", "iPadMini3Cellular-iPadMini3Cellular", "iPodTouchSixthGen-iPodTouchSixthGen", "iPhone6s-iPhone6s", "iPhone6sPlus-iPhone6sPlus", "iPadMini4-iPadMini4", "iPadMini4Cellular-iPadMini4Cellular", "iPadPro-iPadPro", "iPadProCellular-iPadProCellular", "iPadPro97-iPadPro97", "iPadPro97Cellular-iPadPro97Cellular", "iPhoneSE-iPhoneSE", "iPhone7-iPhone7", "iPhone7Plus-iPhone7Plus", "iPad611-iPad611", "iPad612-iPad612", "iPad71-iPad71", "iPad72-iPad72", "iPad73-iPad73", "iPad74-iPad74", "iPhone8-iPhone8", "iPhone8Plus-iPhone8Plus", "iPhoneX-iPhoneX", "iPad75-iPad75", "iPad76-iPad76", "iPhoneXS-iPhoneXS", "iPhoneXSMax-iPhoneXSMax", "iPhoneXR-iPhoneXR", "iPad812-iPad812", "iPad834-iPad834", "iPad856-iPad856", "iPad878-iPad878", "iPadMini5-iPadMini5", "iPadMini5Cellular-iPadMini5Cellular", "iPadAir3-iPadAir3", "iPadAir3Cellular-iPadAir3Cellular", "iPodTouchSeventhGen-iPodTouchSeventhGen", "iPhone11-iPhone11", "iPhone11Pro-iPhone11Pro", "iPadSeventhGen-iPadSeventhGen", "iPadSeventhGenCellular-iPadSeventhGenCellular", "iPhone11ProMax-iPhone11ProMax"],
"kind": "software",
"features": [],
"contentAdvisoryRating": "4+",
"trackCensoredName": "Misfits Emoji",
"languageCodesISO2A": ["EN"],
"fileSizeBytes": "32731136",
"sellerUrl": "http://fan.si",
"averageUserRatingForCurrentVersion": 3.5,
"userRatingCountForCurrentVersion": 4,
"trackViewUrl": "https://apps.apple.com/us/app/misfits-emoji/id1172889389?uo=4",
"trackContentRating": "4+",
"currentVersionReleaseDate": "2016-11-21T17:30:21Z",
"releaseNotes": "Updated icons.",
"isVppDeviceBasedLicensingEnabled": true,
"formattedPrice": "$0.99",
"trackId": 1172889389,
"trackName": "Misfits Emoji",
"primaryGenreName": "Entertainment",
"genreIds": ["6016", "6002"],
"primaryGenreId": 6016,
"sellerName": "Fansi Inc",
"releaseDate": "2016-11-17T18:12:35Z",
"minimumOsVersion": "8.0",
"currency": "USD",
"version": "1.0.1",
"wrapperType": "software",
"artistId": 932552952,
"artistName": "Fan.si Inc.",
"genres": ["Entertainment", "Utilities"],
"price": 0.99,
"description": "Social Club Misfits present this very rare emoji collection featuring dozens of new emoticon pictures and exclusive gifs. Satisfy all you communication needs with pizza, pugs, pineapples, and so much more!",
"bundleId": "si.fan.socialmisfits-emoji"
}]
}

Related

How do I generate short lived service account access tokens in Keycloak

I am generating an access token for a service account as described in the Keycloak docs. As an example:
curl --location --request POST 'http://my.domain/auth/realms/aRealm/protocol/openid-connect/token' \
--header 'Authorization: Basic YXBcnZlcjpNlpuMwRWk1Y02ZFRE15vVp2YUdHJibmNalVvMFmxzVNQVg==' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials'
I do get a token back, but it expires in 30 mins, just as I have configured them in my realm settings, for normal users usage.
But for the use as a service account, I do not want such a long lived token. Even 10-15 secs should be enough to generate the token and then immediately make a request to the server.
I have seen a similar feature in Google auth docs (https://cloud.google.com/iam/docs/create-short-lived-credentials-direct#rest_2) by passing a lifetime param, where you ask for tokens with a specific lifetime.
Keycloak docs (https://www.keycloak.org/docs/latest/server_admin/#_service_accounts) do not mention anything about shorter living tokens. Have I missed a section that mentions something like that? Or is there an alternative way to create short lived tokens?
EDIT:
The Keycloak client created is used by users to logging to the system.
For that usecase, I want their token to be valid for 30mins before
they have to be refreshed again. But for service accounts usage (when
I want my distributed code to make an API request to my own servers) I
want these tokens to have a very short lived lifetime (enough to just
make the API call).
Looking at your requirements, I am under the impression that you are mixing concerns and different authentication flows.
The Keycloak client created is used by users to logging to the system.
For that use-case, I want their token to be valid for 30mins before
they have to be refreshed again.
Without knowing more details about your application, this use-case is typically solved by using a public client. That client would typically use Standard flow, Implicit Flow or Direct Access Grants enabled (not recommended). If possible preferably using Standard flow. Those flows can be used with a confidential client as well -- as long as the secret is stored safely in the backend -- however, it is not very natural, and unnecessary.
The Service Accounts flow (i.e., client credentials in OAuth2 terminology) forces the use of a confidential client. Hence, the use of the client_secret parameter in the request.
So there we start to see conflicts between best practices and the requirements that you have stated.
IMO the cleanest solution is to have a public client that is used by your users to authenticate normally, using the appropriate flow for that. In that client, you would set the 30 minutes as the token lifespan. And for:
But for service accounts usage (when I want my distributed code to
make an API request to my own servers) I want these tokens to have a
very short lived lifetime (enough to just make the API call).
For that use-case, use a different client, a confidential one, with the Service Account flow enabled. Go to the client Advanced Settings menu and set the Access Token Lifespan to 1 minute for example. This value will override the value set at the Realm Level (actually, in practice in can be more complicated than that). The smallest time granularity that you can choose is minutes, for seconds you would have to use the approach showcased by #Bench Vue.
You can change a "access.token.lifespan" for specific client by PUT HTTP call.
PUT call http://my.domain/auth/realms/aRealm/clients/client-id
The Bearer token needs to get by admin with realms-manage role (or master admin, manage-clients)
This is example
my.domain : http://localhost:8180
aRealm : test
client id : a6654164-d2f5-451d-8f8c-25f00f29eec5
"access.token.lifespan": 15
curl --location --request PUT 'http://localhost:8180/auth/admin/realms/test/clients/a6654164-d2f5-451d-8f8c-25f00f29eec5' \
--header 'Accept: application/json, text/plain, */*' \
--header 'Accept-Encoding: gzip, deflate, br' \
--header 'Accept-Language: en-US,en;q=0.9' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJSQjNmSzRrSC1EWmxjLWQ0ZjB3b3JJM3c5d0ZEY0RidFZTS0xmTlJlcVVRIn0.eyJleHAiOjE2NTgwMDEzNjEsImlhdCI6MTY1Nzk2NTM2MSwianRpIjoiNGU4NzRhZWMtMGVjZC00MjEyLThiYmUtNGUxODM2OTRlMGM5IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MTgwL2F1dGgvcmVhbG1zL21hc3RlciIsInN1YiI6ImFiOWNkMjc3LWFhYWEtNGI1My1iMzdlLTBhNzJiODZmZWI0OSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLWNsaSIsInNlc3Npb25fc3RhdGUiOiI2YjU5MTNlMC1lOGFkLTQzM2MtOTkyOC1lNTBjMjQ0MGNmN2MiLCJhY3IiOiIxIiwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiNmI1OTEzZTAtZThhZC00MzNjLTk5MjgtZTUwYzI0NDBjZjdjIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiJ9.LhOegp1UbzaWOrNWb5aKhIzatIvNNg0JPbZeKqnumJ2pdZKL3xTei2Uo6GPKg_YRp9G-YAuCVrQFNWbhP1fDBrDbAEvZHT0Ho4OmlysISFIGP3i9Hr1x3uILLPxGks0iiP7RzCiSubtIwNZRl3nro5bRXkEx24F1drD4hWKW95Z9VAFPkSUW3Urk5Hdgm991pUwUdQCPiyyNj7RL2uiHEsSEoypoT2CviZ518dElmnNFmPafg5K_j39atHX5DxwxEvT5cTfgD6Sg3CmrJupE3CfY31N8OfkmBCA__3mOx31btncK4uG9EsYujxSeHxEZhPV0gUwCx7ZykYkfKhl0OQ' \
--header 'Content-Type: application/json' \
--data-raw '{
"id": "a6654164-d2f5-451d-8f8c-25f00f29eec5",
"clientId": "my-test-client",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": [
"http://localhost:8180/test/*"
],
"webOrigins": [],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": true,
"publicClient": false,
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"access.token.lifespan": 15,
"saml.multivalued.roles": "false",
"saml.force.post.binding": "false",
"frontchannel.logout.session.required": "false",
"oauth2.device.authorization.grant.enabled": "false",
"backchannel.logout.revoke.offline.tokens": "false",
"saml.server.signature.keyinfo.ext": "false",
"use.refresh.tokens": "true",
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"client_credentials.use_refresh_token": "false",
"saml.client.signature": "false",
"require.pushed.authorization.requests": "false",
"saml.allow.ecp.flow": "false",
"saml.assertion.signature": "false",
"id.token.as.detached.signature": "false",
"client.secret.creation.time": "1657583810",
"saml.encrypt": "false",
"saml.server.signature": "false",
"exclude.session.state.from.auth.response": "false",
"saml.artifact.binding": "false",
"saml_force_name_id_format": "false",
"tls.client.certificate.bound.access.tokens": "false",
"acr.loa.map": "{}",
"saml.authnstatement": "false",
"display.on.consent.screen": "false",
"token.response.type.bearer.lower-case": "false",
"saml.onetimeuse.condition": "false",
"request.uris": null,
"frontchannel.logout.url": null,
"default.acr.values": null,
"oauth2.device.polling.interval": null
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"protocolMappers": [
{
"id": "fc55831d-9f85-4db5-8340-38e246fcfdf0",
"name": "Client ID",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "clientId",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientId",
"jsonType.label": "String"
}
},
{
"id": "8e7e2a2c-90be-41a0-8a75-87099184a4a4",
"name": "Client IP Address",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "clientAddress",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientAddress",
"jsonType.label": "String"
}
},
{
"id": "bee09b85-a3db-471a-aa51-9f1504df0a6f",
"name": "Client Host",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "clientHost",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientHost",
"jsonType.label": "String"
}
}
],
"defaultClientScopes": [
"web-origins",
"acr",
"roles",
"profile",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
],
"access": {
"view": true,
"configure": true,
"manage": true
}
}'
you can check it's value by GET HTTP call
The "expires_in":15 tell how much the lifetime
In here, that value is 15 seconds. It should be match as I sent by PUT call.
curl --location --request GET 'http://localhost:8180/auth/admin/realms/test/clients/a6654164-d2f5-451d-8f8c-25f00f29eec5' \
> --header 'Accept: application/json, text/plain, */*' \
> --header 'Accept-Encoding: gzip, deflate, br' \
> --header 'Accept-Language: en-US,en;q=0.9' \
> --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMWkdvVDZ6WFdtMU5BNmh2WXhlbUFrVmFaQWJodnlmWlo4Q2JqRWJ0U3RrIn0.eyJleHAiOjE2NTc5Nzg4MTMsImlhdCI6MTY1Nzk2ODAxMywianRpIjoiOGQ5YjhmYTYtYTk3OS00NGE1LWJhN2YtYTg2M2Q1NjU3NGIwIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MTgwL2F1dGgvcmVhbG1zL3Rlc3QiLCJzdWIiOiI1MzkxMDBmMS01ZmEzLTQxMTUtYTZlYi0zOGNhY2NjM2E3NGEiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiNGRmNjQzMDgtYzg5NC00NGYxLTk4YjctYzZmNDg3YTYzYWYzIiwiYWNyIjoiMSIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6IjRkZjY0MzA4LWM4OTQtNDRmMS05OGI3LWM2ZjQ4N2E2M2FmMyIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6ImZpcnN0IGxhc3QiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyMSIsImdpdmVuX25hbWUiOiJmaXJzdCIsImZhbWlseV9uYW1lIjoibGFzdCIsImVtYWlsIjoidXNlcjFAdGVzdC5jb20ifQ.hjWKdTzSTjJpfW2hRqDOTML_5Uo2s5glAEDzVE567huW0LdYNtElvoi_wdLXNsYwCcVgW8juHsRHdoTlljT91zoZeY9VFg1YUJCF6k6uEkZXgSKvhm87jiyPMQa1Ex_b7wmOza0SFrhz3--PVMKgV6EJ2R1GtbwXfwQeLxsKslaIvjgAIliHNlIkxOA9bTnyFwOpvv93km1E9S3KjExcN1jq-KptslSa8lY35ExyCIatWbu_g9nrWlcQGg14qM8VfcJhrmL4ZDb0uaBk8HbZ52RNU8qu2pB584TsZ5iR29afRF5jTY-3pRiKUnTTOnQDrKLkqPvLhVwJPiRABcClDw'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2910 100 2910 0 0 265k 0 --:--:-- --:--:-- --:--:-- 284k{"id":"a6654164-d2f5-451d-8f8c-25f00f29eec5","clientId":"my-test-client","surrogateAuthRequired":false,"enabled":true,"alwaysDisplayInConsole":false,"clientAuthenticatorType":"client-secret","redirectUris":["http://localhost:8180/test/*"],"webOrigins":[],"notBefore":0,"bearerOnly":false,"consentRequired":false,"standardFlowEnabled":true,"implicitFlowEnabled":false,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":true,"publicClient":false,"frontchannelLogout":false,"protocol":"openid-connect","attributes":{"access.token.lifespan":"15","saml.multivalued.roles":"false","saml.force.post.binding":"false","frontchannel.logout.session.required":"false","oauth2.device.authorization.grant.enabled":"false","backchannel.logout.revoke.offline.tokens":"false","saml.server.signature.keyinfo.ext":"false","use.refresh.tokens":"true","oidc.ciba.grant.enabled":"false","backchannel.logout.session.required":"true","client_credentials.use_refresh_token":"false","saml.client.signature":"false","require.pushed.authorization.requests":"false","saml.allow.ecp.flow":"false","saml.assertion.signature":"false","id.token.as.detached.signature":"false","client.secret.creation.time":"1657583810","saml.encrypt":"false","saml.server.signature":"false","exclude.session.state.from.auth.response":"false","saml.artifact.binding":"false","saml_force_name_id_format":"false","tls.client.certificate.bound.access.tokens":"false","acr.loa.map":"{}","saml.authnstatement":"false","display.on.consent.screen":"false","token.response.type.bearer.lower-case":"false","saml.onetimeuse.condition":"false"},"authenticationFlowBindingOverrides":{},"fullScopeAllowed":true,"nodeReRegistrationTimeout":-1,"protocolMappers":[{"id":"fc55831d-9f85-4db5-8340-38e246fcfdf0","name":"Client ID","protocol":"openid-connect","protocolMapper":"oidc-usersessionmodel-note-mapper","consentRequired":false,"config":{"user.session.note":"clientId","id.token.claim":"true","access.token.claim":"true","claim.name":"clientId","jsonType.label":"String"}},{"id":"8e7e2a2c-90be-41a0-8a75-87099184a4a4","name":"Client IP Address","protocol":"openid-connect","protocolMapper":"oidc-usersessionmodel-note-mapper","consentRequired":false,"config":{"user.session.note":"clientAddress","id.token.claim":"true","access.token.claim":"true","claim.name":"clientAddress","jsonType.label":"String"}},{"id":"bee09b85-a3db-471a-aa51-9f1504df0a6f","name":"Client Host","protocol":"openid-connect","protocolMapper":"oidc-usersessionmodel-note-mapper","consentRequired":false,"config":{"user.session.note":"clientHost","id.token.claim":"true","access.token.claim":"true","claim.name":"clientHost","jsonType.label":"String"}}],"defaultClientScopes":["web-origins","acr","roles","profile","email"],"optionalClientScopes":["address","phone","offline_access","microprofile-jwt"],"access":{"view":true,"configure":true,"manage":true}}

Getting names from geo_target_constant in a Ad report

I am using ads API for getting the spends based on state names of USA.
The below query gives segments.geo_target_state will be returned as an ID instead of name
How can I combine this query with
SELECT
geo_target_constant.name,
geo_target_constant.canonical_name
FROM geo_target_constant
WHERE geo_target_constant.id = <<OBTAINED ID FROM THE BELOW QUERY>>
curl "https://googleads.googleapis.com/v10/customers/${CUSTOMER_ID}/googleAds:searchStream" \
--header "Content-Type: application/json" \
--header "developer-token: ${DEVELOPER_TOKEN}" \
--header "login-customer-id: ${MANAGER_CUSTOMER_ID}" \
--header "Authorization: Bearer ${OAUTH2_ACCESS_TOKEN}" \
--data '{
"query": "
SELECT
campaign.name,
segments.geo_target_state,
metrics.cost_micros
FROM geographic_view
WHERE
geographic_view.location_type = LOCATION_OF_PRESENCE
AND segments.date BETWEEN 20220101 AND 20220430
"
}'
That is geoTargetConstants/21136 must be decoded to
"name": "New Jersey",
"results": [
{
"campaign": {
"resourceName": "customers/1234/campaigns/1234",
"name": "Display - macines - Leads Display Campaign Test - AA"
},
"metrics": {
"costMicros": "66664821"
},
"segments": {
"geoTargetState": "**geoTargetConstants/21136**"
},
"geographicView": {
"resourceName": "customers/6383148790/geographicViews/2840~LOCATION_OF_PRESENCE"
}
},
If you want to use the API to lookup the geo target constant name you will have to issue another search request using the query you provided.
This is likely okay in one off scenarios but breaks down quickly if you need to retrieve multiple geo target constants.
What I have do to resolve this is pull the list of geo targets, see: https://developers.google.com/google-ads/api/reference/data/geotargets, and store them locally in a database. Then I can pull the name from a must faster source.

Empty subscriber list while channel subscribers count isn't zero

Summary
I'm trying to get a list of channel subscribers, but sometimes I get empty response for specific channels with subscriber count > 0.
Details
Following this YouTube Data API documentation,
I'm trying to receive a list of the channel subscriptions, however did received empty list. This is not reproducible for all channels, but it is this channel -- UCWjFX5qjtUCF4Xr-Z4q0h9w.
I'm using this command to get the current subscribers:
curl \
'https://youtube.googleapis.com/youtube/v3/channels?part=snippet%2CcontentDetails%2Cstatistics&key=XXXXX&mine=true' \
--header 'Authorization: Bearer XXXXXX' \
--header 'Accept: application/json' \
--compressed
The API response is:
{
"kind": "youtube#channelListResponse",
"etag": "XXXXpU",
"pageInfo": {
"totalResults": 1,
"resultsPerPage": 5
},
"items": [
{
"kind": "youtube#channel",
"etag": "XXXXXXXaU",
"id": "UCWjFX5qjtUCF4Xr-Z4q0h9w",
"snippet": {....},
"contentDetails": {..},
"statistics": {
...
"subscriberCount": "5",
}
}
]
}
and then try to list the subscriptions:
curl \
'https://youtube.googleapis.com/youtube/v3/subscriptions?part=snippet,id,subscriberSnippet,contentDetails&key=XXXXXX&mine=true' \
--header 'Authorization: Bearer XXXXXX' \
--header 'Accept: application/json' \
--compressed
with the JSON response:
{
"kind": "youtube#SubscriptionListResponse",
"etag": "XXXXX3i0vIY",
"pageInfo": {
"totalResults": 0,
"resultsPerPage": 5
},
"items": []
}
Update the privacy settings in channel settings.
That happens because when invoking the Subscriptions.list with the request parameter mine set to true, you'll obtain a number of channel to which you -- that channel owner -- have subscribed:
mine (boolean)
This parameter can only be used in a properly authorized request. Set this parameter's value to true to retrieve a feed of the authenticated user's subscriptions.
But a channel's statistics.subscriberCount is counting the number of other YouTube users that subscribed to the respective channel:
statistics.subscriberCount (unsigned long)
The number of subscribers that the channel has. This value is rounded down to three significant figures. Please see the Revision History or the YouTube Help Center for more details about how subscriber counts are rounded.
For the other way around -- i.e. for a list of channels that are subscribers of your channel -- there are two other request parameters: myRecentSubscribers and mySubscribers.

Cumulocity - Send Measurement/Alarm/Event using external ID via HTTP

I've been recently exploring Cumulocity and managed to use the external ID to send data (measurements/alarms/events) via MQTT. Its well documented and pretty straight forward.
But I cant find how to send data (measurement/alarm/event) using ExternalID instead of source.
For example, here is how POST of a measurement looks like if you know ClientID of device:
curl -X POST \
https://myTenant.cumulocity.com/measurement/measurements \
-H 'Accept: application/vnd.com.nsn.cumulocity.measurement+json' \
-H 'Authorization: Basic mytoken' \
-H 'Content-Type: application/json' \
-d '{
"c8y_TemperatureMeasurement": {
"T": {
"value": 25,
"unit": "C" }
},
"time":"2019-03-07T10:03:14.000+11:00",
"source": {
"id":"1234567" },
"type": "c8y_TemperatureMeasurement"
}'
Is there a way to replace that "source": {"id":"1234567" }, with external ID?
What would the request look like?
As of today, this is not possible:
Instead you have to first convert the externalID to the source id once (e.g. when the device is booted its done as first actions). Afterwards send all requests (e.g. POSTs to create measurements/alarms/events) using this retrieved sourceID.
This is also described in the Device SDK for HTTP here: https://cumulocity.com/guides/device-sdk/rest#step-1-check-if-the-device-is-already-registered .
Thanks for the good feedback on the documentation!

YouTube artist charts api?

Does YouTube expose it's artist charts via api or is there a way to get the charts data using the youtube api?
I'm talking about the charts data here https://artists.youtube.com/charts/videos
I don't think it's possible using the official Youtube API, if we look at https://artists.youtube.com/charts/videos, it uses YouTube Internal API (InnerTube) with a specific API key (registered to use youtubei API which is not available to developers)
Of course it's a hack just FYI
The API key has https://artists.youtube.com configured as referer, adding the custom header: x-referer:https://artists.youtube.com make it works :
curl -H 'Content-Type: application/json' \
-H "x-referer:https://artists.youtube.com" \
"https://content.googleapis.com/youtubei/v1/browse?alt=json&key=AIzaSyCzEW7JUJdSql0-2V4tHUb6laYm4iAE_dM" \
-d '{
"context": {
"client": {
"clientName": "WEB_MUSIC_ANALYTICS",
"clientVersion": "0.2",
"theme": "MUSIC",
"hl": "en",
"gl": "FR",
"experimentIds": []
},
"capabilities": {
},
"request": {
"internalExperimentFlags": []
}
},
"browseId": "FEmusic_analytics",
"query": "chart_params_type=WEEK&perspective=CHART&flags=viral_video_chart&selected_chart=VIRAL_VIDEOS"
}'
If it doesn't work, get the API key from the network log of https://artists.youtube.com
In the query field, you can modify the selected_chart parameter :
all video :
selected_chart=VIDEOS
viral videos chart :
selected_chart=VIRAL_VIDEOS
artists :
selected_chart=ARTISTS
tracks :
selected_chart=TRACKS

Resources