Help us understand the problem. What is going on with this article?

今年一年間でGoogle App Scriptで作った自分用アプリの復習記

注意

この記事には以下の要素が含まれています。ご了承ください。
Google App Scriptで一年間適当にアプリを作った人の振り返り
適当なコメント、解説
GASについての個人的な見解
くだらない茶番

お前誰や

私はufoと申します。
私は日々の生活の中での「めんどい」と「これほしい」の解決のためにプログラムを書いている、いわば趣味です。
他のQiitaのユーザー様のような専門性?そんなものは皆無ですね。
ですのでそんな未熟な私が、この1年の主に空き時間で作った、ちょっと便利になるようなならないようなGASアプリたちを復習して、
得た学びや来年どうしたいか考えていきたいと思います。
あとは皆さんのなにかのきっかけにでもなればなと思います。

今年一年で作ったGASアプリたち

Line Bot系

・学習時間記録ツール
・反復メモ
・PCstate

Googleカレンダー系

・出勤番号適用ツール

WEBサイト系

・アンケート集計・閲覧ツール
・会計管理支援ツール
・学習量記録ツール
・その他色々

こうやって見ると覚えている範囲でも意外とたくさん作ってたのではないでしょうか。

ちなみに

他のものを含めると30こぐらい作ってました。
大半はPythonのもので部分的にUWPやVBAなどにも手を出していました。

振り返り編

振り返るにあたり、以下のような観点で振り返ることにした。
・作成期間
・作った理由
・設計の流れ
・つまずきポイント
・推しポイント
・使い心地・運用状況
・改善点
それでは、上から順に振り返っていきましょう。

Line Bot系-学習時間記録ツール

回想-3月のある日のこと

私:あー暇だなぁ。
友人氏:暇そうなあんたにちょいとお願いがあるんだけど。
私:なんぞ。
友人氏:Lineのトークルームにおける学習時間記録サービスがほしいんだが。
私:Bot?すたでぃなんとか(スタディプラスのことを指していると思われる)でよくね?
友人氏:アプリいれんのめんどい。今度牛丼おごるから頼むわ。
私:わーかったよ。1日ぐらいでそれっぽいもの作るわ。

製作期間

基本機能の実装:半日
改善策の実行:3日

どんな感じか?

ざっくりすぎるフロー図
image.png
sample動画
https://youtu.be/0LOemLW4RR0
Lineアカウント
試験運用のためやむおえず告知なく削除されるかもしれません。
image.png

機能

・記録
・記録データ閲覧
・直近ので記録消去
・グラフの作成
・WEBUIへのアクセス制限

謎のコード解説はこちらー長いので注意

記録機能"rec"

実装コスト☆4
コード

function record(data,userId,reply_token){
  var user_pos=db_user_search(userId);
  var data_c=dbsh.getRange(4,user_pos).getValue();
  var study_time=data[1];
  var time_mes=data[2];
  if (isNaN(study_time)){
    var send ="パラメータに数値が入力されていません。\nなお漢数字は使用できません"
    }else{
  if (time_mes==="h"){
    study_time=study_time*60
  }
  dbsh.getRange(data_c,user_pos).setValue(study_time);//時間書き込み
  var info=cmdsh.getRange("N1").getValue();
  if (info===undefined){
  var send="お疲れ様でした。"+ userdata_get(user_pos)+"さんは今回"+study_time+"分勉強しました。\n これであなたは、今日トータルで"+ dbsh.getRange(3,user_pos).getValue()+"分しました。\n次も頑張りましょう。"
  }else{
   var send="お疲れ様でした。"+ userdata_get(user_pos)+"さんは今回"+study_time+"分勉強しました。\n これであなたは、今日トータルで"+ dbsh.getRange(3,user_pos).getValue()+"分しました。\n次も頑張りましょう。\n"+info}
    }  
      //メッセージを返信    
   send_message(send,reply_token);
}

・難所
ユーザー管理の方法
主にユーザーのデータのある位置をどのように見極めるのかが難しい。
対処法

function db_user_search(userId){
setsh.getRange("B5").setValue(userId);
  Utilities.sleep(100);
return setsh.getRange("A5").getValue();  
}

は?なにこれ
過去の私の対処方法がなんにもわからないのですが、
多分設定用シート内にある特定の関数の入ったセルに、useridを入れて参照しているようにしか見えないのですが。
これは、改善必須ですね。
(複数のリクエストが来たときにバグるのが目に見えてるし、なんでDBシートを配列化して探さないんでしょうか、この人は)

削除・記録表示機能

実装コスト☆2
コード
記録表示"ch"

function show_user_log(userId,reply_token){
  var user_pos=db_user_search(userId);
  var weekly_log=dlogsh.getRange(4,user_pos).getValue();
  var total_log=wlogsh.getRange(3,user_pos).getValue()+weekly_log;
  if (total_log!=0){
  total_log=total_log;
  }else{
  total_log=0;
  }
  var send=userdata_get(user_pos)+"さんの記録データはこんな感じです。\n 直近1週間(今日を含まない)|"+weekly_log+"時間 \n 全統計(毎週月曜更新)|"+total_log+"時間でした。"
send_message(send,reply_token);
}

削除”rem”

function remove_action(userId,reply_token){
  var user_pos=db_user_search(userId);
  var data_c=dbsh.getRange(4,user_pos).getValue();
  if(data_c<=11){
    var send ="削除できる記録はありません。\nユーザーデータの削除は「remove_request」と実行し管理者にお知らせください。\n後日管理者が削除いたします。"
    } else{
    var del_data=dbsh.getRange(data_c-1,user_pos).getValue();
    var send="直前の記録を削除しました。直前の記録は("+del_data+")です。";
    dbsh.getRange(data_c-1,user_pos).clear();  
    }
     send_message(send,reply_token);
}

これといった難所はなさそうですが、ここにも例の残念関数があるんでなんとも言えません。

友達追加時処理

実装コスト☆3
コード

function follow_init(userid){
  setsh.getRange("B3").setValue(userid);
  if(setsh.getRange("A3").getValue()!="#N/A"){
  }else{
  var db_user_n=setsh.getRange("A4").getValue()+2
    dbsh.getRange(1,db_user_n).setValue(userid);
    dbsh.getRange(2, db_user_n).setValue("user名"+user_pos);
  Utilities.sleep(100);
  var colm=address_serch(db_user_n); 
  dbsh.getRange(3,db_user_n).setValue("=sum("+colm+"11:"+colm+")");
  dbsh.getRange(4,db_user_n).setValue("=COUNTA("+colm+"11:"+colm+")+11");
  wlogsh.getRange(3,db_user_n).setValue("=sum("+colm+"4:"+colm+")/60");
  wlogsh.getRange(4,db_user_n).setValue("=RANK.EQ("+colm+"3,$B$3:$3)"); 
  dlogsh.getRange(3,db_user_n).setValue("=RANK.EQ("+colm+"4,$B$4:$4)");
  dlogsh.getRange(4,db_user_n).setValue("=sum("+colm+"5:"+colm+")/60");
}
}

例によって関数の山が沢山有りますが概ねかんたんでした。

強いて言うなら待機時間をはさみ忘れてうまく動かなかった事例はありますけど。
Utilities.sleep(100);

グラフ生成機能”dg”

一番苦労させられた機能といっても過言ではない。
実装コスト☆6
コード

function wchart_make(userId,reply_token){
var date = new Date();
  var ori = 'https://drive.google.com/uc?export=view&id=';
var pre  = 'https://drive.google.com/thumbnail?sz=w240-h240&id=';
var filename=userId+Utilities.formatDate( date, 'Asia/Tokyo', 'yyyy/MM/dd|HH:mm:ss')+"_week.jpeg"
  var user_pos=db_user_search(userId)
  var colm=address_serch(user_pos); 
  var user_range=colm+"5:"+colm
 var chart = wlogsh.newChart()
  .asLineChart()
  .addRange(wlogsh.getRange('A5:A'))
  .addRange(wlogsh.getRange(user_range))
  .setMergeStrategy(Charts.ChartMergeStrategy.MERGE_COLUMNS)
  .setTransposeRowsAndColumns(false)
  .setNumHeaders(-1)
  .setHiddenDimensionStrategy(Charts.ChartHiddenDimensionStrategy.IGNORE_BOTH)
  .setOption('title', userdata_get(user_pos)+"の記録")
   .setOption('legend.position', 'none')
  .setOption('bubble.stroke', '#000000')
  .setOption('annotations.domain.textStyle.color', '#808080')
  .setOption('textStyle.color', '#000000')
  .setOption('titleTextStyle.color', '#757575')
  .setOption('annotations.total.textStyle.color', '#808080')
  .setOption('hAxis.textStyle.color', '#000000')
  .setOption('vAxes.0.textStyle.color', '#000000')
  .setPosition(10, 2,300,60 )
  .build();
  var imageBlob = chart.getBlob().getAs('image/jpeg').setName(filename);
  //フォルダIDを指定して、フォルダを取得
  var folder = DriveApp.getFolderById('フォルダID');
  var file = folder.createFile(imageBlob)
  //フォルダにcreateFileメソッドを実行して、ファイルを作成
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
  var id=file.getId();
    UrlFetchApp.fetch(line_endpoint, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': reply_token,
      'messages': [{
        "type": "image",
        "originalContentUrl":ori+id,
        "previewImageUrl": pre+id
      },
      {
        "type": "text",
        "text":"週毎の記録のグラフです。\n単位は分で記述されています。\n日付から一週間のトータルの時間が表示されます。"
      }],
    }),
  });
}

処理の流れとしては、
グラフ生成=>ドライブに保存=>共有設定=>送信
という流れですがグラフ生成がきつく、結局スプレッドシートのマクロ機能を利用して、コードを作り、範囲だけ変数に置き換える形にしました。

またドライブに保存処理は、
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
が肝になっています。
これは共有設定を誰でも閲覧可能に設定してあり、
https://drive.google.com/uc?export=view&id=
を利用して画像にしています。

その他機能

その他機能については尺の都合でざっくり割愛するのですが、あとから出てくるWebサイト系で少しだけお話しようかなと思います。
製作難易度はそんなに高くなくて、気楽に作ることができました。


推しポイント・使い心地・運用

このアプリの推しポイントは、Lineで気軽に学習状況を記録でき、更にGoogleAssistantなどからのLine メッセージ送信でも登録できるところかなと思います。
ただ、使い心地は、使ってない期間が長いと反応速度が下がり快適性が損なわれるところ、
グラフ生成などがちょっと重いところです。
一方で運用コストは高くなく、毎日一回の日別統計の集計を自動で行うように設定しているため、ほとんど何もしていません。
またグラフの画像も、1週間で自動削除されるためそんなにストレージの心配もいらないため、結構気楽に運用できます。

改善点

改善点は前述の通り。関数で運用しているところの部分改善が必須レベルなのではないかと思います。
あとは機能追加として、タイマーやタグ付けなども対応すべきかなと思いました。
あとは牛丼を早くおごってもらうことです。

Line Bot系-反復メモ

コードがない=>今は使ってないアプリなので軽くだけ。
・製作経緯
リマインダーがほしい=>Lineに投げればいいのでは
・機能
時間+内容を書くことで指定時間後にラインで通知してくれる。
・推しポイント・使い心地・改善点
練習用で作ったので押せる要素がない。
使い心地は入力が遅い人にはいいけど早い人ならGoogleカレンダーで十分。
改善できる余地が殆どない。

Line Bot系-PCstate

これもメモと同様コードはどこにも有りませんでした。
確か家にあったさーばーPCが壊れたときに捨ててしまっています。
・作成経緯
PCとPCサーバー(NASとか)の生存確認するために、Httpリクエストをして情報を取得してほしい。
・仕組み
エラー時と定期通信のときに接続できなければボット側に通知させる
特定のメッセージを送るとHttpリクエストを送って、中身を返信する。
・推しポイント・難所・使い心地
このときにメシューを実装してみたけどハードルが高くて諦めそうになった。
むちゃくちゃ便利
どうあがいても返信時間が間にあわない問題の対処が大変だった

Googleカレンダー系-出勤番号適用ツール

回想

私:カレンダーにいちいちバイトのシフト入力するの面倒くさいなぁ。
友人氏:アプリじゃだめなの
私:Googleカレンダーと同期できんじゃん
あっ。GAS組めばいいや。

仕組み

コード
const datasheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('form');
const setsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('setting');

function num_spilit(){
   Utilities.sleep(3000);
  var leastrow=setsheet.getRange("A1").getValue();
  // 分割する数値
  var beforeText = String(datasheet.getRange(leastrow, 3).getValue());
  // 数値を文字列に変換して、一文字ずつ分割
  var beforeTextArr = String(beforeText).split('');
  // 分割したテキストを「-」で区切る
  var afterText = '';
  for (var i = 0; i < beforeTextArr.length; i++) {
    afterText += beforeTextArr[i] + ',';
  }
  // テキストの挿入
 datasheet.getRange(leastrow, 5).setValue(afterText);

  //日付情報の整形
  var Y_M=day_check(datasheet.getRange(leastrow, 2).getValue());

  datasheet.getRange(leastrow, 4).setValue(Y_M);

  var event_item=datasheet.getRange(leastrow, 5).getValue().split(",");
  //イベント作成
  Logger.log(event_item);
  createEvents(event_item,Y_M);
}

function day_check(reqmonth){
  var today = new Date();
  var month =Number(Utilities.formatDate( today, 'Asia/Tokyo', 'MM'));
  var select_month=reqmonth.replace(/[^0-9]/g, '');
  if (select_month<month){
  var year=Number(Utilities.formatDate( today, 'Asia/Tokyo', 'YYYY'))+1;
  }else{
  var  year=Number(Utilities.formatDate( today, 'Asia/Tokyo', 'YYYY'));
  }
  return year+"/"+select_month+"/"
}


function createEvents(eventlist,Y_M) {
  var calender_id=setsheet.getRange("A2").getValue();
  var calendar = CalendarApp.getCalendarById(calender_id);
  var count=eventlist.length;
  for (var i=1;i<count;i++)
  {
  var title = String(eventlist[i-1]);
  var tnum=Number(eventlist[i-1]); 
     Logger.log(title);
    if (tnum>0&tnum < 5){
      //朝
  var startTime = new Date(Y_M+i+"/08:00:00");
  var endTime = new Date(Y_M+i+"/15:00:00");
  var option = {
    description: '午前出勤',
  }
  Logger.log(title+"/"+startTime+"/"+endTime+"/"+option)
  var event= calendar.createEvent(title, startTime,endTime, option);
  var color = setsheet.getRange("A3").getValue(); 
  event.setColor(color);

    }else{if(tnum===0){
  var date = new Date(Y_M+i);
  var option = {
    description: '休み',
  }
  var event= calendar.createAllDayEvent(title,date, option);
  Logger.log(title+"/"+startTime+"/"+endTime+"/"+option)
  var color = setsheet.getRange("A5").getValue(); //"11"でも同じ結果
  event.setColor(color);      
    }else{if(tnum===9){
      //9
  var startTime = new Date(Y_M+i+"/08:00:00");
  var endTime = new Date(Y_M+i+"/15:00:00");
  var option = {
    description: '館内整理日',
  }
  var event= calendar.createEvent(title, startTime,endTime, option);
  Logger.log(title+"/"+startTime+"/"+endTime+"/"+option)
   var color = setsheet.getRange("A6").getValue(); //"11"でも同じ結果
  event.setColor(color);     
    }else{
      //午後
  var startTime = new Date(Y_M+i+"/12:30:00");
  var endTime = new Date(Y_M+i+"/19:00:00");
  var option = {
    description: '午後出勤',
  } 
  var event= calendar.createEvent(title, startTime,endTime, option);
  Logger.log(title+"/"+startTime+"/"+endTime+"/"+option)
  var color = setsheet.getRange("A4").getValue(); //"11"でも同じ結果
  event.setColor(color);
    }
         }}
 Utilities.sleep(1000);}
}


入力に、Google formを利用してこんなふうなFormを作成しました。
image.png
image.png

このFormが送信されたのをイベントのトリガーにして、
各番号を1文字ずつ分離、
各項目をイベントとして登録します。

推しポイント・使い心地・運用

推しポイントは数字入力だけで一括登録できる点で、使い心地は、
ミス入力時の対処以外は概ねいい出来だと思います。
運用も完全に放置していますが月イチで問題なく使えています。

改善点

WEBUIにしたほうが使いやすい気がする。
削除機能の実装

WEBサイト系ーアンケート集計・閲覧ツール

個人的に大作になってしまったものシリーズ。

回想

私:GoogleFormのアンケート集計は一部データは公開したいんだよなぁ。
でもアンケート内にこじんじょうほうはいってるからなぁ。
あ、GAS組めばいいか。
友人氏:EXCELにコピペでいいのでは。

見た目

ログイン前
image.png
image.png
image.png

ログイン後
image.png
image.png

主な機能

・ログイン管理
・グラフの生成(Chartjs)
・テーブルの生成・検索(tablesorterとか色々)
.参考文献などの表示機能

難所

・テーブルソートのためのJSを読み込ませると、Bootstrapのメニューバーがバグり機能しなくなる問題を解決するのに、一週間ぐらいかかってほんとうにきつかった。
・テーブルまわりの処理全般
・ログイン処理には若干のセキュリティ的不安があり、
とりあえずセッションIDみたいななにかをつけて4時間毎に強制ログアウトさせるようにしておいた。
この処理実装が大変だった。
・Chartjs用にデータを組み直すのが大変だった。

推しポイント・使い心地・運用

テーブルのみやすさ、探しやすさがかなりよく、グラフもいちいち作らなくても自動で最新のものを作ってくれて便利。
運用はたまに新しく増えたデータに、重複データがないか確認するぐらい。
しかも重複だとはっきりわかるものは自動で避けてくれるので便利。

WEBサイト系・会計管理支援ツール

これはいつの間にか別のもののために使われてしまっていたので原型は有りませんがざっくりと思い出します。

製作経緯

知り合い:お前今度のイベント会計よろー
私:レシート管理メンド・計算メンド・渡すやつバラバラすぎるだろ。
そうだWEBアプリにして各人にやらせよ。

機能

https://www.glideapps.com/
をUI代わりにGoogleFormにデータを登録させ、
GoogleFormのデータをGlide用に変形する処理をGASが行い
GlideでUIとして閲覧、編集できるようにした。

推しポイント・使い心地・運用

昔のバージョンで作っていたので、データの行数制限がなかった気がするが、今はあるせいで使いにくい。
UIが残念になってないところだけは褒めていいと思う。
運用自体はUIを組めていればほとんど何もしなくていい。

WEBサイト系・学習量記録ツール

実はこのツールは自分の投稿に一度上げていることが判明。
https://qiita.com/haraday/items/e4147449b8a812ca7c63
ですので今回は軽く機能と使い心地だけ書いときます。

機能

・スプレッドシートを利用した、学習管理票の作成。
目次
image.png
・目標時間内に追われているかの判定機能・タイマー機能
問題選択画面
image.png
サイドバー表示
image.png
記録画面
image.png
image.png
記録編集画面
image.png
外部からも見れるようにするえつらんUI機能(記録操作などが無効)

推しポイント・使い心地・運用

今でも使っているぐらいなので、そこそこに便利。
推しポイントは問題の写真情報を保存できるところかな。
運用自体は、新しい学習管理票をたさなければ不要。
足すのはちょっと手間かな。

難所

タイマーと記録削除と画像の管理に大苦戦させられた。
タイマーはスタートボタンが複数回押せてしまうのを防ぐために消えるようにし、
記録削除は、記録削除ボタンを押したら、削除用のUI情報を再更新できるようにした。
画像管理は、来年どこかで扱いたいほど重いのでパスします。

で、結局何が言いたいのか

まず

ここまでダラダラと振り返りをよんでくださった皆様お疲れさまでした。
こいつくだらないもの作ってんなぁ。
って思ったら今からでも遅くないので、
あなたの今年の個人的に作ったものを是非Qiitaで紹介してください。
このくだらないように見えるもので発想が湧いた人は、
気楽に作ってみるのもいいのではないでしょうか。

For初心者・未経験者

私はこの記事で皆さん(特に私にはプログラムを組むなんてむずそう、、、って思っている人)にGASのメリット3つを伝えたかったのです。
1・比較的簡単
GASの元言語はJavascriptであり、WEB上に信じられないほど膨大な日本語の情報があり、気軽に学習でき、GAS以外の他のことにも使えるというまさに万能。
2・拡張・準備がかんたん
常時運用するためのコストがものすごく低いため、
仲間内でのオンラインでの飲み会のときの余興で使うおもちゃにでもしたり、
日々のツールとして運用するために一切お金がかかることがなく、
GCPやAWSなどの無料枠運用をチキっている人にもおすすめです。
また、スプレッドシートを始めとするGoogle製サービスとの連携が非常に楽で、無駄な認証処理などに悩ませる必要も減ります。
3・運用が楽
冒頭の振り返りを見たもらった人には伝わると思うのですが、運用というほど運用していません。
放置で問題ないことがほとんどです。


以上伝えたいこと3つでした。

for Everyone

個人的にはGAS最高なんですが、GoogleDriveの仕様変更でスプレッドシートも容量加算対象になるので結構辛い気がします。
なんかいいサービスないかまた検討しようかな。

来年の抱負

GCPをもっとううまく使いたい
Rubyやりたい
PythonでwindowsAPP
その他諸々作れればいいなぁ(機械学習とか)

最後に

残すところ2020もわずか。
世間は厳しい状況が続いているところですが、そこに新たな活路、方法、趣味、ビジネスが生まれることを祈念してこの記事を閉めさせていただこうと思います。

Thank you for your reading

haraday
気ままに欲しい物を作る。 ほぼ趣味でプログラミングしている人です。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away