tag:crieit.net,2005:https://crieit.net/tags/googleapi/feed 「googleapi」の記事 - Crieit Crieitでタグ「googleapi」に投稿された最近の記事 2020-03-22T20:38:57+09:00 https://crieit.net/tags/googleapi/feed tag:crieit.net,2005:PublicArticle/15776 2020-03-22T17:10:52+09:00 2020-03-22T20:38:57+09:00 https://crieit.net/posts/web1week 敗者のweb1week <p>先週行われた <a href="https://crieit.net/boards/web1week-202003">web1week</a>。<br /> 初開催にも関わらず50を超えるサービスが登場し、大盛況のうちに幕を閉じた。</p> <p>経験者ならば理解を得られると思うが、個人開発という舞台には魔物が棲んでいる。<br /> 着手時点で思い描いた予定などペロリと喰われてしまう。</p> <p>不意に削られる作業時間。<br /> 理想と妥協の狭間で揺れる要件。<br /> 技術仕様の落とし穴。<br /> サービスの意義を自問自答し続ける日々。<br /> リリース直後に訪れる無慈悲な障害。</p> <p>これらを乗り越えローンチされた個人サービスの奥には、語られることのない物語が潜んでいる。<br /> そんなサービスが50以上も並ぶweb1weekのボードは圧巻で、たとえHello Worldの様なサービスでも私の心を踊らせてくれる。<br /> とても甲乙など付けられない。</p> <p>しかし「敗者」は存在する。<br /> 「web1week楽しみ!」と方方で意気込みを語り、「やってみませんか?」と様々な人を勧誘した挙句、あろうことか最小限の機能でさえ1weekに間に合わず、最終的には要件すら満たせなかった。<br /> 紛うことなき「敗者」だ。<br /> 個人開発界隈ではよく「リリースすることが大事」と言われている。その通りだ。リリースさえすれば勝ちなのだ。</p> <p>これは、そんな勝負に敗北を喫した男の記録である。</p> <h1 id="立案"><a href="#%E7%AB%8B%E6%A1%88">立案</a></h1> <p>月曜の午前0時、お題「Home」が発表された。<br /> こじつけでも何でも「Home」を絡ませたWebサービスならクリアとのこと。<br /> 初学者でも参加し易いうえに、CGMやIoTを絡めたサービスも構想でき、かつ疫病の脅威に怯える時勢に寄り添った素晴らしいお題だと思った。<br /> それと同時に頭を抱えた。<br /> 1週間で作り上げるとなると出来る事は限られている。ピンと来るアイディアが何も思い浮かばない。</p> <p>「自分が実装してみたい機能」というアプローチで考えてみる。<br /> 我が家には<a target="_blank" rel="nofollow noopener" href="https://store.google.com/jp/product/google_nest_hub">Google Nest Hub</a>というガジェットが存在する。ディスプレイ付きスマートスピーカーというイロモノだ。<br /> スマート家電のような贅沢品は存在しない我が家だが、昨年の私の誕生日に妻から贈ってもらっていた。しかし、動画を見るにはタブレットやテレビに転送で事足りるし、BGMをかける風習も無い。なかなか使う場面が訪れなかった。<br /> これを何とか活用できないか。</p> <p>玄関や冷蔵庫に貼ってるホワイトボード。これをNest Hubで代用できないかと考えた。<br /> 「いってらっしゃい」「おやつにわらび餅を冷蔵庫に入れてます」の様な、家族間のメッセージは手書きの方が嬉しかったりする。<br /> しかし、ホワイトボードのデメリットはホワイトボードの場所へ行かないと書けないことだ。<br /> 仕事中に「冷蔵庫にわらび餅入れてるの忘れてた!」となった場合、LINEに頼るしかない。<br /> スマホから手書きでメッセージを書き、玄関やリビングに置かれたNest Hubに表示されたらエモいのでは。</p> <p>Nest Hubで表示するWebアプリについて調べてみる。<br /> どうやら<a target="_blank" rel="nofollow noopener" href="https://developers.google.com/assistant/interactivecanvas">Interactive Canvas</a>というフレームワークで実装するらしい。<br /> しかし、基本的にVUI(音声ユーザーインターフェース)が前提となる作りで、ストアへ並べるにはGoogleの承認も必要となる。<br /> 今回の要件・納期では厳しい、、</p> <p>方針を変える。<br /> Nest HubにはGoogle Photosにあるアルバムを選択して、フォトフレームとして表示する機能がある。<br /> Nest Hub側でメッセージ画像を入れたアルバムを選択して、Webサービス側ではそのメッセージ画像を更新すれば良いのはないだろうか。<br /> 調べてみた。さすがGoogle様。ちゃんと<a target="_blank" rel="nofollow noopener" href="https://developers.google.com/photos">Google PhotosのAPI</a>も存在する。</p> <p>これでいこう。</p> <ol> <li>WebサービスにGoogleアカウントでログイン</li> <li>手書きUIのCanvasでメッセージを書く。</li> <li>Nest Hub表示用のアルバムをGoogle Photosに作成</li> <li>Canvasを画像化して3.のアルバムにアップロード</li> <li>既にメッセージ画像が存在する場合は削除(アルバム内の画像は1枚のみとする)</li> <li>Nest Hubで表示</li> </ol> <p>OAuth2認証をクライアントで完結できれば、DBすら不要。<br /> この程度なら1週間でいける。</p> <p>市場調査もしてみた。</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">スマートディスプレイ(ディスプレイ付きスマートスピーカー)がどれぐらい普及してるのか気になるのでアンケお願いします!持ってるか持ってないか。押し入れで眠っててもOKです!</p>— きんみ | ツイッター大喜利サイト🎍ついぎり🎍作りました🙄 (@_kinmi) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/_kinmi/status/1236827457027592192?ref_src=twsrc%5Etfw">March 9, 2020</a></blockquote> <p>うん、Nest Hub専用サービスは良い感じのスキマ産業のようだ(強がり)</p> <p>Nuxt.jsでプロジェクトを作成して今日の作業は終了。<br /> この時点で月曜の夜。順調だ。</p> <h1 id="裏の目的"><a href="#%E8%A3%8F%E3%81%AE%E7%9B%AE%E7%9A%84">裏の目的</a></h1> <p>常々思っていたのが「もっと手軽に画像アップロード機能を作れないか」という事だった。<br /> 画像アップロードというのは、ストレージを用意して、アップされる画像の最適化処理を組み、著作権の侵害等を考慮して規約や同意UIの設計をしなければならない。<br /> リリース後は不適切な画像が上がってないかウォッチする義務が発生するし、ストレージの残容量も気にしなければならない。<br /> 更に、増え続ける画像の管理方法(一定期間で削除するか、お金で解決するか)も検討しなければならない。<br /> 個人開発において、割とコストが高めの機能だ。</p> <p>この課題はGoogle Photos等のユーザーが保有するクラウドストレージに公開状態で保存すれば解決するのではないかと考えた。<br /> このご時世、Googleアカウントは誰でも持っている。アカウント登録時に自動で付与されるGoogle Photosの容量は高画質モードなら無制限だし、あくまで「画像を公開しているのはユーザー」という体裁を繕えるので著作権問題もグレーゾーンとなる(漫画村方式)<br /> サービス側で保有するのは画像の静的URLのみだ。</p> <p>今回、Google PhotosのAPIを使いこなせる様になれば今後の個人開発に幅を持たせることが出来るかもしれない。</p> <h1 id="ハマる"><a href="#%E3%83%8F%E3%83%9E%E3%82%8B">ハマる</a></h1> <p>コロナ自粛の影響により勤務時間が削減され、個人開発の時間は比較的多めに取れた。<br /> それでも間に合わなかったのは全てにハマったからだ。<br /> 全てだ。<br /> 時系列でハマりポイントを紹介していく。</p> <h2 id="JS ClientSDKが使いづらい"><a href="#JS+ClientSDK%E3%81%8C%E4%BD%BF%E3%81%84%E3%81%A5%E3%82%89%E3%81%84">JS ClientSDKが使いづらい</a></h2> <p>Google APIをWebで利用する場合、<a target="_blank" rel="nofollow noopener" href="https://github.com/google/google-api-javascript-client">Google API Client Library for JavaScript</a>(GAPI)というライブラリを利用する。<br /> Initial Commitは2011年。それなりに年季の入ったライブラリだ。</p> <h3 id="NPMには登録されていない。"><a href="#NPM%E3%81%AB%E3%81%AF%E7%99%BB%E9%8C%B2%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%81%AA%E3%81%84%E3%80%82">NPMには登録されていない。</a></h3> <p>scriptタグで読み込む必要がある。<br /> Nuxt.jsで外部スクリプトを使用する場合、<code>$nextTicks()</code>を用いてもスクリプトのロードが完了していない場合がある。<br /> ロード状態を監視する必要があるので少し手間だ。</p> <h3 id="DiscoveryDocs"><a href="#DiscoveryDocs">DiscoveryDocs</a></h3> <p>GAPIのスクリプトを読み込んだ時点では<code>init()</code>等、最低限の機能しか保有していない。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/google/google-api-javascript-client/blob/master/docs/start.md">Getting Started</a>に記載されているが、API Key等を設定して初期化する際に<code>DiscoveryDocs</code>というURLを設定する。<br /> DiscoveryDocsはAPI単位で用意されており、URLの先にあるJSONをGAPIでロードすれば機能として利用できる。<br /> Google Photos APIのDiscoveryDocsは下記となる。<br /> <a target="_blank" rel="nofollow noopener" href="https://content.googleapis.com/discovery/v1/apis/photoslibrary/v1/rest"><code>https://content.googleapis.com/discovery/v1/apis/photoslibrary/v1/rest</code></a><br /> スクリプトの肥大化を防ぐための機構だろう。<br /> ここまではまだ良い。手間を感じるがまだ許せる。</p> <h3 id="同期処理"><a href="#%E5%90%8C%E6%9C%9F%E5%87%A6%E7%90%86">同期処理</a></h3> <p>ドキュメントのサンプルはthenチェーンで記載されている。<br /> じゃあ・・・と思って<code>async / await</code>で書き直してみる。動かない。<br /> そう、返却値は<strong>ES6 Promiseじゃない</strong>。<br /> <a target="_blank" rel="nofollow noopener" href="https://google.github.io/closure-library/api/goog.Thenable.html">goog.Thenable</a>という独自インターフェースを継承したオブジェクトだ。<br /> <code>init().then(()=>{ })</code> の中でしかGAPIは動かない。同期的にいくつものAPIを使いたければthenのネストを深め続けるしかない。F*ck。</p> <p>ES6が標準化されたのが2015年。このライブラリの開発当初ならスマートな仕様だったのだろう。<br /> しかし、こちとら去年からJSを学び始めた身。<br /> 終始、この仕様に慣れず生産性が低下した。</p> <h2 id="認証で躓く"><a href="#%E8%AA%8D%E8%A8%BC%E3%81%A7%E8%BA%93%E3%81%8F">認証で躓く</a></h2> <p>ユーザー管理には<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth?hl=ja">Firebase Authentication</a>を採用した。<br /> 手軽に導入でき、「特定のユーザーには使わせない」といったセキュリティ対策も出来る。<br /> Twitter API等、他社のAPIと連携する場合はトークンの保管といった一手間が必要になってくるが、今回はGoogle Platform内で連携するだけ。<br /> 簡単だろうと思った。</p> <h3 id="🙅 Firebase → Google API"><a href="#%F0%9F%99%85+Firebase+%E2%86%92+Google+API">🙅 Firebase → Google API</a></h3> <p>以下の記事を参考に、Firebase UIでログイン→GAPIのイニシャライズを試みる。<br /> <a target="_blank" rel="nofollow noopener" href="https://medium.com/google-cloud/using-google-apis-with-firebase-auth-and-firebase-ui-on-the-web-46e6189cf571">Using Google APIs with Firebase Auth and Firebase UI on the Web</a></p> <p><strong>できない。</strong><br /> この記事ではFirebaseでログインした時点で <em>GAPIの認証状態</em> : <code>gapi.auth2.getAuthInstance().isSignedIn.get()</code>が <code>true</code> になる想定だが <code>false</code> だ。<br /> GAPIでもログインする必要があるのか?と思って試したがFirebaseとGAPIで二重ログインされてしまった。</p> <p>記事のコメント欄に<a target="_blank" rel="nofollow noopener" href="https://medium.com/@slashdrew/this-appears-to-be-no-longer-functional-cc2ca49db640">記載がされている</a>が、現在はFirebaseでログインしてもGAPIでログイン状態を検知しない。<br /> 下記のissueにもその事が記載されており、ステータスはopenのままだ。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/google/google-api-javascript-client/issues/561">https://github.com/google/google-api-javascript-client/issues/561</a></p> <h3 id="🙆 Google API → Firebase"><a href="#%F0%9F%99%86+Google+API+%E2%86%92+Firebase">🙆 Google API → Firebase</a></h3> <p>つまり現状、FirebaseとGAPIを連携するには認証順を逆にしないといけない。<br /> <em>GoogleAPIのサインイン</em> : <code>gapi.auth2.signIn()</code>でログインした後、認証情報を取得して <em>Firebaseの認証情報を使用したサインイン</em> : <code>firebase.auth().signInWithCredential()</code>でもログインするフローとなる。<br /> 下記の記事を参考に実装した。<br /> <a target="_blank" rel="nofollow noopener" href="https://fireship.io/snippets/how-to-use-google-apis-or-gapi-with-firebase-auth/">How to Use Google APIs on the Web</a></p> <h3 id="進捗:だめです"><a href="#%E9%80%B2%E6%8D%97%EF%BC%9A%E3%81%A0%E3%82%81%E3%81%A7%E3%81%99">進捗:だめです</a></h3> <p>この時点で既に平日は終わっていた。残すところは土日のみ。<br /> 対応が長期化した要因は「出来ると書いてあったから」。<br /> Firebase→GAPIの認証は不可能という可能性を考慮していなかった。<br /> この考慮漏れが発生するとプログラマーの思考はどうなるか。<br /> <strong>ひたすらタイポを探し続ける</strong>のである。</p> <h2 id="画像アップロードが用意されてない"><a href="#%E7%94%BB%E5%83%8F%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%8C%E7%94%A8%E6%84%8F%E3%81%95%E3%82%8C%E3%81%A6%E3%81%AA%E3%81%84">画像アップロードが用意されてない</a></h2> <p>まだ間に合う。<br /> 土曜にアップロード機能を実装。<br /> 日曜にデザインを微修正&実機で動作確認。<br /> ここまで盛大に躓いて尚、根拠なく「出来る」と盲信する。<br /> 進捗が遅れてるプログラマーにありがちな<strong>逆算スケジュール</strong>である。</p> <h3 id="🙅 GAPIで画像アップロード"><a href="#%F0%9F%99%85+GAPI%E3%81%A7%E7%94%BB%E5%83%8F%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89">🙅 GAPIで画像アップロード</a></h3> <p>ガイドラインを読んでみる。<br /> <a target="_blank" rel="nofollow noopener" href="https://developers.google.com/photos/library/guides/upload-media">https://developers.google.com/photos/library/guides/upload-media</a><br /> どうやら画像をアップロードするには、二段階の手順を踏まないといけないようだ。</p> <ol> <li>画像のバイナリデータをGoogleにアップロードする</li> <li>上記返却値に含まれるアップロードトークンを用いてアルバムに追加する。</li> </ol> <p>Google Photosの<a target="_blank" rel="nofollow noopener" href="https://content.googleapis.com/discovery/v1/apis/photoslibrary/v1/rest">DiscoveryDocs</a>から該当のメソッドを探してみる。<br /> <strong>無い。</strong><br /> 何度検索しても「uploads」という処理は無い。<br /> 「なぜ無いのか」なんて考える余裕も無い。<br /> (現在、不安になって再度探してみたがやっぱり無い)</p> <p>GAPIじゃGoogleに画像をアップロード出来ない・・・?</p> <h3 id="🙅 axiosでリクエスト"><a href="#%F0%9F%99%85+axios%E3%81%A7%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88">🙅 axiosでリクエスト</a></h3> <p>悩んでいる暇は無い。もうクライアントSDKは捨てる。<br /> ドキュメントにはエンドポイントが記載されているので、そこに対して<code>axios</code>でPOSTリクエストを投げてみる。</p> <p><strong>Network Error.</strong></p> <p>・・・久し振りに見たぜ。<br /> <code>has been blocked by CORS policy: No 'Access-Control-Allow-Origin'</code><br /> <a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/HTTP/CORS/Errors">CORSエラー</a>だ。<br /> おいおい、嘘だろ。CORSだと?<br /> 浅学だが、基本的にはAPI側での対応が必須だったと記憶している。詰んだか?</p> <h3 id="🙅 XMLHttpRequestでリクエスト"><a href="#%F0%9F%99%85+XMLHttpRequest%E3%81%A7%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88">🙅 XMLHttpRequestでリクエスト</a></h3> <p>ドキュメントには「CORSもサポートしているよ」と書かれている。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/google/google-api-javascript-client/blob/master/docs/cors.md">How to use CORS to access Google APIs</a><br /> クライアントSDKを用いれば回避できるよ。と。<br /> <strong>使えねぇんだよチクショウが。</strong><br /> 記事の最後に<a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest"><code>XMLHttpRequest</code></a>を用いた例も記載されている。<br /> 存在は知っていたが初めて使うなこれ。<br /> 愚直に書いてある通り組んでみる。</p> <p><code>has been blocked by CORS policy: No 'Access-Control-Allow-Origin'</code></p> <p>泣く。</p> <h3 id="🙅 gapi.client.request()"><a href="#%F0%9F%99%85+gapi.client.request%28%29">🙅 gapi.client.request()</a></h3> <p>まだだ。諦めない。<br /> 俺は長男だから我慢できたけど次男だったら我慢できなかった。</p> <p>ドキュメントを漁り、もう1つの可能性を見つける。<br /> クライアントSDKには標準で <em>APIを叩く為のメソッド</em> : <a target="_blank" rel="nofollow noopener" href="https://github.com/google/google-api-javascript-client/blob/master/docs/reference.md#api-requests">gapi.client.request()</a>が備わっている。<br /> GoogleAPIのエンドポイントをベタ書きして引数に渡せば叩けるらしい。<br /> クライアントSDKならCORSを回避できるんだろ?信じるぞ?<br /> 叩いてみる。</p> <p><strong>404</strong></p> <p>???<br /> タイポ探しフェーズ(数時間)に入るも、やっぱり問題は無い。<br /> リクエスト内容を確認した。POST先のURLが変わっている。<br /> 画像アップロードのエンドポイントは<code>https://photoslibrary.googleapis.com/v1/uploads</code>だが、ドメイン部が<code>content.googleapis.com</code>に変わってる。<br /> なんだこれは。<br /> 恐らく、クライアントSDKが書き換えていると推測。M*ther F*cker。</p> <p>諦めた。</p> <p>クライアントで完結することを。</p> <h3 id="🙆 Netlify Functions"><a href="#%F0%9F%99%86+Netlify+Functions">🙆 Netlify Functions</a></h3> <p>ホスト先にNetlifyを利用している。<br /> そこでクラウド関数を作れるNetlify Functionsを使ってみようと思い立った。<br /> 使った経験が無い機能だ。下記の記事を参考にHello Worldから始めてみる。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/Sr_Bangs/items/7867853f5e71bd4ada56">【入門】Netlify Functionsコトハジメ</a><br /> そして「Googleに画像をアップロードしてアップロードトークン返すクラウド関数」を立ててみた。<br /> うまくいった。<br /> 初めて使う機能なのに、ハマらず動く。ローカル環境もすぐ出来た。<br /> 感動した。モダン最高。</p> <h3 id="進捗:納期は昨日なので大丈夫です"><a href="#%E9%80%B2%E6%8D%97%EF%BC%9A%E7%B4%8D%E6%9C%9F%E3%81%AF%E6%98%A8%E6%97%A5%E3%81%AA%E3%81%AE%E3%81%A7%E5%A4%A7%E4%B8%88%E5%A4%AB%E3%81%A7%E3%81%99">進捗:納期は昨日なので大丈夫です</a></h3> <p>この時点で日曜の夜。<br /> 軸となる機能は一通り完成したものの、「同名アルバムが存在していた場合」などのシチュエーションに応じた分岐は作れてないし、要件として「既にメッセージ画像が存在していた場合は更新する」といった機能も存在するが未実装だ。実機確認もやれてない。</p> <p>「ごめんなさい」しつつ、月曜に出そう。<br /> そう決めて床についた。</p> <h2 id="画像の削除/更新はできない"><a href="#%E7%94%BB%E5%83%8F%E3%81%AE%E5%89%8A%E9%99%A4%2F%E6%9B%B4%E6%96%B0%E3%81%AF%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84">画像の削除/更新はできない</a></h2> <p>ベットに入った直後、悪寒がした。</p> <p><strong>見覚えがない。</strong></p> <p>散々GoogleAPIのドキュメントと格闘し続けたが、Google Photos内の写真を<strong>削除/更新するエンドポイントは見た記憶がない</strong>。<br /> スマホを取り出し確認してみるも、やはり無い。</p> <p>冗談だろ?クラウドストレージを操作するAPIのくせに削除/更新は出来ない?そんな馬鹿な話があるか。<br /> 調べてみる。<br /> <a target="_blank" rel="nofollow noopener" href="https://issuetracker.google.com/issues/109759781">https://issuetracker.google.com/issues/109759781</a><br /> 結論:<strong>出来ないらしい</strong></p> <p>詰みです。お疲れ様でした。</p> <p>過去画像を含めてランダム表示されるなんてホワイトボードじゃない。</p> <p>「お手数ですがGoogle Photosを開いて、ご自身で過去の画像を削除してください。」<br /> しょーもな。<br /> サービスとして成立していない。</p> <p>・・・いや、まだだ。</p> <p>よし、譲ろう。</p> <p>百歩譲ろう。</p> <p>Google Driveなら削除/更新もAPIで可能じゃなかろうか。<br /> Photosなんかより歴史あるクラウドストレージだ。<br /> Google Driveへアップした写真をNest Hubで表示できれば問題無い。</p> <p><a target="_blank" rel="nofollow noopener" href="https://support.google.com/googlenest/thread/671055">Can I connect my Google Drive to my Google home hub? </a><br /> 結論:<strong>出来ないらしい</strong></p> <p>こうして私は、web1weekの敗者となった。</p> <h2 id="Safari対応(おまけ)"><a href="#Safari%E5%AF%BE%E5%BF%9C%EF%BC%88%E3%81%8A%E3%81%BE%E3%81%91%EF%BC%89">Safari対応(おまけ)</a></h2> <p>負け犬でも遠吠えぐらいは出来る。<br /> 「手書き画像をGoogle Photosへアップするサービス」としてローンチしよう。<br /> そう決めて実機確認に入った。</p> <h3 id="CORSぞ"><a href="#CORS%E3%81%9E">CORSぞ</a></h3> <p>MacのChrome、Android Chromeは問題なく動作完了。<br /> 妻に「iPhoneでこれ使えるか確認してほしい」と依頼する。<br /> 返信は「画面が驚きの白さ」。<br /> ここで初めてMacのSafariでも動作確認してみる。</p> <p><code>has been blocked by CORS policy: No 'Access-Control-Allow-Origin'</code></p> <p><strong>CORSぞ</strong>。</p> <p>何度立ちはだかれば気が済むのだCORSよ。<br /> まぁ思い当たる節はあった。<br /> GAPIが使い辛いので、下記のようにクラアントSDKを動的に読み込んでplugin化していたのだ。</p> <pre><code class="javascript">export default async ({ $axios, store }, inject) => { // GAPIの読み込み const gapiScript = document.createElement('script') const src = await $axios.$get('https://apis.google.com/js/api.js') gapiScript.appendChild(document.createTextNode(src)) document.head.appendChild(gapiScript) const gapi = window.gapi // ロード処理(Promise化) const clientLoad = new Promise((resolve, reject) => { gapi.load('client:auth2', () => { resolve() }) }) // GAPI初期化処理 const init = () => { return clientLoad.then(() => { return gapi.client.init({ apiKey: authConfig.Google.apiKey, clientId: authConfig.Google.clientId, discoveryDocs: authConfig.Google.discoveryDocs, scope: authConfig.Google.scopes.join(' ') }) }) } inject('gapi', gapi) inject('gapiInit', init) } </code></pre> <p>不安はあったが、動いたのでそのままにしていた。<br /> 外部スクリプトを動的に読み込む場合、普通はCORSの問題が発生する。<br /> 素直にscriptタグで読み込むように変更した。</p> <p>というか、なぜChromeでは読み込めたのか分からない。<br /> 同じGoogle製品だから?<br /> プラットフォーマー恐るべし。</p> <h3 id="クロスサイトトラッキング"><a href="#%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0">クロスサイトトラッキング</a></h3> <p>さて、初期表示までは問題なくSafariで動作した。<br /> しかしGoogleへログインしても認証状態を検知しない。<br /> プログラムが「ログイン中」と判断しないのだ。<br /> こればっかりは思い当たる節も無い。</p> <p>issueが上がっていた。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/google/google-api-javascript-client/issues/503">gapi auth2 issue on safari</a><br /> 同様の事象、かつオープンのままだ。<br /> Safariの場合、Cookieを全削除するか、プライベートブラウズなら問題無いらしい。<br /> マジかよ・・・<br /> 頑張れば何か対策できるのかもしれないが、負け犬にそんな根性は無い。<br /> UserAgentから「Safari、もしくはiPhone/iPad」を判定して、該当する場合は長ったらしい注意文言を表示するようにした。</p> <h1 id="敗因"><a href="#%E6%95%97%E5%9B%A0">敗因</a></h1> <p>次回以降のweb1weekで勝つための敗因分析を行う。</p> <h2 id="PoC(概念実証)不足"><a href="#PoC%EF%BC%88%E6%A6%82%E5%BF%B5%E5%AE%9F%E8%A8%BC%EF%BC%89%E4%B8%8D%E8%B6%B3">PoC(概念実証)不足</a></h2> <p>一番の原因はこれ。<br /> 事前に「画像の削除/更新は出来ない」と知っていれば別案を採用していた。<br /> とはいえ、1週間という過密スケジュールで検証工程を取れるかは疑問が残る。<br /> 少なくとも、</p> <ol> <li>機能の洗い出し</li> <li>実装に必要な外部インターフェースのドキュメントはちゃんと読む</li> </ol> <p>まぁ、、当たり前のことはちゃんとしよう。ということ。</p> <h2 id="「出来ない」の判断が遅い"><a href="#%E3%80%8C%E5%87%BA%E6%9D%A5%E3%81%AA%E3%81%84%E3%80%8D%E3%81%AE%E5%88%A4%E6%96%AD%E3%81%8C%E9%81%85%E3%81%84">「出来ない」の判断が遅い</a></h2> <p>認証や画像アップロード等、ハマった際に「出来ない」という判断が出来ていない。<br /> 昨今の潤沢な開発環境に甘え、「出来ない事は無い」と思い込んでいる節がある。<br /> 確かに大抵の場合、ハマる原因は外部インターフェースの仕様変更、もしくはタイポが多い。<br /> 塩梅が難しいが、もっと早いタイミングでissueを探そう。</p> <h1 id="Google Photosを利用した画像アップローダーは作れるのか"><a href="#Google+Photos%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9F%E7%94%BB%E5%83%8F%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%80%E3%83%BC%E3%81%AF%E4%BD%9C%E3%82%8C%E3%82%8B%E3%81%AE%E3%81%8B">Google Photosを利用した画像アップローダーは作れるのか</a></h1> <p>冒頭に記した「裏の目的」について。<br /> そもそもGoogle Photos APIの<a target="_blank" rel="nofollow noopener" href="https://developers.google.com/photos/library/guides/acceptable-use">利用規約</a>には「ホスティングサービスとして使うならCloud Storageを使え」と明記してある。<br /> 開発するサービスを「Google Photosへアップした写真の共有ギャラリー」という位置付けにするならグレーかな?と思ったが、<br /> 結論から言うと「多分、不可」だ。<br /> 少なくとも、そう言うサービスを作れるだけのAPIが提供されていない。</p> <p>まず前述の通り、API経由での画像の削除/更新は出来ない。<br /> あくまで画像のアップロードのみで、アルバム間の移動すら出来ない。</p> <p>そして、更に致命的なことに<strong>静的なURLは取得できない</strong>。<br /> 写真のURLを取得するAPIは存在するが、一定期間が過ぎると無効化されるらしい。<br /> これはGoogle Photosの「URLで共有する」と同じURLなのだろう。<br /> URL先には静的な画像が埋め込まれているらしいが、それを取得するAPIは存在しない。<br /> スクレイピングを駆使すれば取得できるかもしれないが、実用的では無いだろう。</p> <p>「ユーザーのプライベートストレージを用いた画像アップローダー」は別サービスを検討した方が良さそうだ。</p> <h1 id="ゴミを公開する"><a href="#%E3%82%B4%E3%83%9F%E3%82%92%E5%85%AC%E9%96%8B%E3%81%99%E3%82%8B">ゴミを公開する</a></h1> <p>これはweb1weekじゃない。<br /> 私が作りたかったものでもない。<br /> ゴミだ。</p> <p><a target="_blank" rel="nofollow noopener" href="https://nest-board.netlify.com/">Nest Board</a><br /> <a target="_blank" rel="nofollow noopener" href="https://nest-board.netlify.com/">https://nest-board.netlify.com/</a></p> <p>Google APIは申請無しで使用できるが、Googleから承認されていない場合、ログイン時に警告画面が出る。<br /> 利用する際は注意文言を読んで頂きたい。</p> <p>需要あるか分からないが、Nest Hub側で表示する際の手順も明記しておく。</p> <blockquote> <p><strong>1. スマホ(iPhone/Android)でGoogle Homeアプリを開く</strong><br /> Nest Hubの初期設定に必要なアプリなのでインストールされているはず。<br /> <br /> <strong>2. 表示したいNest Hub > フォトフレームを編集 > Google フォト の順で選択</strong></p> <p><strong>3. Nest Boardで作成したアルバムを選択する</strong><br /> 初期値は「Nest Board」<br /> <br /> <strong>4. 「フォトフレーム」画面の下部にある「個人的な写真の整理」を「リアルタイム共有アルバムのみ」にする</strong><br /> これを設定しないとNest Hubがフォトフレームに不向きな写真と判定して表示されない。<br /> ハマりポイント。</p> </blockquote> <p>以上の手順を踏めば、Nest Hubに手書きのメッセージ画像が表示される。</p> <p><a href="https://crieit.now.sh/upload_images/e5d58ba13a25c14fac70e13548eff8d95e770af16b1cc.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e5d58ba13a25c14fac70e13548eff8d95e770af16b1cc.jpg?mw=700" alt="敗北者じゃけぇ" /></a></p> <p>普段はソースコードを公開したりしないのだが、今回はリポジトリをpublicにした。<br /> なぜなら敗者だからだ。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/kin-mi/nest-board">https://github.com/kin-mi/nest-board</a></p> <h1 id="おわり"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A">おわり</a></h1> <p>次は勝つ。</p> きんみ tag:crieit.net,2005:PublicArticle/15587 2019-12-08T00:51:56+09:00 2019-12-12T07:36:34+09:00 https://crieit.net/posts/582e915854052994bfba960a9f0f66a4 キャップ野球向けイベントカレンダー作ってみました。【12/12 update】 <p><a href="https://crieit.now.sh/upload_images/3cebc8b53a7a1a70fcf9eb56cb0dfce15debc00300520.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3cebc8b53a7a1a70fcf9eb56cb0dfce15debc00300520.jpg?mw=700" alt="" /></a></p> <h1 id="キャップ野球向けイベントカレンダー"><a href="#%E3%82%AD%E3%83%A3%E3%83%83%E3%83%97%E9%87%8E%E7%90%83%E5%90%91%E3%81%91%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC">キャップ野球向けイベントカレンダー</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://cap-calendar.netlify.com/">https://cap-calendar.netlify.com/</a></p> <p>最近キャップ野球関係のサービスをいくつか作っているのですが、</p> <ul> <li>キャップ野球・全国大会非公式特設サイト</li> <li>野球リーグスコア管理システム ver3α</li> <li>優勝ラインシミュレーター</li> </ul> <p>まだ普及途上にあるマイナースポーツなので、色々広報手段が足りないようで「<a target="_blank" rel="nofollow noopener" href="https://shogi-sanpo.com/">こんなイベントカレンダー</a>欲しい」という声を聞いたので作ってみました。</p> <h1 id="検討した仕組み"><a href="#%E6%A4%9C%E8%A8%8E%E3%81%97%E3%81%9F%E4%BB%95%E7%B5%84%E3%81%BF">検討した仕組み</a></h1> <p>マイブームというわけではないのですが、個人開発ではDBレスのサービスを作ることが多いです。スキーマの設計が面倒なのと個人でデータを持ちたくないのが....。</p> <h2 id="最近作った主なDBレスサービス"><a href="#%E6%9C%80%E8%BF%91%E4%BD%9C%E3%81%A3%E3%81%9F%E4%B8%BB%E3%81%AADB%E3%83%AC%E3%82%B9%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9">最近作った主なDBレスサービス</a></h2> <ul> <li>キャップ野球・全国大会非公式特設サイト</li> <li>ドラフト風画像作成サービス「ドラフトなう!」</li> <li>アニメランキング作成サービス「Annict Access 3」</li> <li>音楽ランキングメーカー</li> <li>優勝ラインシミュレーター</li> <li>ボウリング幹事アプリ「bowling party manager」</li> </ul> <h2 id="timetree"><a href="#timetree">timetree</a></h2> <p>カレンダー共有ということで一番最初に考慮したのがtimetree。<br /> ただし、<a target="_blank" rel="nofollow noopener" href="https://developers.timetreeapp.com/ja/docs/api">外部公開されているAPI</a>の中にカレンダーに登録されているイベントの一覧を取得するAPIがないことがわかり(19/12/7時点)、採用を断念しました。</p> <h2 id="google calendar API"><a href="#google+calendar+API">google calendar API</a></h2> <p>定番のカレンダー。ただし、現在公開されているv3 APIはOAuth必須となっている。極力ユーザに余計なアクションをさせたくなかったので選択肢から外しました。</p> <h2 id="google public calendar"><a href="#google+public+calendar">google public calendar</a></h2> <p>googleカレンダーには誰でも閲覧できる「公開カレンダー」があり、<br /> その仕組みを調べていたところ、OAuthなしでデータを取得できるという結論に達しました。</p> <h1 id="イベントの種類タグ"><a href="#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%81%AE%E7%A8%AE%E9%A1%9E%E3%82%BF%E3%82%B0">イベントの種類タグ</a></h1> <p>イベントの種類を分類するために、イベントのタイトルにタグをつけてもらう方式にしました。<br /> 例:<code>【イベントの種類】【イベントの場所】タイトル</code></p> <h2 id="強敵、正規表現...."><a href="#%E5%BC%B7%E6%95%B5%E3%80%81%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE....">強敵、正規表現....</a></h2> <p>エンジニアとして避けては通れない道なのはわかっているのですが、1つめの【】の中は取得できるものの、繰り返しの取得ができず、</p> <p>(ノ`Д)ノ彡 ┻━┻ ←こんな感じになりました</p> <p>結局文字列を再帰的に検索するという力業で解決しました。<br /> 文字列処理はHSP時代に腐るほどやったんや....</p> <pre><code class="javascript">findTag=(str,tags)=>{ console.log(tags) let beginIdx = str.indexOf('【'); let endIdx = str.indexOf('】'); if(beginIdx !== -1 && endIdx !== -1){ let tagStr = str.substring(beginIdx + 1,endIdx); tags.push(tagStr); this.findTag(str.substring(endIdx + 1,str.length),tags); return tags; }else{ return tags; } } </code></pre> <p>ロースキルでごめんなさい。</p> <h1 id="採用フォント"><a href="#%E6%8E%A1%E7%94%A8%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88">採用フォント</a></h1> <p><a href="https://crieit.now.sh/upload_images/6845d2efb6214962dccd0f411720e2585debc2bd16cfe.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6845d2efb6214962dccd0f411720e2585debc2bd16cfe.png?mw=700" alt="ELLZKugUwAAB9xs.png" /></a></p> <p>普段Noto Sans JPを好んで使っているのですが、Noto Sans JPのままでは若干固いイメージだったので、<code>M PLUS Rounded 1c</code>を使っています。</p> <h1 id="動作イメージ"><a href="#%E5%8B%95%E4%BD%9C%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8">動作イメージ</a></h1> <div class="iframe-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/6xYVAQPyqwY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div> <h1 id="未実装の機能"><a href="#%E6%9C%AA%E5%AE%9F%E8%A3%85%E3%81%AE%E6%A9%9F%E8%83%BD">未実装の機能</a></h1> <ul> <li>更新日時順表示</li> </ul> ckoshien tag:crieit.net,2005:PublicArticle/15394 2019-09-13T18:30:51+09:00 2019-09-13T18:30:51+09:00 https://crieit.net/posts/NOW-Google-API-GitHub NOWでデプロイできるGoogleフォームみたいに追記できるAPIを作ったので、GitHubに公開してみた <p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/sheets/api/">Spread Sheet API</a>を見てたら、Googleフォームみたいに空いている行に追記できるらしい...<br /> 便利そうなので、汎用的に使えるようにNOW APIとしてデプロイできるようにしてみた&GitHubにも公開してみた。</p> <h3 id="作ったもの"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE">作ったもの</a></h3> <p>GitHub: <a target="_blank" rel="nofollow noopener" href="https://github.com/memory-lovers/append-row-api_zeit-now">Append Row API using ZEINT NOW and Spread Sheet API</a></p> <p>こんな感じで、API叩くとスプレッドシートに追記できる。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/b3e01a00-c186-30fa-92ca-a2450240c40f.gif" width="600" /></p> <h3 id="使い方: デプロイする"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9%3A+%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%99%E3%82%8B">使い方: デプロイする</a></h3> <h4 id="1. git clone"><a href="#1.+git+clone">1. git clone</a></h4> <p>まずはgit clone</p> <pre><code class="console">$ git clone https://github.com/memory-lovers/append-row-api_zeit-now.git </code></pre> <h4 id="2. サービスアカウントのキーファイルの配置"><a href="#2.+%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E3%82%AD%E3%83%BC%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E9%85%8D%E7%BD%AE">2. サービスアカウントのキーファイルの配置</a></h4> <p><code>credential.json</code>というファイル名で、認証情報のキーファイルを配置</p> <h4 id="3. 追記したいスプレッドシートの権限設定"><a href="#3.+%E8%BF%BD%E8%A8%98%E3%81%97%E3%81%9F%E3%81%84%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E3%82%B7%E3%83%BC%E3%83%88%E3%81%AE%E6%A8%A9%E9%99%90%E8%A8%AD%E5%AE%9A">3. 追記したいスプレッドシートの権限設定</a></h4> <p>そのままだとサービスアカウントに書き込み権限がないためエラーに...<br /> そのため、追記したいスプレッドシートの共有権限にサービスアカウントを追加が必要。</p> <p>サービスアカウントの作成やスプレッドシートの共有設定は、<br /> 以下の記事がわかりやすかった...(<em>´ω`</em>)<br /> 「<a target="_blank" rel="nofollow noopener" href="https://techblog.lclco.com/entry/2018/11/30/120000">Node.jsでGoogleスプレッドシートを操作する - LCL Engineers' Blog</a>」</p> <h4 id="3. ローカルで試す"><a href="#3.+%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%81%A7%E8%A9%A6%E3%81%99">3. ローカルで試す</a></h4> <p><code>now dev</code>コマンドでローカルで動かすことができます。<br /> 実行すると<code>http://localhost:5001</code>で起動します。</p> <pre><code class="console">$ now dev -p 5001 // or $ npm run dev </code></pre> <h5 id="nowコマンドやアカウントがない場合..."><a href="#now%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%82%84%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%8C%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88...">nowコマンドやアカウントがない場合...</a></h5> <p><a target="_blank" rel="nofollow noopener" href="https://zeit.co">こちらのZEITのページ</a>からログイン&アカウント作成!</p> <p><a target="_blank" rel="nofollow noopener" href="https://zeit.co/docs/">公式ドキュメント</a>にあるように、インストールとログイン!</p> <pre><code class="console"># nowコマンドのインストール $ npm i -g now # CLIでのログイン $ now login </code></pre> <h4 id="3. ZEIT now にデプロイ"><a href="#3.+ZEIT+now+%E3%81%AB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4">3. ZEIT now にデプロイ</a></h4> <p><code>now</code>コマンドでデプロイできます。<br /> プロジェクト名は、<code>now.json</code>の<code>name</code>に書いてある<strong>append-api</strong>になります。</p> <pre><code class="console">$ now // or $ npm run deploy </code></pre> <h3 id="使い方: API の呼び出し"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9%3A+API+%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97">使い方: API の呼び出し</a></h3> <p>デプロイした API は、以下のパラメタを受け取ります</p> <ol> <li>追記するシートの ID: <code>spreadsheetId</code></li> <li>追記する内容の配列: <code>values</code></li> </ol> <p>curl で呼び出すサンプルは以下のとおりです。<br /> URL には、<code>https://append-api.memory-lovers.now.sh</code><br /> のようなデプロイした URL を設定。</p> <p>ローカルで起動した場合は、<code>https://localhost:5001</code>を設定。</p> <pre><code class="bash">#!/bin/bash SHEET_ID='YOUR_SHEET_ID' URL='API_URL' curl -i \ -H "Accept: application/json" \ -H "Content-Type:application/json" \ -X POST --data '{ "spreadsheetId": "'$SHEET_ID'", "values": [ ["A", "B", "C"], ["D", "E", "F"] ] }' \ "$URL/append" </code></pre> <hr /> <h3 id="コードはこんな感じ。"><a href="#%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AF%E3%81%93%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98%E3%80%82">コードはこんな感じ。</a></h3> <p>主にExpressに関する処理が多いですが、Google APIsを使うのは、<br /> <code>const doAppend = async (spreadsheetId, values) => {</code>のあたりに集約。</p> <pre><code class="javascript">import bodyParser from "body-parser"; import Express from "express"; import { google } from "googleapis"; require("./credential.json"); // サービスアカウントの認証情報 const app = Express(); // POSTのBODYにJSONを使うため、body-parserを有効化 app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); /** * Spread Sheetに行を追加する処理 * @param {String} spreadsheetId シートID * @param {String[][]} values 追記するデータ。2次元配列で指定 */ const doAppend = async (spreadsheetId, values) => { // パラメタのチェック if (!spreadsheetId || !values) throw new Error("Error: Invalid Params"); // Spread Sheet APIを使うための認証処理 const auth = await google.auth.getClient({ scopes: ["https://www.googleapis.com/auth/spreadsheets"] }); const sheets = google.sheets({ version: "v4", auth }); // APIを呼び出して、行の追加処理 const req = { // シートのID spreadsheetId: spreadsheetId, // A1に追記することを指定 range: "A1", // 追記する形式を指定。 valueInputOption: "USER_ENTERED", // A1に値があったら下方向に空欄を探しにいく insertDataOption: "INSERT_ROWS", // 追加する行のデータ。2次元配列で指定 resource: { values: values } }; await sheets.spreadsheets.values.append(req); }; // '/append'にアクセスしたら、doAppend関数を呼ぶようにマッピング app.post("/append", async (req, res) => { try { // パラメタのチェック if (!req.body) throw new Error("Error: Empty Body"); // パラメタの取得 const spreadsheetId = req.body.spreadsheetId || ""; const values = req.body.values || ""; // 追記処理の呼び出し await doAppend(spreadsheetId, values); res.end(); } catch (error) { console.error(`Error in append: ${error}`, error); res.status(500).send({ error: `${error}` }); } }); export default app; </code></pre> <h5>はまったところ...<code>credential.json</code>を認識しない...</h5> <p>now.jsonに環境変数<code>GOOGLE_APPLICATION_CREDENTIALS</code>を設定して、<br /> 読み込むファイルを指定していたけど、エラーが...</p> <pre><code class="json">"env": { "GOOGLE_APPLICATION_CREDENTIALS": "./credential.json" } </code></pre> <blockquote> <p>Error in append: Error: The file at ./credential.json does not exist, or it is not a file.</p> </blockquote> <p>デプロイされたフォルダを見てみると、<code>credential.json</code>が配置されていない...</p> <p>いろいろ調べてみたところ、ビルドをするので関連のないファイルは配置されないっぽい...<br /> なので、<code>index.js</code>の冒頭に以下を追加して、読み込むように変更してみたところ、<br /> うまく認識されるようになったヽ(=´▽`=)ノ</p> <pre><code class="javascript">require("./credential.json"); // サービスアカウントの認証情報 </code></pre> <h3 id="活用事例1: 問い合わせ管理"><a href="#%E6%B4%BB%E7%94%A8%E4%BA%8B%E4%BE%8B1%3A+%E5%95%8F%E3%81%84%E5%90%88%E3%82%8F%E3%81%9B%E7%AE%A1%E7%90%86">活用事例1: 問い合わせ管理</a></h3> <p>開発しているWebサービスの問い合わせ効率化のために利用(<em>´ω`</em>)</p> <p>Nuxtに用意したフォームで受けた内容をスプレッドシートに転記して管理できるように♪</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">書籍のリクエスト、できてきた(*´ω`*)ピタゴラスイッチ的にFirestoreトリガーで、Slackに通知とSpread Sheetに追記ヽ(=´▽`=)ノ<br><br>これで、だいぶ楽になるはず(*´ω`*)♪ <a target="_blank" rel="nofollow noopener" href="https://t.co/9SIY2T23u3">pic.twitter.com/9SIY2T23u3</a></p>— 積読ハウマッチ📚きらぷか (@kira_puka) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka/status/1172029880680079360?ref_src=twsrc%5Etfw">September 12, 2019</a></blockquote> <p>以前、<a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/d372cf8ce1c5c98360bd">書いた記事</a>のSlackAPIも使い、<br /> 通知と管理を一度にできるようになりましたヽ(=´▽`=)ノ</p> <h3 id="活用事例2: 統計情報の収集"><a href="#%E6%B4%BB%E7%94%A8%E4%BA%8B%E4%BE%8B2%3A+%E7%B5%B1%E8%A8%88%E6%83%85%E5%A0%B1%E3%81%AE%E5%8F%8E%E9%9B%86">活用事例2: 統計情報の収集</a></h3> <p>定期的にユーザ数やデータ数などを集計してスプレッドシートに追記できるように!<br /> スプレッドシートに追記するとグラフを出せるようになるのですてき(<em>´ω`</em>)</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">SpreadSheetAPIとなかよくなれたのでとりあえず、取ってる統計データを入れてみた(*´ω`*)ここ1週間で、1,300万円分が積まれ、総額2,000万円近く、登録されてた( ゚д゚)!強者達が...集まってきている...((((;゚Д゚))))ガクガクブルブル<a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/%E7%A9%8D%E8%AA%AD%E3%83%8F%E3%82%A6%E3%83%9E%E3%83%83%E3%83%81?src=hash&ref_src=twsrc%5Etfw">#積読ハウマッチ</a> <a target="_blank" rel="nofollow noopener" href="https://t.co/esCqTrQmLK">pic.twitter.com/esCqTrQmLK</a></p>— 積読ハウマッチ📚きらぷか (@kira_puka) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka/status/1171674794653188096?ref_src=twsrc%5Etfw">September 11, 2019</a></blockquote> <p>個人開発なので常に稼働が足りないですが、<br /> こういった裏方作業的なのも、もっと効率化していけるようになりたい...!!</p> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%EF%BC%81%EF%BC%81">こんなのつくってます!!</a></h2> <p>最近、積読用の読書管理アプリ「積読ハウマッチ」をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで</p> <h1 id="参考にしたサイト様"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E6%A7%98">参考にしたサイト様</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/castaneai/items/a53d0b89bca5b84654be">スプレッドシート API で行を追加する - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/howdy39/items/ca719537bba676dce1cf">Googleスプレッドシートをプログラムから操作 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/suisuina/items/a41932088acacea4835e">Google spreadsheetの値を取得する - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/docs/authentication/api-keys?rd=1&visit_id=637037608757242156-1685701782">API キーの使用  |  認証  |  Google Cloud</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/googleapis/google-api-nodejs-client#using-api-keys">googleapis/google-api-nodejs-client: Google's officially supported Node.js client library for accessing Google APIs. Support for authorization and authentication with OAuth 2.0, API Keys and JWT (Service Tokens) is included.</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://dev.classmethod.jp/server-side/node-js-server-side/using-google-apis-node-js-client/">Google APIs Node.js Client を使って Google Analytics のページビューを取得する | DevelopersIO</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/zeit/now/issues/749">Storing complex secrets · Issue #749 · zeit/now</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://spectrum.chat/zeit/now/storing-authentication-credentials-file~aee6b468-9ea0-4f2d-9b7a-f07831df2fd9">Storing authentication credentials file · ZEIT</a></li> <li><a href="https://crieit.net/posts/Zeit-Now">ZeitのNowにデプロイするファイルを指定する時の注意 - Crieit</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://blog.hokuma.net/serverless/deploy_to_now/">Nowを使って静的サイトとAPIを単一レポジトリで運用する</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/miminashi/items/f48cd4c25b06fcab5ea3">curlチートシート - Qiita</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど