GASとGoogleスプレッドシートを使ってTwitterのDM自動返信botを作成する

# 目次
- 記事の概要
- 必要なもの
- システムの概要
- スクリプトの説明
- 実装手順
- 応答のカスタマイズ
- おまけ：ウェルカムメッセージの設定
- 参考にしたサイト

# 記事の概要
TwitterのDM(ダイレクトメール)に届いたメッセージに対応する返答を自動で送信するbotの作り方を紹介します。
Google Apps Script(GAS)を利用するため自分のパソコンにプログラミング環境を構築せずに、無料で作成することができます。

# 必要なもの
- Googleのアカウント
- TwitterおよびTwitter developersのアカウント

# システムの概要
GASでGoogleスプレッドシートとTwitter APIを連携させ、動かします。
5分ごとにTwitter APIを通して自身のアカウントに来たDMを取得し、それに対する返信を行います。
![概要図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/3c793ac3-1378-4d83-e311-4da55bc7ab5e.png)

# コード
コードは以下のGithubのページで公開しています。
https://github.com/MokonaSato/Twitter-DM-auto-reply-bot

### (1)	認証とTwitterへのアクセス
OAuth1を利用して認証を行い、Twitterへアクセスします。
```js
//認証を実行する関数
function logOAuthURL() {
  var twitterService = getTwitterService();
  Logger.log(twitterService.authorize());
}

// OAuth1.0の認証で、Twitterにアクセスする関数
function getTwitterService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth1.createService('twitter')
    // Set the endpoint URLs.
    .setAccessTokenUrl('https://api.twitter.com/oauth/access_token')
    .setRequestTokenUrl('https://api.twitter.com/oauth/request_token')
    .setAuthorizationUrl('https://api.twitter.com/oauth/authorize')
    // Set the consumer key and secret.
    .setConsumerKey(VAL_CONSUMER_API_KEY)
    .setConsumerSecret(VAL_CONSUMER_API_SECRET)
    // Set the name of the callback function in the script referenced
    // above that should be invoked to complete the OAuth flow.
    .setCallbackFunction('authCallback')
    // Set the property store where authorized tokens should be persisted.
    .setPropertyStore(PropertiesService.getUserProperties());
}

// 認証の確認後に表示する可否メッセージを指定する関数
function authCallback(request) {
  var twitterService = getTwitterService();
  var isAuthorized = twitterService.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput('Success! You can close this tab.');
  } else {
    return HtmlService.createHtmlOutput('Denied. You can close this tab');
  }
}
```

### (2)	ユーザーID検索 (getUserId関数)
自分自身のTwiterアカウントのユーザーIDを取得する関数です。
getDMmedia関数で取得したDMの送り主が自分か、他のアカウントかを判断する際に使用します。
```js
//userIDを検索する関数
function getUserId() {
  try {
    var twitterService = getTwitterService();
     
    if (!twitterService.hasAccess()) {
      Logger.log(twitterService.getLastError());
      return null;
    }
    var url = "https://api.twitter.com/1.1/users/lookup.json?screen_name=" + userid
    var response = twitterService.fetch(url, {
    method: "get",
    contentType: 'application/json'
    });
    Logger.log(JSON.parse(response.getContentText())[0].id_str)
    return JSON.parse(response.getContentText())[0].id_str
  }
  catch (ex) {
    Logger.log(ex)
    return null;
  }
}
```

### (3)	スプレッドシートを検索 (searchWords関数)
送られてきたメッセージがスプレッドシートのA列にあるかどうか検索し、対応するメッセージを返す関数です。getDMmedia関数から返されたDM時刻がスプレッドシートのC2に書き込んである時刻よりも後かどうかを判定し(つまり、すでに返信したメッセージか否かを判定し)、C2の時刻よりも後のDMにのみメッセージがスプレッドシートのA列にあるかどうかを検索します。A列にあればそれに対応するメッセージを返し、なければsubtext(送られてきたメッセージがスプレッドシート内に存在しない場合に返信するメッセージを格納)を返します。C2の時刻よりも前のDMに対しては何も返しません。取得したDMのうち、一番遅く送られたDMの送信時刻をC2に書き込みます。
```js
//スプレッドシートから検索ワードを検索する関数
function searchWords(keys) {
  //検索ワードがスプレッドシートになかった時に出力するワード
  var sub_text = subtext;
  //検索ワード記入
  const searchWord = keys; //検索する言葉
  //スプレッドシートの情報を取得
  const sheet =  SpreadsheetApp.getActiveSpreadsheet();
  const keysArray = sheet.getRange("A:A").getValues().flat(); //二次元配列を一次元化
  const valuesArray = sheet.getRange("B:B").getValues().flat(); //二次元配列を一次元化
  const keysArrayLen = keysArray.length; //配列の長さを取得
 //配列内の要素を１つずつ取得し、一致するか検証
  for(var i=0; i<keysArrayLen; i++){
    const isExisted = keysArray[i].indexOf(searchWord, 0);
    if(isExisted != -1){
      return valuesArray[i];
    }
  }
  return sub_text;
}
```

### (4)	DMの取得 (getDMmedia関数)
自分のアカウントに対し送られてきたメッセージと自分自身が送ったメッセージの両方を時系列順に取得し、自分ではないアカウントのメッセージついて、送り主のID、メッセージを送信した時刻、メッセージの内容をarrayで返す関数です。
```js
//送られてきたDMを取得する関数
function getDmMedia(){
  try {
    var twitterService = getTwitterService();
    if (!twitterService.hasAccess()) {
      Logger.log(twitterService.getLastError());
      return null;
    }
    var url = 'https://api.twitter.com/1.1/direct_messages/events/list.json?count=' + count
    var response = twitterService.fetch(url, { method: "GET" });
    var json = JSON.parse(response); //DMをJSON形式で取得

    //スプレッドシートに書き込んだ前回の最後の応答を読み込み
    //シートを読み込み
    const sheet =  SpreadsheetApp.getActiveSpreadsheet();
    // C2セルを選択
    var range = sheet.getRange('C2');
    // セルの値を取得
    var value = range.getValue();
    var userID = getUserId(userid)
    let array = [ ]
    var time_rec = value
    for(record = 0; record < json["events"].length; record++){
      //最近送信したor送信されたDMの送信時刻と送信者id、メッセージを取得
      var time = json["events"][record]["created_timestamp"]
      var senderID = json["events"][record]["message_create"]["sender_id"]
      var keys = json["events"][record]["message_create"]["message_data"]["text"]
      var dmtime = time/1000
      const jstTime = Utilities.formatDate(new Date(dmtime*1000), "JST", 'yyyy-MM-dd HH:mm:ss');
      if (senderID != userID & time > value){
        send_message = searchWords(keys)
        array.push({senderID, send_message, keys})
        time_rec = Math.max(time_rec, time)
        }
      }
    var set_values = range.setValue(time_rec);
    array = array.reverse();
    return array;
  }
  catch (ex) {
    Logger.log(ex);
    return null;
  }
}
```


### (5)	DMの返信 (sendDM関数)
DMを取得(getDMmedia関数) → スプレッドシート検索(searchWords関数) → 返信の手順でDMを返信する関数です。
```js
//DMを返信する関数
function sendDM(_, userID, text){
  array = getDmMedia();
  if (array != null){
    for (record = 0; record < array.length; record++){
      const results = array[record];
      var userID = results["senderID"];
      var text = 'お問い合わせありがとうございます。検索結果はこちらです：\n' + results["send_message"];
      
      try{
      
        var service = getTwitterService();
        var payload = JSON.stringify({
          event: {
            type: 'message_create',
            message_create: {
              target: {
                recipient_id: String(userID)
                },
              message_data: { text: text }
              }
            }
          });
        var response = service.fetch('https://api.twitter.com/1.1/direct_messages/events/new.json',{
          method: 'POST',
          contentType: 'application/json',
          payload: payload
        });
      } catch(e) {
        Logger.log('Exception:'+e);
      }
    }
    return response;
  }
}
```

# 実装手順
- (1)	Googleアカウントを作成する
- (2)	Twitterアカウントを作成する
- (3)	Twitter Developers Platformに登録する
- (4)	Googleスプレッドシートを作成する
- (5)	GASでプログラムを記述
- (6)	Twitterと連携
- (7)	テスト

### (1)	Googleアカウントを作成する
以下のページを参考にGoogleアカウントを作成してください。
[Google アカウントの作成 - Google アカウント ヘルプ](https://support.google.com/accounts/answer/27441?hl=ja)

### (2)	Twitterアカウントを作成する
ブラウザの新しく作成したタブまたはウィンドウで、以下のページを参考にTwitterアカウントを作成してください。お好みの方法で登録してください。
[Twitterアカウントを登録する方法とTwitterアカウントを作成する方法](https://help.twitter.com/ja/using-twitter/create-twitter-account)

アカウントを作成した後、「設定とサポート」→「アカウント」→「アカウント情報」→「電話」から電話番号を登録してください(アカウント作成時に電話番号を登録した場合は飛ばしてください)。

### (3)	Twitter Developers Platformに登録する
1. 以下のページにアクセス。
[Twitter開発者プラットフォーム | Twitter Developer Platform](https://developer.twitter.com/ja)

1. 図のような画面が表示されるので、右上の「新規登録」をクリック。
![TwitterDeveloperPlatform1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/2de38c15-ad67-cb9a-5ef2-22051542e79e.png)

1. 情報を埋めます。
・Twitter AccountとEmailは自動で入力されます。
・What country are you based in? → Japan
・What's your use case? → Making a bot
・Will you make Twitter content or derived information available to a government entity or a government affiliated entity? → No
・Get involved and updated → チェックしてもしなくてもOK
![TwitterDeveloperPlatform2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/cf277916-3891-504e-f927-3d3985888aac.png)


1. Developer agreement & policyに同意する。Accept Terms & Conditionsにチェックを入れて「Submit」をクリック

1. 登録したメールアドレスにメールが来るので、文中の「Confirm your Email」の黄色いボタンをクリックします。

1. アプリ名を入力して次のページに進んでください。

1. API Key や Tokenなどが表示されますが、後で再発行するので記録しなくて大丈夫です。「Dashboard」→「Yes, I saved them」をクリックしてDiveloper PortalのDashboardのページに飛びます。
	
1. 左のタブの「Products」→ 「Twitter API v2」をクリックします。上のタブ「Elevated」をクリックし、「Do you need Elevated access for your Project?」の右にある「Apply」をクリックします。
![TwitterDeveloperPlatform6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/fe20e07e-f255-cad5-9fb6-b01b6920474f.png)


1. 必要事項を埋めます。
・What’s your current coding skill level? → 適当なものを選択
・Want updates and access to more resources? → 任意でチェック
「Next」をクリック
![TwitterDeveloperPlatform7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/4f727722-6774-71bb-dadf-63a1dd9e6fd0.png)

1. Twitterのデータをどのように活用するかを英語で記述します。
今回は
・「In English, please describe how you plan to use Twitter data and/or APIs. The more detailed the response, the easier it is to review and approve.」に回答
・「Are you planning to analyze Twitter data?」→「Yes」
・「Will your app use Tweet, Retweet, like, follow, or Direct Message functionality?」→「Yes」
・他2つは「No」
にしました。
以下の内容を英語の文章としてまとめて記述しました。
・botの説明(DMに自動返信すること、5分ごとにDMを取得することなど)
・自分自身のアカウントの情報を取得し利用すること
・自分に対して送られたDMを取得し、送り主のidや送信時刻、メッセージを取得し利用すること
・ここで得た情報を外部に公開しないこと
[2021年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説](https://www.itti.jp/web-direction/how-to-apply-for-twitter-api/)

1. 次のページは確認画面になっており、さらに先に進むと利用規約に同意するページになります。利用規約の下のチェックボックスにチェックを入れて「Submit Application」をクリックし、申請します。

### (4)	Googleスプレッドシートを作成する
1. (1)で作成したアカウントのGoogle ドライブを開きます。

1. 新規のGoogleスプレッドシートを作成します。名前も変更します。

1. A1に「メッセージ」、B1に「応答」、C1に「最後に応答したDMのタイムスタンプ」と入力します。

1. 「A」をクリックしてA列全体を選択し、「表示形式」→「数字」→「書式なしテキスト」を選択
![Googleスプレッドシート1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/d1ed3ac8-d358-c29f-1280-61d5d34f4cc7.png)

1. A2より下のA列に相手からのメッセージを、B2より下のB列にそれに対する応答を入力します。今回はポケモンのパルデア図鑑番号を送ると対応するポケモンの名前を返してくれるbotを作成するつもりで、図鑑番号001~009をA列に、それに対応するポケモンの名前をB列に入力してみます。

1. 「拡張機能」→「Apps Script」を選択します。
![Googleスプレッドシート2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/b843e579-5d5c-3f29-4456-c6b5a4aa2197.png)

### (5)	GASでプログラムを記述
1. Apps Scriptのページが開けたら、「無題のプロジェクト」と書いてあるところをクリックし、名前を変更します。

1. ブラウザの新しく作成したタブまたはウィンドウで、以下のGithubのページを開き、「Twitter_DM_auto_reply.gs」のコードを全てコピーします。
https://github.com/MokonaSato/Twitter-DM-auto-reply-bot

1. Apps Scriptのタブまたはウィンドウに戻り、既に入力されているコードを全て消し、Githubのコードをペーストします。

1. 左側のタブの歯車マーク「プロジェクトの設定」をクリックし、「ID」の項目のスクリプトIDをコピーし、メモ帳アプリなどにメモします。
![AppScript2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/5b94c454-2e00-0ca5-7f9a-156a09e1aedb.png)

1. 左側のタブの<>マーク「エディタ」をクリックし、コードを貼り付けた画面に戻ります。
左から2番目のタブ「ライブラリ＋」の+マークをクリックします。
現れた画面の「スクリプトID」に「1CXDCY5sqT9ph64fFwSzVtXnbjpSfWdRymafDrtIZ7Z_hwysTY7IIhi7s」→「検索」→「追加」
同様に「ライブラリ＋」→「スクリプトID」に「1rgo8rXsxi1DxI_5Xgo_t3irTw1Y5cxl2mGSkbozKsSXf2E_KBBPC3xTF」→「検索」→「追加」
これにより、「ライブラリ＋」の下に「OAuth1」「TwitterWebService」が追加されます。
![AppScript3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/bf6e4967-fd08-76c7-0348-8a6319563375.png)

### (6)	Twitterと連携
1. ここで一度Twitter Developers Platformに戻ります。
左側のタブの「Projects & Apps」→「登録したアプリ名」をクリックし、ページをスクロールすると、このような画面が現れます。「Edit」をクリックしてください。
![TwitterDivelopersPlatform3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/eede2816-d303-c333-d319-5abc505c7992.png)

1. 項目を埋めます。
・App permissions → Read and write and Direct message
・Type of App → Web App, Automated App or Bot
・Callback URI / Redirect URL → https://script.google.com/macros/d/スクリプトID/usercallback を入力。スクリプトIDの部分は(5)4.でコピーしたスクリプトIDで置き換えてください。
・Website URL → 特に登録したいWebページがなかったため、今回はTwitterアカウントのホーム画面のURLにしました。関連するWebページがあればそのURLを登録してください。
他の項目はいじらずに、「save」をクリックします。
![TwitterDivelopersPlatform4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/c72460c5-0e77-fdde-1f0e-2d5276804bcf.png)

1. 上部のタブの「Keys and tokens」→ 「Consumer Keys」の項目の「Regenerate」をクリックします。表示されるAPI KeyとAPI Secretをそれぞれコピーし、メモ帳アプリなどに保存してください。
![TwitterDivelopersPlatform5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/e090b535-62f0-b0cf-b099-52cdb7fb398a.png)

1. ここでApps Scriptのスクリプトに戻り、2行目の`let VAL_CONSUMER_API_KEY = ''`の’’内にAPI Keyを、3行目の`let VAL_CONSUMER_API_SECRET = ''`の’’内にAPI Secretを貼り付けてください。フロッピーディスクのマークをクリックして保存します。

1. 上部のタブ、「デバッグ」の隣のプルダウンから「logOAuthURL」を選択 →「実行」をクリックしてください。
![AppsScript6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/ea59a773-45b1-5b6c-6523-62b7bb9e6890.png)

1. 「承認が必要です」の画面が出たら「権限を承認」をクリックしてください。アカウント選択の画面になったら、(1)で登録したGoogleアカウントを選択してください。以下の画面が表示されたら「詳細」をクリックし、「~に移動」をクリックします。
![警告画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/b6931689-4bf1-4b38-4be0-f1fcfd7670b8.png)

1. 「~がGoogle アカウントへのアクセスをリクエストしています」の画面になったら「許可」をクリックしてください。
![警告画面3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/26401769-21bb-1518-d302-dbeb8625f300.png)

1. Apps Scriptの画面に遷移すると、実行ログが表示されます。httpsから始まるURLが表示されるのでコピーし、ブラウザで新しいタブまたはウィンドウを作成し、検索欄にペーストし、実行します。
![AppsScript4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/5f0eb8cd-91c6-67a9-7fa2-026b9399241e.png)

1. Twitterの認証画面が出るので「Autholize Apps」をクリックします。
![認証画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/9efab9be-40dd-da08-cd92-ea6ac4a5842c.png)

1. 画面が変わり、「Success! You can close this tab.」が表示されれば成功です。タブまたはウィンドウを閉じてください。
![認証成功.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/b9901e4d-bd79-a2c8-5b71-17380dcb3e72.png)

1. Apps Scriptのスクリプトに戻り、5行目の`userid = ''`の’’内に使用するアカウントの@以下のIDを記入します。
6行目の`subtext = ""`の””内に、メッセージに対する応答がスプレッドシートに登録されていなかった時のメッセージを記入します。

1. 最左端の時計マーク → 右下の「+ トリガーを追加」
・「実行する関数を選択」→ sendDM
・「イベントのソースを選択」→ 時間主導型
・「時間ベースのトリガーのタイプを選択」 → 分ベースのタイマー」
・「時間の間隔を選択 (分)」 → 5分おき
「保存」をクリック
![AppsScript5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/227e725e-fae6-8928-438a-2e3e4c4b149c.png)

以上で、自動返信botは停止させるまで自動的に稼働します。

この自動返信サービスを終了したい時は最左端の「<>」→ 上部のタブ「デバッグ」の隣のプルダウンから「resetTwitterService」を選択、「実行」をクリックしてください。自動返信botが停止します。停止した際は、トリガーを削除してしまいましょう。

### (7)	テスト
1. (2)のTwitterアカウントにGoogleスプレッドシートのA列に登録したメッセージをDMで送ってみます。

1. (2)で登録したTwitterアカウントでTwitterを開き、「メッセージ」→ 歯車マークの「設定」から「すべてのアカウントからメッセージリクエストを許可する」にチェックを入れるとフォローしていないアカウントからもDMが来ます。運用方法によってはチェックを入れてください。
![Twitter1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/7f3ededf-60a3-79fb-3cc0-c0524b2d2a6a.png)

1. (2)で行ったのと同様の方法でテストアカウントを作成します。

1. 先ほど「すべてのアカウントからメッセージリクエストを許可する」にチェックを入れていない場合は(2)で作成したアカウントとここで作成したテストアカウントを相互フォローの状態にします。

1. ここでで作成したテストアカウントから(2)で作成したアカウントへメッセージを送ります。

1. 送信してから5分以内に送ったメッセージに対応する返信が来れば成功です。

1. 次にGoogleスプレッドシートのA列に記載していないメッセージを送ってみます。

1. 送信してから5分以内に(6)でsubtextに格納したメッセージが返信されれば成功です。
![DM応答1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/2ed1e7ea-e51e-b3ae-95f8-23d259ca4034.png)

# 応答のカスタマイズ
ここまででA列に登録したメッセージがDMで送られてきたらそれに対応するB列に登録したメッセージを返信するbotが完成しました。ここでは返信をより丁寧にするなど、カスタマイズする方法を紹介します。

### スプレッドシートに返信内容そのものを記入する
想定されるDMの送信・返信パターンが少なければアリかもしれません。
![Googleスプレッドシート3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/c425e447-de61-cdd1-3396-4568ed130dc2.png)


### sendDM関数を編集する
現在、139行目は`var text = results["send_message"];`となっていますが、例えば以下のコードに置き換えるとこのように応答することができます。改行を入れたい箇所には’\n’を入力してください。
```
var text = 'お問い合わせありがとうございます。検索結果はこちらです：\n' + results["send_message"];
```
![DM応答2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/2c363734-d250-4ea3-ecd0-ee1668142230.png)


また、文字列(「こんにちは」など)に対する応答と数字(「001」など)とでtextの設定を変えたい場合は以下のようにif文を設定します。
```
if (isFinite(results["keys"])){
        var text = 'お問い合わせありがとうございます。検索結果はこちらです：\n' + results["send_message"];
      }
      else{
        var text = results["send_message"];
      }
```

![Googleスプレッドシート4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/9688b72f-cf2c-a75b-78be-00b60ec70be1.png)

![DM応答3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/097cb4d3-aedd-9a95-de43-4bc5129612c6.png)

# おまけ：ウェルカムメッセージの設定
ウェルカムメッセージを設定すると、初めてDMを送ってくれる人に簡単な挨拶をすることができます。

ウェルカムメッセージを設定するには、フォロー外の人からもDMを送れるように設定する必要があります((7)2.参照)。

私は以下のサイトのコードをGoogle Colaboratoryで実行しました。
[【Python】Twitter APIでウェルカムメッセージを実装する方法-irucabot](https://irucabot.com/blog/twitterapi/welcomemessage_py/#4)

Google Colaboratoryの使い方はこちらを参考にしてください。
[GoogleColaboratory - OpenSquareJP | 井上貢一研究室](https://design.kyusan-u.ac.jp/OpenSquareJP/?GoogleColaboratory)

![ウェルカムメッセージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118714/2fc22394-08be-1ffa-bb6d-060bb4c01d7d.png)

# 参考にしたサイト
[GASとTwitter APIでTwitterトレンドを5分ごとにスプレッドシートに記録する](https://mangalab.hatenablog.com/entry/twitter_trend)
[TwitterAPIを使ってライバロリさんにDMを送るbotを作る【GAS(GoogleAppsScript)】
](https://zw-kakeru.com/technology/raibarori-dm-bot/)