最近のこと

漫画サイトの作成

自分の漫画作品を掲載するシンプルな漫画サイトを作った。
https://manga.ricemountainer.net/

スタックはAstro on Cloudflare Workers。+R2 (Public Bucket)。AstroはClaude Codeがそれにしようぜ!って言ってきたのでそれに従った形である。もうこの部分に口だせるほど知見はないし任せる。R2はバインディング使って参照。この部分はAPIキーいらずなので楽。デザインが素っ気ない気もするが、かといって「じゃあこんな感じで!」って指示出せるほどそっちに知見があるわけではないのでまあこんなもんだろう。つくづくAIってのは使う人次第だなと感じる。

このサイトそのものの開発の途中で、いくつかのスクリプトも作ってもらった。例えば漫画作品ページに表示する画像の画像ファイル名はアプリのコードで書いているが(といっても設定ファイルみたいな扱いだが)、そのファイルがR2側にいるかどうかをチェックするためのものだったりとか。他にもキャラが喋る台詞を暗号に変換したりするのとか。いわゆるN2H的な補助ツール群である。あるほうが色々「豊か」になるのだが作るの面倒くさいし後回しでいいか…と自分ひとりならそう思っていただろうが、Claude相手ならこういうのも「作って」って言えばすぐ作ってくれるので便利。

漫画のページ画像は、今の所は基本的に手動でR2にアップロードすることを考えている。ツールにしてもいいのだが、漫画を「描く」=つまりページ画像を「作る」こと自体はこのサイトのプロジェクトとは別のフォルダ上でやってるので、アップロードツール作るならそっちにツールを配置するか、あるいは漫画作業フォルダからページ画像をツール側に持ってくるかしないとならず、二重管理か、そうでないにしても管理上の手間が生じる。つまりツールにしたところで必ず「手動」の運用が生じるので、であればもはや自分で直接R2に入れちゃうのも同じだなと思っており、今の所はあまりツールにするメリットを感じられていない。といいつつもその辺の面倒くささ自体も全部Claudeと共有して最適な「運用」を踏まえたツールに仕立て上げてもらうというのもありかもしれないので、そのうち何かやるかもしれない。

そんな感じでサイトはすんなり完成してしまったので基本的には早くも運営フェーズに入っている。サイト自身というより漫画作品そのものを早く・多く描き上げることが重要なので、そっちを進めていく。

漢字勉強用のフラッシュカードサイト

子供の期末テスト向けに急遽1時間くらいで作った。といってもうち30分くらいは対象漢字をテキストエディタにぱちぱち打ってた自分の作業時間であり、Claude Codeの開発時間は実質30分程度である。指示待ちとかビルド待ちとか考えるともっと短いかもしれない。

期末テストまで1週間を切ってる中で急遽開発に着手したので、つくりとしてはかなりシンプル。Vite+Reactで構成されており、動作のほとんどがフロント側だけで完結する。学習状況はlocalstorageに保存し、DBすら使わない。ただ最終的に「ちゃんと勉強してるのか」を知りたくなり、裏でR2に結果を吐き出してSlackに通知させるなどの小細工処理は実装させてもらった。(そんなことやるくらいなら最初からDB入れておいても良かったな、、、と後からちょっと後悔)

worker.devのデフォルトドメインは封印、カスタムドメイン経由でのアクセスしか許容しないようwrangler.tomlを設定。カスタムドメインはCloudflareプロキシでWAFを経由し、WAFは我が家のグローバルIPアドレス以外ブロックするので、我が家専用。このため認証機構も付けなかった。どうせ使うのうちの家族だけなんだし別にPublicアクセス可能にしても弊害はないとは思ったけど…まあ使いたくなったら言ってくれたらそのときだけWAFのセキュリティルール解除してもいいし。

こういう、「即席で必要になる完全個人用途のアプリ」の開発にはClaude Codeは本当に便利だ。1時間もあればそれなりのものが出来てしまう。自分でコード書いてた頃からは絶対に考えられない生産性である。文字通り「桁違い」。なにかを「作る」という目的においてClaude(というよりAI)はもう必要不可欠だと改めて感じた。(※ここでいう「作る」はWebアプリやシステムなどを指しており、イラストや漫画は含んでいません、と一応注釈)

ルーチンワーク記録システムの刷新

筋トレとか、英単語フラッシュカードとか、着たTシャツとかの記録用に使ってる、完全自分用のシステム、ここで「自分専用の「Tシャツ・筋トレ・英単語勉強Webアプリ」」と言ってるやつで、内部的なプロジェクトコードは「ルーチンワーク記録システム」なので以後そのように呼称するが、これの刷新を進めている。Workers(+Supabase)で動いてるやつなので「PagesからWorkersに移行しなきゃ」ってことにはなってないのだが、いかんせん完全自作(自分でコード書いたという意味)なのでClaudeで書き換えたいなーとは前々から思っていて、最近ちょっとずつ着手し始めてる。

フレームワークに拘りは特になかったのだが、元がRemixだったことからClaudeがそれを読み取ってReact Router v7を採用した。もはや自分でコード見ることはないだろうから別に文句はない。Claudeも最初はReact Router v8で開始したのだが、これとCloudflareのviteプラグインの相性があまりよくないみたいで、初期セットの時に色々苦労してた(その結果v7系を選んだ、ということらしい)。これに関してわざわざREADME.mdに以下のような恨み言に近いことを書いている(Claude的には「備忘録」の位置づけのようだが)

現時点では v7 系を採用している。v8 が最新だが、@cloudflare/vite-plugin と 組み合わせると SSR 環境で React が二重ロードされ実行時エラーになるため (workers-sdk#11825)、 動作を確認できた v7 系で構築している。

そうなんだ… 大変だね (他人事)

それと、これを機にDBをSupabaseからD1に移管する。Supabaseが嫌になったわけではないが、全部Cloudflareに寄ってる方が管理コストが低くて楽であるためだ。ただCloudflareに一点集中するのも個人的に良いこととは思ってないし、D1にはそれはそれで不満もある(ここでいくつか書いてる)。D1の不満のうち、アプリケーション的な関わりの部分は基本的にClaudeが全部吸収してくれるはずなのであまり気にしてなくて、どちらかというとD1変更に伴う運用の変化のほうが気になっている(例えばSQL打たざるを得なくなったときどこでどうするかとか-CloudflareにサインインしてD1コンソール行けば出来るのは知ってるがこの運用スタイルはSupabaseにはないのでその差をどう見るか、という点など)。まあ運用開始しちゃえばなんとかなるんだろうけど、細かい部分までは整理できていないので長い目で見て評価していかなければならない。

現行ルーチンワーク記録システムには冒頭挙げた「筋トレ」「英単語フラッシュカード」「Tシャツ」以外にも複数機能があるため、機能ごとに徐々に移管する方式で作業を始めている。既に一部機能は移管済で、新システム側で運用を開始している。移管に際して、現行機能のいけてない部分はこれを機に改善したいと思っていて、テーブル設計からやり直してたりするので、全面的に移管するのにはまだ少し時間がかかる。別に急ぐことでもないので、順次進めていく。

最後に投稿したXのURLを記録しておく機能

ここ半年くらい?で、Xの表示ルールが変わってるようで、自分で自分に返信したタイプのポスト(いわゆるツリー)の表示が、「直近で最新の返信」ではなく「10~20回前の返信」に変わってしまった。しかもXのプロフィールの初期表示で「All」を選択するか「Replies」タブを選択しないとそもそも出てこなくなった。平たく言えばすごく「辿りづらくなった」。まあXの最近のルール的に「自分で自分に返信」を忌避する方向性になってるみたいなので仕方ないかと思う一方で、筋トレやTシャツなどひたすらツリー繋げるようにしてるタイプのアクティビティはこのままだと使いづらく、なんとかもう少しやりやすくならないかと思って、機能開発することにした。アイディアはいたってシンプルであり、

  1. 対象のツリー(リプする際のポスト)に特定のハッシュタグをつける
  2. 自分のアカウントの最新投稿をIFTTTでポーリングしてWebhookする
  3. Webhook側でハッシュタグを見て、そのポストのURLをDBに記録する

という感じだ。これのWebhookは実は↑の「ルーチンワーク記録システム」に導入した機能である(これが運用的に一番使いやすかったので)。といいつつも「ルーチンワーク記録システム」はCloudflare WAFでウチのグローバルIP以外ブロックしておりIFTTTからはリクエストできない。ので、このカスタムドメイン+WebhookのエンドポイントのときだけはWAFをSKIPして先に通し、さらにリクエストヘッダを追加するようにCloudflareに設定し、Webhook(Workers)側でそのリクエストヘッダを見て、通す・通さないを制御するよう実装した。といっても実装はClaudeで、俺がやったのはCloudflareの設定とIFTTTのアプレット構築だけだが…まあ何はともあれこれにより「最新のツリー返信はどこか」を簡単にたどれるようになったというわけである。

ただ、ここまでやっておいてなんだが、そもそもXが「自分から自分への返信」を勧めていないのだから本来それに逆らった動きをするべきではないというのは薄っすら心の中ではわかっていて、このムーブメント(自分に自分で返信しまくるアクティビティ)自体潮時の気もしており、せっかく始めちゃったから、というので若干意地になって続けてる部分があるような自覚もある。このためにCloudflareのWAFに穴を開けているというのには「そこまでしてやることか?」って気がするし、IFTTTの無料プランの貴重なアプレット2つのうち1つをこれに使ってしまっているのも勿体ない気もする。「こうすればできそうだな」というのを試してみたくなった、というやつなので、ただの技術者好奇心によるものなのである(自分用の開発物はほぼそんな感じだが)。なのでそのうち辞めるかもしれない。

子供の昔の動画の最終更新日時を上書き更新するツール

子供の昔の動画や写真をGoogle Photoに入れたいと前から思ってたんだけど(ハンディカメラで撮ったやつで、Google DriveにはあるがPhotoには入ってないのがある)、以前のPC買い替えの時にDISK間のコピーをしたことが原因だと思われるが、「最終アクセス日時」がその時点の日時で上書きされてしまい、撮影日時とメタデータが不一致になっていた。そのままGoogle Photoにアップロードすると、例えば2011年に撮影したものなのに2016年の日時の動画とみなされてPhoho上そこに混入してしまい変なことになる。いつか修正したいなーと思ってたのだが面倒くさくてやっておらず、ダラダラと10年以上そんな状況が続いていたんだが、この前ふと思いついてClaudeにそれを修正するツールを作ってもらった。ハンディカメラで取り込んだ動画(写真含む)はファイル名がYYYY-MM-DD_HH24MISS.m2tsあるいはYYYYMMDDHH24MISS.m2tsなので(前者はパナソニックだったかなんだか、後者はたしかソニー、のメーカー仕様)、そのファイル名を読み込んで「最終アクセス日時」を強制上書きすればいいだけなのだ。これくらいは自分で作れそうだがClaudeのほうが速いし確実なのでお願いした。Pythonで実装したらしい。そこはもはやなんでもいいが。Publicにしてあるのでよかったらどうぞ:
https://github.com/ricemountainer/tool-overwrite-atime

メタデータの更新は実のところそんな問題ではなくて、本当の問題はこれで過去10年以上分の動画や写真を再度Google Photoにアップロードするところなのだ。これが滅茶苦茶時間かかるし実際まだ長男誕生の初年度(15年前に撮影したやつ)に着手し始めたばかりだ。ただの機械的な作業なので「1日○○件」とか決めてあとはそれに従ってやってくだけでそのうち終わるのは目に見えてるが、途方もない作業に思える。といいつつもその辺もスケジュールにしてってClaudeにお願いしたらいい感じのスケジュールひいてくれそうだし、管理用のツールや手段なんかも作ってくれそうだけど…(SlackのList使うとか)まあいいや…その辺は追々考えていくことにする。とりあえずこういう枝葉のところにもClaude(というかAI)は多いに活用できるという話なのである。

余談なのだが同様の点を課題に感じている人は他にもいるようで、ちょっとググったら以下のようなブログを見つけた。
https://pmp-style.hatenablog.com/entry/CorrectTime
発想は同じで、ファイル名ベースで「最終アクセス日時」を書き換えるツールとのことだ。ただこれはソニーのハンディカメラで取り込んだ動画にしか使えない(対応しているファイル名の正規表現がYYYYMMDDHH24MISS.m2ts固定になっている)ので、これ以外のファイル名に関しては事前にファイル名を修正しておくなど必要になる。ただまあ要するにLinuxでいうとこのatimeを書き換えればいいだけの話なので、それくらいはClaudeですぐ作れそうだな、と思って↑の話に至る。とはいえ、「そもそもWindowsがちょっとしたことで「最終アクセス日時」を上書きしたりしなければこんなこと考慮しなくて済んだのに余計な事しやがって」という気持ちは正直ある。この点は上のブログの人がfsutil behavior set disablelastaccess 1で最終アクセス日時の上書き更新を無効化する手順を紹介しているところに根本的に同じ気持ちがありそうだなとシンパシーを感じていたりする。