Currently the module doesn't support a task list that isn't in a folder.
Here's the updated fix that supports the scenario where the task is not in a folder in a workspace.
/* Magic Mirror Module
* By: Andrew Matayka
* Module: MMM-ClickUPv2
* Version: 0.01
*/
const NodeHelper = require("node_helper");
const request = require("request");
const fs = require("fs");
let accessToken = undefined;
module.exports = NodeHelper.create({
//Ran on the start of the node helper
start: function () {
console.log("%cMMM-ClickUPv2 (node): Node Helper Started!", "color: yellow;");
},
//Ran when receiving notification from the module
socketNotificationReceived: function (notification, payload) {
if (notification === "Request_AccessToken") {
this.config = payload;
if (this.config.debug) console.log("\n%cMMM-ClickUPv2 (node): Request Access Token Notification Received!", "color: yellow;");
this.requestAccessToken();
} else if (notification === "Request_TasksList") {
this.config = payload;
if (true) console.log("\n%cMMM-ClickUPv2 (node): Request Tasks List Notification Received!", "color: green;");
this.requestTasks();
}
},
requestAccessToken: function () {
const self = this;
const path = self.path;
let aTFData = "";
//Find out if AccessToken is already found and in file. If not find the Access Token
if (accessToken === undefined) {
return new Promise(resolve => {
fs.readFile(path + '/accessToken', 'UTF-8', (error, data) => {
if (error) {
self.sendSocketNotification("Error", {
error: error
});
//if(data.includes("no such")) {
let fd = fs.open(path + '/accessToken', 'w', (err, file) => {
if (err) {
throw err;
}
console.log("File is created.");
});
// fd.close(); // This was an error in the original, closing it immediately. Left as-is.
this.requestAccessToken();
// return;
//}
return console.error("MMM-ClickUPv2 (node): " + error);
}
aTFData = data;
//If the AccessToken file does not contain the Access Token, or does not exist.
if (aTFData.length === 0) {
if (this.config.debug) console.log("%cMMM-ClickUPv2 (node): Requesting Access Token!", "color: yellow;")
request({
url: "https://api.clickup.com/api/v2/oauth/token",
method: "POST",
headers: {
"content-type": "application/json",
"cache-control": "no-cache"
},
form: {
client_id: self.config.clientID,
client_secret: self.config.clientSecret,
code: self.config.accessCode
}
},
function (error, response, body) {
if (error) {
self.sendSocketNotification("Error", {
error: error
});
return console.error("MMM-ClickUPv2 (node): " + error);
}
if (response.statusCode === 200) {
let responseJson = JSON.parse(body);
accessToken = responseJson.access_token;
if (self.config.debug) console.log("%cMMM-ClickUPv2 (node): Access Token Received: '" + accessToken + "'!", "color: yellow;");
fs.appendFile(path + '/accessToken', accessToken, (err) => {
if (err) {
console.log(err)
}
if (self.config.debug) console.log("%cMMM-ClickUPv2 (node): Writing Access Token to file!", "color: yellow;")
});
resolve(accessToken);
} else {
if (self.config.debug) console.log("%cMMM-ClickUPv2 (node): ClickUP api request status - '" + response.statusCode + "'!", "color: yellow;");
if (self.config.debug) console.log("%cMMM-ClickUPv2 (node): ClickUP api request response - '" + response.body + "'!", "color: yellow;");
self.sendSocketNotification("Error", {
error: response.body,
statusCode: response.statusCode
});
}
});
} else { //If the Access Token is found in the accessToken file
if (this.config.debug) console.log("%cMMM-ClickUPv2 (node): Read Access Token from file: '" + aTFData + "'!", "color: yellow;");
resolve(aTFData);
}
});
}).then(value => {
accessToken = value;
self.config.accessToken = value;
self.sendSocketNotification("AccessToken", value);
});
} else {
console.log("Found Access Token! No need to request!");
self.config.accessToken = accessToken;
self.sendSocketNotification("AccessToken", accessToken);
}
},
requestTasks: function () {
const self = this;
if (accessToken !== undefined) {
try {
console.log("Got Access Token, ALL GOOD!");
return new Promise(resolve => {
if (this.config.debug) console.log("%cMMM-ClickUPv2 (node): Requesting Teams ID!", "color: green;");
request({
url: "https://api.clickup.com/api/v2/team",
method: "GET",
headers: {
Authorization: accessToken
}
}, function (error, response, body) {
if (!error) {
resolve(JSON.parse(body).teams[0].id);
} else {
console.error(error);
resolve(error)
}
});
}).then(value => {
if (this.config.debug) console.log("%cMMM-ClickUPv2 (node): Received Teams ID: " + value, "color: green;");
return new Promise(function (resolve) {
request({
url: 'https://api.clickup.com/api/v2/team/' + value + '/space',
method: "GET",
headers: {
Authorization: accessToken
},
}, function (error, response, body) {
if (!error)
resolve(JSON.parse(body).spaces[0].id);
else {
console.error(error);
resolve(error);
}
});
});
}).then(value => {
if (this.config.debug) console.log("%cMMM-ClickUPv2 (node): Received Spaces ID: " + value, "color: green;");
const spaceId = value; // Store Space ID for use in both branches
// --- START: MODIFIED LOGIC ---
// This check handles if folderName is null, undefined, or an empty string ""
if (self.config.folderName) {
// --- FOLDER PATH ---
if (this.config.debug) console.log("%cMMM-ClickUPv2 (node): folderName is set. Looking for folder '" + self.config.folderName + "'", "color: yellow;");
return new Promise(function (resolve) {
request({
url: 'https://api.clickup.com/api/v2/space/' + spaceId + '/folder',
method: "GET",
headers: {
Authorization: accessToken
},
}, function (error, response, body) {
if (!error) {
let folders = JSON.parse(body).folders;
let found = false;
folders.forEach(folder => {
if (self.config.folderName === folder.name) {
resolve(folder.id);
found = true;
}
});
if (!found) {
console.error("MMM-ClickUPv2 (node): Could not find folder named '" + self.config.folderName + "'");
resolve(null); // Resolve with null to stop the chain
}
} else {
console.error(error);
resolve(error)
}
});
}).then(value => {
if (this.config.debug) console.log("%cMMM-ClickUPv2 (node): Received Folders ID: " + value, "color: green;")
if (!value) return new Promise(resolve => resolve(null)); // Stop chain if folder wasn't found
return new Promise(function (resolve) {
request({
url: 'https://api.clickup.com/api/v2/folder/' + value + '/list',
method: "GET",
headers: {
Authorization: accessToken
},
}, function (error, response, body) {
if (!error) {
let lists = JSON.parse(body).lists;
let found = false;
lists.forEach(list => {
if (self.config.listName === list.name) {
resolve(list.id);
found = true;
}
});
if (!found) {
console.error("MMM-ClickUPv2 (node): Could not find list '" + self.config.listName + "' in folder '" + self.config.folderName + "'");
resolve(null);
}
} else {
console.error(error);
resolve(error);
}
});
});
});
} else {
// --- FOLDERLESS PATH ---
if (this.config.debug) console.log("%cMMM-ClickUPv2 (node): folderName is null/empty/missing. Looking for folderless list '" + self.config.listName + "'", "color: yellow;");
return new Promise(function (resolve) {
request({
url: 'https://api.clickup.com/api/v2/space/' + spaceId + '/list',
method: "GET",
headers: {
Authorization: accessToken
},
}, function (error, response, body) {
if (!error) {
let lists = JSON.parse(body).lists;
let found = false;
lists.forEach(list => {
if (self.config.listName === list.name) {
resolve(list.id);
found = true;
}
});
if (!found) {
console.error("MMM-ClickUPv2 (node): Could not find folderless list named '" + self.config.listName + "'");
resolve(null);
}
} else {
console.error(error);
resolve(error);
}
});
});
}
// --- END: MODIFIED LOGIC ---
}).then(value => {
if (this.config.debug) console.log("%cMMM-ClickUPv2 (node): Received Lists ID: " + value, "color: green;");
if (!value) return; // If value is null (list not found), stop here.
return new Promise(function (resolve) {
request({
url: 'https://api.clickup.com/api/v2/list/' + value + '/task?archived=false&subtasks=true&include_closed=true&order_by=due_date',
method: "GET",
headers: {
Authorization: accessToken
},
}, function (error, response, body) {
if (!error)
resolve(JSON.parse(body));
else
resolve(error);
});
}).then(value => {
if (!value) return; // Stop if previous promise failed
if (this.config.debug) console.log("%cMMM-ClickUPv2 (node): Received Tasks!", "color: green;");
self.sendSocketNotification("Tasks_List", value);
});
});
} catch (e) {
console.log(e);
}
} else {
if (this.config.debug) console.log("%cMMM-ClickUPv2: No Access Token Found!!!", "color: cyan;");
}
console.log("Still here");
}
});
Currently the module doesn't support a task list that isn't in a folder.
Here's the updated fix that supports the scenario where the task is not in a folder in a workspace.
File: node_helper.js