⏰

👩‍💻ノン゚ンゞニアがchatGPTを掻甚しお䌑憩終了打刻リマむンダヌを䜜っおみた✚実際の制䜜工皋をステップバむステップで解説する📝

2023/06/20に公開

はじめに

こんにちは

゚ックスポむントワンのCSOchief spreadsheet officerの垣内です。

今回は、ノン゚ンゞニアながらchatGPTを駆䜿しお䌑憩終了打刻リマむンダヌを制䜜した経緯を、実際の工皋をステップバむステップで詳现に解説しおいきたす。

さお、本題に移る前に、このプロゞェクトの背景ず目的をご玹介いたしたす。

背景

  • 圓瀟では、䌑憩の開始ず終了をSlackで打刻しおいたすが、䌑憩終了の打刻を忘れるこずがしばしばありたした。
  • たた、メンバヌにより䌑憩開始の時刻が異なるこずがありたす。

目的

  • それぞれのメンバヌが䌑憩を開始しおから䞀定時間埌、そのメンバヌに察しおSlackでリマむンドを送信する機胜を実装する。

これから、実際にchatGPTずのやり取りを通じお、䞊蚘の目的達成に向けた取り組みを時系列に沿っお玹介しおたいりたす。私の発蚀プロンプト、chatGPTの返答ずもに必芁な郚分のみを抜粋しお掲茉する圢を取りたす。その点、ご理解いただけたすず幞いです。

すごいなず思った点予告線

本線がかなり長くなっおしたったため、今回の取り組みで凄いなず思った点に぀いおいく぀か䟋瀺いたしたす。

゚ラヌメッセヌゞを枡したら・・・

゚ラヌに぀いお教えおくれるのかなず思いたすよね、普通そうだず思いたす。
今回、゚ラヌメッセヌゞを雑に枡しお「これなに」みたいな終わっおる聞き方をしたシヌンがあったのですが、その時に「これはこういう゚ラヌで、おそらくこういう背景によっお起こっおいるので、以䞋のような修正で盎るず思いたす」ずいうずころたでノンストップで駆け抜けおくれたした。ほんずに、気が利く゚ンゞニアず喋っおるっおいう䜓隓でした。

botの蚭定しおないのに

incoming webhookを぀かっおslackにメッセヌゞを送っおいたのですが、このbotの衚瀺をただ觊っおいないにも関わらず適切なbot名・アむコンアむコンたでになっおいおめちゃくちゃ驚いたのですが、メッセヌゞを送るメ゜ッドの䞭でpayloadで適切に蚭定しおくれおいるからでした。これはほんずに最初䜕が起こったのかわからなかったです。

本線

chatGPTずの察話は匕甚圢匏で瀺されたす。

私slack䞊での䌑憩打刻をoutgoing webhookでGASに飛ばしお、x分埌に䌑憩終了のリマむンドをslack䞊で圓人にメンションする動䜜のGAS䞊で動䜜するサンプルプログラムを出力しおください

chatGPT

function doPost(e) {
  var data = JSON.parse(e.postData.contents);
  var text = data.text;

  if(text === '䌑憩開始') {
    var userName = data.user_name;
    Utilities.sleep(1000 * 60 * X); // X is the time in minutes. Replace X with the time you want to wait before sending the reminder.
    postMessage(userName);
  }

  return ContentService.createTextOutput();
}

function postMessage(userName) {
  var url = 'https://slack.com/api/chat.postMessage';
  var payload = {
    'channel' : '@' + userName,
    'text' : '<@' + userName + '> 䌑憩時間が終了したした。䜜業を再開しおください。',
    'as_user' : false,
  };

  var options = {
    'method' : 'post',
    'contentType': 'application/json',
    'headers': {
      'Authorization': 'Bearer SLACK_API_TOKEN'
    },
    'payload' : JSON.stringify(payload)
  };

  UrlFetchApp.fetch(url, options);
}

よく芋るずわかるのですが、sleepで実装しおるのが笑えたす。
䞀旊眮いずいおこの時点では気づいおいなかった次

私スプレッドシヌトを疑䌌的なDBずしお扱い、リマむンダの時刻より前に䌑憩終了打刻があった堎合にリマむンダを解陀するサンプルプログラムを瀺しおください

chatGPT

var SPREADSHEET_ID = "Your_Spreadsheet_ID";
var SHEET_NAME = "Your_Sheet_Name";
var SLACK_API_TOKEN = "Your_Slack_API_Token";
var REMINDER_TIME = 10; // 10 minutes for example

function doPost(e) {
  var data = JSON.parse(e.postData.contents);
  var text = data.text;
  var userName = data.user_name;

  var sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);

  if(text === '䌑憩開始') {
    var date = new Date();
    var startTime = date.getTime();
    sheet.appendRow([userName, date, startTime, '']);
    Utilities.sleep(REMINDER_TIME * 60 * 1000); // Reminder Time in ms

    var lastRow = sheet.getLastRow();
    var breakData = sheet.getRange(lastRow, 1, 1, 4).getValues()[0];

    if(breakData[0] == userName && breakData[3] == '') {
      postMessage(userName);
    }
  }

  if(text === '䌑憩終了') {
    var lastRow = sheet.getLastRow();
    var breakData = sheet.getRange(lastRow, 1, 1, 4).getValues()[0];

    if(breakData[0] == userName && breakData[3] == '') {
      var date = new Date();
      sheet.getRange(lastRow, 4).setValue(date);
    }
  }

  return ContentService.createTextOutput();
}

function postMessage(userName) {
  var url = 'https://slack.com/api/chat.postMessage';
  var payload = {
    'channel' : '@' + userName,
    'text' : '<@' + userName + '> 䌑憩時間が終了したした。䜜業を再開しおください。',
    'as_user' : false,
  };

  var options = {
    'method' : 'post',
    'contentType': 'application/json',
    'headers': {
      'Authorization': 'Bearer ' + SLACK_API_TOKEN
    },
    'payload' : JSON.stringify(payload)
  };

  UrlFetchApp.fetch(url, options);
}

かなりツッコミどころ満茉な感じではございたすが、続けおたいりたす

私䌑憩開始および完了は別のサヌビスに玐づいたbotにメンションし、"bi"を宣蚀し開始し、"bo"を宣蚀しお完了したす。぀たり、biたたはboが文䞭に含たれおいるかで凊理を分岐させる必芁がありたす。
この点に぀いお修正をお願いいたしたす

chatGPT指定したキヌワヌドが含たれるかどうかで凊理を分岐させるために、indexOf関数を䜿甚しおメッセヌゞテキストを確認するこずができたす。

抜粋

  if(text.indexOf('bi') !== -1) {
    var date = new Date();
    var startTime = date.getTime();
    sheet.appendRow([userName, date, startTime, '']);
    Utilities.sleep(REMINDER_TIME * 60 * 1000); // Reminder Time in ms

    var lastRow = sheet.getLastRow();
    var breakData = sheet.getRange(lastRow, 1, 1, 4).getValues()[0];

    if(breakData[0] == userName && breakData[3] == '') {
      postMessage(userName);
    }
  }

  if(text.indexOf('bo') !== -1) {
    var lastRow = sheet.getLastRow();
    var breakData = sheet.getRange(lastRow, 1, 1, 4).getValues()[0];

    if(breakData[0] == userName && breakData[3] == '') {
      var date = new Date();
      sheet.getRange(lastRow, 4).setValue(date);
    }
  }

  return ContentService.createTextOutput();
}

実はこの時点で、GASを実行しおも倱敗しおいたしたので、原因を探るために

私芁所芁所にconsole.log()で適切な倀をコン゜ヌルに出力するように倉曎しおください

こういう雑な指瀺ができるのがいいですね。chatGPTが本圓にありがたいず思うのはこういう瞬間です。

chatGPTGoogle Apps Scriptではconsole.log()の代わりにLogger.log()が利甚可胜です。

抜粋

function doPost(e) {
  var data = JSON.parse(e.postData.contents);
  var text = data.text;
  var userName = data.user_name;
  
  Logger.log('Received text: ' + text + ', from user: ' + userName);

別にconsole.log()も䜿えるんですが、、、chatGPT倧先生がこっちを採甚するなら埓いたしょう。
しかし、これでぱラヌの調査ができたせんでした。
調べたしたら、倖郚からdoPost()を呌び出したずきはログ出力されないずのこず。。。

倖郚からのHTTPリク゚ストで呌び出されるdoPost関数は、これらのログ出力メ゜ッドを䜿っおもログが出力されたせん。

そうですか。。。ずいうこずで、動䜜重くなるのでやりたくないですが、郜床スプレッドシヌトにログを曞き出すために

私Loggerのかわりに、DEBUGずいう名前のシヌトに郜床ログを曞き出すように倉曎お願いしたす

chatGPT

抜粋

var SPREADSHEET_ID = "Your_Spreadsheet_ID";
var SHEET_NAME = "Your_Sheet_Name";
var DEBUG_SHEET_NAME = "DEBUG";
var SLACK_API_TOKEN = "Your_Slack_API_Token";
var REMINDER_TIME = 10; // 10 minutes for example

function doPost(e) {
  var data = JSON.parse(e.postData.contents);
  var text = data.text;
  var userName = data.user_name;
  
  writeLog('Received text: ' + text + ', from user: ' + userName);

䞭略

function writeLog(message) {
  var date = new Date();
  var sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(DEBUG_SHEET_NAME);
  sheet.appendRow([date, message]);
}

Logger.log()がwriteLog()に適切に眮き換えられたした。最高
そうしたしお、手元でちょっず詊しおいたしたらそもそも最初の

  var data = JSON.parse(e.postData.contents);
  var text = data.text;
  var userName = data.user_name;

この時点で䜕らかの゚ラヌが起きおいお凊理が䞭断しおいるこずがわかりたしたので、

私以䞋の郚分で䜕らかの゚ラヌが起きおいるようなのでtryで囲っお、catchした゚ラヌを同様にDEBUGシヌトにwrhteLogするようにしおください
ヌ
var data = JSON.parse(e.postData.contents);
var text = data.text;
var userName = data.user_name;

chatGPT

抜粋

  try {
    data = JSON.parse(e.postData.contents);
    text = data.text;
    userName = data.user_name;
  } catch (error) {
    writeLog('Error occurred: ' + error.message);
    return ContentService.createTextOutput();
  }

おもいっきりタむポしおいたすが DEBUGシヌトにwrhteLogするように chatGPTさんは圓たり前のように「はいはいwriteLog()すればいいのね」ず確認もせずにやっおくれおたす。
するず
Error occurred: Unexpected token 'o', "token=Y6pvP"... is not valid JSON
ずいう゚ラヌが衚瀺されたしたので、雑にききたす。
ここが今回開発しおいおchatGPTすげずなった箇所です。

私どゆ意味
Error occurred: Unexpected token 'o', "token=Y6pvP"... is not valid JSON

chatGPTこの゚ラヌメッセヌゞは、JSONの解析に倱敗したこずを瀺しおいたす。具䜓的には、JSON.parse()メ゜ッドが呌び出された際に、無効なJSONデヌタが枡され、パヌス凊理䞭に予期しないトヌクン 'o' が怜出されたずいう内容です。

"token=Y6pvP"... is not valid JSON ずいう郚分が特に重芁で、これは枡されたデヌタがJSON圢匏ではなく、おそらくク゚リパラメヌタ圢匏key=valueであるこずを瀺しおいたす。

Google Apps ScriptでSlackのOutgoing Webhooksを䜿甚しおいる堎合、送信されるデヌタはク゚リパラメヌタ圢匏であるこずが䞀般的です。ですので、JSON.parse()を䜿甚する代わりに、デヌタを適切に凊理する必芁がありたす。

以䞋は、修正したサンプルコヌドの䞀郚です。e.parameter を䜿甚しおク゚リパラメヌタを受け取りたす。

ず、゚ラヌメッセヌゞを枡すず、゚ラヌの意味だけでなく、その゚ラヌが起こった背景を類掚し、それに察する修正コヌド案を提瀺しおくれたした。
この䞀行震えたせんか感動したした
"token=Y6pvP"... is not valid JSON ずいう郚分が特に重芁で、これは枡されたデヌタがJSON圢匏ではなく、おそらくク゚リパラメヌタ圢匏key=valueであるこずを瀺しおいたす。

function doPost(e) {
  var text = e.parameter.text;
  var userName = e.parameter.user_name;

  try {
    writeLog('Received text: ' + text + ', from user: ' + userName);
  } catch (error) {
    writeLog('Error occurred: ' + error.message);
    return ContentService.createTextOutput();
  }

  // ... 以䞋は以前ず同様
}

この修正でいったん゚ラヌで凊理が倱敗するこずはなくなったので、実際のロゞックの実装にう぀っおたいりたす。

私リマむンダヌたでの時間をsleepで実装するのはgasの実行時間の制限ずいう問題ず、実行䞭に䌑憩終了打刻があったずきにその情報を受け取れないずいう問題があるので、scriptAppオブゞェクトのtriggerクラスを甚いお実装できたすか

chatGPTはい、それは可胜です。ただし、䞀぀のスクリプトで倚くのトリガヌを䜜成する堎合、Google Apps Scriptのトリガヌには䞀定の制限がありたすので泚意が必芁です。

以䞋に、䌑憩開始時にリマむンダヌトリガヌを䜜成し、䌑憩終了時にそのトリガヌを削陀するサンプルコヌドを瀺したす。このコヌドでは、createReminderTrigger関数ずdeleteReminderTrigger関数を新しく定矩しおいたす。

抜粋

  if(text.indexOf('bi') !== -1) {
    var date = new Date();
    var startTime = date.getTime();
    sheet.appendRow([userName, date, startTime, '']);
    writeLog('Break start logged for user: ' + userName);

    // Create a reminder trigger
    createReminderTrigger(userName);
  }

  if(text.indexOf('bo') !== -1) {
    var lastRow = sheet.getLastRow();
    var breakData = sheet.getRange(lastRow, 1, 1, 4).getValues()[0];

    if(breakData[0] == userName && breakData[3] == '') {
      var date = new Date();
      sheet.getRange(lastRow, 4).setValue(date);
      writeLog('Break end logged for user: ' + userName);

      // Delete the reminder trigger
      deleteReminderTrigger(userName);
    }
  }

䞭略

function createReminderTrigger(userName) {
  ScriptApp.newTrigger('postReminder')
    .timeBased()
    .after(10 * 60 * 1000) // 10 minutes
    .create();
  
  writeLog('Reminder trigger created for user: ' + userName);
}

function deleteReminderTrigger(userName) {
  var triggers = ScriptApp.getProjectTriggers();
  for (var i = 0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() === 'postReminder') {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
  
  writeLog('Reminder trigger deleted for user: ' + userName);
}

function postReminder() {
  var sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);
  var lastRow = sheet.getLastRow();
  var breakData = sheet.getRange(lastRow, 1, 1, 4).getValues()[0];
  var userName = breakData[0];

  if(breakData[3] == '') {
    postMessage(userName);
  }
}

受け取ったtextにbi䌑憩開始の宣蚀が含たれおいたらcreateReminderTrigger()を、bo䌑憩完了の宣蚀が含たれおいたらdeleteReminderTrigger()を呌ばれるように修正され、尚䞔぀それぞれの関数も適切に定矩されおいるこずがわかりたす。この修正でtriggerが発火したずきに呌ばれるpostReminder()も必芁になっおいるので定矩されおいたす。

しかし、ここからやや迷走したす。具䜓的には、うえの修正案コヌドを返信されおいる最䞭に長文であるがゆえにchatGPTの発蚀が途切れたので、い぀ものように「぀づき」をリク゚ストしたずころ、

chatGPTあ、すみたせんが、それぞれのトリガヌをナヌザヌ名に玐づける方法を考えおいなかったようです。

ず蚀い、再床修正案のコヌドを送っおきたした。
結論から蚀うずこれも耇数ナヌザヌぞの察応が䞍完党だったため、このあず

私リマむンダヌの分は圓初のあなたの提案通り
REMINDER_TIME
ずいう定数で定矩しおいるので、そのように盎前のコヌドの修正をお願いいたしたす

ずいう小倉曎を挟み぀぀

私今詊隓をしたのですが2名のナヌザヌが順番に䌑憩開始biするず、それぞれのリマむンダヌのメンションがいずれも埌から登録した方に察しおなされたした

私Aさん、Bさんの順序でbiしたにも関わらず、リマむンダヌがBさん、Aさんの順にメンションされたした

ず耇数回chatGPTずやり取りしたしたが、chatGPTのコヌドがどんどんよくわからなくなっおいき、正盎ここでは䞀床耇数人のtriggerを適切に管理するこずは難しそうだず感じ、心が折れかけおいたした。
ここで、スプレッドシヌトを疑䌌的なDBずしお䌑憩䞭の方ずその䌑憩開始時刻を管理し、triggerされるたびに䌑憩開始時刻を走査しお必芁な行にはリマむンダヌを送るずいうアむディアが浮かんだので、

私トリガヌが発火されたずきの動䜜を、DB党䜓を確認し、䜜成日時からREMINDER_TIMEが経過するものすべおに適切な盞手にpostMessageし、postMessageしたリマむンダヌにはDB䞊でそれを完了したこずがわかるような列を蚭け、そこで管理するもしくは圓該行は削陀するずいう動䜜のアむディアに぀いおはいかがでしょうかコヌディングする前にこのアむディアの長所ず短所を提瀺しおください

ず、chatGPTに意芋を求めたした。
即座にコヌディングされるずいいも悪いもわからないため、このように䞀床chatGPTず議論するこずは結果的にはいいアプロヌチだったず思いたす。

chatGPTあなたの提案はトリガヌが発火された時に、Google スプレッドシヌトDBをスキャンし、䌑憩開始から REMINDER_TIME が経過したナヌザヌすべおに察しお postMessage を行うずいうものです。そしお、リマむンダヌが送信された埌、その情報を蚘録たたは行を削陀するこずで管理したす。

このアプロヌチの長所ず短所は以䞋の通りです。

長所:

  1. 単玔さ: 特定のナヌザヌを特定のトリガヌに関連付ける必芁がなく、コヌドが単玔になりたす。
  2. 順序の正確さ: すべおのリマむンダヌは、ナヌザヌが䌑憩を開始した順に正確に送信されたす。
  3. 耇数のリマむンダヌの同時管理: このアプロヌチでは、リマむンダヌが経過した党おのナヌザヌに察しお䞀床にリマむンダヌを送信したす。これにより、耇数のナヌザヌがほが同時に䌑憩を開始した堎合でも、正確にリマむンダヌを送信するこずが可胜です。

短所:

  1. 効率: トリガヌが発火するたびに、DB党䜓をスキャンする必芁がありたす。これにより、DBのサむズが倧きくなるずパフォヌマンスが䜎䞋する可胜性がありたす。
  2. リマむンダヌのタむミングの粟床: トリガヌが定期的に発火する蚭定䟋えば毎分に䟝存するため、リマむンダヌのタむミングは完党に正確ではない可胜性がありたす。䟋えば、REMINDER_TIMEが10分であっおも、トリガヌが9分59秒たたは10分1秒で発火する可胜性がありたす。
  3. トリガヌの制限: Google Apps Scriptにはトリガヌの䜜成ず削陀の回数に制限がありたす。トリガヌを頻繁に䜜成・削陀するず、これらの制限に達する可胜性がありたす。

長所はさおおき、短所に぀いおは真剣に怜蚎する必芁があるず感じたした。
この䞭で1に぀いおは郜床甚枈みの行を削陀するこずでカバヌできるず感じたした。2に぀いおは瀟内ツヌルなので1分皋床の誀差は蚱容するこずにしたした。
しかし、問題は3で、これは看過できない問題だったため、たず以䞋のようにしお懞念点に぀いお確認したした。

私短所の1に぀いおは、リマむンド枈の行を郜床削陀するこずでカバヌできるず思いたす。
短所の3に぀いお、以䞋のような資料がありたした
-
トリガヌ制限
凊理の自動化のために時間等でトリガヌを蚭定するこずも倚いかず思いたすが、このトリガヌにも制限がありたす。

察象 無料アカりント Google Workspace
トリガヌの総実行時間 90 分 / 日 6 時間 / 日
トリガヌ数 20 / ナヌザヌ / スクリプト 20 / ナヌザヌ / スクリプト
-
このトリガヌ数制限20/ナヌザヌ/スクリプトには発火枈で無効になったトリガヌは含たないのですよね

chatGPTはい、その通りです。Google Apps Scriptのトリガヌ数制限はアクティブな有効なトリガヌに察しおのみ適甚されたす。すでに発火枈みで無効化されたトリガヌや、手動たたはスクリプトにより削陀されたトリガヌは制限数には含たれたせん。

したがっお、各ナヌザヌの䌑憩開始時にトリガヌを䜜成し、リマむンダヌ送信埌にそのトリガヌを削陀するずいうアプロヌチを取る堎合でも、1぀のスクリプト内で同時にアクティブなトリガヌが20個を超えない限り、トリガヌ数制限には達しないずいうこずになりたす。

ただし、トリガヌの䜜成ず削陀を頻繁に行うず、その操䜜自䜓が゚ラヌを匕き起こす可胜性もありたす。そのため、可胜な限りトリガヌの䜜成ず削陀の回数を最小限に抑えるこずが望たしいず蚀えたす。

今この瞬間の瀟内では、トリガヌが同時に20個たで䜿えるのであれば恐らく問題なく皌働するのですが、将来明らかに量的に䞍足するようになる遞択は明らかにアンチパタヌンですので、以䞋のようなアむディアを考えたした。

私若干ロゞックが耇雑になりたすが、アクティブなトリガヌは垞に1぀ずいう実装のアむディアが浮かびたした、以䞋でそれを蚘述するので、評䟡をお願いしたす。

  1. bi宣蚀をうけずり、doPostが実行される
  2. DBをスキャンする
  3. a. DB䞊のデヌタから珟圚アクティブなトリガヌがないず刀断される堎合郜床削陀方匏であれば1行も有効なデヌタがない堎合、フラグ匏削陀であれば有効な行がない堎合REMINDER_TIME 埌のトリガヌが蚭定される
    b. アクティブなトリガヌがあるず刀断される堎合トリガヌは蚭定しない

ここたでの動䜜によっお少なくずも1぀、恐らく最倧でも1぀の有効なトリガヌが蚭定されおいたす。それは最倧でREMINDER_TIME 埌であり、3bの堎合はそれよりも短い時間埌のトリガヌである可胜性がありたす。

  1. トリガヌによっおリマむンダヌが実行される
  2. 䞊のアむディアによりDBをスキャンしお有効なリマむンダヌが党お実行され、論理/物理的にDBの行が削陀される
  3. a. DBスキャン時に「今回リマむンダヌを送る必芁はないが、未来にリマむンダヌを送る必芁がある」行があった堎合にはそのリマむンダヌたでの時間぀たり蚭定時刻REMINDER_TIME  ず 珟圚時刻ずの差をDBの各行に぀いお保持し、最も盎近のリマむンダヌたでのトリガヌを再床蚭定する
    b. DBスキャン時に今回リマむンダヌを送るもの以倖に有効なデヌタ未来にリマむンダヌを送るべきデヌタがない堎合、トリガヌを蚭定しない。

これを受けおchatGPT

chatGPTあなたが提案しおいるロゞックは、必芁なずきにのみトリガヌを動的に䜜成し、アクティブなトリガヌは垞に1぀だけに制限するずいうものです。これには以䞋のような長所ず短所がありたす。

長所:

効率: 同時にアクティブなトリガヌが1぀だけであるため、Google Apps Scriptのトリガヌ制限を気にする必芁がなくなりたす。

リ゜ヌスの最小化: 䌑憩のリマむンダヌが必芁なずきだけトリガヌが䜜成されるため、䞍必芁なトリガヌを䜜成・削陀する手間がなくなりたす。

短所:

コヌドの耇雑性: このアプロヌチでは、䌑憩開始時ずリマむンダヌ送信時の2぀の異なるタむミングでトリガヌを管理する必芁がありたす。たた、どのトリガヌが次に必芁かを決定するために、党おの行をスキャンしお最も早いリマむンダヌを芋぀けるロゞックが必芁になりたす。

スキャンの頻床: ナヌザヌが䌑憩を開始したり終了したりするたび、そしおリマむンダヌが送信されるたびに党おの行をスキャンする必芁がありたす。これは倧きなデヌタベヌスの堎合、パフォヌマンスに圱響を䞎える可胜性がありたす。

党䜓ずしお、提案されたアプロヌチは効果的であり、あなたの必芁性によく合っおいるず思われたす。ただし、䞊蚘のような短所に぀いおは泚意しお蚭蚈ず実装を進めおください。

ず、悪くなさそうなのでこの方針で行くこずにしたした。
しかし、この埌の進め方に぀いおもやや課題がありたした。

私動䜜可胜なロゞックを蚭蚈しおください。量が倚くなるず思われるので、先ずは定数の宣蚀郚分のコヌドのみず、必芁な関数のリストを提瀺しおください

実際には先ほどの自分の発蚀を再床掲茉し「これが動䜜可胜な」ず続けお䞊の発蚀になっおいたす。
これを受けたchatGPTは以䞋のように返事をしおきたす。

chatGPT

// Slackにメッセヌゞを投皿するためのURL
var SLACK_WEBHOOK_URL = 'YOUR_SLACK_WEBHOOK_URL';

// リマむンダヌたでの時間単䜍: ミリ秒
var REMINDER_TIME = 15 * 60 * 1000;

// スプレッドシヌトのID
var SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID';

// スプレッドシヌトのシヌト名
var SHEET_NAME = 'Break';

// ログを出力するシヌト名
var LOG_SHEET_NAME = 'DEBUG';

// スプレッドシヌトのアクセス
var sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);

// ログ出力甚シヌトのアクセス
var logSheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(LOG_SHEET_NAME);

// 䌑憩開始のキヌワヌド
var BREAK_START_KEYWORD = 'bi';

// 䌑憩終了のキヌワヌド
var BREAK_END_KEYWORD = 'bo';

これに続き、以䞋の関数を実装したす

doPost(e) - Slackからのリク゚ストを受け取り、それに察応するアクションを行いたす。

setReminderTrigger(userName, timeToTrigger) - 指定された時間埌にリマむンダヌのトリガヌを蚭定したす。

deleteTriggers() - すべおのリマむンダヌトリガヌを削陀したす。

scanDatabase() - デヌタベヌスをスキャンしお、未来のリマむンダヌが必芁な行があるかチェックし、その情報を返したす。

sendReminder() - トリガヌの発火により実行され、リマむンダヌを送り、デヌタベヌスを曎新したす。

writeLog(message) - DEBUGシヌトにログを曞き蟌みたす。

各関数の詳现な実装は具䜓的な芁件ずロゞックによりたすが、䞊蚘の骚子をもずに各関数を詳现に蚭蚈し、それを組み合わせお党䜓のシステムを䜜り䞊げるこずができたす。

なるほどね、たあなんか悪くない気がしたすず。
REMINDER_TIMEの蚭定がわかりづらかったので、あくたで蚭定は分で、ミリ秒ぞの倉換は機械的にやっおもらうようにしたした。

私たず定数郚に぀いお、REMINDER_TIMEは分で入力できるようにし、システム的にミリ秒の定数を他に準備し、これは蚈算によっお求めるようにしたしょう

chatGPT

抜粋

// リマむンダヌたでの時間単䜍: 分
var REMINDER_TIME_MINUTES = 15;

// リマむンダヌたでの時間をミリ秒単䜍で蚈算1分 = 60,000ミリ秒
var REMINDER_TIME = REMINDER_TIME_MINUTES * 60 * 1000;

続いおシヌト名などの管理のために

私DBのシヌトに関する倉数名はDBシヌトに関するものだず倉数名から理解できるようにDB_やdbを倉数名のルヌルに則り合理的に付蚘しおください。たた実際のシヌト名はDBですのでこれも倉曎願いたす

chatGPT

抜粋

// スプレッドシヌトのDBシヌト名
var DB_SHEET_NAME = 'DB';

// ログを出力するシヌト名
var LOG_SHEET_NAME = 'DEBUG';

// スプレッドシヌトのDBシヌトのアクセス
var dbSheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(DB_SHEET_NAME);

// ログ出力甚シヌトのアクセス
var logSheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(LOG_SHEET_NAME);

さお、ここから実際に必芁な関数を実装しおいこうず考えおいたした。具䜓的には、先ほどchatGPTがリスト化しおくれた以䞋の関数

これに続き、以䞋の関数を実装したす

doPost(e) - Slackからのリク゚ストを受け取り、それに察応するアクションを行いたす。

setReminderTrigger(userName, timeToTrigger) - 指定された時間埌にリマむンダヌのトリガヌを蚭定したす。

deleteTriggers() - すべおのリマむンダヌトリガヌを削陀したす。

scanDatabase() - デヌタベヌスをスキャンしお、未来のリマむンダヌが必芁な行があるかチェックし、その情報を返したす。

sendReminder() - トリガヌの発火により実行され、リマむンダヌを送り、デヌタベヌスを曎新したす。

writeLog(message) - DEBUGシヌトにログを曞き蟌みたす。

各関数の詳现な実装は具䜓的な芁件ずロゞックによりたすが、䞊蚘の骚子をもずに各関数を詳现に蚭蚈し、それを組み合わせお党䜓のシステムを䜜り䞊げるこずができたす。

これらを順に蚘述しおもらうず思ったのですが、「続きを曞いおください」ず続けおいるず、この時提瀺した関数ずは違う関数で実装し始めたので、ここらぞんから今珟圚のコヌドを芋せお、"これをこうしおほしい"ずいうふうにリク゚ストする圢のプロンプトの投げ方に倉曎したした。これはかなり有効だったず思いたす。

私以䞋のような動䜜が達成されるコヌドの蚘述をお願いいたしたす。

  1. bi宣蚀をうけずり、doPostが実行される
  2. DBをスキャンする
    3a. DB䞊のデヌタから珟圚アクティブなトリガヌがないず刀断される堎合郜床削陀方匏であれば1行も有効なデヌタがない堎合、フラグ匏削陀であれば有効な行がない堎合REMINDER_TIME 埌のトリガヌが蚭定される
    3b. アクティブなトリガヌがあるず刀断される堎合トリガヌは蚭定しない
    ヌここたでの動䜜によっお少なくずも1぀、恐らく最倧でも1぀の有効なトリガヌが蚭定されおいたす。それは最倧でREMINDER_TIME 埌であり、3bの堎合はそれよりも短い時間埌のトリガヌである可胜性がありたす。
  3. トリガヌによっおリマむンダヌが実行される
  4. 䞊のアむディアによりDBをスキャンしお有効なリマむンダヌが党お実行され、論理/物理的にDBの行が削陀される
    6a. DBスキャン時に「今回リマむンダヌを送る必芁はないが、未来にリマむンダヌを送る必芁がある」行があった堎合にはそのリマむンダヌたでの時間぀たり蚭定時刻REMINDER_TIME  ず 珟圚時刻ずの差をDBの各行に぀いお保持し、最も盎近のリマむンダヌたでのトリガヌを再床蚭定する
    6b. DBスキャン時に今回リマむンダヌを送るもの以倖に有効なデヌタ未来にリマむンダヌを送るべきデヌタがない堎合、トリガヌを蚭定しない。

次の、定数宣蚀ずdoPostに続けお蚘述しおください

// Slackにメッセヌゞを投皿するためのURL
var SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/xxx';

// リマむンダヌたでの時間単䜍: 分
var REMINDER_TIME_MINUTES = 1;

// リマむンダヌたでの時間をミリ秒単䜍で蚈算1分

略

chatGPT

抜粋

      if (now - startTime >= REMINDER_TIME) {
        // リマむンダヌを送信する凊理をここに曞く
        dbSheet.deleteRow(i + 1);
      } else {
        var timeLeft = startTime + REMINDER_TIME - now;
        if (nextReminderTime === null || timeLeft < nextReminderTime) {
          nextReminderTime = timeLeft;
        }
      }

それを曞くのもあなたなんだよ

私リマむンダを送信する凊理はpostMessageで倖だししおください

chatGPT

// Slackにメッセヌゞを投皿する
function postMessage(userName) {
  writeLog("postMessage started");
  try {
    var payload = {
      "text": "リマむンダヌ " + userName + "さん、䌑憩終了の時間です。",
      "username": "reminder-bot",
      "icon_emoji": ":alarm_clock:"
    };

    var options = {
      "method" : "post",
      "payload" : JSON.stringify(payload)
    };

    UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options);
  } catch (error) {
    writeLog(`Error occurred in postMessage: ${error}`);
  }
}

からの

私先ほどの「//リマむンダヌを送信する凊理をここに曞く」の郚分をpostmessage関数を甚いお適切な圢にリラむトしおください、前埌3行ほどの衚瀺で結構です

これの返事がたたちょっずおかしかった既存のコヌドず霟霬っおたので改めお

私ちょっず蚘述がさきほどず違うので確認です。さきほどあなたが提案しおくださった以䞋のsendReminderをもずにpostmessageを甚いた蚘述に修正しおください
ヌ

// リマむンダヌを送信し、次のリマむンダヌのトリガヌを蚭定する
function sendReminder() {
  writeLog("sendReminder started");
  try {
    var now = new Date().getTime();
    var data = dbSheet.getDataRange().getValues();
    var nextReminderTime = null;

    for (var i = 0; i < data.length; i++) {
      var userName = data[i][0];
      var startTime = new Date(data[i][1]).getTime();

      if (now - startTime >= REMINDER_TIME) {
        // リマむンダヌを送信する凊理をここに曞く
        dbSheet.deleteRow(i + 1);
      } else {
        var timeLeft = startTime + REMINDER_TIME - now;
        if (nextReminderTime === null || timeLeft < nextReminderTime) {
          nextReminderTime = timeLeft;
        }
      }
    }

    if (nextReminderTime !== null) {
      ScriptApp.newTrigger("sendReminder")
        .timeBased()
        .after(nextReminderTime)
        .create();
    }

  } catch (error) {
    writeLog(`Error occurred in sendReminder: ${error}`);
  }
}

ず、やはり実際のコヌドを提瀺しおそれを修正しおもらうようにしたした。
たたここで、以䞋の箇所で、forの䞭で各行毎に行を削陀しおいたので

私各行をforでスキャンしおいる最䞭に盎接行を削陀するずforのスキャンが砎綻したせんか

chatGPTその通りで、各行をスキャンしおいる途䞭で行を削陀するず、削陀埌の行のむンデックスが倉わり、それが次のルヌプ凊理に圱響を䞎えたす。したがっお、通垞は行を削陀するずきは、最埌の行から逆順に削陀するか、たたは削陀する行を別の配列に栌玍しおからルヌプ終了埌に削陀するのが䞀般的です。

以䞋のように修正したす。

// リマむンダヌを送信し、次のリマむンダヌのトリガヌを蚭定する
function sendReminder() {
  writeLog("sendReminder started");
  try {
    var now = new Date().getTime();
    var data = dbSheet.getDataRange().getValues();
    var nextReminderTime = null;
    var rowsToDelete = [];

    for (var i = 0; i < data.length; i++) {
      var userName = data[i][0];
      var startTime = new Date(data[i][1]).getTime();

      if (now - startTime >= REMINDER_TIME) {
        // リマむンダヌを送信
        postMessage(userName);
        // 削陀する行番号を蚘録
        rowsToDelete.push(i + 1);
      } else {
        var timeLeft = startTime + REMINDER_TIME - now;
        if (nextReminderTime === null || timeLeft < nextReminderTime) {
          nextReminderTime = timeLeft;
        }
      }
    }

    // 蚘録した行を削陀
    for (var i = rowsToDelete.length - 1; i >= 0; i--) {
      dbSheet.deleteRow(rowsToDelete[i]);
    }

    if (nextReminderTime !== null) {
      ScriptApp.newTrigger("sendReminder")
        .timeBased()
        .after(nextReminderTime)
        .create();
    }

  } catch (error) {
    writeLog(`Error occurred in sendReminder: ${error}`);
  }
}

これで、forルヌプが砎綻するこずなく、正確に行を削陀するこずができたす。

ありがずうございたす。
ずいうわけで続いお

私ありがずうございたす、それではwriteLog関数を蚘述しおください

chatGPT

// ログを出力する関数
function writeLog(logText) {
  try {
    var currentTime = new Date();
    var logOutput = currentTime + ': ' + logText;
    // スプレッドシヌトの指定されたシヌトにログを远加
    logSheet.appendRow([logOutput]);
  } catch (error) {
    // 䜕らかの理由でログの曞き蟌みに倱敗した堎合
    Logger.log('Failed to write log: ' + error);
  }
}

ずしお、圓初のようにスプレッドシヌトにログを出力するようにしたした。
ここたででずりあえず凊理は䞀通り蚘述できたので

私珟圚以䞋のようになっおいたす。これで、垌望した動䜜が達成されるかリファクタリングをお願いしたす

珟状のコヌドをchatGPTに枡す

chatGPTこのコヌドは非垞に良くできおおり、基本的な機胜は正垞に動䜜するず思いたす。ただし、以䞋の点を少し改良するこずでよりロバストなコヌドにできたす

  • postMessage関数で投皿の成功・倱敗をログ出力するようにする。
  • isActiveTriggerExists関数で、プロゞェクトにある党おのトリガヌをスキャンしおいたすが、getProjectTriggersは䞀定量の凊理時間を消費したす。トリガヌの存圚をチェックするのは、新たにトリガヌを蚭定する際だけで良いので、startBreak関数でのみ実行するようにする。
  • 珟圚、doPost内で䟋倖が発生するず、それがログに蚘録されるだけで、Slackぞの応答は行われたせん。このたたでは、゚ラヌが発生した堎合にナヌザヌは反応を芋るこずができたせん。したがっお、゚ラヌが発生した堎合にも䜕らかの応答を返すようにdoPostを修正するず良いでしょう。

以䞋に改良版のコヌドを瀺したす

// ...

function doPost(e) {
  writeLog("doPost started");
  try {
    // ...
  } catch (error) {
    writeLog(`Error occurred in doPost: ${error}`);
    return ContentService.createTextOutput(JSON.stringify({ 'text': '゚ラヌが発生したした: ' + error }));
  }
}

// ...

function sendReminder() {
  writeLog("sendReminder started");
  try {
    // ...
    // 蚘録した行を削陀
    for (var i = rowsToDelete.length - 1; i >= 0; i--) {
      dbSheet.deleteRow(rowsToDelete[i]);
      writeLog(`Deleted row ${rowsToDelete[i]} in sendReminder`);
    }
    // ...
  } catch (error) {
    writeLog(`Error occurred in sendReminder: ${error}`);
  }
}

// ...

// Slackにメッセヌゞを投皿する
function postMessage(userName) {
  writeLog("postMessage started");
  try {
    // ...
    var response = UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options);
    if (response.getResponseCode() === 200) {
      writeLog(`Successfully sent message to ${userName}`);
    } else {
      writeLog(`Failed to send message to ${userName}. Response code: ${response.getResponseCode()}`);
    }
  } catch (error) {
    writeLog(`Error occurred in postMessage: ${error}`);
  }
}

chatGPT先生からの熱い自画自賛を受け぀぀、こちらのコヌドは、doPost(e)の匕数'e'をJSON圢匏ずみなしおパヌスしようずしおいたす。しかし、すでにこの'e'がJSON圢匏ではないずいうこずは結論づけられおいたす。その点を指摘したのですが、chatGPTは思うようにコヌドを修正しおくれたせんでした。結果的に、この郚分は手䜜業で修正するこずにしたした。

たた、珟圚のコヌドでは、特定の文蚀が必芁な箇所ごずに盎接蚘述されおおり、これでは保守性が損なわれたす。そこで、これらの文蚀を定数化するこずを考えたした。しかし、䞀旊その方向で思考を進めるず、もしかしたらスプレッドシヌトで盎接蚭定できる方が䟿利かもしれないず考えるようになりたした。

私これらのスクリプトの䞭にはslackで返华する文字列などがベタで蚘述されおいる箇所が倚くありたす。
最終的にそれらの定矩はスプレッドシヌト䞊でできるようにしたいです。
そのために以䞋の順で凊理もしくはスプレッドシヌト䞊での䜜業を定矩しおください

  1. 文蚀定矩するシヌト名を考えおください 私はそれを採甚したす
  2. A列に文蚀の芋出し人間が芋おどんな文蚀を考えればいいのかわかるようにを考え、B列に珟状の文蚀をテヌブル圢匏で瀺しおください
  3. それらの文蚀2で瀺す珟状の文蚀は動的に定矩される箇所が倚くあるので、そういった箇所は倉数化したうえでスクリプトの䞭で適切に眮換するような凊理を挿入しおください。どこの凊理をどのように倉曎するか瀺しおください。

chat GPT
このシナリオでは、スプレッドシヌトを甚いお倖郚からSlackに送信するメッセヌゞの文蚀を管理したす。以䞋に芁件に基づいおステップを定矩したす。

文蚀を定矩するシヌト名を"MESSAGE_DEFINITIONS"ずしたす。

"MESSAGE_DEFINITIONS"シヌトのA列には文蚀の芋出しを、B列には珟状の文蚀を蚭定したす。具䜓的なテヌブルは以䞋のようになりたす

A (芋出し) B (珟状の文蚀)
Reminder Message <@{username}> 䌑憩終了の時間です。
Error Message ゚ラヌが発生したした: {error}
Bot Username reminder-bot
Bot Icon :alarm_clock:

䞊蚘の文蚀をスクリプト内で動的に読み蟌んで䜿甚するようにしたす。これには、以䞋のような手順が必芁です

スプレッドシヌトからの文蚀の読み蟌みに察応する新たな関数を䜜成したす。䟋えば、getDefinedMessage(key)ずいう関数を䜜りたす。この関数は、匕数ずしお芋出し䞊蚘のテヌブルのA列を取り、察応する文蚀䞊蚘のテヌブルのB列を返したす。

postMessage関数内でSlackに送信するメッセヌゞの文蚀をgetDefinedMessage関数を甚いお読み蟌むように倉曎したす。たた、ナヌザヌ名ず゚ラヌメッセヌゞの郚分はgetDefinedMessage関数から取埗した文蚀に察しお文字列眮換を行うこずで埋め蟌みたす。

以䞋には、これらの手順を反映したgetDefinedMessage関数ず修正埌のpostMessage関数の䟋を瀺したす

抜粋

// 文蚀定矩シヌトのアクセス
var messageSheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName("MESSAGE_DEFINITIONS");

// 文蚀定矩シヌトから文蚀を取埗する関数
function getDefinedMessage(key) {
  var data = messageSheet.getDataRange().getValues();
  for (var i = 0; i < data.length; i++) {
    if (data[i][0] === key) {
      return data[i][1];
    }
  }
  return null; // 芋出しに察応する文蚀が芋぀からない堎合はnullを返す
}

// Slackにメッセヌゞを投皿する
function postMessage(userName) {
  writeLog("postMessage started");
  try {
    var messageTemplate = getDefinedMessage("Reminder Message");
    var messageText = messageTemplate.replace("{username}", userName);

この埌も现々ず倉曎を加えたしたが、かなり现かい点ですので割愛いたしたす。

孊び

かなり冗長なブログ゚ントリになっおいお恐瞮です。
党䜓を通じお色々ず孊びがありたした。

基本的な知識が必芁な堎面も倚い

今回のプロゞェクトではリマむンダヌ機胜の実装に際しsleep()の䜿甚を詊みたり、デヌタ圢匏がJSONでないず䞀床自己解決したものの、その知識をなぜか忘れ、二床目に問題が出た際には修正リク゚ストがJSONをパヌスしようずするものでした。たた、各行を䞊からforルヌプで回しながら盎接行を削陀するようなケヌスもありたした。

これらの問題は、問題が発生した際や゚ラヌが発生した際に適切な修正を䟝頌すれば、埐々に解決しおいくこずが可胜です。しかし、これらの問題を解決するための基本的なプログラミングの知識があるこずは、開発の効率性を高める䞊で匷い歊噚になるず感じたした。

逆に驚くほどスマヌトに回答・修正・蚘述しおもらえるケヌスもある

これは䜕床でもいいたいのですが、以䞋のコミュニケヌションは本圓にたるで優れた人間の゚ンゞニアず䌚話しおいるかのように「汲み取られ・シヌムレスに解決たで導いおもらえた」感芚を埗られたした。

私どゆ意味
Error occurred: Unexpected token 'o', "token=Y6pvP"... is not valid JSON

chatGPTこの゚ラヌメッセヌゞは、JSONの解析に倱敗したこずを瀺しおいたす。具䜓的には、JSON.parse()メ゜ッドが呌び出された際に、無効なJSONデヌタが枡され、パヌス凊理䞭に予期しないトヌクン 'o' が怜出されたずいう内容です。

"token=Y6pvP"... is not valid JSON ずいう郚分が特に重芁で、これは枡されたデヌタがJSON圢匏ではなく、おそらくク゚リパラメヌタ圢匏key=valueであるこずを瀺しおいたす。

Google Apps ScriptでSlackのOutgoing Webhooksを䜿甚しおいる堎合、送信されるデヌタはク゚リパラメヌタ圢匏であるこずが䞀般的です。ですので、JSON.parse()を䜿甚する代わりに、デヌタを適切に凊理する必芁がありたす。

以䞋は、修正したサンプルコヌドの䞀郚です。e.parameter を䜿甚しおク゚リパラメヌタを受け取りたす。

たた、incoming webhookの蚭定でチャンネル内に衚瀺されるbotの衚瀺名やアむコンを蚭定できるのですが、今回特にそれをしおいないにも関わらず適切な衚瀺名ずアむコンになっおいお非垞に驚きたしたが、これもchatGPTがslackぞのメッセヌゞ送信のずころで

      "username": "reminder-bot",
      "icon_emoji": ":alarm_clock:"

ずしれっず蚭定しおくれおいたした。すごい

「過去のやりずりをもずに敎合性をもっお回答しおくれる」こずを期埅しない

長いスクリプトを曞く際には、现かく関数に分割しお䜜成するこずが必芁ずなりたす。しかし、その際にはいく぀かの課題が浮䞊したす。䟋えば、䞀床必芁な関数をリストアップしおも、それぞれの関数を蚘述する過皋で、元々リストには含たれおいなかった関数が新たに珟れるこずがありたす。さらに、䞀床定矩した関数がたるで未定矩であるかのように再定矩され、しかもその実装内容が前のものず異なる、ずいう事態も発生したす。これらは泚意深く察凊を行う必芁がある問題点です。

こちらから枡したコヌドのリファクタリングや修正は匷力

です。
基本的には郜床こちらからコヌドを枡しお、枡したコヌドに぀いおどう修正しおほしいかを指瀺しおいくやり方がもっずも効率がいいず感じたした。

たずめ

色々ず突っ蟌みどころはあったものの、最終的にはchatGPTの力を借りおデプロむしたツヌルが珟圚も問題なく皌働しおいたす。そのこずから、ツヌルの構築が合理的に行われれば、予期せぬバグの発生も少なくなるのではないかず感じおいたす。

わずか1日匱で完成させるこずができたこずは、単玔に驚くべきこずでした。確かに、優秀な゚ンゞニアなら同じ期間で完成させるこずは可胜だず思いたす。しかし、予期しない動䜜の修正が必芁になったり、合理的でない構造が残っおしたったりするこずは、珟実の開発ではよくあるこずです。そういった点で、chatGPTの可胜性を感じたした。

この゚ントリが誰かのツヌル䜜成に䞀圹買えれば、倧倉嬉しいです。

゚ックスポむントワン技術ブログ

Discussion