目的
Todoistの完了タスクをログに残したい。有料プランに切り替えれば済む話なのかもしれないが、それだけのために課金するのもどうか。
ということで他の方法で記録していたのですが、今まではIFTTTで簡単に実現していました。IFTTTの有料化に伴い、別の処理に置き換えます。
概要
TodoistのWebhooksにより、指定したURLに完了タスクデータをPOSTします。
あらかじめGoogle Apps ScriptのPOST処理を作成し、このURLを使用します。
処理の中でSpreadsheetに記録します。このときヘッダ行を見て記録する列を認識します。
処理の流れとは逆の順に準備を進めます。(多少行って戻ってしますが)
- 記録先のSpreadsheetを作成する
- 記録処理のGoogle Apps Scriptを用意する
- WebhookのURLを設定する
手順
記録先のSpreadsheetを作成する
- 任意の場所、名前でSpreadsheetを作成する
- このとき1行目を下の画像のように設定する(スクリプトの処理を理解した上で変更も可能)
- URLを後で使う
記録処理のGoogle Apps Scriptを用意する
- 任意の場所、名前でGoogle Apps Scriptを作成する
作成する方法、準備については他の記事を参照
https://qiita.com/tags/googleappsscript - スクリプトを下の内容に書き換え
var SPREADSHEET_ID = "********"; // ここにSpreadsheetのURLを
var TODOIST_API_TOKEN = "********"; // 後述
// Compiled using ts2gas 3.6.3 (TypeScript 3.9.7)
var Todoist = /** @class */ (function () {
function Todoist() {
}
Todoist.prototype.read = function () {
var _this = this;
if (typeof this.labels !== "undefined" || typeof this.projects !== "undefined") {
return;
}
var url = "https://api.todoist.com/sync/v8/sync"
+ ("?token=" + TODOIST_API_TOKEN)
+ "&sync_token=*"
+ '&resource_types=[%22labels%22,%22projects%22]';
var response = UrlFetchApp.fetch(url);
var data = JSON.parse(response.getContentText("UTF-8"));
this.labels = {};
if (Array.isArray(data.labels)) {
data.labels.forEach(function (label) { return _this.labels[label.id] = label.name; });
}
this.projects = {};
if (Array.isArray(data.projects)) {
data.projects.forEach(function (project) { return _this.projects[project.id] = project.name; });
}
};
Todoist.prototype.labelName = function (id) {
this.read();
return this.labels[id];
};
Todoist.prototype.labelNames = function (ids) {
var _this = this;
var names = [];
ids.forEach(function (id) { return names.push(_this.labelName(id)); });
return names.join(",");
};
Todoist.prototype.projectName = function (id) {
this.read();
return this.projects[id];
};
return Todoist;
}());
var MySpreadsheet = /** @class */ (function () {
function MySpreadsheet(id, sheetId) {
if (sheetId === void 0) { sheetId = 0; }
if (id.match(/^https:\/\//)) {
this.ss = SpreadsheetApp.openByUrl(id);
}
else {
this.ss = SpreadsheetApp.openById(id);
}
this.sheet = this.ss.getSheets()[sheetId];
}
MySpreadsheet.prototype.headers = function () {
var range = this.sheet.getRange(1, 1, 1, this.sheet.getMaxColumns());
return range.getValues()[0];
};
MySpreadsheet.prototype.appendRow = function (row) {
var insertRow = [];
this.headers().forEach(function (name, index) {
insertRow.push((name && name in row) ? row[name] : "");
});
this.ss.appendRow(insertRow);
};
MySpreadsheet.prototype.unshiftRow = function (row) {
this.sheet.insertRowAfter(1);
var range = this.sheet.getRange(2, 1, 1, this.sheet.getMaxColumns());
this.headers().forEach(function (name, index) {
if (name) {
range.getCell(1, index + 1).setValue(row[name]);
}
});
};
return MySpreadsheet;
}());
var RESPONSE_STATUS;
(function (RESPONSE_STATUS) {
RESPONSE_STATUS["SUCCESS"] = "success";
RESPONSE_STATUS["ERROR"] = "error";
})(RESPONSE_STATUS || (RESPONSE_STATUS = {}));
var MyResponse = /** @class */ (function () {
function MyResponse(status, message) {
this.status = status;
this.message = message;
}
MyResponse.prototype.toJSON = function () {
return {
status: this.status,
message: this.message
};
};
MyResponse.prototype.create = function () {
var output = ContentService.createTextOutput();
output.setMimeType(ContentService.MimeType.JSON);
output.setContent(JSON.stringify(this));
return output;
};
return MyResponse;
}());
function doPost(e) {
var data = JSON.parse(e.postData.contents);
var response = new MyResponse(RESPONSE_STATUS.SUCCESS, "ok");
try {
var todoist = new Todoist();
var row = {
content: data.event_data.content,
project: todoist.projectName(data.event_data.project_id),
due: "",
completed_at: Utilities.formatDate(new Date(data.event_data.date_completed), Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm:ss"),
labels: "",
priority: data.event_data.priority,
url: data.event_data.url
};
if (data.event_data.due !== null) {
row.due = data.event_data.due.date;
}
if (data.event_data.labels.length > 0) {
row.labels = todoist.labelNames(data.event_data.labels);
}
var ss = new MySpreadsheet(SPREADSHEET_ID);
ss.appendRow(row);
}
catch (err) {
console.error(err);
console.log(data);
response.status = RESPONSE_STATUS.ERROR;
response.message = err.message;
}
return response.create();
}
コンパイル前のコードはこちら。修正する場合は参考にしてください。
https://github.com/kunikada/todoist-logs-completed
WebhookのURLを設定する準備
- Todoistの管理コンソールを開く
App Management Console
https://developer.todoist.com/appconsole.html -
Create a new app
App name
は適当に -
Test token
を後で使う
Google Apps Scriptを公開する
- さきほどのスクリプトを開く
-
Test token
をコード中のTODOIST_API_TOKEN
に設定 - コードを保存する
- メニュー > 公開 > ウェブアプリケーションとして導入...
- URLを後で使う
WebhookのURLを設定する
- Todoistの管理コンソールを開く
- 作成したAppを開く
-
Webhooks callback URL
にさきほどのURLを設定 -
Watched Events
のitem:completed
にチェック Save webhooks configuration
おわり
お疲れさまでした。
参考
Todoist API 公式ドキュメント
https://developer.todoist.com/
Google Apps Script リファレンス
https://developers.google.com/apps-script/reference