GitHub Actions を使って Qiita のviews 数を Slack に通知するアクションを python で作った

JavaScript や TypeScript で作られたアクションは割とよく見るのですが、 Python で作られた Actions は見ないので（そもそも Python で書く必要がない？）今回は Python を使って Action コードを作ってみたいと思います。

## 動作環境

Actions は Docker コンテナのアクションまたは JavaScript アクションの2種類あります。Python で書くために今回は Docker コンテナのアクションを採用します。 Docker コンテナで Python が実行できるようにベースイメージには Python を設定します。

## ディレクトリ構成

Python コードは src ディレクトリ配下に置き Docker の``COPY`` コマンドでコンテナ内にコピーさせます。``action.yml``は GitHub Actions のメタデータファイルです。
https://docs.github.com/ja/actions/creating-actions/creating-a-docker-container-action#creating-an-action-metadata-file


```
.
├── Dockerfile
├── action.yml
├── entrypoint.sh
└── src
    ├── main.py
    ├── qiita.py
    ├── requirements.txt
    └── slack.py
```

```yaml:action.yml
name: 'Slack Notify'
description: 'You can notify slack of GitHub Actions.'
runs:
  using: 'docker'
  image: 'Dockerfile'
```

## モジュール

slackに通知するためのコードで sdk を使用できるようにするために、``slack-sdk``モジュールをインストールします。

```plaintext:requirements.txt
slack-sdk==3.5.1
```

## ビュー数の取得

Qiita 記事のビュー数を取得するコードを書いていきます。ビュー数を取得するためには個別記事を取得する必要があります。そのためにまずは記事一覧取得APIを実行して一覧を取得し、取得したデータから個別記事のIDを抜き出して個別記事取得APIを叩きビュー数を取得します。qiita api のトークンは環境変数設定して取得できるようにします。

```python:qiita.py
import urllib.request
import json
import os

def sendRequest(url):
    req = urllib.request.Request(url)
    req.headers = {
        'Authorization': 'Bearer ' + os.environ['TOKEN']
    }
    with urllib.request.urlopen(req) as response:
        decode = json.loads(response.read().decode('utf-8'))
    return decode

def getIds(url):
    decode = sendRequest(url)
    ids = []
    for s in range(len(decode)):
        ids.append(decode[s]['id'])
    return ids

def getViews(ids):
    originUrl = 'https://qiita.com/api/v2/items/'
    views = []
    for s in range(len(ids)):
        url = originUrl + ids[s]
        decode = sendRequest(url)
        views.append({
            "title": decode['title'],
            "views": decode['page_views_count']
        })
    
    return views

def views():
    url = 'https://qiita.com/api/v2/users/kiyo27/items?page=1&per_page=20'
    ids = getIds(url)
    return getViews(ids) 
```

## slack に通知

slack から sdk が提供されているのでそれを使用します。webhook url は環境変数に設定しておきます。

https://slack.dev/python-slack-sdk/webhook/index.html


```python:slack.py
import os
from slack_sdk.webhook import WebhookClient

def notify(views):
    url = os.environ['WEBHOOK']
    webhook = WebhookClient(url)

    blocks = []
    for s in range(len(views)):
        title = views[s]['title']
        viewCount = views[s]['views']
        text = title + "\n:star::star::star::star: "
        text = text + str(viewCount) + "views"
        blocks.append({
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": text
            }
        })

    response = webhook.send(text="fallback", blocks=blocks)
```

## コンテナ作成

ベースイメージに python を設定して、qiita の view 数を取得するコードと slack に通知するコードを``COPY``コマンドコンテナ内に取り込みます。コンテナ起動時に実行されるブートコードとして``entrypoint.sh``を設定しておきます。

```Dockerfile:Dockerfile
FROM python:3.9.5-slim-buster

COPY src /usr/app
COPY entrypoint.sh /entrypoint.sh

RUN pip install -r /usr/app/requirements.txt \
    && chmod +x /*.sh

ENTRYPOINT ["/entrypoint.sh"]
```

``entrypoint.sh``の中身。``main.py`` を実行しています。

```shell:entrypoint.sh
#!/usr/bin/env bash

python /usr/app/main.py
```

``main.py``の中身。コマンドラインから実行されたとき、 run メソッドを呼び出します。 run メソッドでは qiita の個別記事の view 数を取得し、取得結果を slack に通知するロジックにしています。

```python:main.ph
import qiita
import slack


def run():
    views = qiita.views()
    slack.notify(views)

if __name__ == "__main__":
    run()
```

## Actions で実行

作成したアクションを実行するために、 Actions の workflow を作成します。``uses``に先ほど作成したアクションを指定します。サンプルアクションは[こちら](https://github.com/kiyo27/action-slack-notify)で GitHub に上がっています。 Qiita API トークンと Slack Webhook URL はコード内では環境変数から取得しているので、 workflow 構文で``env``を使用して環境変数を設定しています。環境変数として設定する値は、GitHub の シークレットを利用して設定しておきます。設定の仕方は下のリンクから確認できます。あとはこの workflow ファイルを GitHub にプッシュすればアクションが実行されて slack に qiita の 個別記事ごとの views 数が記載された状態で通知がきます。

https://docs.github.com/ja/actions/reference/encrypted-secrets

```yaml:workflow.yml
on: [push]

jobs:
  slack_notify:
    runs-on: ubuntu-latest
    name: A job to send a message to slack
    steps:
    - name: Slack notification
      id: slack
      uses: kiyo27/action-slack-notify@main
      env:
        TOKEN: ${{ secrets.TOKEN }}
        WEBHOOK: ${{ secrets.WEBHOOK }}
```

![Desktop-screenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/780549/65146a58-86d8-03ee-8e6e-1617d4a27b7a.png)