skip to content
Site header image Nerdy Momo Cat

Add to Notion through Todoist

Last Updated:

Comment

I wish there was a way to run python on valtown -- because this is a script I want to use with OpenAI in the future and I want instructor and pydantic for LLM-fuzzy-estimation-processing of fields.

This js script will do in the meantime I guess.

Add to a notion page as a callout or to a page name to notion database through todoist (corresponding valtown script)

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)


  • The script either adds a page with content as title to database (which I use with Notion Reference Manager), or adds the content in callout with date to a page.
  • Uses a single project in Todoist and sections to identify which page/database the content goes into