📥

Toggl APIで日報用の活動記録を収集する

2024/02/17に公開

仕事をしていると、日報という形で一日の業務を報告することもあると思う。
Togglを使えば、業務の記録を可視化できるし、APIを使えば日報に役立つ形式でデータを取得できる。

ので、書いた。

環境

今回は以下の環境で実行するものを作成した。

  • Deno 1.40.5
  • WSL2 Ubuntu 22.04

Toggl の使い方

  • 仕事に関することをまとめるため、Client に会社名を設定する
  • 仕事に関する Project は、会社の Client に属するように設定する
  • 雑多なタスクは misc. というプロジェクトにまとめている

Toggl APIを Deno で叩く

API token の取得

ドキュメントに従い、API token を取得する。

Authentication | Track Your Way

記録したTime Entriesを取得する

Toggl で記録された Time Entries は以下のエンドポイントで取得できる。

Time entries | Track Your Way

  • クエリに start_dateend_date を指定することで、その範囲の Time Entries を取得できる
  • meta を指定することで、ClientProject などの値も取得できる
    • 余計にAPIを叩く必要がなくなる

実装

下記は実際の実装例で、 要点は以下の通り。

  • API tokenはファイル読み込みにした
  • 一日の範囲が 6:00 から 翌 6:00 までとなるように取得する
// report.ts

const d = new Date();
d.setTime(d.getTime() - 6 * 60 * 60 * 1000);
const today = d.toISOString().split("T")[0];
const tomorrow = new Date(d.getTime() + 24 * 60 * 60 * 1000).toISOString().split("T")[0];
const start_date = encodeURIComponent(today + "T06:00:00+09:00");
const end_date = encodeURIComponent(tomorrow + "T06:00:00+09:00");
const endpoint = `https://api.track.toggl.com/api/v9/me/time_entries?start_date=${start_date}&end_date=${end_date}&meta=true`;

const api_token = (await Deno.readTextFile("api_token.txt")).trim();

const res = await fetch(endpoint, {
    method: "GET",
    headers: {
        "Content-Type": "application/json",
        "Authorization": `Basic ${btoa(`${api_token}:api_token`)}`,
    },
});

const data = await res.json();

type Entry = {
    "client_name": string;
    "project_name": string;
    "description": string;
    "duration": number;
};
const client = "会社名";
const work_entries = data.filter((entry: Entry) => entry.client_name === client);

// Projectごと、タスクごとに集計する
type Summary = Record<string, Record<string, number>>;

const summary: Summary = {};
for (const entry of work_entries) {
    const project = entry.project_name;
    const task = entry.description;
    const duration = entry.duration;

    if (!summary[project]) {
        summary[project] = {};
    }
    if (!summary[project][task]) {
        summary[project][task] = 0;
    }
    if (duration > 0) {
        summary[project][task] += duration;
    }
}

// 集計結果を markdown 書式で扱いやすい形にする
// Project 「misc.」 だけはあとで表示する
for (const project in summary) {
    if (project === "misc.") {
        continue;
    }
    console.log(`### ${project}`);
    for (const task in summary[project]) {
        const duration = summary[project][task] / 3600;
        console.log(`- ${task}: ${duration.toFixed(2)}h`);
    }
    console.log("");
}
if (summary["misc."]) {
    console.log("### misc.");
    for (const task in summary["misc."]) {
        const duration = summary["misc."][task] / 3600;
        console.log(`- ${task}: ${duration.toFixed(2)}h`);
    }
}

使い方

deno run --allow-net --allow-read report.ts

WSLの外(クリップボード)に引き渡す

deno run --allow-net --allow-read report.ts | iconv -t utf-16 | clip.exe
GitHubで編集を提案

Discussion