python→Google Spreadsheet→LINEで、Raspberry Piでの処理状況を定期通知させる

##はじめに
これを作ろうと思ったきっかけについて。
Raspberry Piでちょっとしたバッチ処理を動かすことになったのですが、Raspberry Piに限らずサーバーを長期間稼働させていると、知らぬ間にサーバー停止していた。という事象があり得ます。
また、サーバー自体は動いているけど、何かしらの理由でバッチ処理がコケ始めるということも考えられますが、流石に毎日ログインして確認するのは手間になります。
なので、Raspberry Pi上でバッチ処理が正常か異常かの結果をスマホに通知できると嬉しいと思いました。通知がない場合はサーバーで異常が起きたと判断できますし。
それを実装する一つの方法として、
1. cronでGoogle Spreadsheetにデータ更新を行うpythonプログラムを定期実行させる
2. IFTTTでGoogle Spreadsheetの更新をトリガーにLINE通知させる
上記で実現できそうだったのでやってみた。という記事でございます。
※色々手探りでやってみた感じなので、拙い感じとなってますが。。。

##(1) Google SpreadSheetへの投稿準備

 - [Python Quickstart](https://developers.google.com/sheets/api/quickstart/python)にアクセスして、以下を実施
 - "Enable the Google Sheets API"ボタンをクリックして、適当なプロジェクト名を入力。「credentials.json」をローカルに保存する
 - 下記コマンドで専用のライブラリを導入する。

```
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
```

投稿準備について、過去の記事で分かりやすくまとまっているのでオススメです。
https://qiita.com/connvoi_tyou/items/7cd7ffd5a98f61855f5c

##(2) Google SpreadSheetへの投稿用プログラムの作成
あらかじめ、Google Drive上にSpreadsheetを作成しておきます。
ファイルURLの一部をプログラム内に記述する必要があるので、控えておきます。

```
https://docs.google.com/spreadsheets/d/<XXXXXXXXXXXXXXXXXXX>/edit#gid=zzzzzzzz
# "<XXXXXXXXXXXXXXXXXXX>"を控えておく
```

下記のような、GoogleSpreadsheetへの
「server_check.py」という名前で作っておきました。

```python:server_check.py
from __future__ import print_function
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

SCOPES = 'https://www.googleapis.com/auth/spreadsheets'
creds = None
if os.path.exists('token.pickle'):
    with open('token.pickle', 'rb') as token:
        creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
        creds.refresh(Request())
    else:
        flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
        creds = flow.run_local_server(port=0)
    # Save the credentials for the next run
    with open('token.pickle', 'wb') as token:
        pickle.dump(creds, token)

service = build('sheets', 'v4', credentials=creds)

# ここまではgoogle提供のガイドに記載されているものとほぼ同じ
# 以下、Spreadsheet固有の情報を記入。
spreadsheet_id = '<XXXXXXXXXXXXXXXXXXX>'
sheetname='シート1'
range_ = sheetname

# 日付情報を取得する
import datetime
_str_dt=datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")

# A列、B列、C列へ書き込むため、データを代入
# A列は日付を、B列、C列には任意の文字列を(今回はテスト的な文字列で代用)
values = [
  [_str_dt,"Status-1","Status-2"]
]
body = {
  'values' : values
}

# Spreadsheetへの書き込みを実施
result=service.spreadsheets().values().append(spreadsheetId=spreadsheet_id,valueInputOption='RAW',range=range_,body=body).execute()
print(result)
```

上記プログラムでは"Status-1"のようなテストメッセージにしています。
実際に運用させるときは、ログファイルの出力を抽出したりして、
values配列に追加する形になると思います。

## (3) プログラムの実行とブラウザでのアクセス許可
必要な箇所が修正できたら、上記pythonプログラムを実行します。
**その際、「credentials.json」ファイルが同じディレクトリに存在している必要があります。**
また、プログラムの初回実行時はブラウザ認証する必要があるため、GUI環境で実行する必要があります。

実行すると、ブラウザが立ち上がりgoogleアカウントの確認ページが表示されます。画面に従ってアクセス権を付与します。
(途中、安全でないページへの警告が表示されますが続けます)
"The authentication flow has completed. You may close this window."みたいな文字がブラウザに出力されると設定完了ぽいです。

アクセス権の付与が完了すると、Spreadsheetにエントリーが追加(先頭行に値が入力)されているはずです。
また、プログラムと同じディレクトリに「token.pickle」ファイルが作成されます。
このファイルがあれば、次回以降はブラウザでの認証をSKIPして、行が追加されるようになります。

## (4) IFTTTでの通知設定

1. IFTTTアプリが必要なので、ない場合はインストール＆初期セットアップ
2. "Make your own Applets from scratch"でアプレットを作る
3. This(トリガー)は「Google Sheets」->「New row addedto spreadsheet」
4. (2)で作成したシート情報を入力する
5. That(アクション)は「LINE」->「Send message」
6. Recipientは"1:1でLINE Notifyから通知を受け取る"
7. メッセージ本文をカスタマイズする

## はまったところ

### プログラム実行環境でブラウザが開けない場合
今回の最終的な目標はラズベリーパイから対象プログラムを動かすことでしたが、ラズベリーパイにはGUI環境は導入していませんでした（通常はSSH接続で作業）。
ラズベリーパイ上で"server_check.py"を実行してもブラウザが開けないため、アクセス許可の設定ができません。
＊コンソール上に表示されるURLを、PC上のブラウザにコピペすると最後の画面でエラーになってしまいます。

そういった場合ですが、別のGUI環境で同じプログラムを実行して、生成された「token.pickle」ファイルを対象のサーバーに配置すれば、CUI上でもプログラムを実行できると思われます。
自分は同じプログラムをMac上で実行して、生成されたtoken.pickleをラズベリーパイに送ることで、ラズベリーパイからも実行できるようになりました。

### プログラム実行時になんかエラーが出てしまう
たまに、プログラムを実行するとエラーが出てしまうことがありました。

```
[root@localhost python]# python server_check.py 
Traceback (most recent call last):
  File "server_check.py", line 17, in <module>
    creds.refresh(Request())
  File "/usr/local/lib/python3.7/site-packages/google/oauth2/credentials.py", line 182, in refresh
    self._scopes,
  File "/usr/local/lib/python3.7/site-packages/google/oauth2/_client.py", line 248, in refresh_grant
    response_data = _token_endpoint_request(request, token_uri, body)
  File "/usr/local/lib/python3.7/site-packages/google/oauth2/_client.py", line 124, in _token_endpoint_request
    _handle_error_response(response_body)
  File "/usr/local/lib/python3.7/site-packages/google/oauth2/_client.py", line 60, in _handle_error_response
    raise exceptions.RefreshError(error_details, response_body)
google.auth.exceptions.RefreshError: ('invalid_scope: Some requested scopes were invalid. {invalid=[a, c, d, e, g, h, i, l, m, ., /, o, p, r, s, t, u, w, :]}', '{\n  "error": "invalid_scope",\n  "error_description": "Some requested scopes were invalid. {invalid\\u003d[a, c, d, e, g, h, i, l, m, ., /, o, p, r, s, t, u, w, :]}",\n  "error_uri": "http://code.google.com/apis/accounts/docs/OAuth2.html"\n}')
[root@localhost python]# 
```
どういったエラーなのかはっきりと分からないのですが、自分は「token.pickle」を再作成すると解消しました。
再作成するには、「token.pickle」を削除してプログラムを実行すれば良いです。
ただ、またブラウザからの設定からになるので、ブラウザを開けないCUI環境は注意してください。

ちなみに、どういった理由で起きるのかも不明なのですが、個人的な感覚だと下記のタイミングでエラーになった気がします。

 - プログラムを大幅に改修した際
 - 別のプログラムを実行して認証を行なった際

## おわりに
これでバッチ処理の異常時や、サーバー停止時に早めに気づけるようになりましたが、毎日LINEがくるとちょっとうんざりですね。
本当は「便りが無いのは元気な証拠」スタイルで、異常を検知した際(サーバーから異常を検知、もしくは定時連絡がない)に、LINEへ通知させるようにしたかったのですが、それは次の課題ということで。