プログラム

徒然日記 – メモを取りながらプログラムを書く

GAS(Google Apps Script)で書きたいと思っていたちょっとしたプログラムがあったので午前中からラジオを聴きながらプログラムを書いていた。やりたいことは

といった内容。

Redashで得られる結果がカラム名と取得行がまとめられたJSONが返ってくる仕様なのでそれを読み取りつつスプレッドシート上に反映させていくというもので非常にシンプルではあるのだけれども悲しいかな以前の様に頭の中だけでまとめきれなくなってきているので原点回帰してノートにメモを取りながらコードを書いていった。

スプレッドシート上のここに書いたものを読み取って参照して得られたJSONをこういった形に解釈してスプレッドシート上のフォーマットの項目名を参照してセルにその値を書き込んでいって...とノート上に書き取りながら考えを整理していくとそれなりにまとめられて、それを見ながらコードに書き落としていくとやりたいことが実現できた気がする。

このやり方、四半世紀...25年以上前にBASIC言語でプログラム書いていたとき*1と変わらず、なんか懐かしいなと思いつつ楽しみながら一日を過ごしていた。


今日読んだ印象に残った記事。自分も同感なところが多かった。

r25.jp

機嫌については自分も思うことがあるのでどこかで書いてみたい。

*1:学校のパソコン室にあった台数に限りがあって1台を3名くらいで使っていたので自分の番ではないときは紙のノートの上でプログラムを書いていた記憶がある

google apps scriptでスプレッドシートの特定の列, 行を配列として取り込む

google apps scriptのgetRange と getValuesを利用して特定のシートの指定した範囲のセルに含まれる値を取得することができるのだが、これだと列方向に対して値を取得した際に

[["A1"], ["A2"], ["A3"], ... ]

といった形で取得され、逆に行に対して値を取得すると

[["A1", "B1", "C1", ...]]

という形で取得される。複数の行・列を指定しての範囲を指定してセルの情報を取得する場合はよいのだけれども単一の行と列を取得する場合は少し扱いにくい。

f:id:hideack:20200211113912p:plain
1次元配列で返って欲しい例

上の様になってほしいので

parseLine("シート名", "B:B");

みたいな形で呼び出すとシンプルな1次元配列で返すものを準備した。

function parseLine(sheetName, range) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
  var selectRange=sheet.getRange(range);
  var values=selectRange.getValues();
  var responseArray = [];
  var isCol = false;
  var isNum = new RegExp(/^[0-9]+(\.[0-9]+)?$/);
  if (isNum.test(range.charAt(0))) {
    for(var i=0; i<values[0].length; i++) {
      responseArray.push(values[0][i].toString());
    }
  } else {
    for(var i=0; i<values.length; i++) {
      responseArray.push(values[i][0].toString());
    }
  }
  return responseArray;
}

得てしてこういうのを書くと既にAPIとして備わってること多い気もするのだけれども実際どうだろう。

2022年12月24日追記

サンプルソースのインデントが崩れていたので更新しました

徒然日記 – fastifyのテンプレートエンジン

昨夜思いつきで雑に書いたプログラムを休憩時間にfastifyを使って動かしてみようと思ってこれまた雑に書いてみたところでHTMLを返したくなり、テンプレートエンジンのところどうするのがいいんだろうと思ってて見つけたものを使った。

github.com

まだ表に出せてないんだけどもう少し整えてgithubとかに上げたい。

こういう形で実際に思いついたのをすぐ手を動かして試してみるという感覚、しばし書いている通り忘れたくないので続けねば。激減はしてるけれども。

【カラーミーショップAPI】GAS(Google Apps Script)でAPIを利用する方法

カラーミーショップ では、カラーミーショップAPIというAPIを用意していてネットショップを運営するための管理画面から行えるオペレーションであったり、ショップ自体が供える情報を取得する仕組みがある。この仕組みを活用してカラーミーショップでは今年の5月からカラーミーショップアプリストアというネットショップを運営するにあたっての機能をアドオンしたり効率化することができるプラットフォームの提供を開始している。

自分自身がこのプラットフォームに参加してもらうためのEC支援系の企業の方々だったり、エンジニアの方とお話することがまま増えていく中で、もっとAPI活用できないかなと考えたとき、例えばサービスで提供しているCSVダウンロードの機能だったりをgoogle docs (Google Apps Script) とカラーミーショップAPIで置き換えられないかなと思って試してみた。あと、自分自身がもう少しこの周りをもっと利用しないと気づけないところもあるのではないかと思ってみたところもモチベーションだったりする。

ちなみに開発者体験を向上させたいという視点からエンジニアサイドでもハッカソンインベントもチャレンジしている。

少し話がずれてしまった。

以下、一例として「ネットショップに登録している商品の "ID, 商品名, 価格" をgoogle docsスプレッドシートに書き出す」というオペレーションを試してみた。こういったところからネットショップだったりの運営を自動化してみるところにチャレンジしてみるのも一案だと思う。


googel docsでスプレッドシートを新規作成する

新しくスプレッドシートの新規作成をして、そこから "ツール > スクリプトエディタ" を選択。

スクリプトIDを控える

スクリプトエディタを開いた際の "ファイル > プロジェクトのプロパティー" を開くと以下の様なダイアログが表示される。

f:id:hideack:20191104151818p:plain

スクリプトIDが表示されるダイアログ

ダイアログ中に表示される スクリプトID を後に利用するので控える。

カラーミーショップAPIでアプリケーション登録をする

カラーミーショップAPIのアプリケーション登録から今回のGASでカラーミーショップAPIを利用するアプリケーションを登録する。
登録は https://api.shop-pro.jp/oauth/applications/new から行える。

ディベロッパー登録していない場合は https://api.shop-pro.jp/developers/sign_up から行える。

f:id:hideack:20191104142443p:plain

カラーミーショップAPIの新規アプリケーション登録画面

登録の際に必要となるリダイレクトURIには、一つ前の手順で控えたスクリプトIDを含めた以下のものを設定する。

https://script.google.com/macros/d/スクリプトID/usercallback で設定する

アプリケーション登録を完了した際に表示されるクライアントID, クライアントシークレットを控える

画面上に表示されるクライアントID及び、クライアントシークレットを控える。

f:id:hideack:20191104142646p:plain

アプリケーション登録完了時の画面

OAuth認証をするためのライブラリを追加する

Google Apps ScriptでOAuth認証を行うためのライブラリを登録する。スクリプトエディタのメニューにある "リソース > ライブラリ..." を選択して表示されるウインドウ中の Add a library のフォームに以下の識別子を入力する。

1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF

f:id:hideack:20191104142854p:plain

OAuth認証用のライブラリを追加する

そうすると "OAuth2" のライブラリが見つかるので最新のバージョンを指定して保存する。

f:id:hideack:20191104143300p:plain

OAuth2のライブラリが見つかるので最新のものを選択

ソースコードを記述する

試しにカラーミーショップAPIから認証を取った利用店舗の商品情報を取得してみる。以下、サンプルソースコード。

上記のソースコードを入力し終えた後、スクリプトエディタの "実行 > 関数を実行" で以下の順番を踏んで関数を実行する。

doAuthentication

実行が正常に終わった後、"表示 > ログ" で表示されるURLをブラウザに貼り付けて開く。そうするとカラーミーショップAPIでのOAuth認証が走り「認証に成功しました。タブを閉じてください。」という表示が行われたら認証成功。

getProducts

実行するとカラーミーショップAPIから取得された認証ショップの商品情報がシート (ここでは "シート1" という名前固定) に書き出されていく

f:id:hideack:20191104144951p:plain

シートに書き出された様子(ここでは商品ID, 商品名, 価格が書き出されている)

追伸(2020年5月30日)

はてなブログから移設した際にソースコードが読みにくくなっていたのでgistを利用して貼り付け直しました。

スクリーンショットを取るAPI的なもの – 徒然日記

連休一日目。昨日、どうも心持ちがすっきりしない出来事があり、夜もあまり熟睡できない感もあってすぐ目が覚めてしまい気分を紛らわしたい気持ちになったので久しぶりにいちからコードを書いた。こういうときの自分の解決策は

  • プログラムを書く
  • 写経をする*1
  • 料理を作る

の三択になる。

大した内容ではなく、やってることはpuppeteerを使ってクエリパラメータで渡されたURLのスクリーンショットを撮り、そのPNGを返し、その撮影した内容はファイルキャッシュしてくれるといったものになる。実際に使うにはPaaSにカジュアルにおいて使うことを想定して書いてみた。

これで何ができるのかというと具体的な用途としてはスプレッドシートに書いたURLのスクリーンショットをセル中に差し込むみたいなことができる様になる。世に同様のサービスはいくつもあると思うのだけれどもそれなりの契約の手間だったりコスト感だったりを考えたときに使えるケースもあるのではないかなと思っている。*2

f:id:hideack:20190713184934p:plain
google docsスプレッドシートでIMAGE関数使ってセル中にスクリーンショットを表示させる

ただ、スプレッドシートでそれなりの数のURLが列挙されている状況でこのスクリーンショット参照用のURLを並べるとそこそこのリクエストが並行してサーバーに飛ぶのでここは使い方次第かもしれない。ちなみにherokuの無料プランだと5行くらいURLを書いた各セルの横にこのスクリーンショットを参照するURLを記載したときは捌けるくらいだった。もっとも、今回の実装、オンザフライ方式*3で本当は多分キューイングして端からスクリーンショットを撮るとかするのが正解なのかなとビールを呑みながら思っていたりする。

他の用途としては例えばSlackのボットにスクリーンショットを撮らせる際に使うみたいなこともできそうだなというのは思ったりした。

コード、こういう書き方がベストなのかさっぱり見当がつかないので、識者の方で気づいたところがあったらコメントもらえると嬉しい。

*1:ちなみに般若心経

*2:例えば何かしらの調査をするがためのシートを作りそこにスクリーンショットを差し込む目的だけのために外部サービス契約するのもなんだかなと思う気持ちがある

*3:もしかして死語?

ロリポップ!マネージドクラウドでFastify

FastifyというオーバーヘッドがExpressに比べて小さいと言われているサーバーをロリポップ!マネージドクラウドで動かしたときのメモ。
www.fastify.io

Fastifyのサンプルで書かれているものを以下のところだけ書き換えた。

待機ポートの指定

f:id:hideack:20190706171540p:plain
ロリポップ!マネージドクラウド環境変数設定の画面

ホスト名の指定

サーバーをlistenさせるときの記述の第2引数で渡すbind先がデフォルトでは 127.0.0.1 になっているので、これを 0.0.0.0 で明示した。

fastify.listen(PORT, '0.0.0.0', function (err, address) {

上記2点を踏まえて以下の様に書いてデプロイすることで動かすことができた。最初、後者の指定に気づかず数度デプロイして躓いていたので、もしかしたら誰かの役に立つかもしれないのでメモとして残しておく。*2

const fastify = require('fastify')({
logger: true
})
const PORT = process.env.PORT || 5000
// Declare a route
fastify.get('/ping', function (request, reply) {
reply.send({"ping":"pong"});
});
// Run the server!
fastify.listen(PORT, '0.0.0.0', function (err, address) {
if (err) {
fastify.log.error(err)
process.exit(1)
}
fastify.log.info(`server listening on ${address}`)
})

*1:ちなみにherokuだと環境変数PORTは自動的に割り当てられ、また立ち上げ毎にポート番号は変わる。

*2:もっとも、githubリポジトリのREADMEに「If you are running Fastify in a container (Docker, GCP, etc.), you may need to bind to 0.0.0.0.」とキチンと書いてあるのである

fly.ioを試してみる

Edge Worker PaaS の fly.io が面白い - mizchi's blog というエントリを見てなんだか楽しそうだと思い自分も試してみた。ほぼ内容的には先のエントリと同じになりそうなのだけども記録として残しておく。

https://fly.io/からアカウントを作成をすると以下の様なダッシュボードが表示されてすぐにサンプルアプリケーションの作成ができる。

fly.ioダッシュボード

CLIコマンドからもアプリケーション作成できるのだけれどもダッシュボード開いたので "Add new app"ボタンを押してアプリケーションを作ってみる。 fly-sample なるかなり大胆な名前をつけたら通ってしまってアプリケーションができてしまった。
すぐにURLが割り振られるのでアクセスしてみると以下の様な画面が表示されデプロイを受け入れる状態になっていることがわかる。また実際のデプロイ方法等が表示される。

何もデプロイしてない状態

これ以降に関してはCLIコマンド(flyコマンド)が必要になるのでnpmからインストールする。

$ npm install -g @fly/fly

サンプルで何か作るときは fly new で雛形を吐き出せる。

☁  fly new
Start a new project with one of the following templates:
assembly-script
cache-for-errors
getting-started
glitch-custom-hostname
graphql-stitching
html-dom
http-cache
load-balancer
object-store
rate-limiter
static-site
watermark-image
Browse template source at https://github.com/superfly/fly/tree/master/examples

出力される雛形と同じサンプルはgithub上でも公開されている。ひとまず簡単に試して見るために $ fly new static-site して中身を少しだけ書き換えて以下の様な処理を書いてみる。

addEventListener('fetch', function (event) {
let url = require('url');
let parsedUrl = url.parse(event.request.url, true);
let day = parsedUrl.query.day;
let redirectLocation = "http://hideack.hatenablog.com/";
if (day) {
let backDate = new Date();
backDate.setDate(backDate.getDate() - day);
let targetDate = `archive/${backDate.getFullYear()}/${("0" + (backDate.getMonth() + 1)).slice(-2)}/${("0" + backDate.getDate()).slice(-2)}`;
redirectLocation = redirectLocation + targetDate;
}
event.respondWith(new Response('Redirecting',
{
headers: {
'Location': redirectLocation
},
status: 302
}
));
});

fly.io にアクセスした際にクエリパラメータ(day)をつけた日数分前のこのはてなブログアーカイブページへ遷移するというシンプルなものである。http://fly-sample.edgeapp.net/?day=10でアクセスすると10日前のアーカイブページに302リダイレクトする。

手元で動作を確認する際は fly server と実行することで localhost:3000 でアクセスできるサーバーが立ち上がる。また、デプロイは fly --app fly-sample deploy といった形でプロジェクトのルートでコマンドを実行することで実現できる。

fly.ioにデプロイしてる様子

なるほど。一連の流れからPaaSとしての動きは理解できた。
CDN Edge Workerとしての活用のところがまだ自分は理解できてないので誰かに教えてもらおう。

Slackでタイムカードに打刻する

以前書いた PixelaでSlackにおける自分の発言数を記録する - テノニッキ (@hideack 's diary) 応用編として、自分が参加しているSlackのチャンネルを眺めるくん (slack-cli-stream - npm ) に 特定のチャンネル特定のユーザー または 特定の発言内容 に合致する場合のみフックさせて任意のスクリプトを実行できる様にしてみました。

こうすると先日書いたPixelaへの記録以外にも、特定の条件に合致したときに打刻が行える様になります。

例えば弊社GMOペパボであれば、タイムカードのシステムに使っているサービスに対して打刻する GitHub - yano3/kinnosuke-clocking-cli: Clocking in/out cli for Kinnosuke. というシェル上で実行できるコマンドがあるので

定義用のYAML

token: xoxp-xxxxx-xxxxx-xxxxx-xxxxx
hooks:
-
user: hideack
hook: curl -X PUT https://pixe.la/v1/users/hideack/graphs/slack-message/increment -H 'X-USER-TOKEN:xxxxx' -H 'Content-Length:0'
-
user: hideack
channel: example_room
keyword: 出社
hook: "kinnosuke-clocking-cli -y && curl -s -X POST -d channel=example_room -d message='出社打刻したよ' http://takosan.example.com/notice"
-
user: hideack
channel: example_room
keyword: 退社
hook: "kinnosuke-clocking-cli -y -out && curl -s -X POST -d channel=example_room -d message='退社打刻したよ' http://takosan.example.com/notice"

といった形で記載し、slack-cli-stream を実行してターミナルにSlackのメッセージ内容を垂れ流した状態にしておけば、Slack上の #example_roomhideack というユーザの人が 出社 or 退社 といったワードを発言した際にhookに記載したコマンドを実行ようになりました。上の例であれば、打刻用のコマンドを実行して特定チャンネルに通知されます。

言ってしまうと、普通にbot立てればいいじゃんという話ではあるのですが、

  • 個人のトークンを使ってごにょごにょするときに置く場所気を使う
  • 永続的に動いて無くても別に良い (自分がMac開いているときだけ動いてれば十分)

といったケースであれば便利になるケースもある。かも。

あと、打刻に関しても普通にターミナルからコマンドを実行すればいいじゃないかという話なのですが、これもまたインターフェースとしてのSlackが個人的に優先度が高い*1のでこういった実装を自分用にしてみたという経緯だったりします。

www.npmjs.com

*1:UX的にどうこうというわけではなく単純に開いている時間差な気がする

PixelaでSlackにおける自分の発言数を記録する

githubでお馴染みのコミット数に応じて頻度が色付けされるグラフを任意の数字で作れる Pixela というサービスをリリースされた時点から知って、何か試してみたいなと思っていたので、Slackの自分の発言したメッセージ数を記録してみた。

こんな感じになる

まずは、前提としてPixelaのGetting Startedを完了させてユーザー登録とグラフの作成が完了しているとします。*1実際にPixelaに記録する方法として以前自分が作ったSlack全チャンネル眺めるくん ( slack-cli-stream - npm )に特定のユーザーの発言のみを表示対象にするオプションとメッセージ検知をトリガーにhookして任意のコマンドを実行できる様に実装してみています。インストール自体はnpmに登録しているので

$ npm install slack-cli-stream

で完了するはず。壊れていたらごめんなさい。

インストールした後、以下の様なYAMLを準備します。tokenはSlack API呼び出し用、hookは --hook オプションを付けたときに実行したいコマンドを記載するので、hookにはhook毎にPixelaに対して呼び出し毎に数値をインクリメントできる様にcurlコマンドでインクリメントのAPIを呼び出す記述を書きます。

token: xoxp-*****-*****-*****-*****
hook: curl -X PUT https://pixe.la/v1/users/hideack/graphs/slack-message/increment -H 'X-USER-TOKEN:******' -H 'Content-Length:0'

上のYAMLを準備して下の様にオプションを指定してターミナルで起動させておきます。*2

$ slack-cli-stream -u terry -s ~/.slack.yaml --hook

そうすると、Slack上の自分の発言( -u オプションで指定したユーザ ) だけがターミナルに流れていく様になって、且つメッセージ受信のタイミングでYAMLに書いたコマンドをフックしてくれることから、YAML中で指定したコマンド(curlでPixelaのAPIを叩く)が実行されるのでそのタイミングでPixela側に反映されていきます。

ある程度数字貯めた後眺めると、この日Slackでたくさん喋ったなとか一日中席外していたな。とか振り返れるかなと思ったりしています。一応このブログのサイドバー(スマホであればフッタ)に表示させてみているのでグラフに更新がなかったら何かあったと思ってください(!)

今回は自分自身の発言数を記録したのですが、もう少しだけ機能足せば自分が観測している範囲のSlackでの :+1: の数を記録していって良い話題が多かったなとかも観測できそうだなと思ったりしています。

f:id:hideack:20181202102932p:plain
起動させた様子

f:id:hideack:20181202102810p:plain
記録された様子。一週間試してみたので既にいくつか記録されてる。

*1:ユーザー登録とかもAPIで完結してるのがとてもよいと思った。こういうサービス作ってみたい。

*2:弊社で稀に生じるSlack IDとソーシャルで使ってるIDが異なる問題によりSlackで指定するIDとPixelaで取得したIDが異なる

Franzに任意のアプリケーションを追加する(プラグインの作成と追加)

Franzという複数のメッセージング・コミュニケーション系のサービスを1つにまとめて管理することができるツールを最近使っている。

meetfranz.com

もともと社内ではSlackを利用しているのでSlackアプリを使って過ごしていたのだけれども、だんだん社外とのやり取りが増えるときにこれまではメールが主体だったのがChatworkだったりfacebookメッセンジャーだったりでコミュニケーションを取ることが増えてきた。そうしたときにChromeに複数のそういったアプリケーションをタブに複数開いて立ち上げておいてもよいのだけれども、やはり使っていくと不便が多い*1

そうしたときにFranzを使うとブラウザとは独立して管理することができるのでとても便利。但し、Franz自体にはデフォルトで65個のサービスを設定できる様になっているのだけれども、国内のみ普及しているサービスだったりするとデフォルトでは用意されていなかったりする。

そうしたときにFranzにはプラグインの機構があり任意のアプリケーションを追加することができる。一つだけ難があってFranzは内部ブラウザで別ウインドウのポップアップを出すことができないので、それが前提になっているアプリケーションはプラグインで追加した場合も実際に使うことができないので注意。この制限があって会社で利用しているワークフローシステムのアプリケーションはFranz上で使えなかった。残念。

プラグイン(レシピ)の作り方

plugins/integration.md at master · meetfranz/plugins · GitHub ここに書かれている通りなのでそこまで複雑ではない。必要なファイルは以下の通り。

  • package.json
  • icon.png
  • icon.svg
  • index.js
  • webview.js

これらのファイルを ~/Library/Application Support/Franz/recipes/dev 以下のアプリケーション名を入れたディレクトリを1段掘った後配置する。それぞれのファイルの内容を一番シンプルに書くと以下の様な感じになる。

package.json

設定ファイルとして利用していて、Franzに登録したいWebサービスに関する情報を記載する。npmのもの踏襲だが config , id あたりがFranz用に拡張されているところ。

{
"id": "(Franzで識別用に使われるID)",
"name": "(登録したいサービス名)",
"version": "1.0.0",
"description": "(このプラグインの説明)",
"config": {
"serviceURL": "(登録したいサービスのURL)",
"serviceName": "(登録したいサービス名・アプリケーション一覧掲載用)"
},
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git@github.com:xxx/franz-foo-plugin.git"
},
"author": "hideack",
"license": "MIT"
}

index.js

基本下の一行でよい。更にURLのバリデーションを行う場合にはここに処理を追記する。

module.exports = Franz => Franz;

webview.js

未読のメッセージ数をアイコンにバッジとして表示するなどのフロントエンド側の処理を記述することができる。Franzに登録ができる様にするWebアプリケーション中にある未読数等の要素を抜き出し、バッジとして設定することができる。

module.exports = (Franz) => {
const getMessages = function getMessages() {
/*
    // ex.
    const elements = document.querySelectorAll('.msg-unread-count');
    let count = 0;
    if (elements[0]) {
      count = parseInt(elements[0].innerHTML, 10);
    }
    */
count = 1;
Franz.setBadge(count);
};
Franz.loop(getMessages);
};

*1:誤ってタブ閉じたり、開きそこねていて連絡が来ていたのを気づかなかったりいろいろある