2023年を振り返る

これはなに

  • 今までのやり方を変えて目標に取り組んだ1年でした
  • 実際にやったり、人に会ったり、本を読んだりして考え方が変わった
  • 少し先を見てどうすべきか考えるスキルが伸びた気がします

やったこと

今年は公私ともにイベントがたくさんあった。まず第一子が産まれ父になった。毎朝子どもの顔を見ると幸せな気持ちになる。よく笑ってくれるし、元気に育ってくれて最高です。

YAPC::Kyoto 2023にオンラインで登壇した。発表の一部は第78回 Perl Webアプリケーションのリプレイスで大事なこと ~Stranglerパターンで段階的に移行する(1) | gihyo.jpに書いた。WEB+DB PRESS休刊前に書けてよかった。ただ、YAPCで話したリプレイスは別の方法に変更して現在は進めている。さらに良い改善方法が見つかったからだ。今の進捗状況を見てると、サンクコストを気にせずに今年の2月に方針転換を決断してよかったと思う。

方針転換からの反省と、今後の改善の手がかりを探すために生産性の計測に着手した。計測の取り組みはDiverseが開発生産性を計測する理由 - Diverse developer blogDiverseエンジニアチームの23年の開発を振り返る - Diverse developer blogに書いた。この計測によって誤った思い込みが正されてよかったし、次に取り組むべき課題も見えてきた。

そして、今年は自分がコードを書く時間が徐々に減っていった。意図的に減らしたというより、中長期の成長を考えると別のことをやる必要性に気づき、結果的に減っていった。この気づきは今年読んだ本、前職の同僚や他社のCTOに会うことで生まれた。

特に、今年読んだ本の中で『スーパーエンジニアへの道―技術リーダーシップの人間学』がダントツで良く、今の自分に突き刺さるアドバイスが書かれていた。この本は91年初版で自分が生まれたころに書かれた本だ。まさかこんな古い本に今の自分の課題が言語化されているとは思ってもなかった。

事業の経営、社員のマネジメント、目標設定はプログラミングの技術よりも歴史が長い。CTOは技術はもちろん、経営に関わる周辺知識の理解も必要だ。その理解には、少なくとも00年代以降の本じゃないと時代が合わないから読む価値は低いと思い込んでいた。でも、自分の知識が乏しいだけで、それは間違いだと思い直した。やり方や考え方を変える大きな転機になったし、もっと自分の知らない事と人に向き合おうと思い直した。

あとは公私ともにコミュニティへの参加を積極的に行った。CTO協会は個人会員で参加しており、他社のCTOと積極的に会って話した。プライベートは子どもが出来たので奥さんの友人と会ったり、自分が通っているCrossFitのコミュニティに子連れで参加した。育児を理由に外への接点を自分も妻も減らすのは嫌で、誰かと何かをやることに億劫にならない家庭でありたい。

来年もやること

今年の10月から毎朝5分の振り返り日記をつけている。昨日の反省点や改善点を毎朝起きて1時間以内に書いている。GitHubにdayoneというプライベートリポジトリを作り、ブラウザからMarkdownで書き残している。これが一番ストレスなく継続できると判断した。日々の目標に向き合って、どうすれば課題解決できるか頭の負荷が高すぎず少なすぎずバランスが良い。さらに朝なので改善に取り組む時間がある。来年もこれを継続する。

毎朝5分日記が象徴的だったが、今年は「少し先を見てどうすべきか考える」スキルが伸びた気がする。目標に向かって何ができるか来年もトライしていきます。良いお年を。

2022年を振り返る

これはなに

  • 今年も1年お疲れ様でした
  • CTOに就任して1年経過し、5月からは取締役CTOになった
  • 来年も「売上と開発の課題を同時に解決しつづける組織」にするために最善を尽くす

目標は達成できたか

ちょうど1年前に CTOになった経緯とその決意を書く - kurotyannの覚え書き を書いた。 書いた目標すべてに試行錯誤をして成果を得た。しかし、途中でこの目標の一部は方向転換が必要になった。

今年の4~5月頃に親会社から独立した。 プロダクト、組織、経営、カルチャーなど会社のすべてに手を加える必要がでた。 社内では第二創業期と呼んで新たなスタートをきった。

外部に向けた象徴的な変更だと、会社ロゴ、コーポレートサイト、会社のビジョンをリニューアルした。 さらに、プロダクトを1つ親会社へ譲渡した。

このタイミングで自分もCTOから取締役CTOになった。 経営判断に技術視点が必要だと感じ、経営サイドへも挑戦することにした。 今年1番の挑戦だったし、今後の目標達成に必要な選択だった。

来年の目標

冒頭にも書いたが、「売上と開発の課題を同時に解決しつづける組織」にするのが目標です。 おそらく今の立場を継続する限り、この目標が変わることはなさそう。

CTOに就任した当時は、技術負債や組織コミュニケーションの改善に注目してた。 これらはもちろん必須だが、会社のP/Lと連動した決定でないと解決のインパクトが小さい。 あと、今の自分にできることに注目しすぎたと反省もした。 自分が技術や決定のボトルネックにならないよう、常にGo Higherしないとダメだった。

ずっと売上と利益が伸び続ける前提なら、いくらでも好きにやれるが、あらゆる課題がそれを阻んでいる。 経営と技術の両面から根本を解決するには、役員という立場に自らを置かないとダメだった。

技術への理解不足を嘆くエンジニアもいるが、エンジニアが技術以外の立場に歩み寄る選択肢もあると思う。 あらゆる経験はスキルアップになり、デカいことやるなら、チームが必要だ。

とはいえ、コードを書いてサービスを開発するなら、エンジニアの個々の技術力とプロダクトの開発環境がベースで整ってないといけない。 そして、これはエンジニアが率先してやらないといけない。一方ではなく両方やるし、チームと個人どっちだではなく、両方だし同時だ。 これらを可能にするには、解決のインパクトが大きい課題(いろんな問題が芋づる式で解決される「解の重心」となる課題)にリソースを全集中できるようにトップが頑張らないとダメだ。

なので、最後に一言いうなら「家族に迷惑かけないレベル」で最善を尽くします。良いお年を。

2021年の人生を振り返る

これはなに

  • 公私ともに大きなイベントがあった1年だった
  • 今年からフォーマットを変えて、目標の振り返りと、来年の目標設定に変更した

目標は達成できたか

去年の振り返りブログで宣言した、今年やりたいことは以下の2つだった。

Kipping Bar Muscle-Upは夏頃にできるようになった。肩への負担が怖くて連続は無理だけど、1回でもできたことが嬉しい。 また夏にできたから、その後はpistol squatに変更してこれも安定してできるようになった。 これでopen gamesの予選メニューで出来ない種目は、逆立ち歩きとRing Muscle-Upの二つぐらいかと思う。

ただ、CTOになった直後に業務がガッと忙しくなり、トレーニングの負荷を下げた時期が続いてしまった。 最近は業務にも慣れ、少しずつ強度を上げている。でも、結果はイマイチ。 来年2月の2022 NOBULL CrossFit Openにエントリーはするけど、去年より成績が悪くなりそう。 RxでWODを完遂できなくても、Rxで出来る種目には挑戦する。

一方、英語はマイケルの言っていることが相変わらず聞き取れない。そもそも公私の変化で英会話を夏頃にやめてしまった。 英語より優先すべき課題があったり、夏から同棲のために引っ越しをし、パートナーとの時間をさらに大切にしたくなった。 英語は嫌いではないが、プライベートやcrossfitや本業よりも優先したくはないので、来年の目標からは外す。

来年の目標

去年の振り返りで

2021年も「自分をさらに鍛えて、私生活を充実させつつ、仕事でも成果を出す」年にします 💪

2020年の自分のエンジニア人生を振り返る - kurotyannの覚え書き

と書いて締めくくっていた。たぶん、ずっとそうなんだろうと思う。 仕事が忙しくても、英会話のようにcrossfitをやめることはなさそう。

ただ、最優先は家族やパートナーになった。 自分の家族やパートナーを大事にしないと、一緒に働く人の家族やパートナーも大事にできない気がする。 CTOは精一杯やるけど、それはトレーニングとプライベートが充実していることで成立し、そこから成果に結びつくと考える。 この考えを忘れずに来年も前進するぞ!

良いお年を!🎍

CTOになった経緯とその決意を書く

これはなに

  • 責任ある仕事に就いたので経緯と決意を残したかった
  • 会社とは独立して自分の決意を優先した文章を書きたかった
  • 来年は決意に共感してくれる人を増やしたい

課題を解く意思

今年の初めに急成長中の会社に所属する優秀なエンジニアと軽く立ち話をする機会があった。 話題は、自分が大学のアルバイト時代に遭遇した「非効率な業務」だった。 どんな流れでその話題になったかは忘れたが、ただの雑談としか自分は思ってなかった。 彼が「どうすればそれは解決できるかな...」と言うまでは。彼はまさに絵文字の 🤔 のように考え出した。

軽い立ち話での雑談だ。しかも、10年以上前の大学のアルバイトの話だ。 取るに足らない話題なのは明白だ。 ただ、彼の表情からは課題を解いてみようとする意思が伝わってきた。 「ああ、ここに大きな差があるんだな」と自分は悟ったのを強烈に覚えている。

本質的な課題に向き合えない日々

さきの出来事は今年の2月ごろだった。それからずっと課題を解くことを意識してきた。 ただ、解こうとする課題がどこか的外れで本質的ではない気がしてならなかった。

必要なことだし無駄なことではない。でも、クリティカルではなくインパクトがない。 そんな課題をちょこちょこ解こうとする感じだった。

本質的な部分が見えてないわけではなかったと、今振り返ってそう思う。 ただ、解くために必要なスキルと向き合い方が全くわからなかった。 当時はスキル不足だと思い、得意とするモバイルからインフラやサーバーの課題に挑戦していた。

本質的な課題に向き合うチャンス

今年の9月だった。CEOから「会社の技術力を上げてほしい」と依頼された。 このとき本質的な課題への向き合い方が見えた気がした。 今までは組織の中での「視点、視野、視座」が定まってなかったんだと。

CEOからはCTOの言葉はなかったが、詳細を聞くとCTO以外に適切なポジションはなかった。 CTOと言わなかったのは自分が入社2年目であり、会社にCTOがいたことがなかったからだろう。

依頼されたMTGで、CEOからの依頼を承諾した。 ただし、条件に以下を提示した。

  • 入社歴の長いエンジニアリングマネージャーをVPoEにすること
  • CTOとVPoEでCTO室を設立して、二人三脚で課題へ向き合うこと

条件の意図は、技術力向上には組織理解が不可欠なので、入社歴の長い社員の協力が必要だった。 さらに、本質的な課題を解くために、数名のメンバーをチームとして迎える編成が必要だった。 条件は意図通りに承諾され、本質的な課題へ9月から向き合い始めている。

実はこのとき、FlutterKaigiの運営スタッフとして11月開催の準備中だった。 CTOとの兼務は難しく、10月にスタッフ業務を辞退したが、開催時のスタッフ一覧に自分を加えてくれたことは感謝しかなかった。

やれることはたくさんある

9月~12月からはクリティカルであり、解決するとインパクトのある課題にいくつか向き合えた。 CTOは責任と裁量がバーンと増え、やれることがドカンっと増える。

リンクが有効なものは、採用広報として年内に公開できたものだ。 来年には社外の人たちに理解できる形で他の事例もアウトプットしていく。 自社の良い事例が公開されてないので、外部から見て「何をやっているのかわからない」ことが歯痒い。

来年は本質的な課題に向かってあがく

この業界は採用広報活動が年々活発になり、コロナ禍でさらに加速している。 文章、音声、動画と多様な媒体で広がり、他社のCTOの取り組みが簡単に学べる。

年内の取り組みは他社の良い事例を参考に、自社に合致するものを選び実行したものだ。 共に働いたことがあるCTOがやっていたことも真似した。

ただ自分で考え、CTO室のKick-Off MTGで伝えたことがある。

# CTOとVPoEは全てを解決する役職ではないです

- 戦略と方向性は示しますが、CTOとVPoEが全課題を解決するわけではない
- Missionのとおり「自分たちで課題を解決する組織」を目指そう
- 余裕のあるスケジュールで常にスキルアップを忘れずに 💪

CTOになったから何でも出来るようになったわけではない。 組織課題に向き合いつつ、時間を作ってスキルアップし、先手を打ち続けないといけない。 本質的な課題はチームでないと解決できない。「忙しい、時間がない」を枕詞に言わない使わない。余裕のあるスケジュールを作り、メンバーを成長させる。

あがけるだけ、あがいて来年も成長していく。

英会話が趣味になった話

■ これはなに

  • 去年の11月から始めたレアジョブ英会話のレッスン回数が今日で200回を超え、レッスン時間が100時間を超えた
  • 仕事で必要に迫られ始めたが、途中でチームメンバーの移動があり、英語で話す・書く機会はほぼゼロになった
  • 機会は減ったけど、朝の英会話を続けることが習慣になり趣味になり楽しい
2020/11/24 〜 2021/5/29
f:id:kurotyann:20210529155353p:plain

■ 英会話が趣味になった理由

1. 毎日やらないルーティーンにした

だいたい隔日で朝8:30 ~ 9:30で行うルーティーンにした。これ以外の時間にはやってない。1レッスンが25分でこれを1日に2回やるので、1日に約1時間(25分超えることもある)となる。

レッスン前に教材となるニュースを下読みして予習もするので、実際は1時間以上はかかっている。下記がレアジョブの画面で見える僕のレッスン履歴です。

11月 12月 1月 2月 3月 4月 5月
f:id:kurotyann:20210529154743p:plain f:id:kurotyann:20210529154759p:plain f:id:kurotyann:20210529154812p:plain f:id:kurotyann:20210529154826p:plain f:id:kurotyann:20210529154844p:plain f:id:kurotyann:20210529154854p:plain f:id:kurotyann:20210529154910p:plain

やっていない平日の朝は、クロスフィットというトレーニングに出かけている。つまり、運動と英会話が交互に来るのが平日の朝のルーティーン。一方、週末は時間があるので、英会話の後にクロスフィットに行くので両方やるのが週末の朝のルーティーンです。

無理して毎日やらないこと、毎回同じ時間帯にやることが、今日まで続いている理由だと思う。

2. 同じ講師を予約しない

これはレアジョブで働く知人から勧められて、そうすると決めた。予約する講師は毎回違う人にしている。 すると、最初の1~2分がほぼ自己紹介で始まり、毎回新鮮な状態で会話がスタートする。

さらに「前回の講師とは、うまく話せなかったなー」とか「まったく聞き取れなかったー」としても「まあ、ええか」となる。これは人によるけど、「2度と会うことはないだろうから、どう思われても気にしなくてOK」なのが英会話を始める心理的ハードルを下げてくれた。

逆に教室に出向いて実際に人と話すだと、すぐに止めたかもしれない。コロナだからそもそも選択肢になかったけど。

3. 教材を途中でデイリーニュースに変えた

最初は中学や高校の教科書みたいな教材を使っていた。男女の登場人物が出てきて会話とかを聞いたり、自分と講師が登場人物になりきってロールプレイするみたいな例のやつだ。2ヶ月ぐらいたって、これにはマンネリ感とストレスを感じるようになった。

自分が英語をうまく喋れないことにストレスはもちろん感じる。でも、それ以上に教材の題材とロールプレイがストレスだった。コロナでろくに外出できないのに観光地やお祭りに行ったときの会話や、ほぼあり得ないような仕事や恋愛の出来事など、作り話だから当たり前だけど、日に日にストレスが増していった。

そこで3,4月ぐらいから、デイリーニュースに教材を変えた。これが楽しい。まず今の状況を反映してるから、リアルの日常と英会話が結びついてくる。

さらにニュースだから、社会、経済、政治とか社会人だと知っとくと良いよね的なものから、世界中の珍事件などバラエティに飛んでて飽きない。Podcastでニュースが配信されるのも良いし、レッスンの後半でニュースをもとに講師と会話するのも楽しい。講師の話も鮮度が良いから。

教材をデイリーニュースに変えたことで、純粋に会話を楽しめるようになった。

4. コロナのせいで知らない人と話すのが楽しくなった

コロナのせいで人と話す機会がどんどん減っている。だからといって、仕事で雑談や必要のない出社をしてまでコミュニケーションをとろうなんて思わない。仕事は効率的に済ませたいし、プライベートでこそ友達と会って雑談したい。でも、それも難しい。

当たり前だけど、友達もコロナで行動が制限されてて話のネタが少ない。コロナ前だと「どこ行った」「こんなことがあってさー」に加えて、美味い飯と酒を飲みながら一緒にしゃべるのが普通にできてた。それが出来ない。

そこに毎回知らない人と強制的に話す時間ができると、英語であっても面白い時間になる。人と話すことに飢えていたのかもしれない。面白い講師もたくさんいるので、あっという間に25分たったなと思う日が増えている。

英会話はコロナの時期に気軽に会話を楽しむ良い娯楽=趣味になった。

5. 仕事を理由に英会話を続けなくても良くなった

今年の5月までは自分のチームに外国人のメンバーがおり、そのマネージャーが僕だった。そのメンバーが別チームに移動となったので英語を使う機会が減り、必ずしも英語が必要とは言い切れない状態になった。

こうなると、「なんで英会話続けてるんだけ?」となり、でも止めたい気持ちもないから「あ、これもはや趣味では」となった。

■ まとめ

昨年末の振り返りブログに今年の目標の一つに「バイリンガルニュースでマイケルが言っていることを7割わかるレベルになる」を書いた。

現時点では3割も怪しい。対して英語ができない人ほどこういう無茶な目標を立てがち。しかし、年末に書いたときの気持ちより、かなり前向きに取り組めている。受験と大学時代に約2ヶ月の短期留学で英語は勉強したけど、それ以降はGoogle翻訳などのテクノロジーに依存しまくっている。今は、その方が圧倒的に効率が良い。

でももしかしたら、ここから俺の英会話力が上がっていくのかも!?て思い始めたそんな今年の上半期でした。

2020年の自分のエンジニア人生を振り返る

■ これはなに

■ アウトプット

個人ブログ

この振り返りの記事を除くと1件のブログを書いた。 前職でGoogle Drive API v3を使って手作業を自動化したとき書いた。 そこそこ対応に時間がかかったので、書いてよかった。

会社ブログ

前職の会社ブログだが、1件の記事を書いた。 ニーズのある内容だったようで、SNSはてブでの反応が良かった。

Qiita

今年は2件の記事を書いた。 どちらもアドベントカレンダーの記事で、これで5年連続となる。 Qiitaの記事は転職先で得た知見をもとに書いた。

副業

今年の7月に4年半勤めた会社を退職して別の会社へ転職した。 転職により、引き継ぎが必要だったので退職後も副業でサポートした。 後任のエンジニアへスムーズに引き継げて良い副業だった。

OSS活動

業務でビデオ通話やライブ機能の実装が必要だったので、今年はそれに関係するOSSへのコミットが増えた。 これがきっかけで、初めて個人でpub.devにOSSライブラリを公開できた。 個人で公開したOSSライブラリの中で最も多いスターを貰えたし、issueやPRなども世界中のエンジニアから届いたので、とても良い経験ができた。

■ キャリア

転職した

今年の7月に4年半勤めた会社を退職して別の会社へ転職した。 前職は古い順から数えて5番目で、エンジニアだと2番目の古株になっていた。

前職の最後に手掛けたサービスでFlutterとFirebaseを経験しており、転職先でも同じ技術スタックで仕事している。 FlutterとFirebaseの経験が自分のキャリアを伸ばす強みになると確信できた1年だった。

コミュニケーションが英語のチームに入った

外国人のエンジニアがいるため、GitHubやSlackのコミュニケーションが英語になった。 会話は日本語だが、エンジニアだけの会議になると英語の割合が少し増える。 特に1on1は、英語の割合が大きくなったので苦労している。

危機感を感じたので、レアジョブで英会話のレッスンを開始したり、ディクテーションなど英語の学習時間を増やした。 来年は英語も頑張っていきたい。

■ 今年と来年の目標

今年の目標は達成できたか?

去年の振り返りブログで宣言した、今年やりたいことは以下の2つだった。

  • ① トレーニングして懸垂ができる健康的な身体に戻す
  • ② 仕事で得た知見を今年以上にアウトプットする

①は完璧に達成した。

クロスフィットというスポーツトレーニングにどっぷりハマってしまい、週3で朝に激しめの運動をしている*1。 このおかげで転職先の健康診断の結果が「異常なし」に回復していたので、めちゃくちゃ嬉しかった。 さらに運動のおかげで睡眠も良くなり、朝型人間になった。あらゆることを午前中に完了させる癖がつき始めている。

②も達成できた。

アドカレも2本書いたし、個人でOSSライブラリを公開して良い成果を出したので、これも達成できた。

来年の目標について

今年はコロナの影響で今後の人生について考える時間が増えた。 このままでいいのだろうかとか、どう生きていきたいのかなど、思い悩む日が増えた。 自分の結論は、「仕事ばかりではなく、私生活を充実させつつ、仕事で成果を出す」となった。

つまり、来年のやりたいことは...

来年の目標には、技術的な目標を個人ブログには書かないことにした。 エンジニアとしての成長は会社の目標設定で定めるから、個人ブログに書く必要がない。

むしろ、「今後、自分はどう生きていきたいのか、暮らしていきたいのか」の方が重要で、そのための目標を定めることにした。 2021年も「自分をさらに鍛えて、私生活を充実させつつ、仕事でも成果を出す」年にします 💪

Google Drive API v3を使ってスプレッドシート(csv)や画像(png)のダウンロード・アップロードをやってみた

■ これはなに

  • Drive APIでSpread Sheet(csv)や画像(png)のダウンロード・アップロードが必要で実装に時間がかかった
  • Drive APIの仕様を事前に知っておいた方が実装しやすいので、ブログにアウトプットしておく
  • Drive APIのおかげで、ブラウザからGoogle Driveを開いてポチポチする無駄な手作業は無くなった

■ 前提

■ やったこと

1. APIでアクセスするDriveの範囲(権限)を制限

  • API経由でアクセスできるユーザーをGCPのサービスアカウントで作成する
  • サービスアカウントがアクセスできるDriveの範囲を制限する

手順やコード

  1. GCPの「APIとサービス」→「ライブラリ」でDrive APIを有効にする
  2. GCPの「APIとサービス」→「 認証情報」 →「サービスアカウント」でアカウントを作成
  3. サービスアカウントに設定したメールアドレスで、Driveのフォルダのユーザー権限にサービスアカウントを追加・権限変更ができるようになる
  4. あとは googleapis にサービスアカウントのキー(.json)を下記の方法で渡せば、APIを利用できる
import fs = require('fs');
import { google, drive_v3 } from 'googleapis';
import { JWT } from 'googleapis-common';

export class DriveApp {
  private readonly jwtAuth: JWT;

  /*
   * ref:
   *   - https://developers.google.com/identity/protocols/oauth2/scopes#drive
   */
  constructor() {
    this.jwtAuth = new google.auth.JWT({
      keyFile: '../credentials/service_account_key.json',
      scopes: ['https://www.googleapis.com/auth/drive'],
    });
  }

  private async driveApi(): Promise<drive_v3.Drive> {
    try {
      await this.jwtAuth.authorize();
      return google.drive({
        version: 'v3',
        auth: this.jwtAuth,
      });
    } catch (e) {
      console.log(`🧨 method: driveApi 🧨`);
      throw e;
    }
  }

// 省略

2. DriveのSpread Sheetをcsv形式でダウンロード

注意1: Drive APIはシート指定のダウンロードに未対応

  • Drive APIとは別にGoogle Spread Sheet専用API(Sheets API)もある

  • Drive APIは、Spread Sheet内のシートを指定してダウンロードは出来ない

  • Drive APIは、対象のSpread Sheetの先頭のシートのみダウンロードする

    • Spread Sheet内のシートを指定してデータをダウンロードしたい場合は、Sheets APIなら可能だった
    • しかし、csv形式のダウンロードには未対応で、Sheets APIの独自形式でのダウンロードになる
  • 使うAPIを増やしたくなかったので、Spread Sheetの複数シート管理をやめてファイル単位にシートを分割してDrive APIのみで対応した

注意2:Drive APIの検索クエリは変わってる

  • 世の中には、こういう検索クエリの指定形式もあるのかもしれないが、特殊な仕様でわかりづらかった
  • 特に注意してほしいのは、 name contains '${targetName}' は前方一致であり、部分一致ではないところ

  • 当時の苦悩はTwitterでつぶやいたりした

手順やコード

/*
 * ref:
 *   - https://developers.google.com/drive/api/v3/reference/files/list
 *   - https://developers.google.com/drive/api/v3/reference/files/export
 */
async exportCSV(folderId: string, spreadSheetName: string): Promise<any> {
  try {
    const driveApi = await this.driveApi();
    const response = await driveApi.files.list({
      fields: 'files(id, name)',
      q: DriveApp.queryBuilder(DriveMimeType.spreadsheet, folderId, spreadSheetName),
    });

    const fileId = response.data.files?.[0]?.id;
    if (!fileId) {
      throw new Error(`❌ ファイルが見つかりません(${folderId}, ${spreadSheetName})`);
    }

    const fileExport = await driveApi.files.export({
      fileId: fileId,
      mimeType: 'text/csv',
    });
    return fileExport.data;
  } catch (e) {
    console.log(`🧨 method: exportCSV 🧨`);
    throw e;
  }
}

/*
 * ref:
 *   - ゴミ箱は対象外(trashed = false)
 *   - https://developers.google.com/drive/api/v3/mime-types
 *   - https://developers.google.com/drive/api/v3/reference/query-ref
 */
private static queryBuilder(
  type: DriveMimeType | null,
  folderId: string | null,
  targetName: string | null
): string {
  let query = 'trashed = false';
  query = type ? `mimeType = '${getMimeType(type)}' and` + query : query;
  query = folderId ? `'${folderId}' in parents and` + query : query;
  query = targetName ? `name contains '${targetName}' and` + query : query;
  return query;
}

3. ローカルのcsvをSpread Sheet形式でDriveへアップロード

  • mimeType でアップロード形式を指定できる
  • 間違って別のフォルダに作成したり、ファイルを更新したりしないように、 parentsaddParents にファイルが配置されるフォルダIDを指定する

手順やコード

/*
 * ref:
 *   - https://developers.google.com/drive/api/v3/reference/files/create
 *   - https://developers.google.com/drive/api/v3/manage-uploads#import_to_google_docs_types_
 */
async create(
  folderId: string,
  localFilePath: string,
  spreadSheetName: string
): Promise<drive_v3.Schema$File> {
  try {
    const driveApi = await this.driveApi();
    const response = await driveApi.files.create({
      media: {
        mimeType: 'text/csv',
        body: fs.createReadStream(localFilePath),
      },
      requestBody: {
        parents: [folderId],
        name: spreadSheetName,
        mimeType: getMimeType(DriveMimeType.spreadsheet),
      },
    });
    return response.data;
  } catch (e) {
    console.log(`🧨 method: create 🧨`);
    throw e;
  }
}

/*
 * ref:
 *   - https://developers.google.com/drive/api/v3/reference/files/update
 *   - https://developers.google.com/drive/api/v3/manage-uploads#import_to_google_docs_types
 */
async update(folderId: string, localFilePath: string, fileId: string): Promise<void> {
  try {
    const driveApi = await this.driveApi();
    await driveApi.files.update({
      fileId: fileId,
      addParents: folderId,
      media: {
        mimeType: 'text/csv',
        body: fs.createReadStream(localFilePath),
      },
      requestBody: {
        mimeType: getMimeType(DriveMimeType.spreadsheet),
      },
    });
  } catch (e) {
    console.log(`🧨 method: update 🧨`);
    throw e;
  }
}

4. 画像をpng形式でDriveからダウンロード

手順やコード

/*
 * ref:
 *   - https://developers.google.com/drive/api/v3/reference/files/list
 *   - https://developers.google.com/drive/api/v3/reference/files/get
 *   - https://github.com/googleapis/google-api-nodejs-client/issues/1768
 */
async downloadImage(folderId: string, basePath: string, imageName: string): Promise<void> {
  try {
    const driveApi = await this.driveApi();
    const response = await driveApi.files.list({
      fields: 'files(id, name)',
      q: DriveApp.queryBuilder(null, folderId, imageName),
    });

    const fileId = response.data.files?.[0]?.id;
    if (!fileId) {
      throw new Error(`❌ 画像が見つかりません(${folderId}, ${imageName})`);
    }

    const dest = fs.createWriteStream(`${basePath}/${imageName}`);
    driveApi.files.get(
      {
        fileId: fileId,
        alt: 'media',
      },
      {
        responseType: 'arraybuffer',
      },
      (err, res): void => {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        dest.write(Buffer.from(res.data));
      }
    );
  } catch (e) {
    console.log(`🧨 method: downloadImage 🧨`);
    throw e;
  }
}

■ 終わりに