slackにドキュメントを投げると印刷してくれるプリントbotを作った

作りました。

備忘録。

サークルとか家とか、セキュリティガバなところ向けです。

ネットワークがどこにつながっていても、slackにドキュメントを投げるだけで印刷してくれるので地味に便利。

構成の概要

  • ラズパイ4(3でも余裕)
  • ubuntu server
  • cups

ラズパイとプリンタを設定

まずはubuntu serverをラズパイにインストール。ラズパイ公式が出してるRaspberry Pi Imagerを使うとすんなり入れられて楽。

serverなのでGUIはありません。ここは好みなのでDesktopでも無問題です。

印刷サーバとしてCUPSを入れます。デフォルトで入っているかも。CUPSはLinuxでよく使われている印刷システムです。タイ語感ある。

$ sudo apt install cups

この辺りは基本的にCUPSを使ったっプリントサーバの構築なので、たくさん情報が落ちています。ウェブブラウザから設定をいじれるようにして、プリンタの追加とかまでしちゃうのがおすすめ。

同じネットワークにプリンタをおいて、lpコマンドで適当な画像とかをデフォルトプリンタで印刷できるようになったら設定はOK。


Slackの設定

slack apiのページからcreate New Appでアプリを作る。

スクラッチを選択してアプリ名などを入力して作る。

socket modeのタブからEnable Socket modeを選んでオンにする。アクセスできるパーミッション設定とかの画面が出てきますがとりあえずそのまま進みました。

socket modeのタブのEvent SubscriptionsがNoになってると思うので、Event SubscriptionsをクリックしてOnにします。

Subscribe to bot eventsを開いて、適当に権限を追加していきます。

  • app_mention
  • file_change
  • file_created
  • file_public
  • file_shared
  • message.channels
  • message.groups
  • message.im
  • message.mpim
    あたりを入れてみました。セキュリティを気にする人はちゃんと調べてやってください。

次にApp homeタブでbotの名前などを設定します。

Messages TabのAllow users to send Slash commands and messages from the messages tabをチェックしておきます。これがないとDMが送れません。

続いてoAuth & Permissionsタブからinstall します。

インストール出来たらBot User OAuth Tokenがもらえます。xoxb-から始まる英数字です。

そして、Scopes欄からbotが使える権限を追加していきます。

  • app_mentions:read
  • channels:join
  • chat:write
  • chat:write.public
  • files:read
  • im:write
  • links:read

あたりを追加しました。あとで試して足りなかったら追加していくスタイル。追加したらreinstallが必要です。

最後に、Basic informationタブに移動してApp-Level Tokensを見ます。

TokenNameをクリックするとTokenが見えます。xapp-から始まる英数字です。

先ほどのxoxb-から始まるトークンとこのトークンの二つが必要なのでメモっておきます。


Pythonコードを書く

適当な場所にpythonファイルを設置してSlackのAPI操作とファイル保存、印刷をやらせます。slack_boltライブラリを使用しました。

終わりみたいなコードを添付。トークンを何らかの形で入れましょう。平文じゃなくて環境変数とかで保持しておくのが良い。

from datetime import datetime
from urllib.parse import urlparse
import requests
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import subprocess
import os
import sys
python_installation_path = sys.executable
print(f"The Python installation path is: {python_installation_path}")

SLACK_API_TOKEN = "xapp-"
SLACK_BOT_USER_TOKEN = "xoxb-"


app = App(token=SLACK_BOT_USER_TOKEN)

#DMはmessageで拾う
@app.event("message")
def handle_message_events(event, say, logger):
    if event["channel_type"] == "im":
        try:
            reply = download_from_slack(event["files"][0]["url_private_download"], SLACK_BOT_USER_TOKEN)
            say(thread_ts=event["event_ts"], text=reply)
        except:
            say(thread_ts=event["event_ts"], text="ファイルがない。")


@app.event("file_public")
def handle_file_public_events(body, logger):
    logger.info(body)
@app.event("file_change")
def handle_file_change_events(body, logger):
    logger.info(body)
@app.event("file_shared")
def handle_file_shared_events(body, logger):
    logger.info(body)

#パブリックチャンネルとかはapp_mentionで拾う
@app.event("app_mention")
def respond_to_mention(event, say, logger):
    try:
        reply = download_from_slack(event["files"][0]["url_private_download"], SLACK_BOT_USER_TOKEN)
        say(thread_ts=event["event_ts"], text=reply)
    except:
        say(thread_ts=event["event_ts"], text="ファイルがない。")

def download_from_slack(download_url: str, auth: str) -> str:
    
    try:
        img_data = requests.get(
            download_url,
            allow_redirects=True,
            headers={"Authorization": f"Bearer {auth}"},
            stream=True,
        ).content
        parsed_url = urlparse(download_url)
        path = parsed_url.path
        # パスからファイル名を取得し、拡張子を抽出
        file_name, file_extension = os.path.splitext(os.path.basename(path))
        check_printable = check_supported_extension(file_extension)
        if check_printable ==True:
            if not os.path.exists("temp_data"):
                os.makedirs("temp_data")
            timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
            filename = os.path.join("temp_data", f"{timestamp}{file_extension}")
            #print(filename)
            with open(filename, "wb") as f:
                f.write(img_data)
            subprocess.run(["lp", filename])
            
            print(f"File '{filename}' sent to the printer.")
            os.remove(filename)
            
            reply = "任せろ!!印刷しました."
            return reply
        else:
            reply = check_printable+"は対応してない."
            return reply

    except:
        reply = "エラーです。"
        return reply

def check_supported_extension(file_extension):
    supported_extensions = [
        ".pdf",".PDF" ".ps", ".txt", ".html", ".jpeg",".JPG",".jpg", ".png",".PNG", ".gif",".GIF", ".tiff",
        ".odt", ".doc", ".xls", ".ppt"
    ]

    if file_extension.lower() in supported_extensions:
        return True
    else:
        return file_extension.lower()

    
    
if __name__ == "__main__":
    handler = SocketModeHandler(app, SLACK_API_TOKEN)
    handler.start()

足りないPythonライブラリ等を適当にpipなりでインストールして、ターミナルから実行。

slackから追加したappにDMを送ると、何かしらのログが表示されます。APIのパーミッションが足りない場合、親切なことに何が足りないのかログに出力してくれるので、参考にしてreinstall。

↑のコードでは、ドキュメントをDMで投げるとbotから返信が来て印刷報告を受け取れるようになっています。

どこかのチャンネルにbotを招待すれば、@botの名前でメンションを飛ばしつつファイルを送る形で印刷できます。


自動起動の設定

プログラムがうまく動いたら、ラズパイの自動起動設定をして終了です。

/etc/systemd/system/適当な名前.serviceをいじって自動起動を設定します。

例)

[Unit]
Description=printBot auto run

[Service]
ExecStart=/usr/bin/python3 /home/ubuntu/ws/main.py
Restart=always
User=root
[Install]
WantedBy=multi-user.target
~                                    

それから、ラズパイの起動直後はうまくPythonコードが動かない可能性もあるので、時間差で起動するようにしておきます。

/etc/systemd/system/適当な名前.timerを作っていじります。

例)

[Unit]
Description=適当な名前.service timer

[Timer]
OnBootSec=6min
Unit=適当な名前.service

[Install]
WantedBy=multi-user.target

6分にしました。

sudo systemctl enable 適当な名前.timer

で有効化します。

再起動して、自動で起動していたら万歳です。

sudo systemctl status 適当な名前.service

で動いているか確認できます。

動いていない場合、表示されているPIDを以下のコマンドに打ち込むとpythonファイルが停止したときのエラーを見られます。

journalctl _PID=xxxx

handaru

handaru.net

handaru(はんだる)です。 工作が趣味です。

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です