研究室Slack用の印刷Bot「sp-print」を作った
研究室Slack用に、PDFをそのままプリンターへ投げられるBotを作った。
名前は sp-print。
研究室では、論文や資料のPDFをSlackに貼って共有することが多い。そこから印刷したいときに、いったんダウンロードして、印刷設定を開いて、プリンターを選んで、みたいな作業を毎回やるのが地味に面倒だった。
なので、SlackにPDFを投稿したら、そのままスレッド上で印刷までできるBotを作った。
使い方
使い方はかなり単純。
- 印刷したいPDFをSlackの対象チャンネルに投稿する
- Botが
:printer:リアクションを付ける - スレッド内に出てくる「印刷」ボタンを押す
- 部数、片面/両面、カラー、用紙サイズを選ぶ
- Botがプリンターにジョブを投げる
印刷状況も同じスレッド内で更新される。

- 受付
- CUPSへ投入済み
- 印刷完了
- プリンター接続エラー
- キュー待機中
- キャンセル
みたいな状態をBotが返してくれる。
どう動いているか
構成は、Goで書いたサーバー + Slack Bot + CUPS。
Slack連携はSocket Modeを使っている。Goサーバー側からSlackへWebSocketで接続して、PDF投稿イベントやボタン操作、モーダル送信を受け取る。
最初はSlackからGoサーバーへHTTPリクエストを送ってもらう構成も考えた。ただ、研究室内で動かすサーバーを外向きに公開するのは面倒だし、ネットワーク的にも避けたい。Socket Modeなら、こちらからSlackへ外向き接続するだけで済む。
sp-print server -> outbound WebSocket -> Slack
外から研究室内のサーバーへ到達できるようにする必要がないので、今回の用途にはかなり相性がよかった。
PDFが投稿されると、Socket Mode経由でイベントを受け取り、Botが対象ファイルかどうかを判定する。
対象は今のところPDFのみ。許可したチャンネル以外では反応しないようにしている。
PDF投稿を検知したら、Botはすぐには印刷しない。まず投稿にリアクションを付けて、スレッド内に印刷ボタン付きのメッセージを出す。ユーザーがボタンを押して、モーダルで印刷設定を選んだら、SlackからPDFをダウンロードして lp コマンド経由でCUPSへ投げる。
初期版ではプリンターは1台固定にした。研究室用途なら、まずはこれで十分そう。
チャンネルをうるさくしない
最初は、PDFが投稿されたらBotがチャンネルに印刷ボタンを返す形にしていた。
ただ、Slackのスレッド返信はチャンネル側にも返信プレビューとして見えるので、Botの投稿が増えるとチャンネルがややうるさくなる。
最終的には、次の形にした。
- PDF投稿にはBotがリアクションだけ付ける
- 印刷ボタンはスレッド内に1つだけ出す
- 受付、投入済み、完了、失敗などは同じメッセージを更新する
これで、チャンネル側にはPDF投稿とリアクションだけが見える。詳細な印刷状況はスレッドを開けば分かる。
Botは便利だけど、チャンネルに常駐するものなので、こういう見た目のうるささは意外と大事だと思った。
実装メモ
Goサーバー側では、主にこのあたりを実装した。
- Socket Mode接続
- Slackイベントの受信
- ボタン操作、モーダル送信の受信
- PDFファイル判定
- 許可チャンネル判定
- Slackからのファイルダウンロード
lpコマンドへの安全な引数マッピング- 印刷ジョブの状態通知
- Slackイベントの短期重複排除
Socket Modeでは、Slackから届いたenvelopeに対してACKを返してから処理を進める。印刷やファイルダウンロードみたいな重い処理は、受信処理の中で抱え込まないようにしている。
印刷設定は、Slackのモーダルから受け取った値をそのままコマンド引数にせず、許可した値だけを lp のオプションに変換するようにした。こういうところは雑にやると危ない。
作ってみて
Slack BotはSocket Modeにすると外向き公開なしで動かせるので、研究室内のサーバーで運用するにはかなり扱いやすかった。
一方で、実際に使うBotとしては、Slack上でどこに何を表示するか、失敗したときにどこまで通知するか、二重実行をどう防ぐか、みたいな運用寄りの部分のほうが大事だった。
とりあえず研究室内で使うには十分なところまでできたので、しばらく使ってみる。
便利だったら、次は複数プリンター対応や、チャンネルごとのプリンター切り替えも入れてもいいかもしれない。