初心者向け Python編）Github ActionsをつかってTwitter botを作ってみたぞ

### Twitter botを作りたい
私は乃木坂４６というアイドルグループのファンで、そのメンバーがインタビューなどで語っている内容について「若いのに、なかなかステキなことを語っておるな」とかねがね感じていました。そんなわけでそいつを皆さんに共有したいなと思うものの、いちいち手動で配信するのはめんどくさいからどうしようと思ってグズグズしていたところ、なんとなく見つけたのが、個別にサーバ環境などを準備しなくても、決められた処理を定期的に実行してくれるGithub Actionsという機能（サービス）です。

この投稿では、Githubのシロウトがインターネットでググって見つかったソースをもとに試行錯誤しながら作ったTwitter botを紹介しますが、とりあえず”動けばいい”（DONE IS BETTER THAN PERFECT！）という体なので、細かい部分は目をつぶってください。

### 前提
Twitter botを作るためには色々なやり方があるみたいですが、今回は私が作って実際に動いている実績があるやり方について説明します。

具体的には言語はPythonで、OAuth1Sessionライブラリを使った実現方法ですので、PythonやOAuth1Sessionを使いたくない人は別の投稿を参照してください。

### まずやるべきこと
当たり前ですが、とりあえずTwitterのアカウントの取得が必要です。
それができたらTwitter APIを利用するための
CONSUMER_KEY、CONSUMER_SECRET、ACCESS_KEY、ACCESS_KEY_SECRET
という４つのAccess-KEYだかTOKENとか呼ばれる情報を取得してください。
コイツラの具体的な取得方法は、Twitter公式を始め、色んな人がいろいろな場所で説明していますので、そちらを参考にどうぞ。

たとえば
>twitter api access-key

などのキーワードでググるとズラズラ出てきます。

### 次にやること
Githubのアカウントを取得し、レポジトリを作っておいてください。
こちらについてもインターネットから情報を集めれば直ぐにできますよ。

>Githubの使い方

などというキーワードで登録方法などいっぱいヒットします。
Gitへ資源をpushするにはgitコマンドを使うのが玄人っぽいのですが、素人の私はブラウザからリポジトリにドロップしたり、Visual Studio codeのgit拡張を使ってローカルと同期を取ったりしています。やり方にについてもインターネットには情報がいっぱい転がってますよ。

### Github Actions ってなに？
ソースコードを管理するサービスのGithubが、処理を自動的に実行する環境を提供してくれています。それがGithub Actionsと呼ばれるものです。Github Actionsを実行するためには、ymlという拡張子をもつファイルで、何をやらせるかの定義が必要です。ymlという拡張子を持つファイルのことを、一般にヤムルファイルって呼ぶっぽい感じですが、よく知りません。とりあえずわたしはヤムルと読んでます。
このymlファイルのレポジトリ上の置き場所は決まっているので、Github Actionsのドキュメントをちゃんと読んでね。

```
 レポジトリ\.github\workflows\xxx.yml
```
みたいな感じです。

[Github公式 サンプルワークフローを作成する](https://docs.github.com/ja/actions/using-workflows/about-workflows#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%83%AF%E3%83%BC%E3%82%AF%E3%83%95%E3%83%AD%E3%83%BC%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B)

### ymlファイルの中身
ymlファイルでは様々なことを定義して実行させることができるようですが、今回はそんな細かいことは無視していますので、詳しいことを知りたい方はGithub公式のドキュメント（ところどころ、ってか重要部分はほぼ英語）を参照してください。

[Github公式 ワークフローについて](https://docs.github.com/ja/actions/using-workflows/about-workflows)

私の環境ではこんな感じのymlファイルを定義しています。
```
# ワークフロー名
name: nogizakaMeigen_bot

# 発火タイミング
on:
#   push:
  schedule:
  　- cron: '0 1,3,5,7,9 * * *'
  workflow_dispatch:
jobs:
  build:
    # Ubuntuの最新版環境内で処理
    runs-on: ubuntu-18.04

    # 実行する処理＆コマンド指定
    steps:
      - uses: actions/checkout@v2
      - name: Set up Python 3.8
        uses: actions/setup-python@v1
        with:
          python-version: 3.8
      - name: Install dependencies
        run: |
          # pip更新
          python -m pip install --upgrade pip
          # 必要なパッケージインストール
          pip install requests requests_oauthlib
      - name: Run script
        run: |
          # pythonコードの実行
          python nogizakaMeigen.py
```
name:はみてパッと分かる名前をテキトーつけとけば良いみたいです。

```
# 発火タイミング
on:
#   push:
  schedule:
  - cron: '0 1,3,5,7,9 * * *'
```
このon:の部分で実行タイミングを定義しています。push:はymlファイルを登録、更新するとすぐに実行されましたが、それ以外でどうやって起動するのか知りません。

追記）on:のとこに workflow_dispatch:を追加しとけば、ymlファイルを登録しているレポジトリのActionsを選択し、実行したいworkflowsファイルを選択すれば「Run Workflow」というボタンが表示されるので、任意のタイミングでの実行が可能になります。
またpush:を書いとけば、ymlファイルが更新されると起動するみたいですね。push:とかschedule:とかworkflow_dispatch:はズラズラ並べて書いてても良いみたいで、それぞれ自分のタイミングで勝手に発火したりするみたいです。

現在有効になっているschedule: -cron:では起動タイミング（起動させることを発火って呼びがちらしい）を指定してます。でも自分が望んだ指定時間通りに起動されないのですが、まあ動いているから良し。

ちなみに-cronの構文は
[Github公式 on scheduleの説明](https://docs.github.com/ja/actions/using-workflows/workflow-syntax-for-github-actions#onschedule)
に書いてありますが、よくわかりませんので簡単に説明すると、クォートで囲まれた部分の意味は
```
分 時 日 月 曜日
```
だそうです。詳しくは[GitHub Actions ワークフローで処理を定期実行する方法](https://gotohayato.com/content/514/)などを読めばわかりやすそうな気がします。

なお「時」はUTCなのでJSTの場合は+9となります。
私の場合、「毎月毎日、日本時間の10:00、14:00、16:00、18:00に実行」としていますが、Githubの状態によってきっかりジャストで発火はしません。10-20分くらいは遅れるのが通常っぽいです。またツイートを確認すると実際には12:00以降に1時間毎に実行されているっぽいですね。まあ動いているからいいけれど、なにか定義を間違ってるんでしょうか？

追記）
実行状況を色々と見てきたけれど、起動時間はほぼあてにならないという結論です。scheduleイベントはなんとなくそんなふうに動いてればオーケーという処理にしか使えなさっぽい。"Done is better than perfect" を実践している感じです。
企業や有料ユーザならきちんと動くんかな？

### それ以降の定義

Pythonを使って、特にバージョン指定などない場合、jobs:からrun:までの定義はそのままで問題ないと思います。
bulde:,steps:以降、色々書いてありますけど、簡単に解釈すれば

>build:でubuntu 18.04の環境つくって
>続いてsteps:で
>　Python 3.8の実行環境つくって
>　pipを最新バージョンにして
>　利用する外部ライブラリのrequestsとrequests_oauthlibをインストールする

ってだけのことですね。ってことは、Github Actionsとは、毎回実行環境を構築しているっぽいですね。もっともbotを作るときにこのやり方が正しいのかどうかはわかりませんので、詳しい人がいらっしゃったらぜひ教えて下さい。

```
- uses: actions/checkout@v2
　　　uses: actions/setup-python@v1
```
の actions/は、Github Actionsで準備されているスクリプトとかなにかだと思いますが、詳しくは調べてみてください。とりあえず動いているので、これはこれでオーケー。

さいごの
```
     - name: Run script
        run: |
          # pythonコードの実行
          python nogizakaMeigen.py
```
は、単純に同じレポジトリのmainブランチ(mainブランチってのがポイントらしい）にあるPythonで書かれた「nogizakaMeigen.py」というコードを実行しますよってことです。

### つぎにPythonで書いたコード

こいつもググって見つけたコードを最大限流用しました。
```
from requests_oauthlib import OAuth1Session
import random
import requests

CONSUMER_KEY = 'xxxxxxx'
CONSUMER_SECRET = 'xxxxxxx'
ACCESS_KEY = 'xxxxxxx'
ACCESS_KEY_SECRET = 'xxxxxxx'

url_text = 'https://api.twitter.com/1.1/statuses/update.json'
url_meigen = "https://raw.githubusercontent.com/xxxxxxx/xxxxxxx/main/nogizaka_meigen.csv"

def main():
    # 名言リストを取得する
    ans = requests.get(url_meigen)
    ans = ans.text.replace(',',' ')
    ans = ans.replace('\r\n',',')
    ans = ans.replace('\u3000',' ')
    ans = ans.split(',')
    ans_list = ans[1:]

    # OAuth1Sessionの認証処理
    twitter = OAuth1Session(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_KEY, ACCESS_KEY_SECRET)

    meigen = ans_list[random.randint(0,len(ans_list)-1)]
    
    twitter.post(url_text, params ={'status': meigen })
    
if __name__ == "__main__":
    main()
```
それぞれの項目には、最初でTwitterから取得した情報をそれぞれ定義してください。
```
CONSUMER_KEY = 'xxxxxxx'
CONSUMER_SECRET = 'xxxxxxx'
ACCESS_KEY = 'xxxxxxx'
ACCESS_KEY_SECRET = 'xxxxxxx'
```

Twitter APIの接続先URLを定義しています。この部分は引用ソースそのままなので、いちど見直したほうが良いかもしれません。

```
url_text = 'https://api.twitter.com/1.1/statuses/update.json'
```

つぶやくための元ネタはローカルの表計算ソフトで入力した後にCSV形式で保存し、ソースとは別に管理していますが、Github　Actionsでプライベートリポジトリ内のCSVファイルを直接扱えるかどうかよく分からなかったから、CSVファイルを別の公開用レポジトリにプッシュして参照するようにしてます。

```
url_meigen = "https://raw.githubusercontent.com/xxxxxxx/xxxxxxx/main/nogizaka_meigen.csv"
```

### 重要）csvファイルをローカルで作成する場合に気をつけないと泣きそうになるポイント
Githubは他のインターネットサービスと同じく、UTF-8を基本にデータが扱われます。macOSの場合は特に問題はありませんが、Windowsで、かつマイクロソフトOfficeを利用して作成したcsvファイルをそのままGithubへpushすると、文字化けするので注意が必要です。Windowsでなにかのツールをつかってcsvファイルの文字コードをshift-jisからUTF-8に変換して一度保存した後にpushしなければ悲しい気持ちになるんで注意が必要です。

### 処理してる内容の説明（続き）

main関数すぐで実行している一連の処理は、Githubから取得したCSVファイルからゴミを取り除いたり、ヘッダ情報を無視したり、参照するためのリスト形式にするために思いつきでゴニョゴニョやってるだけです。
キモは、取得した内容はCSVとして項目を区切るカンマを先にスペースに変換した後、改行コード(\r\n)をカンマにしてやれば、ステキな一次元のリストが完成します。Pythonプロの方々はもっとかっこいいコードがかけると思いますが、とりあえず動いてるからヨシ。

なお、今回はつぶやき内容の追加や削除が簡単にできるようにと思って、別リポジトリにプッシュしてるCSVファイルを参照していますが、めんどくさい場合にはソースコードに直接リスト形式でつぶやきを登録して、そいつを変数にセットすればオーケーです。流用元のコードはそうなってました。たとえば私も最初は下にあるようなリストを定義していましたし。
```
ans_list = [
　　　　　　　'いいかんじのつぶやきその１ 小野妹子 #乃木坂46',
　　　　　　　'いいかんじのつぶやきその２ 中大兄皇子 #乃木坂46',
　　　　　　　'いいかんじのつぶやきその3 厩戸皇子 #乃木坂46'
　　　　　　]
```
とかいうかんじです。

### Twitter botの最大のポイントはここからです。

認証情報をセットしてツイートを投げるためのオブジェクトを作ります。
```
    # OAuth1Sessionの認証処理
    twitter = OAuth1Session(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_KEY, ACCESS_KEY_SECRET)
```

つぶやき用のリストから、ランダムに選んでツイート変数に設定してます。ここではrandom関数をつかって選ぶやり方をしてます。
```
    meigen = ans_list[random.randint(0,len(ans_list)-1)]
```

追記）
リストからランダムに項目を選択する場合の乱数発生はrandomじゃなんとなくつまんなかったので、より予測が困難な乱数を発生させることができる（らしい）secrats関数を使うように変更してみました。別に予想が困難だろうがどうだろうが、バラバラっぽく選んでくれたらいいんですけど。ここは気分で。

リストからの選択処理は、以下の部分だけ書き換えて、問題なく動いとります。
```
#標準ライブラリだけど、使う前には肝心のsecratsのインポートを忘れずに
import secrets

meigen = ans_list[secrets.randbelow(len(ans_list)-1)]
```
で、最初に作ったオブジェクトのpostメソッドを使ってつぶやきをTwitterへ投げつければおしまい。

``` 
    twitter.post(url_text, params ={'status': meigen })
```
本当はエラー処理とか仕込んだほうが良いんだろうけど、エラーになってもちっとも困らないし、時々ログ見ときゃいいって感じだし、とりあえず動けば良しということです。

ツイッターのAPIについてわかりやすいサイトがあったので引用します。

[Twitter REST APIの使い方](https://syncer.jp/Web/API/Twitter/REST_API/#section-5)

とりあえずPythonコードが正しく動いているかの確認したいときは、コード自体は単体で動きますのでGoogle Colaboに貼り付けたりして実行してみればオーケーです。

### さいごに

Twitter botの作り方に関する情報は色々出回っていますが、とりあえず写経ででもやっていたいと思った方が、こいつをきっかけに色々と検索して自分のやりたかったことを実現できればよいなと思ってます。

現場からは以上です。