Python初学者がToDoリストリマインダーbotを作ってみた

みなさんこんにちは！ @ometora_sateyan と申します。
本記事は [富士通クラウドテクノロジーズ Advent Calendar 2023](https://qiita.com/advent-calendar/2023/fjct) 20 日目の記事です。
昨日は @sameshima_alt さんの、[UbuntuでDAW環境を構築するためにN100ミニPCを買った話](https://sameshima-fjct.hatenablog.com/entry/2023/12/19/ubuntu_n100pc_daw)でした！
実際に機器を購入して、趣味の音楽のための環境構築、、、
憧れますし、1つのロマンですね。
今回構築された環境で作られる音楽作品もこれから楽しみですね！

さて本日はPythonを使ってSlack botとしてToDoリマインダーを作ってみたお話です。

## 経緯

個人的なお話で恐縮です。
現在社会人(エンジニア)歴5年目であり、情報工学を専攻していた大学時代を含めると初めてプログラミングに触れてから10年以上経っているのですが、今となってはかなりメジャーであるPythonについては実はほとんど触ったことがありませんでした。(学生時代に機械学習を扱う講義で少し触れたくらい)
そんな中、今年度からPythonを扱う業務にアサインされることになり、急遽習得が必要となりました。
そこで社内ツール等でPythonが多く使われていることを見つけ、せっかくなら勉強しながら何かモノを作ってみようと思うようになったのが、本記事のキッカケです。

## 今回やりたいこと

さて今回は **ToDoリストのリマインダー** を作ってみます。
以下の要件を設定します。
- ToDoタスクはGitLabのissueで管理
- 日常のあらゆるタスク(電球交換や家賃振込など)をissueとして作成
  - 可能ならば期限も設定
- SlackのIncoming Webhook設定を使ったbotで毎日決まった時間に積み残しのToDoタスクを通知する
  - 今回は毎朝10:00とする
- 使用言語はPython

## 用意するもの

- Slack
- GitLab
- 動作確認用の環境
  - ちなみに今回私は `VirtualBoxのVM(CentOS) + Dockerコンテナ(Pythonイメージ)` を用いました

## 作成

### プロジェクト作成

さて、まずはGitLabのプロジェクトを作成します。
今回は以下の2つのプロジェクトを作成します。
- ToDoタスク管理用
  - 今回は `todo` プロジェクトとします
  - こちらのプロジェクトの `Project ID` をメモしておきます
    - 2023年12月現在のGitLab仕様では、左メニューの `Settings` -> `General` で確認できます
- リマインダー実装用
  - 今回は `reminder` プロジェクトとします

GitLabのプロジェクト作成画面より作成します。
公開設定については `Private` にすることをおすすめします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625165/3007612c-b650-e104-81cd-56be7b3349aa.png)

### Slackのbot設定

次にSlackのbot設定を行います。
後にPythonコードでSlackを呼び出す際に必要なパラメータをこちらで設定します。
また実際に通知するときに、どういうアカウント(どういう名前でどういうアイコン)が送っているのかの見え方の設定もこちらで行います。

まず[SlackのWebhook設定のサイト](https://my.slack.com/services/new/incoming-webhook/)にアクセスします。
未ログインならログインして、右上のワークスペース欄が今回botを追加したいワークスペースになっていることを確認します。
`チャンネルへの投稿` の欄にて、今回追加したいチャンネル名を指定して、 `Incoming Webhook インテグレーションの追加` を押下します。
(今回は `#private-todo` というチャンネルに投稿するようにしてみます)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625165/135abe7b-5225-adde-d7ed-f4a65f9ca4a5.png)

ボタンを押下すると `新しい機能が追加されました！` という表示とともに、Webhookインテグレーションについての各設定の画面に遷移します。
まず冒頭の `Webhook URL` はメモしておきましょう。(後で使います)
また `名前をカスタマイズ` や `アイコンをカスタマイズする` も好みに合わせて設定します。
そして `設定を保存する` を押下して設定を保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625165/ecc2b8b0-8307-f127-76be-aceb398603a2.png)

### Pythonで実装

さてGitLabやSlackの準備も整ったので、早速実装していきましょう。
まず根幹となる処理について、以下のようなPythonコードを作成します。
```python:executor.py
import gitlab
import requests
import json
import datetime
import sys

gl = gitlab.Gitlab(private_token=sys.argv[1]) # トークンで認証しながらGitLabクラスのインスタンスを作成

project = gl.projects.get(iterator=True, id={todoプロジェクトのProject ID}) # GitLabのプロジェクト情報を取得

issues = project.issues.list(state='opened', iterator=True) # 取得したプロジェクトにてopenされているissue情報を取得

today = datetime.date.today() # 今日の日付を取得

title = "" # 全issueリスト
title_out = "" # 期限切れissueリスト
title_top = "" # 締切7日以内issueリスト
title_nor = "" # 締切が近くないissueリスト

# issue.titleがissueのタイトル、issue.due_dateが締切日となっている
for issue in issues:
  if issue.due_date is None:
    date_sub = 999
  else:
    due_time = datetime.datetime.strptime(issue.due_date, '%Y-%m-%d')
    date_sub = (datetime.date(due_time.year, due_time.month, due_time.day) - today).days
  if date_sub < 0:
    title_out = title_out + "【期限切れ :cry: 】 :skull_and_crossbones: :skull_and_crossbones: *" + due_time.strftime('%Y/%m/%d') + "ﾏﾃﾞ " + issue.title + "* :skull_and_crossbones: :skull_and_crossbones: \n"
  elif date_sub < 7:
    title_top = title_top + "【もうすぐ締切！！】 :fire: :fire: *" + due_time.strftime('%Y/%m/%d') + "ﾏﾃﾞ " + issue.title + "* :fire: :fire: \n"
  else:
    title_nor = title_nor + due_time.strftime('%Y/%m/%d') + "ﾏﾃﾞ " + issue.title + "\n"

title = title_out + title_top + title_nor
if title == "":
  title = ":tada: :tada: No task!! :tada: :tada:" # もしタスクがないとき(openされているissueがないとき)

requests.post("{作成したSlack Webhook設定のWebhook URL}", data = json.dumps({"text": "*==== :sleepy: " + today.strftime('%Y年%m月%d日') + " 本日のタスク :sleepy: ====* \n\n" + title})) # ここでSlackに投稿される

print("reminder posted!!") # おわり
```
文言などは好みに合わせてアレンジしてみましょう！

参考までに今回用いたAPIのドキュメントは以下となります。
- python-gitlab
  - https://python-gitlab.readthedocs.io/en/stable/index.html

### botの実行準備

冒頭で作成した `reminder` プロジェクトにて、先ほどの `executor.py` をプッシュします。

次にPythonコード内からGitlabを呼び出す際に必要なトークンの参照設定を行います。(もしトークン未作成の場合はあらかじめ作成しておきます)
わざわざ変数の参照設定をしなくてもpythonコード内に直に書くことで一応動きますが、トークンを誰でも見られるところに書くのはセキュリティ的にあまり良くないので、、、
- GitLabの `reminder` プロジェクトの左メニューにて `Settings -> CI/CD` と入っていき、 `Variables` にて `Expand` を押下
- `Add variable` を押下し、以下のパラメータを入力
  - Type: `Variable(default)`
  - Environments: `All(default)`
  - 以下の3つ全てにチェック
    - `Protect variable`
    - `Mask variable`
    - `Expand variable reference`
  - Key: `GITLAB_TOKEN`
  - Value: `トークンの値`
- 画面下方の `Add variable` を押下

次に実行の設定を行います。
今回は自動実行を行いたいのですが、ここでGitLabのCIジョブを活用することにします。
`reminder` プロジェクト配下に以下のファイルを作成します。
```yml:.gitlab-ci.yml
image: python:3.11.3

stages:
    - execute

execute_script:
    stage: execute
    only:
        - web
        - schedule
    script:
        - pip install python-gitlab
        - python executor.py $GITLAB_TOKEN
```

さて実行準備は整いました！
ちなみにもし別にブランチを作成している場合は、mainブランチにマージしておきましょう。

## ToDoリスト作成

それでは `todo` プロジェクト配下にissueとしてタスクを登録してみましょう。
今回は以下のようなタスクを用意します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625165/b5119560-14fc-587c-2aae-f45cbe32ea93.png)

## 自動実行設定

最後に毎日決まった時間に自動実行されるよう設定したいと思います。
- `reminder` プロジェクトの左メニューにて `Build -> pipeline schedules` と移動
- `Create a new pipeline schedule` を押下
- 以下のパラメータを指定
  - Description: `reminder`
  - Interval Pattern: `Custom` を選択して `0 10 * * *`
  - Cron timezone: `[UTC+9] Tokyo`
  - Select target branch or tag: `main`
  - Variables: (特に指定しない)
  - `Actived` にチェック
- `Create pipeline schedule` を押下

以下のようなスケジューラー設定が作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625165/da049772-6e8a-93d1-65c6-57ede958ea98.png)

## リマインダー稼働

これで毎朝10時にリマインダーが届くようになりました！
スマホ版アプリでSlackにログインしていたら、10時になると通知が届くようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625165/45a29a38-42f4-e59c-b088-f458ab0c7fe1.png)

そしてSlackの #private_todo チャンネルにもタスクが出るようになりました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625165/a828a160-2020-d926-f6af-bf0cd6f807c1.png)

## 感想

今回は比較的シンプルなものを作りましたが、複数の既存プラットフォームや既存ライブラリを組み合わせながら、どうやって実現させるか考えるのが難しいながらも楽しかったです。
今まで業務でSlackやGitLabの便利機能に触れていき、いつか自分で設定してみてから普段の生活に役立つモノを作ってみたいと思っていたので、今回それを実現することができて良かったです。
今度はもっと応用させたもの(Slackワークフロー機能を使ってみたり、わざわざGitLab画面に行かずにタスク作成できるようにしたり)を考えてみようと思います。

最後まで読んでくださり、ありがとうございました！
明日は @ystkfujii さんによる **traefikのplugin作ってみる** という内容の記事が予定されております。
近年主流のクラウド環境やDocker/k8sなどのコンテナ環境で活用されており、ルーティングを行ってくれるというtraefik、これを使ってどのようなものが出来上がるのか楽しみですね！