sironekotoroの日記

Perl で楽をしたい

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

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

講義に利用したスライド・動画類は以下 Perl 入学式の公式サイトで公開しています。参加された方も、参加できなかった方も、ぜひ復習に使ってください。

www.perl-entrance.org

問題の意味がわからない、とか、このような解答例はどうだろう?という方は Discord の Perl 入学式チャンネル(招待コード)や、twitterハッシュタグ #Perl入学式 をつけて聞いてみてください。

Perl入学式 オンライン 2020 第3回

今回は以下の項目について学習しました。

  • if 文
  • 配列
  • for 文

そして最後にまとめとして「fizzbuzz」をやりました。第 1 回 〜 第 3 回で学習した内容を全て含む良問です。

fizzbuzz についてはスライドでも紹介していますが、こちらの記事が有名です。

www.aoky.net

Perl のイベント紹介

この時期になると、各エンジニアコミュニティがアドベントカレンダーというものを始めます。

12 月 1 日から 12 月 25 日にかけて 1 日ずつ持ち寄りで記事を投稿していくという企画です。

2020 年の Perlアドベントカレンダーはこちらです。

見て分からなくても 3 年後、4 年後困った時に「昔、何かで見たな・・・?」と繋がることがありますので目を通しておくこと良いと思います。

qiita.com

また、来年 2 月に Perl のオンラインイベントが開催されることになりました。

楽しみですね〜

次回オンライン第 4 回!

2021 年の 1 月開催予定です。

次回は配列に要素を加えたり取り出したりといった配列の操作関数をやります。

そして最後の変数であるハッシュをやります。

第 4 回ともなると、まったくの初心者の方が途中から入るのは厳しいです。

とはいえ、過去の動画や講義資料は Perl 入学式の公式ページに掲載しております。

ぜひ閲覧いただいた上で参加ください!お待ちしております!!

Perl で Chatwork API を使って添付ファイルをダウンロードする

チャットツール変更

お仕事で使うチャットツールを Chatwork から Slack に変更することになりました。

ユーザーも作り、運用ルールはメルカリさんの物をちょっと変えて・・・メルカリさんありがとうございます。

mercan.mercari.com

さて、これでめでたしめでたし。

で・・・終わることもなく。Chatwork のログの抽出を行うことになりました。

いろいろな手段があり、いろいろと情報もあります。スクレイピングツールとか、Ruby の Gem とか。

が、今回は正攻法で Chatwork のプランをエンタープライズプランに変更した上でログのエクスポートを行いました。

やはり、正攻法ならではの安心感がありますね。

エンタープライズプランにするにあたって最低限の課金にするため、必要最低限のメンバー以外は一気に退会させました。

あれ?添付ファイルは・・・?

エンタープライズプランの管理者(1名)は、メニューからログのエクスポートが可能です。

翌朝、ダウンロードリンクが記されたメールが届きます。早速ダウンロード・・・あれ?圧縮済みとはいえ全ログのサイズが 10MB 以下?

そう、Chatwork のログエクスポートは csv ファイルのみ。添付ファイルはダウンロードリンクが files1.csv ってな csv ファイルにまとめられています。

面倒くさいことは Perl

この files1.csv に記載されている 内容 はこんな感じです。

アップロード日時 ルームID ルーム名 アカウントID アカウント名 ファイル名 ダウンロードURL ファイルサイズ

肝心のダウンロード URL はこんな感じ

https://www.chatwork.com/service/packages/chatwork/subpackages/archive/download_file.php?fid=?????????

なお、ファイルサイズはテキストで 18.70 KB とか 3.27 MB って文字列で書いてあるので、サイズの比較できないですね・・・

このダウンロードリンクからダウンロードできるのはエンタープライズプランの管理者(1名)のみです。

これをフォルダに分けつつ、心を込めて 1 つ 1 つ保存していくの? 無理無理ー

添付ファイル保存依頼のものだけで 10000 ファイルを超えます。 人力では絶対無理ー

ここはダウンローダーで一気に処理でしょ!と思うも、このダウンロードのパスにどうエンタープライズプランの管理者(1名)の認証情報を加えればいいかわかりません。

basic 認証、ってこともないはずですが、ダメ元でやってダメでした。

Perl のモジュールで Chatwork 扱えないかなぁ、と思ったものの、API のバージョンが古いものしかないみたいです。残念。

ってことで、モジュール作ることにしました。

API の概要はここです。

developer.chatwork.com

これを見ると、ダウンロードしたいファイルの room_idfile_id がわかればいけそう。

ただし、この API でできるのはダウンロードするリンクを生成するまで。そのダウンロードリンクの制限時間 30 秒というちょっと捻った感じです。

ということで、files1.csv から ダウンロードリンク を抜き出し、そのリンクから room_idfile_id を抽出。

それをもとに URL を組み立てて、API にアクセス、返ってきたダウンロードリンクからダウンロードします。

API の利用制限は 5 分で 300 回、1 秒に 1 回ですね・・・ってことで、ダウンロードごとに 1 秒の sleep 入れると良さそうです。

モジュールにしたものの

この勢いで Chatwork の API 網羅するモジュール作ったろ!って思ったのですが、自分がエンタープライズプランの管理者でいられる時間は短く、あと 1 ヶ月未満です。

だって Chatwork を解約前提なのだもの・・・

ということで、モジュールは作ってみたものの、自分が使う最低限だけ実装したのでした。

github.com

もっと早く管理者権限を手にしていればなー、と思ったのでした。

Perl で Google Sheet API をつかってセルに値を書き込む

さくっとやりました!と言いたいところなのですが

3 ヶ月じゃないな 2 ヶ月くらいだな。

Google Sheet API でセルの内容を書き換えるメソッドは 3 つあるんですが、自分の用途に合致した values/batchUpdate を使いました。

developers.google.com

後、Qiita のこの記事がとても参考になりました。

qiita.com

こんな感じでセルを埋めることができます。

f:id:sironekotoro:20201123121228p:plain

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

use Data::Dumper;
use HTTP::Tiny;
use JSON;
use URI;
use utf8;

my $ACCESS_TOKEN = '';

my $SPREADSHEET_ID = '1sVNSpvtWPPkv5Qnb0tifYHpKMxaehxj5ChAeQ1G1kgA';

my $GOOGLE_SHEET_API = "https://sheets.googleapis.com/v4/spreadsheets/";

my $bearer = join " ", ( 'Bearer', $ACCESS_TOKEN );

# Method: spreadsheets.values.batchUpdate  |  Sheets API
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate
values_batchUpdate();

sub values_batchUpdate {
    my $ht
        = HTTP::Tiny->new( default_headers => { Authorization => $bearer, } );

    my $SHEET_URI
        = $GOOGLE_SHEET_API . $SPREADSHEET_ID . '/values:batchUpdate';
    my $URI = URI->new($SHEET_URI);

    my $response = $ht->request(
        'POST', $URI,
        {   content => encode_json(
                {   valueInputOption => 'USER_ENTERED',
                    data             => [
                        {   range  => 'シート1!A1',
                            values => [
                                [ 200,    100, '=A1+B1' ],
                                [ 'hoge', '',  'fuga' ]
                                ]
                        }
                    ],
                    includeValuesInResponse      => 'true',
                    responseValueRenderOption    => 'UNFORMATTED_VALUE',
                    responseDateTimeRenderOption => 'FORMATTED_STRING',
                }
            )
        },
    );
    # print Dumper $response;
}

Perl で Google Sheet API をつかってセルの値を取得する

これも GET だけでできるのでお手軽ー

説明よりもコードを読んでもらったほうが早い気がする。

配列リファレンスの中の配列リファレンス、てな形でデータが取得できるので、csv にするなりなんなり後は自由自在ですね(力量による)

ちょっと詰まったところ

この API は取得するセルの範囲を指定する必要があります。

シート1!A1:C2 こんな感じで。

もう一段楽をするためには、値が入っているセルの範囲を取得する必要があります。

値が入っているセルの行と列の最大値が欲しい・・・!

Google Apps Script だと getLastRow() とか getLastColumn() みたいなやつ。

developers.google.com

となると、API でアクセスして、情報が入っている最後の Row(行)と Column(列)を把握して・・・あれー、そんな関数見当たらないぞー

困ったときは Google 先生、そして stack overflow。

stackoverflow.com

You can set the range to "A2:D" and this would fetch as far as the last data row in your sheet.

あ、そういう・・・列さえ把握しておけばいい的な・・・

で解決したのでした。

Perl で Gogle Sheet API をつかってセルの値を取得する

以下がコードです。

情報をとるときには 2 つの方法 values.get, values.batchGet があるので、太っ腹に両方やってみました。

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

use Data::Dumper;
use HTTP::Tiny;
use JSON;
use URI::QueryParam;
use URI;
use utf8;

my $SPREADSHEET_ID = '1sVNSpvtWPPkv5Qnb0tifYHpKMxaehxj5ChAeQ1G1kgA';

my $ACCESS_TOKEN  = '';

my $GOOGLE_SHEET_API = "https://sheets.googleapis.com/v4/spreadsheets/";

my $bearer = join " ", ( 'Bearer', $ACCESS_TOKEN );

values_get();
values_batchGet();


# Method: spreadsheets.values.get
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get
sub values_get {
    my $ht
        = HTTP::Tiny->new( default_headers => { Authorization => $bearer, } );

    my $SHEET_URI
        = $GOOGLE_SHEET_API . $SPREADSHEET_ID . '/values/シート1!A1:C';
    my $URI = URI->new($SHEET_URI);

    my $res  = $ht->get($URI);
    my $data = decode_json( $res->{content} );

    print Dumper $data->{values};

}

# Method: spreadsheets.values.batchGet
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet
sub values_batchGet {
    my $ht
        = HTTP::Tiny->new( default_headers => { Authorization => $bearer, } );

    my $URI
        = URI->new( 'https://sheets.googleapis.com/v4/spreadsheets/'
            . $SPREADSHEET_ID
            . '/values:batchGet' );
    $URI->query_param( ranges => 'シート1!A1:C');

    my $res = $ht->get($URI);
    my $data = decode_json( $res->{content} );

    print Dumper $data->{valueRanges}->[0]->{values};

}

Perl で Google Sheet API をつかってタイトルを変更する

なんか、適当に文言を入れ替えて自動生成したみたいなタイトルになってきたなぁ・・・

それはそれとして、Google Drive API に引き続き、Google Sheet API を使ってホゲホゲするってやつです。

以前、モジュールを使ってやってみたんですが、せっかく Web API 投げる方法もわかったことだし、この方法でやってみよう!ってことで。

使う都度 Any::Moose の警告が出るのが気に食わないってのもあります。

sironekotoro.hateblo.jp

Google Sheet API の許可とアクセストークン が必要です

かつての記事で書いてます・・・なんか、遠い昔の気がする

sironekotoro.hateblo.jp

sironekotoro.hateblo.jp

タイトルを変更する

まずはジャブから・・・早速、公式のリファレンスを確認します。

developers.google.com

おぉ、わかりやすい。もっと他の言語での実装例を参考にして回るかと思ってた。あっさり。

リファレンスにあるリクエストボディ は json にしておく必要があります。

これは先のページには書いてないんですが、正しい構造のJSON 送らないとエラーで「まともな JSON で送って〜」と言われます。

今回は好みで HTTP::Tiny 使いました。

リクエストボディを設定するときは公式リファレンスの \%options のところで content をキーにする必要があるので注意です。

metacpan.org

エラーメッセージを見て直しつつ完成したのが以下スクリプトです。

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

use HTTP::Tiny;
use JSON;
use URI;
use utf8;

my $SPREADSHEET_ID = '1sVNSpvtWPPkv5Qnb0tifYHpKMxaehxj5ChAeQ1G1kgA';

my $ACCESS_TOKEN = '';

my $GOOGLE_SHEET_API = "https://sheets.googleapis.com/v4/spreadsheets/";

my $bearer = join " ", ( 'Bearer', $ACCESS_TOKEN );

my $ht = HTTP::Tiny->new( default_headers => { Authorization => $bearer, } );

my $url = URI->new( join "",
    ( $GOOGLE_SHEET_API . $SPREADSHEET_ID . ':batchUpdate' ) );

my $response = $ht->request(
    'POST',
    $url,
    {   content => encode_json {
            requests => [
                {   updateSpreadsheetProperties => {
                        properties =>
                            { title => "Perl から API 叩いて変更したタイトル" },
                        fields => "title"
                    }
                }
            ]
        }
    },
);

print $response->{status} . "\n";
print $response->{reason} . "\n";

f:id:sironekotoro:20201121164441p:plain

ちなみに、タイトルを変えても更新日は変わらない模様。

Perl で Slack API を使ってユーザー一覧を取得する(追記あり)

Slack のユーザー一覧を取得せよ!

ってなお仕事が降ってきたものの、Slack のメニューとか探したど見当たらず。

これは API 使えってことなのかなぁ・・・使うんはいいけど、普通の非テックな会社だと厳しいんでは。

もっと高い料金プランだと出てくるのかな?

それとも、非テックでも Slack 使うような会社ならそれくらいいけるんだろうか。

API を使うそのまえに

ってわけで、いつも通り Perl でやります・・・が、やる前に API を利用できるようにする準備から。

こちらの記事が大変役に立つというか、そのまま進んでそのまま OAuth Token を入手することができました。

ありがたい。

qiita.com

WebService::Slack::WebApi

あとは、Perl の便利モジュールがうまいことやってくれます。

metacpan.org

#!/usr/bin/env perl
use strict;
use warnings;
binmode STDOUT, ":utf8";

use WebService::Slack::WebApi;

my $ACCESS_TOKEN = 'xoxp-hogehogefugafuga';

my $slack = WebService::Slack::WebApi->new( token => $ACCESS_TOKEN );

my $users = $slack->users->list;

# $users の中にユーザーの情報が入っているので、
# とりたい情報が気になる人は Data::Dumper で中を覗いてみる

for my $member ( @{ $users->{members} } ) {

   # 削除済みユーザーや、bot も含まれているので除外する
    next if ( $member->{deleted} == 1 ) || ( $member->{is_bot} == 1 );

    print "$member->{name}\t$member->{profile}->{display_name}\n";
}

あっさり。

2020-11-20 追記

依頼主もメンバー一覧とるのに API しかなさそうー、って言ってはいたんですが、slack のワークスペースの管理者であれば、確認方法があることを教えていただきました。

そりゃあるよなぁ!(テノヒラクルー

ありがとうございます・・・って、bio 見たらこの方、slack のAPI SDK 作っておられるのですね・・・助かりました。

画面左上のワークスペース -> 設定と管理 -> メンバーを管理する、から確認することができました。

f:id:sironekotoro:20201120102113p:plain

Perl で Google Drive API をつかって特定のファイルを削除する

ファイルの上書き終わったところでこのシリーズ(シリーズ?)終わらせようと思っておりました。

しかし CRAD:Create, Read, Add, Delete の一角たる Delete やらないっていうのはないんじゃないか?

どうせ後で必要になりそうだし・・・あぁ、でも面倒〜。

ってことでやりました。

いざ手をつけるとすぐでしたが、普段は色々なモジュールの裏に隠れている HTTP の DELETE メソッドを明示的に初めて使ったように思います。

情報処理試験の教本や問題集でしか見たことのなかった(そして問題には出そうにない) DELETE メソッド、本当にあって本当に使えるんや・・・という謎の感動がちょっとあります。

そして、削除が成功した時の HTTP ステータスコード 204 これも初見でした。

先輩と覚える HTTP ステータスコード · GitHub

削除したら応答も何もないよねというのわかる。

はー!これで CRAD 全部揃ったーはずー!!

・・・しかし、なんか my 宣言が並んでてアレな感じのコードですね・・・

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

binmode STDOUT, ":utf8";

use URI;
use HTTP::Tiny;

my $ACCESS_TOKEN  = "";

my $GOOGLE_DRIVE_API = "https://www.googleapis.com/drive/v3/files/";

my $delete_fileid = "1SNmgEpX2xnFCbeRQWtGg4PdU0ic6h92IO-VuynVGli8";

my $bearer = join ' ', ( 'Bearer', $ACCESS_TOKEN );

my $uri = URI->new( $GOOGLE_DRIVE_API . $delete_fileid );

my $ht = HTTP::Tiny->new( default_headers => { Authorization => $bearer } );

my $response = $ht->delete($uri);

print $response->{status} . "\n";    # 204