GoogleAppsScriptでConfluenceのページを定期的に更新

## はじめに
「[**Confluence**](https://ja.atlassian.com/software/confluence)」はAtlassianが提供する共有ワークスペース。
現在、社内の情報共有の場として、使用していますが、
アドインや提携しているサービスが豊富で、自由度は**無限大！！**のように感じます。

### Confluenceのページ定期更新にいたる背景
とある先輩社員Nさんのつぶやきから、始まりました…。

Nさん：「コンフルのページにイベントまで○○日みたいなカウントダウンのやつ欲しいんだけどー」

私：「ｲｲﾈ!!やってみよう！(htmlマクロあるっぽいし、Javascriptでおけでしょ)」

#### --- 問題発生 ---

- 社内で使用しているクラウド版コンフルでは、「htmlマクロ」が使えない？
- クラウド版でも代替のアドインがあった！だが…私はコンフルの管理者権限を持っていないので、勝手に追加はできない…。(しかも、~~こんなことのために~~管理者様の手を煩わせるわけにはいかない…)

#### --- かすかな希望 ---

- コンフルにはAPI(GET,POST,PUT,DELETE)が存在する。
- 個人単位でAPIキーが発行できる。(管理者様の手を煩わせず、1人で完結できる！)
- 1日毎に定期更新すれば、あたかも毎日勝手にカウントダウンしているように見せれる！？

Nさん：「無理そうならいいやー、ちょっと言ってみただけー」

(無理なことなんてない、かすかな希望があるんです)
**APIをたたくスクリプトを用意して、1日おきに定期実行で該当箇所更新させたる！**

て、ことで、長くなりましたが、
Google Apps Script(以下GAS)で、コンフルAPIをたたいて、ページを更新させたいとなりました。
加えて、定期実行も！(GASをチョイスしたのは、**定期実行が簡単に設定できる**からです。※1)

※1 後述しますが、毎日の定期実行が簡単に設定できるのは、1時間単位。
　　なので、0時ぴったりなど、クリティカルな時間調整が必要な場合には向いていません。

## やってみよう
ConfluenceRESTAPIドキュメント：https://developer.atlassian.com/cloud/confluence/rest/

#### 1.APIキーの取得
まずは、[ATTLASSIANアカウント管理ページ](https://id.atlassian.com/manage/api-tokens)に遷移し、「APIトークンを作成する」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250217/99e75d7a-1709-1320-72d5-3ae1e6774c0a.png)

その後、ラベルに任意の値を入力して、「作成」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250217/d044ad69-cf11-0ce6-9c17-74046e852ec8.png)

作成後、~~2度と見れないからなとかなり脅されるので、黙って~~コピーして、保管する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250217/622923a2-b074-bf87-dcb7-ab3f602fed3d.png)

#### 2. GASにスクリプトを記述

##### ○ソースコード1
以下の3点を認証ヘッダに付与してAPIリクエストを行うので、定義&ヘッダ作成関数を用意する。

- APIの対象となるワークスペース
- アクセスに使用するユーザー
- APIキー<br/>

```javascript:コード.gs
// API情報を定義
// APIで表示及び更新したいコンフルのワークスペース
var API_BASE_URL = 'https://xxxxxxxxx/wiki'; 
var user_id = 'メールアドレス';
var password = '1.で取得したAPIキー';

// 更新対象のページIDを定義
var page_id = '111111111';
 
/* 認証ヘッダー作成関数 */
function create_headers(user_id, password) {
  return {
    'content-type'  : 'application/json',
    'Authorization' : 'Basic ' + Utilities.base64Encode(user_id + ':' + password)
  };
}
```

##### ○ソースコード2
GET時のパラメータである**expand**はGET時に何を取得するかを**カンマ区切り**で指定する。

|キー|取得するもの|
|:-- |:-- |
|space|取得対象のページが所属するスペース|
|version|取得対象のページのバージョン|
|history|取得対象のページの履歴|
|body|取得対象のページ本文|


```javascript:コード.gs
/* コンフルのGETAPI呼び出し関数 */
function get_previous_content(headers, page_id, expand_string) {
  var options = {
    'headers'  : headers,
    'method'  : 'get'
  };
　
  // 記事内容を取得
  var api_url      = API_BASE_URL + '/rest/api/content/' + page_id + '?expand=' + expand_string;
  var response     = UrlFetchApp.fetch(api_url, options).getContentText('UTF-8');
  var content_json = JSON.parse(response);
  
  return content_json;
}

/* コンフルのPUTAPI呼び出し関数 */
function put_content(headers, page_id, current_version, new_page_title, new_page_body, expand_string) {
  var new_version = current_version + 1; // バージョンを更新
  // 更新内容を定義
  var payload = {
    'type'    : 'page',
    'version' : {'number' : new_version},
    'title'   : new_page_title,
    'body'    : {
      'storage' : {
        'value'          : new_page_body,
        'representation' : 'storage'
      }
    }
  };
  payload = JSON.stringify(payload);
  
  options = {
    'headers' : headers,
    'payload' : payload,
    'method'  : 'put'
  };
  
  // 記事内容を更新
  var api_url      = API_BASE_URL + '/rest/api/content/' + page_id;
  var response     = UrlFetchApp.fetch(api_url, options).getContentText('UTF-8');
  var content_json = JSON.parse(response);
 
  return content_json;
}

```

###### ○ソースコード3
Mainとなる関数。
現在の記事内容を取得して、
**\<strong>～日\</strong>**の部分を1日減算して、ページを更新する処理を定義する。
正規表現の部分を変更すれば、どんな箇所でも更新可能です。

```javascript:コード.gs
/* 記事を更新する関数 */
function updateContent() {
  // 現在の記事内容を取得
  var headers      = create_headers(user_id, password);
  var prev_content = get_previous_content(headers, page_id, 'body.storage,version');
  
  // 記事内容を変更
  var pattern_match = prev_content.body.storage.value.match(/<strong>([\s\S]*?)日<\/strong>/);
  
  // 数値部分を抜粋
  var day = parseInt(pattern_match[0].replace(/[^0-9^\.]/g, ""), 10);
  // 0よりも大きければ、日数を減算
  if (day > 0) {
    day -= 1;
    // 更新後コンテンツを定義
    var new_page_content = prev_content.body.storage.value.replace(/<strong>([\s\S]*?)日<\/strong>/, "<strong>" + day + "日</strong>");
    // 更新
    var content_json = put_content(headers, page_id, prev_content.version.number, prev_content.title, new_page_content, null);
  }
}

```

#### 3. スクリプトを実行
実行関数に*updateContent*を指定して実行！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250217/bae680df-1ff0-37d6-6859-2bf8a5a78961.png)

意図したページになっているかを確認。

#### 4. GASを定期実行する。
赤枠の「トリガー」をクリックして、トリガー設定画面に進む。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250217/aa4ebc4b-ca2a-a707-ae9a-67602925ffb5.png)

トリガーを追加をクリックして、トリガーの詳細を設定。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250217/a979148a-d2a9-3fcf-d86a-e3459f929297.png)

今回の詳細は、
**updateContent関数を1日に1回午前0時～1時の間に実行する。**
「**午前0時～1時の間に**」→この部分が前述したGASがクリティカルな時間調整が必要な場合には向いていない理由
何分に実行するかどうかまでは、指定できません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250217/c53d340e-ea3c-8bbe-24e8-704e1a939c07.png)

これで、定期実行設定完了！
あとは、明日の実行結果を待つのみ。

## まとめ
- コンフルに管理者権限等がなくてもAPIキーは発行できるので、個人ベースでAPIを使用できる。
- GASの記法はほぼJavascriptなので、学習コストが抑えられる。
- GASでhtmlを扱うのは少々困難。(私が正規表現苦手なだけ…??何度もデバックして確認しました…。)
- GASなら定期実行も簡単にできる。
- 間違っても、定期実行で1分おきとかにしないように！！(通知を受けている人がいると、えらいことに…。)