Cloudflare Workers から Connpassのイベント一覧を取得してDiscordに通知を出すBotを作成した
現在Connpass上で、Cloudflare Meetupグループが立ち上がり、すでに札幌、福岡、大阪、仙台、名古屋、長野で募集を開始しています。さらにまだ募集開始されていませんが、4-21 熊本、4-25東京での募集が決定しており、今後追加で新潟、広島、沖縄、金沢も現地のエンジニアコミュニティと会話が始まっています。
日本全国の運営メンバーとはDiscordで会話をしています。もしDiscordの参加に興味があるかたはTwitter:@kameoncloudまで連絡ください。
そこでの会話でConnpassのイベント一覧からCloudflareが含まれているものがあれば通知が欲しいね、という話がありました。ということでWorkersで作ってみました。すでにDiscordへの通知済イベントのフラグ管理にはKVを使いました。
純粋なWorkersとKVのサンプルはこちらをご覧ください。
上記の例ではハンドラーがFetch Handlerになっているのでこのままでは使えません。今回はWorkersのCron機能を使うために、Scheduled Handlerを使います。
先にコードを共有すると以下になります。
export default {
async scheduled(controller, env, ctx) {
const url = 'https://connpass.com/api/v1/event/?keyword=cloudflare';
async function gatherResponse(response) {
const { headers } = response;
const contentType = headers.get('content-type') || '';
const text = await response.text();
return text;
}
const init = {
headers: {
'content-type': 'application/json;charset=UTF-8',
'User-Agent': 'cloudflareworkers',
},
};
const response = await fetch(url, init);
const results = await gatherResponse(response);
let data;
try {
data = JSON.parse(results);
} catch (e) {
console.log("Error parsing JSON:", e);
console.log("JSON input:", results);
}
console.log(data.results_returned)
for (let i = 0; i < data.results_returned; i++) {
console.log(data.events[i].event_url)
const value = await env.KV.get(data.events[i].event_url);
if (value === null) {
console.log('not found');
env.KV.put(data.events[i].event_url, data.events[i].event_url)
console.log('added');
const webhook_url = 'https://discord.com/api/webhooks/xxxxx/xxxxx';
const data2 = {
content: data.events[i].event_url,
};
console.log(data2);
fetch(webhook_url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data2),
})
.then((response) => {
console.log(`statusCode: ${response.status}`);
return response.json();
})
.then((data2) => {
console.log(data2);
});
}
else {
console.log('found');
}
}
return new Response("ConnpassをCloudflareで検索して新規イベントだったらKVに登録してアラートを出す");
},
};
(勿論KVストアはKVとしてバインドされています)
今回は単純なWebhookをDiscordに飛ばしています。設定は簡単です。通知を出したいチャンネルの歯車をクリックします。
連携サービスを左ペインからクリックして、ウェブフックを押します
新しいウェブフックを押し、"ウェブフックURL"をコピーすればOKです。
このURLで上記のconst webhook_urlの値を置き換えてください。
ConnpassAPI諸元はこちらです。https://connpass.com/about/api/
今回は単純にイベント一覧を出しています。
これだと将来的に数が多すぎたりConnpass側にも負荷がかかってしまうため、絞り込みたいところですが、残念ながらイベント募集開始日やイベントページ公開日での絞り込みができず、良い方法がありませんでした。
少し様子を見たいと思います。
Workersをcronで実行させるにはwrangler.tomlには以下を追記する必要があります。
[triggers]
crons = ["*/1 * * * *"]
この例では1分単位で実行しています。今はテストを行っているので、1分単位にしていますが、これも普段お世話になっているConnpassさんの負荷を考えると5分ぐらいにしたいところですね。
はまったところですがConnpassAPIをたたく際にはUser-Agentがないとエラーとなり、空のJSONが戻ってくるようでした。(ドキュメントには記載がなかったのですが数時間はまって要約解決できたためたぶんそういう仕様と思います)このため
const init = {
headers: {
'content-type': 'application/json;charset=UTF-8',
'User-Agent': 'cloudflareworkers',
},
};
としてUser-Agentをセットしています。
APIから戻ってきたJSONをパースした後、以下でイベント件数を検知し、イベント募集ページURLを取得しています。URLがKVに存在していなければKVに保存しWebhookを飛ばしています。
for (let i = 0; i < data.results_returned; i++) {
console.log(data.events[i].event_url)
const value = await env.KV.get(data.events[i].event_url);
if (value === null) {
console.log('not found');
env.KV.put(data.events[i].event_url, data.events[i].event_url)
console.log('added');
const webhook_url = 'https://discord.com/api/webhooks/xxxxx/xxxxx';
const data2 = {
content: data.events[i].event_url,
};
console.log(data2);
fetch(webhook_url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data2),
})
.then((response) => {
console.log(`statusCode: ${response.status}`);
return response.json();
})
.then((data2) => {
console.log(data2);
});
}
else {
console.log('found');
}
}
イベント件数を絞り込まず取得してその回数分KVを呼び出しているので、n+1のようにあまり実装としてはよろしくないかな?と思っています。多対多の関係性ではないので厳密にはn+1問題ではないのでしょうが、シングルテーブルのKVストアを使う場合どのようにするのがベストプラクティスかご存じの方は教えてもらえるとありがたいです。ConnpassAPIからイベント登録日が戻ってくれば嬉しいところです。
順調に動作しているようです!
Discussion