イベントの申込がどれくらい増えているか、毎朝チェックしたい。そう思って、Peatixの申込データを自動で取得してメール通知する仕組みを作った。
同じような記事はすでにいくつかあって、Google Apps Script(GAS)から直接Peatixのデータを取りに行く方法が紹介されている。今回わたしもその方法を試したが、途中でうまくいかなくなった。理由も含めて、実際にやった手順をまとめておく。
Peatixの申込データはどこにあるのか
Peatixのイベントページには、裏側で使われている集計用のURLがある。イベントURLの末尾に get_view_data をつけるだけで、申込数などのデータがJSON形式で見られる。
https://peatix.com/event/イベントID/get_view_data
イベントIDは、イベントページのURLやHTMLソースの中から見つけられる。試しにブラウザでこのURLを開いてみると、こんな内容が返ってくる。
{
"json_data": {
"event": {
"seatsSold": 63,
"tickets": [
{ "name": "早割チケット", "seatsSold": 42, "seatsMax": 500 },
{ "name": "一般チケット", "seatsSold": 15, "seatsMax": 1000 }
]
}
}
}
含まれているのは合計申込数とチケット種別ごとの枚数だけで、参加者の名前やメールアドレスは入っていない。主催者側が公開設定にしている集計情報という位置づけだと考えている。
GASから直接取りに行くと、ブロックされた
最初はGoogle Apps Scriptから直接このURLにアクセスしようとした。ところが、実行してみるとJSONではなくエラーページが返ってくる。
いろいろ試してわかったのは、Peatixが日本国外のIPアドレスからのアクセスにはJSONを返さないらしい、ということだった。GASのサーバーは海外にあるため、ここで弾かれてしまう。
中継サービスを挟めば回避できるかと思い、Jina ReaderやCloudflare Workerといった外部サービス経由で試したが、どちらも海外拠点で動くため同じ理由でうまくいかなかった。
日本にあるパソコンから取得する
結局たどり着いたのは、日本国内にあるパソコン(今回はわたしのMac)から直接Peatixにアクセスして、その結果をGASに渡すという構成だった。
毎朝、Macが起動する
↓
MacがPeatixからJSONを取得する
↓
GASのWebアプリにデータを送る
↓
GASがメール送信とスプレッドシートへの記録をする
役割を分けたことで、それぞれの仕組みがシンプルになった。Mac側はデータを取ってくるだけ、GAS側はメールと記録だけを担当する。
手順1 GASでWebアプリを作る
Google Apps Script(script.google.com)で新しいプロジェクトを作り、次のようなコードを貼りつける。
const CONFIG = {
eventName: 'イベント名',
eventUrl: 'https://example.peatix.com/',
mailTo: '通知したいメールアドレス',
propKey: 'peatix_last_state',
sharedKey: '好きな合言葉',
logSheetId: '記録用スプレッドシートのID',
};
function doPost(e) {
try {
const payload = JSON.parse(e.postData.contents);
if (payload.key !== CONFIG.sharedKey) {
return ContentService.createTextOutput('forbidden');
}
const event = payload.json_data.event;
notify_(event);
return ContentService.createTextOutput('ok');
} catch (err) {
MailApp.sendEmail(CONFIG.mailTo, 'Peatix通知エラー', 'エラー: ' + err.message);
return ContentService.createTextOutput('error: ' + err.message);
}
}
function notify_(event) {
const props = PropertiesService.getScriptProperties();
const last = JSON.parse(props.getProperty(CONFIG.propKey) || '{}');
const total = event.seatsSold;
const diff = (last.total == null) ? null : total - last.total;
const ticketLines = (event.tickets || []).map(t => {
return `・${t.name}: ${t.seatsSold}/${t.seatsMax}枚`;
}).join('\n');
const subject = `Peatix申込 合計${total}枚`;
const body = `合計申込: ${total}枚\n\n${ticketLines}`;
MailApp.sendEmail(CONFIG.mailTo, subject, body);
const ticketMap = {};
(event.tickets || []).forEach(t => { ticketMap[String(t.id)] = t.seatsSold; });
props.setProperty(CONFIG.propKey, JSON.stringify({ total: total, tickets: ticketMap }));
}
「合言葉」は、誰でも呼び出せるURLにしないための簡単な仕組み。決まった文字列を一致させたときだけ処理を進めるようにしている。
書けたら、右上の「デプロイ」から「新しいデプロイ」を選び、種類は「ウェブアプリ」にする。実行するユーザーは自分、アクセスできるユーザーは全員にして、デプロイ後に表示されるURLを控えておく。このURLがMac側から呼び出す宛先になる。
手順2 Macから取得してGASに送るスクリプトを作る
シェルスクリプトを1つ作る。
#!/bin/zsh
GAS_URL="手順1で控えたデプロイURL"
EVENT_ID="イベントID"
json=$(curl -s --max-time 60 \
-A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0 Safari/537.36" \
"https://peatix.com/event/${EVENT_ID}/get_view_data")
if [[ "$json" != \{\"json_data\"* ]]; then
echo "取得に失敗した"
exit 1
fi
printf '%s' "$json" | python3 -c "
import json,sys
d=json.load(sys.stdin)
d['key']='手順1で決めた合言葉'
print(json.dumps(d))
" | curl -s -L -H "Content-Type: application/json" -d @- "$GAS_URL"
Peatixに投げるときは、ブラウザからのアクセスに見えるよう User-Agent を指定している。これがないと弾かれることがあった。
保存したら、ターミナルで一度実行してみる。
chmod +x fetch_and_post.sh
./fetch_and_post.sh
メールが届けば成功。
手順3 毎朝自動で実行されるようにする
Macには、決まった時刻に処理を実行する仕組み(launchd)が最初から入っている。次のような設定ファイルを作る。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.peatix-notify</string>
<key>ProgramArguments</key>
<array>
<string>/bin/zsh</string>
<string>フルパスでfetch_and_post.shの場所</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>8</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</dict>
</plist>
~/Library/LaunchAgents/ に保存して、ターミナルで登録する。
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.peatix-notify.plist
これで毎朝8時に、Macが起動していれば自動で実行される。眠っている時間帯でも、次に起きたタイミングで実行してくれるようだった。
スプレッドシートで宛先や文面を変えられるようにする
メールの宛先や本文をコードに直接書いてしまうと、変えるたびにGASを再デプロイする必要があって面倒だった。そこで、スプレッドシートに「設定」というシートを作り、そこから読み込むようにした。
function readSettings_() {
const ss = SpreadsheetApp.openById(CONFIG.logSheetId);
let sheet = ss.getSheetByName('設定');
if (!sheet) {
sheet = ss.insertSheet('設定');
sheet.getRange('A2').setValue('送信先メールアドレス');
sheet.getRange('B2').setValue(CONFIG.mailTo);
sheet.getRange('A3').setValue('追伸文');
sheet.getRange('B3').setValue('ここに説明文を入れる');
}
return {
mailTo: sheet.getRange('B2').getValue(),
footerNote: sheet.getRange('B3').getValue(),
};
}
これで、宛先や文面を変えたいときはシートのセルを書き換えるだけになった。コードは触らなくていい。
申込の推移をスプレッドシートに残す
毎回のデータをそのまま流すだけではもったいないので、実行するたびに1行ずつ記録するようにした。
function logToSheet_(event, total, diff) {
const ss = SpreadsheetApp.openById(CONFIG.logSheetId);
let sheet = ss.getSheetByName('記録');
if (!sheet) {
sheet = ss.getSheets()[0];
sheet.setName('記録');
const header = ['日時', '合計申込数', '前日比', ...(event.tickets || []).map(t => t.name)];
sheet.appendRow(header);
}
const now = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss');
const row = [now, total, diff, ...(event.tickets || []).map(t => t.seatsSold)];
sheet.appendRow(row);
}
これで、日ごとの推移がスプレッドシートに積み上がっていく。
やってみて思ったこと
海外サーバー経由のアクセスが弾かれる、という仕様には正直気づくのに時間がかかった。同じ壁にぶつかっている人はいるかもしれない。GASだけで完結させようとせず、手元のパソコンと組み合わせるという発想に切り替えたところで、ようやくうまく動いた。
仕組みとしてはそれほど難しいものではなく、パーツを一つずつ確認しながら組み立てていけば、初めてでも再現できると思う。

