Last active
September 20, 2022 11:21
-
-
Save kazuooooo/6acb1f20c0a3616edaeefa09f1626cc6 to your computer and use it in GitHub Desktop.
お弁当ボットのソースコードです(Google App Script)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Const = { | |
spreadSheetId: 'xxxxxxxxxxxxxxxxxx', | |
obentColumnIdx: 6, | |
sheetStartDate: new Date(2017, 0, 19), | |
sheetTopMarginRows: 4, | |
scheduleOrderTime: new Date(2017, 0, 1), | |
scheduleRemindTime: new Date(2017, 0, 1), | |
triggers: { | |
triggerDays: [ScriptApp.WeekDay.SUNDAY, | |
ScriptApp.WeekDay.MONDAY, | |
ScriptApp.WeekDay.TUESDAY, | |
ScriptApp.WeekDay.WEDNESDAY, | |
ScriptApp.WeekDay.THURSDAY], | |
callTrigger: { | |
hour: 17, | |
minute: 0 | |
}, | |
reminderTrigger: { | |
hour: 21, | |
minute: 0 | |
}, | |
orderTrigger: { | |
hour: 22, | |
minute: 0 | |
} | |
} | |
} | |
function doPost(e) { | |
var request = parse_request(e); | |
var commandar = loadCommander(); | |
switch (true) { | |
case /^obento/.test(request.text): | |
commandar.command_order(request); | |
break; | |
case /^ノ/.test(request.text): | |
commandar.command_add(request); | |
break; | |
case /^_/.test(request.text): | |
commandar.command_cancel(request); | |
break; | |
default: | |
no_command(request); | |
} | |
// return dummy value | |
return ContentService.createTextOutput("hello world!(dummy_message)"); | |
} | |
function parse_request(e) { | |
var request = {}; | |
request.text = e.parameters["text"][0]; | |
request.user = e.parameters["user_name"][0]; | |
return request; | |
} | |
// Commandar | |
loadCommander = function() { | |
SpreadSheet = loadSpreadSheet(); | |
Commandar = {}; | |
Commandar.command_order = function (request) { | |
// prod | |
var fax_email_address = "[email protected]" | |
// dev | |
// var fax_email_address = "[email protected]" | |
var params = parse_order_text(request.text); | |
var subject = "MAMORIO株式会社" + params.month + "月" + params.day + "日分注文" | |
var body = "いつもお世話になっております。MAMORIO株式会社です。" + params.month + "月" + params.day + "日、お弁当" + params.amount + "個お願い致します。" | |
// mailを送信 | |
MailApp.sendEmail(fax_email_address, | |
subject, | |
body); | |
// 結果をslack通知 | |
var slack_result = "<@" + request.user + ">" + params.month + "月" + params.day + "日分" + params.amount + "個注文しました。(FAXが送信できたらここに通知されます。)" | |
loadSlackModule().send_slack(slack_result) | |
} | |
Commandar.command_add = function(request) { | |
var date = loadDateUtils().parseDateTime(request.text); | |
SpreadSheet.write_sheet(request.user, date, 1); | |
var amount = loadSummerizer().summerizeOrder().orderAmount; | |
var slack_result = "<@" + request.user + "> \n" + (date.getMonth() + 1) + "月" + date.getDate() + "日分 注文で登録しました。 \n 現在合計" + amount + "個" | |
loadSlackModule().send_slack(slack_result) | |
} | |
Commandar.command_cancel = function(request) { | |
var date = loadDateUtils().parseDateTime(request.text); | |
SpreadSheet.write_sheet(request.user, date, -1); | |
var amount = loadSummerizer().summerizeOrder().orderAmount; | |
var slack_result = "<@" + request.user + "> \n" + (date.getMonth() + 1) + "月" + date.getDate() + "日分 注文をキャンセルしました。 \n 現在合計" + amount + "個" | |
loadSlackModule().send_slack(slack_result) | |
} | |
function parse_order_text(text) { | |
var hankaku_text = convert_hankaku(text) | |
var reg = /(\d{1,2})月\s*(\d{1,2})日\s*(\d{1,2})/ | |
var result = hankaku_text.match(reg) | |
var params = {}; | |
params.month = result[1]; | |
params.day = result[2]; | |
params.amount = result[3]; | |
return params; | |
} | |
function convert_hankaku(text) { | |
return text.replace(/[A-Za-z0-9]/g, function(s) { | |
return String.fromCharCode(s.charCodeAt(0) - 0xFEE0) | |
}); | |
} | |
return Commandar; | |
} | |
//SpreadSheet | |
loadSpreadSheet = function() { | |
SpreadSheet = {}; | |
SpreadSheet.dateToIdx = function(date) { | |
var sday = Const.sheetStartDate; | |
var millisecondsPerDay = 24 * 60 * 60 * 1000; | |
var days = (date - sday) / millisecondsPerDay; | |
var result = Math.floor(days) + Const.sheetTopMarginRows; | |
//HACK: 日付指定した日時がちょうど0時になってMath.ceil(days)が効かないので1日足してfloorをかける | |
return Math.floor(days + 1) + Const.sheetTopMarginRows; | |
} | |
SpreadSheet.write_sheet = function(user, date, val) { | |
var timeSheet = SpreadsheetApp.openById(Const.spreadSheetId) | |
var userSheet = timeSheet.getSheetByName(user); | |
var row = SpreadSheet.dateToIdx(date); | |
var cell = userSheet.getRange(row, Const.obentColumnIdx); | |
var sum = cell.getValue() + val; | |
cell.setValue(sum); | |
} | |
return SpreadSheet; | |
} | |
//Summerizer | |
loadSummerizer = function() { | |
SpreadSheet = loadSpreadSheet(); | |
Summerizer = {}; | |
Summerizer.summerizeOrder = function() { | |
collectedOrder = collectOrder(); | |
var summerizedOrder = {}; | |
summerizedOrder.date = getTommorow(); //tmp 必要そうなら引数で渡せるようにする | |
summerizedOrder.orderAmount = 0; | |
summerizedOrder.orderUsers = []; | |
summerizedOrder.noOrderUsers = []; | |
collectedOrder.forEach(function(order) { | |
if (order.value >= 1) { | |
summerizedOrder.orderAmount += order.value; | |
summerizedOrder.orderUsers.push(order.name); | |
} else { | |
summerizedOrder.noOrderUsers.push(order.name); | |
} | |
}); | |
return summerizedOrder; | |
} | |
function collectOrder() { | |
var collectedOrder = []; | |
var usersSheets = getUserSheets(); | |
userSheets.forEach(function(sheet) { | |
collectedOrder.push(getUserOrder(sheet)); | |
}); | |
return collectedOrder | |
} | |
// ユーザーのシートを配列で取得 | |
function getUserSheets() { | |
var timeSheet = SpreadsheetApp.openById(Const.spreadSheetId) | |
var allSheets = timeSheet.getSheets() | |
return userSheets = allSheets.filter(function(sheet) { | |
return !(/^\_/.test(sheet.getSheetName())) | |
}); | |
} | |
// ユーザーの注文するしないを取得 | |
function getUserOrder(sheet, date) { | |
var userOrder = {} | |
var date = (typeof date !== 'undefined') ? date : getTommorow(); | |
var row = SpreadSheet.dateToIdx(date); | |
var cell = sheet.getRange(row, Const.obentColumnIdx); | |
userOrder.name = sheet.getSheetName(); | |
userOrder.value = (cell.getValue() == '') ? 0 : cell.getValue() | |
return userOrder; | |
} | |
return Summerizer; | |
} | |
// Scheuduled Tasks(最大1時間に1回しか実行出来ないので注意) | |
function setUpTimeTriggers() { | |
// 今あるトリガーは削除 | |
deleteAllTrigger(); | |
// お弁当いる方〜? | |
setCallTrigger(); | |
// リマインド | |
setReminderTrigger(); | |
// 注文 | |
setOrderTrigger(); | |
} | |
// 注文 | |
function scheduledOrder(){ | |
var summerize = loadSummerizer().summerizeOrder(); | |
var SlackModule = loadSlackModule(); | |
var orderUsers = summerize.orderUsers; | |
//個数が0ならreturnさせる | |
if(summerize.orderAmount <= 0){ | |
SlackModule.send_slack('今日は注文がありませんでした。'); | |
return; | |
} | |
var notifyText = SlackModule.addAts(orderUsers).join(' ') + ' お弁当を注文します'; | |
SlackModule.send_slack(notifyText); | |
SlackModule.send_slack('obento ' + (summerize.date.getMonth() + 1) + '月' + summerize.date.getDate() + '日' + summerize.orderAmount + '個'); | |
} | |
// リマインダー | |
function scheduledRemind() { | |
var summerize = loadSummerizer().summerizeOrder(); | |
var SlackModule = loadSlackModule(); | |
var noOrderUsers = summerize.noOrderUsers; | |
var notifyText = SlackModule.addAts(noOrderUsers).join(' ') + 'まだお弁当を注文していません。夜10時に注文されます。' | |
SlackModule.send_slack(notifyText); | |
} | |
function setCallTrigger() { | |
for (var i = 0; i < Const.triggers.triggerDays.length; i++) { | |
ScriptApp.newTrigger("triggerCall") | |
.timeBased() | |
.onWeekDay(Const.triggers.triggerDays[i]) | |
.atHour(Const.triggers.callTrigger.hour) | |
.nearMinute(Const.triggers.callTrigger.minute) | |
.create(); | |
} | |
} | |
function setReminderTrigger() { | |
for (var i = 0; i < Const.triggers.triggerDays.length; i++) { | |
ScriptApp.newTrigger("triggerReminder") | |
.timeBased() | |
.onWeekDay(Const.triggers.triggerDays[i]) | |
.atHour(Const.triggers.reminderTrigger.hour) | |
.nearMinute(Const.triggers.reminderTrigger.minute) | |
.create(); | |
} | |
} | |
function setOrderTrigger() { | |
for (var i = 0; i < Const.triggers.triggerDays.length; i++) { | |
ScriptApp.newTrigger("triggerOrder") | |
.timeBased() | |
.onWeekDay(Const.triggers.triggerDays[i]) | |
.atHour(Const.triggers.orderTrigger.hour) | |
.nearMinute(Const.triggers.orderTrigger.minute) | |
.create(); | |
} | |
} | |
function triggerCall() { | |
loadSlackModule().send_slack('<!channel> 明日お弁当いる方〜?'); | |
} | |
function triggerReminder() { | |
scheduledRemind(); | |
} | |
function triggerOrder() { | |
scheduledOrder(); | |
} | |
function deleteAllTrigger() { | |
var triggers = ScriptApp.getProjectTriggers(); | |
for (var i = 0; i < triggers.length; i++) { | |
ScriptApp.deleteTrigger(triggers[i]); | |
} | |
} | |
function everyMinuteTrigger() { | |
ScriptApp.newTrigger("triggerTest") | |
.timeBased() | |
.onWeekDay(ScriptApp.WeekDay.SUNDAY) | |
.atHour(20) | |
.nearMinute(17) | |
.create(); | |
} | |
//Slack | |
loadSlackModule = function() { | |
var SlackModule = {}; | |
SlackModule.send_slack = function(message, options) { | |
//prod | |
var incomingURL = "https://hooks.slack.com/services/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"; | |
//dev | |
//var incomingURL = "https://hooks.slack.com/services/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" | |
var options = (options || {}); | |
options["text"] = message; | |
var send_options = { | |
method: "post", | |
payload: { | |
"payload": JSON.stringify(options) | |
} | |
}; | |
UrlFetchApp.fetch(incomingURL, send_options); | |
return message; | |
} | |
SlackModule.addAts = function(users) { | |
return users.map((function(user) { | |
return '<@' + user + '>'; | |
})); | |
} | |
return SlackModule; | |
} | |
//DateUtil | |
loadDateUtils = function() { | |
var DateUtils = {}; | |
// 今を返す | |
var _now = new Date(); | |
var now = function(datetime) { | |
if (typeof datetime != 'undefined') { | |
_now = datetime; | |
} | |
return _now; | |
}; | |
DateUtils.now = now; | |
// テキストから時間を抽出 | |
DateUtils.parseTime = function(str) { | |
str = String(str || "").toLowerCase().replace(/[A-Za-z0-9]/g, function(s) { | |
return String.fromCharCode(s.charCodeAt(0) - 0xFEE0); | |
}); | |
var reg = /((\d{1,2})\s*[:時]{1}\s*(\d{1,2})\s*(pm|)|(am|pm|午前|午後)\s*(\d{1,2})(\s*[:時]\s*(\d{1,2})|)|(\d{1,2})(\s*[:時]{1}\s*(\d{1,2})|)(am|pm)|(\d{1,2})\s*時)/; | |
var matches = str.match(reg); | |
if (matches) { | |
var hour, min; | |
// 1時20, 2:30, 3:00pm | |
if (matches[2] != null) { | |
hour = parseInt(matches[2]); | |
min = parseInt(matches[3] ? matches[3] : '0'); | |
if (_.contains(['pm'], matches[4])) { | |
hour += 12; | |
} | |
} | |
// 午後1 午後2時30 pm3 | |
if (matches[5] != null) { | |
hour = parseInt(matches[6]); | |
min = parseInt(matches[8] ? matches[8] : '0'); | |
if (_.contains(['pm', '午後'], matches[5])) { | |
hour += 12; | |
} | |
} | |
// 1am 2:30pm | |
if (matches[9] != null) { | |
hour = parseInt(matches[9]); | |
min = parseInt(matches[11] ? matches[11] : '0'); | |
if (_.contains(['pm'], matches[12])) { | |
hour += 12; | |
} | |
} | |
// 14時 | |
if (matches[13] != null) { | |
hour = parseInt(matches[13]); | |
min = 0; | |
} | |
return [hour, min]; | |
} | |
return null; | |
}; | |
// テキストから日付を抽出 | |
DateUtils.parseDate = function(str) { | |
str = String(str || "").toLowerCase().replace(/[A-Za-z0-9]/g, function(s) { | |
return String.fromCharCode(s.charCodeAt(0) - 0xFEE0); | |
}); | |
if (str.match(/(明日|tomorrow)/)) { | |
var tomorrow = new Date(now().getFullYear(), now().getMonth(), now().getDate() + 1); | |
return [tomorrow.getFullYear(), tomorrow.getMonth() + 1, tomorrow.getDate()] | |
} | |
if (str.match(/(今日|today)/)) { | |
return [now().getFullYear(), now().getMonth() + 1, now().getDate()] | |
} | |
if (str.match(/(昨日|yesterday)/)) { | |
var yesterday = new Date(now().getFullYear(), now().getMonth(), now().getDate() - 1); | |
return [yesterday.getFullYear(), yesterday.getMonth() + 1, yesterday.getDate()] | |
} | |
var reg = /((\d{4})[-\/年]{1}|)(\d{1,2})[-\/月]{1}(\d{1,2})/; | |
var matches = str.match(reg); | |
if (matches) { | |
var year = parseInt(matches[2]); | |
var month = parseInt(matches[3]); | |
var day = parseInt(matches[4]); | |
if (isNaN(year) || year < 1970) { | |
// | |
if ((now().getMonth() + 1) >= 11 && month <= 2) { | |
year = now().getFullYear() + 1; | |
} else if ((now().getMonth() + 1) <= 2 && month >= 11) { | |
year = now().getFullYear() - 1; | |
} else { | |
year = now().getFullYear(); | |
} | |
} | |
return [year, month, day]; | |
} | |
return null; | |
}; | |
// 日付と時間の配列から、Dateオブジェクトを生成 | |
DateUtils.normalizeDateTime = function(date, time) { | |
// 時間だけの場合は日付を補完する | |
if (date) { | |
if (!time) date = null; | |
} else { | |
date = [now().getFullYear(), now().getMonth() + 1, now().getDate()]; | |
if (!time) { | |
time = [now().getHours(), now().getMinutes()]; | |
} | |
} | |
// 日付を指定したけど、時間を書いてない場合は扱わない | |
if (date && time) { | |
return (new Date(date[0], date[1] - 1, date[2], time[0], time[1], 0)); | |
} else { | |
return null; | |
} | |
}; | |
// 日時をいれてparseする | |
DateUtils.parseDateTime = function(str) { | |
// add | |
if (str === "ノ" || str === "_") { | |
return getTommorow(); | |
} | |
// add end | |
var date = DateUtils.parseDate(str); | |
var time = DateUtils.parseTime(str); | |
if (!date) return new Date(); | |
if (time) { | |
return (new Date(date[0], date[1] - 1, date[2], time[0], time[1], 0)); | |
} else { | |
return (new Date(date[0], date[1] - 1, date[2], 0, 0, 0)); | |
} | |
}; | |
// Dateから日付部分だけを取り出す | |
DateUtils.toDate = function(date) { | |
return (new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0)); | |
}; | |
// 曜日を解析 | |
DateUtils.parseWday = function(str) { | |
str = String(str).replace(/曜日/g, ''); | |
var result = []; | |
var wdays = [/(sun|日)/i, /(mon|月)/i, /(tue|火)/i, /(wed|水)/i, /(thu|木)/i, /(fri|金)/i, /(sat|土)/i]; | |
for (var i = 0; i < wdays.length; ++i) { | |
if (str.match(wdays[i])) result.push(i); | |
} | |
return result; | |
} | |
var replaceChars = { | |
Y: function() { | |
return this.getFullYear(); | |
}, | |
y: function() { | |
return String(this.getFullYear()).substr(-2, 2); | |
}, | |
m: function() { | |
return ("0" + (this.getMonth() + 1)).substr(-2, 2); | |
}, | |
d: function() { | |
return ("0" + (this.getDate())).substr(-2, 2); | |
}, | |
H: function() { | |
return ("0" + (this.getHours())).substr(-2, 2); | |
}, | |
M: function() { | |
return ("0" + (this.getMinutes())).substr(-2, 2); | |
}, | |
s: function() { | |
return ("0" + (this.getSeconds())).substr(-2, 2); | |
}, | |
}; | |
DateUtils.format = function(format, date) { | |
var result = ''; | |
for (var i = 0; i < format.length; i++) { | |
var curChar = format.charAt(i); | |
if (replaceChars[curChar]) { | |
result += replaceChars[curChar].call(date); | |
} else { | |
result += curChar; | |
} | |
} | |
return result; | |
}; | |
return DateUtils; | |
}; | |
// Global Util | |
function getTommorow(){ | |
var date = new Date(); | |
date.setDate(date.getDate() + 1); | |
return date; | |
} | |
// For Debug | |
function debugSlack(){ | |
loadSlackModule().send_slack('<!channel> 明日お弁当いる方〜?(<https://docs.google.com/spreadsheets/d/xxxxxxxxxxxx/edit#gid=475934721>に1を入力すれば未来分もまとめて注文できます)'); | |
} | |
function callTrigger() { | |
loadSlackModule().send_slack('<!channel> 明日お弁当いる方〜?2'); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment