skip to content
Site header image Demo Astro Notion Setup

Lorem ipsum dolor sit amet 我爱你 with 愛してます

This is lorem ipsum


Lorem ipsum content with an image and a random file below

import process from "node:process";
import { TodoistApi } from "npm:@doist/todoist-api-typescript";
import { Client } from "npm:@notionhq/client";

const TODOIST_API_KEY = process.env.TODOIST_API_KEY;
const todoistapi = new TodoistApi(TODOIST_API_KEY);
const NOTION_API_KEY = process.env.NOTION_API_KEY;
const notion = new Client({
  auth: NOTION_API_KEY,
});

var add_to_notion_todoist_project_id = "PROJECT_ID_HERE";

var todoist_dict_mapping = {
  "habit": {
    "todoist-section-id": "SECTION_ID_HERE",
    "notion-map-type": "page",
    "notion-id": "PAGE_ID_HERE",
  },
  "papers": {
    "todoist-section-id": "SECTION_ID_HERE",
    "notion-map-type": "database",
    "notion-id": "DB_ID_HERE",
  },
};

function getNotionId(section_id) {
  if (!section_id) {
    return [todoist_dict_mapping["dump"]["notion-map-type"], todoist_dict_mapping["dump"]["notion-id"]];
  }

  for (var key in todoist_dict_mapping) {
    if (todoist_dict_mapping[key]["todoist-section-id"] === section_id) {
      return [
        todoist_dict_mapping[key]["notion-map-type"] || todoist_dict_mapping["dump"]["notion-map-type"],
        todoist_dict_mapping[key]["notion-id"] || todoist_dict_mapping["dump"]["notion-id"],
      ];
    }
  }

  return [todoist_dict_mapping["dump"]["notion-map-type"], todoist_dict_mapping["dump"]["notion-id"]];
}

function convertDateObject(due) {
  function convertToISOWithOffset(datetimeStr, timezoneStr) {
    const date = new Date(datetimeStr);
    const [, sign, hours, minutes] = timezoneStr.match(/GMT ([+-])(\d{1,2}):(\d{2})/);
    date.setUTCMinutes(date.getUTCMinutes() + (parseInt(hours) * 60 + parseInt(minutes)) * (sign === "+" ? 1 : -1));
    return date.toISOString().split(".")[0]
      + `${sign}${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
  }
  const formatDate = (date, datetime, timezone) => {
    let isoString = datetime ? datetime : date;
    if (timezone && timezone.startsWith("GMT") && timezone.length > 3) {
      return convertToISOWithOffset(datetime, timezone);
    } else {
      return isoString;
    }
  };

  return {
    start: due ? formatDate(due.date, due.datetime, due.timezone) : new Date().toISOString(),
    end: null,
    time_zone: due && due.datetime && due.timezone && due.timezone.startsWith("GMT") && due.timezone.length > 3
      ? null
      : (due && due.datetime && due.timezone ? due.timezone : "America/Los_Angeles"),
  };
}

async function addCalloutToNotionPage(page_id, content, date) {
  console.log(JSON.stringify(date));
  const response = await notion.blocks.children.append({
    block_id: page_id,
    children: [{
      "callout": {
        "rich_text": [{
          "type": "mention",
          "mention": {
            "type": "date",
            "date": date,
          },
        }],
        "icon": {
          "type": "external",
          "external": {
            "url": "https://www.notion.so/icons/circle-dot_lightgray.svg",
          },
        },
        "children": [{
          "paragraph": {
            "rich_text": [{
              "text": {
                "content": content,
              },
            }],
          },
        }],
      },
    }],
  });
  console.log(JSON.stringify(response));
}

async function addPageToNotionDatabse(database_id, content) {
  const response = await notion.pages.create({
    "parent": {
      "type": "database_id",
      "database_id": database_id,
    },
    "properties": {
      "Name": {
        "title": [{
          "text": {
            "content": content,
          },
        }],
      },
    },
  });
}

export default async function(interval: Interval) {
  var tasks = await todoistapi.getTasks({
    projectId: add_to_notion_todoist_project_id,
  });

  for (const task of tasks) {
    console.log(task);
    const [mappedNotionType, mappedNotionId] = getNotionId(task.sectionId);
    if (mappedNotionId)
    {
      if (mappedNotionType == "page" && mappedNotionId) {
        addCalloutToNotionPage(mappedNotionId, task.content, convertDateObject(task.due));
      }
      else if (mappedNotionType == "database" && mappedNotionId) {
        addPageToNotionDatabse(mappedNotionId, task.content);
      }
      todoistapi.deleteTask(task.id);
    }
  }
}
Remember this code is in format of val town and hence the weird imports. You would need to switch that to require probably (I ain’t good at JS)
Code in case you want to host somewhere else
import process from "node:process";
import { TodoistApi } from "npm:@doist/todoist-api-typescript";
import { Client } from "npm:@notionhq/client";

const TODOIST_API_KEY = process.env.TODOIST_API_KEY;
const todoistapi = new TodoistApi(TODOIST_API_KEY);
const NOTION_API_KEY = process.env.NOTION_API_KEY;
const notion = new Client({
  auth: NOTION_API_KEY,
});

var add_to_notion_todoist_project_id = "PROJECT_ID_HERE";

var todoist_dict_mapping = {
  "habit": {
    "todoist-section-id": "SECTION_ID_HERE",
    "notion-map-type": "page",
    "notion-id": "PAGE_ID_HERE",
  },
  "papers": {
    "todoist-section-id": "SECTION_ID_HERE",
    "notion-map-type": "database",
    "notion-id": "DB_ID_HERE",
  },
};

function getNotionId(section_id) {
  if (!section_id) {
    return [todoist_dict_mapping["dump"]["notion-map-type"], todoist_dict_mapping["dump"]["notion-id"]];
  }

  for (var key in todoist_dict_mapping) {
    if (todoist_dict_mapping[key]["todoist-section-id"] === section_id) {
      return [
        todoist_dict_mapping[key]["notion-map-type"] || todoist_dict_mapping["dump"]["notion-map-type"],
        todoist_dict_mapping[key]["notion-id"] || todoist_dict_mapping["dump"]["notion-id"],
      ];
    }
  }

  return [todoist_dict_mapping["dump"]["notion-map-type"], todoist_dict_mapping["dump"]["notion-id"]];
}

function convertDateObject(due) {
  function convertToISOWithOffset(datetimeStr, timezoneStr) {
    const date = new Date(datetimeStr);
    const [, sign, hours, minutes] = timezoneStr.match(/GMT ([+-])(\d{1,2}):(\d{2})/);
    date.setUTCMinutes(date.getUTCMinutes() + (parseInt(hours) * 60 + parseInt(minutes)) * (sign === "+" ? 1 : -1));
    return date.toISOString().split(".")[0]
      + `${sign}${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
  }
  const formatDate = (date, datetime, timezone) => {
    let isoString = datetime ? datetime : date;
    if (timezone && timezone.startsWith("GMT") && timezone.length > 3) {
      return convertToISOWithOffset(datetime, timezone);
    } else {
      return isoString;
    }
  };

  return {
    start: due ? formatDate(due.date, due.datetime, due.timezone) : new Date().toISOString(),
    end: null,
    time_zone: due && due.datetime && due.timezone && due.timezone.startsWith("GMT") && due.timezone.length > 3
      ? null
      : (due && due.datetime && due.timezone ? due.timezone : "America/Los_Angeles"),
  };
}

async function addCalloutToNotionPage(page_id, content, date) {
  console.log(JSON.stringify(date));
  const response = await notion.blocks.children.append({
    block_id: page_id,
    children: [{
      "callout": {
        "rich_text": [{
          "type": "mention",
          "mention": {
            "type": "date",
            "date": date,
          },
        }],
        "icon": {
          "type": "external",
          "external": {
            "url": "https://www.notion.so/icons/circle-dot_lightgray.svg",
          },
        },
        "children": [{
          "paragraph": {
            "rich_text": [{
              "text": {
                "content": content,
              },
            }],
          },
        }],
      },
    }],
  });
  console.log(JSON.stringify(response));
}

async function addPageToNotionDatabse(database_id, content) {
  const response = await notion.pages.create({
    "parent": {
      "type": "database_id",
      "database_id": database_id,
    },
    "properties": {
      "Name": {
        "title": [{
          "text": {
            "content": content,
          },
        }],
      },
    },
  });
}

export default async function(interval: Interval) {
  var tasks = await todoistapi.getTasks({
    projectId: add_to_notion_todoist_project_id,
  });

  for (const task of tasks) {
    console.log(task);
    const [mappedNotionType, mappedNotionId] = getNotionId(task.sectionId);
    if (mappedNotionId)
    {
      if (mappedNotionType == "page" && mappedNotionId) {
        addCalloutToNotionPage(mappedNotionId, task.content, convertDateObject(task.due));
      }
      else if (mappedNotionType == "database" && mappedNotionId) {
        addPageToNotionDatabse(mappedNotionId, task.content);
      }
      todoistapi.deleteTask(task.id);
    }
  }
}
Remember this code is in format of val town and hence the weird imports. You would need to switch that to require probably (I ain’t good at JS)



sample2.html Download