Last active
September 24, 2020 15:54
-
-
Save kazuhito-m/a7d5b045f93d40375af1ebb9140e8fd1 to your computer and use it in GitHub Desktop.
KaraokeTweet.gs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* JoySoundの歌唱履歴ページを定期的にポーリングし、追加があればSpreadSheetに書きつつ、TwitterへつぶやくGASスクリプト。 | |
* | |
* Usage | |
* 1. GoogleAppScriptに新たにスクリプトを作り、このソースを貼る。 | |
* 2. ファイル->プロジェクトのプロパティ->スクリプトのプロパティを開き、以下の変数を設定する。 | |
* - joysound_id : JoySoundの「うたスキ(https://www.joysound.com/utasuki)」にログインするためのID | |
* - joysound_password : 上記のパスワード | |
* - spreadsheet_id : GoogleSpreadSheetのID | |
* - twitter_api_key : TwitterのAPI key | |
* - twitter_api_secret_key : TwitterのAPI secret key | |
* - twitter_access_token : TwitterのAccess token | |
* - twitter_access_token_secret : TwitterのAccess token secret | |
* - user_nick_name : Tweetに表示するユーザ名の愛称 | |
* 3. 編集->現在のプロジェクトのトリガー を選択、トリガー画面で「トリガーの追加」をクリックし、以下の条件で鶏が設定する | |
* - 実行する関数を選択 : main | |
* - イベントのソースを選択 : 時間手動型 | |
* - 時間ベースのトリガーのタイプを選択 : 分ベースのタイマー | |
* - 時間の間隔を選択(分) : 15分おき | |
*/ | |
function getKaraokeHistory(mailAddress, password) { | |
// Login | |
const url = 'https://www.joysound.com/utasuki/login.htm?path=%252Futasuki%252Fmypage%252Fhistory%252Findex.htm&redirect=true'; | |
const response = UrlFetchApp.fetch(url, { | |
"payload": 'loginId=' + mailAddress + '&password=' + password, | |
"method": "POST", | |
'followRedirects': false, | |
}); | |
const httpStatus = response.getResponseCode(); | |
if (httpStatus !== 302) { | |
console.log('Failed. HTTP status: ' + httpStatus); | |
return; | |
} | |
const returnedCookies = response.getAllHeaders()['Set-Cookie']; | |
// History Api call | |
const cookies = returnedCookies | |
.map(cookie => cookie.replace(/;.*/, '')) | |
.join('; '); | |
const headers = { | |
"Cookie" : cookies, | |
'X-JSP-APP-NAME': 'www.joysound.com', | |
'Referer': 'https://www.joysound.com/utasuki/mypage/history/index.htm', | |
} | |
const apiUrl = 'https://www.joysound.com/api/1.0/member/@me/karaokeHistory?count=20&orderBy=0&sortOrder=desc&startIndex=0&maxPageNum=1'; | |
const apiResponse = UrlFetchApp.fetch(apiUrl, { | |
'headers': headers | |
}); | |
return JSON.parse(apiResponse.getContentText()); | |
} | |
function writeHistorySheet(id, karaokes) { | |
const spread = SpreadsheetApp.openById(id); | |
var sheet = spread.getSheets()[0]; | |
const lastRow = sheet.getLastRow(); | |
const value = sheet.getRange(lastRow, 1).getValue(); | |
const lastPlayDateTime = Number.isInteger(value) ? Number.parseInt(value) : 0; | |
const sortAndFilterd = karaokes | |
.filter(d => d.playDateTime > lastPlayDateTime) | |
.sort((l, r) => l.playDateTime - r.playDateTime); | |
let row = lastRow + 1; | |
for (let data of sortAndFilterd) { | |
const song = data.selSong; | |
const artist = data.artist; | |
writeOf(sheet, row, 1, data.playDateTime); | |
writeOf(sheet, row, 2, data.key); | |
writeOf(sheet, row, 3, song.selSongNo); | |
writeOf(sheet, row, 4, song.selSongName); | |
writeOf(sheet, row, 5, song.selSongNameRuby); | |
writeOf(sheet, row, 6, artist.artistName); | |
writeOf(sheet, row, 7, artist.artistNameRuby); | |
row++; | |
} | |
return sortAndFilterd; | |
} | |
function writeOf(sheet, row, col, value) { | |
sheet.getRange(row, col).setValue(value); | |
} | |
function formatOf(date) { | |
const d = date; | |
return d.getFullYear() + | |
'/' + zp(d.getMonth() + 1) + | |
'/' + zp(d.getDate()) + | |
' ' + zp(d.getHours()) + | |
':' + zp(d.getMinutes()) + | |
':' + zp(d.getSeconds()); | |
} | |
function zp(num) { | |
return ("00" + num).slice(-2); | |
} | |
function executeTwitterApi(url,method,body) { | |
// プロパティ取得 | |
const properties = PropertiesService.getScriptProperties(); | |
const API_KEY = properties.getProperty('twitter_api_key'); | |
const CONSUMER_SECRET = properties.getProperty('twitter_api_secret_key'); | |
const ACCESS_TOKEN = properties.getProperty('twitter_access_token'); | |
const TOKEN_SECRET = properties.getProperty('twitter_access_token_secret'); | |
// 認証情報作成 | |
const date = new Date(); | |
const timestamp = date.getTime().toString().substr(0,10); | |
const nonce = timestamp; // 実行毎に一意であれば何でもいい | |
let authorizationParameters = [ | |
'oauth_consumer_key=' + API_KEY, | |
'oauth_nonce=' + nonce, | |
'oauth_signature_method=HMAC-SHA1', | |
'oauth_timestamp=' + timestamp, | |
'oauth_token=' + ACCESS_TOKEN, | |
'oauth_version=1.0' | |
]; | |
const encodedUrl = encodeURI(url) | |
let baseUrl = encodedUrl | |
let queryParams = new Array(); | |
if(encodedUrl.match(/\?/)){ | |
baseUrl = encodedUrl.split('?')[0] | |
queryParams = encodedUrl.split('?')[1].split('&') | |
queryParams.forEach(function(param){ | |
authorizationParameters.push(param); | |
}) | |
authorizationParameters = authorizationParameters.sort() | |
Logger.log(queryParams) | |
} | |
// Signature作成 | |
const signature_base_string = method + '&' + encodeURIComponent(baseUrl) + '&' + encodeURIComponent(authorizationParameters.join('&')); | |
const signature_key = encodeURI(CONSUMER_SECRET) + '&' + encodeURI(TOKEN_SECRET); | |
const signature = encodeURIComponent(Utilities.base64Encode(Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1, signature_base_string, signature_key))); | |
const authorization = 'OAuth ' + authorizationParameters.join(',') + ',oauth_signature=' + signature; | |
// APIパラメータ作成 | |
const parameters = { | |
method : method, | |
headers : {"Authorization": authorization}, | |
muteHttpExceptions : true | |
}; | |
// API実行 | |
const response = UrlFetchApp.fetch(encodedUrl,parameters); | |
return response | |
} | |
function tweeting(text){ | |
const url = "https://api.twitter.com/1.1/statuses/update.json?status=" + text; | |
const method = 'POST'; | |
executeTwitterApi(url,method) | |
} | |
function tweetHistory(histories, nickName) { | |
let firstTime = true; | |
for (let h of histories) { | |
if (!firstTime) Utilities.sleep(3000); | |
firstTime = false; | |
console.log(h); | |
const dateText = formatOf(new Date(h.playDateTime)); | |
const keyText = 'Key:' + h.key.replace('+', '+').replace('-', 'ー'); | |
const base = `${nickName}は${dateText}に「${h.selSong.selSongName}/${h.artist.artistName}」を${keyText}で歌いました。 #カラオケ歌うとつぶやかれるヤツ`; | |
tweet = base.replace('(', '(').replace(')', ')'); | |
tweeting(tweet); | |
} | |
} | |
function main() { | |
const properties = PropertiesService.getScriptProperties(); | |
const KARAOKE_ID = properties.getProperty('joysound_id'); | |
const KARAOKE_PASSWORD = properties.getProperty('joysound_password'); | |
const SPREADSHEET_ID = properties.getProperty('spreadsheet_id'); | |
const USER_NICK_NAME = properties.getProperty('user_nick_name'); | |
const histories = getKaraokeHistory(KARAOKE_ID, KARAOKE_PASSWORD); | |
const registerd = writeHistorySheet(SPREADSHEET_ID, histories.myKaraokes); | |
if (registerd.length > 0) tweetHistory(registerd, USER_NICK_NAME); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment