TwitterじゃんけんBot作ってみた：すずともBot①

こんにちは！すずともです。
すずともBot紹介記事第1回目ということで、今回は僕が作成した「じゃんけんボット」を紹介したいと思います！

※僕はQiita & Python初心者なので、説明が分かりにくいところや、改良した方がいいところなどあったらコメントお願いします！！

#１．じゃんけんBotとは
Twitterですずとも宛て([@SuzuTomo2001](https://twitter.com/SuzuTomo2001))に@ツイートをした際に、ツイートの内容に「グー」や「チョキ」などじゃんけんの手が入っていたら、Botがじゃんけんをして、結果を返してくれるというものです。

まぁ、実際のツイートを見てもらった方が早いですね。

###実際に稼働してる様子
下のツイートがbotTesterというアカウントからすずとも宛てに@ツイートをしたものです。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">【Qiita用】<a href="https://twitter.com/SuzuTomo2001?ref_src=twsrc%5Etfw">@SuzuTomo2001</a> パーキングエリア</p>&mdash; botTester (@botTest46558316) <a href="https://twitter.com/botTest46558316/status/1261349152690106368?ref_src=twsrc%5Etfw">May 15, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

すると、このツイートの返信はこのように返ってきます。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">私はチョキ<br>あなたの負け！<br><br>勝ち：8回<br>負け：8回<br>あいこ：6回<br><br>またじゃんけんしようね(o^―^o)<a href="https://twitter.com/hashtag/%E3%81%99%E3%81%9A%E3%81%A8%E3%82%82Bot?src=hash&amp;ref_src=twsrc%5Etfw">#すずともBot</a></p>&mdash; すずとものついったー@支配人の🐤「とりあえずナナシス」 (@SuzuTomo2001) <a href="https://twitter.com/SuzuTomo2001/status/1261349177985916930?ref_src=twsrc%5Etfw">May 15, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

botTesterは「パーキングエリア」という文を送ったのですが、Botはその「パー」という部分をじゃんけんの手として捉えます。Botはチョキを出したので、今回はbotTesterの負け(すずともBotの勝ち)ですね。

###仕様
- 手として反応する文字
    - カタカナ文字：「グー」「チョキ」「パー」
    - ひらがな文字：「ぐー」「ちょき」「ぱー」
    - 絵文字：「✊」「✌」「✋」（他の似たような絵文字は非対応。👊はグーというよりは殴られてるからねw）
    ※痛文字には非対応。需要があれば対応してもいいけどめんどくさいので後回しw
- 反応するツイート
    - ツイートの本文に「@ SuzuTomo2001」とすずとも宛てにメンションされたツイート
    - すずとものツイートへのリプライ（内部的にはリプ文の最初には「@ SuzuTomo2001」が入っているため）
- 勝敗記録の保存
    - データベースにて各Twitterアカウントごとに勝敗数を記録しており、じゃんけんをするごとに更新されじゃんけんのリプライにこれまでの勝敗数が表示される。
    - アカウントはユーザID(@~のやつじゃないよ)で区別されているため、アカウント名やユーザネーム(@~)を変更しても影響しない。
- その他
    - 1ツイートに複数の手が含まれている場合は、すべての手に対してじゃんけんが行われる。文の最初の方から順番に実行される。「うまくパーキングできた！✌」というツイートの場合には、最初に「パー」に対するじゃんけんのリプ、次に「✌」に対するじゃんけんのリプというように返信される。
    - すずともに相談してくれればだいたいのことはしてあげられる。（勝敗記録のリセット~~・改ざん~~、アカウント変更による勝敗記録の移行など）

#２．じゃんけんBot 処理の流れ
プログラムの流れはこんな感じになります。

1. 自分に@ツイートが来たら、本文を解析する。
1. 本文に対象となる文字が含まれていたら、じゃんけんをする。
1. じゃんけんを行って、結果をデータベースに記録する
1. 勝ち負けとこれまでの勝敗記録が記された文を返信する。

#３．言語
- python3.6

といっても、僕はCしか使ったことない人間だったのでコードについては自信ないです。

#４．コード書いていこう！
Twitter APIを使えるライブラリをググってたら、「Tweepy」というものがありコードも簡単そうだったのですが、開発終了したそうなので、別のライブラリを使おうと思います。

Twitter APIにはOAuth1.0という仕組みで接続するようなので、そのためのライブラリ「requests_oauthlib」というものを使っていきます。インストールされてない方は、pipでインストールしてあげてください。（requrstsというライブラリも必要なので一緒にインストールしていきます。）

`$ pip install requests requests_oauthlib`


##（１）APIを使う準備をしよう
Twitter APIを使うためには、

- Consumer API
- Consumer API secret
- Access token
- Access token secret

の4つのキーが必要となります。今回はこの4つのキーは取得済みということで進めていきます。
また、使用方法によってはこのAPIキーを別のスクリプトからも使用する可能性があるため、汎用性を考え別ファイルにキーをまとめていきたいと思います。

こんな風に`twitter_api_key.py`というファイルを作成します。キーは各自の物を入れてください。
Pythonは変数の宣言がいらないので楽ですね。

```python:twitter_api_key.py
# JankenBot Key
janCK = '' # Consumer API key
janCS = '' # Consumer API secret key
janAT = '' # Access token
janAS = '' # Access token secret
```

では、さっきのライブラリと、このAPIキーをメインのファイルから読み込んでみましょう。

```python
from requests_oauthlib import OAuth1Session

from twitter_api_key import (
    janCK as CK, # janCK を CK に置き換える
    janCS as CS, # janCS を CS に置き換える
    janAT as AT, # janAT を AT に置き換える
    janAS as AS  # janAS を AS に置き換える
)
```

こんな感じでrequests_oauthlibのOAuth1Sessionという関数を読み込んであげます。そしてさっきのファイルのAPIキーも読み込んであげます。
この時、APIキーの名前`jan~`をCK,CS,AT,ASと置換えてあげると、後々別のAPIキーを使いたくなった時にこの部分を変えるだけでいいので楽だと思います。


##（２）次は、@ツイートの監視
自分宛てに@ツイートがされたかどうかというのを知るためには、世の中のツイートを監視する必要があります。
しかし、ツイートなんて全世界ですごい速度でされているので全部のツイートの中から調べるなんて無理ですね。

そこで、Twitter APIの「[Filter realtime Tweets](https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter)」というものを使っていきます。
これは、検索条件を指定して、その条件にマッチしたツイートをリアルタイムで取得できるという機能です。

検索条件はいろいろ指定することができますが、無料プランだと「キーワード(track)」と「位置(locations)」という条件が指定できるようです。
今回はキーワードに「@ SuzuTomo2001」と指定して絞り込むことで、自分宛ての@ツイートをリアルタイムで取得することができます。


コードはこんな感じです。

```python
import sys
import json

myUserName = '@SuzuTomo2001' # 監視するツイッターのUserName(@~のやつ)

if __name__ == '__main__':
    try:
        # auth情報の取得
        auth = OAuth1Session(CK,CS,AT,AS)
        # リクエストを送る
        request = auth.post('https://stream.twitter.com/1.1/statuses/filter.json',
                           params = {'track':myUserName},
                           stream = True)
        
        # リクエストのステータスコードを確認
        if request.status_code == 200:
                if b: # 空文字でないかの確認(30秒おきに接続確認のため空文字が送られてくる)
                    # 取得したJsonデータ(バイト列)を辞書形式に変換
                    status = json.loads(b)
                    GetTweet(auth,status)
        else:
            print(f'HTTP ERRORE: {request.status_code}')

    except KeyboardInterrupt: # Ctrl + C で強制終了できる
        print('End')
    except: # その他のエラー
        print('except Error:',sys.exc_info())
```

`if __name__ == '__main__'`は、基本必要ないですが、慣習的に入れておきます。気になる方は調べてみてください。

また、HTTPリクエスト系は弾かれることも多いので一応tryを使って処理をしておきます。
では、その中身を解説していきます。

####認証情報の取得
```python
auth = OAuth1Session(CK,CS,AT,AS)
```
このようにOAuth1SessionにAPIキーを渡してあげることで、認証情報を取得することができます。

####リクエストを行う
```python
request = auth.post('https://stream.twitter.com/1.1/statuses/filter.json', params = {'track':myUserName}, stream = True)

if request.status_code == 200:
    #したい処理
else:
    print(f'HTTP ERRORE: {request.status_code}')
```
Twitter APIの公式ページ「[Filter realtime Tweets](https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter)」を見てもらえばわかりますが、ResourceURLは「https://stream.twitter.com/1.1/statuses/filter.json 」となります。

また、パラメータとしてdictionaryを渡してあげます。
今回はキーワードに「@ Suzutomo2001」を入れるため、keyは`'track'`、valueは`myUserName`とします。
テスト用に別アカウントで動作させることも考え、`myUserName`という変数をあえて作っています。

そして、`stream = True`とすることで、ストリーミングができ、リアルタイムで情報を取得することができます。

リクエストしたら、`status_code`を確認します。
今回は簡易的な実装のため、成功したとき(`status_code == 200`)のみの処理しか書いていませんが、本来ならばエラーが帰ってきたときの処理も必要となります。
これはまた今度、完璧な処理方法について記事を書こうと思ってるので詳しくはその機会に。

とりあえず成功しなかった時のステータスコードだけ表示するようにしています。
特によくあるエラーを下記に示しておきます。詳しくは[ここ](https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/connecting)の「HTTP Error Codes」という項目を見てください。

| status_code | エラー内容と対処法 |
|:------------|:-----------------|
| 401         | OAuth情報が間違っています。正しいAPIキーを渡しているか確認してください。 |
| 420         | APIへの接続頻度が高すぎます。（とはいってもプログラムデバッグ中はよくあること）15分ほど待ってから再接続を試みてください。 |

####したい処理を書く
上記のように、ステータスコードが200であることを確認出来たら、次は実際に受け取ったツイートの処理を行っていきます。

```python
for b in request.iter_lines():
    if b: # 空文字でないかの確認(30秒おきに接続確認のため空文字が送られてくる)
        # 取得したJsonデータ(バイト列)を辞書形式に変換
        status = json.loads(b)
        GetTweet(auth,status)
```

ここでは、for文を使ってリアルタイムに来たツイートの処理を行っています。
僕も詳しくはわかりませんが、監視中はこのfor文をずっとループするような感じですね。

`b`に現在のツイート情報が入ったJson形式のデータがバイト列で入っています。
これを標準ライブラリjsonを使って辞書形式に直しておきます。(その方が後々の処理が楽なので)

そしてGetTweetという関数にauth情報とJson形式に直したツイートを渡してあげましょう！
じゃんけんの処理はこの関数で書いていきます。

---
やっとツイートを監視する準備が整いました。
ここまでのプログラムは他のツイート監視システムでも使えると思うので、参考にしてください。


##（３）「手」の抽出
さて、ここでじゃんけんの判定と生きたいところですが、まだ、ツイート本文から「手」となる文字を抽出できてません。
ということで、「手」を抽出するコードを書いていきます。

コードはこんな感じ

```python
import re

hands = [["グー","チョキ","パー"],
         ["ぐー","ちょき","ぱー"],
         ["✊","✌","✋"]]

def GetTweet(auth,status):
    reText = "|".join(sum(hands,[]))
    # ツイートの本文から手を抽出
    userHands = re.findall(reText,status['text'])
    
    for hand in userHands:
        Judge(auth,status,hand)
```

本文から手の抽出に正規表現を使うので、正規表現の標準ライブラリ`re`をインポートしておきます。

`hands`には、判定したい文字を2次元配列で入れておきます。
グーチョキパーの順で上記のように配列に入れておくことで、カタカナ、平仮名、絵文字に対応しています。

まずは、本文に`hands`の文字が入っているかを確かめたい(この時手の種類は関係ない)ので、2次元配列を1次元配列にまとめてしまいましょう。いろいろな方法はありますが、今回は標準で入ってるsum関数を使いたいと思います。第2引数には`[]`を指定してください

```python
sum(hands,[]) # = ["グー","チョキ","パー","ぐー","ちょき","ぱー","✊","✌","✋"]
```

これら全部の要素をORで検索します。正規表現では`|`がORを表すので、全部の要素を`|`でつなげるために文字列のjoin関数を使います。

```python
reText = "|".join(sum(hands,[]))
# reText = グー|チョキ|パー|ぐー|ちょき|ぱー|✊|✌|✋
```

本文の中に入っている手を”すべて”検索するために、reモジュールのfindallという関数を使います。
第1引数に正規表現の文字列を、第2引数に対象の文字列を指定することで抽出できます。

```python
userHands = re.findall(reText,status['text'])
```

`status['text']`でツイートの本文を取得できます。reTextにマッチする部分を配列にして返してくれます。

>
ツイート本文：'@SuzuTomo2001 パーキングできた✌'
マッチした配列：['パー','✌']

そして、配列をfor文を使って順番にJudge関数に渡してあげます。このときauthの情報`auth`とツイートの情報`status`も渡してあげましょう
Pythonもこのあたり楽ですね。C#もforeachというのがありますが…

```python
for hand in userHands:
    Judge(auth,status,hand)
```

---
さて、次からは本題、じゃんけんの判定に行きたいと思います！


##（４）じゃんけんの判定
まずはコードから。

```python
import random
def Judge(auth, status , userHandStr):
    # ScreenNameの取得(@~のやつ)
    screenName = status['user']['screen_name']
    # 自分のアカウントで自分にじゃんけんをすると無限にじゃんけんが続いてしまうためここで弾く
    if screenName == myUserName:
        return
    
    # 変数の初期化
    userHand = handType = 0
    for i ,i_item in enumerate(hands):
        for j , j_item in enumerate(i_item):
            if (j_item == userHandStr):
                userHand = j
                handType = i
    
    # Botの手をランダムで決定
    botHand = random.randint(0,2)

    # じゃんけんの判定
    judge = (userHand - botHand + 3) % 3
```

変数として、`userHand`と`handType`を用意します。
2重for文を使って、ツイートに含まれていた手の形(`userHand`、グー、チョキ、パー)と、種類(`handType`、カタカナ、平仮名、絵文字)を取得します。
enumerateを使うことでfor文の中で要素とその要素のインデックスが同時に得られます。

`botHand`を標準ライブラリのランダムモジュールを使って0~2の間で生成しています。

`userHand`も`botHand`も、`hands`配列を見ればわかる通り、0=グー、1=チョキ、2=パーですね！

そしてじゃんけんの判定をします。この記事の中でここが一番の肝です！
式を見て、分かりますか？

ここを詳しく解説しようと思うと、QiitaのMarkdownだと見にくかったりするので、下記のサイトを参考にしてください！そんなに難しくはないです。

[じゃんけん勝敗判定アルゴリズムの思い出](https://staku.designbits.jp/check-janken/)

初めはif文使って全部判定していたのですが、コードが長くなって
スマートじゃないなぁと思っていたところこのサイトに出会いました。
1行で処理できるなんて目からウロコですね！！

この式で得られる`judge`は、0=あいこ、1=(ユーザ側の)負け、2=(ユーザ側の)勝ちとなります。

---
この後に、データベースの処理を書いていきます。

##（５）データベースの取得と更新
ここでは、先ほど算出したじゃんけんの結果をデータベースに記録するコードを書いていきます。
使用するデータベースは`MySQL`です。

####データベースの準備
あらかじめ下記のようなテーブルを作成しておきます。

|userid|name|win|lose|draw|
|:----:|:--:|:-:|:--:|:--:|
|ユーザID|スクリーンネーム(@~のやつ)|勝ち数|負け数|引き分け数|

useridはBIGINT(20)
nameはVARCHAR(255)
win,lose,drawはINT(11)
としてあります。
また、userIdをPrimary Keyとすることで、同じユーザのデータ重複を防げます。
nameはあとでデータベースのデータを見たときに分かりやすくするために用意しているだけなので、なくても構いません。

jankenbotというスキームにresultという名前の上記のようなテーブルがあるという前提でコードを書いていきます。

####データの取得

```python
# import MySQLdb を行ってください。
# まだライブラリをインストールしてない方は、
# $ pip install mysqlclient
# でインストール。

def UpdateDatabase(status,result):
    # UserIDとScreenNameの取得
    userid = status['user']['id']
    screenName = status['user']['screen_name']
    
    # データベースに接続
    conn = MySQLdb.connect(user='userName',passwd='password',host='localhost',db='jankenbot',charset="utf8mb4")
    con = conn.cursor()

    # SQL文の発行
    con.execute(f"SELECT draw,lose,win from result where userid={userid}")
    # 結果の取得
    data = con.fetchall()
```
データベースを取得する際の`userName`と`password`は各自の物を入力してください。
また、今回は同じサーバ(パソコン)の中にデータベースがありますので、`host='localhost'`としています。

#####SQL文

```python
curs.execute(f"SELECT draw,lose,win FROM result WHERE userid={userid}")
```
このようにSQL文を発行します。
これは、UpdateDatabase関数の引数にとったuseridと一致するidを持った行のdraw,lose,winのカラムを取得する文です。

取得順はさっき計算したじゃんけんの判定式の結果に対応する順番に取得していますので、データベースに登録されているカラムの順番とは異なっていても構いません。

fetchall関数でデータの取得が出来ます。タイプは2次元のタプル型となります。
1次元目がデータの行(データの個数)、2次元目がカラム(draw,lose,win)となります。

今回取得するデータはuseridカラムがPrimary Keyに設定されているため、常にデータ件数(1次元目の個数)は1となるため、実際のデータは下記のように取得できます。

```python
print(data[0][0]) # 引き分けた数
print(data[0][1]) # 負けた数
print(data[0][2]) # 勝った数
```
で取得できます。

####データの処理
先ほどのUpdateDatabase関数に次のコードを追記してください。

```python
# 前のコードに追記

history = [0] * 3
if len(data) != 0:
    for i in range(3):
        history[i] = data[0][i]

history[result] += 1
```

ここでは`history`という配列を作成して、先ほど取得したデータを移しています。
改めて配列に移す理由としては、

- `data[0][0]`というように2次元だと扱いにくい
- 取得したデータはタプル型なので値の変更ができない
- データがない場合もあるので、`history`配列を用意することでデータがない場合にも対応できる

というような理由があります。

そして`history`配列の`result`番目の値を1足すことで、じゃんけんの判定を配列のデータに反映させます。

####データベースのデータ更新
データ処理のコードの後にこのコードを追記してください。

```python
# 前のコードに追記

# データベース更新
con.execute(f'REPLACE INTO result (userid,name,draw,lose,win) VALUES({userid},"{screenName}",{history[0]},{history[1]},{history[2]})')
conn.commit()
```

REPLACEを使うことで、すでにデータがあった場合には値が上書きされ、なかった場合には新規登録されるようになります。

SQL文を発行した後には、`conn.commit()`を実行して処理を確定してください。

####戻り値
データ更新のコードの後に追記

```python
# 前のコードに追記

con.close()
conn.close()

return history
```

最後にデータベースを閉じます。
そして、リプライ文に使用する勝敗数が必要なため、`history`を戻り値として返します。

---
これにてデータベース関連のプログラムは書き終わりました！

ここで、もう一度Judge関数にもどってコードを追記していきます。

##（６）リプライの送信！

```python
# グローバル変数として追記
results = ["とあいこ！","の負け！","の勝ち！"]

#Judge関数に追記

history = UpdateDatabase(status,judge)

replyText = f'@{screenName}'
replyText += f'私は{hands[handType][botHand]}\n'
replyText += f'あなた{results[judge]}\n'

replyText += f'\n勝ち：{history[2]}回\n負け：{history[1]}回\nあいこ：{history[0]}回\n'
replyText += '\nまたじゃんけんしようね(o^―^o)'
replyText += '\n#すずともBot'

request = auth.post('https://api.twitter.com/1.1/statuses/update.json',
                    params = {'status' : replyText, 'in_reply_to_status_id' : status['id']})
```

#####リプライ文の作成
まずは、先ほど作ったUpdateDatabase関数を実行して、勝敗数を得ましょう！

そうしたら、リプライ文の作成です。

まずは、@~を付けてリプ用のメンションに。

そして、Botの手を出します。このとき、Judge関数の前の方で取得した`handType`を使うことがポイントです。
これにより、相手がカタカナで出して来たらカタカナで、絵文字で出して来たら絵文字で。というように相手に合わせた種類の手を出すことができます。

次に、判定を書きましょう。ここでも一工夫です。
上記のコードのように、`results`という配列を用意します。
これは、判定を計算した`judge`に対応してます。配列番号0=あいこ、1=負け、2=勝ち でしたね！
この配列をつくることで、動的に文を作ることができます。
「あなた"とあいこ"、あなた"の負け"、あなた"の勝ち！"」 というように。

そして、勝敗数を追加して、いろいろ書いてリプライ文の完成です。

#####リプライの送信
リプライ文を送信するために(というかツイートを送信するために)使うURLは
「https://api.twitter.com/1.1/statuses/update.json」
です。

送信するパラメータは2つ。
リプライの本文(status)と、リプライをするツイート(in_reply_to_status_id)です。
この2つをDictionary型にします。

今回は確認してませんが、`request.status_code`が200だったらツイート成功です！
お疲れ様でした！

######in_reply_to_status_idについて
Twitterでは、ツイート1つ1つをIDで識別しています。
もちろん、これはユーザを識別するIDとは別になっています。

リプライをするときには、その対象となるツイートが必要です。
今回は、`status['id']`で監視したツイートのIDが取得できるため、それをin_reply_to_status_idにすることで、そのツイートに返信という形をとることができます。

もし、in_reply_to_status_idを指定しなかったら、自分の普通のツイートとしてツイートされる（空リプみたいになる）ので注意していくださいね。
※テスト中に「あれ？リプライ来ないなぁ。」って思ってたらTLに文だけ垂れ流してたということもあったのでw

他のパラメータについて詳しく知りたい方は、APIのページ（https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update ）を見てください。

#５．Ubuntuでのプログラム常時起動
一応、すずともBotは自宅のサーバ(Ubuntu Server)で動いているのですが、その方法も示しておきます。

普通にできたPythonファイルを実行すると、プログラムが終了しないと思います。このままだと、他のことができないと思うので、バックグラウンドで動作するように＆サーバの再起動でプログラムが自動起動するようにしたいと思います。

そのためには、systemdという機能を使います。

systemdのサービスを追加するには`/etc/systemd/system/`にファイルを追記します。

```:jankenTwitter.service
[Unit]
Description = janken Twitter Reply App

[Service]
ExecStart = #pythonファイルの絶対パス
Restart = no
Type = simple
```
細かい設定は僕も詳しく分からないので省きます。知りたい方は、調べてみることをお勧めします。

まずは、ファイル名。今回は`jankenTwitter.service`としました。ファイル名の拡張子を除いた部分がサービス起動の際に必要となりますので、覚えておいてください。

`Discription`は、このサービスの説明を書きます。自分が分かりやすいように書けばいいと思います。

`ExecStart`は、サービス起動の際に実行するコマンドを書きます。今回は作ったpythonファイルの絶対パスを入力します。
※systemdからプログラムを起動する際には、７．コード全文の冒頭にある2行のコメントを入れてください。これを書いておかないと、ファイルを開くだけでPythonで実行してくれないので…

基本的には、この2点を抑えていれば動くと思います。

サービスを起動するときは、

```
$ sudo systemctl start jankenTwitter
```
としてください。`jankenTwitter`というところには、自分が作成したファイル名を入れます。
今回は`jankenTwitter.service`というファイルで作成してありますので、`jankenTwitter`で起動が出来ます。
※文字通り、`start`を`stop`に変えればサービスを終了できます。

起動が出来たら、

```
sudo systemctl status jankenTwitter
```
で起動状態を確認してください。
`active(running)`となっていたら起動成功です！

サーバを再起動した後にこのサービスを自動で実行するときには、

```
$ sudo systemctl enable jankenTwitter
```

で自動起動設定が完了です。
再起動後にstatusをみて起動できているかを確認してみてください。


####はまった点
systemdを使うときにいくつかはまりポイントがあったので、説明しておきます。

1.ファイルの権限
　systemdで起動する際にはrootで起動するみたいです。rootに実行権限がないとエラーを吐かれます
2.ライブラリ関係
　systemdで起動すると、さっきも言った通りrootで起動されます。
　このとき、Pythonもrootで実行されますので、ライブラリもroot権限で使える場所にインストールされていないとライブラリが読み取れずエラーを吐かれます。
　具体的には、pipでインストールするときに、`sudo pip ~`をsudoを付けることで解決します。
　もしエラーを吐かれた場合にはライブラリをsudoでインストールしなおしてみてください。

#５．最後に
冒頭にも書きましたが、僕はQiita & Python初心者です。
プログラムの方ならまだ自信ありますが、記事を書く(というか人に説明する)という点では、まったく自信ありません。

人にうまく伝えるのって難しいなぁと思いながら記事やブログを書いてます。
ただ、伝わったかどうかは、反応がなければ分かりません。
なので、どんな些細なコメントでも構いません。いろんなコメントお待ちしておりますm(__)m

ここまで読んでいただいた方、ありがとうございました。

(多分いないと思うけど)もしこの記事通りにプログラムを組んでくれている方がいて、うまくできないなどありましたらコメントいただければできる限り対応したいと思います。

あと、@ SuzuTomo2001 でじゃんけんしてもいいよ～(というか歓迎)
通知見るだけで特に返事はしないし、特に何も思わないので、気軽に遊んでくれたらなぁって思います！

#６．コード全文
最後にコード全文を載せておきます。
記事内でバラバラに書いてしまった部分があるので、どこに書けばいいか分からなかったら、このコード全文をみて探してください。

また、記事内と下記のコードが違う可能性もあります。基本的には下記のコードが最終版のため、下記のコードを優先してください。
また、そのような部分が見つかりましたらコメントにて一報くださるとありがたく思います。

```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import json
import re
import random

import MySQLdb

from requests_oauthlib import OAuth1Session

from twitter_api_key import (
    janCK as CK, # janCK を CK に置き換える
    janCS as CS, # janCS を CS に置き換える
    janAT as AT, # janAT を AT に置き換える
    janAS as AS  # janAS を AS に置き換える
)


myUserName = '@SuzuTomo2001' # 監視するツイッターのUserName(@~のやつ)

hands = [["グー","チョキ","パー"],
         ["ぐー","ちょき","ぱー"],
         ["✊","✌","✋"]]

results = ["とあいこ！","の負け！","の勝ち！"]


def UpdateDatabase(status,judge):
    # UserIDとScreenNameの取得
    userid = status['user']['id']
    screenName = status['user']['screen_name']
    print(userid,type(userid))
    
    # データベースに接続
    conn = MySQLdb.connect(user='username',passwd='password',host='localhost',db='jankenbot',charset='utf8mb4')
    con = conn.cursor()

    # SQL文の発行
    con.execute(f'SELECT draw,lose,win FROM result WHERE userid={userid}')
    # 結果の取得
    data = con.fetchall()

    history = [0] * 3
    if len(data) != 0:
        for i in range(3):
            history[i] = data[0][i]

    # 判定の反映
    history[judge] += 1

    # データベース更新
    con.execute(f'REPLACE INTO result (userid,name,draw,lose,win) VALUES({userid},"{screenName}",{history[0]},{history[1]},{history[2]})')
    conn.commit()

    con.close()
    conn.close()

    return history


def Judge(auth, status , userHandStr):
    # ScreenNameの取得(@~のやつ)
    screenName = status['user']['screen_name']
    # 自分のアカウントで自分にじゃんけんをすると無限にじゃんけんが続いてしまうためここで弾く
    if screenName == myUserName:
        return 
    
    # 変数の初期化
    userHand = handType = 0
    for i ,i_item in enumerate(hands):
        for j , j_item in enumerate(i_item):
            if (j_item == userHandStr):
                userHand = j
                handType = i
    
    # Botの手をランダムで決定
    botHand = random.randint(0,2)

    # じゃんけんの判定
    judge = (userHand - botHand + 3) % 3

    history = UpdateDatabase(status,judge)

    replyText = f'@{screenName}'
    replyText += f'私は{hands[handType][botHand]}\n'
    replyText += f'あなた{results[judge]}\n'

    replyText += f'\n勝ち：{history[2]}回\n負け：{history[1]}回\nあいこ：{history[0]}回\n'
    replyText += '\nまたじゃんけんしようね(o^―^o)'
    replyText += '\n#すずともBot'

    request = auth.post('https://api.twitter.com/1.1/statuses/update.json',
                        params = {'status' : replyText, 'in_reply_to_status_id' : status['id']})
    
    
    # 確認用のprintです。
    print(status)
    
    print(userHand,botHand,judge,history)

    print(replyText)
    print (request)



def GetTweet(auth,status):
    reText = "|".join(sum(hands,[]))
    # ツイートの本文から手を抽出
    userHands = re.findall(reText,status['text'])
    for hand in userHands:
        Judge(auth,status,hand)


if __name__ == '__main__':
    try:
        # auth情報の取得
        auth = OAuth1Session(CK,CS,AT,AS)
        # リクエストを送る
        request = auth.post('https://stream.twitter.com/1.1/statuses/filter.json',
                           params = {'track':myUserName},
                           stream = True)
        
        # リクエストのステータスコードを確認
        if request.status_code == 200:
            for b in request.iter_lines():
                if b: # 空文字でないかの確認(30秒おきに接続確認のため空文字が送られてくる)
                    # 取得したJsonデータ(バイト列)を辞書形式に変換
                    status = json.loads(b)
                    GetTweet(auth,status)
        else:
            print(f'HTTP ERRORE: {request.status_code}')

    except KeyboardInterrupt: # Ctrl + C で強制終了できる
        print('End')
    except: # その他のエラー
        print('except Error:',sys.exc_info())
'''