Qiita予約投稿機能を作ってみた ver2.0 API使用版

## はじめに
[Qiita Advent Calendar 2024](https://qiita.com/advent-calendar/2024) 楽しかったですね！
さて、本イベントの中でしれっと予約投稿機能が実装されておりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3519892/7d91838b-862e-5d1c-903c-042eb01257c9.png)
**この機能、普段から欲しくない？**…ということで、[以前公開した記事](https://qiita.com/iketo/items/545e5a59a9ed7283f35e)から機能を大幅アップデートして、Qiita予約投稿機能のChrome拡張ver2.0を作成してみました。

## 使い方
拡張機能は下記リンクから入手してください。

https://chromewebstore.google.com/detail/qiita%E4%BA%88%E7%B4%84%E6%8A%95%E7%A8%BF/fdlailgogjlojolaiehgpkhbcikjgdeh?hl=ja&utm_source=ext_sidebar

本ツールは予約投稿画面、設定画面の２画面で構成されています。
＜予約投稿画面＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3519892/f171a0e5-2441-b89d-713d-9ccd0431a85b.png)
＜設定画面＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3519892/3aad0a2c-b664-d90e-f0e4-2f1126cf2606.png)

### 事前準備
1. 設定画面に移動して、[アクセストークンの入手方法](https://qiita.com/maiamea/items/680cca06f7825595cba0)を参考にAPIのアクセストークンを入力します。
2. Organizationに紐づけて記事を投稿する場合は、組織のURI(`https://qiita.com/organizations/xxxx` の末尾`xxxx`)を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3519892/d10215aa-5f83-bdf0-c97b-125bb2dff4b4.png)
3. 保存ボタンを押すと、次回以降は設定不要でAPIへのアクセス・組織紐づけが行われます。

### 投稿予約
1. 投稿予約したい記事を限定共有記事として公開します。
2. 限定共有記事のページにアクセスして本ツールを開きます。URLが自動入力されていることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3519892/1cb4967f-5b77-bde4-9c10-83ec8fccdea7.png)
3. 予約日時を選択して予約ボタンを押すと投稿予約完了です。ポップアップで予約日時が確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3519892/151f2b48-34cf-a75e-9912-d7ab9cadeadd.png)



## 中身について
ソースコードは以下で公開しています。
まだまだ２年目の駆け出しエンジニアなので初心者感丸出しかもしれませんがご容赦ください(^^;

https://github.com/kento-git-ikeda/Qiita_scheduled_publish

### 予約の仕組み
予約ボタンを押すと、予約日時をローカルストレージに保存します。
```popup.js
document.getElementById("publishButton").addEventListener("click", async () => {
  //~~~//
    // スケジュールをlocalStorageに保存
    chrome.storage.local.get({ publishSchedules: [] }, (result) => {
      const schedules = result.publishSchedules;
      schedules.push(scheduleData);
      chrome.storage.local.set({ publishSchedules: schedules }, () => {
        // 最新のスケジュール情報を取得
        chrome.storage.local.get({ publishSchedules: [] }, (latestResult) => {
          const latestSchedule =
            latestResult.publishSchedules[
              latestResult.publishSchedules.length - 1
            ];

          if (latestSchedule && latestSchedule.scheduledDate) {
            alert(
              `予約投稿を次の日時に設定しました: ${latestSchedule.scheduledDate}`
            );
          } else {
            alert("スケジュールが設定されていません");
          }

          window.close();
        });
      });
    });
//~~~//
```
このとき、[Qiita API v2](https://qiita.com/api/v2/docs)のGETリクエストを叩いて限定共有記事の内容を保存します。
```popup.js
    const articleData = await getArticle(itemId, accessToken);
```
```popup.js
async function getArticle(itemId, accessToken) {
  const response = await fetch(`https://qiita.com/api/v2/items/${itemId}`, {
    method: "GET",
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });
  //~~~//
  return await response.json();
}
```

### 公開の仕組み
バックグラウンドで毎分チェックを行い、投稿の予約があるか確認します。
```background.js
setInterval(() => {
  chrome.storage.local.get({ publishSchedules: [] }, async (result) => {
    //~~~//
  });
}, 60000);
```
予約投稿時刻を過ぎた記事は、[Qiita API v2](https://qiita.com/api/v2/docs)のPATCHリクエストにより公開記事に変更されます。
```background.js
      if (scheduledTime <= currentTime) {
        try {
          await updateArticleToPublic(
            schedule.itemId,
            schedule.accessToken,
            schedule.organization,
            schedule.articleData
          );
        } catch (error) {
        //~~~//
        }
      //~~~//
      }
```
```background.js
async function updateArticleToPublic(
  itemId,
  accessToken,
  organization,
  articleData
) {
  const response = await fetch(`https://qiita.com/api/v2/items/${itemId}`, {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: articleData.title,
      body: articleData.body,
      tags: articleData.tags,
      organization_url_name: organization,
      private: false, // 公開状態にする
    }),
  });
  //~~~//
  return await response.json();
}

```

## 今後の改修予定
Qiita公式からリリースがあった場合はSTOPします。

### 予約機能強化（優先度：★★★）
現状ではブラウザを落とすと機能停止してしまうので、対策を調査中です。

### CSSの整備（優先度：★★）
操作画面が生のHTMLのままなのでQiitaのデザインに寄せていく予定です。

### 組織紐づけ機能の強化（優先度：★★）
組織を複数登録可能にし、紐づけの有無を選択できるようにする予定です。

### リファクタリング（優先度：★）
現状各プログラムに処理を直書きしてしまっているので、APIを使用する関数は１か所にまとめる等整理したいです。

## おわりに
上にあげた改修予定以外にもここをこうしてほしいという要望や先輩方からのアドバイスがありましたらコメントお願いします！反応いただけるととても嬉しいです。
もちろんGitHubの共同編集も大歓迎です(^^)