はじめに
日本語プログラミング言語「なでしこ」は、初学者でも簡単にプログラミングできる言語として有名ですが、元々は事務処理を簡単にするために開発されました。
しかし、今やクラウド全盛期時代。仕事に関するデータも、多くはクラウド上で管理するようになりました。加えて、クラウドベースで動くことを想定したChromeOSを4割の子ども達が使っており、クラウドベースの事務作業等も増えるのではないかと予想されます。
事務処理を簡単にするために開発された「なでしこ」を、クラウドベースの事務作業でも使いたい!という人が居てもおかしくはありません。そこで今回は、クラウド上でプログラムを実行させられるGoogle Apps Script(通称GAS)を使って、なでしこを動かしてみたいと思います。
そもそもGASって?
一言で言えば、Googleのサービスを自動化するためのスクリプト言語です。一般ユーザーでもある程度までは無料で利用でき、加えてGoogleの各種サービスに接続できるため、クラウドベースの事務作業を自動化するにはぴったりです。しかも、GAS自体がJavascriptをもとにしているため、汎用性が高いという特徴があります。
つまり、「なでしこ」の構文をJavascriptに変換して実行しているなでしこ3は、GAS上で実行できる可能性があるということです。
「こんにちは」を表示するまで
なでしこ3には、様々なランタイムが存在します。
とはいえ、当然のことながらGAS向けはありません。そこで、Webブラウザ向けのwnakoをとりあえず使ってみることにしましょう。GASにおいて、Javascriptで書かれたライブラリを実行するには、スクリプトIDを使って、GAS上に登録されているライブラリを追加する方法が王道です。しかし、公式のものかどうか、コードが改変されていないか、アップデートに対応しているのか等、様々な懸念事項があるため、今回は、邪道な方法を取ることにしました。
なでしこ3のwnako本体(wnako3.js)は、ネット経由で配布されています。そこで、これを取得して、GAS上で動作させようとしました。ただ(当然なのですが)、wnakoはWEBブラウザに取り込まれることを想定しており、以下のような問題点がありました。
-
<script type='なでしこ'></script>
のタグの中に書かれた文字列をなでしこのプログラムとして解釈する設計となっている - WEBページには存在するwindowやdocumentといったオブジェクトがGASには無い
そこで、なでしこのソースコードを読みながら、どのような機能が使われているのか調べ、GAS上でなんとか動作するテンプレートを作成しました。
const document={
querySelectorAll: function(e){
return [{text: "「こんにちは!なでしこさん!」をコンソール表示", type: "なでしこ", src: "wnako3.js?run"}];
}
};
const window={addEventListener: (e, func, _)=>{func({});}};
const navigator={userAgent: "Google Apps Script"};
function myFunction() {
eval(UrlFetchApp.fetch('https://cdn.jsdelivr.net/npm/nadesiko3@3.3.71/release/wnako3.js').getContentText());
}
このコードをGASに貼り付けて、myFunctionを実行することで、3行目の途中に書いてあるように、「こんにちは!なでしこさん!」をコンソール表示
が、なでしこのプログラムとして解釈され、実行されます。
もっと短いテンプレート
上述したテンプレートを作成したときは気がついていなかったのですが、document
をわざわざ偽装せずとも、navigator.nako3
にWebNakoCompiler
が入っているので、直接runAsync
を実行すれば良いことに気が付きました。そこで、次のような「もっとも短いテンプレート」を作成しました。
const window={addEventListener: ()=>{}};
const navigator={userAgent: ""};
function myFunction() {
eval(UrlFetchApp.fetch('https://cdn.jsdelivr.net/npm/nadesiko3@3.3.71/release/wnako3.js').getContentText());
navigator.nako3.runAsync("「こんにちは!」とコンソール表示。", "");
}
単純に「こんにちは!」と表示させる場合は、このコードが最小構成だと思います。ただし、runNakoScript
を呼び出しているわけではなく、loadDependencies
等のメソッドも実行されていないので、どういった不具合があるのかはよく分かりません。
テンプレートの改良
とはいえ、このままでは、毎回毎回Contents Delivery Network(CDN)からwnakoを貰ってくることになります。そこで、ちょっとだけテンプレートを改良して、wnakoをキャッシュしてもらうことにしました。
const document={
nadesiko_text: `「こんにちは!なでしこさん!」をコンソール表示`,
querySelectorAll: function(e){
if(e=="script") return [{text: this.nadesiko_text, type: "なでしこ", src: "wnako3.js?run"}];
}
};
const window={addEventListener: (e, func, _)=>{if(e=="DOMContentLoaded") func({});}};
const navigator={userAgent: "Google Apps Script"};
function useNadesiko3() {
eval((()=>{
const cache=CacheService.getScriptCache();
const cache_keys=cache.get("nadesiko3_cache_control");
if(cache_keys!=null){
const cache_keys_array=[];
for(let i=0; i<cache_keys-0; i++) cache_keys_array.push(i);
return Object.values(cache.getAll(cache_keys_array)).join("");
}else{
const cdn=UrlFetchApp.fetch('https://cdn.jsdelivr.net/npm/nadesiko3@3.3.71/release/wnako3.js').getContentText();
const cdn_array=cdn.match(/.{1,99999}/g);
cache.putAll({"nadesiko3_cache_control": cdn_array.length+"", ...cdn_array}, 60*60);
return cdn;
}
})());
}
function myFunction() {
document.nadesiko_text=`
//↓ここになでしこのコードを書く↓
「こんにちは」と、コンソール表示
//↑ここになでしこのコードを書く↑
`;
useNadesiko3();
}
利用する際には、myFunctionを選んで実行すると、//↓ここになでしこのコードを書く↓
より下の部分が、なでしこのコードとして解釈され、実行されます。ポイントとしては、Googleのキャッシュサービスのvalue値が、String型しか受け付けていなかったので、cdn_array.length+""
として文字列化して保存しています。
事務処理活用例
さて、ようやく、なでしこを使うことができるようになりました。ここからは、「GAS × なでしこ」のサンプルコードを紹介したいと思います。
例1:傘アラート
前に私が書いた記事で、例3として紹介していた、「天気予報APIを使った傘アラート」をGAS上のなでしこに移植してみました。
document.nadesiko_text=`
●(《URL》から)HTTP取得処理とは
「UrlFetchApp.fetch("{《URL》}").getContentText()」をJS実行。
ここまで
《URL》は「https://api.open-meteo.com/v1/forecast?latitude=34.6872&longitude=135.5258&daily=precipitation_probability_max&timezone=Asia%2FTokyo&forecast_days=3」。
《URL》からHTTP取得処理して、JSONデコード。
《データ》は、それ。
《明日の降水確率》は、《データ》["daily"]["precipitation_probability_max"][1]。
もし、《明日の降水確率》が0以上ならば、《傘が必要?》は「傘は要らないでしょう。」。
もし、《明日の降水確率》が40以上ならば、《傘が必要?》は「傘はあると良いでしょう。」。
もし、《明日の降水確率》が60以上ならば、《傘が必要?》は「必ず傘を持っていきましょう。」。
「明日の大阪市の降水確率は{《明日の降水確率》}%です。{《傘が必要?》}」をコンソール表示。
`;
なでしこにもともと入っているHTTP取得
は、グローバル関数であるfetch
を実行しようとするので、GAS上では動きません。そこで、独自の関数HTTP取得処理
を作り、そこでJS実行しています。
JS実行を使わないバージョン
GASのfunction myFunction() {
の上などに、次のようなコードを書くと、JS実行を使わずとも、実現することができます。
const fetch=(e)=>{
return {text: ()=>UrlFetchApp.fetch(e).getContentText()}
};
function myFunction() {
document.nadesiko_text=`
《URL》は「https://api.open-meteo.com/v1/forecast?latitude=34.6872&longitude=135.5258&daily=precipitation_probability_max&timezone=Asia%2FTokyo&forecast_days=3」。
《URL》からHTTP取得して、JSONデコード。
《データ》は、それ。
(以下略)
「fetch関数がない?なら作ればいいじゃないか!(Javascriptで)」という発想になります。なでしこの中だけでは完結しませんが、本来、wnakoが使われている環境(ブラウザ上)にGASの環境を近づけているので、ある意味、正しい方法とも言えます。
例2:傘アラート(メール通知版)
前に私が書いた記事でも、上記の例1でも、致命的欠点がありました。それは、「能動的にしか見られない」ということです。つまり、前回の記事では、特定のWEBページを、上記の例1ではGASの「実行ボタン」を押さないと、傘アラートが見られませんでした。でも、面倒ですよね?毎朝7時~8時ぐらいに傘アラートを、メールで通知してくれると便利ですよね?
ということで、このあたりからがGASの本領発揮になります。以下に、サンプルコードを掲載します。
document.nadesiko_text=`
●(《URL》から)HTTP取得処理とは
「UrlFetchApp.fetch("{《URL》}").getContentText()」をJS実行。
ここまで
●(《宛先》へ《タイトル》の《本文》を)GMAIL送信処理とは
「GmailApp.sendEmail("{《宛先》}","{《タイトル》}","{《本文》}")」をJS実行。
ここまで
《URL》は「https://api.open-meteo.com/v1/forecast?latitude=34.6872&longitude=135.5258&daily=precipitation_probability_max&timezone=Asia%2FTokyo&forecast_days=3」。
《URL》からHTTP取得処理して、JSONデコード。
《データ》は、それ。
《明日の降水確率》は、《データ》["daily"]["precipitation_probability_max"][1]。
もし、《明日の降水確率》が60以上ならば、
《メールタイトル》は「【必要!!!】絶対持って行ってね!!!!」。
《傘が必要?》は「必ず傘を持っていきましょう。」。
違えば、もし、《明日の降水確率》が40以上ならば、
《メールタイトル》は「傘、あるといいね」。
《傘が必要?》は「傘はあると良いでしょう。」。
違えば、
《メールタイトル》は「傘は要らないよ!」。
《傘が必要?》は「傘は要らないでしょう。」。
ここまで。
「osane@mail.example.com」へ「{《メールタイトル》}」の「明日の大阪市の降水確率は{《明日の降水確率》}%です。{《傘が必要?》}」をGMAIL送信処理。
`;
ここで「osane@mail.example.com」には、傘アラートを受信するためのメールアドレスを設定してください。そしてこのコードを、毎日、決まった時間に自動実行させます。
自動実行の設定は、とても簡単で、GASのエディタ上で左のメニューの「トリガー」を押し、右下にある「トリガーを追加」で下記の図のように設定&保存すれば完了です。
このような、メールが届きます。
毎日決まった時刻に通知する方法
「毎日、7時ちょうどにメールしてほしい!」といった要望は多少なりともあるでしょう。残念ながら、GASのデフォルトのトリガーでは、「午前7時~8時」といった中途半端な時刻にしか動作させられません。例えばこの記事のようにGASのコードを追記するのが一般的です。もちろん、なでしこのJS実行
を使えば、何でもできるようにはなりますが、JS実行
を使いすぎると日本語プログラミングの良さが段々薄れていってしまうので、ここでは紹介していません。
例3:チケット申し込みの当落メール
より、実務に近い、サンプルプログラムを作ってみましょう。今回の例は、「Google Form等でチケット申し込みの募集を掛けたものの、応募者多数で後から申し込んだ人に、断りメールを入れないといけない」という場面です。
例えば、Google Formの結果のスプレッドシートで、以下のようなものが手に入ったとしましょう。加えて、E列に用意可能な枚数も、手動で(あるいは関数を使って)なんとか入力できたとしましょう。
B列に書かれているメールアドレスをコピペして、1件1件メールを送るのは、とてつもない手間です。図は20人しか書かれていませんが、これが100人、200人となると、考えただけでも寒気がします。そこで、なでしこでこんなコードを作ってみましょう。
document.nadesiko_text=`
●(《宛先》へ《タイトル》の《本文》を)GMAIL送信処理とは
「GmailApp.sendEmail("{《宛先》}","{《タイトル》}","{《本文》}")」をJS実行。
ここまで
●(《URL》の)スプレッドシート全データ取得処理とは
「SpreadsheetApp.openByUrl("{《URL》}").getDataRange().getValues()」をJS実行。
ここまで
《URL》は「https://docs.google.com/spreadsheets/d/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/edit#gid=0」。
《URL》のスプレッドシート全データ取得処理。
《全データ》は、それ。《改行記号》は「\\n」。
《ありがとう文》は「この度は、チケットをお申し込みいただきありがとうございました。 {《改行記号》} 定員を上回る、沢山の方にお申込みいただきましたため、本会は、先着順でのご案内となりました。{《改行記号》}その結果、」
《用意できたよ文》は「枚のチケットをご用意させていただきました。{《改行記号》}{《改行記号》}当日、お会いできることを楽しみにしています。」
《ごめんね文》は「大変申し訳ありませんが、チケットをご用意させていただくことは出来ませんでした。{《改行記号》}{《改行記号》}また次回、お申しいただきますよう、よろしくお願いいたします。」
《全データ》を反復
もし、対象キーが0でなければ、
もし、対象[4]が0でなければ、
対象[1]へ「チケット申し込みについて」の「{対象[2]}様。{《改行記号》}{《ありがとう文》}{対象[4]}{《用意できたよ文》}」をGMAIL送信処理。
違えば、
対象[1]へ「チケット申し込みについて」の「{対象[2]}様。{《改行記号》}{《ありがとう文》}{《ごめんね文》}」をGMAIL送信処理。
ここまで。
ここまで。
ここまで。
`;
《URL》
には、前述のスプレッドシートのURLを記入してください。関数スプレッドシート全データ取得処理
では、指定されたURLに書かれている全てのデータを取得しています。これを反復で繰り返して、見出し行(対象キーが0の行)以外のところで、メールを送信しています。
成功すると、チケット申し込み時に記入したメールアドレスへ、このようなメールが届きます。
例4:勤務時間打刻システム
最後に、出勤・退勤時間を管理をするシステムをなでしこを使って作ってみましょう。GASにdoGet
関数を実装し、ウェブアプリとしてデプロイすると、WEBサーバの代わりにもなります。これを使ってGASとなでしこで、退勤システムを作ってみましょう。
このようなスプレッドシートがあるとします。シート「社員一覧」には、社員の名前の一覧があり、シート「出勤退勤」には、何時何分に出勤or退勤したのかが書かれています。これを自動化してみましょう。
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style type="text/css">
html, body{height: calc(100% - 8px);}
body{display: flex; justify-content: space-between;}
body > div{width: calc(50vw - 12px); height: 100%;}
#now_time{text-align: center; font-size: 5rem;}
.control_main_div{height: 40%; display: flex; justify-content: space-evenly; align-items: center;}
#control > div > *{font-size: 2rem;}
#control_button > button{width: 30%; height: 60%;}
#message_area{height: 10%; font-size: 1.5rem; text-align: center;}
</style>
</head>
<body>
<div style="background-color: #F2D16D; display: flex; align-items: center; justify-content: center;">
<div id="now_time"></div>
</div>
<div id="control">
<div class="control_main_div" id="username"></div>
<div class="control_main_div" id="control_button">
<button id="attend" style="background-color: #F26389">出勤</button>
<button id="leave" style="background-color: #5C82F2">退勤</button>
</div>
<div id="message_area"></div>
</div>
</body>
</html>
まずはこのようなhtmlファイルをGAS内に作ります。このファイルに、なでしこのプログラム(クライアント側で動作する部分)を書いていきます。《URL》
には、GASのWEBアプリのURLを書いておきます。また、<?!= employee_list ?>
では、サーバー側からもらった変数を埋め込んでいます(詳しくは後述)。
<script type="text/javascript" src="https://nadesi.com/v3/cdn.php?v=3.4.23&f=release/wnako3.js&run"></script>
<script type="なでしこ">
《社員リスト》とは変数。
《URL》は『https:/\/script.google.com/macros/s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/exec』。
●(《ターゲット》に《文字》を)表示処理とは
「{《ターゲット》}」のDOM要素ID取得して、「{《文字》}」をDOMテキスト設定。
ここまで。
●(《挨拶》で)出勤退勤処理とは
《社員リスト》のテキスト取得して、《社員名》に代入。
もし、《社員名》が「選んでください」でなければ、
「message_area」に「{《挨拶》}、{《社員名》}さん」を表示処理。
《出勤?退勤?》は「退勤」。
もし、《挨拶》が「おはようございます」ならば、《出勤?退勤?》は「出勤」。
《記録データ》は{}。
《記録データ》@「出勤退勤」は《出勤?退勤?》。
《記録データ》@「名前」は《社員名》。
《URL》へ《記録データ》をPOST送信。
違えば、
「message_area」に「正しい名前を選んでください」を表示処理。
ここまで。
10秒後には、
「message_area」に「」を表示処理。
ここまで。
ここまで。
WINDOWを読込した時には、
「username」にDOM親要素設定。
「<?!= employee_list ?>」をJSONデコードして、《社員データ》に代入。
《社員データ》に「選んでください」を配列追加して、セレクトボックス作成し、《社員リスト》に代入。
《社員リスト》に「選んでください」をテキスト設定。
「attend」のDOM要素ID取得。
それをクリックした時には
「おはようございます」で出勤退勤処理。
ここまで。
「leave」のDOM要素ID取得。
それをクリックした時には
「お疲れ様でした」で出勤退勤処理。
ここまで。
1秒毎には、
「now_time」に「{今日}<br>{今}」を表示処理。
ここまで。
ここまで。
</script>
この例を作っている時(開発時には、それをクリックした時には
の中で《社員リスト》
を参照していました)、変数《社員リスト》
をグローバル変数として定義しておらず(WINDOWを読込した時には、
の中で宣言していた)、結構な時間が吹っ飛びました。なでしこには、スコープチェーンの仕組みがないんですね...
このあたりは、単にWebページになでしこを埋め込んでいるだけなので、そこまで難しくはないかと思います。このWebページを表示させると、次の写真のようになります。
あと、必要なものは「1. 社員一覧を取得してindex.htmlに埋め込む機能」「2. 出勤or退勤データをPOSTされた時にスプレッドシートへ書き込む機能」です。この2つはgsファイル(サーバ側)の方で実装します。
function doGet() {
const nadesiko_text=`
●(《URL》の)スプレッドシート接続処理とは
「SpreadsheetApp.openByUrl("{《URL》}")」をJS実行。
ここまで
●(《スプレッドシート》の《シート名》を)スプレッドシート全データ取得処理とは
DOCUMENT["all_get_temp"]=《スプレッドシート》。
「document.all_get_temp.getSheetByName("{《シート名》}").getDataRange().getValues()」をJS実行。
ここまで
《社員一覧》=[]。
《URL》は「https://docs.google.com/spreadsheets/d/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/edit」。
《URL》のスプレッドシート接続処理。
それの「社員一覧」をスプレッドシート全データ取得処理して、《全社員データ》へ代入。
《全社員データ》を反復
《社員一覧》に対象@1を配列追加。
ここまで。
DOCUMENT["result"]は《社員一覧》。
`;
document.nadesiko_text="";
useNadesiko3();
navigator.nako3.runSync(nadesiko_text, "");
const data=HtmlService.createTemplateFromFile("index");
data.employee_list=JSON.stringify(document.result);
return data.evaluate();
}
doGet
関数では、index.htmlを取得し、なでしこを実行した後に、HTMLファイルを返却しなければなりません。そのため、例1~3と同じように、useNadesiko3
から実行してしまうと、非同期実行になってしまい、なでしこで書いたプログラムが実行される前に返却してしまいます。そこで、navigator.nako3.runSync
から直接、同期的に実行し、完了してからHTMLファイルを返却する、ということを行っています。なお、data.employee_list
に代入した値が、前述した<?!= employee_list ?>
へ展開されます。
(なでしこ内で作った変数を外部のJavascriptから読み取る、賢いやり方ってあるのでしょうか...?今回は、とりあえず、documentなどのグローバル変数に入れて共有するようにしています。)
このプログラムでは、現在非推奨になっている機能「runSync」を使っています。私の環境では正常に動作しましたが、正常に動作しない or 機能自体が削除されることも考えられます。こちらのページを確認するようにしてください。(プログラムが得意な方は直してください...)
このコードを追加すると、次の写真のようになり、人を選べるようになります。
続いて、POSTされた時に実行されるdoPost
関数を作ります。こちらは、なでしこを実行してから、何かを返却する必要がないので、通常通りuseNadesiko3()
から実行しています。
function doPost(e){
document.doPost_arg=e.parameter;
document.nadesiko_text=`
●(《URL》の)スプレッドシート接続処理とは
「SpreadsheetApp.openByUrl("{《URL》}")」をJS実行。
ここまで
●(《スプレッドシート》の《シート名》へ《データ》を)スプレッドシート追加処理とは
DOCUMENT["post_temp1"]=《スプレッドシート》。
DOCUMENT["post_temp2"]=《データ》。
「document.post_temp1.getSheetByName("{《シート名》}").appendRow(document.post_temp2)」をJS実行。
ここまで
DOCUMENT["doPost_arg"]を《受信データ》へ代入。
《出勤退勤データ》=[]。
《出勤退勤データ》@0は「{今日} {今}」。
《出勤退勤データ》@1は《受信データ》["名前"]。
《出勤退勤データ》@2は《受信データ》["出勤退勤"]。
《URL》は「https://docs.google.com/spreadsheets/d/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/edit」。
《URL》のスプレッドシート接続処理。
それの「出勤退勤」へ《出勤退勤データ》をスプレッドシート追加処理。
`;
useNadesiko3();
return ContentService.createTextOutput("OK");
}
上手くいくと、出勤・退勤のボタンをWebページ側で押した時に以下の写真のように、スプレッドシートへ記録されていきます。
おわりに
例をいくつか見ているとわかる通り、GAS上でなでしこを実行する場合は、どうしてもJS実行
に頼るしかない部分がいくつか出てきてしまいます。とはいえ、GASとなでしこを使うと、様々なクラウドベースの事務作業を自動化できる可能性があるのも事実です。面倒な作業をなでしこで自動化してみてはどうでしょうか?
( あと、プラグインを作るほどの元気がなかったので誰か作ってください。 どちらかというと、必要なのはランタイムですね...)
最後に注意です。
今回作成したテンプレートのままでは、多くの命令が使えません。特にplugin_browser系はほとんどが使えないでしょう。
今回紹介したようなwnakoの使い方は、本来想定されていない(たぶん)ものです。実行は自己責任でお願いします。
以上。