はじめに
現在一部の有志の人たちで、Notion のまとめページを作っています。このチームで新着記事の収集システムの設計を任されました。手動で収集するのは大変なので、RSS から自動で記事を収集し、データベースに登録します。管理者が内容を確認し、必要項目を登録した上で公開チェックをオンにするという流れです。すでに以下の逆引きNotion のページで Google Form から記事登録する仕組みは作っているので、Notion 登録部分は簡単に移行できます。今回は、RSS の取り込みがうまくできるかというところと、第三者に使ってもらうシステムとして設計するというところを中心に説明していきます。
スプレッドシートの準備
実装は GAS で行います。そのためまず、Google Spreadsheet を用意しました。 最初の3行はシステムの管理および初期設定用のデータ置き場です。以下の 6 つの値を登録します。
- 登録用チェック日付: 通常は空白にして前日の記事を取得して Notion に登録します。ただし、新しい RSS サイトを登録した時には動作確認をしたいので、その RSS サイトの新着記事のある日付を記載します。ここに日付が記載されている場合には、リストの一番最後の RSS を取得し、この日付の記事に対して処理を行います。これで Notion に登録が行われていれば成功ということになります。テストが終了したらこの日付は消しておきます。
- notion_api_token: スクリプト内に api token を直接記載したくないので、いつものようにスクリプトプロパティに登録します。自分の場合は、スクリプトを直接修正するのですが、今回第三者に提供することもあり、スプレッドシートから読み込むことにしました。初期設定時のみここから値を取り込み、プロパティに登録します。設定が終了したら空白に戻されます。
- database_id: こちらはデータベースIDを登録します。notion_api_token と同様に初期設定の時のみ記載し、終了したらこちらも空白に戻されます。
- タイトルの属性名: タイトルを登録するデータベースの属性名を記載します。
- URL の属性名: 記事への URL を登録するデータベースの属性名を記載します。
- 更新日付の属性名: 記事の登録日付を登録するデータベースの属性名を記載します。
その下は実際に登録したい RSS を並べます。必要なのは URL だけで Title は人間がわかりやすいように記載しているだけです。登録チェック日付が登録されている場合には、最下段のものだけが有効になります。また、追加で固定値を登録しておきたい属性があれば、そのタイトルと属性の型を記述しています。
Notion データベースの作成
こちらはまとめサイトと同じ形に設定しておきました。
スクリプトの記述
スクリプトプロパティ登録
スクリプトプロパティの登録は最初に一回だけ実施します。いつもはスクリプト内に記述しているのですが、人に使ってもらうということでスプレッドシート上に記載してもらい、一度だけ実行してもらうことにしました。実行すると記述したデータは消えるようにしてあります。
// NOTION_API_TOKEN と DATABASE_ID をプロパティに登録する(1回だけ実行し、終わったらスプレッドシートの該当部分を空白に戻す) function storeTokenAndIds() { const scriptProperties = PropertiesService.getScriptProperties() const sheet = SpreadsheetApp.getActiveSheet() const notionApiTokenCell = sheet.getRange("B2") const databaseIdCell = sheet.getRange("B3") scriptProperties.setProperties({ "NOTION_API_TOKEN": notionApiTokenCell.getValue(), "DATABASE_ID": databaseIdCell.getValue() }) // 登録が終わったのでセル内容を削除 notionApiTokenCell.setValue("") databaseIdCell.setValue("") // 登録できたことを確認 console.log("notionApiToken = " + notionApiToken()) console.log("databaseId = " + databaseId()) } // NOTION_API_TOKEN を取得 function notionApiToken() { return PropertiesService.getScriptProperties().getProperty("NOTION_API_TOKEN") } // DATABASE_ID を取得 function databaseId() { return PropertiesService.getScriptProperties().getProperty("DATABASE_ID") }
スプレッドシートの固定値を取得
毎回スプレッドシートから値を取得するのは面倒なので、あらかじめ固定値は取得しておきます。
function readConstant() { const sheet = SpreadsheetApp.getActiveSheet() const ans = [ ["cellDate", "B1"], ["titleProperty", "B4"], ["urlProperty", "B5"], ["dateProperty", "B6"] ].reduce((hash, line) => { hash[line[0]] = sheet.getRange(line[1]).getValue() return hash }, {}) const others = sheet.getRange(7, 3, 8, sheet.getLastColumn()-2).getValues() if (others[0][0] != "") { ans["types"] = others[0] ans["titles"] = others[1] } ans["lastRow"] = sheet.getLastRow() ans["lastColumn"] = sheet.getLastColumn() return ans }
RSS からの情報取得
RSS からデータを取り出し、checkDate と等しいものだけをフィルタして返すだけの関数です。特に難しいことはしていません。
// RSS から情報を取得する function getRSS(url, checkDate) { const checkDateStr = Utilities.formatDate(checkDate, "JST", "yyyy-MM-dd") const xml = UrlFetchApp.fetch(url).getContentText() const root = XmlService.parse(xml).getRootElement() const items = root.getChildren("channel")[0].getChildren("item") return items.filter(item => { const dateStr = item.getChildText("pubDate") if (dateStr) { const date = Utilities.formatDate(new Date(dateStr), "JST", "yyyy-MM-dd") return date == checkDateStr } else { return false } }) }
Notion へのページ登録
上記で取得した定数、RSS のデータ、RSS の行、および登録日付を受け取り、Notion に登録する関数です。最初に RSS のデータから記事のタイトルと URL だけ取得します。また、追加属性については、とりあえず text, select, multi_select 属性に対応してみました。
function registerNotion(constant, item, line, date) { const title = item.getChildText("title") const link = item.getChildText("link") const payload = { parent: { database_id: databaseId(), }, properties: { [constant.titleProperty]: { title: [ { text: { content: title } } ] }, [constant.urlProperty]: { url: link }, [constant.dateProperty]: { date: { start: Utilities.formatDate(date, "JST", "yyyy-MM-dd") } } } } if (constant.titles) { const others = constant.sheet.getRange(line, 3, 1, constant.lastColumn - 2).getValues()[0] others.forEach((value, index) => { if (value != "") { const title = constant.titles[index] const type = constant.types[index] switch (type) { case "rich_text": payload.properties[title] = { rich_text: [ { type: "text", text: { content: others[index] } } ] } break; case "multi_select": payload.properties[title] = { multi_select: [ { name: others[index] } ] } break; case "select": payload.properties[title] = { select: { name: others[index] } } break; } } }) } const options = { "method": "POST", "headers": { "Content-type": "application/json", "Authorization": "Bearer " + notionApiToken(), "Notion-Version": "2022-02-22", }, "payload": JSON.stringify(payload) } Utilities.sleep(400) return JSON.parse(UrlFetchApp.fetch("https://api.notion.com/v1/pages", options)) }
メイン関数
メイン関数はこんな感じになりました。B1 に日付が入っていない場合には通常モードとして、記録されているすべての RSS を検索し、昨日の記事をピックアップして Notion に登録します。日付が入っている場合には、一番最後の RSS の特定日付のものを抽出して Notion に登録します。これで登録されることが確認できたら、日付を消せば明日以降は自動登録される形になります。
// メイン関数 function main() { const constant = readConstant() const cellDate = constant.cellDate if (cellDate == "") { // 通常モード for (let line = 9; line < constant.lastRow; line++) { var yesterday = new Date() yesterday.setDate(yesterday.getDate() - 1) const url = constant.sheet.getRange(line, 2).getValue() getRSS(url, yesterday).forEach(item => { registerNotion(constant, item, line, yesterday) }) } } else { // 登録確認モード const line = constant.lastRow const url = constant.sheet.getRange(line, 2).getValue() getRSS(url, cellDate).forEach(item => { registerNotion(constant, item, line, cellDate) }) } }
トリガーの設定
時間主導型のトリガを設定すれば終了です。数日動くことを確認したら、依頼主に納品しようと思います。
おわりに
今のところ、note の RSS でしかテストしていないので、その他の RSS についてもテストしていこうと思います。