見出し画像

GASを使って #コンパス のデッキをランダムに生成するTwitterBotを作った話


手短に。

こんなのを作りました。
@Deck_Cps

目次

1.開発経緯
2.Bot用のTwitterアカウント作って色々申請
3.開発環境つくるよ
4.ソース書いた
5.トリガー引いて放置
6.保守作業めんどい
7.最後に


1.開発経緯

TwitterはBotが気楽に作れるSNSと聞いて遊び半分で作り始めた。
自分が好きなスマホゲーム #コンパス とTwitterのBotが組み合わさったら
面白そうと思い、「カードデッキをランダムで生成する」ものを思い付いた。


2.Bot用のTwitterアカウント作って色々申請

詳細は大幅に割愛します。
ここから始める方はこのサイトとか参考にしていただければ。
最近はTwitter Developer Portalの申請形式が変化しているので
何とか解読して、「Read Write」できるappを作成します。


3.開発環境つくるよ

そんなに大したことではないのですが、
1.Googleスプレッドシートから新規作成
2.「拡張機能」のApps Scriptをクリック
3.TwitterWebServiceというライブラリをV8で使える環境作る

4.ソース書いた

gitにも上げてますが、こちらにも上げておきます。

//コード.gs

// twitterエンドポイントURL
var tw_endpoint_statuses = "https://api.twitter.com/1.1/statuses/update.json";
var tw_enpoint_media = "https://upload.twitter.com/1.1/media/upload.json";

//認証用インスタンスの生成
var twitter = TwitterWebService.getInstance(
 'xxxxxxxxxxxxxxxxxxxxxxxxx',//API Key
 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'//API secret key
);

//アプリを連携認証する
function authorize() {
 // @ts-ignore
 twitter.authorize();
}

//認証を解除する
function reset() {
 // @ts-ignore
 twitter.reset();
}

//認証後のコールバック
function authCallback(request) {
 // @ts-ignore
 return twitter.authCallback(request);
}

// 乱数結果を取得する関数
function Deck() {
 
 Logger.log("Deck_Start");
 
 // 定義ファイルの取得
 var spreadsheet = SpreadsheetApp.openById('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
 var sh = spreadsheet.getActiveSheet();
 Logger.log("Loding_spreadsheet");
 
 //スプレッドシートの指定
 var sheet = spreadsheet.getSheetByName('hero');
 
 //配列を定義
 var deck = [];
 var deckIds = [];

 //実行フラグ関係
 let Img_Twi_flag = false;
 const image_tweet_interval = 12;
 let Twicount = 0;

 // 項目数を取得
 let Tweetct = sheet.getRange("Q2").getValue();
 const Heroct = sheet.getRange('B:B').getValues().filter(String).length - 1;
 let Cardct = sheet.getRange('H:H').getValues().filter(String).length - 1;
 let Imagect = sheet.getRange('I:I').getValues().filter(String).length - 1;
 
 //項目数の内容をログに出力
 Logger.log("SpreadSheet_getRange\nTweetct:"+Tweetct+"\nHeroct:"+Heroct+"\nCardct:"+Cardct+"\nImagect:"+Imagect);
 
 // 出力する項目の一覧を取得する
 var heroList = sheet.getRange(2, 2, Heroct, 1).getValues();
 var cardList = sheet.getRange(2, 8, Cardct, 1).getValues();
 var ImageList = sheet.getRange(2, 9, Imagect, 1).getValues();
 Logger.log("SpreadSheet_getValues");
 
 //ランダムな値を取得する
 let Herorm = Math.floor(Math.random()*(Heroct));
 let Cardrm = Math.floor(Math.random()*(Cardct));
 
 if( Tweetct >= image_tweet_interval )
 {
   //画像付きツイート処理
   Img_Twi_flag = true;
   sh.getRange("Q2").setValue(1);
   Logger.log("画像ツイートフラグは:"+Img_Twi_flag);
 }
 else
 {
   //文字のみのツイート処理
   Img_Twi_flag = false;
   // @ts-ignore
   Twicount = Tweetct + 1;
   sh.getRange("Q2").setValue(Twicount);
   Logger.log("画像ツイートフラグは:"+Img_Twi_flag);
 }
 
 Logger.log("TweetCount:"+Tweetct);
 
 Logger.log("Random_Deck_go")
 
 //True:画像ツイ
 //False:文字ツイ
 if( Img_Twi_flag )
 {
   //4回繰り返す
     for ( let i = 0; i < 4; i++) {
   Cardrm = Math.floor(Math.random()*(Cardct));
   Logger.log(i+":"+cardList[Cardrm]);
   
   //deckというcardListインデックスに結果を代入する
   //deckIdsというImageListインデックスにも結果を代入する
   deck.push(cardList[Cardrm]);
   deckIds.push(ImageList[Cardrm]);
   
   //引いた番号がカードリストの最終番号ではないかどうか
   if( Cardrm != Cardct ){
    //結果番号のカード内容をカードリストの最終番目に格納する(実質の除外)(5枚のカードの3番目を引いた時、3番の内容を5番目の内容と交換し、合計カード数を-1以下略)
    cardList[Cardrm] = cardList[Cardct-1];
    ImageList[Cardrm] = ImageList[Imagect-1];
   }
   
   //選んだ結果がカード配列の最終番号であった場合、次選定する合計カード数を-1し除外する(5枚のカードの5番目を引いた時、次のランダム範囲は1~4番までにし5番目を除外する)
   Cardct--;
   Imagect--;
 }
 
 // 取得したカードを出力する関数へ引き渡す
 Logger.log("Send_PostTweet");
 postTweet_pic(heroList[Herorm],deck[0],deck[1],deck[2],deck[3],deckIds);

 }
 else
 {
    //4回繰り返す
 for ( let i = 0; i < 4; i++) {
   Cardrm = Math.floor(Math.random()*(Cardct));
   //deckというcardListインデックスに結果を代入する
   deck[i] = cardList[Cardrm];
   Logger.log(i+":"+cardList[Cardrm]);

   //引いた番号がカードリストの最終番号ではないかどうか
   if( Cardrm != Cardct ){
    //結果番号のカード内容をカードリストの最終番目に格納する(実質の除外)(5枚のカードの3番目を引いた時、3番の内容を5番目の内容と交換し、合計カード数を-1以下略)
    cardList[Cardrm] = cardList[Cardct-1];
   }
   
   //選んだ結果がカード配列の最終番号であった場合、次選定する合計カード数を-1し除外する(5枚のカードの5番目を引いた時、次のランダム範囲は1~4番までにし5番目を除外する)
   Cardct--;
   
 }
 
 // 取得したカードを出力する関数へ引き渡す
 Logger.log("Send_PostTweet");
 postTweet_txt(heroList[Herorm],deck[0],deck[1],deck[2],deck[3]);

 }
 

}

// twitterへ文字ツイートを投稿
function postTweet_txt(hero,deck1,deck2,deck3,deck4) {
 
 // @ts-ignore
 var service  = twitter.getService();
 var endPointUrl = 'https://api.twitter.com/1.1/statuses/update.json';
 
 var response = service.fetch(endPointUrl, {
   method: 'post',
   payload: {
     status: '【' + hero + '】' + 'で\n' + '\n' + '・'+ deck1 + '\n' + '・'+ deck2 + '\n' + '・'+ deck3 + '\n' + '・'+ deck4 + '\n' + '\nを使ったデッキ'
   }
 });
}

// twitterへ画像ツイートを投稿
// 返り値はツイートした投稿のID
function postTweet_pic(hero,deck1,deck2,deck3,deck4,img_ids){
 Logger.log("Start_PostTweet");
 // @ts-ignore
 var service  = twitter.getService(); //TwitterWebServiceのインスタンス
 
   // 順番にエンコード→アップロードする。*2
   for(var i = 0; i < img_ids.length; i++){
     Logger.log("uploding_media..."+i);
     var img_blob = DriveApp.getFileById(img_ids[i]);
     var img_64 = Utilities.base64Encode(img_blob.getBlob().getBytes());
     var img_upload = service.fetch(
       tw_enpoint_media, { 
         'method' : "POST", 
         'payload': { 'media_data': img_64 } 
       }
       
     ); 
     
     Logger.log("upload_success!!");
     img_ids[i] = JSON.parse(img_upload).media_id_string;
   }
   

 
 // ツイート
 Logger.log("Start_Tweet!");
 var response = service.fetch(tw_endpoint_statuses, {
   method: "post",
   payload: {
     'status' : '【' + hero + '】' + 'で\n' + '\n' + '・'+ deck1 + '\n' + '・'+ deck2 + '\n' + '・'+ deck3 + '\n' + '・'+ deck4 + '\n' + '\nを使ったデッキ',
     'media_ids' : img_ids.join(',')  // すでにアップロードした画像のid(カンマ区切りで結合) *3
   }
 });
 
 tw_id = JSON.parse(response).id_str;
 Logger.log("finish_Tweet");
 Logger.log("やればできるやん!('ω')");
 return tw_id;
 
}

Gitはこちら
https://github.com/Leeside18/Deck_CpsBot


5.トリガー引いて放置

スクリーンショット 2021-03-19 215659

あとは実行する関数を選択して好きなタイミングで実行。

ちなみに文字だけツイートか画像付きツイートかは
実行するたびにスプレッドシートの特定セルをカウントアップして更新し、その値を読むことで今回はどちらの処理なのかを分けています。

画像2


6.保守作業めんどい

なかなかにめんどいので色々と工夫はしました。
けどやっぱりめんどい。
 1.カード、ヒーローの合計数は実行時に列ごと取得した配列からLengthとって乱数を設定した。
 2.GoogleDriveに保存しているカード画像を自動で読み取りidをリスト化するツールを作成し、カード大量追加が来ても更新に時間がかからないようにした。

画像3



7.最後に

開発当初は配列がけっこうガバく、一度選ばれたカードをもう一度選んだら
再度選びなおしていたが、GASの処理時間の都合もあって、カードが重複してしまう問題とかもあった。その辺の解決策もソースに記載しているので
是非とも配列を扱う機会があれば参考にしてほしい。
キーワードは「除外」だった。

画像付きツイートについては環境をGASに置いていることもあって
GoogleDriveが一番適切だと考えた。これだと15GBまでしか置けないが、
画像が埋まるようならその時考える。
事前に格納したDriveから画像のidを取得しておき、スプレッドシートに「カード名」「画像id」を紐づける作業がめんどかった。けど
Twitterへのアップロード処理が何とか形になったのがこのBotづくりの収穫だった。
これを機にGAS極めたいなー

この記事が気に入ったらサポートをしてみませんか?