Twitterでいいねしたツイートを、フォルダ分けする方法

#1.はじめに
この記事は、自分の課題解決の備忘録です。
僕と同様に『いいねしたツイートをフォルダ分けしたり、検索出来るようにしたい』と思っている方の手助けになれば、とても嬉しいです。


#2.開発理由と背景

『Twitterのブックマーク欄って、フォルダ分け出来ないし検索機能もないから、まあまあ不便なんだよな～。プログラミングってやつを使えば、フォルダ分け機能とか作れたりするのかな』と思ったからです。

よくよく調べてみると、
・TwitterAPIとスプレッドシート、GoogleAppsScriptというプログラミング言語（以下GAS）の3つを使えば、それっぽいものが作れそう。
・TwitterAPIを利用して、自分がブクマしたツイートを扱うことは出来ない。
・TwitterAPIを利用して、自分がいいねしたツイートは扱うことが出来る。
ということが分かりました。

なので、ブクマしたツイートではなく、自分がいいねしたツイートを、GASを使ってスプレッドシートに出力、整理、蓄積することにしました。

#3.理想の状態
・ツイート本文の内容や、ユーザー名、ツイートのジャンルによって、探しているツイートを検索出来る。
・ツイートのジャンルごとに、フォルダ分けが出来る。
・最近いいねしたツイートが、シートのファーストビューに定期的に自動追加される。
・誰のどんなツイートかが、一目で分かる。

スプレッドシートを使い、上記4点を満たした状態を作っていきます。


#4.成果物に必要なもの
##4-1.そもそも準備しておく必要のあるもの

・Twitter開発者アカウント（開発者申請する必要があります。）
・Bearer token（開発者アカウントを作成出来さえすれば、スムーズに取得出来ます）
・Googleアカウント
・無題のスプレッドシート（Chromeの検索窓にて”sheet.new”と入力し、Enterを押すと、新規シートを作ることが出来ます。）

※開発者アカウントとBearer tokenを準備する方法は、下記記事をご参照ください。
・https://specially198.com/get-the-content-of-a-tweet-using-twitters-api-in-gas/
・https://www.virment.com/create-bearer-token-twitter-api-and-retrieve-tweet/



##4-2.成果物に必要な機能

`・ツイート本文の内容や、ユーザー名、ツイートのジャンルによって、探しているツイートを検索出来る。`
➝ツイートに関する文字列を抜け漏れなく抽出したうえで、スプレッドシートにて、ショートカットキー『ctrl+F(Macはcommand+F)』を使えば問題無さそうです。


`・ツイートのジャンルごとに、フォルダ分けが出来る。`
➝スプレッドシートのプルダウンメニューとフィルタ機能を使えば問題無さそうです。
※参考：https://trinity.jp/175944/

`・最近いいねしたツイートが、シートのファーストビューに定期的に自動追加される。`
必要な機能を細分化したうえで、それぞれの機能を実装するために必要な情報を整理します。
➝①シートに存在しない、新規のツイートのみを取得する。
　②シートの2行目以前に、新規ツイート数と同じだけ行数を空ける。
　③空けた行に新規ツイートを出力する。
　④完成したスクリプトを定期的に実行する。
　➝①GASのincludesメソッドと、シートに既にあるツイートのURL、新たに取得してきたツイートのURLの3つを使います。
　　②GASのinsertRowsBeforeメソッドを使います。
　　③GASのsetValuesメソッドを使います。
　　④GASのトリガー機能を使います。

`・誰のどんなツイートかが、一目で分かる。`
➝スプレッドシートのIMAGE関数を使うことで、取得したツイートのプロフィール画像URLや、ツイートに添付されている画像や動画のURLを、セルに画像として表示出来そうです。



`・スプレッドシートの準備`
列に何の情報が入ってるかを分かりやすくするため、準備するスプレッドシートの1行目は、下記の通り文字を入力して、行を固定しておきます。
※固定したい行のセルを選択し、表示 > 固定 > 一行 と選択していくと、指定した行を固定することが出来ます。

<table>
<th>genre</th>
<th>tweet_time</th>
<th>tweet_url</th>
<th>profile_image</th>
<th>user_name</th>
<th>full_text</th>
<th>retweet_count</th>
<th>favorite_count</th>
<th>media_url_1</th>
<th>media_url_2</th>
<th>media_url_3</th>
<th>media_url_4</th>
</table>


#5.コード
https://script.google.com/home > 新しいプロジェクトを開く > function( ){ }という文字を削除する > 下記コードをコピペする


```javascript:コード.gas
const twitterToken = 'xxx'// コピーしたBearer token
const twitterId = 'xxx' // 取得したいTwitterのid（今回は自分のTwitterIDを入力します）


// スプレッドシートの読み込み
const spreadsheet = SpreadsheetApp.openById('xxx') // https://docs.google.com/spreadsheets/d/xxxxxxxxx/edit~~~のxxx部分
const sheet = spreadsheet.getSheetByName('xxx') // 画面左下のタブに書いてあるシート名　例）'シート1'
```






```javascript:コード.gas
function requestTweetData(){
   // header,option,paramsの設定
  const headers = {
    'Authorization': 'Bearer '+ twitterToken
  }
  const options = {
    'method': 'GET',
    'headers': headers
  }
  const params = '?screen_name=' + twitterId + '&count=200&tweet_mode=extended'

  // いいねしたTweetを取得する
  const response = UrlFetchApp.fetch('https://api.twitter.com/1.1/favorites/list.json' + params, options)
  const jsonData = JSON.parse(response.getContentText())

  // 取得したTweetデータを配列に整形
  const tweetData = []
  
  jsonData.map((tweet) => {
    const tweetTime = Utilities.formatDate(new Date(tweet.created_at), 'Asia/Tokyo', 'yyyy/MM/dd (E) HH:mm:ss')
    const fullText = tweet.full_text
    const retweetCount = tweet.retweet_count
    const favoriteCount = tweet.favorite_count
    const profileImage = tweet.user.profile_image_url
    const userName = tweet.user.name
    const tweetUrl = 'https://twitter.com/' + tweet.user.screen_name + '/status/' + tweet.id_str
    
    //ツイートに画像・動画が含まれている場合、URLを取得して、IMAGE関数を適用させておく。
    if(tweet.extended_entities){
      const mediaUrl = []
      tweet.extended_entities.media.map((media) => {
        mediaUrl.push('=IMAGE("' + media.media_url + '")' )
      })
      for(let i = mediaUrl.length; i < 4;i++){
        mediaUrl.push('')
      }
      tweetData.push({
          tweet_time: tweetTime,
          tweet_url: tweetUrl,
          profile_image_url: '=IMAGE("' + profileImage + '")',
          user_name: userName,
          full_text: fullText,
          retweet_count: retweetCount,
          favorite_count: favoriteCount,
          media_url_1: mediaUrl[0],
          media_url_2: mediaUrl[1],
          media_url_3: mediaUrl[2],
          media_url_4: mediaUrl[3]
        })
      
    }else{
      tweetData.push({
          tweet_time: tweetTime,
          tweet_url: tweetUrl,
          profile_image_url: '=IMAGE("' + profileImage + '")',
          user_name: userName,
          full_text: fullText,
          retweet_count: retweetCount,
          favorite_count: favoriteCount,
          media_url_1: '',
          media_url_2: '',
          media_url_3: '',
          media_url_4: ''
        })
    }
  })
  return TweetData
}
```

```javascript:コード.gas

function setTweetData(tweetData){
  const curTweets = []
  for(let i = 0; i < tweetData.length;i++){
      const eachTweetData = tweetData[i]
      const eachTweetValue = []
      for(let item in eachTweetData){
        eachTweetValue.push(eachTweetData[item])
      }
      curTweets.push(eachTweetValue)
    }


  // スプレッドシートにツイートを出力する
  const lastRow = sheet.getLastRow();

  function setTweetData(curTweets){
    const startRow = lastRow + 1;
    const startColumn = 2;
    const numColumns = curTweets[0].length;
    const range = sheet.getRange(startRow, startColumn, curTweets.length, numColumns);
    range.setValues(curTweets);
  }




// 初回実行（シート上にツイートが無い場合）と、追加更新（シート上にツイートがある場合）の2パターンを作成する。
  const lastColumn = sheet.getLastColumn();
  const checkRange = sheet.getRange(2,2,lastRow,lastColumn)

  if(checkRange.isBlank()){

    // 初回データ入力
    setTweetData(curTweets)

    // セルのサイズを整える
    sheet.setRowHeights(2,lastRow,100);
    sheet.setColumnWidths(9,12,150);
    Logger.log( curTweets.length + "件ツイートが追加されました")

  }else{

    // 更新/2回目以降のデータ入力
    // 既にシートに蓄積している全ツイートのツイートURLを取得する。
    const tweetsUrl = sheet.getRange(2,3,lastRow,1).getValues();
    const savedUrl = tweetsUrl.reduce((pre,current) => {pre.push(...current);return pre},[]);
    
    // 既存ツイートのURLと被っていないURLを判定。これに基づき、新規ツイートのインデックス番号を取得する。
    const newTweetsIndex = []
    for(let i = 0;i < curTweets.length;i++){
      const trueOrFalse = savedUrl.includes(curTweets[i][1])
      if(trueOrFalse === false){
          newTweetsIndex.push(i)
        }
      }
      //新規ツイートのインデックス番号に基づいて、新規ツイートを取得する。
      const newTweets = []
      for(let i = 0;i < newTweetsIndex.length ;i++){
        newTweets.push(curTweets[newTweetsIndex[i]])
      }


      // シート2行目以前に、新規ツイート数と同じだけ行数を空け、新規ツイートを出力する。
      function setNewTweetsData(newTweets){
        if(newTweets.length > 0){

          sheet.insertRowsBefore(2,newTweets.length);
          const range = sheet.getRange(2, 2, newTweets.length, newTweets[0].length);
          range.setValues(newTweets);
          sheet.setRowHeights(2,newTweets.length,100);
          Logger.log( newTweets.length + "件ツイートが追加されました")


        }else{

          Logger.log("最新ツイートはありません")
          
        }
      }
      setNewTweetsData(newTweets);
  }
}
```

```javascript:コード.gas
function execution(){
  setTweetData(requestTweetData());
}
```


#6.コード以外の実装

##6-1.自動で関数を実行させる
《手順》
コードを書いている画面（エディタ画面）を開く > 画面左端にあるタイマーのボタン > 画面右下にある『+トリガーを追加』>

実行する関数を選択：execution
実行するデプロイを選択：Head
イベントのソースを選択：時間主導型
（時間ベースのトリガーのタイプを選択という欄以降はどれ選んでもOKです。求めている頻度で関数が実行されるように調整しましょう。）

　> 保存

※非常に分かりやすい画像を使用されている下記記事もご参照ください。
https://www.yukibnb.com/entry/beds24_getbookingscsv_checkout_line#トリガーの設定画面を開く


##6-2.シート上でフォルダ分けを機能させる
###6-2-1.プルダウンメニューの追加
《手順》
シートを開く > データ > データの入力規則 > セル範囲を”シート名!A2:A”に設定、条件を”リストを直接指定”に設定、項目は自由に設定 > 保存

シートに追加したツイートに対して、プルダウンメニューからジャンルを設定してみます。（ツイートごとにジャンルを割り振るのは手動です泣）

###6-2-2.フィルタ機能の設定
《手順》
A列全体を選択 > データ > フィルタ表示 > 新しいフィルタ表示を作成

A1セルに、緑の逆三角形アイコンが追加されます。
これをクリックし、プルダウンメニューで設定した項目を選択すると...
![フィルタ機能モザあり②.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/877563/5bc71f4f-c93b-a3e6-22ce-03da5ff02385.jpeg)

ツイートをジャンルごとに振り分けられました！！


##6-3.おまけ
この記事の趣旨とは外れた内容ですが、『retweet_count』『favorite_count』という文字列を、リツイート・いいねボタンのアイコン画像に置き換えてみます。遊び心です。

リツイートボタン
https://icooon-mono.com/15907
いいねボタン
https://icooon-mono.com/14006

⇩

画像をDL、セル内に画像を挿入したのち、リツイート・いいね数を表示している列全体を選択。列全体の文字を中央揃えに設定すると...
![リツイートいいねボタン.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/877563/c591e1d6-e589-e0aa-bab3-c3a31878cbc2.jpeg)

めっちゃ良い感じになりました！！



#7.参考元
・GASで自分がいいねしたツイートの画像・動画を自動で保存したい
　　https://qiita.com/ivgtr/items/bf839d07cb98c87ae0bc
※今回、こちらの記事をかなり参考にさせていただきましたm(__)m

・【公式】Twitter開発者向けドキュメント>APIリファレンス
　　https://developer.twitter.com/en/docs/twitter-api/v1/tweets/post-and-engage/api-reference/get-favorites-list
・Googleスプレッドシートのフィルタ表示機能が便利
　　https://trinity.jp/175944/
・Twitter APIのBearer Tokenを作成してツイートを取得してみるまでの手順
　　https://www.virment.com/create-bearer-token-twitter-api-and-retrieve-tweet/
・GASでTwitterのAPIを使用して、ツイートの内容を取得する
　　https://specially198.com/get-the-content-of-a-tweet-using-twitters-api-in-gas/
・JavaScriptで2次元配列を1次元配列に変換する今風なやり方 reduce + スプレッド演算子(...) しかも速い!!
　　https://qiita.com/Toyoharu-Nishikawa/items/5faa1834926d67e5ac04
・【Beds24のAPI連携シリーズ】チェックアウト一覧を毎朝自動LINE通知するGAS
　　https://www.yukibnb.com/entry/beds24_getbookingscsv_checkout_line#トリガーの設定画面を開く