Honoでランダムにアイスクリームを提案するLINE BOTを作る
はじめに
コンビニのアイスクリームでCMJ(チョコモナカジャンボ)が味、食感、満足感など様々な点で最強すぎることに気づいた。
アイスクリームを食べるときはほぼ必ずCMJしか食べなくなってしまったため、今回は新しい最強のアイスクリームを見つけるをコンセプトに、ランダムでアイスクリームを提案してくれるLINE BOTを作成する。
コンセプト
- CMJに変わる新しい最強のアイスクリームを見つける
- 熱いと話題のHonoを触ってみる
- ついでにHonoと相性が良いCloudflare Workersも使ってAPIサーバーを構築する
できたもの
デモ
QR
リポジトリ(README編集中)
フロー
- ユーザーが「今日のアイスクリーム」と送信
- アイスクリーム情報(商品画像、商品名、商品価格)を返却
観点
まず終わらせろの精神にもとづき、必要最低限の実装をした。
- セブンイレブンで提供されている商品かつ関東地方
- 特定のメッセージに対してのみ返信
1個ずつ解説します。
セブンイレブンで提供されている商品かつ関東地方
セブンイレブンで提供されている商品かつ、関東地方に場所を限定した。
セブンイレブンに絞った理由は以下の通りです。
情報が1ページにまとまっていること
まずはじめにユーザーへ返却する情報は以下と定義した。
- 商品画像
- 商品名
- 商品価格
この3つさえあればブラウザで商品の検索や、店舗へ行って探すときに困らないと判断した。
またこれらのデータはブラウザからスクレイピングする必要があるため、取得するコードを楽に書きたかったこともあり情報が1ページで表示されているものを選んだ。
自社ブランド以外の商品がある
大手コンビニ各社では自社ブランド(セブンプレミアム、ファミマル、ローソンセレクト)を展開しているが、一部コンビニの商品サイトにパルムやスーパーカップなど大手メーカーのアイスクリームが一覧として表示されていない。
今回はCMJを越える最強のアイスクリームを発見することが目的なので、自社ブランドしか提供していないコンビニは対象から外すことにした。
店舗数が多いこと
コンビニのアイスクリームに絞った以上、店舗数が多いほうが最強のアイスクリームを探しやすいと判断した。
拡張性
今回は作成規模の関係で関東地方のアイスクリームしか検索の対象にしていないが、関西のアイスクリームやスイーツも対象にしたいなど、様々な要件に対応できるコンビニが適切と判断した。
特定のメッセージにのみ返信する
本当はUIUXなどユーザー体験を高めるために細かいところまで気を配りたかったが、それをするとまた面倒くさくなってリリースできないことをリスクと考えた。
今回は「今日のアイスクリーム」と送信するだけでアイスクリームの情報を提供できるようにした。
作成後の感想
追加で実装したい部分
cron triggersを使ったデータの自動更新
私が確認した限り、毎週火曜日午前7時時点でセブンイレブンの新商品が更新されている。
朝からアイスクリームを食べる人は少ないであろう読みで、毎週火曜日午前7時にアイスクリームの情報洗い替えすることで、最新のアイスクリーム情報を提供できる仕組みを作成したい。
こちらが参考になると思い進めているが、なぜかkv_namespaces
の値が取得できず詰まってしまったので、定期実行は一度保留にしてリリースすることを優先させた。
技術的な部分
簡単な実装しかしていないがHonoのユーザー体験が良く、直感的にAPIを作成することができた。
Cloudflare Workersとの相性もよく、爆速でデプロイできることからも小規模アプリであれば尚更使いやすいんじゃないかなと感じた。
LINE Messaging APIでのアプリ?開発も始めてでしたが、公式ドキュメントが総じて分かりやすくFlex Messageの作成もGUI上で行うことができたので触りとしてはちょうどよかった。
定期実行のエンドポイントにはアクセスを極力させたくないためBearer Auth
で認証を設けています。
import { Context, Hono } from 'hono';
import { router } from './api';
import { prettyJSON } from 'hono/pretty-json';
import { Bindings } from './types';
import { bearerAuth } from 'hono/bearer-auth';
import { doSomeTaskOnASchedule } from './model';
const app = new Hono();
app.use('/', prettyJSON());
app.use(
'/scheduled',
async (
c: Context<{
Bindings: Bindings;
}>,
next,
) => {
const token = c.env.BEARER_TOKEN;
const auth = bearerAuth({ token });
return auth(c, next);
},
);
app.route('/', router);
const scheduled: ExportedHandlerScheduledHandler<Bindings> = async (event, env, ctx) => {
ctx.waitUntil(doSomeTaskOnASchedule(env.API_URL, env.BEARER_TOKEN));
};
export default {
fetch: app.fetch,
scheduled,
};
/**
*
* @description スケジューリング関数。定時になったら指定されたAPIを叩く
* @param {string} apiUrl
*/
export const doSomeTaskOnASchedule = async (apiUrl: string, token: string) => {
await fetch(`${apiUrl}/scheduled`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
}).catch((err) => {
console.log(`LINE API error: ${err}`);
return null;
});
};
おわりに
CMJを越えるアイスクリームを見つけられますように🍨
参考にした記事など
補足(2024年4月20日更新)
以下の内容を追加
- cron triggersを使ったデータの自動更新
- リッチメニューの追加
- 「今日のアイスクリーム」以外の内容を受け取ったときに返答する自動メッセージを追加
- 認証周りの内容
- 参考記事を追加
今日のアイスクリーム以外の応答メッセージ
リッチメニュー
Discussion