ã ãã¸ã¹ããã¼ã ããã°ãªã¬ã¼3æ¥ç®ã
ã¨ã³ã¸ãã¢ãªã³ã°ã°ã«ã¼ã ãã¸ã¹ããã¼ã æå±ã®èè°·(@_a_akira)ã§ãã æè¿ã¯ãã¸ã«ã«ã¹ãã¼ã診ç(以éãã¸ã¹ã)ã¨ããå»çæ©é¢åãã«äºç´ããã£ãã·ã¥ã¬ã¹æ±ºæ¸ããªã³ã©ã¤ã³è¨ºçãå°å ¥ã»å©ç¨ã§ããã¢ããª(Flutter)ãã¡ã¤ã³ã§ä½æãã¤ã¤ãããã¯ã¨ã³ãã®APIãWebããã³ããæ©è½ãã¨ã«å®è£ ãã¦ãã¾ãã
çªç¶ã§ãããã¢ããªã®ã¬ãã¥ã¼ã¯æ®æ®µã©ã®ããã«ç¢ºèªãã¦ãã¾ããï¼
ã¬ãã¥ã¼ãå®æçã«ç¢ºèªãããã¨ã«ãã£ã¦ãã¦ã¼ã¶ã¼ã®åã³ãå®æããããäºæãã¦ããªããã°ã®çºè¦ã«ç¹ãããã¾ãã
ãµã¼ãã¹ãéçºã»éå¶ããå´ã«ã¨ã£ã¦ã¯ããããå¹ççã«è¡ããã¨ã¯éè¦ã¨è¨ããã§ãããã
ä¾ãã°ãGoogle Playã¯ã¡ã¼ã«ã§ã®ã¬ãã¥ã¼éç¥ã¯ããã¾ãããApp Storeã§ã¯ãµãã¼ããã¦ãããä»ãµã¼ãã¹ã¸ã®éç¥ã¯ã§ãã¾ããã
App Storeã®ã¬ãã¥ã¼ã«ã¯RSSãããã®ã§ãRSSãã£ã¼ãã§éç¥ã§ãã¾ããããã®ã¾ã¾ã ã¨ä»»æã®å½¢å¼ã«æ´å½¢ãããªãåé¡ãããã¾ãã
å¤é¨ãã¼ã«ãå©ç¨ããå ´åã§ãå©ç¨æ°ã«å¶éããããä¼ç¤¾ã§åºãã¦ããè¤æ°ã¢ããªã®Android, iOS両OSã®ã¬ãã¥ã¼ãã³ã¹ãããªãã¹ããããã«Slackã¸éç¥ãããããé¸æè¢ããã¯å¤ãã¾ããã
ããã§ãä»åã¯Google Playå´ã¯APIçµç±, App Storeå´ã¯RSSãå©ç¨ãã¦Google App Script(以ä¸GAS)ã使ã£ã¦ã¢ããªã®ã¬ãã¥ã¼ãSlackã«éç¥ããæ¹æ³ãç´¹ä»ãã¾ãã
ç®æ¬¡
- ç®æ¬¡
- ã¬ãã¥ã¼åå¾ã®æºå
- GASããSlackã«ã¬ãã¥ã¼ãéä¿¡ãã
- ã¾ã¨ã
- ãã¾ã
- We are hiring!
ã¬ãã¥ã¼åå¾ã®æºå
App Store
App Storeã®ã¬ãã¥ã¼åå¾æ¹æ³ã¯
ã®2ã¤ãç¨æããã¦ãã¾ãã
APIã«ããæ¹æ³ã¯ç§å¯éµãGASå´ã§èªã¿è¾¼ãå¿ è¦ããããããã¬ãã¥ã¼åå¾ã®ã¿ã§äºè¶³ããä»åã¯RSSã«ããåå¾ãé¸æãã¾ããã
RSSã§åå¾ããå ´åã¯ãã®URLã§åå¾ã§ãã¾ãã
https://itunes.apple.com/[country code]/rss/customerreviews/id=[apple id]
ã¬ãã¥ã¼ã50件ãè¶ ããå ´åã®pageãªãã·ã§ã³ãsortByãªãã·ã§ã³ãªã©ãç¨æããã¦ãã¾ãã ä»åã¯1æéãã¨ã«åå¾ããããã«ãããã1æéã§50件ã¬ãã¥ã¼ãå¢ããªãã¨ä»®å®ãã¦ããã¼ã¸æ°ã¯æå®ãããææ°é ã«ã½ã¼ããã¾ãã
Apple IDããããã°ãã©ã®ã¢ããªã§ããã®å½¢å¼ã§åå¾ã§ãã¾ãã AppIe IDã¯App Store Connectããè¦ãã¾ãã
- ãã¸ã¹ãã¢ããªã®ã¬ãã¥ã¼ https://itunes.apple.com/jp/rss/customerreviews/id=1563102530/sortBy=mostRecent/xml
Google Play
Google Playå´ã¯Google Play Developer APIã使ã£ã¦ã¬ãã¥ã¼ãåå¾ãã¾ãã
å ·ä½çã«ã¯Google Playã¨ç´ä»ããGCPã®ããã¸ã§ã¯ãã®OAuthã¯ã©ã¤ã¢ã³ããã
- Refresh Token
- ã¯ã©ã¤ã¢ã³ãID
- ã¯ã©ã¤ã¢ã³ãã·ã¼ã¯ã¬ãã
ã使ã£ã¦åå¾ããAccess Tokenã使ã£ã¦ã¢ããªã®ã¬ãã¥ã¼ãåå¾ãã¾ãã
Access Tokenãåå¾ããã«ã¯ãGoogle Play Developer APIã使ãããã®æºåãå¿
è¦ã«ãªãã¾ãã
æé ãå¤ãé·ããªã£ã¦ãã¾ã£ãããã詳ããåå¾æ¹æ³ã¯å人ããã°ã«æ¸ãã¾ããã®ã§ãã¡ããã覧ä¸ããã
ä¸è¨ã®æ¹æ³ã§Access Tokenãåå¾ããã
https://www.googleapis.com/androidpublisher/v3/applications/[package_name]/reviews?access_token=[access token]
ã®å½¢å¼ã§ã¬ãã¥ã¼ãåå¾ã§ãã¾ãã
Review APIã®è©³ããä»æ§ã¯ä»¥ä¸ã®ãã¼ã¸ãè¦ã¦ä¸ããã åå¾ã®ä»ã«ãã¬ãã¥ã¼ã«è¿ä¿¡ãªã©ãAPIçµç±ã§ã§ãã¾ãããä»åã¯åå¾APIã®ã¿ä½¿ãã¾ãã
GASããSlackã«ã¬ãã¥ã¼ãéä¿¡ãã
RSS, APIããã¬ãã¥ã¼ã®åå¾ãã§ããã®ã§GASããSlackã«éãã³ã¼ãã解説ãã¦ããã¾ãã
ä»åã¯å¼ç¤¾ã§ãªãªã¼ã¹ãã¦ããè¤æ°ã¢ããªã®ã¬ãã¥ã¼ãSlackã«éç¥ãããããã1ãµã¼ãã¹ã§ã¯ãªãè¤æ°ãµã¼ãã¹ã§ä½¿ããããæ±ç¨çã«ä½ã£ã¦ãã¾ãã
åå¾æ¥æãä¿åãã
Google Play Review APIã¯åå¾æ¥æã®æå®ãã§ãããApp Storeã¯RSSã«ããåå¾ãé¸æããããã両ãã©ãããã©ã¼ã ã¨ãã¬ãã¥ã¼ã®æçµåå¾æ¥æã¯æå®ã§ãã¾ããã GASã¯æéããã¯ã§å®æå®è¡ããããããåå¾æ¥æã®ç¶æ ãæããªãå ´åã¯æ¯ååãéç¥ãSlackã«éã£ã¦ãã¾ãã¾ãã ããã§ãåå¾ããã¬ãã¥ã¼ã®æå¾ã®æ¥ä»ãGASã®Script Propertyã«ä¿åãã¦ããããã§ã«éä¿¡ããã¢ããªã®ã¬ãã¥ã¼ã¯Slackã«éç¥ããªãä»çµã¿ã«ãã¾ããã
function formatDateJST(date) { return Utilities.formatDate(date, "JST", "yyyy/MM/dd HH:mm:ss"); } function setLastReviewDate(appID, date) { PropertiesService.getScriptProperties().setProperty(appID, date); } function getLastReviewDate(appID) { return PropertiesService.getScriptProperties().getProperty(appID); }
ã¢ãã«å®ç¾©
- ã¢ããªã®æ å ±
ãã®ã¯ã©ã¹ã¯APIçµç±ã§åå¾ããããã®ã¢ããªæ
å ±ã表ãã¯ã©ã¹ã«ãªãã¾ãã
ã¬ãã¥ã¼ã¯ã¾ã¨ãã¦1ã¤ã®ãã£ã³ãã«ã«éã£ã¦ããã®ã§ãããä¸é¨ã®ã¢ããªã§è¤æ°ãã£ã³ãã«ã«éä¿¡ãããè¦æããã£ããã extraSlackPostUrl
ã¨ããã®ã追å ãã¦ä»ã®ãã£ã³ãã«ã«ãéããããã«ãã¾ããã
class AppInfo { constructor(androidId, appleId, name, icon, googlePlayAccount, extraSlackPostUrl) { this.androidId = androidId; this.appleId = appleId; this.name = name; this.icon = icon; this.googlePlayAccount = googlePlayAccount; this.extraSlackPostUrl = extraSlackPostUrl; } }
- ã¬ãã¥ã¼æ å ±
const Platform = { ANDROID: ":android:", IOS: ":appleinc:", }; class Review { constructor(updatedAt, author, rating, title, message) { this.updatedAt = updatedAt; this.author = author; this.rating = rating; this.title = title; this.message = message; } }
- ã¢ã«ã¦ã³ã
å¼ç¤¾ã§ã¯ã¢ããªãåºãã¦ããã°ã«ã¼ãä¼ç¤¾ãã¨ã«ãããããã¼ã¢ã«ã¦ã³ããç°ãªãããããã®ããã«å®ç¾©ãã¾ããã 1社ã®ã¿ã®éç¨ã®å ´åã¯ããã¯å®ç¾©ããªãã¦ã大ä¸å¤«ã§ãã
const GooglePlayAccount = { M3: 1, Digikar: 2, }
App Store
App Store ã¬ãã¥ã¼RSSã®JSONããã¼ã¹ãã¦è¿ãé¢æ°ã§ãã
RSSåå¾URLæå¾ã® xml
ã json
ã«ããã¨ãJSONå½¢å¼ã§è¿ã£ã¦ãã¾ãã
Script Propertyã«ä¿åãããæ¥ä»ããæ°ããã¬ãã¥ã¼ãããã°ã¬ãã¥ã¼ã®ãªã¹ãã«è©°ãã¦è¿å´ãã¾ãã
function getAppleReviews(appInfo) { const feedURL = "https://itunes.apple.com/jp/rss/customerreviews/id=" + appInfo.appleId + "/sortBy=mostRecent/json"; const response = UrlFetchApp.fetch(feedURL); const entries = JSON.parse(response.getContentText()).feed.entry; if(entries == undefined) { return []; } const reviews = []; let lastCheckedAt = getLastGetDate(appInfo.appleId); if (lastCheckedAt != null) { lastCheckedAt = new Date(lastCheckedAt); } for (let i = 0; i < entries.length; i++) { // æ稿æ¥æ const reviewUpdatedAt = new Date(entries[i].updated.label); // æå¾ã«åå¾ããã¬ãã¥ã¼ããå¾ã®ã¬ãã¥ã¼ã®ã¿éã if (lastCheckedAt != null && reviewUpdatedAt.getTime() <= lastCheckedAt.getTime()) { break; } const review = new Review( reviewUpdatedAt, entries[i].author.name.label, // author parseInt(entries[i]["im:rating"].label), // rating entries[i].title.label, // title entries[i].content.label, // ã³ã¡ã³ã ); reviews.push(review); } // ææ°ã¬ãã¥ã¼ã®æ¥ä»ãä¿å if(reviews.length > 0) { setLastGetDate(appInfo.appleId, reviews[0].updatedAt); } return reviews; }
Google Play
Google Playã¯App Storeã¨ç°ãªãAPIã使ãããå°ãè¤éã§ãã
ã¯ã©ã¤ã¢ã³ãID, ã¯ã©ã¤ã¢ã³ãã·ã¼ã¯ã¬ãã, Refresh Tokenã使ã£ã¦ _getAccessToken
ã§ã¢ã¯ã»ã¹ãã¼ã¯ã³ãåå¾ãã¦ãã¾ãã
ãã®ãã¼ã¯ã³ã¯ä¸å®æéãéããã¨å©ç¨ã§ããªããªãããæ¯ååå¾ããå¿
è¦ãããã¾ãã
ãªãã¤ã¬ã¯ãã®URLã¯ãªãã§ãè¯ãã®ã§ãä»å㯠https://google.com
ã«ãã¦ãã¾ãã
const DIGIKAR_CLIENT_ID = "digikar_client_id.apps.googleusercontent.com"; const DIGIKAR_CLIENT_SECRET = "digikar_client_secret"; const DIGIKAR_REFRESH_TOKEN = "1//digikar_refresh_token"; const M3_CLIENT_ID = "m3_client_id.apps.googleusercontent.com"; const M3_CLIENT_SECRET = "m3_client_secret"; const M3_REFRESH_TOKEN = "1//m3_refresh_token"; const REDIRECT_URI = "https://google.com"; function getAndroidReviews(appInfo) { let accessToken; if(appInfo.googlePlayAccount == GooglePlayAccount.M3) { accessToken = _getAccessToken(M3_CLIENT_ID, M3_CLIENT_SECRET, REDIRECT_URI, M3_REFRESH_TOKEN); } else if(appInfo.googlePlayAccount == GooglePlayAccount.Digikar) { accessToken = _getAccessToken(DIGIKAR_CLIENT_ID, DIGIKAR_CLIENT_SECRET, REDIRECT_URI, DIGIKAR_REFRESH_TOKEN); } else { return []; } const url = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/" + appInfo.androidId + "/reviews\?access_token=" + accessToken; const response = UrlFetchApp.fetch(url); const entries = JSON.parse(response.getContentText()).reviews; if(entries == undefined) { return []; } const reviews = []; var lastCheckedAt = getLastGetDate(appInfo.androidId); if (lastCheckedAt != null) { lastCheckedAt = new Date(lastCheckedAt); } for (var i = 0; i < entries.length; i++) { const comment = entries[i].comments[0].userComment; // æ稿æ¥æ const reviewUpdatedAt = new Date(comment.lastModified.seconds * 1000); // æå¾ã«åå¾ããã¬ãã¥ã¼ããå¾ã®ã¬ãã¥ã¼ã®ã¿éã if (lastCheckedAt != null && reviewUpdatedAt.getTime() <= lastCheckedAt.getTime()) { break; } const review = new Review( reviewUpdatedAt, entries[i].authorName, comment.starRating, comment.deviceMetadata.productName, // Googleã¯Titleãªã comment.text.trim(), ); reviews.push(review); } // ææ°ã¬ãã¥ã¼ã®æ¥ä»ãä¿å if(reviews.length > 0) { setLastGetDate(appInfo.androidId, reviews[0].updatedAt); } return reviews; } function _getAccessToken(clientId, clientSecret, redirectUri, refreshToken) { const payload = { "client_id": clientId, "client_secret": clientSecret, "redirect_uri": redirectUri, "grant_type": "refresh_token", "refresh_token": refreshToken, }; const params = { "method": "POST", "payload": payload, "muteHttpExceptions": true }; const response = UrlFetchApp.fetch('https://accounts.google.com/o/oauth2/token', params); const data = JSON.parse(response.getContentText()); const access_token = data.access_token; return access_token; }
Slackéä¿¡
åå¾ããæ å ±ãå ã«æ´å½¢ãã¦Slackã«éç¥ããé¢æ°ã§ãã
const BOT_NAME = "App Review Reporter" const SLACK_URL = "https://hooks.slack.com/services/****/****/****"; const STAR_ENABLE = "â "; const STAR_DISABLE = "â"; function sendSlack(platform, appInfo, reviews) { let message = ""; for (var i = 0; i < reviews.length; i++) { const review = reviews[i]; message += ` ${platform} ${appInfo.icon} ${appInfo.name} ${review.title} ${review.author} ${formatRating(review.rating)} ${review.message} ${formatDateJST(review.updatedAt)} \n`; } if (message.length == 0) { return; } const jsonData = { "text": message, }; let options = { "method": "post", "contentType": "application/json", "payload": JSON.stringify(jsonData), }; UrlFetchApp.fetch(SLACK_URL, options); // 追å ã§å¥ã®ãã£ã³ãã«ã«éãå ´å if(appInfo.extraSlackPostUrl != null) { UrlFetchApp.fetch(appInfo.extraSlackPostUrl, options); } } function formatRating(rating) { var result = ""; for (var i = 0; i < rating; i++) { result += STAR_ENABLE; } for (var i = 5 - rating; i > 0; i--) { result += STAR_DISABLE; } return result; }
å¼ã³åºã(main)é¢æ°
ä»ã¾ã§ã®ã³ã¼ãã使ã£ã¦åå¾ããã¬ãã¥ã¼ãSlackã«ãã¹ããã¾ãã å®éã¯7件ã®ã¢ããªã®ã¬ãã¥ã¼ãç£è¦ãã¦ããã®ã§ãããä¾ã§ã¯2ã¤ã®ã¢ããªã®ã¬ãã¥ã¼ãç£è¦ãã¦ãã¾ãã
function getReview() { const digisma = new AppInfo( "jp.digikar.smart.app", "1563102530", "M3ãã¸ã«ã«ã¹ãã¼ã診ç", ":digisma:", GooglePlayAccount.Digikar, ); const m3comApp = new AppInfo( "com.m3.app.android", "868617306", "m3.com", ":m3com:", GooglePlayAccount.M3, ); _getAppReviews(digisma); _getAppReviews(m3comApp); } function _getAppReviews(appInfo) { // Google Play const androidReviews = getAndroidReviews(appInfo); sendSlack(Platform.ANDROID, appInfo, androidReviews); // App Store const appleReviews = getAppleReviews(appInfo); sendSlack(Platform.IOS, appInfo, appleReviews); }
ããªã¬ã¼ã®è¨å®
æå¾ã«å¼ã³åºãé¢æ°ãæé主å°åã®ããªã¬ã¼ã«è¨å®ãã¾ãããã ãã»ã©ã¢ããªã®ã¬ãã¥ã¼ãæ¥ã¦ããã¢ããªã§ãªããã°ãããã¾ã§é »ç¹ã«å¼ã°ãªãã¦ãè¯ãã¨æãã¾ããã«ã¹ã¿ãã¼ãµãã¼ããå¿ è¦ãªã¬ãã¥ã¼ãæ¥ãå ´åã¯è¿ éã«å¯¾å¿ã§ããããã«1æéã«1åçºåããããã«è¨å®ãã¦ãã¾ãã
Slackã®ã¹ã¯ãªã¼ã³ã·ã§ãã
ä¸è¨ã®è¨å®ãçµããã°å®æçã«ã¢ããªã®ã¬ãã¥ã¼ãSlackã«éããã¦ããããã«ãªãã¾ã ð
ãã©ãããã©ã¼ã ãã¢ããªãã¢ã¤ã³ã³ã«ã§ãã¦ããããããã£ãããããããã®ã¬ãã¥ã¼ã«ãªã¢ã¯ã·ã§ã³ã§ããã®ã¯ç¢ºèªãããã以ä¸ã«Slackã«éãã¡ãªãããªã®ããªã¨æãã¾ãã
ã¾ã¨ã
GASã使ã£ã¦ã¢ããªã®ã¬ãã¥ã¼ãSlackã«éç¥ããæ¹æ³ãç´¹ä»ãã¾ããã
Slackã«éç¥ãããã¨ã§ãçµµæåãªã¢ã¯ã·ã§ã³ã§åå¿ã§ããããã«ãªã£ãããã«ã¹ã¿ãã¼ãµãã¼ãã®æ¹ããã°ããåãåããã«è¿
éã«å¯¾å¿ã§ããããã«ãªã£ãçµæããã¼ã å
¨ä½ãã¢ããªã¬ãã¥ã¼ã¸ã®æèãé«ã¾ãè¯ããã£ããã«ãªãã¾ããã
ä»åè¡ã£ãã¬ãã¥ã¼éç¥ã¯APIã®é½åä¸ã³ã¡ã³ãä»ãã®ã¬ãã¥ã¼ã®ã¿Slackã«éç¥ãã¦ãããããä»å¾ã¯ã³ã¡ã³ããªãã®ã¬ãã¥ã¼ãéããããã«ãªã«ãããã®æ¹æ³ãèãã¦ããããã¨æã£ã¦ãã¾ãã
ãã¾ã
ã¢ããªã¬ãã¥ã¼ã®Slackéç¥ã¨åæã«ãã¢ããªå ã¬ãã¥ã¼( iOS, Android ) ã®å®è£ ããã¾ããã
ãã®APIã使ãã¨ã¹ãã¢ã«é£ã°ãã«ã¢ããªå ããã¬ãã¥ã¼ãéä¿¡ã§ããããã«ãªãã¾ãã
ãã¸ã¹ãã¢ããªã§ã¯äºåã«ç»é²ããã¯ã¬ã¸ããã«ã¼ãããèªåã§ä¼è¨ãè¡ããããã«å¸°ãã¦ãè¬å±ã«å¦æ¹ç®ãéããã¦ããã¨ããä»ã¾ã§ã«ã¯ãªãç
é¢ä½é¨ãä¸ããããããã診å¯å¾ã«ã¦ã¼ã¶ã¼ã®æºè¶³åº¦ãä¸çªé«ããªãã¨èãã表示ã¿ã¤ãã³ã°ã¯è¨ºå¯å¾ã«ãã¼ã ç»é¢ã表示ããã¿ã¤ãã³ã°ã§ã¬ãã¥ã¼è¨´æ±ããã¦ãã¾ãã
(1åç®ä»¥éã®è¡¨ç¤ºãã¸ãã¯ã¯ãã©ãããã©ã¼ã ã«ãã£ã¦ç°ãªãããæ¯å表示ã¯ããã¾ãã)
ã¢ããªå ã¬ãã¥ã¼å®è£ åã®ç´1å¹´åæç¹ã¯ã¦ã¼ã¶ã¼æ°ãå°ãªãã£ãã®ãããã¾ãããã¢ããªã®ãªãªã¼ã¹ããç´1å¹´ã§ã¬ãã¥ã¼ãiOSã§37件, Androidã9件ã®ã¿ã§ããã
ããããã¢ããªå ã¬ãã¥ã¼æ©è½ã®ãªãªã¼ã¹å¾6æ¥ã§iOSã110件ãAndroidã20件ã«ã¾ã§å¢ãã¾ããï¼
ããã¦ãã¢ããªã®ãªãªã¼ã¹ããç´2å¹´ãã¢ããªå
ã¬ãã¥ã¼æ©è½ãªãªã¼ã¹ããç´1å¹´ãã£ãä»ã¯iOSã6012件ãAndroidã¯691件ã«å¢ããè©ä¾¡ããããã4.5, 4.25ã¨ãããªãã«é«ãæ°åãç¶æã§ããããã«ãªãã¾ããð
ã¢ããªå
ã¬ãã¥ã¼ãå
¥ããã ãã§ã¾ãããããªã«ãã¬ãã¥ã¼æ°ãå¢ããã¨ã¯æããªãã£ãã§ãã
We are hiring!
ãã¸ã¹ãããã£ã¨ç¥ãããã¨æã£ã¦ãããæ¹ã¯ãã²ç´¹ä»è³æã覧ä¸ããï¼
ã¨ã ã¹ãªã¼ã§ã¯ããã¸ã¹ã以å¤ã®ãµã¼ãã¹ãããããããã¾ãã
ã¨ã³ã¸ãã¢ã«éãããã¶ã¤ãã¼ããããã¯ãããã¼ã¸ã£ã¼ãç©æ¥µçã«æ¡ç¨ãã¦ãã¾ãã
ä»ã®ãµã¼ãã¹ã«ãèå³ãæãããæ¹ã¯ä¸è¨ãããåãåãããã ããã