くりにっき

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

Google Slidesで作ったスライドをSpeakerDeckに公開するためのTips

前置き

普段発表用の資料は Google Slides で作成し、Google SlidesでPDFにエクスポートした後に https://speakerdeck.com/ にアップロードしています。

長年の暗黙知が積み重なってきたのでメモ。

フォントをM PLUS 1pにする

Google SlidesだとデフォルトのフォントがArialとかになっていますが、このフォントだとPDF化した時にレイアウトが崩れたり文字が掠れるため、PDF化する前提なら初手でM PLUS 1pにするのが無難です。

参考URL

speakerdeck.com

note.com

絵文字は使わない

絵文字が含まれるPDFをSpeakerDeckにアップロードしたらいつまで経ってもスライドが作成されなかったり、作成されたスライドもダウンロードできない(ダウンロードできてもPDFがぶっ壊れていて表示できない)という事象がありました。(今はなおってるかも?)

そのため、自分のスライドではなるべく絵文字は使わないようにしています。(どうしても使いたい場合には絵文字を画像としてスライド貼り付ける)

ソースコードを貼り付ける時にいい感じにシンタックスハイライトを効かせるやつ

いくつかあるので紹介

SlidesCodeHighlighter

一番よく使うやつ。ソースコードをテキストとして貼り付けたい場合にはこれが一番使いやすかった。

romannurik.github.io

日本語の説明

monomonotech.jp

Carbon

carbon.now.sh

オフラインでも動くのと、コードを画像として貼り付けるので見る環境によって変わらないのがポイント。

VSCodeなど

VSCodeなどで書いてリッチテキストでコピペ

個人メモ用のNotionを作った

sue445.notion.site

モチベーション

業務で Notion を使ってるんだけど、データベース機能が圧倒的に強いので個人メモ用にもNotionを使ってみることにした。

maekdownで書ける方が嬉しいので esa は引き続き使い続けるつもりです。

コンテンツ

現時点で作ってるものは下記

アイドルランドプリパラ 楽曲リスト

sue445.notion.site

アプリのリリース以降各種攻略サイトが全然更新されなくて困ったので自分用に作った。(特に楽曲ごとのノーツ数がまとまってるサイトがなくて困った)

プリティーシリーズイベントDB

sue445.notion.site

各演者がどのライブに出演していたかをたまに調べたくなるのと、そのライブのBlu-rayとかが発売されているかどうかがまとまってるページがなかったので作った。

プリティーシリーズのライブ、2021年まではそれなりに円盤が発売されていたんだけど最近は発売されないことが多くてつらい...

Twitter botをAPI v2移行する(Ruby編)

https://github.com/sue445/pribirthdaybotTwitter API v2移行した時のメモ

tl;dr;

移行先のgem

自分が当時調べた範囲だとAPI v2対応しているAPIクライアントのgemは下記の2つしかなかったです。

github.com

github.com

ちなみに前者のsimple_twitterは最近メンテナになりました。

sue445.hatenablog.com

その他に必要になりそうなgem

TwitterのOAuth2のリフレッシュトークンからアクセストークンを取得するのに必要だったgem

github.com

ウェブアプリでTwitterのOAuth2認証したい場合に必要なgem

github.com

botAPI v2対応する時の注意点

アクセストークン(厳密にはベアラートークン)には有効期限がある(約2時間)ので、botで利用する時には実行時にリフレッシュトークンからトークンリフレッシュを行い毎回新しいアクセストークンを生成する必要があるのですが、一度利用したリフレッシュトークンは使えなくなるのでトークンリフレッシュの時に返ってくるリフレッシュトークンをDBとかに保存しておく必要があります。(このリフレッシュトークンを次回実行時に使う)

追記: 2023/07/21 10:40

これ書いた後に知ったんだけどAPI v2を使う上でOAuth2は必須ではない(一部のエンドポイントはOAuth 1.0aのアクセストークンとかでTwitter API v2が使えるらしい *1 )ので、OAuth1.0aであればリフレッシュトークンは考えなくてよさそう

手順

リフレッシュトークンを作成する

一番最初にリフレッシュトークンを取得するところだけは手で作る必要があります。

自分用にはこういうアプリをローカルで動かして認証するようにしていました。

github.com

Twitter Appについているデフォルトのトークンでもいいんですが、Appを管理しているTwitterアカウントとbotTwitterアカウントが異なる場合にはこっちの方がいいと思います。( https://developer.twitter.com/ への登録は電話番号認証が必要なのでなるべく1つのTwitterアカウントに集約させたい)

権限はTwitter botの要件であればこれで十分。 *2

# https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code
TWITTER_V2_SCOPE = "tweet.read tweet.write users.read offline.access"

リフレッシュトークンを使う場合には最低限 offline.access は必須。他は用途に応じて調整してください。

このアプリをローカルで動かせば下記のようなjsonが返ってくるので、ここにあるtokenとrefresh_tokenを使います。

{
    "provider": "twitter2",
    "uid": "14540215",
    "info": {
        "name": "sue445",
        "email": null,
        "nickname": "sue445",
        "description": "フルスタックキュアエンジニア。 プリキュア/サザエさん/プリパラ/プリ☆チャン/プリマジ/オヤジギャグコミッタ 【警告】上級者向けアカウント。フォローすると実況ですごい勢いでTLが埋まるよ!【危険】",
        "image": "https://pbs.twimg.com/profile_images/378800000394619528/fb8a746f6955be08134050ba2adaa1f2_normal.png",
        "urls": {
            "Website": "https://t.co/fZVVmoHpLx",
            "Twitter": "https://twitter.com/sue445"
        }
    },
    "credentials": {
        "token": "XXXXXXXXXXXXXXXXXXXXXX",
        "refresh_token": "XXXXXXXXXXXXXXXXXXXXXX",
        "expires_at": 1689828440,
        "expires": true
    },
    "extra": {
        "raw_info": {
            "data": {
                "verified": true,
                "protected": false,
                "description": "フルスタックキュアエンジニア。 プリキュア/サザエさん/プリパラ/プリ☆チャン/プリマジ/オヤジギャグコミッタ 【警告】上級者向けアカウント。フォローすると実況ですごい勢いでTLが埋まるよ!【危険】",
                "entities": {
                    "url": {
                        "urls": [
                            {
                                "start": 0,
                                "end": 23,
                                "url": "https://t.co/fZVVmoHpLx",
                                "expanded_url": "http://sue445.hatenablog.com/",
                                "display_url": "sue445.hatenablog.com"
                            }
                        ]
                    }
                },
                "name": "sue445",
                "location": "Tokyo / Japan",
                "id": "14540215",
                "profile_image_url": "https://pbs.twimg.com/profile_images/378800000394619528/fb8a746f6955be08134050ba2adaa1f2_normal.png",
                "url": "https://t.co/fZVVmoHpLx",
                "created_at": "2008-04-26T05:33:43.000Z",
                "public_metrics": {
                    "followers_count": 2859,
                    "following_count": 1678,
                    "tweet_count": 456027,
                    "listed_count": 182
                },
                "username": "sue445"
            }
        }
    }
}

botでのAPI v2移行

自分がsimple_twitterのメンテナをしてるってことでsimple_twitterを例に出しますが全体的な流れはtweetkitでもそんなに変わらないと思います。

Gemfile に下記を追加。

gem "simple_twitter"
gem "twitter_oauth2"

こういうclassを追加 *3

# frozen_string_literal: true

class TwitterClient
  API_ENDPOINT = "https://api.twitter.com/2"

  class Error < StandardError
  end

  # @return [Hash]
  def get_me
    simple_twitter_client.get("#{API_ENDPOINT}/users/me")
  end

  # @param [String] text
  def post_tweet(text)
    simple_twitter_client.post("#{API_ENDPOINT}/tweets", json: { text: text })
  end

  private

  # @return [String]
  def access_token
    return @access_token if @access_token

    oauth2 = TwitterOAuth2::Client.new(
      identifier: ENV["TWITTER_V2_CLIENT_ID"],
      secret:     ENV["TWITTER_V2_CLIENT_SECRET"],
    )

    # FIXME: refresh_tokenをDBなどから取得する
    oauth2.refresh_token = load_refresh_token

    ret = oauth2.access_token!

    # FIXME: refresh_tokenをDBなどに保存する
    save_refresh_token(ret.refresh_token)

    @access_token = ret.access_token
  end

  # @return [SimpleTwitter::Client]
  def simple_twitter_client
    @simple_twitter_client ||= SimpleTwitter::Client.new(bearer_token: access_token)
  end
end

ここでポイントになるのがrefresh_tokenの読み込みと保存です。

priborthdaybotだと実行環境にCloud Functionsを使っているのでFirestoreに保存するようにしていますが、ここはアプリの要件によって変わると思います。(だいたいはRDBやKVSになるはず)

具体的な作業内容は https://github.com/sue445/pribirthdaybot/pull/61/files を見てください。

simple_twitterのメンテナになった&v2.0.0をリリースした

tl;dr;

id:yhara さんのsimple_twitterのメンテナになりました。

github.com

simple_twitter については下記エントリを参照

zenn.dev

メンテナになった経緯

https://github.com/sue445/pribirthdaybotTwitter API v2対応をしていて既存のtwitter gemから移行先*1としてsimple_twitterを選んだところ、いくつか動かない箇所があったり挙動に不満があったのでいくつかパッチを投げました。

そうしたらメンテナになってました。

ちなみにこの手のパッチ投げたらメンテナになってたエピソードは1年ぶりn度目くらいです。(たくさんありすぎて数えるのを諦めた)

v2.0.0について

大きいところだと

  • positional argumentからkeywords argumentへの変更 *2
  • Twitter APIからエラーが返されたらRubyでもエラーにする *3

です。

特に前者は悩みがあったんですが、yharaさんから合意が得られたのでメジャーバージョンアップしました。

あとは

  • テストやCIを整備
  • yardで作ったgemのリファレンスをGitHub Pagesで公開
    • 頑張りポイント: getpost など、メタプロで動的生成してるメソッドに対してもリファレンスを書いた *4
  • gemspecの rubygems_mfa_required *5 を有効化

など、自分がいつもやってるやつを追加しました。

詳しくは https://github.com/yhara/simple_twitter/blob/main/CHANGELOG.md#v200-2023-07-17 に全部書いています。

今後の展望

simple_twitterという名の通りシンプルなgemなので、PRが来ない限りは機能追加をする余地はあまりなさそうです。

メソッドをメタプロで動的生成してる関係でIDEでの補完が効きづらいのが不便なのでrbsを追加したいとは思ってます。

(一応途中まではできてるんだけど *6 、利用してる他のgemのrbsがなくてsteepでエラーになるので https://github.com/ruby/gem_rbs_collection にPRを送る必要がありそう)

mainブランチよりも古いブランチでCIを実行できなくしたい

たまにやりたくなるやつメモ

モチベーション

CIでTerraformを動かしていると

  • mainブランチを自動apply
  • PRとかでマージ前に挙動を確認したい場合はPRに出ているブランチを手動apply

というワークフローを作ることがよくあるんですが、最新のmainブランチよりも古いブランチをapplyした時にapply済の構成が巻き戻って事故になるため、そうならないように防止したかったというやつです。

実際のスクリプト

どのCIで動かすかによって多少は変わってくると思うけどだいたいこれで動くと思います。

default_branch=`git remote show origin | grep 'HEAD branch:' | cut -d : -f 2 | tr -d '[[:space:]]'`
commit_sha=`git show --format='%H' --no-patch`

echo '[ERROR] 下記でエラーになった場合、実行中のブランチが古いのが原因なので git rebase を行ってください'
git merge-base --is-ancestor origin/${default_branch} ${commit_sha}

解説

  • デフォルトブランチがmainとかmasterとかdevelopとか気にしたくないので自動で判断するようにしています
  • commit_sha (現在のコミットハッシュ)に関しては、各CIで環境変数があるのでそれを利用するといいでしょう
    • GitHub Actionsだと GITHUB_SHA *1
    • GitLab CIだと CI_COMMIT_SHA *2
  • もし現在のコミットハッシュが最新のmainブランチよりも古かった場合には git merge-base --is-ancestor *3 でエラーになります
  • CIがエラーになったらだいたいみんなログを見に行くと思うので、チェックコマンドの直前に雑にechoでエラーログを出しています

おまけ:実際にGitLab CIで動かしてるやつ

GitLab CIの場合、ジョブ実行時点でremoteのブランチの内容が取ってこれていないため明示的に git fetch しています。

before_script:
  - "default_branch=`git remote show origin | grep 'HEAD branch:' | cut -d : -f 2 | tr -d '[[:space:]]'`"
  - git fetch
  - echo '[ERROR] 下記でエラーになった場合、実行中のブランチが古いのが原因なので git rebase を行ってください'
  - git merge-base --is-ancestor origin/${default_branch} ${CI_COMMIT_SHA}

研鑽Rubyプログラミングを読んだ #研鑽Ruby

最近家で読書する習慣が薄れてたのですが *1 頑張って研鑽Rubyプログラミング(以下:研鑽Ruby)を読みました。

研鑽Rubyプログラミング ― 実践的なコードのための原則とトレードオフwww.lambdanote.com

雑感

リファクタリング:RubyエディションメタプログラミングRuby 第2版 をgemの開発やメンテナンス方面に特化させたような感じ。(2冊とも読んだのが結構前になるので厳密には違うかもしれない)

バージョンアップ時になるべく非互換を防ぐような機能開発を行ったりdeprecation warningを仕込んだりするのはgemに関係なく他の言語のライブラリ開発でも普通に参考になります。

僕は10年以上gemを開発してますが終始「わっかる~」って頷きながら読んでました。

そんな自分でもちょいちょい知らないこともあって紛れもなく中級者~上級者向けの1冊

読書中のメモ

  • ISUCONで使えそうなパフォーマンス改善ネタがちょいちょいあった
    • バックトレース無し例外とか
  • Array#firstArray#lastArray#[] に置き換えてパフォーマンス改善するやつはrubocop-performanceへのPRネタとしてもよさそう
  • sequelやsinatraなどの有名所のgemのDSLの実装方法が解説されていてむっちゃ参考になった
  • 今まで自分が何気なくやってきた作業の流れ(機能追加のPRを入れる前に、機能追加しやすくするためのリファクタリング系のPRを先にmainにマージする)に関して「系統的アプローチ」って名前がついてるの助かる
    • 名前がつくことで他の人に伝えやすくなる
  • パフォーマンス改善に関してトレードオフも提示してるのが好印象
  • Rubyでこんな書き方はしないだろーw」と思いつつも年1~2回くらい必要に迫られそうなので、また必要に応じて読み返しそう

宣伝

2023/5/11(木)~13(土)に開催予定の RubyKaigi 2023 に「Fix SQL N+1 queries with RuboCop」というタイトルで発表予定です。

rubykaigi.org

研鑽Rubyを読みながらちょいちょい「これ、RubyKaigiの発表資料に書いたやつだ~」「この発表資料をそのまま出すとPolished Rubyist *2からツッコミが入るから補足入れないと」ってのがあって焦りました。

実は今回の発表資料は2年前の社内勉強会の発表資料がベースになってるので研鑽Rubyの影響は受けてないです。 先に研鑽Rubyを読んでたらもっと違う形になってそう。

5/12(金)に僕のトークを聞きに来てくれる人は予習として研鑽Rubyを読んでるとそういった一致点を見つけられて面白いかもしれないです。

*1:主にマスターデュエルのせい

*2:研鑽Ruby読了済みのRubyist

GitHub Actionsで特定の条件の時だけenvironmentを設定したい

tl;dr;

environment三項演算子でいい感じにする

モチベーション

TerraformのワークフローをGitHub Actionsで動かしてるんだけど、terraform apply した時(具体的にはmainブランチか手動でのbuild実行時 *1 )のみ environment をセットしてDeployments *2に通知したかった。( terraform plan (Terraformのdry run)ではDeploymentsには通知したくない)

実装例

こんな感じ。

environment: ${{ ((github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch') && 'terraform-apply') || '' }}

github.com

分かりづらいんですが terraform apply 時のみDeploymentsに通知されています。

解説

environment に空文字を渡してもエラーにならず、非空文字が渡された時のみそれが environment としてDeploymentsに通知される」という仕様を利用しています。*3

実はGitHub Actionsに三項演算子は無いのですがそれっぽいものは実装することができます。

ググれば色々ありますが自分は下記を参考にしました。

qiita.com

*1:後者はトピックブランチでの手動applyを想定

*2:https://docs.github.com/ja/rest/deployments/deployments?apiVersion=2022-11-28

*3:将来的に挙動が変わる可能性はあります