sironekotoroの日記

Perl で楽をしたい

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 の編集にチャレンジします・・・ってところで、すでにもう十分長いので次回に続きます。

Perl入学式 オンライン 2020 第1回お疲れ様でした

Perl入学式 オンライン 第1回

受講された方、サポーターの方、お疲れ様でした。 講師の人です。

拙いところやアクシデントもありましたが、皆様に助けられました。ありがとうございます。

今回ですが、Perlの学習内容としては以下のみとなり、特に復習問題はありません。

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

print "Hello World!";

講義の途中で言った通り、

「確実に実行結果がわかる、エラーのでない構文」

を一つ知っているのは、とても強い武器です。

この先スクリプトを書いていく中で、 print "Hello World"; がエラーになるなら、それはその部分の周辺、おそらくそこより前に間違いがあるはずです。

「ここまではプログラムが動いた」というアンカー、目印として大いに利用してください。

Perl入学式 オンライン

2020年初頭から猛威を振るう新型コロナウイルスのため、従来の対面形式での Perl入学式は開催が難しい状態です。

このため、サポーター陣は校長の id:xtetsuji さん、Perl入学式 in大阪 の id:tomcha さんを中心にオンラインで開催すべく、半年近く準備を行なってきました。

大幅なテキストの改修に加え、ツール選定・改修、配信環境の構築など例年以上の作業がありました。

なんとか2020年中に第1回を開催することができ、本当に嬉しいです。

講義終了後の放課後トーク

参加する人が誰もいないのでは・・・?

と思っていたのですが、杞憂に終わりました。

Discord で寄せていただいた質問や意見のテキストに xtetsuji さんと自分が回答していく「ラジオの公開生放送」的なスタイルでしたが、盛り上がって楽しかったです。

過去の第1回

昨年度の資料はこちらになります。Windows 環境に Perl を入れる方法や、ターミナルでの操作を解説しています。

また、もっと早く先に進みたい!という方もぜひ参考にしてください。

www.perl-entrance.org

自分が過去の第1回について書いてきたエントリは以下となります。

エディタ(VSCode)とか、いろいろ書いております。

第2回の開催について

11月の土曜日、7日、14日、28日のいずれかを予定しています。

次回の参加、お待ちしております!

Minilla で作ったモジュールにデータフォルダを入れる

全国の銀行・支店コードを取得・表示する ZenginCode というものがあります。

github.com

これには!なんと!!Perl 版がないので(しょんぼり)自分で作って使ったりしています。

きっかけは経理業務で必要に迫られ・・・必要に迫られ・・・楽をする必要に迫られ・・・楽がしたい・・・

github.com

モジュールの中にデータフォルダを作りたかった

ZenginCode の Ruby 版だと、gem(Perlでいうところのモジュール)の中に全銀コードのデータが格納されており、そのまま利用が可能です。

自分の Perl モジュールは Minilla で作っているのですが、Ruby 版と同じ様にモジュール内にデータファイルを入れる方法がわかりませんでした。

結果、銀行コードの一覧のみを別にダウンロードして、モジュールはそこを参照しにいく、という構成で作りました。

Minillaのモジュールの中にデータフォルダ作れます

が、同じような問題にあたり、そして解決した人がいることを発見!

qiita.com

こちらの方はポケモンGoのデータを用いて、同じようなことをしている・・・!

そして、minilla でモジュール内にデータを置いておく方法についてはしっかり Web+DB での連載でも言及されておりました。あぁ。

gihyo.jp

ソースコード以外のファイルをモジュールに同梱したい場合があります。たとえば,テンプレートファイルや辞書ファイルなどです。そういったファイルはshareディレクトリに配置します。

その場合,CPANにアップロードしてからも正しく動作させるために,コード内からそれらのファイルに直接アクセスするのではなく,File::Shareなどのモジュールを通してアクセスして開発する必要があります。

さっそくチャレンジ・・・File::Share ってなに?

File::Shareなどのモジュールを通してアクセスして開発する必要があります。

File::Share 初めて聞きますね。ってことで Google 翻訳片手で見てみます。

metacpan.org

・・・ダメだよくわからん。でも大丈夫、Perl の場合は SYNOPSYS が充実してるので、まずはそれをコピって実行。

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

use File::Share ':all';

my $dir = dist_dir('Foo-Bar');
my $file = dist_file('Foo-Bar', 'file.txt');

で、こんなエラー

Failed to find share dir for dist 'Foo-Bar' at /Users/sironekotoro/.plenv/versions/5.28.1/lib/perl5/site_perl/5.28.1/File/Share.pm line 37.
[Finished in 0.2s with exit code 2]

ううん、わからないので Google 翻訳で

File :: Shareはshare、対応するモジュールが開発パスからロードされたことを認識した場合、ローカルディレクトリを探します。

ううーん。

このモジュールの元となった File::ShareDir も見てみたんですがよくわからず・・・で色々試行錯誤した結果です。

File::Share とは

File::Share とは汎用的なファイルのパス解決ツールではなく、モジュール作成時に使うもののようです。

標準モジュールではないので、cpanm File::Share などでインストールが必要です。

まぁ、汎用的なやつなら File::Find とか Path::Tiny とかあるものね。

やってみる

  1. $ minil new Sironekotoro::File としてモジュールの雛形を作る

  2. 作ったモジュールのフォルダの中に share フォルダを作成する

  3. share フォルダの中に test.txt を作成する。中には適当に hoge とか書いておく

  4. モジュールの中に test.pl を作成する。名前の通りテスト用

この時点で Sironekotoro-File フォルダの中身はこうなってます。

$ tree
.
├── Build.PL
├── Changes
├── LICENSE
├── META.json
├── README.md
├── cpanfile
├── lib
│   └── Sironekotoro
│       └── File.pm
├── minil.toml
├── share
│   └── test.txt
└── t
    └── 00_compile.t

4 directories, 10 files

手を入れるのは lib/Sironekotoro/File.pmtest.pl です。

まずは lib/Sironekotoro/File.pm 、わかりやすいように warn を仕掛けておきます。

package Sironekotoro::File;
use 5.008001;
use strict;
use warnings;

use File::Share 'dist_dir';

our $VERSION = "0.01";

sub new {
    my $class = shift;
    my $self  = bless {}, $class;
    return $self;
}

sub read {
    my $self = shift;

    my $dir = dist_dir('Sironekotoro::File');

    warn '$dir: ' . $dir;

    my $data = "$dir/test.txt";

    warn '$data: ' . $data;


    # ファイルを読み込む処理
    my $str;
    open my $FH , '<', $data;
    for my $line (<$FH>){
        chomp $line;
        $str .= $line;
    }
    close $FH;

    return $str;
}


1;

次にテスト用の test.pl

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

use lib qw/lib/;
use Sironekotoro::File;

my $obj = Sironekotoro::File->new();

print "The text of the file's contents\n";
print $obj->read . "\n";

これで $ perl test.pl とすると、無事、モジュール内のファイルを読んでくれます。

そして、モジュールに仕掛けた warn の効果でこのような出力になります。

$ perl test.pl
The text of the file's contents
$dir: /Users/sironekotoro/Dropbox/perl/temp/Sironekotoro-File/share at lib/Sironekotoro/File.pm line 21.
$data: /Users/sironekotoro/Dropbox/perl/temp/Sironekotoro-File/share/test.txt at lib/Sironekotoro/File.pm line 25.
hoge

これで、モジュールの中の $dir には、モジュール内の share ディレクトリ までのフルパスが入っていることがわかりました。

同様に $data にはファイルまでのフルパスです。

なるほど、これであればモジュールからファイルを参照できますね。

Zengin::Perl 鋭意改良・・・します

ということで、やりたかったことを実現する方法を見つけました。

空き時間になんとか良い感じにしていきたいですね。

あと、最近やっと Mouse の良さ的なものに気づいたので、Mouse 使ったオブジェクト指向にしていきたいなぁ・・・

なお、全銀データをモジュール内に持つ場合、モジュールを定期的にアップデートする必要が出てきますねぇ・・・さてどうするか。考えるの楽しいですね。