sironekotoroの日記

Perl で楽をしたい

Mojolicious::Lite で Google Drive API のアクセストークンを表示する

そろそろ前に進まないとと思いつつ

ここしばらく、Google の提供する API を扱うためのことやってきたんですが、やりつつ一つの疑念がありました。

途中で Web にアクセスして URL を貼り付けるところ、そのまま HTTP の GET で良いのでは?

いちいちブラウザに貼り付けなくても良さそうな・・・?

というか、最初からブラウザで動かしたら?

それって、 Perl 入学式 でやった Mojolicious::Lite で実装できそう・・・と言うことで実装してみました。

下準備としては、Google Compute Platform の API 設定の OAuth 同意画面で、アプリケーションのホームページを http://localhost:3000 にしておくことです。

言わずと知れた Mojolicious を morbo で起動した時の URL とデフォルトポートですね。

f:id:sironekotoro:20201104192003p:plain

出来上がったもの

苦労したところ

Google の認証画面に遷移するところまでは割とすんなり行きました。

問題はそっから ACCESS_TOKEN と REFRESH_TOKEN つくるところで詰まりました。

結局、 Net::Google::OAuth のソースを参考にしつつ作ることに・・・

おかげで、メールアドレスやリダイレクトのURLをURLエスケープするとか、HTTP::Tiny の POST つかって JSON で送るとかを読み取ることができました。

コード

Google Drive API での利用を想定して SCOPE のところがそうなってます。

http://localhost:3000 -> (POST) -> Google の認証画面 -> (GET) -> http://localhost:3000/code という感じで遷移します。

URL の組み立てのところが雑だなぁ・・・と思うんですが、まぁそこはそれってことで。

#!/usr/bin/env perl
use Mojolicious::Lite;
use HTTP::Tiny;
use JSON;
use URI::Escape;
use URI;

my ( $CLIENT_ID, $CLIENT_SECRET, );

get '/' => sub {
    my $c = shift;

    $c->render( template => 'index' );
};

get '/code' => sub {
    my $c = shift;

    my $code = $c->param('code');

    # Exchange code to token
    my $param = {
        'client_id'     => $CLIENT_ID,
        'client_secret' => $CLIENT_SECRET,
        'redirect_uri'  => 'http://localhost:3000/code/',
        'code'          => $code,
        'grant_type'    => 'authorization_code',
        'access_type'   => 'offline',
    };

    my $json = encode_json($param);

    my $ht       = HTTP::Tiny->new();
    my $response = $ht->request(
        'POST',
        'https://accounts.google.com/o/oauth2/token',
        {
            headers =>
                { 'Content-Type' => 'application/json; charset=utf-8', },
            content => $json,
        },
    );

    $json = decode_json( $response->{content} );

    $c->stash(
        access_token  => $json->{access_token},
        refresh_token => $json->{refresh_token}
    );

    $c->render( template => 'code' );
};

post '/post' =>
    sub {
    my $c = shift;
    $CLIENT_ID     = $c->param('CLIENT_ID');
    $CLIENT_SECRET = $c->param('CLIENT_SECRET');
    my $MAIL  = $c->param('MAIL');
    my $SCOPE = $c->param('SCOPE');

    my $redirect_url = uri_escape('http://localhost:3000/code/');
    my $login_hint   = uri_escape($MAIL);
    my $scope        = uri_escape($SCOPE);

    # url組み立て
    my $url
        = "https://accounts.google.com/o/oauth2/v2/auth?client_id=$CLIENT_ID&redirect_uri=$redirect_url&scope=$scope&response_type=code&approval_prompt=force&access_type=offline";

    $c->redirect_to($url);

    };

app->start;
__DATA__

@@ index.html.ep
% layout 'default';
% title 'Welcome';
<h1>Welcome to the Mojolicious real-time web framework!</h1>

<form action="/post" method="post">
    <ul>
        <li>CLIENT_ID: <input name="CLIENT_ID" size=80 type="text" value=""></li>
        <li>CLIENT_SECRET: <input name="CLIENT_SECRET" size=80 type="text" value=""></li>
        <li>MAIL: <input name="MAIL" size=20 type="text" value=""></li>
        <li>SCOPE: <input name="SCOPE" size=40 type="text" value="https://www.googleapis.com/auth/drive"></li>
    </ul>
    <input type="submit" value="POSTで投稿する">
</form>


@@ code.html.ep
% layout 'default';
% title 'Welcome';
<h1>Welcome to the Mojolicious real-time web framework!</h1>

<ul>
    <% if ( stash('access_token') )  {%>
        <li>ACCESS TOKEN:<%= $access_token %></li>
    <% } %></textbox>

    <% if ( stash('refresh_token') )  {%>
        <li>REFRESH TOKEN:<%= $refresh_token %></li>
    <% } %>
</ul>

<a href="http://localhost:3000">トップページ</a>

@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
  <head><title><%= title %></title></head>
  <body><%= content %></body>
</html>

CircleCI で Perl のテストをローカルでやってみた

環境構築から

この本を参考にしました。

gihyo.jp

Perl の例は掲載されていないですが、他の言語での例は豊富です。

なので、そっから応用していけるのではないか、していきたい!という気持ち。

$ brew install circleci

バージョン確認

$ brew info circleci
circleci: stable 0.1.11393 (bottled)
(以下略)

あと、Docker for Mac も必要になります。

いつもの Hello, World! の下準備

作業用のディレクトリに CircleCI 用のフォルダを作る

$ mkdir -p .circleci/

設定を記述する YAML ファイルを用意する

$ touch .circleci/config.yml

エディタで YAML ファイルを編集する

実行時に必要になる Perl の入った Docker イメージは好きなのを使ってください。Perl が入っていれば ok です、多分。

ってことで、去年くらいに作った自作の Perl 入りの Docker イメージ sironekotoro/alpine-perl を使います。

version: 2.1

jobs:
  build:
    docker:
      - image: sironekotoro/alpine-perl
    steps:
      - run: perl -e 'print "Hello, World!\n"';

workflows:
  version: 2
  workflow:
    jobs:
      - build

出来上がったファイルが文法上正しいかを確認する

$ circleci config validate
Config file at .circleci/config.yml is valid.

大丈夫だったら実行します。

$ circleci local execute

ログが流れ・・・そして最後にコマンドが実行されて Success! となって終わりです。

(省略)
perl -e 'print "Hello, World!\n"';
Hello, World!
Success!

ちなみに、2020年11月1日より Docker Hub では pull の回数に制限が入ります。念のため、Docker Hub の認証情報を追加しておきます。

support.circleci.com

version: 2.1

references:
  docker_hub_authentication: &docker_hub_authentication
    auth:
      username: DockerHubでのユーザー名
      password: DockerHubでのパスワード
jobs:
  build:
    docker:
      - image: sironekotoro/alpine-perl
        <<: *docker_hub_authentication
    steps:
      - run: perl -e 'print "Hello, World!\n"';

workflows:
  version: 2
  workflow:
    jobs:
      - build

circleci 上で、Perl の コマンドが実行できることがわかりました。素晴らしい!

Hello World! が動くってことは prove -l も動くっしょ

prove -lPerl のテスト時に使われるコマンドですが、もちろん動きます。

実際のモジュールで試してみます。これも自作の雑モジュールです。こういうとき助かるー!

$ git clone git@github.com:sironekotoro/Acme-MetaVar.git
$ cd Acme-MetaVar
$ mkdir -p .circleci/
$ touch touch .circleci/config.yml

そしてお好みのエディタで .circleci/config.yml 編集します。

先の例とは、 steps セクションが異なっています。

version: 2.1

references:
  docker_hub_authentication: &docker_hub_authentication
    auth:
      username: DockerHubでのユーザー名
      password: DockerHubでのパスワード
jobs:
  build:
    docker:
      - image: sironekotoro/alpine-perl
        <<: *docker_hub_authentication
    steps:
      - checkout
      - run: prove -l;

workflows:
  version: 2
  workflow:
    jobs:
      - build

編集が終わったらローカルで実行です。

$ circleci local execute
(中略)
====>> prove -l;
prove -l;
t/00_compile.t .. ok
t/01_hoge.t ..... ok
t/02_fuga.t ..... ok
All tests successful.
Files=3, Tests=3,  0 wallclock secs ( 0.04 usr  0.02 sys +  0.29 cusr  0.08 csys =  0.43 CPU)
Result: PASS
Success!

という感じで、CircleCI に入門したのでした。

なぜ CircleCI やろうと?

Perl のモジュールオーサリングツール Minilla で モジュールを作ると CI ツールである Travis への連携が簡単にできます。

そのテスト結果が出るのが思ったより楽しかったので、他の CI ツールはどうなんだろう?という好奇心から。

最終的にはこういうのをやりたいんですよねー

  1. GitHub 上にあるリポジトリ から持ってくる

  2. Makefile とかで色々やる

  3. 色々やった後の結果で、 diff があるようだったら自動で P/R 作成

  4. これを毎日定時実行

本を読むとできそうなのですが、うちが作れるかどうかはまた別の話・・・

Perl から Google Drive にファイルを「上書きっぽい感じ」でアップロードする

ここまで( 7 時間くらい)のあらすじ

  1. 前回の Google Drive の続きをやろう。Google Drive の特定のフォルダの配下にあるファイルを上書きアップロードしよう。

    • Google Drive は同名ファイルをフォルダ内に作成できる。
    • このため、同じファイルを続けてアップロードしても上書きにならず、同名ファイルが増えて行く。
    • f:id:sironekotoro:20201101161845p:plain
  2. 前回も使った Net::Google::Drive だと、ファイルリスト取得時にクエリーを差し挟めず、「特定のフォルダの配下にあるファイルのみ」という条件で取れない

    • 全ファイル、または名前で検索して、そのファイルの親ID(フォルダID)と照合することで特定は可能。
    • ただし、検索で引っかかったファイル数だけAPIを発行することになり遅い
  3. 他にある Perl から Google Drive を扱うモジュールを試すものの、うまくいかなくて泣く

    • Net::Google::Drive::Simple(0.18):Crypt::SSLeay のインストールがどうやってもダメ

    • WWW::Google::Drive(0.05):Google のサービスアカウント作って new してもエラーは出ないものの、SYNOPSIS 通りにメソッド呼んでも空の応答しか返ってこない

    • Google::RestApi(0.4):どうやって Google の認証情報渡せばいいかわからない〜、YAML で渡す時の key とか

  4. 諦めて Net::Google::Drive 使うか・・・うわぁ!uploadFile メソッドで file_id 指定できない!上書きできない!

    1. 名前で検索して、特定の親IDを持つファイルをリストアップ
    2. そのファイルを削除
    3. 新しいファイルをアップロード

    というのをやってみたコードが以下です。まぁ、動きだけ見れば上書きっぽい。

    終わった後だと上書きと見分けがつかない・・・いや、 file_id 変わってるか・・・

#!/usr/bin/env perl
use strict;
use warnings;

use feature qw/say/;
use Data::Dumper;
binmode STDIN,  ":utf8";
binmode STDOUT, ":utf8";

use Carp;

use Net::Google::Drive;

my $CLIENT_ID    = " ";
my $CLIENT_SECRET = " ";
my $ACCESS_TOKEN = " ";
my $REFRESH_TOKEN  = " ";

my $disk = Net::Google::Drive->new(
    -client_id     => $CLIENT_ID,
    -client_secret => $CLIENT_SECRET,
    -access_token  => $ACCESS_TOKEN,
    -refresh_token => $REFRESH_TOKEN,
);

# 更新(削除->アップロード)対象のファイル名
my $write_up_filename = 'test.txt';

# アップロード先のフォルダID
my $folder_id = '12ZOW6qR0BxDeksOuCwnWA85w4KgTh5ey';

my $files = $disk->searchFileByName( -filename => $write_up_filename, )
    or croak "File '$write_up_filename' not found";

# 特定のフォルダIDを親にしている key があり
# 特定のフォルダIDを親にもち
# ゴミ箱の中にないファイルのみを対象にする
my @metadatas = ();
for my $file ( @{$files} ) {
    my $metadata = $disk->getFileMetadata( -file_id => $file->{id}, );

    if (   exists $metadata->{parents}[0]{id}
        && $metadata->{parents}[0]{id} eq $folder_id
        && $metadata->{labels}->{trashed} == 0 )
    {
        push @metadatas, $metadata;
    }
}

# ファイル削除
for my $metadata (@metadatas) {
    $disk->deleteFile( -file_id => $metadata->{id} );
}

# ファイルアップロード
# -dest_file :アップロードしたいファイル(のパス)
# -parents   :Google Drive上のフォルダの下に格納するかを指定
my $res = $disk->uploadFile(
    -source_file => $write_up_filename,
    -parents     => [$folder_id]
    ,    # 配列リファレンスの中に指定すること
);

なお、アップロードはファイルのみ。フォルダをアップロードすることはできませんでした。

ううーん。まぁ、場合によっては使えるか・・・

追記

Net::Google::Drive::Simple なんですが、GitHub に meta::cpan よりマイナーバージョンが1上がったやつ(0.19)を発見。

これは勝てる!って思ったけどやっぱ Crypt::SSLeay への依存は変わらずでインストールできませんでした。

しょんぼり。

github.com

Lenovo 製 M75q-1 Tiny の USB Recovery Creator で大変だった(追記あり)

まとめ:Lenovo 製 M75q-1 Tiny の USB Recovery Creator でメディア作成に失敗し続ける人へ

  • 購入した Lenovo 以外の、他の Windows 機で USB Recovery Creator を動かしてメディアを作ってみたら成功した。

  • BUFFALO 製 USB メモリでは USB からのブートに失敗。Trancend 製 USB メモリからはブートが成功。換装した SSDリカバリすることができた。

n = 1 、つまり自分の経験のみですが参考になれば。

Lenovo M75q-1 Tiny 買いました

比較対象はMacBook Pro の13インチです。なぜ大事なそれを書き忘れるかな・・・

現在利用している Windows マシンは富士通製の FMV-ESPRIMO D582 という機種です。

www.fmworld.net

2016年に中古で買ったものです。なんでこれかというと、当時の会社で使っていたPCと同じ機種だったからです。

会社PCが壊れても、筐体交換でHDD載せ替えだけで動くだろう、と。

今思うと、会社への忠誠心が高めですね。

2.5 インチのマウンターを追加し、SSD を積んでいます。

速度は申し分ないのですが、元が 32bit Windows7 だったため、そろそろ動かないソフトも出てきました。

M75q-1 Tiny

週末になると、価格ドットコムから専用モデルのページへのリンクが出てきます。そこから最小構成にダストカバー、VESAマウントなどを揃えて買いました。

f:id:sironekotoro:20201031155906p:plain

一部ネット界隈で安い!と噂のモデルです。到着した時にはその小ささに驚きました。

最小構成にしたのは、初期搭載メモリとSSDを換装して利用しようと思ったからです。

https://amzn.to/3kKCXaIamzn.to

https://amzn.to/34Jx5ZSamzn.to

メモリの換装はうまく行きました。というか、とても簡単でした。

問題は SSD の換装に必要なリカバリツールの作成、USB Recovery Creator でした。

USB Recovery Creatorリカバリーメディアを作るまでの長い旅

ダウンロードが 99% で止まる

SSD を載せ替えるためには、USB Recovery Creator なるツールを利用して、USB メモリをリカバリーメディアにする必要があります。

support.lenovo.com

この時点で推奨とされる 16GB 以上の容量をもつ USB メモリがなかったため、追加で BUFFALO の 32GB の USB メモリ(RUF3-K32GA-BK/N)を注文しました。

USB メモリ到着後、家の低速回線で 1 時間かけてダウンロード・・・するも、99 %でダウンロードの進捗が止まります。

これはちゃんと対処法があります。

エラー「メタファイルが見つかりません」または 自分のダウンロードが2時間以上も 99%で停止しています。 これは、通常「.rmf」ファイルが正しくダウンロードされなかったことを意味します。ダウンロードされた場所から拡張子名に「.rmf」を含むファイルを探してください。 1. 拡張子名が「.rmf.prg」の場合「.rmf」に変更してください。それからツールを実行してダウンロードを続行します。 2. 拡張子名が「.rmf」の場合、それを削除してください。それからツールを起動してダウンロードを続行してください。

わかってるんなら自動で削除してくれ・・・って思うものの、できない理由もあるのでしょう。

USB メモリが使えなくなる

さて、この対処(「拡張子名が「.rmf.prg」の場合「.rmf」に変更」)をすると、ダウンロードが完了します。

USB メモリを差し込んで、いざ作成・・・!

「書き込みに失敗しました」

リトライしようとすると、ダウンロードしたファイルの一部が消えるのか、「***がありません」のエラーが出ます。

しかも、この書き込みに失敗した後はUSBメモリのフォーマット等ができなくなります。

手元にある私物の MacBook Air でディスクユーティリティを利用し、 USB メモリを exFAT 形式でフォーマットすることで復活します。

で、USB の差込口を変えつつ試しますがダメです。その都度、MacBook Air で USB メモリを初期化します。

ダウンロードしたファイルが良くないのかも、ってことで再度ダウンロードするも状況変わらず。

この時点で窓から投げ捨てたくなってきますが、ぐっと我慢します。

USB メモリではなく、 USB 接続の HDD にメディアを作れないだろうか?

ダメでした。USB メモリじゃないって弾かれます。

Lenovo に頼らず、Windows 純正のリカバリツールは?

USB メモリに書き込みはできるのですが、USB メモリから起動しませんでした。

USB メモリを交換する

USB メモリから起動しないことで、USB メモリの初期不良を疑い、色々と調べます。

Web で検索していると、 Lenovo と BUFFALO の相性が悪い、的な話を見つけました。

ここで追加投資するのー?と思うものの、800 円くらいで直るなら・・・ということで Transcend の USB メモリを買います。

https://amzn.to/2HI18bHamzn.to

しかし、結果は変わりませんでした。

別な PC で USB Recovery Creator を動かしてみる

この時点で、本体の USB コントローラや差込口の初期不良を疑っていました。

M75q を投げ捨てるのにちょうど良い溶鉱炉を探す途中、この USB Recovery CreatorLenovo 製品でしか動かないのだろうか?と疑問に思いました。

このツールは、 Lenovo 製 PC が動かなくなった時に工場出荷時状態に戻すためにも使えるものです。

Lenovo 製 PC が動かない時のためのツールだから、他の Windows マシンでも動くのでは?

幸い、会計ソフトを利用するために、MacParallels というソフトを入れて Windows10 が起動するようにしています。

その Windows に USB Recovery Creator をインストールして稼働したところ・・・ダウンロードが99%で止まることもなく、BUFFALO 、Transcend どちらの USB メモリにも書き込みが成功しました。

・・・えー、 Lenovo のツールが Lenovo 製マシンの上で動かなくて、Mac の上に仮想化して載ってる Windows マシンでまともに動くってどういう・・・

いざ SSD 換装 して リカバリメディアからブート

ここでやっと SSD を換装します。やや蓋とぶつかる感がありますが、そこはギュッと押し込みます。

まずは、BUFFALO の USB キーを差し込んで USB メモリからの起動を試みます。起動順の設定変更は起動直後から F12 キーです。

・・・はいだめー、認識しません。BUFFALO め・・・

続いて Transcend の USB キーで試したところ、無事 USB からのブートが成功。リカバリを完了させることができました。

長かった

なお、Windows が起動するまでで精神力を使い果たした感があるので、移行はこれからです。

まぁ、ボチボチやっていきますー

追記

みんな大変だったんだね・・・

追記2

リカバリ完了後、HandBreak で動画エンコードして早い早いと遊んでいたのですが、HDD も追加してやっとハードウェアの追加が完了しました。

HDD は容量 2TB 、厚さ 7mm のものを購入しました。装着してみた感想ですが、HDD は薄い方が良さそうですね。

https://amzn.to/37g3aIKamzn.to

これでやっと旧PCからの移行を進められそうです。

追記3

部屋の模様替えに伴い、VESA マウントキットを装着して M75q-1 Tiny をモニタ裏に装着しました。

なかなか良い感じです。

なお、マウントするための枠をモニタ裏にネジで装着し、そこにスライドさせる形で M75q-1 Tiny をスライドさせる感じです。

しっかり固定というわけではないので、モニタを縦横でグルングルン回転させる方は要注意。

f:id:sironekotoro:20210131110815j:plain

Perl から Google Drive にファイルをあげたりダウンロードしたり一覧を取ったり

Google OAuth のクライアントIDとクライアントシークレットを入手する

  1. まずは Google Cloud Platform でプロジェクト作成から。

    もちろん、既存のプロジェクトに Google Drive API の利用権限を与えてもokです。

    f:id:sironekotoro:20201025162317p:plain

  2. プロジェクトの作成が終わったら、左上のナビゲーションメニューから 「APIとサービス」-> 「ライブラリ」と進みます。

    f:id:sironekotoro:20201025184243p:plain

  3. 検索スペースに Google Drive と入力して Google Drive API を選択し、Google Drive API を有効化します。

    f:id:sironekotoro:20201025184402p:plain

  4. 概要のページに戻りましたが、「有効化のステータス」が「有効」になっていますでしょうか?なっていればokです。

    f:id:sironekotoro:20201025184535p:plain

  5. 左側のメニュー(左上には「APIとサービス Google Drive API」とある)から、認証情報を選択します。

    f:id:sironekotoro:20201025184638p:plain

  6. 上にある「認証情報を作成」をクリックし、「OAuth クライアントID」を選択します。

    クライアントIDを作成するためには同意画面の作成が必要、という事で右側(または下側)にある「同意画面を設定」ボタンをクリックします

    f:id:sironekotoro:20201025184824p:plain

  7. OAuth同意画面は前回と同様に「内部」を選択して「作成」ボタンをクリックします。

    f:id:sironekotoro:20201025185003p:plain

  8. サービス名を入力します。わかりやすい名前にしておきましょう。

    f:id:sironekotoro:20201025185205p:plain

  9. 同じ画面の「Google API のスコープ にある「スコープの追加」ボタンをクリックします。

    f:id:sironekotoro:20201025185357p:plain

  10. スコープでは /auth/drive を選択し、右下の「追加」ボタンをクリックします

    f:id:sironekotoro:20201025185725p:plain

  11. 同意画面に戻りますが、Google API のスコープに Google Drive が追加されていればokです。

    画面を下にスクロールして、「保存」ボタンをクリックします。

    f:id:sironekotoro:20201025185836p:plain

  12. 同意画面に戻るので、左側メニューから「認証情報」をクリックし、上にある「認証情報を追加」をクリックし、「OAuth クライアントID」をクリックます。

    アプリケーションの種類は「デスクトップアプリ」を選択し、下にある「作成」ボタンをクリックします。

    f:id:sironekotoro:20201025190016p:plain

  13. OAuthクライアントが作成されました!毎度ながら長い道のりですね・・・

    f:id:sironekotoro:20201025190217p:plain

ACCESS TOKEN と REFRESH TOKEN を手に入れる

前回利用した 3 つのキーを取得するスクリプトを使おうと思ったのですが・・・Google Sheet の時には利用しなかった ACCESS TOKEN が必要になることが判明しました。

sironekotoro.hateblo.jp

ってことで、先のスクリプトを改修しました。

使い方も前回を参考にしてください。

#!/usr/bin/env perl
use strict;
use warnings;

use Net::Google::OAuth;

my $CLIENT_ID = "";
my $CLIENT_SECRET = "";
my $SCOPE         = 'drive';
my $EMAIL         = 'hogehoge@sironekotoro.com';

my $oauth = Net::Google::OAuth->new(
    -client_id     => $CLIENT_ID,
    -client_secret => $CLIENT_SECRET,
);

$oauth->generateAccessToken(
    -scope => $SCOPE,
    -email => $EMAIL,
);

print "This is ACCESS TOKEN:\n";
print "=" x 20 . "\n";
print $oauth->getAccessToken() . "\n";
print "=" x 20 . "\n";

print "This is REFRESH TOKEN:\n";
print "=" x 20 . "\n";
print $oauth->getRefreshToken() . "\n";
print "=" x 20 . "\n";

これで、Google Drive API に必要な 4 つの キーが手に入りました。

  • クライアントID
  • クライアントシークレット
  • アクセストーク
  • リフレッシュトーク

Google Drive API を使う!

やっとこさです。

今回も色々と大変だろうなー、と思っていたんですが、モジュールがとても使いやすく、ほとんど詰まる事なく Google Drive にファイル上げたり下げたりすることができました。

めちゃくちゃ助かりました。すごい!

metacpan.org

ほとんど SYNOPSIS に載っている通りなのですが、うちの試したスクリプトを載せておきます。

ファイルのダウンロード、アップロードについてはコメント外してお試しください。

#!/usr/bin/env perl
use strict;
use warnings;

use feature qw/say/;
use Data::Dumper;
binmode STDIN,  ":utf8";
binmode STDOUT, ":utf8";

use Carp;

use Net::Google::Drive;

my $CLIENT_ID              = "";
my $CLIENT_SECRET    = "";
my $ACCESS_TOKEN    = "";
my $REFRESH_TOKEN  = "";

my $disk = Net::Google::Drive->new(
    -client_id     => $CLIENT_ID,
    -client_secret => $CLIENT_SECRET,
    -access_token  => $ACCESS_TOKEN,
    -refresh_token => $REFRESH_TOKEN,
);

# # ファイル一覧
my $file_name = '*';    # アスタリスクで全部のファイルの情報を取ってくる

my $files = $disk->searchFileByNameContains( -filename => $file_name )
    or croak "File '$file_name' not found";

for my $file ( @{$files} ) {
        say '=' x 20;
        say $file->{id};
        say $file->{name};
        say $file->{mimeType};
        say $file->{kind};
        say '=' x 20;
}

# # ファイルダウンロード
# # -dest_file :保存先(ローカル)でのファイル名
# # -file_id   :ダウンロードしたいGoogle Drive上のコンテンツ
# $disk->downloadFile(
#     -dest_file => './branches.csv',
#     -file_id   => '1juzsinLmryTufEIY53h3nuFPaq_l4Lxn',
#     );


# # ファイルアップロード
# # -dest_file :アップロードしたいファイル(のパス)
# # -parents   :Google Drive上のフォルダの下に格納するかを指定
# # 無指定の場合はトップに保存
# # フォルダIDはフォルダURLの後半のところ
# # https://drive.google.com/drive/folders/{Google Drive上のフォルダID}

# my $res = $disk->uploadFile(
#     -source_file => './test.txt',
#     -parents   => ['hogehogefuga'], # 配列リファレンスの中に指定すること
#     );

# # アップロードしたファイルの file_id などが返ってくる
# say Dumper $res;

この記事は誰向けのものか?

もちろん、自分向けの記事です。大体、2 週間くらい前の自分向けの記事。

Perl から Google Sheet を読み書きする

Perl から Google Sheet を読み書きする・・・ための3種の TOKEN を手に入れる」の続きです。

sironekotoro.hateblo.jp

編集対象の Google Sheets を用意する

API で書き込みを行う Google Sheet を一つ用意します。

用意した Sheet の URL から、以下の場所にあるスプレッドシートの ID を控えておきましょう。

スプレッドシートの ID は URL の中のここです。

例:https://docs.google.com/spreadsheets/d/{スプレッドシートID}/edit#gid=0

テスト用なので、こんな内容にしてみます。

f:id:sironekotoro:20201018145759p:plain

Perl のモジュールを用意する

Perl のモジュールはこちらを利用します。

metacpan.org

息を吸うように $ cpanm Net::Google::Spreadsheets::V4 ・・・すると、関連するモジュールのインストールに失敗してインストールできません。

自分の環境では Net::Google::DataAPI がインストール後のテストに失敗しているようでした。

個別でテストなしでのインストールを行います。-n をつける事でテストなしでのインストールを試みます。

$ cpanm -n Net::Google::DataAPI

無事インストールできたので、再度 $ cpanm Net::Google::Spreadsheets::V4 ・・・インストール完了しました。

ちなみに、インストール後に以下のスクリプトで確認できるのですが・・・

#!/usr/bin/env perl
use strict;
use warnings;

use Net::Google::Spreadsheets::V4;

モジュールを呼び出した「だけ」のスクリプトですが、以下の警告が出ます。

Any::Moose is deprecated. Please use Moo instead

これは先ほどのインストールエラーの原因でもあったのですが、このモジュールが利用しているモジュールの一つである Any::Moose が deprecated 非推奨である旨のメッセージです。

無視をしてもスクリプトは動くので続けます。

Any::Moose が何者であり、なぜ deprecated なのかはこちらを参考

gihyo.jp

blogs.perl.org

Google Sheets を読んでみる(シート自体の情報)

以下のスクリプトを手元の環境で実行します。

#!/usr/bin/env perl
use strict;
use warnings;

use Net::Google::Spreadsheets::V4;

my $CLIENT_ID    = "";
my $CLIENT_SECRET = "";
my $REFRESH_TOKEN    = "";
my $SPREADSHEET_ID = "";

my $gs = Net::Google::Spreadsheets::V4->new(
    client_id      => $CLIENT_ID,
    client_secret  => $CLIENT_SECRET,
    refresh_token  => $REFRESH_TOKEN,
    spreadsheet_id => $SPREADSHEET_ID,
);

 # シートのプロパティを取得
my $sheet = $gs->get_sheet( sheet_id => '0' );

# シートのプロパティを表示
for my $key ( sort keys %{ $sheet->{properties} } ) {
    printf( "%s:%s\n", $key, $sheet->{properties}->{$key} );
}
Any::Moose is deprecated. Please use Moo instead at /Users/sironekotoro/.plenv/versions/5.30.1/lib/perl5/site_perl/5.30.1/Net/Google/DataAPI/Auth/OAuth2.pm line 2.
gridProperties:HASH(0x7fd1f5eb9f18)
index:0
sheetId:0
sheetType:GRID
title:シート1

シートのプロパティであるシートのタイトル名の取得ができたと思います。

Google Sheets を読んでみる(セルの内容の取得)

おまちかね、セルの取得です。

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;

use Net::Google::Spreadsheets::V4;

my $CLIENT_ID      = "";
my $CLIENT_SECRET  = "";
my $REFRESH_TOKEN  = "";
my $SPREADSHEET_ID = "";

my $gs = Net::Google::Spreadsheets::V4->new(
    client_id      => $CLIENT_ID,
    client_secret  => $CLIENT_SECRET,
    refresh_token  => $REFRESH_TOKEN,
    spreadsheet_id => $SPREADSHEET_ID,
);

my ( $content, $res, ) = $gs->request( GET => '?includeGridData=true' );

print Dumper $res;

これでセルの情報を表示できます・・・が、セルの背景色や文字色といったすべての情報が降ってくるので、莫大な量です。

うちの環境では3386行の表示(Dumper)でした。ひゃー

今回はセルの入力内容だけが欲しいので、出力を絞ります。先のスクリプトを1行変更します。

# my ( $content, $res, ) = $gs->request( GET => '?includeGridData=true' );

# fields パラメータで欲しいプロパティだけ書いておく
my ( $content, $res, ) = $gs->request( GET => '?includeGridData=true&fields=sheets.data.rowData.values(formattedValue)' );

これで Dumper での出力が151行程度になりました。ギガに優しいですね。

セルの内容だけ出力するスクリプトはこうなります。

Dumper の結果を頑張ってパースしたんですが、多分どっかに絶対楽な方法があると思うんですよね・・・うちが知らないだけで・・・

#!/usr/bin/env perl
use strict;
use warnings;

use Net::Google::Spreadsheets::V4;

my $CLIENT_ID      = "";
my $CLIENT_SECRET  = "";
my $REFRESH_TOKEN  = "";
my $SPREADSHEET_ID = "";

my $gs = Net::Google::Spreadsheets::V4->new(
    client_id      => $CLIENT_ID,
    client_secret  => $CLIENT_SECRET,
    refresh_token  => $REFRESH_TOKEN,
    spreadsheet_id => $SPREADSHEET_ID,
);

use Data::Dumper;

my ( $content, $res, $hoge )
    = $gs->request( GET =>
        '?includeGridData=true&fields=sheets.data.rowData.values(formattedValue)'
    );

my $cells = $content->{sheets}->[0]->{data}->[0]->{rowData};

my $count_row = 1;
my @col       = ( 'A' .. 'Z' );

for my $rows ( @{$cells} ) {

    my $count_col = 0;

    # 行の処理
    my $values = $rows->{values};
    for my $value ( @{$values} ) {

        # 列の処理
        print $col[$count_col]
            . $count_row . ':'
            . $value->{formattedValue} . "\n";

        $count_col++;

    }
    $count_row++;

}
Any::Moose is deprecated. Please use Moo instead at /Users/sironekotoro/.plenv/versions/5.30.1/lib/perl5/site_perl/5.30.1/Net/Google/DataAPI/Auth/OAuth2.pm line 2.
A1:hoge
B1:fuga
A2:piyo
B2:Perl

Google Sheets に書いてみる

途中にあるあからさまなデータ構造のおかげで、雰囲気は掴めるのではないかと・・・

あ、途中にある use utf8; 忘れると文字化けするので注意。

#!/usr/bin/env perl
use strict;
use warnings;

use Net::Google::Spreadsheets::V4;

my $CLIENT_ID      = "";
my $CLIENT_SECRET  = "";
my $REFRESH_TOKEN  = "";
my $SPREADSHEET_ID = "";

my $gs = Net::Google::Spreadsheets::V4->new(
    client_id      => $CLIENT_ID,
    client_secret  => $CLIENT_SECRET,
    refresh_token  => $REFRESH_TOKEN,
    spreadsheet_id => $SPREADSHEET_ID,
);

use utf8;

my ( $content, $res ) = $gs->request(
    POST => ':batchUpdate',
    {   requests => [
            {   updateCells => {
                    start => {
                        sheetId     => 0,
                        rowIndex    => 0,
                        columnIndex => 0
                    },
                    rows => [
                        {   values => [
                                {   userEnteredValue => {
                                        stringValue =>
                                            'Perl入学式'
                                    }
                                },
                                {   userEnteredValue => {
                                        stringValue =>
                                            '始まりました!'
                                    }
                                },
                            ],
                        },
                        {   values => [
                                {   userEnteredValue => {
                                        stringValue => '詳細はconnpassで'
                                    }
                                },
                                {   userEnteredValue => {
                                        stringValue =>
                                            'Perl入学式オンラインで検索!'
                                    }
                                },
                            ]
                        }
                    ],
                    fields => "userEnteredValue"
                }
            },

        ]
    }
);

f:id:sironekotoro:20201018152620p:plain

あからさま〜!

というわけで

ここまでくるのに苦労したんですが、いやー、大変でした。

多分絶対もっと楽な方法があるはずなんだよなぁ・・・

最終的には、 CSV ファイルや Perl のデータ構造をこの API を使って Google Sheets にあげるところまで行きたいですね。

(できそう、と思うと途端に筆が鈍る人)

それではー

Perl から Google Sheet を読み書きする・・・ための3種の TOKEN を手に入れる

いやもう、ほんと大変だったわ・・・(以下おっさんの一人語りが続くのでカット)

「かつての自分」に教えるつもりで「APIを利用する前」から始めます。

その1:Google Cloud Platform で CLIENT ID と CLIENT SECRET を手に入れる

Google Cloud Platform にログインできている、というところから始めます。(画像右上の「コンソール」というリンク)

f:id:sironekotoro:20210528113746p:plain

  1. Google Cloud Platform にログイン後、左上のロゴの右にあるグループ名、またはプロジェクト名をクリックする

    f:id:sironekotoro:20201018141614p:plain

  2. プルダウンメニュー右上にある「新しいプロジェクト」をクリックする

    f:id:sironekotoro:20201018141730p:plain

  3. プロジェクト名を入力し(最初から入力されているものでも良い)、請求先アカウントを選択して「作成」ボタンを押す

    f:id:sironekotoro:20201018141903p:plain

  4. 少し待つと、プロジェクト作成が完了する

    f:id:sironekotoro:20201018142025p:plain

  5. ロゴ横のプロジェクト名をクリックし、作成したものを選択する

  6. ロゴをクリックし、左側メニュー「ホーム」、さらに「ダッシュボード」を選択する

  7. 左側メニューの「APIとサービス」、さらに「APIライブラリ」をクリックする

  8. APIとサービスを検索」の入力欄に sheets と入力すると、Google Sheets API が表示されるのでクリックした上で「有効にする」をクリックして選択する

    f:id:sironekotoro:20201018142345p:plain

  9. ロゴをクリックし、左側メニューの「APIとサービス」、さらに「OAuth同意画面」をクリックする。

    • 組織で運用している場合には、組織内部ユーザーだけが利用するものか、外部のユーザも利用できるものかを選択する画面が出る。

    • 今回はテスト運用するので「内部」を選択し「作成」をクリックする

    f:id:sironekotoro:20201018142218p:plain

  10. 「OAuth同意画面」の設定を行う

    • アプリケーション名:アプリケーション名、または自分がわかりやすいもの

    • Google API のスコープ:「スコープを追加」ボタンをクリックし以下のものにチェックを入れて右下の「追加」をクリックする

    • 画面下にある「保存」ボタンを押す

    f:id:sironekotoro:20201018142521p:plain

    f:id:sironekotoro:20201018142843p:plain

  11. 左側メニュー「認証情報」をクリックする

  12. 画面上部にある「認証情報を作成」から「OAuth クライアント ID」を選択する

    f:id:sironekotoro:20201018143147p:plain

  13. アプリケーションの種類を選択するプルダウンメニュー が出てくるので、ここでは「デスクトップアプリ」を選択し、「作成」ボタンをクリックする

    • 手元のローカル環境からクラウドにある Google Sheets API を叩くので・・・という理解

    • 名前はデフォルトのままで良い

    f:id:sironekotoro:20201018143441p:plain

  14. おめでとう!ここでやっと CLIENT ID と CLIENT SECRET が発行されました!!

    • ここで控えておく

    f:id:sironekotoro:20201018143647p:plain

    • 控えなくても、左側メニューの「APIサービス」から「認証情報」を選び、OAuth 2.0 クライアントID 欄にあるユーザー右の 鉛筆マークから再確認ができる

    f:id:sironekotoro:20201018144502p:plain

その2:モジュールを使って REFRESH TOKEN を入手する

先に取得した CLIENT ID と CLIENT SECRET を用いてGoogle から REFRESH TOKEN の取得に必要な URL を返してもらいます。

ここでの手続きには認可のプロトコルである OAuth2 が用いられています。もちろんここはモジュールに頼ります。

様々なモジュールがありますが、今回は Net::Google::OAuth を利用します。

いつもの $ cpanm Net::Google::OAuth でインストール。

metacpan.org

以下のスクリプトに以下の情報を埋めた上で実行します。

  • $CLIENT_ID:その1で入手したものを利用

  • $CLIENT_SECRET:その1で入手したものを利用

  • $SCOPE:今回はspreadsheets

  • $EMAILGoogle Cloud Platform でログインした時のメールアドレス

use strict;
use warnings;

use Net::Google::OAuth;

my $CLIENT_ID           = "";
my $CLIENT_SECRET = "";
my $SCOPE                = 'spreadsheets';
my $EMAIL                 = 'hogehoge@sironekotoro.com';

my $oauth = Net::Google::OAuth->new(
    -client_id     => $CLIENT_ID,
    -client_secret => $CLIENT_SECRET,
);

$oauth->generateAccessToken(
    -scope => $SCOPE,
    -email => $EMAIL,
);

print "This is REFRESH TOKEN:\n";
print "=" x 20 . "\n";
print $oauth->getRefreshToken() . "\n";
print "=" x 20 . "\n";

そして、ターミナルから実行すると・・・謎の長いURLが表示されます。

改行いれてURLが分かりやすくしてますが、今回はこんな感じ

$ perl net_google_oauth.pl
Please open this URL in your browser:

https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&client_id={CLIENT ID}&login_hint=hogehoge@sironekotoro.com&nonce=89497-868562-391621&redirect_uri=http%3A%2F%2Flocalhost%3A8000&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fspreadsheets&state=uniq_state_32316

Insert redirected address from browser here:

このターミナルの画面は後で使うので、閉じずに開いておいてください。

この真ん中のURL部分をコピペしてブラウザに貼り付けて実行するとー?

サービスやお仕事とかでたまーに出てくる認証画面が出ました。よし!と許可すると・・・

f:id:sironekotoro:20201018144721p:plain

ページを表示できない?localhost:8000?え?手元にウェブサーバ立てるの・・・?

f:id:sironekotoro:20201018144804p:plain

となったんですが(少なくとも自分はそうなった)、ここで重要なのはURLです。

この時点のブラウザのURL欄はこのようになっているはずです。

http://localhost:8000/?state=uniq_state_32316&code=4/0AfDhmriDKniEEcVnKYQsaux_qTkOwTQGP8KFMeDDPLfoQQFCPSCGnasgiIylr-TRgNMTjA&scope=https://www.googleapis.com/auth/spreadsheets

このURLの中にあるcode=に続く文字列、これが REFRESH_TOKEN を取得するための鍵になります。上の例だと

4/0AfDhmriDKniEEcVnKYQsaux_qTkOwTQGP8KFMeDDPLfoQQFCPSCGnasgiIylr-TRgNMTjA

です。

先に開いたままにしているターミナルに、この code が含まれている URL をまるっと貼り付けると・・・最後の鍵、REFRESH TOKEN が手に入ります。

次回に続く

さて、ここまでの長い旅路で Google Sheets API を利用するために必要な3つのキーが揃いました。

  • CLIENT ID
  • CLIENT SECRET
  • REFRESH TOKEN

では実際に Google Sheet の編集にチャレンジします・・・ってところで、すでにもう十分長いので次回に続きます。