ChromeとToggl とSlackを連携して、リモートワークを快適にする（その１）

# 目標

1. Chrome上で管理している業務内容を、時間管理ツール「[Toggl](https://www.toggl.com/)」で計測する
1. Togglとビジネスチャットツール「[Slack](https://slack.com/intl/ja-jp/)」を連携して、現時点の業務内容を共有する

# 背景

今年6月に株式会社RevCommに入社いたしました。
弊社RevCommは、フルリモートフルフレックス勤務体制というとても働きやすい環境となっています。
ただ、今自分がどんな作業を抱えているか、とか、どんな作業をしているか、というのが職場内の雰囲気でなんとなく伝わる感じではないので、自分から作業内容を明確にしていこうと思いました。
弊社では、GithubやGoogleカレンダーなど、基本的にはChrome上で業務内容を管理しています。
どうせならどのくらい作業に時間がかかっているかも実績として残していきたいと思ったので、Toggl＋Chromeプラグインで、各タスクの入力を簡単に行なっていくことにしました。
そして、Togglの計測状況を随時Slackに流していくことで、自動的に「今、自分がどのような業務を行なっているか」を周知できるようにしてみようと思いました。

# 作業概要

 1. ChromeにGoogleアカウントでログインする
 1. TogglにGoogleアカウントでログインする
 1. ChromeにTogglプラグインを入れる
 1. Togglプラグインの設定を行う
 1. Toggl API トークンを取得する
 1. Slack Webhook URL を取得する
 1. Toggl API と Slack Webhook URL を通じて、取得した現在のTogglタスクをSlackに投稿する
 1. 7.を実行する shellスクリプトを作成する
 1. launchd で 8. を定期的に自動実行する

# 手順詳細

## 1. ChromeにGoogleアカウントでログインする

[Chrome](https://www.google.com/intl/ja_jp/chrome/) を入手して、Googleアカウントでログインする

## 2. TogglにGoogleアカウントでログインする

「[Toggl](https://www.toggl.com/)」 の右上ページから、「Log in」 ＞　 「Toogl track」（真ん中）　＞　「Login via Google」 でログインする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1782834/713beff6-d6f2-104d-a40e-08aa932871cd.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1782834/f9234fce-25d4-f4b2-ce5a-c59ac4981239.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1782834/821b72d9-8de2-cffd-0046-180e4c072db4.png)

## 3. ChromeにTogglプラグインを入れる

Chromeウェブストアから「[Toggl Track: Productivity & Time Tracker](https://chrome.google.com/webstore/detail/toggl-track-productivity/oejgccbfbmkkpaidnkphaiaecficdnfn?utm_source=chrome-ntp-icon)」を導入する

## 4. Togglプラグインの設定を行う

拡張機能から先ほど導入したTogglプラグインの設定画面を開く

「Integrations」でタスクがあるサイトにチェックを入れていく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1782834/8df2adec-1fa5-97c7-f02b-ad9962f16941.png)

Chromeを再起動すると、連携したサイトのタスク粒度ページに「Start Timer」のボタンが追加されている。
（例は、Githubのissueページ）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1782834/dccc28d1-7c99-4d22-4cb8-77b298aa446b.png)

上記ボタンをクリックすると、Togglでタスクの計測が開始される
プロジェクト、タグも登録しておくと、後でレポートの時に集計しやすくなる

## 5. Toggl API トークンを取得する

「[Toggl Trackのプロフィールページ](https://track.toggl.com/profile)」の下部にある「API token」の欄で、「--Click to reveal--」をクリックすると、トークン値が表示されるので、コピーする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1782834/74f28e87-be9e-4818-a90b-da4d4278330d.png)

## 6. Slack Webhook URL を取得する

[Slackの公式手順](https://slack.com/intl/ja-jp/help/articles/115005265063-Slack-%E3%81%A7%E3%81%AE-Incoming-Webhook-%E3%81%AE%E5%88%A9%E7%94%A8)に従って、Slack Webhook URL を取得する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1782834/3e19b27d-6a7a-f37c-80f6-ebfc60b3ece4.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1782834/874d4877-2162-d9d5-32ba-da1982f84d77.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1782834/aa778a15-b82a-1e62-63e3-1e08acf4aebe.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1782834/c17d59e0-aa23-4844-9d6e-1bd168398d08.png)

次の画面でWebhook URLが表示されるので、コピーして保存。

※投稿するチャネルごとにURLの取得が必要。意図しないチャネルへのURLを使わないよう注意！

## 7. Toggl API と Slack Webhook URL を通じて、取得した現在のTogglタスクをSlackに投稿する

投稿にはPythonを使います。

### 7.1. 必要ライブラリ

```
pip install requests
```

### 7.2. 自動投稿スクリプト

#### スクリプトの仕様

 - 現在Togglでタスクが計測されていたら、そのタスクをSlackに投稿する
 - Slackに投稿したタスクは前回タスクとして `history.txt` に保存する
 - 次回スクリプトを起動した際
    - `history.txt`と同じタスクの場合　… Slackに投稿しない
    - `history.txt`と違うタスクの場合　… Slackに投稿して、`history.txt`を上書きする
    - タスクが計測されていない場合　…　日付が変わっていなければ、`history.txt`のタスクの終了をSlackに投稿する

```toggl2slack.py
# -*- coding: utf-8 -*-
import requests
import json
import datetime
import os

# Slack hooks
SLACK_HOOKS_URL = '<6で取得したSlack Webhook URL>'
TOGGL_API_TOKEN = '<5で取得したToggle API token>'
file_name = './history.txt'


def get_toggl():
    headers = {'content-type': 'application/json'}
    auth = requests.auth.HTTPBasicAuth(TOGGL_API_TOKEN, 'api_token')
    current = requests.get('https://track.toggl.com/api/v8/time_entries/current', auth=auth, headers=headers)
    if current.status_code != 200:
        print("togglの情報取得に失敗しました")
        return None, [], None
        
    current_json = current.json()
    if not current_json['data']:
        return None, [], None
    
    print(f'{current_json["data"]}')    
    description = current_json['data']['description']

    # この時点でプロジェクト名をとってきておく
    pname = None
    if 'pid' in current_json['data']:
        pid = current_json['data']['pid']
        pname = get_project_name(pid)    
        print(f'pid: {pid}, pname: {pname}, description: {description}')

    return pname, current_json['data']['tags'], description


def get_project_name(pid):
    headers = {'content-type': 'application/json'}
    auth = requests.auth.HTTPBasicAuth(TOGGL_API_TOKEN, 'api_token')
    current = requests.get(f'https://track.toggl.com/api/v8/projects/{pid}', auth=auth, headers=headers)
    
    if current.status_code != 200:
        print("togglの情報取得に失敗しました")
        return None
    
    current_json = current.json()
    if not current_json['data']:
        return None
    return current_json['data']['name']


def check_old_toggl(now_task):
    prev_task = None
    with open(file_name, mode='r', encoding='utf-8') as f:
        history = f.read()
        prev_task = history.strip()

    write_history(now_task)
    return (prev_task == now_task), prev_task


def write_history(str_history):
    with open(file_name, mode='w', encoding='utf-8') as fw:
        fw.write(str_history)


def write_slack(title, description):
    text = f"""【{title}】　`{description}` """

    payload = {'username': 'Toggl',
               'text': text,
               'icon_emoji': ':clock10:'}

    requests.post(SLACK_HOOKS_URL, data=json.dumps(payload))


if __name__ == '__main__':
    now = datetime.datetime.now()
    now_str = now.strftime('%Y/%m/%d %H:%M:%S')

    # 現在のタスクの取得
    pname, tags, description = get_toggl()
    now_task = f'{description} ({pname}：{", ".join(tags)})'
    is_same, prev_task = check_old_toggl(now_task)

    if not pname and not description:
        print(f'[{now_str}] not pname+description')
        stat = os.stat(file_name)
        mdt = datetime.datetime.fromtimestamp(os.stat(file_name).st_mtime)
        if now.day != mdt.day:
            # 日付が変わっていたら、問答無用で終了
            write_history('')
            # ログクリア
            os.remove('./out.log')
            exit(1)
        elif prev_task and not description and 'None' not in prev_task:
            # slackへの書き込み(次が始まってない時だけ)
            print(f'[{now_str}] write end')
            write_slack("作業終了", prev_task)

            # 終了して次が始まってない場合
            write_history('')
            exit(1)
    
    if not pname:
        # プロジェクト名が入ってない場合、スルー
        exit(1)

    if is_same:
        print(f'[{now_str}] same')
        # 前回取得したものと同じ場合は終了
        exit(1)

    if description:
        print(f'[{now_str}] write start')
        # slackへの書き込み
        write_slack("作業開始", now_task)
```

## 8. 7.を実行する shellスクリプトを作成する

### 8.1. shellスクリプト

私はAnacondaを導入したので、`conda activate` も内部で行なっています。

```toggl2slack.sh

# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('/Users/***/opt/anaconda3/bin/conda' 'shell.zsh' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
    eval "$__conda_setup"
else
    if [ -f "/Users/***/opt/anaconda3/etc/profile.d/conda.sh" ]; then
        . "/Users/***/opt/anaconda3/etc/profile.d/conda.sh"
    else
        export PATH="/Users/***/opt/anaconda3/bin:$PATH"
    fi
fi
unset __conda_setup
# <<< conda initialize <<<

cd <toggl2slack.pyへのフルパス> && conda activate bot && python toggl2slack.py

```

### 8.2. shellの権限変更

シェルに実行権限を付与する

```
chmod 744 toggl2slack.sh
```

## 9. launchd で 8. を定期的に自動実行する

### 9.1. 設定ファイル

`~/Library/LaunchAgents` ディレクトリ以下に `toggl2slack.plist` を配置する

 - 5分おきに 8. で作成した `toggl2slack.sh` を実行する

```toggl2slack.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>toggl2slack</string>
    <key>ProgramArguments</key>
    <array>
        <string>sh</string>
        <string>/Users/***/work/bot/toggl2slack/toggl2slack.sh</string>
    </array>
    <key>StartInterval</key>
    <integer>300</integer>
    <key>StandardOutPath</key>
    <string>/Users/***/work/bot/toggl2slack/out.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/※*＊/work/bot/toggl2slack/error.log</string>
</dict>
</plist>
```

# Slackへの投稿イメージ

Togglで計測を開始すれば、自動的にSlackに作業状況が通知されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1782834/f37a7b70-06a7-8183-3396-2121363e43c0.png)

## 次回予告

せっかくTogglで時間を計測できるようにしたのですから、レポートも一括で取ってこれるようにしちゃいましょう！