LoginSignup
7
10

More than 3 years have passed since last update.

Dropboxのファイル追加をSlackに通知するフォルダ監視くんを作った

Last updated at Posted at 2019-01-20

はじめに

大学の研究室ではDrobpoxのフォルダを共有して、卒論や修論のファイルを置いてもらって、順番に添削を行っています。
添削後は添削済み用のフォルダに入れます。
フォルダ構成は以下のような感じです。
添削フォルダ.png
原稿を添削したら、添削済みのフォルダに入れてSlackで通知します。
reply.png
研究室では30名近くを1名で見ていて、
添削ごとに返信するのは正直手間です。
ですので、添削済みのフォルダに入れれば自動的に通知するアプリを作りました。

対象者

  • Dropboxの変更をSlackで通知したい方

処理の流れ

  1. Dropboxで監視したいフォルダの内容変更
  2. Dropbox APIで変更が検知され、WebhookによりPOSTパラメータが送られる =>Webhookについてはこちらの記事を参考にしてください
  3. あらかじめ指定されたWebサーバでPOSTパラメータを受け取る
  4. SlackにPOSTパラメータを送る
  5. Slackでメッセージが通知される

前準備

Dropboxアプリの登録 => こちらの記事を参考にしてください

Dropboxのアプリの作成

Dropboxのアプリを作成したら、アクセストークンを取得してメモしておきます。
そのあと、files/list_folderでアクセストークンと監視したいフォルダのパスを入力します。
* フォルダのパスの例: /Lab/2_添削フォルダ_開発版/
list_folder.png
Submit Callを押すとResponseのところに、該当のフォルダ内容が表示されます。
Response.png
上記の内容は、フォルダ「/Lab/2_添削フォルダ_開発版」に 「a.txt」 が入っている状況です。

このResponseの中にcursorがあります。これは(おそらく)フォルダの内容を時間軸で保持している状態になります。このcursorの内容もメモしておきます。

Dropbox API用のプログラムの作成

Dropboxのフォルダで変更があったとき、通知を受け取るプログラムを作ります。
こちらの記事 (ぱふぅ家のホームページ)を参考にPHPブログラムを作っていきます。
これからのプログラムはWebサーバに置きます。ただし、httpsが使用されるWebサーバに限ります。
まずDropboxのアクセストークンをメモしたdropboxAccessToken.txtを作成します。

dropboxAccessToken.txt
xxxxxxx <= Dropboxのアクセストークンを記述する

つぎに、先ほどメモしたcursorを記述します。

cusor.txt
xxxxxxx <= cursorを記述する

DropboxからWebhookで通知を受け取ったら、
変更ファイルのみを抽出するプログラムを記載します。

dropboxUpdateNotificationAPI.php
<?php

//Dropboxの更新を通知するクラス
class dropboxUpdateNotificationAPI {
    private $error;     //エラーフラグ(エラーがあればTRUE)
    private $errmsg;        //エラーメッセージ(APIが返す error_summary の内容)
    private $responses; //直前の結果
    private $access_token_path; //Dropboxで得られるアクセストークンのファイルパス
    private $cursor_file_path; // cursorが保存されるファイルのパス

/**
 * コンストラクタ
 * @param   なし
 * @return  なし
*/
function __construct() {

    //$this->webapi = '';
    $this->error  = FALSE;
    $this->errmsg  = '';
    $this->responses = NULL;
    $this->access_token_path = __DIR__ . "/dropboxAccessToken.txt";
    $this->cursor_file_path = __DIR__ . "/cursor.txt";
}

/**
 * デストラクタ
 * @return  なし
*/
function __destruct() {
    unset($this->responses);
}
/**
 * Dropbox API v2:アクセストークンを用いたリクエスト
 * @param   string $url    リクエストURL
 * @param   array  $argv   Dropbox-API-Argに渡すパラメータ配列(省略可能)
 * @param   array  $post   POSTに渡すパラメータ配列(省略可能)
 * @param   string $upload アップロードするデータ
 * @return  bool TRUE:リクエスト成功/FALSE:失敗
*/
function requestDropboxAPIv2($url, $argv=NULL, $post=NULL, $upload=NULL) {
    $flagArg = FALSE;
    $headers[] = 'Authorization: Bearer ' . $this->getAccessToken();
    // cURLを使ってリクエスト
    $curl = curl_init() ;
    curl_setopt($curl, CURLOPT_URL , $url);
    curl_setopt($curl, CURLOPT_HEADER, 1) ;
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER , FALSE);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER,  TRUE);
    curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($curl, CURLOPT_TIMEOUT, 5);
    if ($argv != NULL) {
        $headers[] = 'Dropbox-API-Arg: ' . json_encode($argv);
        $flagArg = TRUE;
    }
    if ($upload != NULL) {
        $headers[] = 'Content-Type: application/octet-stream';
        curl_setopt($curl, CURLOPT_POSTFIELDS, $upload);
        $flagArg = FALSE;
    } else if ($post != NULL) {
        $headers[] = 'Content-Type: application/json';
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($post));
    }
    curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);

    $res1 = curl_exec($curl);
    $res2 = curl_getinfo($curl);
    curl_close($curl);
    $res = substr($res1, $res2['header_size']);

    //結果処理
    //$this->webapi = $url;
    preg_match("/HTTP\/[0-9\.]+\s([0-9]+)\s(.+)/ims", $res1, $arr);
    if (! isset($arr[2])) {
        $this->error   = TRUE;
        $this->errmsg  = 'Dropbox API failure';
    } else if ($arr[1] >= 300) {
        $json = json_decode($res);
        $this->error   = TRUE;
        $this->errmsg  = $arr[2];
    } else {
        $this->responses = $flagArg ? $res : json_decode($res);
    }
    return (! $this->error);
}

/**
 * Dropbox API v2:更新された添削ファイル名の取得
 * @param   string $cursor: カーソル
 * @return  ファイル名/FALSE: 存在しない
 * カーソルをfileに保存しておく
 */
function getUpdatedCorrection() {
    $url = 'https://api.dropboxapi.com/2/files/list_folder/continue';   //リクエストURL
    $post['cursor'] = $this->getCursor(); //カーソルの取得
    $res = array();

    if ($this->requestDropboxAPIv2($url, NULL, $post) == FALSE) {
        $res = FALSE;
    } else if (isset($this->responses->entries[0]->name)) { //ファイルが存在したら
        $res = $this->responses->entries; //配列にする
        //ファイルが存在するのでcursorの更新
        $this->saveCursor ($this->responses->cursor);
    }
    return $res;
}

/**
 *  Dropboxのアクセストークンを取得する関数
 * @return string   Dropboxのアクセストークン
 */
function getAccessToken() {
    $handle = fopen($this->access_token_path, "r") or //ファイルオープン
    $this->fopen_error_func($this->access_token_path);  //ファイルオープン失敗時に呼ばれる関数
    $at = fgets($handle);
    fclose($handle);
    return ($at);
}

/**
 * cursorの取得する関数
 * @return  string  cursor
*/
function getCursor() {
    $handle = fopen($this->cursor_file_path, "r") or //ファイルオープン
                $this->fopen_error_func($this->cursor_file_path);  //ファイルオープン失敗時に呼ばれる関数
    $cursor = fgets($handle);
    fclose($handle);
    return ($cursor);
}

/**
 * cursorを保存する関数
 * @param   string  cursor
 * @return  なし
 */
function saveCursor($cursor) {
    $handle = fopen($this->cursor_file_path, "w"); //ファイルオープン
    fputs($handle, $cursor); //保存
    fclose($handle);
}

/**
 * ファイルオープンが失敗した時に呼ばれる関数
 * @param   string  $fname  ファイル名
 */
function fopen_error_func($fname) {
    $error_file = fopen ("error.log", "a");
    fputs($error_file, $fname . " was not opened.\n");
    fclose($error_file);
    die;
}
}

WebhookでPOSTパラメータを受け取るプログラムを作成します。

webhook.php
<?php
//verification request
if(isset($_GET['challenge'])){
    echo htmlspecialchars($_GET['challenge']);
    die;
}

//Dropbox APIの読み込み
require_once(__DIR__ . '/dropboxUpdateNotificationAPI.php');
$pdb = new dropboxUpdateNotificationAPI();  //更新を通知するクラス

//更新ファイルを取得できない場合
if (($update_files = $pdb->getUpdatedCorrection()) == FALSE) {
    die;
}

$path = __DIR__ . "/dropbox.log"; //更新情報の記載
$file = fopen ($path, "w");
fputs($file, "---更新---\n");

foreach($update_files as $uf) {
    $uf_arr = (array) $uf;

    if($uf_arr[".tag"]=="file") {
        //文字列が含まれていない場合
        if (strpos($uf_arr["name"],'添削済み') === false) {
            continue;
        }
        fputs($file, $uf_arr["name"] . " が添削されました\n");
    }else if ($uf_arr[".tag"]=="deleted") {
        fputs($file, $uf_arr["name"] . "が 削除されました\n");
    }
}
fputs($file, "---更新ここまで---\n");
fclose ($file); //ファイルを閉じる
?>

冒頭の$_GET['challenge']は、DropboxのWebhookに登録すると、確認のために送られるので、その対応のために記載されます。
その後、Dropbox APIのアプリページでWebhookのアドレスを登録します。
webhook.png
ここのWebページはhttpsである必要があります。
登録ができれば、該当のフォルダに何かファイルを入れてください。
うまくいけばdropbox.logが追加され、変更内容が記載されます。

SlackのAppの生成

Slack側でapiを作成するために、以下のURLをアクセスします。
https://slack.com/services/new/incoming-webhook
「Post to Channel」でチャンネルを選択します。
Workspaceが異なる場合は、右上のアイコンで選択し直します。

いきなりチャンネルに通知させるのは他人に迷惑がかかるかもしれませんので、
Privately to @xxx(you)のように自分に送るといいかもしれません。

Incoming WebHooksのページのWebhookで「Webhook URL」をメモします。
slack_webhook.png

slackWebhookURL.txt
https://hooks.slack.com/services/xxxxxxx <= slackのWebhookのURLを記述する

Slackにメッセージを通知するプログラムです。

slackMessageSendingAPI.php
<?php

//Slackにメッセージを通知するクラス
class slackMessageSendingAPI {
    private $webhookURL_path; //SlackのWebhook URLのファイルパス
    private $webhookURL;

/**
 * コンストラクタ
 * @pram    なし
 * @return  なし
 */
function __construct() {
    $this->webhookURL_path = __DIR__ . "/slackWebhookURL.txt";
    $this->webhookURL = $this->getWebhookURL(); //Webhook URLの取得
}

/**
 * webhook URLの取得
 * @param   なし
 * @return  string  Webhook URL
 */
private function getWebhookURL() {
    $handle = fopen($this->webhookURL_path, "r") or //ファイルオープン
                $this->fopen_error_func($this->webhookURL_path);  //ファイルオープン失敗時に呼ばれる関数
    $webhookURL = fgets($handle);
    fclose($handle);
    return($webhookURL);
}

/**
 * ファイルオープンが失敗した時に呼ばれる関数
 * @param   string  $fname  ファイル名
 */
private function fopen_error_func($fname) {
    $error_file = fopen ("error.log", "a");
    fputs($error_file, $fname . " was not opened.\n");
    fclose($error_file);
    die;
}

/**
 * slackにメッセージ送信
 * @param   string  $message メッセージ
 * @return  なし
*/
public function sendMessage($message) {
    $message_data["text"] = $message;
    //メッセージのjson化
    $message_json = json_encode($message_data);
    //payloadの値としてURLエンコード
    $message_post = "payload=".urlencode($message_json);

    $slack_file = fopen ("slack.log", "w");
    fputs($slack_file, "message: " . $message . "\n");
    fputs($slack_file, "url: " . $this->webhookURL . "\n");
    fclose($slack_file);

    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL , $this->webhookURL);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $message_post);
    curl_exec($curl);
    curl_close($curl);
}

// End of Class ===========================================================
}

?>

webhook.phpにSlack API用の内容を記述します。
すでに記載したwebhook.phpを編集します。

webhooks.php
()

//Slack APIの読み込み
require_once(__DIR__ . '/slackMessageSendingAPI.php');
$psk = new slackMessageSendingAPI(); //slackにメッセージを送るクラス

()

foreach($update_files as $uf) {
    $uf_arr = (array) $uf;

    if($uf_arr[".tag"]=="file") {
        //文字列が含まれていない場合
        if (strpos($uf_arr["name"],'澤野添削済み') === false) {
            continue;
        }
        //slackメッセージ
        $psk->sendMessage($uf_arr["name"] . " が添削されました");
        fputs($file, $uf_arr["name"] . " が添削されました\n");
    }else if ($uf_arr[".tag"]=="deleted") {
        fputs($file, $uf_arr["name"] . "が 削除されました\n");
    }
}
()

これで、Dropboxの監視フォルダが変更されたら、
Slackで通知されます。

おわりに

はじめてのQiitaに投稿しました。
不備などあるかもしれませんが、
ご連絡いただけると助かります。

参考記事

7
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
10