【GAS】見逃し防止！気になる番組をGoogleカレンダーに登録しよう【NHK番組表API】

# TL; DR

- 登録したキーワードにマッチするNHKの番組をGoogleカレンダーに自動登録
- [GAS](https://workspace.google.com/intl/ja/products/apps-script/)で実装
- 番組情報は[NHK番組表API](https://api-portal.nhk.or.jp/)から取得

https://github.com/Syuparn/nhk-reminder

# はじめに

ミャクミャク様のアニメを見逃しました :sob:

放送日を1週間間違えNHKプラスも配信終了、アマプラにも来てなくて詰みです...

https://www.nhk.jp/p/ts/M39QRW64X4/

再放送の一縷の望みに賭け、**放送予定がカレンダーに自動登録される** ように準備します。

# 作ったもの

https://github.com/Syuparn/nhk-reminder

Google Apps Script(GAS)上で定期実行され、自分で登録したキーワードにマッチする番組がGoogleカレンダーへ自動登録されます。

NHK総合、Eテレの番組情報をすべて取得し、番組名、説明文がいずれかのキーワードを含む場合にカレンダーへ登録します。

構成は以下の通りです。

- 以下の処理を毎日定期実行
    - [NHK番組表API](https://api-portal.nhk.or.jp/)で一日分の番組情報一覧を取得
    - キーワードを含む番組のみ抽出
    - 抽出した番組の放送予定を自分のアカウントのGoogleカレンダーへ登録

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/c2783ba2-2ab5-4b44-a0d2-f883ec8a89b8.png)

## 動作例
早速使ってみましょう。
まずはキーワードを設定し定期実行を待ちます（または手動で実行します）。
（試した日に `ミャクミャク` や `万博` に該当する番組は無かったので）`音楽` を設定します。

```
KEYWORDS: "音楽"
```

今回は2025/3/16のNHK総合、Eテレの全番組情報が取得されました（デフォルトでは翌日の番組表を取得します）。
マッチした番組が3件あったようです。

```js
[ { title: 'ＮＨＫスペシャル　新ジャポニズム　第２集　Ｊ－ＰＯＰ“ボカロ”が世界を満たす',
    startDate: Sun Mar 16 2025 21:00:00 GMT+0900 (Japan Standard Time),
    endDate: Sun Mar 16 2025 21:50:00 GMT+0900 (Japan Standard Time),
    options: { description: '世界で注目される日本カルチャーの新たなうねりに迫るシリーズ。いま、初音ミクを起点とする日本発のボーカロイド文化が世界中で熱狂的なファンを獲得。歌声合成技術ボーカロイドはＡｄｏやＹＯＡＳＯＢＩなど新たなアーティストも生み出すカルチャーになった。観客が日本語で大合唱する海外のライブや世界の若者たちが新たな音楽を生む現場に密着。ＡｄｏやＹＯＡＳＯＢＩの独自取材も交え、進化するボカロの魅力に迫る。' } },
  { title: 'あしたが変わるトリセツショー　番外編　フレイル対策１５分エクササイズ',
    startDate: Mon Mar 17 2025 00:10:00 GMT+0900 (Japan Standard Time),
    endDate: Mon Mar 17 2025 00:26:00 GMT+0900 (Japan Standard Time),
    options: { description: 'トリセツショーが伝えた「一生元気で動ける体を目指す体操」の完全版▼フレイル対策のエクササイズを最新研究をもとに開発▼映像と音楽に合わせてテレビの前でできる１５分のプログラム▼足腰が楽に！ふらつき改善！歩行が速く！「出かけたくなった」「階段で息切れしなくなった」２か月試した体験者の変化に本人も専門家も驚き▼“年のせい”だと諦めないで！加齢による体力の衰えは取り戻せる！▼ぜひ録画してご活用ください！' } },
  { title: 'クラシック音楽館　Ｎ響　第２０２７回定期公演',
    startDate: Sun Mar 16 2025 21:00:00 GMT+0900 (Japan Standard Time),
    endDate: Sun Mar 16 2025 23:00:00 GMT+0900 (Japan Standard Time),
    options: { description: 'Ｎ響第２０２７回定期公演【曲目】１．リスト／交響詩「タッソー」▽２．リスト／ファウスト交響曲【演奏】指揮＝ファビオ・ルイージ、テノール＝クリストファー・ヴェントリス、男声合唱＝東京オペラシンガーズ、管弦楽＝ＮＨＫ交響楽団【収録】２０２４年１２月１３日ＮＨＫホール【コンサートα】ユジャ・ワン＆ヴィキンガー・オラフソン　白熱のデュオ・コンサート【曲目】ジョン・アダムズ／ハレルヤ・ジャンクション' } } ]
```

Googleカレンダーを見ると、放送時間に合わせて予定が登録されました。

![calender_result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/b5a8ef13-1271-44c2-84ae-3ddff0b48362.png)

# 構築方法
続いて、実際に動かしてみたい方向けの構築方法についてです。

## 必要なもの

- Googleアカウント
- NHK番組表APIのAPIキー
- Node.js/npm（ローカル環境）
- clasp

## NHK番組表APIのAPIキーを取得

まずは、NHK番組表APIを使うためにAPIキーを発行します。

https://api-portal.nhk.or.jp/

`ユーザ登録がお済みでない方はこちら` からアカウントを新規作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/b0e0901e-350a-49b7-9816-e4cc081a9fac.png)

ログインしたら、`登録済みアプリはこちら` を開き、APIキーを利用するアプリの説明を記載します。
（右側、Actionsの `Enable` ボタンも忘れず押してください）

![nhk_api.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/6b95ac59-801d-4e4f-8aa7-0fdec5ae7ca0.png)

![api_key.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/bb330e02-deeb-4120-bd72-f91e9a9033f1.png)

完了するとAPIキーが取得できます。

## claspでGASプロジェクトを初期化する

GASプロジェクトをリポジトリで管理するために[clasp](https://github.com/google/clasp)を使用しています。
claspについては過去別記事でも紹介しています。

https://qiita.com/Syuparn/items/80c4f48c66ca53ef12b8

[リポジトリ](https://github.com/Syuparn/nhk-reminder)をcloneしたら、claspをインストールします。

```bash
npm install -g @google/clasp
```

続いて、GASにログインします。ブラウザが立ち上がってGASとの連携を設定します。

```bash
clasp login
```

設定が終わったらローカル環境に `.clasp.json` というファイルができていると思うので、`"rootDir": "dist",` を追加します[^dist]。

```json:.clasp.json
{
  "scriptId": "{scriptId（設定されている値そのままでよい）}",
  "rootDir": "dist",
  "scriptExtensions": [
    ".js",
    ".gs"
  ],
  "htmlExtensions": [
    ".html"
  ],
  "jsonExtensions": [
    ".json"
  ],
  "filePushOrder": ["main.ts"],
  "skipSubdirectories": false
}
```

ソースコードをビルドしてGASプロジェクト上へ反映させます。

```bash
npm install
npm run build
clasp push
```

GAS上に `main.gs` というファイルが作成されたら成功です。

## スクリプトプロパティの登録

設定値をスクリプトプロパティとして登録します。

GASの左側メニューの一番下（歯車のマーク）`プロジェクトの設定` を選択し、`スクリプトプロパティ` を登録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/a39b31b4-3f5c-4f3b-8c6a-775e973fa3aa.png)

以下のプロパティを用意しています。

|key|value|
|-|-|
|`NHK_API_KEY`|[NHK番組表APIのAPIキー](https://api-portal.nhk.or.jp/)|
|`KEYWORDS`|カレンダーに登録したい番組を選択するキーワード（複数指定する場合は `,` 区切り）|
|`NHK_AREA_ID`|NHK番組表APIのエリアID（省略可、デフォルト値: `130`（東京都））|
|`ADVANCE_DAYS`|何日後の番組表を取得するか（省略可、デフォルト値: `1`）|

## 定期実行の設定
GASが毎日自動実行されるよう、トリガーを作成します。
GASの左側メニューの下から2番目（目覚まし時計のマーク）、`トリガー` を選択し、以下のトリガーを追加します。

- イベントのソースを選択: `時間主導型`
- 時間ベースのトリガーのタイプを選択: `日付ベースのタイマー`
- 時刻を選択: `午前0時~1時`

![trigger.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/bf1c479a-35ef-4ee5-becc-cf2eeb00ccb0.png)

これで準備完了です。上手く動けば、翌日から自動でGoogleカレンダーへ予定が登録されます。

# 実装
最後に、実装についても軽く紹介します。

## 番組取得
番組一覧を取得するAPI（Program List API）をリクエストしています。以下のパスで、指定した日の番組を全件取得します。

```ts:src/nhk.ts
function fetchNHKProgramsURL({areaID, serviceID, date, apiKey}: {areaID: string, serviceID: string, date: Date, apiKey: string}): string {
  // YYYY-MM-DD形式に整形
  const formattedDate = date.toLocaleDateString("ja-JP", {year: "numeric", month: "2-digit", day: "2-digit"}).replaceAll('/', '-');

  // NHK番組表API https://api-portal.nhk.or.jp/
  return `https://api.nhk.or.jp/v2/pg/list/${areaID}/${serviceID}/${formattedDate}.json?key=${apiKey}`
}
```

API詳細については公式ページをご覧ください。試し打ちも可能です。

https://api-portal.nhk.or.jp/doc-list-v2-con

ちなみに、`serviceID` でチャンネル名を指定するため、NHK総合、Eテレそれぞれについてリクエストしています。

```ts:src/nhk.ts
export function fetchNHKPrograms(date: Date, apiKey: string): CalendarEvent[] {
  // NHK番組表API https://api-portal.nhk.or.jp/ のURL
  // NHK総合
  const urlSougou = fetchNHKProgramsURL({
    areaID: getNHKAreaID(),
    serviceID: serviceIDSougou,
    date: date,
    apiKey: apiKey,
  })
  // Eテレ
  const urlETV = fetchNHKProgramsURL({
    areaID: getNHKAreaID(),
    serviceID: serviceIDETV,
    date: date,
    apiKey: apiKey,
  }) 

  const bodySougou = UrlFetchApp.fetch(urlSougou).getContentText();
  Utilities.sleep(3000) // サーバーへ負荷がかからないよう3秒待機
  const bodyETV = UrlFetchApp.fetch(urlETV).getContentText();

  return [...toCalendarEvents(bodySougou, serviceIDSougou), ...toCalendarEvents(bodyETV, serviceIDETV)];
}
```

レスポンスは以下の形式のJSONで返ってきます。

```json:レスポンス
{
  "list": {
    "g1": [
      {
        "id": "2025031524439",
        "event_id": "24439",
        "start_time": "2025-03-15T0z4:10:00+09:00",
        "end_time": "2025-03-15T04:15:00+09:00",
        "area": {
          "id": "130",
          "name": "東京"
        },
        "service": {
          "id": "g1",
          "name": "ＮＨＫ総合１",
          "logo_s": {
            "url": "//www.nhk.or.jp/common/img/media/gtv-100x50.png",
            "width": "100",
            "height": "50"
          },
          "logo_m": {
            "url": "//www.nhk.or.jp/common/img/media/gtv-200x100.png",
            "width": "200",
            "height": "100"
          },
          "logo_l": {
            "url": "//www.nhk.or.jp/common/img/media/gtv-200x200.png",
            "width": "200",
            "height": "200"
          }
        },
        "title": "地球タクシー　５ｍｉｎ．ドライブ「ハバナを走る　カリブの風」",
        "subtitle": "ドライバーとの対話と車窓からの風景で、世界の街を感じる一風変わった紀行ドキュメント「地球タクシー」 。人気シリーズからのスピンオフ。５分間の心を飛ばすドライブへ！",
        "content": "今回のタクシーは、ひと味違う。ダンディーなドライバー、アルベルトさんは男盛りの４４歳。年代物の真っ赤なオープンカーでさっそうとカリブの風を感じながらハバナの街を駆け抜ける。ラテンの粋と、生活のリアルが路上に入り混じる。",
        "act": "",
        "genres": [
          "0801"
        ]
      },
      // ...
  }
}
```

## カレンダー登録

GASの `Calendar#createEvent` メソッドを使用しています。

https://developers.google.com/apps-script/reference/calendar/calendar?hl=ja#createEvent(String,Date,Date,Object)

```ts:src/calendar.ts
export type CalendarEvent = {
  title: string
  startDate: Date
  endDate: Date
  options: {
    description: string
  }
}
```

```ts:src/googleCalendar.ts
export function registerEvent(event: CalendarEvent): GoogleAppsScript.Calendar.CalendarEvent {
  return CalendarApp.getDefaultCalendar().createEvent(
    event.title,
    event.startDate,
    event.endDate,
    event.options,
  )
}
```



# おわりに
以上、興味のあるNHKの番組を自動でGoogleカレンダー登録する方法の紹介でした。
皆さまも、**気になるジャンルを登録し供給に備えておきましょう**。

~~カレンダーを見逃したらその時はどうしようもありませんね~~

[^dist]: TypeScirptからビルドして生成したJavaScriptをデプロイしているため、ビルド先ディレクトリの `dist` を設定しています。余談ですが、clasp v3から[TypeScriptを直接デプロイすることはできなくなりました](https://github.com/google/clasp?tab=readme-ov-file#drop-typescript-support)。

（4/12追記）

完全勝利 :v: 再放送ありがとう～

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/9d16a2df-637b-492f-8077-501cd3b9ce69.png)