Shotgun APIを初めて使う方へ - Slack連携編 -

今回は、ShotgunとSlackを連携させ、Shotgunに新規にAssetが作成された際に
Slackへ通知を飛ばすような仕組みの構築方法をご紹介します

ShotgunとSlack等の外部サービスとを連携するような設定は
現状、標準機能としては実装されていません

そのため、Shotgun公式で提供されている ShotgunEventDaemon というものを使用します

# ShotgunEventDaemonとは

ShotgunEventDaemonは、Shotgunの操作履歴（EventLog）を常時監視することで、
Shotgunに対して何かしらの変更が行われた瞬間に処理を実行するような仕組みを
構築するためのものです

Shotgunを常に監視する必要があるため、サーバーやどこかのマシン上でShotgunEventDaemonを稼働させ続ける必要があります

![shotgun_to_slack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110677/059bdf86-4ef2-6cab-d2da-1c34bb4cb1f7.png)


# 開発環境

* OS: Windows 10 Pro
* Python2.7.17
    * ShotgunEventDaemonがPython2.7系までしか対応していないため(2020/01/21時点)
* git 2.23.0
* テキストエディタ
    * Visual Studio Code

# Pythonをインストールする

Python2系は、既にサポートが終了してしまっていますが、
ShotgunEventDaemon側がまだpython3系にまだ対応していないようなので
Python2.7をインストールします

下記のページが参考になります
[Python 2.7のインストール](https://www.python.jp/install/windows/install_py2.7.html)

また、Pythonの2.7系と3系の両方をインストールして共存させたい場合は、
下記の記事が参考になりました 
[WindowsにPython2.7とPython3.7が共存できるようにインストールする]
(https://qiita.com/segur/items/23f276320216c3aa7cf7)

# gitをインストールする

Shotgun APIをインストールする際に必要なためインストールします

下記のページが参考になります
[gitのインストールと起動方法](https://qiita.com/ideagear/items/eaf5a33bc15cf61c6a55)

# Pythonにライブラリをインストールする

下記の３つのライブラリをインストールします

* Shotgun API
    * Shotgunから情報を取得するために使用します
* pywin32
    * ShotgunEventDaemonの動作に必要です
* requests
    * Slackへ通知するために使用します

下記のコマンドを実行して、<b>Shotgun API</b>をインストールします

```
pip install git+git://github.com/shotgunsoftware/python-api.git
```

下記のコマンドを実行して、<b>pywin32</b>をインストールします

```
pip install pywin32
```

下記のコマンドを実行して、<b>requests</b>をインストールします

```
pip install requests
```

下記のコマンドを実行して、インストール済みのライブラリの一覧を確認します

```
pip list
```

パッケージのリストの中に先ほどインストールした３つのライブラリがあれば完了です

```
Package      Version
------------ -------
pywin32      227
requests     2.22.0
shotgun-api3 3.2.2
```


# ShotgunEventDaemonをダウンロードする

コマンドプロンプトを開いて、下記のコマンドを実行します
<b>D:\work</b>内にShotgunEventsのパッケージがダウンロードされます
※<b>D:\work</b>に関しては、任意の場所で大丈夫です

```
cd D:\work
git clone git://github.com/shotgunsoftware/shotgunEvents.git
```

<img width="770" alt="2020-01-23_16h41_54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110677/006fc569-4d4a-f8f7-5815-99e95749e42e.png">
ダウンロードしたパッケージ内の <b>shotgunEvents/src</b> フォルダを開きます
<img width="770" alt="2020-01-23_16h41_22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110677/e17aed6a-e05c-69ba-3fb4-05eb2de60ba5.png">

<b>shotgunEventDaemon.py</b> と <b>daemonizer.py</b> がShotgunEventDaemonのコア部分のスクリプトです
この2つのスクリプトによってShotgunの操作履歴の監視やプラグインの読み込みと実行等が行われます

<b>shotgunEventDaemon.conf.example</b> は、動作に必要な設定を定義するためのファイルのサンプルです。

<b>examplePlugins</b>の中には、処理の実行条件と実行する処理を定義するためのShotgunEventDaemonのプラグインのサンプルが入っています。

# ShotgunEventDaemonの設定を行う

## スクリプトユーザーを作成する

下記のページを参考にして、スクリプトユーザーを作成して下さい
※スクリプトの名前には、分かり易いように <b>shotgunEventDaemon</b> と指定して下さい

[Shotgun APIを初めて使う方へ - 導入編 - #スクリプトユーザーの作成](https://qiita.com/Tetsuya_Nozawa/private/da08c057febf19a2a83b#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E4%BD%9C%E6%88%90)

## 設定ファイルを作成する

テキストエディタに下記の内容をコピー&ペーストし、shotgunEvents/src 内に
<b>shotgunEventDaemon.conf</b> という名前で保存して下さい
※下記の設定内容は、shotgunEventDaemon.conf.example の内容を一部修正したものです

```ini:shotgunEventDaemon.conf

[daemon]
# General daemon operational settings

# The pidFile is the location where the daemon will store its process id. If
# this file is removed while the daemon is running, it will shutdown cleanly
# after the next pass through the event processing loop.
pidFile: D:/work/shotgunEvents/src/log/shotgunEventDaemon.pid

# The eventIdFile is the location where the daemon will store the id of the last
# processed event. This will allow the daemon to pick up where it left off when
# last shutdown thus not missing any events. If you want to ignore any events
# since last daemon shutdown, remove this file before daemon startup and the
# daemon will process only new events created after startup.
eventIdFile: D:/work/shotgunEvents/src/log/shotgunEventDaemon.id

# The logging mode to operate in:
# 0 = all log message in the main log file
# 1 = one main file for the engine, one file per plugin
logMode: 1

# The path where to put log files
logPath: D:/work/shotgunEvents/src/log

# The name of the daemon log file. The setup is for 10 log files that rotate
# every night at midnight
logFile: shotgunEventDaemon

# The level of logging that should be sent to the log file. This value is only
# applicable to for the main dispatching engine and can be overriden on a per
# plugin basis. This value is passed to the logging library. Any positive
# integer value is valid but most common cases are:
# - 10 - Debug
# - 20 - Info
# - 30 - Warnings
# - 40 - Error
# - 50 - Critical
logging: 20

# Enable Timing logging
# Timing logging is a separate log file that will log timing information regarding
# event dispatching and processing run time. This is to help diagnose which plugins
# are taking the most amount of time and where any potential queue processing
# delay might be coming from. Valid values are `on` or to enable or anything else
# to disable.
# timing_log: on
timing_log: off

# If the connection to shotgun fails, number of seconds to wait until we retry.
# This allows for occasional network hiccups, server restarts, application maintenance,
# etc.
conn_retry_sleep = 60

# Number of times to retry connection before logging an error level message (which
# sends an email in the default configuration)
max_conn_retries = 5

# Number of seconds to wait before requesting new events after each batch of events
# is done processing
fetch_interval = 5

# Maimum number of events to fetch at once.
max_event_batch_size = 100


[shotgun]
# Shotgun connection options for the daemon

# The Shotgun url the event processing framework should connect to.
server: https://yoursite.shotgunstudio.com/

# The Shotgun script name the framework should connect with.
name: shotgunEventDaemon

# The Shotgun api key the framework should connect with. You'll need to replace
# this random useless key with the one corresponding to the script you've setup.
key: XXXXXXXXXXXXXXXXXXX

# The address of the proxy server used to connect to your Shotgun server
# in the format 111.222.333.444:8080. Leave this empty if you don't have
# a proxy server.
proxy_server: 

# Sets the session_uuid from every event in the Shotgun instance to propagate in
# any events generated by plugins. This will allow the Shotgun UI to display
# updates that occur as a result of a plugin.
#
# Shotgun server v2.3+ required.
# Shotgun API v3.0.5+ required
use_session_uuid: True


[plugins]
# Plugin related settings

# A comma delimited list of paths where the framework should look for plugins to
# load.
paths: D:/work/shotgunEvents/src/plugins


[emails]
# Email notification settings. These are used for error reporting because we
# figured you wouldn't constantly be tailing the log and would rather have an
# active notification system.
#
# Any error above level 40 (ERROR) will be reported via email.
#
# All of these value must be provided for there to be email alerts sent out.

# The server that should be used for smtp connections. The username and password
# values can be uncommented to supply credentials for the smtp connection. The
# smtp port can also be uncommented to supply an alternate port such as 587 for
# GMail TLS SMTP. As for the useTLS parameter, it is a True/False value.
server:
#port: 587
#useTLS: True
#username: yourSMTPUsername
#password: yourSMPTPassword

# The from address that should be used in emails.
from:

# A comma delimited list of email addresses to whom these alerts should be sent.
to:

# An email subject prefix that can be used by mail clients to help sort out
# alerts sent by the Shotgun event framework.
subject: [SG]

```

### 設定内容の確認と修正

下記のパスに関する設定の <b>D:/work</b> の部分は、
<b>ShotgunEventDaemonをダウンロードする</b>の手順で
shotgunEventsパッケージをダウンロードした際の場所によって異なります
別の場所にダウンロードした場合は、その場所のパスに設定内容を修正して下さい

* 対象設定
    * [daemon] セクション
        * <b>pidFile, eventIdFile, logPath<b>
    * [plugins] セクション 
        * <b>pashs</b>

下記の設定を、先ほどShotgunのWeb上で作成したスクリプトユーザーの情報に修正する必要があります

* 対象設定
    * [shotgun] セクション
        * <b>server</b> の値を契約しているShotgunのURLに修正
        * <b>key</b> の値を作成したスクリプトユーザーのAPIキーに修正


その他の設定項目に関しては、ShotgunEventDaemonの公式wikiの [Configuration](https://github.com/shotgunsoftware/shotgunEvents/wiki/Configuration) に詳しく書かれています

## 必要なフォルダを作成する

設定ファイル内で指定したパスの先にフォルダが無いと実行時にエラーが出るため
先にフォルダを作成しておきます

<b>shotgunEvents/src</b> フォルダの中に
<b>log</b>フォルダ と <b>plugins</b> フォルダを作成して下さい

現在の<b>shotgunEvents/src</b> フォルダの中はこのような感じになっているかと思います
<img width="770" alt="2020-01-24_19h10_11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110677/72a35263-03a6-4abd-0f4f-1f111a7547ae.png">


# Slack Botを作成する
※Slackのワークスペースが既に作成されていることを前提に進めます

Slackに通知するために必要なBotを作成します
下記のURLへアクセスして下さい
https://my.slack.com/services/new/bot 

下記の画面が出た場合は、Shotgunの通知を送りたいSlackのワークスペースにサインインして下さい
<img width="700" alt="2020-01-24_19h38_46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110677/be19f5fa-088a-26a2-0de9-df2bd2bb9dae.png">

Botの作成画面が表示されるのでユーザー名に <b>shotgun_bot</b> と入力して
<b>ボットインテグレーションを追加する</b> をクリックします

<img width="685" alt="2020-01-24_19h50_00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110677/b93df656-9a92-0ea5-327c-2f5e80293c57.png">

作成されるとAPIトークンが発行されます
このAPIトークンは、後でSlackに通知するための機能で必要になります
※APIトークンはBotの作成者しか確認出来ないようになっています

<img width="690" alt="2020-01-24_19h52_00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110677/76241a4c-228f-1006-ba93-4aea41b7a8f0.png">

# Slack Botを通知したいチャンネルに追加する

Slack Botは、チャンネルに追加することで、そのチャンネルに対して
APIでメッセージを投稿出来るようになっています
※Slack Botは、一つのチャンネルにしか追加出来ません

今回は、<b>shotgun_notice</b>というチャンネルを作成して、
そこに通知が来るようにしたいと思います。

チャンネルに対して、Slack Botを追加する工程を動画にしました
<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">ShotgunとSlackの連携（SlackBotをチャンネルに追加） <a href="https://t.co/xVBSYgdzCK">pic.twitter.com/xVBSYgdzCK</a></p>&mdash; Tetsuya Nozawa (@lphing) <a href="https://twitter.com/lphing/status/1222471254394847232?ref_src=twsrc%5Etfw">January 29, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 


# Slack通知用プラグインを作成する

ShotgunにAssetが作成された際にSlackへ通知を送るような仕組みを作るにあたって、
ShotgunEventDaemonのプラグインを作成したいと思います
※ShotgunEvent/src/examplePlugins の中に色々サンプルが入っています

テキストエディタに下記のソースコードをコピー＆ペーストして、
先ほど作成した shotgunEvent/src/plugins フォルダの中に <b>new_asset_notification.py</b> という名前で保存して下さい。

プラグイン周りの詳細については、ShotgunEventDaemonの公式wikiの[Plugins](https://github.com/shotgunsoftware/shotgunEvents/wiki/Plugins) に書かれています

```python:new_asset_notification.py
# -*- coding:utf-8 -*-

import os
import time
import json
import logging
import requests

# ShotgunAPIの設定
SHOTGUN_SCRIPT_NAME = "shotgunEventDaemon"
SHOTGUN_SCRIPT_KEY = "ここにShotgunのスクリプトユーザーのアプリケーションキーを記述します"

# SlackBotの設定
SLACK_API_POST_MESSAGE_URL = "https://slack.com/api/chat.postMessage"
SLACK_BOT_API_TOKEN = "ここにSlackBotのAPIトークンを記述します"
SLACK_CHANNEL_NAME = "#shotgun_notice"

# 通知を行う対象のプロジェクト名を設定します
TARGET_PROJECT_NAMES = ["SHOTGUN_API_STARTUP"]


def registerCallbacks(reg):
    """
    Register all necessary or appropriate callbacks for this plugin.
    """

    # notify_new_assetを実行するための条件を指定します
    # Shotgun_Asset_Newの場合は、新しくAssetが作成された際に関数が呼ばれます
    match_events = {"Shotgun_Asset_New": ['*']}

    reg.registerCallback(
        SHOTGUN_SCRIPT_NAME,
        SHOTGUN_SCRIPT_KEY,
        notify_new_asset,
        match_events,
        None,
    )
    reg.logger.setLevel(logging.DEBUG)
    reg.logger.debug("Registered callback.")


def _create_entity_detail_url(sg, entity_type, entity_id):
    # entity_typeとentity_idを元にShotgunのDetailページのURLを作成します
    return "{0}detail/{1}/{2}".format(sg.base_url, entity_type, entity_id)


def _create_attachment(title, message):
    return {"title": title,
            "text": message,
            "footer": "post by shotgun event daemon",
            "ts": str(time.time())}


def _post_message(title, message):
    # Slack Web APIを使用してメッセージを送信します
    data = {'token': SLACK_BOTS_TOKEN,
            'channel': SLACK_CHANNEL_NAME,
            'attachments': json.dumps([_create_attachment(title, message)]),
            'as_user': True
    }
    return requests.post(SLACK_API_POST_MESSAGE_URL, data=data)


def _get_shotgun_data(sg, entity_type, entity_id, fields):
    # Shotgunから情報を取得する
    filters = [["id", "is", entity_id]]
    return sg.find_one(entity_type, filters, fields) or {}


def notify_new_asset(sg, logger, event, args):
    """
    A callback that logs its arguments.
    :param sg: Shotgun API handle.
    :param logger: Logger instance.
    :param event: A Shotgun EventLogEntry entity dictionary.
    :param args: Any additional misc arguments passed through this plugin.
    """
    event_project = event.get("project")
    if event_project is None:
        return
    if event_project.get("name") not in TARGET_PROJECT_NAMES:
        return
    entity = event.get("entity")
    if entity is None:
        return
    entity_type = entity.get("type")
    meta = event.get("meta", {})
    project_name = event_project.get("name")
    asset_name = entity.get("name")
    asset_id = meta.get("entity_id")
    asset_detail_url = _create_entity_detail_url(sg, entity_type, asset_id)

    message_format = u"{asset_link}\n{description}"
    label = u"Created Asset {asset_name}".format(asset_name=asset_name)
    asset_link = u"<"+asset_detail_url+u"|{0}>".format(label.decode("utf-8"))

    asset_data = _get_shotgun_data(sg,
                                   entity_type,
                                   asset_id,
                                   fields=["description"])

    if asset_data.get("description"):
        message = message_format.format(
            asset_link=asset_link,
            description=u"```{0}```".format(
                asset_data.get("description").decode("utf-8")))
    else:
        message = message_format.format(asset_link=asset_link,
                                        description=u"")
    title =  "{0} : Asset".format(project_name.decode("utf-8"))
    _post_message(title, message)

```

## 各自で修正する必要のある部分

### ShotgunAPIの設定

変数 <b>SHOTGUN_SCRIPT_KEY</b> の値に スクリプトユーザーを作成する の工程で作成した スクリプトユーザーのアプリケーションキーを入力して下さい

```python
# ShotgunAPIの設定
SHOTGUN_SCRIPT_NAME = "shotgunEventDaemon"
SHOTGUN_SCRIPT_KEY = "ここにShotgunのスクリプトユーザーのアプリケーションキーを記述します"
```

### SlackBotの設定

変数 <b>SLACK_BOT_API_TOKEN</b> の値に SlackBotする の工程で発行したAPIトークンを入力して下さい

```python
# SlackBotの設定
SLACK_API_POST_MESSAGE_URL = "https://slack.com/api/chat.postMessage"
SLACK_BOT_API_TOKEN = "ここにSlackBotのAPIトークンを記述します"
SLACK_CHANNEL_NAME = "#shotgun_notice"
```

### 通知対象プロジェクトの設定

今回は、<b>SHOTGUN_API_STARTUP</b> と名前が付いているのProjectを対象にしています。
もし、別の名前のProjectを通知対象にしたい場合は、このリストに追記することで対象に含まれます

```python

# 通知を行う対象のプロジェクト名を設定します
TARGET_PROJECT_NAMES = ["SHOTGUN_API_STARTUP"]
```

## 解説

ShotgunEventDaemonはこのプラグインを読み込むと <b>registerCallbacks</b> 関数を実行します
この関数の中で何を条件にして何を実行するかを設定しています

今回の場合は、Assetが新規に作成された際にnotify_new_asset関数が実行されるようになっています

他の条件で実行したい場合は、ShotgunEventDaemonの公式wikiの [Technical_Overview] (https://github.com/shotgunsoftware/shotgunEvents/wiki/Technical_Overview) が参考になります

```python
def registerCallbacks(reg):
    """
    Register all necessary or appropriate callbacks for this plugin.
    """

    # notify_new_assetを実行するための条件を指定します
    # Shotgun_Asset_Newの場合は、新しくAssetが作成された際に関数が呼ばれます
    match_events = {"Shotgun_Asset_New": ['*']}

    reg.registerCallback(
        SHOTGUN_SCRIPT_NAME,
        SHOTGUN_SCRIPT_KEY,
        notify_new_asset,
        match_events,
        None,
    )
    reg.logger.setLevel(logging.DEBUG)
    reg.logger.debug("Registered callback.")
```

<b>notify_new_asset</b> 関数は、受け取った引数から新規に作成されたAssetの情報を取り出して
Slackに投稿するためのメッセージを作成し、Slack Web APIでメッセージを送信します

```python

def notify_new_asset(sg, logger, event, args):
    """
    A callback that logs its arguments.
    :param sg: Shotgun API handle.
    :param logger: Logger instance.
    :param event: A Shotgun EventLogEntry entity dictionary.
    :param args: Any additional misc arguments passed through this plugin.
    """
    event_project = event.get("project")
    if event_project is None:
        return
    if event_project.get("name") not in TARGET_PROJECT_NAMES:
        return
    entity = event.get("entity")
    if entity is None:
        return
    entity_type = entity.get("type")
    meta = event.get("meta", {})
    project_name = event_project.get("name")
    asset_name = entity.get("name")
    asset_id = meta.get("entity_id")
    asset_detail_url = _create_entity_detail_url(sg, entity_type, asset_id)

    message_format = u"{asset_link}\n{description}"
    label = u"Created Asset {asset_name}".format(asset_name=asset_name)
    asset_link = u"<"+asset_detail_url+u"|{0}>".format(label.decode("utf-8"))

    asset_data = _get_shotgun_data(sg,
                                   entity_type,
                                   asset_id,
                                   fields=["description"])

    if asset_data.get("description"):
        message = message_format.format(
            asset_link=asset_link,
            description=u"```{0}```".format(
                asset_data.get("description").decode("utf-8")))
    else:
        message = message_format.format(asset_link=asset_link,
                                        description=u"")
    title =  "{0} : Asset".format(project_name.decode("utf-8"))
    _post_message(title, message)
```

# ShotgunEventDaemonの起動用batを作成する

ShotgunEventDaemonは、Shotgunのメンテナンスなどで頻繁に停止することがあるため、
手軽に起動が出来るようにbatを作成します

下記の内容をテキストエディタにコピー＆ペーストして、<b>shotgunEventDaemon.bat</b>という名前で<b>shotgunEvents</b>フォルダに保存して下さい

```bash:shotgunEventDaemon.bat

pushd %~dp0

python %CD%\src\shotgunEventDaemon.py foreground

popd
exit /b 0
```

shotgunEventsフォルダの中はこのような感じになっているかと思います
<img width="764" alt="2020-01-27_19h54_41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110677/6b656901-43a1-5795-1764-8100c0a03369.png">

# 動作確認

動作確認の様子を動画にしました

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">ShotgunEventDaemonを使ったサンプル <a href="https://t.co/XQOb7AXEmB">pic.twitter.com/XQOb7AXEmB</a></p>&mdash; Tetsuya Nozawa (@lphing) <a href="https://twitter.com/lphing/status/1222470993186193408?ref_src=twsrc%5Etfw">January 29, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 


## 手順

先ほど作成した <b>shotgunEventDaemon.bat</b> を実行します
実行するとコマンドプロンプトが立ち上がり、下記のようなメッセージが表示されます
<img width="734" alt="2020-01-28_15h53_27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110677/468b39d6-9ac8-f072-12f2-8b0e2909d67e.png">

ShotgunEventDaemonが起動している状態で、ShotgunのAssetページを開いて、
<b>+ アセット</b> ボタンからアセットの作成画面を開きます

アセット名と説明を入力したら、アセットを作成をクリックします

<img width="878" alt="2020-01-28_16h32_33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110677/224e54e0-1317-330a-8de3-e17287014940.png">

Assetが作成されたことをShotgunEventDaemonが検知して、Slack通知用プラグインが実行されるため、
Slackを開くと通知が飛んでいるかと思います

<img width="821" alt="2020-01-28_16h32_57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110677/81ad3385-7e28-44cc-3cad-be40ae7907ea.png">

# あとがき

今回は、ShotgunEventDaemonとSlack Botを使ったShotgunとSlackを連携する仕組みについてご紹介致しました。
ShotgunEventDaemonは、設定等が複雑ではありますが、使いこなせれば色々なものと連携させることが出来る強力なシステムだと思います。
ぜひご活用頂ければと思います。