月齢を表示する拡張機能を作成

## はじめに
本記事は Qiita Advent Calendar 2022 の 8 日目です！

本日 2022/12/08 は満月、コールドムーンです！
今回、満月にちなんで月齢をブラウザに表示できる Google Chrome の拡張機能を作成しました。

## 現在の月齢を取得する
まず根本となる月齢が無いと話になりません。
月齢を算出するにはどうすればいいか調査をしてみました...が

https://qiita.com/Real_analysis/items/1b269f3ed50bdedc0dbd

https://ja.wikipedia.org/wiki/%E6%9C%88%E9%BD%A2#%E3%82%B0%E3%83%AC%E3%82%B4%E3%83%AA%E3%82%AA%E6%9A%A6%E3%81%8B%E3%82%89%E3%81%AE%E6%9C%88%E9%BD%A2%E8%A8%88%E7%AE%97

https://eco.mtk.nao.ac.jp/koyomi/topics/html/topics2017_1.html

どうやら...計算式などを用いて月齢を算出するのはそう簡単なことではないようです。
気合を入れて計算式を書けば不可能ではなさそうですが、
今回拡張機能自体を作るのが初めてであり、あまり月齢の算出自体に時間をかけたくなかったため、
以下の API を使用させていただきました。ありがとうございます！

https://mgpn.org/api/moon/

## 月齢の画像を用意する

今回、月齢は拡張機能のアイコンとして表示するため、月齢分のアイコン画像が必要となります。
月齢は 0~29 まで存在するので、合計30枚の画像が必要です。
29 が新月で、0~13 までは数字が大きくなるにつれ月が満ちていきます。
14 になると満月になり、15~28 では数字が大きくなるにつれ月が欠けていきます。

こんな感じで手書きで画像を用意しました。
画像作成に慣れていなかったので正直この作業が一番苦労しました...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247154/bd12900d-46b0-36b3-f521-8fcbba422e00.png)


## 拡張機能を作成していく

### manifest.json
```json
{
  "name": "Moon Age",
  "description": "Moon Age",
  "version": "1.0",
  "manifest_version": 3,
  "action": {
    "default_icon": "images/14.png"
  },
  "background": {
    "service_worker": "moonAge.js"
  },
  "permissions": [
    "alarms"
  ],
  "host_permissions": [
    "https://mgpn.org/api/moon/v3position.cgi"
  ]
}
```

今回、月齢を一定間隔で取得させるため `service_worker` で動作させ、
`alarms` を許可しています。
また、月齢APIへのアクセスを許可するため、`host_permissions`に月齢APIのURLを記載しています。

### moon.js

```js
const tokyoLat = 35.6894
const tokyoLon = 139.6917
const moonApiUrl = `https://mgpn.org/api/moon/v3position.cgi?lat=${tokyoLat}&lon=${tokyoLon}`

fetchMoon()

chrome.alarms.create('reloadMoon', { delayInMinutes: 120, periodInMinutes: 120 });

chrome.alarms.onAlarm.addListener(function (alarm) {
  if (alarm.name == 'reloadMoon') {
    fetchMoon()
  }
});

function fetchMoon() {
  fetch(moonApiUrl)
    .then((response) => {
      if (!response.ok) {
        throw new Error(`HTTP error: ${response.status}`);
      }
      return response.text();
    })
    .then((result) => setMoon(result))
}

function setMoon(json) {
  const parsedJson = JSON.parse(json)

  let moonAge = parsedJson?.result[0]?.age
  moonAge = Math.round(moonAge)

  setMoonIcon(moonAge)
}

function setMoonIcon(moonAge) {
  chrome.action.setIcon({ path: `images/${moonAge}.png` });
}

```

個々に解説していきます。

```js
const tokyoLat = 35.6894
const tokyoLon = 139.6917
const moonApiUrl = `https://mgpn.org/api/moon/v3position.cgi?lat=${tokyoLat}&lon=${tokyoLon}`
```

今回使用する API はパラメタとして座標を指定する必要があります。
今回は東京の座標を指定して取得する仕様にしました。


```js
fetchMoon()

chrome.alarms.create('reloadMoon', { delayInMinutes: 120, periodInMinutes: 120 });

chrome.alarms.onAlarm.addListener(function (alarm) {
  if (alarm.name == 'reloadMoon') {
    fetchMoon()
  }
});
```

ブラウザ起動時に`fetchMoon()`で API から月齢を取得しアイコンに反映します。
また、一定間隔で月齢を取得する関数を動かすため、`chrome.alarms`を使用しています。

https://developer.mozilla.org/ja/docs/Mozilla/Add-ons/WebExtensions/API/alarms/create


頻繁に取得し直す必要はないため、今回は2時間おきに再取得するようにしました。


```js
function fetchMoon() {
  fetch(moonApiUrl)
    .then((response) => {
      if (!response.ok) {
        throw new Error(`HTTP error: ${response.status}`);
      }
      return response.text();
    })
    .then((result) => setMoon(result))
}
```

`fetch` を使い、API から月齢を取得しています。

```js
function setMoon(json) {
  const parsedJson = JSON.parse(json)

  let moonAge = parsedJson?.result[0]?.age
  moonAge = Math.round(moonAge)

  setMoonIcon(moonAge)
}

function setMoonIcon(moonAge) {
  chrome.action.setIcon({ path: `images/${moonAge}.png` });
}
``` 
取得結果は JSON 形式なのでパースし、月齢を取得しています。
月齢は小数の値となっているので、今回は四捨五入して整数に変換しています。

月齢の値ごとの画像ファイルを用意しているので、
整数に変換した月齢に対応する画像ファイルを`chrome.action.setIcon`でセットします。

### 動かしてみる

#### 2022/11/30 18時頃
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247154/01a1fbbc-fcd5-3965-f625-3699ab95c04b.png)

#### 2022/12/06 14時頃

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247154/dc8b8fd2-4331-b469-c4aa-6b8c353cc373.png)

正しく月齢を取得し、対応したアイコンが表示されていそうです！