LIVESENSE ENGINEER BLOG

リブセンスエンジニアの活動や注目していることを発信しています

PipedreamでSlackスラッシュコマンドを作る

インフラエンジニアの中野(etsxxx)です。

Slackで少し凝った機能・・・例えば簡単な演算ツールを作ったり、ChatOpsを行おうとすると、コードを書いて動かしたくなることがあります。しかし、やりたい処理は簡単だったとしても、従来は、”書いたコードをどこで動かすか”に悩んだものです。

Pipedreamはまさにその救世主となりうるサービスで、感銘を受けたので利用法の紹介記事を書いています。

ちなみに、この記事は元々2023年3月に書いたメモが元なので、スクリーンショットが古い部分があったらごめんなさいw

Pipedream is 何?

Pipedreamは、ZapierやIFTTTなどに代表される、サービス間を連携するサービスです。ただ、ノーコードではなくローコードと自己紹介しています。

pipedream.com

ローコード。つまり、Pipedreamは自分で書いたコードが動かせるサービスです。

そのため、サービスの連携時にちょっと凝ったことをしたいと思った時、PythonやNode.jsでコードが書けることがPipedreamの最大の特徴といえるでしょう。将来的にはGoにも対応するみたい(記事執筆時点ではBeta)で、コードを書けることに力を入れていることがわかります。Pipedream内では Source-available triggers and actions と言っています。

ちなみにPipedreamでも既に用意された機能ならポチポチするだけで連携を作れるので、ZapierやIFTTT感覚で利用することができます。さらにちなみに、厳密にはZapierなどのノーコードなプラットフォームも少しはコードが書けることが多かったりするので、Pipedreamとの差異は、実はIntegrationの量とコードに対する注力と価格設定くらいかもしれません。

価格については、2023/05/26現在は無料プランでも結構太っ腹で、約100回/日のIntegrationを実行できます。(細かな制限はここでは省略) 頻度が高いとか、重たい処理をするとか、凝ったことをするなら、有料化するほうが良さそうです。有料でも $29/月 から目的・サポート別に段階的にプランがあり、結構リーズナブルな印象を受けました。

Slackのスラッシュコマンドを作る

冒頭で述べた通り、私はローコードと聞いて利用目的に、真っ先にSlackのスラッシュコマンドを思いつきました。スラッシュコマンドの実装とは、Webhookによるサービス間連携とほぼ同義です。

ここでは、Slackのスラッシュコマンドを試験的に実装してみたので、その内容を共有します。

実装したスラッシュコマンドは、パーセントエンコーディング(=URLエンコードされた)文字列をデコードするツールです。Slackは日本語URLを自動的にURLエンコードするので、元の文字列がわからなくなって困る時があるんですよねw

こういうやつです。

https://ja.wikipedia.org/wiki/%E3%83%91%E3%83%BC%E3%82%BB%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0

これをデコードして、こういう文字列に変換したいわけです。

https://ja.wikipedia.org/wiki/パーセントエンコーディング

利用イメージ

Slackでの呼び出し方はこんな感じを予定しています。

/urldecode エンコードされたURL文字列

それをPipedreamで処理して、こんな返答をしてもらいたいと思います。

 @呼び出したユーザー
 ```
 [original]
 https://ja.wikipedia.org/wiki/%E3%83%91%E3%83%BC%E3%82%BB%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0

 [decoded]
 https://ja.wikipedia.org/wiki/パーセントエンコーディング
 ```

補足1: ブログのマークダウンの都合で、行頭に半角スペースを入れています。

補足2: スラッシュコマンドは呼び出した人にしか実行したコマンドが見えないので、チャンネルで利用する想定だと、突然Pipedreamがメッセージを送ってきたみたいに見えてしまいます。そのため、呼び出した人の名前と変換前の文字列を入れて返答しています。

PipedreamでTrigger(入力)を準備

PipedreamのTriggerは Webhook (Body Only) を雛形にします。画面でポチポチと選択すれば、すぐにテンプレートが用意されます。

このとき、自動的にユニークなURLが生成されます。これがWebhookの送信先となるURLです。

Pipedream: Triggerの設定

このスラッシュコマンドでは、Pipedreamのワークフローで処理をしてから返答(Asynchronus responses)をしたいので、Triggerの HTTP Response は空のAckとなる Return HTTP 204 No Content に変えています。(この記事での実装例だと即時に処理して返答できるレベルですが、汎用的な作りにしています)

Triggerのセットアップが進んだら、最初のWebhook Event待ちになるので、Slackの準備に行きましょう。

Pipedream: Webhook Event待ち状態

Slack管理画面でスラッシュコマンドの準備

Slackのワークスペース管理画面から Slash Command を作ります。

ここはSlackの話なので詳しくは割愛しますが、スラッシュコマンドの Request URL で先程のTriggerで払い出されたURLを設定すれば繋ぎ込みはOKです。

Slack管理画面: スラッシュコマンドの設定

Slack側の設定が終わったら、試しに適当なSlackチャンネルでスラッシュコマンドを呼び出してみましょう。WebhookがPipedreamで設定したTriggerに送られているはずです。

PipedreamでPythonステップを追加

再びPipedreamのWorkflowの実装画面に戻り、Triggerの次のステップとして、Pythonを選択して”ローコード”なステップを実装していきます。Pythonを選べば簡単なテンプレートのコードが生成されるので、そこに追記していく形です。

このとき、先ほどSlackから送ったEventの中身(=JSONデータ)を参照しながらコードが書けるので、非常に開発しやすいUIとなっているのが印象的です。(下図)

Pipedream: Workflowの実装

しかもEventは履歴として蓄積されるので、何度もWorkflowの再実行しながらデバッグができます。・・・とはいえ、Slackのスラッシュコマンドの場合は、返信用のURLに利用期限・利用回数の制限があるようで、繰り返し実行するとエラーになってしまいますがw 履歴のおかげで、うまく動いたケースと失敗したケースの比較も簡単でしょう。

今回はURLデコードするPythonコードとして以下のようなものを書きました。Slackからのリクエストに含まれている response_url に返答することで、スラッシュコマンドに対するレスポンスを実現します。

import requests
import json
import urllib

def handler(pd: "pipedream"):
  invoker_id = None
  try:
    invoker_id = pd.steps["trigger"]["event"]["user_id"]
  except:
    pass

  response_url = ""
  try:
    response_url = pd.steps["trigger"]["event"]["response_url"]
  except:
    return "response_url is invalid"  

  text = ""
  try:
    text = pd.steps["trigger"]["event"]["text"].rstrip().lstrip()
  except:
    return "text is invalid"

  decoded_url = urllib.parse.unquote(text.rstrip().lstrip())

  message = "```\n[original]\n%s\n\n[decoded]\n%s\n```" % (text, decoded_url)
  if invoker_id:
    message = "<@%s>" % (invoker_id) + message
  payload = {
    "response_type": "in_channel",
    "text": message
    }
  body = json.dumps(payload)
  r = requests.post(
    response_url,
    data=body,
    timeout=10
    )
  r.raise_for_status()
  return "status: %d, body: %s" % (r.status_code, r.text)

冒頭で def handler() から始まることに疑問が浮かぶ人もいるかも知れませんが、このあたりはテンプレートに含まれている部分なので意識しなくても大丈夫です。

pd というオブジェクトを利用してワークフローの事前のステップ(Trigger)の情報にアクセスできていることがわかるでしょうか。このように、Pipedreamではステップ間のデータのやりとりも簡単です。

余談ながら、PipedreamではKVSなストレージ(Data Storesと呼ばれています)も利用できます。試してみましたが、Pythonのshelveのように扱えてコード的にも簡単でした。これにより、異なるワークフロー間、同時発火するワークフロー間でも、データがやりとりできたりします。

動作テスト

では実装したスラッシュコマンドを試してみます。

スラッシュコマンドでパーセントエンコーディングされたURLを渡します。

動作テスト: スラッシュコマンドの送信

期待通りに返答されました。

動作テスト: スラッシュコマンドの返答

最後に雑多に感想を

なんというか、とにかく利用が簡単でした。以下箇条書きですが:

  • コードを実行する環境を意識しなくて良いことはとにかく楽で、ZapierやIFTTTを利用しているような非エンジニアに、一歩進んでローコードな連携を作ってもらうようなこともできるんじゃないかなぁと思った。
  • TriggerのWebhookは、過去に受け取ったEventをHistoryとして残してくれる。それぞれのEventからWorkflowの再実行もできるし、Workflowがエラーになった場合の再テストもできるので、至れり尽くせり。
  • ステップ間で入出力されるデータのプレビューが見やすい。さらに、そのプレビュー内の値をクリックするだけで変数として引用できる。このような入力サポートが強力で、ノーコード/ローコード開発に便利。
  • Pythonコードを書いているときにもある程度の補完が効いた。簡単な連携用コードを書く分にはそれで十分だった。
  • 今回は2段階のステップというシンプルなワークフローだが、Pipedreamでは何段もの複雑なワークフローも組めるみたい。
  • GitHub連携がうまく組めるとより良い。CLIツールもあるので、工夫すればできそうではある。
  • 余計なお世話なことを言えば、 Pipedream というサービス名で損をしている気がする(ググってみよう)ので、この単語の悪い検索結果が払拭されるくらい、人気のサービスとして流行ると良いなぁと思っている。

この記事は2023年3月に書いたメモが元と述べましたが、実はこの2ヶ月以上に渡って、この実装例のスラッシュコマンド以外にも連携を動かし続けています。途中で触れたData Storesを利用した実装です。しかも1日のリミットを使い切るような頻度で。その間もノートラブルで動き続けており、可用性でも安心感があります。 改めて、Pipedreamが流行りますように。

利用検討したくなった方は、こちらをどうぞ。

pipedream.com