くりにっき

フルスタックキュアエンジニアです

feed_squeezerを作った

これなに?

RSSフィードを任意のキーワードで絞り込んだ結果をさらに別のフィードとして返すためのproxy的なウェブアプリです。

github.com

モチベーション

僕はSlackで色々なRSSフィードを購読しています。

YouTubeのチャンネルにもRSSフィードが存在しているのでSlackで購読しています。

例えば 東映アニメーション公式YouTubeチャンネル - YouTube にはプリキュアをはじめとして様々な動画が公開されています。その中でプリキュアに関係する動画だけをSlackで購読したいってことがよくあるため作りました。

2〜3年前くらいに作って個人Slack内でほそぼそと運用していたのですが、OSS化する機運が高まったのでOSS化しました。

使った技術

  • Go
  • Bootstrap
  • Vue.js

工夫ポイント

配布形式をどうするか

Webサービスとして公開するのも考えたのですが、色んなところで使われだすとGoogle Cloudの個人アカウントのCloud RunやCloud Functionsの無料枠が一瞬で吹き飛びそうな気がしたのでDockerイメージやバイナリでの配布にしています。

ちなみにこの画面はローカルで動かしているのを撮影したのでlocalhostになってますが、実際はSlackbotからアクセスできる場所に置く必要があるので自分の好きな場所にDockerイメージやバイナリを動かして利用してください。

僕のおすすめはCloud Runで、Terraformのサンプルコードも置いています。 https://github.com/sue445/feed_squeezer/tree/main/_examples/gcp_cloud_run_terraform

GoでGoogleライクな検索クエリで文字列マッチするような関数を作った

  • AAA BBB : AND検索
  • AAA | BBB : OR検索
  • (AAA BBB) | CCC, (AAA | BBB) CCC : 括弧をつけて検索条件の優先順位付け

具体的には https://github.com/alecthomas/participle というパーサーライブラリを用いて実装しました。

github.com

participleは汎用的なパーサーライブラリなので使うのが難しかったのですが、ChatGPTに助けてもらいながら実装できました。

取得したフィードを一瞬だけキャッシュする

Slackでフィードを購読する時は /feed subscribe *1 を使います。

登録されたフィードは10〜15分おきくらいにSlackbotによる購読チェックが実行されます。

この時にSlackbotは同一のURLに対してほぼ同時にHEADとGETを実行します。

YouTubeチャンネルのフィードには短時間ながらRateLimitが設定されているため、瞬間的にたくさん呼び出すと429エラー(Too Many Requests)になります。

この429エラーを回避するためにキャッシュを導入しました。

バイナリにviewを同梱する

feed_squeezerに渡すurlやqueryなどのパラメータはURLエンコーディングする必要があります。

手でURLエンコーディングするのは大変なのでいい感じに変換するためのフォームをトップページに用意しています。

Goの https://pkg.go.dev/embed を用いてHTMLファイルをバイナリに含めています。

ただしjsやcssなどもバイナリに含めるとバイナリサイズが大きくなりそうだったのでHTMLファイルで利用しているBootstrapやVue.jsなどはCDN経由で参照するようにしました。

ちなみにこれがOSS化する前のfeed_squeezerの画面です。Bootstrapを入れるだけで全然違うなw

最近のエコシステムに乗っかる

今までGo製のツールを作る時はバイナリリリースとかCIでのlint実行とかはオレオレ実装を流用してたのですが、この機会に https://github.com/goreleaser/goreleaserhttps://github.com/golangci/golangci-lint にも初挑戦しました。

マッドマックス:フュリオサを見た感想

映画『マッドマックス:フュリオサ』 を3回見に行った感想。

せっかくなので全部別々の劇場に見に行った

特に記載がない場合は字幕版です。

新宿バルト9(DolbyCinema)

tjoy.jp

上映初日に行った。 家から徒歩圏内なのでよく行ってる。

DolbyCinemaなんだけど他がすごすぎて映画体験としては物足りなく感じた。

立川シネマシティ(極上爆音上映)

cinemacity.co.jp

極上爆音上映で有名な映画館。

バルトの翌日に行った。(つまり上映開始2日目)

前日に買ったパンフで予習。

最前列中央で全身でスピーカーの音圧を浴びた。

Fury Roadやガルパンの極爆はだいたい最前列から埋まっていったのに、フュリオサは最前列自分しかいないのが意外だった。

グランドシネマサンシャイン 池袋(ULTRA 4DX吹替)

www.cinemasunshine.co.jp

事前にFury RoadのBDで復習した。

4DXむっちゃ揺れる。人間ドックでグイングイン回転するやつを思い出した。

正面だけじゃなく両サイドにもスクリーンがあるのすごい。

www.cinemasunshine.co.jp

いつもの癖で最前列に座ったけど両サイドにもスクリーンあるのを知ってたら真ん中とか後方とかに座ってたな。

吹替版は初めてだったんだけど最後の吹替版のキャストを見て「マジ!?」ってなった。フュリオサがファイさんなの全然気づかなかったよ...

wwws.warnerbros.co.jp

#RubyKaigi 2024に参加した& #kaigieffectLT 大会に登壇した

RubyKaigi 2024

rubykaigi.org

気になった発表

RubyGems on ruby.wasm

1年前にruby.wasm上で3rd party gemを動かすのにむっちゃ苦労した *1 ので、この発表が今回一番聞きたかったやつ。

セッション終わった後に登壇者を捕まえて「発表だとマイルストーンについて言及されてなかったと思うんですが(現時点でnative extensionの対応以外はmasterに入ってるので順当にいけば12月のruby 3.4には入るという認識)、いつ頃リリース予定なんすか?」って聞いたら「細かい調整にあと1ヶ月くらいかかりそう」とのことでした。(たった1ヶ月!?)

The depths of profiling RubyVernier: A next generation profiler for CRuby

どっちもプロファイラの発表。

プロファイラのことよりも、個人プロダクト(native extension)で困ってることのヒントになるかなと思って発表を聞いた。

発表後に資料で取り上げられてたgemのソースもむっちゃ読んだ。

https://github.com/osyoyu/pf2 はRustのnative extensionで面白かったが自分のお悩み解決には至らなかった。

https://github.com/jhawthorn/vernier はCのnative extensionで自分が使ってるCの関連メソッドも利用してたので自分のお悩み解決のヒントになるかなと思ったんだけど、実際にこのgemの実装を試したけどダメだった。

その他

やんちゃハウス2024

面白そうなので知り合いのyancyaさんが主催してるシェアハウスに参加。

yancya.house

参加者同士の交流はそんなになく各々が好き勝手にやってる感じでした。

やんちゃハウスに関しては同じハウスメンバーの kenchanさん のエントリが詳しいです。

diary.shu-cream.net

やんちゃハウス2024は RubyMusicMixin 2024 の会場から徒歩2分という立地。

あまりに近すぎて、RubyMusicMixin中にローカルオーガナイザーの はなちんさんスマホのバッテリが切れて困ってた時にハウスに充電しに行ったり、RubyMusicMixin終了後にかなりお疲れモードだったので朝まで寝かせていたりもしました。

twitter.com

#kaigieffect LT大会

登壇してました。

timeedev.connpass.com

発表資料

esa-pages.io

IntelliJ IDEAなどのTerminalでvimを使うと壊れる件の回避策

前置き

IntelliJ IDEAなどでgit操作をする時はIDE内のTerminalを使っているのですが、1〜2週間前からcommitメッセージの入力とかでvimの挙動が急におかしくなったので調べた。

期待した挙動(Macのitem2)

実際の挙動(IntelliJ IDEA内のTerminal)

確認した環境

OS

macOS 14.5(Sonoma)

vimのバージョン

Homebrewでインストールしたvimが使われてそう

$ vim --version
VIM - Vi IMproved 9.0 (2022 Jun 28, compiled Oct 19 2023 08:52:34)
macOS 版 - arm64
適用済パッチ: 1-2049
Compiled by Homebrew

IDE

IntelliJ IDEA、RubyMine、Golandの下記バージョンで再現

  • 2023.2.4
  • 2024.1.1

分かったこと

手元の ~/.vimrc を消したらなおったので自分の ~/.vimrc *1に原因がありそうだった。

回避策

~/.vimrc の下記を消したらなおったが *2、これがないと今度はIDEAのTerminal以外でのvimで色が出なくなるので悩ましい、、、

syntax on
colorscheme desert

地域.rbカレンダーでconnpass APIの個人利用申請をした

tl;dr;

connpass APIの仕様変更で 地域.rbカレンダー を閉じるつもりでしたが、個人利用申請を行ったので2024年5月23日(木)以降もカレンダーが更新されます

時系列

今年の2月にconnpass APIの無償提供終了が発表

3月頃にプランが発表されて、料金体系が個人向けじゃなさそうなので地域.rbカレンダーを閉じる(connpassの有料APIに対応しない)ことを決意

この時点では固定IPアドレスも費用かかりそうできっついなあということで閉じる方針は継続。

これを見てRubyKaigi後に機運が高まった。*1(実質KaigiEffect*2

申請した2日後くらいに許諾された!!!

詳しいログ

github.com

具体的にやったこと

2024年5月23日(木)以降もConnpass APIを使うには固定IPアドレスが必要なので、その対応を行いました。

地域.rbカレンダーのカレンダー生成のエンドポイントはCloud Functionsで動いているため*3Serverless VPC Access Connectorを利用してCloud Functionからインターネットに出る時にCloud NATを経由して送信元のIPアドレスを固定化しました。

変更前後の構成は下記のような感じです。

Before

After

この図だと端折っていますが、送信元のIPアドレスが本当に固定化されているかの動作確認としてVPC内にVMを立てたりもしています。(Cloud NATはVPCからインターネットに出る時の送信元を全て固定IPアドレスにするので、VPC内にVMを立てても当然同じIPアドレスになる)

Terraform

具体的な方針は下記が参考になりました。

dev.classmethod.jp

これを参考に実装したのが下記のTerraformになります。

# VPC
locals {
  subnet_cidr = "10.128.0.0/20" # 10.128.0.0 - 10.128.15.255

  # Requires /28 for Serverless VPC Access Connector
  # c.f. https://cloud.google.com/vpc/docs/serverless-vpc-access
  vpc_access_connector_cidr = "10.128.16.0/28" # 10.128.16.0 - 10.128.16.15
}

resource "google_compute_network" "main" {
  name                    = "main"
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "main" {
  name          = google_compute_network.main.name
  network       = google_compute_network.main.self_link
  ip_cidr_range = local.subnet_cidr
  region        = "asia-northeast1"
}

resource "google_compute_router" "main" {
  name    = google_compute_network.main.name
  region  = google_compute_subnetwork.main.region
  network = google_compute_network.main.self_link
}

resource "google_vpc_access_connector" "main" {
  name          = "main"
  machine_type  = "f1-micro"
  ip_cidr_range = local.vpc_access_connector_cidr
  network       = google_compute_network.main.id

  # NOTE: requires 2+ instances
  min_instances = 2
  max_instances = 3
}

# Cloud NAT
resource "google_compute_router_nat" "main" {
  name   = google_compute_network.main.name
  router = google_compute_router.main.name
  region = google_compute_router.main.region

  nat_ip_allocate_option = "MANUAL_ONLY"
  nat_ips                = [google_compute_address.nat.self_link]

  source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
}

resource "google_compute_address" "nat" {
  name        = "nat"
  region      = google_compute_subnetwork.main.region
  description = "Static Address for Cloud NAT"
}

アプリケーション側(デプロイ時にFunctionをVPC Connectorに紐づける設定)は下記

github.com

ハマったポイント:Terraformでapplyする時だけなぜかエラーになる

具体的には下記のようなエラーです

│ Error: Error waiting to create Connector: Error waiting for Creating Connector: Error code 13, message: An internal error occurred: VPC Access connector failed to get healthy. Please check GCE quotas, logs and org policies and recreate.
│ 
│   with google_vpc_access_connector.main,
│   on vpc.tf line 27, in resource "google_vpc_access_connector" "main":
│   27: resource "google_vpc_access_connector" "main" {

設定自体は問題なさそうだし、なんなら全く同じ設定をコンソールから設定できたので本当に謎でした。( terraform apply でのみエラーになる)

Terraformで利用しているサービスアカウントに対して service-PROJECT_NUMBER@gcp-sa-vpcaccess.iam.gserviceaccount.com の「サービスアカウントユーザー( roles/iam.serviceAccountUser )」*4 のIAMロールが必要なのが原因でした。 *5

最後に

作った後で気づいたけど自分の用途だとオーバーテクノロジーだったかもしれないです。

似たようなことをやりたい人は参考にして下さい。

*1:このポストはRubyKaigi直前のものなんだけど機運が高まったのはRubyKaigi後

*2:https://ybiquitous.me/blog/2021/kaigi-on-rails-2021

*3:icsを返すエンドポイントを自分のGoogle Calendarに登録してからGitHub Pagesで公開している

*4:https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountUser

*5:https://cloud.google.com/vpc/docs/configure-serverless-vpc-access?hl=ja#service_account_permissions

kagaribiを作った

kagaribiって?

Cloud Firestoreを手軽に使うためのgemです。

https://github.com/petergoldstein/dallihttps://github.com/redis-rb/redis-client のようなシンプルなインターフェースでFirestoreにアクセスしたくて作りました。

github.com

元々は個人アプリで3〜4年くらいずっと使ってたやつでそのうちgem化しようとは思ってたんですが、いい感じのgem名が思いつかずに今に至ってました。

この度いい感じのgem名を思いついたのでgem化しました。

gem名の由来は「Firestore -> Fire -> 火 -> 篝火🔥*1」です。

使い方とかはREADMEを参照

頑張りポイント

Firestoreって普通に使ってるとたまに「Could not load the default credentials.」みたいなエラーが出てリトライするとなおるんですが、今まで運用してきて遭遇したそれ系のエラーはだいたい網羅してます。

https://github.com/sue445/kagaribi/blob/v0.1.0/lib/kagaribi/collection.rb#L111-L149

Switch用ファミリーコンピュータ コントローラーを本体以外でも充電したい

前置き

www.nintendo.com

このコントローラはSwitchの両サイドに1コンと2コンを挿して充電する方式です。

参考画像: https://news.denfaminicogamer.jp/news/240509l/attachment/img-main-02

本体以外でも充電したいことがたまにあるのでジョイコン充電器を探したんですが、ファミコンのコントローラーが普通のジョイコンよりもサイズが大きいため実際に充電できるやつを見つけるのに苦労しました。

比較した写真

元々はNintendo Switch Online加入者限定商品なんですが、今度一般発売されるらしい *1のでこの手の需要がありそうな気がしてメモ。

実際に充電できたやつ

実際にファミコンコントローラーを充電できた充電器はこちらになります。

探せば他にも色々ありそうだけど、確実に充電できるものが欲しい人はこれを買うといいと思います。

実際に買って試したけどダメだったやつ

最初に買ったやつがこれ。

ケーブルタイプなのでいけるかと思ったんですが、ファミコンコントローラーの充電端子の溝にうまく入らなくて充電できなかったです。