面倒なスクショ作業を自動化してみた！

# 要は
SnapBotという画面変化を検知してスクリーンショット~Slackのスレッドへの送信を自動で行うツールを開発しました。
このBotを使用することで、単純作業であるスクリーンショットのアップロードを自動化し、作業効率を向上させることができます。



こんな感じでスレッド上でスクショを送り続けます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2898059/57fc9eca-67c3-bd3b-ca8b-ae0126e76670.png)


# Why
なぜこのBotを作ろうと思ったのかというと、勉強会などの機会にスクリーンショットのアップロード作業が手間だったからです。
毎回手動でスクリーンショットを撮影して、それをSlackにアップロードする必要がありました。

# What
SnapBotを、作成しました！
- SlackAppとopenCVを使用して、画面の変化を検知してスクショをアップロードするBot

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2898059/dfea0e1b-93c1-3263-ecd2-5cfa3a457741.png)

このBotを使用することで、スクリーンショットを自動的に取得し、Slackチャンネルにアップロードすることができます。以下に、Botの作成手順を説明します。

# How

簡単に技術紹介します。
詳しくはレポジトリを参照してください！
- https://github.com/KazuSh1geru/desktop_screenshoter

## SlackAppを作成

manifest.yamlを使用しました.
漏れやすいポチポチ設定を省けるのでめっちゃ便利。。

とりあえず、ファイルの読み書きを設定しました

```
display_information:
  name: SnapBot
features:
  bot_user:
    display_name: SnapBot
    always_online: true
oauth_config:
  scopes:
    bot:
      - files:write
      - files:read
settings:
  interactivity:
    is_enabled: true
  org_deploy_enabled: false
  socket_mode_enabled: true
  token_rotation_enabled: false

```


## スクリーンショット

スクリーンショットコマンドを使用しました。

```
import os

def screenshot():
    os.makedirs("images", exist_ok=True)
    os.system(
        'screencapture -x -o -R0,0,1680,1080 ./images/$(date "+%y%m%d_%H%M%S")_image.png'
    )

```

## 直近のスクリーンショットと差分を比較

opencvで画像の特徴量を抽出して差分を定量化しています

```
import cv2
import numpy as np

THRESHOLD = 10


def judge_image_difference(img1_path, img2_path):
    diff = _image_difference(img1_path, img2_path)
    print(diff)

    return _judge_diff(diff, threshold=THRESHOLD)


def _image_difference(img1_path, img2_path):
    # 画像読み込み
    img1 = cv2.imread(img1_path)
    img2 = cv2.imread(img2_path)

    # 画像をグレースケールに変換
    img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
    img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

    # 特徴量抽出器の生成
    orb = cv2.ORB_create()

    # 特徴量の検出と記述子の計算
    _, des1 = orb.detectAndCompute(img1_gray, None)
    _, des2 = orb.detectAndCompute(img2_gray, None)

    # 特徴量のマッチング
    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
    matches = bf.match(des1, des2)

    # マッチングされた特徴点の距離の平均値を計算
    dist_sum = 0
    for match in matches:
        dist_sum += match.distance
    dist_avg = dist_sum / len(matches)

    # 2つの画像の距離を計算
    diff = np.abs(dist_avg)

    return diff


def _judge_diff(diff, threshold=THRESHOLD):
    if diff < threshold:
        return True
    else:
        return False

```

## スレッドへ送信

`slack_sdk`を使用しました
Slack_Bot_tokenなどの変数は`.env`に隠蔽しました

```
import logging
import os
from dotenv import load_dotenv

# Import WebClient from Python SDK (github.com/slackapi/python-slack-sdk)
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

load_dotenv()

# WebClient instantiates a client that can call API methods
# When using Bolt, you can use either `app.client` or the `client` passed to listeners.
client = WebClient(token=os.getenv("SLACK_BOT_TOKEN"))

def send_image_to_thread(is_true, image_path):
    """
    与えられた判定結果と画像を使用して、スレッドに画像を送信する関数。
    判定結果がTrueの場合は、画像をスレッドに送信します。
    """
    if is_true:
        try:
            # Call the files.upload method using the WebClient
            # Uploading files requires the `files:write` scope
            result = client.files_upload_v2(
                channels=os.getenv("CHANNEL_ID"),
                thread_ts=os.getenv("THREAD_TS"),
                file=image_path,
                initial_comment="SNAP :camera:",
            )
            # Log the result
            logger.info(result)
        except SlackApiError as e:
            print(f"Error: {e}")
    else:
        print("判定結果はFalseです。何もしません。")

```

# 気づき
- Slack Appの開発ハードルが低いことを知れた
  - 充実したライブラリとパッケージ
  - manifest.ymlを用いた再現性の高いアプリの構築
- Slackへのアクションまで一貫すると"自然"に使える
  - 普段から使用するコミュニケーションツール上で動くのは、体験としてめっちゃ良かった

# 余談1
CopilotとChatGPTが大活躍して数日で要件通り動くものができました
MVPのハードルが下がっているのをめっちゃ感じました

# 余談2
記事に関しても骨子はChatGPTが作ってくれました、楽ちん