Last active
July 31, 2023 14:11
-
-
Save naosim/772ddc29cf6a0d140d6db635dfc3f452 to your computer and use it in GitHub Desktop.
spreadsheetでtaskAPIを使ってタスクを管理する
This file contains hidden or 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
// いい感じに同期します | |
// 新規タスクの追加 | |
// 変更点の更新 | |
// GoogleTasksの読み込み | |
function update() { | |
new MainService().update(); | |
} | |
function onOpen() { | |
const customMenu = SpreadsheetApp.getUi() | |
customMenu.createMenu('タスク') | |
.addItem('更新', 'update') | |
.addToUi() | |
} | |
// スプレッドシートをセットアップします。 | |
function setupSheet() { | |
new TaskSheetRepository().setupSheet(); | |
} | |
class MainService { | |
constructor() { | |
this.googleTasksRepository = new GoogleTasksRepository(); | |
this.taskSheetRepository = new TaskSheetRepository(); | |
} | |
static dateToText(dateOrText) { | |
if(!dateOrText) { | |
return dateOrText; | |
} | |
if(toString.call(dateOrText).indexOf('Date') == -1) { | |
return dateOrText; | |
} | |
return dateOrText.toLocaleDateString(); | |
} | |
static convertGoogleTaskToSheetTask(googleTask) { | |
const short = [googleTask.title, MainService.dateToText(googleTask.dates.due), googleTask.notes].filter(v => v).map(v => v.trim()).join('\n').slice(0, 140); | |
const result = { | |
id: googleTask.id, | |
title: googleTask.title, | |
notes: googleTask.notes, | |
due: googleTask.dates.due, | |
completed: googleTask.dates.completed, | |
updated: googleTask.dates.updated, | |
short | |
} | |
return result; | |
} | |
update() { | |
// シート上の変更をGoogleTaskに反映する | |
const diff = this.taskSheetRepository.getDiffTasks(); | |
const newTaskItems = diff.filter(v => v.status == 'new'); | |
const updateTaskItems = diff.filter(v => v.status == 'update'); | |
newTaskItems.forEach(v => { | |
this.googleTasksRepository.insert(v.data); | |
}) | |
updateTaskItems.forEach(v => { | |
this.googleTasksRepository.update(v.id, v.data); | |
}) | |
// googleからタスクを取得 | |
const googleTasks = this.googleTasksRepository.getTasks(); | |
// sheet用に変換 | |
const sheetTasks = googleTasks.map(v => MainService.convertGoogleTaskToSheetTask(v)); | |
// sheetへ保存 | |
this.taskSheetRepository.updateSheet(sheetTasks); | |
} | |
} | |
class DiffJudge { | |
judge(s, b) { | |
const eqDate = (a, b) => { | |
if(!a && !b) { | |
return true; | |
} | |
if(a && !b) { | |
return false; | |
} | |
if(!a && b) { | |
return false; | |
} | |
return a.getTime() == b.getTime(); | |
}; | |
const result = {status: 'none', id: s.id, data: {}}; | |
if(!s.id) { | |
return {status: 'new', data: s}; | |
} | |
if(s.title != b.title) { | |
result.status = 'update'; | |
result.data.title = s.title; | |
} | |
if(s.notes != b.notes) { | |
result.status = 'update'; | |
result.data.notes = s.notes; | |
} | |
if(!eqDate(s.due, b.due)) { | |
result.status = 'update'; | |
result.data.due = s.due; | |
} | |
return result; | |
} | |
} | |
class TaskSheetRepository { | |
constructor() { | |
this._sheetName = 'シート1'; | |
this._backupSheetName = 'タスクバックアップ' | |
this._columns = [ | |
'short', | |
'completed', | |
'title', | |
'notes', | |
'due', | |
'id', | |
'updated', | |
] | |
this.spreadSheet = SpreadsheetApp.getActiveSpreadsheet(); | |
} | |
getSpreadSheet() { | |
return this.spreadSheet; | |
} | |
getSheet() { | |
if(!this.sheet) { | |
this.sheet = this.getSpreadSheet().getSheetByName(this._sheetName); | |
} | |
return this.sheet; | |
} | |
getBackupSheet() { | |
return this.getSpreadSheet().getSheetByName(this._backupSheetName); | |
} | |
setupSheet() { | |
const sheet = this.getSheet(); | |
sheet.clear(); | |
const range = sheet.getRange(1, 1, 1, this._columns.length); | |
range.setValues([this._columns]); | |
const backupSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet(); | |
backupSheet.setName(this._backupSheetName); | |
backupSheet.getRange(1, 1, 1, this._columns.length).setValues([this._columns]); | |
} | |
updateSheet(sheetTasks) { | |
const table = [ | |
this._columns, | |
...sheetTasks.map(t => this._columns.map(c => t[c])) | |
]; | |
const updateSheet = (sheet) => { | |
sheet.clearContents(); | |
const range = sheet.getRange(1, 1, table.length, this._columns.length); | |
range.setValues(table); | |
} | |
updateSheet(this.getBackupSheet()); | |
updateSheet(this.getSheet()); | |
} | |
getDiffTasks() { | |
const aryToTask = ary => { | |
return ary.reduce((memo, v, i) => { | |
memo[this._columns[i]] = v; | |
return memo; | |
}, {}) | |
} | |
const sheetValues = this.getSheet().getDataRange().getValues().slice(1).map(ary => aryToTask(ary)); | |
const backupValues = this.getBackupSheet().getDataRange().getValues().slice(1).map(ary => aryToTask(ary)); | |
return sheetValues.map((s, i) => { | |
const b = backupValues[i]; | |
return new DiffJudge().judge(s, b); | |
}).filter(v => v.status != 'none'); | |
} | |
} | |
class GoogleTasksRepository { | |
constructor() { | |
this.taskListId = null; | |
if(!this.taskListId) { | |
throw new Error('GoogleTasksRepository.taskListIdを設定してください'); | |
} | |
} | |
insert(task) { | |
/* | |
{ | |
title: string, | |
notes: string, | |
due: Date, | |
} | |
*/ | |
const input = { | |
title: task.title, | |
notes: task.notes | |
}; | |
if(task.due) { | |
input.due = Utilities.formatDate(task.due, "Asia/Tokyo", "yyyy-MM-dd") + "T00:00:00.000Z" | |
} | |
Tasks.Tasks.insert(input, this.taskListId); | |
} | |
update(taskId, task) { | |
} | |
getTasks() { | |
return Tasks.Tasks.list(this.taskListId, { | |
showCompleted: true, | |
showHidden: true | |
}).items.map(v => { | |
v.dates = {}; | |
// 日付をDate型に変える | |
const keys = ['updated', 'completed', 'due']; | |
keys.forEach(k => { | |
if(v[k]) { | |
v.dates[k] = new Date(v[k]); | |
} | |
}) | |
return v; | |
}); | |
/* memo | |
{ | |
"kind": string, | |
"id": string, | |
"etag": string, | |
"title": string, | |
"updated": string, | |
"selfLink": string, | |
"parent": string, | |
"position": string, | |
"notes": string, | |
"status": string, | |
"due": string, | |
"completed": string, | |
"deleted": boolean, | |
"hidden": boolean, | |
"links": [ | |
{ | |
"type": string, | |
"description": string, | |
"link": string | |
} | |
] | |
} | |
*/ | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment