Twitter × GASで検索したキーワードをslackに定期投稿する
前回の記事で開発者としてのアカウント申請を終えましたので、今回は実装したTwitter botについて再び手順を残していこうと思います。
...色々あって当初予定していたものとは違うものを実装することにしました。
アプリケーションの作成
ヘッダーバーに公式ドキュメントがあります。
まずはTwitter Developerの自分のアカウント名のプルダウンメニューから「Apps」を選択。
何も作ってないのでこの画面が出ます。「Create an app」でアプリケーションの作成を行います。
作成するアプリケーションの詳細を入力していきます。
どこかの記事で日本語だと申請が通りにくい、みたいな感じの書き込みをみた気がしたので、念のため英語で入力。
- App name(必須、最大32文字):アプリの名前
- Application description(必須、10〜200文字):アプリの説明
- ユーザーに表示されるアプリの説明文。どういった用途の物なのかを書いておくと良い。
実際に書いた文章 => It is an application that regularly posts the results of searching with fixed keywords on Twitter to Slack.
(前回記事同様、自分の言葉が望ましいようです。日本語で書いて英語に翻訳)
- Website URL(必須):ウェブサイトのURL
- [原文訳]ウェブサイトのURLは、アプリによって作成されたツイートのアトリビューションのソースとして使用され、ユーザー向けの承認画面に表示されます。
- ブログの新着記事投稿の自動ツイートであればブログのURLでいいんでしょうが、今回は参考サイトを例に作成する予定のGASスクリプトURL(現状は空のGAS)を新規作成し、そのURLを記載。
- [原文訳]ウェブサイトのURLは、アプリによって作成されたツイートのアトリビューションのソースとして使用され、ユーザー向けの承認画面に表示されます。
- Allow this application to be used to sign in with Twitter:このアプリケーションを使用してTwitterでサインインすることを許可するか(チェックボックス)
- Callback URLs:コールバックした時に表示するURL
- これも言葉だけではサッパリ意味不明でした。が、Webアプリでたまに見かける○○を使ってサインイン(or ログイン)の時に変遷したページから戻ってくる時のURLを入力する、ってくらいは分かりました。今回はログインもクソもないので無視。
- これも言葉だけではサッパリ意味不明でした。が、Webアプリでたまに見かける○○を使ってサインイン(or ログイン)の時に変遷したページから戻ってくる時のURLを入力する、ってくらいは分かりました。今回はログインもクソもないので無視。
- Terms of Service URL:利用規約のURL
- Privacy policy URL:プライバシーポリシーのURL
- Organization nam:組織名
- Organization website URL:組織のウェブサイトURL
この4項目は特にないので無視。必須でもない。
- Tell us how this app will be used(必須、100文字以上):アプリの使用方法
- この部分はTwitter者の従業員のみが見る部分らしいです。このアプリの使用方法と、これを使用することによる自分と自分の顧客が何をすることを可能にするのか?とあります。
アプリの要件さえ決まっていれば何となくかけます。勿論、翻訳は使いまくりですが💦
- この部分はTwitter者の従業員のみが見る部分らしいです。このアプリの使用方法と、これを使用することによる自分と自分の顧客が何をすることを可能にするのか?とあります。
一通り必須項目を入力したら、Createを押す。 とりあえず注意事項っぽいので再び翻訳。
同じ名前のアプリケーションは作れない、URLが長い、とあり失敗したので記入し直す。
で、アプリ名はすぐに通るものにできたけど、URLは流石に代用できないので困った...。
websiteの指定先としての定義みたいなものを色々探してみたところこちらの記事を参考にさせていただました。
とりあえず自分のTwitterアカウントのURLを入力。
作成完了したら詳細一覧が表示されます。
ここの「keys and tokens」でアプリケーションで使用するConsumer API keysが生成され、Access tokenの発行ができるようです。
また、PermissionsタブのAccess permissions はツイートの読み込みだけの場合は Read onlyになってるか確認する必要があるようです。
自動ツイート(書き込み)も行う場合は「Read and write」するみたいですね。
この時点でTwitter APIを呼び出す準備は整っているようです。
Googleアカウント(GAS)とSlackアカウントは既に準備済みなので省略します。
これ以降の手順として
- Slackのwebhookの口(Incoming webhook)を用意
- GASからそのwebhookを呼び出すスクリプトを書く
- それを定期実行させるトリガーをGASに設定させる
のようにしていくようです、順番にやっていきます。
Slack側の設定
検索した結果をメッセージとして送信した時の投稿先のチャンネル開設。
Slack での Incoming Webhook の利用を参考に設定をしていく。
メッセージを投稿するワークスペースでAppを選択
検索して出てきた項目を選択し、
一応、ヘッダー右上のワークスペース名を確認。
投稿するチャンネルをセレクトボックスから選択。
選択後は設定画面に飛ぶ。ページ上部の有効/無効で有効になっているか確認。下の方にはJSONデータでテキストを投稿する際の送信先Webhook URL
などが記載されている。
このアプリ設定ページへのアクセスはヘッダーバー「管理」の「カスタムインテグレーション」にある。
後は、ワークスペースを一度再読み込みしてみて、追加したチャンネルに連携した旨の投稿がされていればOK。
GASスクリプトファイルの準備
Googleドライブから開くやつかその他で直接用意する。
Slack API 公式ドキュメントを参考に、SlackのWebhook URLを呼び出すコードを書いていく。
var slackWebhookUrl = 'https://hooks.slack.com/services/XXXXXXXXXXX/YYYYYZZZZZAAAAABBBBBCCCCCDDDDDEEEEEE'; function firstApp() { var data = { 'text': 'Hello, this message is from my GAS App', }; var options = { 'method': 'post', 'contentType': 'application/json', 'payload': JSON.stringify(data) }; UrlFetchApp.fetch(slackWebhookUrl, options); }
'payload'
はslack appの設定ページに出てきましたが、webhook URLにJSON payloadでテキストを送信する、とあるのでこの形式になるようです。
LINE APIの時もそうでしたが、GASからwebhook URLを呼び出すにはUrlFetchApp
classが持っているfetch
を使い、引数には投稿先であるwebhook URL
とHTTPヘッダの内容やテキストなどをJSON形式に変換したオブジェクトにして渡す。
記述が完了したら、GASスクリプトを実行して投稿できているかを確認する。
実行したい関数を選択肢、再生マークのボタンで実行。
※ 初めて送信する場合は、前回と同様に送信することをGoogleアカウントが許可するか別窓で出てくる。詳細からプライベートページに行くように促すと、今回はブラウザではないので外部サービスへ直接送信するか許可するか聞かれる。のでこれを承認する。
ランタイムV8が悪さしてエラー吐くかもしれないと見かけたので、無効にして送信→成功。
続けて有効にして送信→成功。
送信自体は問題なくできるようです。
トリガーの設定
一定時間ごとに動かす設定をコードで書く必要はないみたい。
先ほどのGASのツールバーの再生ボタンの左にある時計みたいなアイコンをクリック。
トリガーを追加をクリックすると、モーダル状の設定画面が出てくる。
イベントのソースを選択を押すと「時間主導型」「カレンダーから」など出てくるが、ここで時間主導を選択。
特定の時間や日付ベースを選んだりすることで、そのカテゴリーによって何日とか何時とか何時間おきに、とか選べる。
今回はお昼頃に検索をかけて定期的に投稿をして欲しいので
のように選択肢、保存。
実際に来るか確認したいなら1時間おきなどにしておいて確認しておいたほうがいいかもしれない。
キチンと日付を跨いで投稿されているのが確認できました。
ひとまずGASとSlackで連動させる作業は一旦終了。
TwitterとSlackを連携させる
Slack側のドキュメントです。
まずSlackのワークスペースのサイドバー上部の「App」を押して出てくる画面の右上の「Appディレクトリ」をクリック。
立ち上がった別窓の検索欄にそのまま「Twitter」と入れて検索
Slackに追加→Twitterインテグレーションの追加→連携アプリを認証(Authorize app)をクリック
今回はTwitter上でGASを利用して自動検索されたツイートをSlackに投稿するので、ツイート追跡は特に書きません。投稿するチャンネルは既に作ってあるのでそれを指定。
インテグレーションが発言をした際の明示として、「自動キーワード検索」のような形で名前をつけておきます。最後に設定を保存(Save Setting)。
Twitter Bearerトークン取得
特定のアカウントのデータにアクセスする場合(ユーザー情報読み込み、ユーザーとしての書き込み)にはアクセストークン認証が必要。
公開されている情報にアプリケーションとしてのアクセスを行う場合にはアプリケーション認証であるBearerトークン
が必要とのこと。
(Bearerトークン = ベアラートークン = 無記名トークン)
今回の要件では個人としての、ではなくてアプリケーション上で公開されている情報にアクセスするためBearerトークン
を用いる。
認証処理の流れはドキュメントの図が利用する構文もついているので見やすいです。
とりあえず公式のApplication-only authentication and OAuth 2.0 Bearer Tokenを見ます。
- Bearerトークンを使用するとユーザーコンテキストなしでアプリケーションに代わってAPIリクエストを行うことができる、これはアプリケーションのみの認証と呼ばれる。
- Bearerトークンは、oauth2 / invalidate_tokenを使用して無効化できます。Bearerトークンが無効になると、新しい作成の試行で別のBearerトークンが生成され、以前のトークンの使用は許可されなくなります。
- アプリケーションに対して未処理で存在することができるBearerトークンは1つだけであり、このメソッドへのリクエストが繰り返されると、無効になるまで同じ既存のトークンが生成されます。
- 成功した応答には、授与されたBearerトークンを説明するJSON構造が含まれます。
- このメソッドによって受信されたトークンはキャッシュされるべきです。試行回数が多すぎる場合、リクエストはコード99のHTTP 403で拒否されます。
公式ドキュメントより翻訳して引用
1回きりの使い捨てできるトークンが使えるみたいなものかな。
Accessトークンは免許証、Bearerトークンは映画のチケットや駐車券みたいなイメージらしいっす。
Twitterリファレンス、GASドキュメントを参考にTwitterのBearerトークンを取得するプログラムを書く。
// Twitter Developer でアプリを作成した時に生成されたcomsumer-keyとsecret_keyのキーペアを入力(機密です、注意) var consumer_key = 'xxxxxxxxxxxxxxxxx'; var consumer_secret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; function searchTweetsApps() { // Twitter Bearerトークンの取得(検索APIの呼び出しに必要) // POST oauth2/token https://developer.twitter.com/en/docs/basics/authentication/api-reference/token var blob = Utilities.newBlob(consumer_key + ':' + consumer_secret); var credential = Utilities.base64Encode(blob.getBytes()); var formData = { 'grant_type': 'client_credentials' }; var basic_auth_header = { 'Authorization': 'Basic ' + credential }; var options = { 'method': 'post', 'contentType': 'application/x-www-form-urlencoded;charset=UTF-8', 'headers': basic_auth_header, 'payload': formData, }; var oauth2_response = UrlFetchApp.fetch('https://api.twitter.com/oauth2/token', options); var bearer_token = JSON.parse(oauth2_response).access_token; // Todo:Twitter 検索APIの呼び出し // Todo:Slackに通知 Incoming Webhook }
STEP1:まず、コンシューマとシークレットキーを用意し、その2つのキーをURLエンコードする必要があるそうです。
で、連結する際に:
で繋げるとあります。Utilities.newblob()
構文とUtilities.base64Encode('blob obj' + getBytes())
はGASでbase64にエンコードするを参考に作る。
STEP2:STEP1で用意したエンコードされた文字列は、POST oauth2/token
にリクエスト発行することにより、Bearerトークンと交換する必要がある...専門用語だけでは分かりませんが、何をすべきかは書いてあります。
- リクエストは
HTTP POST
であること。 - リクエストは
STEP1で用意した{Basic エンコードの値}を含むAuthorization:ヘッダー
を含める。(Basicの後に半角のスペースが必要なので注意) - リクエストは
application/x-www-form-urlencoded;charset=UTF-8
の値を持つcontentType
ヘッダーを含める。 - リクエストの本文'payload'は
grant_type=client_credentials
であること。
今回のコードではoptions
の内容はそれぞれ上記の項目を一つのオブジェクトとしてまとめたもの。
GASからURLを呼ぶ時の書き方は上記の通り。今回のリソース先はAPIリファレンス POST oauth/tokenを参考にhttps://api.twitter.com/oauth2/token
と記入し、ヘッダーはoptions
で用意したJSON形式のものを引数に渡している。
次のJSON.parse()
では文字列をJSONとして解析し、オブジェクトを構築する構文。引数にはリソース先のURLにFetchした際のレスポンスを渡している。
HTTP/1.1 200 OK Status: 200 OK Content-Type: application/json; charset=utf-8 ... Content-Encoding: gzip Content-Length: 140 {"token_type":"bearer","access_token":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2FAAAAAAAAAAAAAAAAAAAA%3DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}
アプリケーションは、返されたオブジェクトのtoken_typeキーに関連付けられた値が無記名であることを確認する必要があります。 access_tokenキーに関連付けられている値は、Bearerトークンです。
公式リファレンスより引用
このレスポンスの中から.access_token
と書くことで、最初に連結してエンコードされたBearerトークンを取得できるようです。accessトークンを渡しているならそのままaccessトークンを取得できる...と解釈。
デバッグとして関数最下にLogger.log(bearer_token);
と記述し、関数を実行。ツールバーの「表示(View)」→「ログ(Logs)」で確認できる。
Search Tweets(Twitter検索API)
Search Tweetsにはプランが3種類あって無料使用できるのはStandard search APIのみ。
翻訳。
無料版は過去7日文迄しか遡れないようですが...まぁ十分。
参照するページがあっちこっち行って分かりにくいですが、Application-only authentication and OAuth 2.0 Bearer TokenのSTEP 3(ようは続き)を見ていくと、
「Bearer Tokenを使用して、アプリケーションのみの認証をサポートするAPIエンドポイントにリクエストを発行できます。 Bearer Tokenを使用するには、通常のHTTPSリクエストを作成し、AuthorizationヘッダーにBearer <手順2のbase64 bearer token value>の値を含めます。署名は必要ありません。」(引用を翻訳し、抜粋)
とあるので、'Authorization': 'Bearer ' + bearer_token
の形でヘッダーに渡してあげてリクエストをする。
// Twitter 検索APIの呼び出し // GET https://api.twitter.com/1.1/search/tweets.json var search_keyword = 'xxxxx -rt'; var bearer_auth_header = { 'Authorization': 'Bearer ' + bearer_token }; var search_response = UrlFetchApp.fetch( 'https://api.twitter.com/1.1/search/tweets.json?q=' + search_keyword + '&lang=ja&result_type=recent&count=10', { 'headers': bearer_auth_header }); result = JSON.parse(search_response);
このコードでは検索URLの準備と、それを利用してのリクエストを行っている。 URLではエンコードされた文字を使用する必要があるようです。
search_keyword
に入れてある文字列は検索用語で、Using the standard search endpoint公式ドキュメントのテーブルの辺りに書き方が書いてありますね。
タグを検索したい場合、「#」は検索演算子として使用する場合は「%23」のように書く、また複数検索する場合の区切りとして使用するキーワード間のスペースは「%20」か「+」で書くようです。
-rt
でリツイートを検索対象から外すことができるようです。
最後には検索APIのリクエストをしている。URLはドキュメントのBest Practices にいくつか例が載っているのでそちらを参考にする。
#superbowl
で検索APIを使用する際のURL
https://api.twitter.com/1.1/search/tweets.json?q=%23superbowl&result_type=recent
- さらに、特定の言語(今回は日本語)のみを検索結果としてリクエストする場合
https://api.twitter.com/1.1/search/tweets.json?q=%23superbowl&lang=ja&result_type=recent
これらlang
やresult_type
などの使用できるパラメータ(公式リファレンス)について
recent
は最新の投稿の取得。count
はデフォルトでは15件、最大100件。今回は毎日定刻に検索を行うので10件を指定。
Twitterの検索結果をSlackに投稿する
Twitter検索を行った場合の結果の取得についてはExample Response(公式リファレンス)
statusesにオブジェクトが入っているのでkeyを使って取り出すことができる。
今回の検索結果はresult
に代入されているのでそちらからstatusesで取り出してあげればいいようです。
また、GASからSlackに投稿するコードは前述したコードが利用できそうです。
// Slackに通知 Incoming Webhook result.statuses.forEach(function(status) { var data = { 'text': '-----------------------------------------\n' + status.text + '\n----------------------------------------\n' + status.created_at + '\n ' + }; var options = { 'method' : 'post', 'contentType': 'application/json', 'payload' : JSON.stringify(data) }; UrlFetchApp.fetch(slackWebhookUrl, options); });
data
の部分は投稿される本文になりますが、今回はstatusesには複数データが入っているので、forEach文を使用して1件ずつ繰り返して投稿するような流れになっています。
最後にトリガーで定期実行するように設定。前回の関数は今後使用しないため、こちらをそのまま書き換えていく。
検索キーワード 'rails+%23Google Apps Script -rt'
実行してみたが反応せず。Logger.logでデバッグしたらnullだったので取得すら失敗してるみたい。
検索URLにエンコード以外でスペース入れてるのが問題なのかもと思い、'%23GoogleAppsScript -rt'で検索
(本当はrailsでもやってみたのですが災害情報ばっかり引っかかるので変えました(笑))
リツイートが表示されている...とりあえず公式リファレンスを参考に記事、実装コード共に修正。
後、意外と一つの単語に対して調べる場合、大分前まで遡ってるので件数は3件くらいでもいいのかも?
もしくは検索用語増やすか。
var search_response
をcount=3
に修正var search_keyword
を'%23GoogleAppsScript -filter:retweets'
に修正- 細かいが区切り線を変数
postSplit
などの名前として用意
何か格言みたいなのばっかりになったな...
参考にさせていただいた記事
公式ドキュメント/Twitter APIリファレンス一覧
GASを使って、Twitter検索した結果をSlackへ定期投稿してみた
Google Apps ScriptからSlackへ定期投稿してみた
Slack API 公式ドキュメント
Twitter REST APIの使い方
Twitterの検索結果のAPIが返すオブジェクトの公式リファレンス
先人にただただ感謝!!
APIの利用は構文の組み立て方とか、どこのドキュメント見ればいいかとか、正直難しかったけども普段使いしているアプリと直結させたりできるので使い方次第ではとても便利。
LINE API共々慣れていければいいな...
したらな❗️ 👋