Pipedreamを使ってGitHubのデイリーアクティビティをTwitterに報告する

とあるきっかけでPipedreamを使い始めたのですが、なかなか面白いサービスだったので愛用しています。
今回はPipedreamの紹介をしつつ、タイトルの通り「GitHubのアクティビティをカウント」して「Twitter上にツイートする」ということを解説してみます。

## Pipedreamとは

https://pipedream.com

### こんなサービス

いわゆる「ワークフローPaaS」です。

サービスやPipedream本体で発生する何かしらのイベントを「トリガー」として起点と定め、他のサービスなどの処理を「アクション」として連携実行させていきます。
このアクションとトリガーのセットを「ワークフロー」として定義して、管理・実行させる基盤を提供しています。

「トリガー」「アクション」「ワークフロー」というような表現を見て想像つく方はいると思いますが、古くはIFTTTやZapier、さらにはMicrosoft Power Automateあたりと類似したサービスです。

<details><summary>例）「Pocketで特定のタグを付けたらSlackに投稿する」ワークフロー</summary>

![share-url-from-pocket.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6805/1b990aab-7385-f08e-3a56-5fba3048eeeb.png)

画像は概要のみの表示になっていますが、Pocket側では「URLを保存した」時ではなく「特定のタグを付いた」 [^1] 時をトリガーにしています。
また、Slack側では指定したチャンネルに対して、`Saved for times by Pocket: <{{steps.trigger.event.given_url}}|URL>` というメッセージを投稿するアクションを定義しています。 [^2]

このあたりを見ると、「Zapierに似ている？」と思うかもしれません。
</details>

### 少々ユニークな特徴

前述の通り類似サービスが色々とある中で、Pipedreamにはちょっと変わった特徴があります。
それは、**「アクションを完全自作出来る」** という点です。

「完全自作」という表現をもうちょっと具体的に説明すると、Pipedreamにはサービス用の各種アクションに加えて、次のような自作を前提にしたアクションが存在します。

* Node.jsのプログラムを実行する（コードは自分で書く）
* Pythonのプログラムを実行する（コードは自分で書く）
* シェルスクリプトを実行する（コードは自分で書く）

これらの自作用アクションを定義してワークフローに組み込めるため、提供されているアクションではカバーできない範囲を自分の手で解決できるという、ノーコード系とFaaSの中間のようなサービスとなっています。

<details><summary>例）手元にある自作アクションをもとにした解説</summary>

下記のコードは、「PyPI上にあるオーナーが`attakei`=自分であるパッケージのRSSフィードURLを全て取得する」というアクションです。

```python
import httpx
from bs4 import BeautifulSoup


def handler(pd: "pipedream"):
  resp = httpx.get("https://pypi.org/user/attakei/")
  soup = BeautifulSoup(resp.content, "html.parser")
  return {
    "urls": [
      f"https://pypi.org/rss{a['href']}releases.xml"
      for a in soup.find("div", class_="package-list").find_all("a", class_="package-snippet")
    ]
  }
```

このアクション上では、「このコードを実行する」とだけ定義されていますが、当然ながら正しく動作します。
これは、アクションの実行環境が次のことをしているためです。

1. コードから`import`を検出
1. 必要なサードパーティライブラリを判定
1. ライブラリをインストール
1. インストール後にアクションを実行
</details>

## Pipedreamのワークフローを作る

ここからは記事タイトルにもあるような「GitHubのデイリーアクティビティをTwitterに報告する」ワークフローを作る流れを解説していきます。

### トリガーとアクション

この手のサービスの考え方は基本的には他と変わらず、「いつ（どんな条件で）」「何を（どんな動作）」するかの設計・整理から始まります。

今回必要なことは、この1個のトリガーと2個のアクションです。

* いつ
    * 毎日決まった時刻に
* 何を
    * 前日のGitHubのアクティビティを集計して
    * Twitter上にツイートする

#### トリガー: 毎日決まった時刻に処理する

「毎日ツイートする」ことが目的のため、**Scheduleトリガー**を利用しています。
書式に柔軟性があり、なおかつタイムゾーン指定も可能なため、特に困ること無く時間設定が出来ます。
![trigger-schedule.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6805/63feb790-a7c9-ffdb-b221-f115e6c3d7db.png)

#### アクション1: 対象アクティビティの取得

GitHubのアクティビティを取りたいのですが、GitHub用に提供されているアクションは「Issueを作る」「リポジトリの一覧を取得する」のようなものが中心でアクティビティを取得できません。
しかし、GitHubはGraphQL APIを提供しているため、事前にアカウント連携をした上で「GraphQLのクエリーでアクティビティを取得する」ことが可能です。

<details><summary>アクション内で定義しているコード全文</summary>

`python_graphql_client`はサードパーティライブラリですが、当然のように自動でインストールして実行してくれます。


```python
from datetime import date

from python_graphql_client import GraphqlClient


def get_client(token: str) -> GraphqlClient:
  headers = {
    "Authorization": f"Bearer {token}"
  }
  return GraphqlClient(endpoint="https://api.github.com/graphql", headers=headers)


def handler(pd: "pipedream"):
  token = f"{pd.inputs['github']['$auth']['oauth_access_token']}"
  client = get_client(token)
  query = """
    query($username: String!, $date: DateTime) {
      user(login: $username) {
        contributionsCollection(from: $date, to: $date) {
          contributionCalendar {
            totalContributions
            weeks {
              contributionDays {
                contributionCount
                weekday
                date
              }
            }
          }
        }
      }
    }
  """
  variables = {
    "username": "attakei",
    "date": date.today().strftime("%Y-%m-%dT00:00:00"),
  }
  result = client.execute(query=query, variables=variables)
  print(result["data"])
  return {
    "date": date.today().strftime("%Y-%m-%d"),
    "contributions": result["data"]["user"]["contributionsCollection"]["contributionCalendar"]["weeks"][0]["contributionDays"][0]["contributionCount"]
  }
```

</details>

### アクション2: ツイートする

こちらは、ありふれた処理なので最初から提供されている「Twitterにツイートする」アクションをそのまま使います。
前述のコードの`handler`関数が`date`,`contributions`をキーにしている`dict`を返しており、後続のアクションはそのを`{{ }}`形式で利用できています。

![action-tweet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6805/a81b393f-280d-b7ea-5abd-7f794a8e8328.png)
※実際にはGitLabのアクティビティも取っています

## 実際に動いている記録

このワークフローは、日々安定して動作しています。

![inspector.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6805/2e427913-6c5c-2382-3b01-ddc3f051f07e.png)

こちらは、上記画像の最下部にある11/27分のツイートです。
Twitterページの方では分かるのですが、`Pipedream, Inc`経由での投稿になっています。

https://twitter.com/attakei/status/1596884093060382725?s=20&t=1VFwRZ_Acgat3LOlgst0Pw

## まとめ

IFTTTやZapierを利用したりする際にたまに発生する「この処理はLabmdaで」といったことが無く、Pipedreamで概ね完結出来る点は利用にあたってのメリットと言えるでしょう。
また、「無いなら作る」の障壁が低いため、ソフトウェアエンジニアとの親和性がより高いサービスかなというのが、使っていての感想です。

[^1]: 基本的に節操なく保存しているので、その中で「社内に需要がありそう」なものを投げ込む専用のタグを用意しています。
[^2]: Slackの文法としては、`Saved for times by Pocket: [URL]`という記載になります。