これは ZOZO Advent Calendar 2024 カレンダー、シリーズ 8 の 23 日目の記事です。
はじめに
弊社では社内wikiとしてConfluenceを利用しています。
定例などで毎日同じ資料を作成する必要があったのですが、Confluence APIとGoogle Apps Script(GAS)を使用して資料作成を自動化してみたので紹介しようと思います。
後述しますが、今回使用するAPIのバージョンは非推奨になっているので、可能であれば最新バージョンのものを採用するようにしてください(本記事も最新バージョンへの移行が完了次第更新しようと思います)。
Confluence API
Confluenceはページ情報の取得やページ作成といった機能をAPIとして提供しています。
ref. https://developer.atlassian.com/cloud/confluence/rest/v1/intro
手元で動作確認する前に、Confluence APIを使用するためにはアクセストークンが必要なのでマイページからパーソナルアクセストークンを生成しておきます。
(https://<ドメイン>/wiki/plugins/personalaccesstokens/usertokens.action
からトークン管理ページに飛べます)
トークンを生成したらAPIを叩いてみます。
今回はページ作成APIを使用するので、Postmanで動作確認してみます。
AuthorizationヘッダーにはBearer xxxx(パーソナルアクセストークン)
の形で値をセットします。
リクエストボディには作成するページのスペースキーを指定します。
スペースキーはスペースのホーム画面のサイドバーから「ページ」を選択するとhttps://<ドメイン>/wiki/collector/pages.action?key=<スペースキー>
に遷移するので、このURLから取得できます。
(適当なページに対してコンテンツ取得APIを叩いても取得できます)
コンテンツ取得APIの実行例(jqで整形しています)
$ curl -X GET 'https://xxxx/wiki/rest/api/content/774059380' -H 'Authorization: Bearer xxxx' -H 'Accept:application/json' | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2366 100 2366 0 0 17859 0 --:--:-- --:--:-- --:--:-- 17789
{
"id": "774059380",
"type": "page",
"status": "current",
"title": "2024/12/23 チーム定例",
"space": {
"id": xxxx,
"key": "xxxx", # これがスペースキー
"name": "xxxx",
"type": "personal",
"_links": {
"webui": "/spaces/viewspace.action?key=xxxx",
"self": "https://xxxx/wiki/rest/api/space/xxxx"
},
"_expandable": {
"metadata": "",
"icon": "",
"description": "",
"retentionPolicy": "",
"homepage": "/rest/api/content/xxxx"
}
},
"history": {
"latest": true,
"createdBy": {
"type": "known",
"username": "xxxx",
"userKey": "xxxx",
"profilePicture": {
"path": "/wiki/images/icons/profilepics/default.svg",
"width": 48,
"height": 48,
"isDefault": true
},
"displayName": "xxxx",
"_links": {
"self": "https://xxxx/wiki/rest/api/user?key=xxxx"
},
"_expandable": {
"status": ""
}
},
"createdDate": "2024-12-19T14:55:18.399+09:00",
"_links": {
"self": "https://xxxx/wiki/rest/api/content/774059380/history"
},
"_expandable": {
"lastUpdated": "",
"previousVersion": "",
"contributors": "",
"nextVersion": ""
}
},
"version": {
"by": {
"type": "known",
"username": "xxxx",
"userKey": "xxxx",
"profilePicture": {
"path": "/wiki/images/icons/profilepics/default.svg",
"width": 48,
"height": 48,
"isDefault": true
},
"displayName": "xxxx",
"_links": {
"self": "https://xxxx/wiki/rest/api/user?key=xxxx"
},
"_expandable": {
"status": ""
}
},
"when": "2024-12-19T15:00:55.741+09:00",
"message": "",
"number": 1,
"minorEdit": false,
"hidden": false,
"_links": {
"self": "https://xxxx/wiki/rest/experimental/content/774059380/version/1"
},
"_expandable": {
"content": "/rest/api/content/774059380"
}
},
"extensions": {
"position": "none"
},
"_links": {
"webui": "/pages/viewpage.action?pageId=774059380",
"edit": "/pages/resumedraft.action?draftId=774059380&draftShareId=76490fab-b46c-4d65-a2af-1d0ed52ae1b8",
"tinyui": "/x/dDUjLg",
"collection": "/rest/api/content",
"base": "https://xxxx/wiki",
"context": "/wiki",
"self": "https://xxxx/wiki/rest/api/content/774059380"
},
"_expandable": {
"container": "/rest/api/space/xxxx",
"metadata": "",
"operations": "",
"children": "/rest/api/content/774059380/child",
"restrictions": "/rest/api/content/774059380/restriction/byOperation",
"ancestors": "",
"body": "",
"descendants": "/rest/api/content/774059380/descendant"
}
}
親ページのIDも同様に、生成するページの親ページのURL(https://<ドメイン>/wiki/pages/viewpage.action?pageId=<ページID>
)から取得できます。
APIを叩くとページが作成されるのを確認できました。
HTMLタグもいい感じに見出しやテーブルなどに解釈してくれてますね。
ちなみに、テーブルが空欄だと余白が潰れて見栄えがあまり好ましくなかったので無理やり穴埋めしています。
このように、Confluence内で編集するより勝手は悪いので、凝ったデザインとかは難しいかもしれないです。
最新バージョンのAPIでは正常なレスポンスを確認できなかったので、とりあえず運用するという目的で今回は非推奨バージョンでAPIを叩きます(動作確認でき次第、本記事も更新しようと思います)。
最新バージョンでの動作確認
Bearerトークンをセットして投稿APIを叩いていますが、You are already logged in
というタイトルのHTMLページがレスポンスされ、ページは作成されません。
(同じような現象で問い合わせされていたが、解決してなさそう)
有識者の方、原因に心当たりあればご教示いただけますととっても助かります。
GAS
ページ作成できることを確認できたので、GASで定期的に実行してSlackに通知しようと思います。
Slack Appの準備
先にSlack Appを作成しておきます。
(こちらのブログが図解も豊富で分かりやすいかもです)
-
https://api.slack.com/apps から
Create New App > From scratch
を選択します - アプリ名を入力し、ワークスペースを選択したらアプリが生成されます
- 左のメニューバーからOAuth & Permissionsを選択し、Add an OAuth Scopeでスコープを追加します。今回は
chat:write
、chat:write.customize
、chat:write.public
、incoming-webhook
を追加しました - 左のメニューバーから
Install App
を選択し、ワークスペースに通知する権限を許可します -
Bot User OAuth Token
が表示されるので控えておきます
スプシの準備
自分の所属するチームの定例は①毎日行う、②ファシリテーターは週ごとに交代するという運用方法なので、その要件に沿って実装していきます。
ファシリテーターを毎週変更するために、スプシに以下の内容でシートを用意しておきます。idはSlackで担当者をメンションするためのメンバーIDです。Slackでメンバーのプロフィールを開き、メンバーIDをコピー
で取得できます。
GASの準備
GASの内容は以下のように実装しました。
MEMBER_NUM = 3;
let token = PropertiesService.getScriptProperties().getProperty("SLACK_TOKEN");
let channelID= PropertiesService.getScriptProperties().getProperty("CHANNEL_ID");
function main() {
let facilitator = getFacilitator();
let pageID = createPage();
if (facilitator) {
sendSlackMessage(facilitator, pageID);
}
}
// その週のファシリテーターを取得します
function getFacilitator() {
let today = new Date();
let dayOfWeek = today.getDay();
if (dayOfWeek === 0 || dayOfWeek === 6) {
return;
}
let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('<スプシのシート名>');
let membersLastRow = sheet.getLastRow()
let members = sheet.getRange(2, 2, membersLastRow - 1).getValues().map(row => row[0]);
let facilitator_index = sheet.getRange('C2').getValue();
if (dayOfWeek === 1) {
if(facilitator_index+1 >= MEMBER_NUM) {
facilitator_index = 0
} else {
facilitator_index++;
}
updateSpreadSheet(sheet, facilitator_index)
sheet.getRange('C2').setValue(facilitator_index)
}
return members[facilitator_index];
}
// Confluence APIでページを作成します
function createPage() {
let url = "https://<ドメイン>/wiki/rest/api/content";
let dt = new Date();
let y = dt.getFullYear();
let m = ("00" + (dt.getMonth()+1)).slice(-2);
let d = ("00" + (dt.getDate())).slice(-2);
let title = y+m+d+' チーム定例';
let payload = {
"title": title,
"type": "page",
"space": {
'key':'<スペースキー>'
},
"ancestors": [
{
"id": "<親ページのID>"
}
],
"body": {
"storage": {
"representation": "storage",
"value": "<h1>質問・共有事項</h1><table><tr><th>記入者</th><th>内容</th><th>議事録</th></tr><tr><td></td><td></td><td></td></tr></table><br/><h1>進捗確認</h1><h2>案件1</h2><a href='https://example.com'>https://github.com/project1</a><br/><h2>案件2</h2><a href='https://example.com'>https://github.com/project2</a><br/>"
}
}
}
let options = {
"method": "post",
"headers": {
"Authorization": "Bearer <パーソナルアクセストークン>",
"Accept": "application/json",
"Content-Type": "application/json"
},
"payload" : JSON.stringify(payload),
"muteHttpExceptions": true
}
let response = JSON.parse(UrlFetchApp.fetch(url, options));
return response["id"];
}
// Slackに通知します
function sendSlackMessage(facilitator, pageID) {
let message = "<@" + facilitator + ">\n今日のチーム定例のファシリテーターです。\n資料: https://<ドメイン>/wiki/pages/viewpage.action?pageId=" + pageID;
let slackApp = SlackApp.create(token);
slackApp.postMessage(channelID, message);
}
トークンはSlack App作成時に控えておいたBot User OAuth Token
です。
チャンネルIDは通知したいチャンネル名を右クリックし、コピー>リンクをコピー
で取得したURLから取得できます(https://<ドメイン>/archives/<チャンネルID>
という形式)。
どちらもスクリプトプロパティにキー・バリューを保存し、PropertiesService.getScriptProperties().getProperty()
で取得しています(スクリプトプロパティは左メニューバーのプロジェクトの設定
から保存できます)。
試しにmain関数を実行してみましょう。対象の関数がmainに指定されていることを確認し、実行ボタンを押します。
Confluenceにページが作成されてSlackに通知が飛んでいることを確認できました!
定期実行の設定
最後に、定期的にGASを実行するようにトリガーを追加します。
左メニューバーのトリガー
を選択し、トリガーを追加
をクリックします。
ポップアップが表示されるので、お好みで関数の実行タイミングを設定します。今回は毎日ページ作成と通知を行うために時間主導型
・日付ベースのタイマー
・午前11時〜午後12時
で設定しました。
これで毎日指定した時刻の間に関数が実行され、資料の作成とSlackの通知を自動化することができました🎉
まとめ
Confluence APIとGASを使って、毎日資料を作成してSlackに通知する方法を見てきました。
今回の実装だと、定例で更新された内容を次の資料に反映させることができなかったり、資料の内容をHTMLで直書きしていて更新が面倒なので、この辺りは今後改善していきたいですね。
ではまた。