Fitbit Web API：歩数の取得

# はじめに

この記事は[前回](https://qiita.com/ishidad2/items/c18881a36b58acdd5a76)の続きです。
まだFitbit Web APIを実行したことがない方は以下の記事を参考にしてください。


https://qiita.com/ishidad2/items/c18881a36b58acdd5a76

# やること

今回はWeb APIよりステップ（歩数）の情報を取得してみようと思います。

# API情報

[公式リファレンス](https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/)を見ると以下のエンドポイントでSteps（歩数）の取得ができるようです。
また、このエンドポイントは`calories` , `distance` , `elevation` , `floors` , `steps`
を`resource`に指定することで、それぞれデータを取得することができるようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/3bf391cd-40ac-f75e-9139-b9b96474437f.png)

:::note info
パラメータの指定方法
:::
|パラメータ|指定|説明|
|:-:|:-:|:-:|
|user-id|必須|現在ログインしているユーザーの場合は「-」(ダッシュ) を使用。|
|resource|必須| `calories` , `distance` , `elevation` , `floors` , `steps` のいずれかを指定。|
|date|必須| `yyyy-MM-dd` または `today`のいずれかを指定
|detail-level|オプション|詳細レベルを指定。`1min` , `5min` , `15min`|
|start-time|オプション|開始時間を指定。`HH:mm`|
|end-time|オプション|終了時間を指定。`HH:mm`|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/08e20f01-41c6-a321-c42b-ea05611d7169.png)

# データの取得

.envの各値は[前回の記事](https://qiita.com/ishidad2/items/c18881a36b58acdd5a76#%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%81%AE%E8%A8%AD%E5%AE%9A)を参考に設定してください。

```.env
CLIENT_ID=
ACCESS_TOKEN=
REFRESH_TOKEN=
```
`getSteps.js`というファイルを作成します。（中身はURLの組み立て部分が変わっているだけで前回とほぼ同じです）

```javascript
require('dotenv').config();
const axios = require('axios');
const fs = require('fs');
const path = require('path');

async function refreshAccessToken() {
    const url = 'https://api.fitbit.com/oauth2/token';
    const clientId = process.env.CLIENT_ID;
    const refreshToken = process.env.REFRESH_TOKEN;

    const body = new URLSearchParams();
    body.append('grant_type', 'refresh_token');
    body.append('client_id', clientId);
    body.append('refresh_token', refreshToken);

    const config = {
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    };

    try {
        const response = await axios.post(url, body.toString(), config);
        const { access_token, refresh_token } = response.data;

        updateEnvFile(clientId, access_token, refresh_token);
        console.log('Access token and refresh token updated.');
        return access_token;
    } catch (error) {
        console.error('Error refreshing token:', error.response.data);
    }
}

function updateEnvFile(clientId, accessToken, refreshToken) {
    const envPath = path.join(__dirname, '.env');
    const envContents = `CLIENT_ID=${clientId}\nACCESS_TOKEN=${accessToken}\nREFRESH_TOKEN=${refreshToken}\n`;

    fs.writeFileSync(envPath, envContents, 'utf8');
    console.log('.env file has been updated with new tokens.');
}

async function getSteps(date = 'today', detail_level = '1min', start_time = '05:00', end_time = '06:59') {
    const accessToken = await refreshAccessToken();
    const url = `https://api.fitbit.com/1//user/-/activities/steps/date/${date}/1d/${detail_level}/time/${start_time}/${end_time}.json`

    try {
        const response = await axios.get(url, {
            headers: {
                'Authorization': `Bearer ${accessToken}`
            }
        });
        return response.data;
    } catch (error) {
        console.error('Error fetching steps rate data:', error.response.data);
    }
}

(async () => {
  const stepsData = await getSteps();  // 今日の1日分の歩数データを取得
  console.log(JSON.stringify(stepsData));
})();

```

## 実行

```bash
node getSteps.js
```

## 結果

ちょっと長いので折りたたんでいます。

<details><summary>結果はこちら</summary>

```json
{
	{
	"activities-steps": [
		{
			"dateTime": "today",
			"value": "6854"
		}
	],
	"activities-steps-intraday": {
		"dataset": [
			{
				"time": "05:00:00",
				"value": 164
			},
			{
				"time": "05:01:00",
				"value": 164
			},
			{
				"time": "05:02:00",
				"value": 95
			},
			{
				"time": "05:03:00",
				"value": 103
			},
			{
				"time": "05:04:00",
				"value": 114
			},
			{
				"time": "05:05:00",
				"value": 114
			},
			{
				"time": "05:06:00",
				"value": 116
			},
			{
				"time": "05:07:00",
				"value": 111
			},
			{
				"time": "05:08:00",
				"value": 117
			},
			{
				"time": "05:09:00",
				"value": 118
			},
			{
				"time": "05:10:00",
				"value": 106
			},
			{
				"time": "05:11:00",
				"value": 116
			},
			{
				"time": "05:12:00",
				"value": 117
			},
			{
				"time": "05:13:00",
				"value": 121
			},
			{
				"time": "05:14:00",
				"value": 119
			},
			{
				"time": "05:15:00",
				"value": 100
			},
			{
				"time": "05:16:00",
				"value": 112
			},
			{
				"time": "05:17:00",
				"value": 111
			},
			{
				"time": "05:18:00",
				"value": 108
			},
			{
				"time": "05:19:00",
				"value": 114
			},
			{
				"time": "05:20:00",
				"value": 103
			},
			{
				"time": "05:21:00",
				"value": 145
			},
			{
				"time": "05:22:00",
				"value": 126
			},
			{
				"time": "05:23:00",
				"value": 112
			},
			{
				"time": "05:24:00",
				"value": 80
			},
			{
				"time": "05:25:00",
				"value": 85
			},
			{
				"time": "05:26:00",
				"value": 93
			},
			{
				"time": "05:27:00",
				"value": 110
			},
			{
				"time": "05:28:00",
				"value": 107
			},
			{
				"time": "05:29:00",
				"value": 107
			},
			{
				"time": "05:30:00",
				"value": 108
			},
			{
				"time": "05:31:00",
				"value": 108
			},
			{
				"time": "05:32:00",
				"value": 116
			},
			{
				"time": "05:33:00",
				"value": 50
			},
			{
				"time": "05:34:00",
				"value": 68
			},
			{
				"time": "05:35:00",
				"value": 107
			},
			{
				"time": "05:36:00",
				"value": 112
			},
			{
				"time": "05:37:00",
				"value": 106
			},
			{
				"time": "05:38:00",
				"value": 107
			},
			{
				"time": "05:39:00",
				"value": 106
			},
			{
				"time": "05:40:00",
				"value": 110
			},
			{
				"time": "05:41:00",
				"value": 108
			},
			{
				"time": "05:42:00",
				"value": 112
			},
			{
				"time": "05:43:00",
				"value": 110
			},
			{
				"time": "05:44:00",
				"value": 116
			},
			{
				"time": "05:45:00",
				"value": 113
			},
			{
				"time": "05:46:00",
				"value": 112
			},
			{
				"time": "05:47:00",
				"value": 112
			},
			{
				"time": "05:48:00",
				"value": 117
			},
			{
				"time": "05:49:00",
				"value": 112
			},
			{
				"time": "05:50:00",
				"value": 113
			},
			{
				"time": "05:51:00",
				"value": 111
			},
			{
				"time": "05:52:00",
				"value": 113
			},
			{
				"time": "05:53:00",
				"value": 109
			},
			{
				"time": "05:54:00",
				"value": 106
			},
			{
				"time": "05:55:00",
				"value": 109
			},
			{
				"time": "05:56:00",
				"value": 109
			},
			{
				"time": "05:57:00",
				"value": 78
			},
			{
				"time": "05:58:00",
				"value": 52
			},
			{
				"time": "05:59:00",
				"value": 61
			},
			{
				"time": "06:00:00",
				"value": 66
			},
			{
				"time": "06:01:00",
				"value": 65
			},
			{
				"time": "06:02:00",
				"value": 0
			},
			{
				"time": "06:03:00",
				"value": 0
			},
			{
				"time": "06:04:00",
				"value": 0
			},
			{
				"time": "06:05:00",
				"value": 10
			},
			{
				"time": "06:06:00",
				"value": 60
			},
			{
				"time": "06:07:00",
				"value": 88
			},
			{
				"time": "06:08:00",
				"value": 22
			},
			{
				"time": "06:09:00",
				"value": 0
			},
			{
				"time": "06:10:00",
				"value": 0
			},
			{
				"time": "06:11:00",
				"value": 0
			},
			{
				"time": "06:12:00",
				"value": 0
			},
			{
				"time": "06:13:00",
				"value": 0
			},
			{
				"time": "06:14:00",
				"value": 0
			},
			{
				"time": "06:15:00",
				"value": 1
			},
			{
				"time": "06:16:00",
				"value": 0
			},
			{
				"time": "06:17:00",
				"value": 0
			},
			{
				"time": "06:18:00",
				"value": 13
			},
			{
				"time": "06:19:00",
				"value": 0
			},
			{
				"time": "06:20:00",
				"value": 0
			},
			{
				"time": "06:21:00",
				"value": 7
			},
			{
				"time": "06:22:00",
				"value": 0
			},
			{
				"time": "06:23:00",
				"value": 0
			},
			{
				"time": "06:24:00",
				"value": 0
			},
			{
				"time": "06:25:00",
				"value": 0
			},
			{
				"time": "06:26:00",
				"value": 0
			},
			{
				"time": "06:27:00",
				"value": 0
			},
			{
				"time": "06:28:00",
				"value": 0
			},
			{
				"time": "06:29:00",
				"value": 0
			},
			{
				"time": "06:30:00",
				"value": 0
			},
			{
				"time": "06:31:00",
				"value": 6
			},
			{
				"time": "06:32:00",
				"value": 4
			},
			{
				"time": "06:33:00",
				"value": 0
			},
			{
				"time": "06:34:00",
				"value": 0
			},
			{
				"time": "06:35:00",
				"value": 0
			},
			{
				"time": "06:36:00",
				"value": 0
			},
			{
				"time": "06:37:00",
				"value": 0
			},
			{
				"time": "06:38:00",
				"value": 0
			},
			{
				"time": "06:39:00",
				"value": 0
			},
			{
				"time": "06:40:00",
				"value": 0
			},
			{
				"time": "06:41:00",
				"value": 0
			},
			{
				"time": "06:42:00",
				"value": 0
			},
			{
				"time": "06:43:00",
				"value": 0
			},
			{
				"time": "06:44:00",
				"value": 0
			},
			{
				"time": "06:45:00",
				"value": 0
			},
			{
				"time": "06:46:00",
				"value": 0
			},
			{
				"time": "06:47:00",
				"value": 0
			},
			{
				"time": "06:48:00",
				"value": 0
			},
			{
				"time": "06:49:00",
				"value": 0
			},
			{
				"time": "06:50:00",
				"value": 0
			},
			{
				"time": "06:51:00",
				"value": 0
			},
			{
				"time": "06:52:00",
				"value": 0
			},
			{
				"time": "06:53:00",
				"value": 0
			},
			{
				"time": "06:54:00",
				"value": 0
			},
			{
				"time": "06:55:00",
				"value": 0
			},
			{
				"time": "06:56:00",
				"value": 0
			},
			{
				"time": "06:57:00",
				"value": 0
			},
			{
				"time": "06:58:00",
				"value": 8
			},
			{
				"time": "06:59:00",
				"value": 25
			}
		],
		"datasetInterval": 1,
		"datasetType": "minute"
	}
}
```
</details>


```javascript
const url = `https://api.fitbit.com/1//user/-/activities/steps/date/${date}/1d/${detail_level}/time/${start_time}/${end_time}.json`
```

実行した際のURLは以下のようになります。

```
https://api.fitbit.com/1//user/-/activities/steps/date/today/1d/1min/time/05:00/06:59.json
```

上記を実行した際の挙動としては「コードを実行した日の05:00−06:59の間の1分間の歩数集計データを取得」となります。

以上。


:::note warn
この記事は個人の見解であり所属する組織を代表しません。
:::