Giter Site home page Giter Site logo

Comments (4)

darekkay avatar darekkay commented on June 5, 2024 2

Thanks everyone! Fixed in d5a7faf

from todoist-export.

aluxian avatar aluxian commented on June 5, 2024 1

Pasting here quickly how I fixed it:

diff --git a/src/app.js b/src/app.js
index 949b8f9..8c845c3 100644
--- a/src/app.js
+++ b/src/app.js
@@ -13,16 +13,16 @@ const config = require("./config");
 const oauth2 = require("simple-oauth2").create({
   client: {
     id: config.client_id,
-    secret: config.client_secret
+    secret: config.client_secret,
   },
   auth: {
     tokenHost: "https://todoist.com",
     tokenPath: "/oauth/access_token",
-    authorizePath: "/oauth/authorize"
+    authorizePath: "/oauth/authorize",
   },
   options: {
-    authorizationMethod: "body"
-  }
+    authorizationMethod: "body",
+  },
 });
 
 const IS_PRODUCTION = process.env.NODE_ENV === "production";
@@ -41,14 +41,16 @@ const skipLogsFor = [
   "/stylesheets/",
   "favicon.ico",
   "favicon-192.png",
-  "manifest.json"
+  "manifest.json",
 ];
 
 app.use(
   morgan(":method :url :status", {
     skip: function(request) {
-      return skipLogsFor.some(part => request.originalUrl.indexOf(part) !== -1);
-    }
+      return skipLogsFor.some(
+        (part) => request.originalUrl.indexOf(part) !== -1
+      );
+    },
   })
 );
 app.use(bodyParser.json());
@@ -61,12 +63,31 @@ app.get(`${subdirectory}/`, (req, res) => {
 });
 
 const callApi = async (api, parameters) => {
-  const response = await axios({
-    method: "post",
-    url: `https://todoist.com/API/v8/${api}`,
-    data: parameters
-  });
-  return response.data;
+  try {
+    const response = await axios({
+      method: "post",
+      url: `https://todoist.com/API/v9/${api}`,
+      data: parameters,
+    });
+    console.log(
+      "called:",
+      response.config,
+      response.status,
+      JSON.stringify(response.headers).substring(0, 140),
+      "\n",
+      JSON.stringify(response.data).substring(0, 140)
+    );
+    return response.data;
+  } catch (err) {
+    console.error(
+      "axios failed",
+      err.config,
+      err.response.data,
+      err.response.status,
+      err.response.headers
+    );
+    throw err;
+  }
 };
 
 const renderErrorPage = (res, message, error) => {
@@ -74,20 +95,20 @@ const renderErrorPage = (res, message, error) => {
   res.status((error && error.status) || 500);
   res.render("error", {
     message,
-    error: IS_PRODUCTION ? undefined : error
+    error: IS_PRODUCTION ? undefined : error,
   });
 };
 
 app.post(`${subdirectory}/auth`, (req, res) => {
   var format = req.body.format || "json"; // csv vs. json
   if (req.body.archived) {
-      format += FORMAT_SUFFIX_INCLUDE_ARCHIVED;
+    format += FORMAT_SUFFIX_INCLUDE_ARCHIVED;
   }
 
   res.redirect(
     oauth2.authorizationCode.authorizeURL({
       scope: "data:read",
-      state: format
+      state: format,
     })
   );
 });
@@ -99,7 +120,7 @@ app.get(`${subdirectory}/export`, async (req, res) => {
 
   try {
     const authResponse = await oauth2.authorizationCode.getToken({
-      code: req.query.code
+      code: req.query.code,
     });
 
     const token = authResponse["access_token"];
@@ -111,65 +132,81 @@ app.get(`${subdirectory}/export`, async (req, res) => {
   }
 });
 
-const escapeCommas = syncData =>
-  syncData.items.map(item => ({
+const escapeCommas = (syncData) =>
+  syncData.items.map((item) => ({
     ...item,
     labels: `"${item.labels.toString()}"`,
-    content: `"${item.content.toString()}"`
+    content: `"${item.content.toString()}"`,
   }));
 
 /* Convert label IDs into their corresponding names */
-const convertLabelNames = syncData => {
+const convertLabelNames = (syncData) => {
   const labelNames = syncData.labels.reduce(
     (acc, label) => ({ ...acc, [label.id]: label.name }),
     {}
   );
 
-  return syncData.items.map(item => ({
+  return syncData.items.map((item) => ({
     ...item,
-    labels: item.labels.map(labelId => labelNames[labelId])
+    labels: item.labels.map((labelId) => labelNames[labelId]),
   }));
 };
 
 /* Convert project IDs into their corresponding names */
-const convertProjectNames = syncData => {
+const convertProjectNames = (syncData) => {
   const projectNames = syncData.projects.reduce(
     (acc, project) => ({ ...acc, [project.id]: project.name }),
     {}
   );
 
-  return syncData.items.map(item => ({
+  return syncData.items.map((item) => ({
     ...item,
-    project_id: projectNames[item.project_id]
+    project_id: projectNames[item.project_id],
   }));
 };
 
 /* Convert user IDs into their corresponding names */
-const convertUserNames = syncData => {
+const convertUserNames = (syncData) => {
   const userNames = syncData.collaborators.reduce(
     (acc, collaborator) => ({
       ...acc,
-      [collaborator.id]: collaborator.full_name
+      [collaborator.id]: collaborator.full_name,
     }),
     {}
   );
 
-  return syncData.items.map(item => ({
+  return syncData.items.map((item) => ({
     ...item,
     assigned_by_uid: userNames[item.assigned_by_uid] || null,
     added_by_uid: userNames[item.added_by_uid] || null,
-    user_id: userNames[item.user_id] || null
+    user_id: userNames[item.user_id] || null,
   }));
 };
 
 const fetchCompleted = async function(token, offset = 0) {
-  const page = await callApi("completed/get_all", { token: token, limit: COMPL_MAX_PAGE_SIZE, offset: offset })
-  if (page.items.length == COMPL_MAX_PAGE_SIZE || Object.keys(page.projects).length == COMPL_MAX_PAGE_SIZE) {
-    const remainder = await fetchCompleted(token, offset + COMPL_MAX_PAGE_SIZE);
-    return {
-      items: page.items.concat(remainder.items),
-      projects: Object.assign({}, page.projects, remainder.projects),
-    };
+  console.log("fetching completed items from offset", offset);
+  const page = await callApi("completed/get_all", {
+    token: token,
+    limit: COMPL_MAX_PAGE_SIZE,
+    offset: offset,
+  });
+  if (
+    page.items.length == COMPL_MAX_PAGE_SIZE ||
+    Object.keys(page.projects).length == COMPL_MAX_PAGE_SIZE
+  ) {
+    try {
+      const remainder = await fetchCompleted(
+        token,
+        offset + COMPL_MAX_PAGE_SIZE
+      );
+      return {
+        items: page.items.concat(remainder.items),
+        projects: Object.assign({}, page.projects, remainder.projects),
+      };
+    } catch (err) {
+      console.log("recursive call failed, maybe we're done?");
+      return page;
+    }
   } else {
     return page;
   }
@@ -179,7 +216,7 @@ const exportData = async (res, token, format = "csv") => {
   const syncData = await callApi("sync", {
     token: token,
     sync_token: "*",
-    resource_types: '["all"]'
+    resource_types: '["all"]',
   });
 
   if (syncData === undefined) {
@@ -188,11 +225,14 @@ const exportData = async (res, token, format = "csv") => {
 
   // Fetch completed tasks (premium-only)
   if (format.includes(FORMAT_SUFFIX_INCLUDE_ARCHIVED)) {
-      if (!syncData.user.is_premium) {
-          return renderErrorPage(res, "Must be Todoist Premium to export archived items.");
-      }
-      format = format.replace(FORMAT_SUFFIX_INCLUDE_ARCHIVED, '');
-      syncData.completed = await fetchCompleted(token);
+    if (!syncData.user.is_premium) {
+      return renderErrorPage(
+        res,
+        "Must be Todoist Premium to export archived items."
+      );
+    }
+    format = format.replace(FORMAT_SUFFIX_INCLUDE_ARCHIVED, "");
+    syncData.completed = await fetchCompleted(token);
   }
 
   if (format === "json") {

The most important changes are in callApi() (v8 -> v9) and fetchCompleted() (for some reason it would fail on the last call).

Excuse the sloppiness and formatting changes, I did this quickly in VS Code, just wanted to get it to work ASAP.

from todoist-export.

kparal avatar kparal commented on June 5, 2024

I'm getting the same error with 410:

Request failed with status code 410

Error: Request failed with status code 410
    at createError (/home/kparal/apps/todoist-export/node_modules/axios/lib/core/createError.js:16:15)
    at settle (/home/kparal/apps/todoist-export/node_modules/axios/lib/core/settle.js:17:12)
    at IncomingMessage.handleStreamEnd (/home/kparal/apps/todoist-export/node_modules/axios/lib/adapters/http.js:236:11)
    at IncomingMessage.emit (node:events:525:35)
    at endReadableNT (node:internal/streams/readable:1358:12)
    at processTicksAndRejections (node:internal/process/task_queues:83:21)

from todoist-export.

kparal avatar kparal commented on June 5, 2024

Great, thanks a lot. Can you please submit a PR? Ping @darekkay

from todoist-export.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.