Google Photosの力を借りてブログをvscodeで書くときの写真の取り扱いを楽にする拡張書いたけど先行きが超不透明です！

# Visual Studio Code Advent Calendar 2020

この記事はVisual Studio Code Advent Calendar 2020 12日目の記事です。
昨日は @akiko-pusu さんの[VisualStudio CodeからMongoDBにつなげてみよう！](https://qiita.com/akiko-pusu/items/ffd6ec8d00a0e6e24b92)でした。SQLの勉強しようと思ったときにほんの少しだけMariaDBにつないだことはあったんですが、また今度トライしよう・・・。


# はじめに

## ブログをvscodeで書く

自分ははてなブログでブログを書いています。はてなブログはMarkdownで書けるのですが、ある程度長文を書くのにブラウザ上の貧弱なエディタで記述するのはストレスが溜まってきます。

そこでVSCodeでブログを書くか、となるのはまあ割と普通の発想だと思います。[Markdown All in One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one)という優れた拡張もありますしね。

## ブログと画像

ブログに欠かせないのが画像です。特に登山の記録なんかを書くときは写真を多用します。自分は写真の置き場としてはてなブログを始めるより前、FC2ブログで書いてた時代からPicasa(今はGoogle Photos)を使っていました。

![Picasa](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F94177%2F119ae099-6a57-6531-ef6b-929d7a18f914.png?ixlib=rb-1.2.2&auto=format&gif-q=60&q=75&w=1400&fit=max&s=44ecae9c97f9e5072c5f3eb8e1819e84)

Picasaの時代はそういう使い方が想定されていてWebUIから写真の静的URLをかんたんに発行できました。

Picasaに写真をおいていたおかげでFC2からはてなにブログを移したときも簡単に移行できたという経緯があるのでブログサービスへの依存を下げるためにも画像はブログサービスには置いときたくないわけです。

はてなブログでは画像をはてなブログ自身にアップロードする他に、Google Photosから写真を貼り付けるシステムがかつて存在していました([現在この機能は一時的に封鎖されています](https://staff.hatenablog.com/entry/2020/06/10/155523))。Picasaが終了してGoogle Photosに移行したとき、Google PhotosのUIにはURL発行ボタンが消えてしまいましたのではてなのこの機能を使っていました。

しかしながら、登山の記録のような大量の写真を貼り付けるようなときにGUIを操作するのは不便であること、この機能を使ったときに画像がHTML形式で書かれており、Markdownとして記述されないことなどの不満がありました。

# vscodeの拡張を作ろう

というわけで、そういう拡張機能を作ろうと考えました。

必要となる機能は

1. Markdownをパースして`![alt](path/to/image)`のような記法で表される画像記法を探す
2. 画像ファイルをGoogle PhotosにアップロードしてURLを得る
3. 画像パスを置換する
4. Markdownに書き戻す

です。

Markdownの置換部分は別のレポジトリに分けてVSCodeの拡張としてはそれを呼び出すことにし、残りの画像アップロードとVSCode上でのUI注力する方針を定めました。

MarkdownのパースがちょっとだるそうだけどまああとはGoogle PhotosのAPI叩いておしまいだしそこまで手間かからずに作れるでしょ。

この言葉が盛大なフラグになるとは思いもよらず、開発は始まったのでした。2019年3月28日のことでした。

# vscodeからOAuth2する

Google Photos APIを叩くわけなので当然OAuth2するわけです。昔過ぎて何を参考にしたかは忘れましたが、1日位で概形ができあがって、あとは叩くAPIが増えるごとに手直ししていきました。

最初はブラウザを立ち上げて認可画面から認可コードをもらって、それをコピペしてもらってvscode側でそれを受け取っていましたが、その方法がdeprecatedになってしまったことも有り、vscode拡張内でHTTPサーバーを立てて、そこにリダイレクトしてもらうことで認可コードを受け取るloopback ip方式に後に改修しました。

- [yumetodo/vscode-google-photos-uploader@`fe4192e`](https://github.com/yumetodo/vscode-google-photos-uploader/commit/fe4192eb4d936b579daaadcc3529e50501625424)
- [yumetodo/vscode-google-photos-uploader@`1bc6583`](https://github.com/yumetodo/vscode-google-photos-uploader/commit/1bc6583295e61c7f0e068bf6f932abf95f479ac2)

vscodeの拡張はnpmエコシステムの恩恵をフルに受け取れるのでこういう改修もさくさくできていいですよね。

# markdownのパース

## 自作期

Markdownの置換をする以上、MarkdownをASTに変換して、そのASTをMarkdownに戻せないとだめだと考えました。ASTを作るだけならいくらでもライブラリはあるし、それこそmarkdown-itでも使えばいいわけです。ただ、ASTからMarkdownに戻すという需要が少ないのかさっぱり見つかりませんでした。

致し方なく自作し始めました。CommonMarkのspecのexampleを見ながらパースを始めます。

Markdownのパースは意外と難しくて、特に`code`記法やコードブロック記法が厄介でした。こういった記法の中の`![alt](path/to/image)`のような記法は無視したかったので、正規表現は使えません。

するとすぐにJavaScriptの標準ライブラリでは文字列操作が貧弱すぎるという問題にぶつかりました。C++の`std::string`のAPIを移植するようなことを始めたりと迷走しはじめます。

それでもある程度形になってきた4月末、

    - Foo

      > bar
      >> foo
      > ```
      >> go

のような閉じないコードブロックの対応や、`Cannot read property '0' of undefined`と言われるようなバグが取れずにいました。

## Rustのライブラリ

もう一度原点に立ち返って、目的を達成するライブラリが本当に無いのか探索したところ、Rust製の[pulldown-cmark](https://crates.io/crates/pulldown-cmark)/[pulldown-cmark-to-cmark](https://crates.io/crates/pulldown-cmark-to-cmark)が見つかりました。

これまで書いてたコードはすべて投げ捨てて、これを呼び出す方向に舵を切りました。Rustのライブラリなので当然WASMの出番です。

Rustをwasmとしてコンパイルするエコシステムがよく整備されていたこと、ライブラリの使い方がわからなくてIssueを立てたら親切に回答してもらえたこともあって、10日程度で(実働2日)形になりました。
[[Question] How to get img alt? · Issue #394 · raphlinus/pulldown-cmark](https://github.com/raphlinus/pulldown-cmark/issues/394)

wasmが絡むnpmライブラリのデプロイの自動化については
[rust+npm+wasm-packなrepoをGithub Actionsで自動テスト＆デプロイできるようにしてみた](https://qiita.com/yumetodo/items/7fda6d38bd5182388e48)
にまとめてあります。

2020年夏、某所からpulldown-cmarkには[`pulldown_cmark::Parser::into_offset_iter()`](https://docs.rs/pulldown-cmark/0.7.2/pulldown_cmark/struct.Parser.html#method.into_offset_iter)というAPIがあり、画像リンクのトークン位置を調べられるためたんなる文字列置換に持ち込めるという指摘がなされた。これを2020年11月末に実装した
[yumetodo/markdown_img_url_editor_rust@`fa64dcb`](https://github.com/yumetodo/markdown_img_url_editor_rust/commit/fa64dcbffd566c2df9931be850aa67bef2345a8c)

# Google Photosとの戦争の幕開け

## ブログの写真が消えた！？

時はさかのぼりまだ自前実装のMarkdownパーサーを使っていた頃、2019年6月はじめに最初のリリースを出しました。それを使って早速ブログを書き[^1]、公開しました。ところが数日後ブログを見てみると写真が消えているのです。どういうことでしょうか？

[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/73896c7f-cdf5-3ef8-e949-0af7f7ad54fc.png)](https://qiitadon.com/@yumetodo/102234037558548055)
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/9c3c8885-ded3-5a18-27f6-3a69d20e79f7.png)](https://qiitadon.com/@yumetodo/102234056576268905)

画像のURLを取ってくるのにGoogle Photos APIの`MediaItem`の中の`baseUrl`というプロパティを参照していました。ところが[Google Photos APIの説明ページ](https://developers.google.com/photos/library/reference/rest/v1/mediaItems#MediaItem)をよく見ると

> A URL to the media item's bytes. This shouldn't be used directly to access the media item.  

という文言が。これだけじゃ意味がよくわかりませんが、ひっそりと貼られているリンク先を見ると

> [Access media items  |  Google Photos APIs  |  Google Developers](https://developers.google.com/photos/library/guides/access-media-items#base-urls)
>
> They are valid for 60 minutes and require additional parameters as they cannot be used as is.

60分しか有効じゃないよーって書いてあります。

開発時は60分を超えて数日間は有効だったので大丈夫と思っていたのですが、たまたまだったようです。そんなん、きぃつけへんって！

結局Google Photos APIでは画像のURLが取れないので、致し方なくウェブスクレイピングするはめになりました。

詳細は
[Google Photosからどうしても永続的な画像リンクを取得したい](https://qiita.com/yumetodo/items/ac77043b28795516be14)
を参照してください。

## Unverified apps

Google のAPIを叩くアプリケーションはGoogleによって承認されていなければなりません。さもなくば次のような画面がユーザーに表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/d3eb11d8-fac5-1dae-861b-50ecc9df915c.png)

2019年8月22日の0時30分頃にGoogleに申請を出しました。実は同日の昼過ぎにはGoogleから承認できないよ―っていうメールが帰ってきていたのですが、メールを見落とし続け、2ヶ月後の10月21日、ようやくメールに気が付きます。

さてここからは[承認取得バトル](https://github.com/yumetodo/vscode-google-photos-uploader/issues/3)の開始です。

基本的には

- https://support.google.com/cloud/answer/7454865
- https://support.google.com/cloud/answer/6158849#public-and-internal
- https://developers.google.com/identity/protocols/OAuth2InstalledApp

に書いてあることをやればいいわけですが、書いてないことで自分が指摘されたことは

- アプリケーションはWebページを持たなければならない
    - そのドメインを所有していることをGoogle Search Consoleを通じて紐付けなければならない=DNSにテキストレコードを設定する必要がある
    - WebページはHTTPSで接続できなければならない
    - **urlに`google`という文字列を含んではいけない**
- **英語でアプリケーションの紹介動画(OAuth2による認可を出すと何が起こるかわかるもの)を作成しYouTubeに投稿しなければならない**

最後2つは想定外やで・・・。

DNSいじる必要があるということで、ドメインを取らないとだめってことですから、課金なしではGoogleのAPIは叩けないってことですね！まじか。

# 出来上がったもの

[Google Photos Uploader(unofficial) - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=yumetodo.google-photos-uploader)

![working.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/c62ac984-01f2-6c7b-d953-503dad1c166f.gif)


紆余曲折の結果次のような処理を行います。

1. Markdownをパースして`![alt](path/to/image)`のような記法で表される画像記法を探す
2. 画像ファイルをGoogle Photosにアップロードする
3. アップロードした画像を共有アルバムに追加する
4. 共有アルバムをスクレイピングして画像のURLを取得する
5. 画像をDLしたものと手元にあるファイルのHashを取り同一性を検証する
6. 画像パスを置換する
7. Markdownに書き戻す

# 先行きが超不透明です！

[Google Photosからどうしても永続的な画像リンクを取得したい](https://qiita.com/yumetodo/items/ac77043b28795516be14)
にも書いたのですが、今回用いた方法で得られる画像のURLの永続性については疑問符が残ります。
すでにPiacasa Web時代からのURLは一部がログインを求められるように予告なく動作が変更されるなど、どうやら終わりの始まりといった様相を呈しています。Googleとしては静的画像ホスティングサイトとして使われたのでは、マネタイズが難しいということなんでしょうか。

Google Cloud Storageなどの有料サービスへの移行を視野に入れつつ検討しようと思ってはいます。~~単純にめんｄ~~

せっかくVSCode拡張製作に初トライしたのに先行きが超不透明でなんだかなぁ。

[^1]: 私の父が