リモートワークで退勤後にテンションを上げるための技術
この記事は、はてなエンジニアAdvent Calendar 2023 の2024年1月9日の記事です。
昨日はid:arthur-1さんの Advent Calendarを25日分続ける技術、あるいは物を発明し素早く作る技術 - Diary of a Perpetual Student でした。アウトプットが続かない私としては25日も続けるのは尊敬です。見習いたい…
弊社はフルリモートで働くことが可能*1で、通勤が苦手な自分にとっては天国です。しかし、リモートで働いていると日々にメリハリが無く、仕事を終えてもテンションが上がらないな…と感じていました。
そこで、退勤ボタンを押した時にテンションを上げるための技術を導入しました。
用意したのはこちらのアイテムです。
早速ですが、実際に退勤ボタンを押した際の様子をご覧ください。
𝑷𝒂𝒓𝒕𝒚 𝑻𝒊𝒎𝒆 ...
どうですか?テンションが上がりそうでしょう?BGM は権利の問題上フリーの音源に変えていますが、実際には Get Wild を流しています。
作り方
準備
まずはスマートプラグとミラーボールをコンセントに繋ぎ、IFTTT 経由でオンオフできるようにします。その辺の詳細は以前 在宅ワーク中に家族に会議中だとアピールするライフハック - magamingのブログで書いたので、そちらを見てもらうとよさそうです。
スマートプラグでなくても、赤外線リモコンがついているミラーボールを、Nature Remo や SwitchBot などのスマートリモコンで操作する形でもよいと思います。
スクリプトの作成
弊社では勤怠システムに Akashi を利用しているので、ブラウザ上で勤怠管理ができます。これが何を意味するか、つまり、JavaScript を埋め込み放題という事です。今回は以下のような JS を用意しました。
const iftttUrl = '<IFTTT でスマートプラグをONにするためのURL>'; // GetWild const videoId = 'NHKq8IOXPxA'; const playerElement = document.createElement('div'); playerElement.id = 'player'; document.body.appendChild(playerElement); const tag = document.createElement('script'); tag.src ='https://www.youtube.com/iframe_api'; const firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); var player; function onYouTubeIframeAPIReady() { player = new YT.Player('player', { // 音だけ再生できればよいので、サイズは 0 height: '0', width: '0', videoId: videoId, }); } const partyTime = () => { fetch(iftttUrl); // ifttt によるスイッチオンにタイムラグがあるので1秒待つ setTimeout(() => { // サビまでシーク player.seekTo(60); player.playVideo(); }, 1000); } const taikinButton = document.querySelector(<退勤ボタンの class>); taikinButton.addEventListener('click', partyTime);
要は iframe の Player を無理やり埋め込んで、退勤ボタンを押したら「IFTTT URL を叩く」「Youtube Player API で再生」をやっているだけです。
スクリプトの埋め込み
最初は Chrome 拡張作ろうかと思ってましたが、めんどくなってきたので今回は ScriptAutoRunner で雑に JS を実行する形にしました。今回のように、雑にJSで遊びたいときにはオススメです。ブラウザ上で動く勤怠システムであれば、Akashi でなくても導入可能なはずです。
いかがでしたか?皆様も退勤に彩りを与えてみてはいかがでしょうか。それではよい退勤ライフを…
GitHub actions で PR が無ければ作る、あれば更新する
こんな感じでできた。gh pr list で標準で jq 使えることが今回の発見。かっこいい。
env: TARGET_BRANCH: 'target-branch' GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: create-or-update-pr: runs-on: ubuntu-latest steps: - name: search pr number and set to env # この場合は head が 'target-branch' かつマージされてないブランチを探している run: echo "PR_NUMBER=$(gh pr list --search "head:${{ env.TARGET_BRANCH }} is:unmerged" --json number --jq ".[].number")" >> $GITHUB_ENV - name: create pull request # 空なら 0 が入る ref. https://docs.github.com/ja/actions/learn-github-actions/expressions#operators if: ${{ env.PR_NUMBER == 0 }} run: gh pr create --base main --head ${{ env.TARGET_BRANCH }} --title "タイトル" --body "本文" - name: update pull request if: ${{ env.PR_NUMBER != 0 }} run: gh pr edit ${{ env.PR_NUMBER }} --title "更新後のタイトル" --body "更新後の本文"
MySQL でテーブル定義を JSON形式で吐き出す
諸事情でテーブル名と、テーブルのカラムを以下のような JSON 形式で出す必要があった。
{ "<テーブル名1>": [ "<カラム名1>", "<カラム名2>", ... ], "<テーブル名2>": [ "<カラム名1>", "<カラム名2>", ... ] }
最初は MySQL でテーブル定義を csv に吐き出して、スプレッドシートに貼り付けて、GAS で JSON 形式に変換する… みたいな回りくどいことをやっていたけど、JSON 周りの関数を使えばどうやら MySQL 一発でいけそうだった。
SELECT JSON_OBJECTAGG( c1.table_name, ( SELECT JSON_ARRAYAGG(column_name) FROM information_schema.columns as c2 WHERE c2.table_name = c1.table_name ) ) FROM information_schema.columns as c1 WHERE table_schema = '<DB名>' ;
これらの関数は MySQL 5.7.22 以降なら使える模様だけど、sort には未対応っぽいのでいい感じに並べ替えたいなら別な方法を取る必要があるかも。
Aggregates a result set as a single JSON array whose elements consist of the rows. The order of elements in this array is undefined.
MySQL :: MySQL 8.0 Reference Manual :: 12.19.1 Aggregate Function Descriptions
【GraphQL】何らかの条件で何らかを集めてくるフィールドの設計、どうする?
何を言っているかわからないと思うけど、例えば「1週間以内に新規登録したユーザー一覧を返す」みたいなフィールドをどう設計するかという話。いくつかパターンがありそう。
1. 専用のフィールドを用意する
query { recentlyRegisteredUsers: [User!]! }
クライアントからはこのフィールド呼べばいいだけなので楽。ただし、新たに「最近更新があったユーザー一覧を返したい」のような要望がでてきたら、似たようなフィールドが無尽蔵に増えていく可能性がある。
2. 全部返すフィールドを特定の条件でフィルタできるようにする
query { users( filter: UserFilter ): [User!]! } enum UserFilter { "1週間以内に新規登録したユーザー" RECENTLY_REGISTERED }
1 よりはフィールドの治安は保たれそうだけど、代わりにフィルタ条件が増えていくことになる。
3. 検索用のフィールドを用意する
query { searchUser( registeredSince: DateTime, registeredUntil: DateTime ): [User!]! }
2 と似てるけど、クライアント側から検索条件を指定するタイプ。シンプルな検索条件ならこれでいいけど、複雑になってくるとクライアント側からのクエリが大変なことになりそうだし、検索条件のオプションも増えまくって大変になりそう。
4. queryString で検索できるようにする
query { searchUser( query: String ): [User!]! }
クエリ文字列でなんでも検索する方式。こちらもシンプルな検索条件ならいいけど、複雑になってくるとクエリを組み立てるのが大変になりそう。queryString をパースする必要があるのでバックエンドの実装は他の案と比べて複雑になるかも。
結局どれがいい?
以下のような方針が良い気がしている。
検索条件がシンプルなら、検索用フィールドから検索できるようにする
今回の例なら、ユーザーの登録時期で検索とかは汎用的な要件なのでこちらがいいのかも?
検索条件が複雑なら、専用のフィールドを用意する
例えば「1ヶ月以内に登録して、なおかつ1週間以内に5回以上更新があったプレミアム登録済みのユーザー一覧」みたいな条件だと特殊な要件すぎるのでこちらがいいのかも?
他に何かオススメあったら教えてください!
キーボードとマウスを新しくした
今まで普通のテンキーレスのキーボードを使っていたんだけど、FPS をプレイするときにマウスが当たってしまってエイムの邪魔になるので60%キーボードを買った。
Ducky One 3 Mini 60% keyboard Classic Pure White
エンターキーの色が違うのがおしゃれ。めっちゃ光ってるけど、ライトパターン10個くらいあって控えめなのも選べるのでよい。
せっかくいいキーボード買ったので、仕事用の Mac Book Pro でも使っている。今までは周りが HHKB とか使っているのを見ても「キーボード買ったところでタイピング速度なんてそんな変わらなくね?」と冷めた目で見て頑なに Mac Book の付属のキーボードを使っていたけど、しばらく使ったところ、確かにタイピング速度は別に変わらないけど気に入ったガジェット使ってると気分は上がっていいな、となった(手のひら高速回転)。
マウスもついでに新しくしてG PRO X SUPERLIGHT にしたんだけど、Mac で使うときにマウス加速が邪魔すぎるので LinearMouse を入れて切っている。Mac 使ってると、こういうのわざわざアプリケーション入れないと設定できないのイマイチだよな〜って思う。アプリケーションごとに音量をいじれないとか、Windows ならそういうのは OS 標準でできるのに…
なお FPS は全然上手くなってない様子。
ChatGPT にホームページを作ってもらう
独自ドメイン取ったので自己紹介サイトでも作ろうかな〜と思ったけど、急にめんどくさくなったので ChatGPT に作ってもらった。
こんな感じになった。
ヘッダにリンクがあるのが嫌なので変えてもらう。
ちゃんと変えてくれた。優秀。
タイトルを変えてもらったり、chatGPT で作っていますという注釈を入れたり、フッタを消してもらったり、アイコンを設置してもらったりした。
最終的に出来上がったのがこちら。
Font Awesome のロードの仕方がちょっと間違っててアイコン読み込まかかったのでそこは手で直したけど、なんかいい感じになった気がする。5分くらいでできたので、たたき台作りとしてはいいかも。
cookie に同意するポップアップを消したい
GDPR の関連で、特に海外のページにアクセスすると「お前のブラウザの cookie 使うけどええか?」というポップアップ来るけど勝手に使ってくれ…という気持ちになる。
同じように考えている人は多いようで、 I don't care about cookies 3.4.5 というブラウザ拡張機能が存在している様子。試しに使ってみたけど、一部のサイトではうまく動かなかった。
実装が気になって見てみると、サイトごとにルールが列挙されていて、このサイトはDOMを隠す、このサイトは Cookie に値をセットする… みたいなことが書かれていた。そしてこのリストに追加したい場合は、リクエストを送ると追加される(可能性がある)世界観のよう。
I-Dont-Care-About-Cookies/rules.js at master · appeasementPolitik/I-Dont-Care-About-Cookies · GitHub
サイトによって実装はバラバラなのでこうするしかないよな〜と思いつつも、なかなかワイルドで面白い。