bhr を作った

bhr という CLI を作った。
Chrome の履歴を読んで、その日に読んだページを日ごとの一覧にしてくれるやつ。名前は browser history reading の頭文字。

ブラウザの履歴をObsidianに記録しているんだけどそこに活用したり、単純に「前にあれみたよなぁー・・・なんだっけ?」ってときに地味に便利。

Rustで実装していてるのでインストールも以下の通り簡単にできる。

$ cargo install bhr

読んだページだけにする

Chrome の履歴をそのまま出すと、ほとんどが読み物ではない。GitHub、Slack、Google の検索結果、X のタイムライン、localhost。なので除外は設定ファイル(~/.config/bhr/config.toml)に書くようにした。

allow = []           # 空なら deny 以外はすべて拾う。書けば、載っているホストだけ拾う
deny = ["github.com", "x.com", "google.com"]   # サブドメインごと落とす
tracking_params = ["utm_*", "fbclid", "ref"]   # URL から落とすパラメータ

localhost、IP アドレス、auth. や login. の認証画面、トップページ(/)、iframe の中で開かれたページは、設定に関係なく常に落とす。サイトごとの事情は設定、どこでも要らないものはコード、で分けている。

同じ日に同じページを何度開いても 1 行にまとめる。まとめる鍵は URL ではなくホストと題にした。検索欄に 1 文字打つたびに別の URL で履歴が残るサイトがあるので、URL で見ると同じページが何十行も並ぶ。残すのはいちばん短い URL。

それでも仕事のドメインは入ってくるので、読み物だけにしたいなら allow に読むサイトを並べるほうが早い。TUI でページを選んで x を押すと、そのホストを deny に足して設定ファイルに書き込む(toml_edit で書くのでコメントは消えない)。

History は写してから開く

Chrome の履歴は ~/.config/google-chrome/Default/History にある SQLite。Chrome が動いている間はロックされているので、一時フォルダにコピーしてから開いている。

visits.visit_time は 1601-01-01 からのマイクロ秒。日付への変換は SQL でやってしまう。

date(v.visit_time / 1000000 - 11644473600, 'unixepoch', 'localtime')

transition の下位 8bit が 3 か 4 のものは iframe で開かれたページなので、ここで落とす。

TUI と、意味で探す

引数なしで起動すると TUI になる。左に月と日、右にその月・日に開いたページ。最初は日付を全部並べていたけど、長すぎるので月を選んでから日を開く形にした。

/ は文字の絞り込み。? は jev に「検索語 で探している人が求めているページか」をはい/いいえで聞いて、その確率の順に並べる。1 ページ 1 リクエストで、min_score(既定 0.7)未満は出さない。CLI でも bhr list --ask "..." で同じことができる。

bhr list | jev のようにパイプで渡す案もあったけど、jev は標準入力を丸ごと 1 つの状態として受け取って答えを 1 つ返すので、ページごとの点数にはならない。なので bhr の中から 1 ページずつ投げている。

何を検索したかも残っていた

作っている途中で、Chrome が SQLite に何を持っているのか気になって、プロファイルの中を全部見てみた。

ファイル 中身
History 履歴、ダウンロード、検索した語、訪問の間のつながり
Shortcuts アドレスバーに打った文字と、開いた URL
Favicons ページのアイコン
Cookies cookie(値は暗号化)
DIPS bounce tracking の記録

History の中の keyword_search_terms に、検索窓に打った語がそのまま残っている。これを日ごとに出す bhr searches を足した(v0.3.0)。

さらに visits.from_visit に「どの訪問から来たか」が入っているので、検索結果の画面から開いたページもたどれる。

# 2026-09-29 に検索した語

- 東名 混雑
  - [交通情報 | ドライバーズサイト | 高速道路・高速情報はNEXCO 中日本](https://www.c-nexco.co.jp/jam/)
  - [東名高速道路の渋滞情報 - NAVITIME](https://www.navitime.co.jp/highwaycongestion/prediction/result?id=3938)
- 新東名 静岡
- 新東名 ガソリン スタンド 上り
  - [新東名・新名神(上り)東京方面|高速GS](https://highwaygs.jp/route/shintomeishin-1.html)

検索語そのものは除外しない。検索エンジンは deny に入っているので、ページと同じ除外をかけると全部消える。開いたページのほうには同じ除外をかけていて、そのおかげで検索結果の 2 ページ目(google.com)は勝手に落ちる。

その代わり、deny に入れている Amazon へのクリックや、トップページ(omarchy plugin で検索して開いた plugins.omarchy.org/)も一緒に落ちる。ここは割り切った。

from_visit は 1 段しかたどっていないので、リダイレクトを挟んだページは取りこぼす。困ったら再帰でたどる。

TUI では、選んでいる月・日の検索語をページ一覧の下の枠に出している。

crates.io に置いた

v* のタグを push すると、GitHub Actions から crates.io に publish されるようにした。

cargo install bhr

Chrome はだいたい 90 日より古い履歴を消すので、残しておきたいなら定期的に bhr list --json で書き出しておくといい。

Firefox(places.sqlite)と Safari(History.db)はまだ読めない。

カテゴリ