Giter VIP home page Giter VIP logo

todoist-net's Introduction

Todoist.Net

Build status Quality Gate NuGet

A Todoist Sync API client for .NET.

Installation

The library is available as a Nuget package.

Install-Package Todoist.Net

Get started

Creating Todoist client

ITodoistClient client = new TodoistClient("API token");

Quick add

Implementation of the Quick Add Task available in the official clients.

var quickAddItem = new QuickAddItem("Task title @Label1 #Project1 +ExampleUser");
var task = await client.Items.QuickAddAsync(quickAddItem);

Simple API calls

// Get all resources (labels, projects, tasks, notes etc.).
var resources = await client.GetResourcesAsync();

// Get only projects and labels.
var projectsAndLabels = await client.GetResourcesAsync(ResourceType.Projects, ResourceType.Labels);

// Get only projects.
var projectsOnly = await client.GetResourcesAsync(ResourceType.Projects);

// Alternatively you can use this API to get projects.
var projects = await client.Projects.GetAsync();

// Add a task with a note.
var taskId = await client.Items.AddAsync(new Item("New task"));
await client.Notes.AddToItemAsync(new Note("Task description"), taskId);

Transactions (Batching)

Batching: reading and writing of multiple resources can be done in a single HTTP request.

Add a new project, task and note in one request.

// Create a new transaction.
var transaction = client.CreateTransaction();

// These requests are queued and will be executed later.
var projectId = await transaction.Project.AddAsync(new Project("New project"));
var taskId = await transaction.Items.AddAsync(new Item("New task", projectId));
await transaction.Notes.AddToItemAsync(new Note("Task description"), taskId);

// Execute all the requests in the transaction in a single HTTP request.
await transaction.CommitAsync();

Sending null values when updating entities.

When updating entities, Todoist API only updates properties included in the request body, using a PATCH request style. That's why all properties with null values are not included by default, to allow updating without fetching the entity first, since including null properties will update them to null.

However, if you want to intentionally send a null value to the API, you need to use the Unset extension method, for example:

// This code removes a task's due date.
var task = new UpdateItem("TASK_ID");
task.Unset(t => t.DueDate);

await client.Items.UpdateAsync(task);

todoist-net's People

Contributors

ahmedzaki99 avatar alexgirarddev avatar bogdanbujdea avatar dependabot[bot] avatar luxifer-art avatar olsh avatar yonka2019 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

todoist-net's Issues

Temp ID assigned to new entities differ from Temp ID in thier add commands

It appears that when we try to create a new entity (e.g. Project) with a TempId assigned to it, the temp_id sent to the Todoist API is not the same.

That results in the error "Invalid temporary id" when we attempt to use the new entity's TempId that we created for other commands, such as the following example in Todoist Docs:

An example that shows how temporary IDs can be used and referenced:

[
  { 
    "type": "project_add",
    "temp_id": "c7beb07f-b226-4eb1-bf63-30d782b07b1a",
    "args": {
      "name": "Shopping List"
    },
    "uuid": "ac417051-1cdc-4dc3-b4f8-14526d5bfd16"
  },
  {
    "type": "item_add",
    "temp_id": "43f7ed23-a038-46b5-b2c9-4abda9097ffa",
    "args": {
      "content": "Buy Milk",
      "project_id": "c7beb07f-b226-4eb1-bf63-30d782b07b1a"
    },
    "uuid": "849fff4e-8551-4abb-bd2a-838d092775d7"
  }
]

So for example, when we try to execute the following code it always fails with the message"Invalid temporary id":

var project = new Project("Shopping List")
{
    Id = new ComplexId(Guid.NewGuid()) // temp id that we create
};
var item = new Item("Buy milk")
{
    ProjectId = project.Id // temp id created
};

var transaction = client.CreateTransaction();

await transaction.Project.AddAsync(project);
await transaction.Items.AddAsync(item);

await transaction.CommitAsync(); // fails with "Invalid temporary id"

But from what I understand, it is intended that the TempId is automatically created (not by the user), and then it could be used in further commands, so, that makes the following code work:

var transaction = client.CreateTransaction();

// Add project to transaction first.
var project = new Project("Shopping List");
await transaction.Project.AddAsync(project);

// Then add item with the automatically created temp id.
var item = new Item("Buy milk")
{
    ProjectId = project.Id // temp id 
};
await transaction.Items.AddAsync(item);

// And finally, commit.
await transaction.CommitAsync(); // success

However, this approach does not cover many scenarios where users need to assign the TempId by hand, for example, when data is created by one service and the Todoist transaction is created by another.

I tried to fix this issue with a simple workaround temporarily, but I need to know first that this fix would not break any other code or result in unexpected behavior. I will create a pull request with the fix and link it to this issue shortly.

Support returning sync token values after committing transactions (batching).

As stated in the official documentation under Add two new tasks section:

In the results, we see the following data:

  • A new sync_token which we can use later on to do more incremental syncs.

These sync tokens returned by batching requests can be very helpful in keeping the sync state.

My suggestion here is to make the ITransaction.CommitAsync() return Task<string> that when completed returns the sync_token value, instead of just empty Task.

That change is expected not to have side effects since the Task<string> behaves exactly like a plain Task but with a result.

Thanks in advance for your time and for the great package ๐Ÿ™‚

Getting today's items is not convinient

I'd like to poll all my todos that are due today. That's one of todoist's main features.

If I've seen it right, that is not very convinient with todoist-net. I could

  1. poll ALL items with Items.GetAsync and afterwards filter myself via DueDate.HasValue && DueDate.Date.Date == Datetime.Now.Date

  2. Create my own custom today filter in todoist, retrieve that filter in todoist.net via Filter.GetAsync. But how can I only poll items with a given ItemFilter?

Wouldn't it be great to have a direct method in todoist-net? Something like Items.GetTodaysItemsAsync()?

Otherwise I'd like to have some help/info on how that use case is intended to work.

Thanks in advance!

Can't connect to Todoist servers when in Class Library(.NET Framework) project

Hi,

Your package is running great when added through Nugets on a Visual Basic Forms project. I can connect to my Todoist, and retrieve list of projects for exemple.

When running the same piece of code to retrieve list of project inside a "Class Library(.NET Framework)", I have 2 errors to connect to Todoist servers, when using GetResourcesAsync method :

  1. WebException: Unable to connect to the remote server
  2. SocketException: An attempt was made to access a socket in a way forbidden by its access permissions

I uninstalled your package in Nugets, downloaded the source code from Github, added the Todoist.Net project to my solution, added a reference to it, in order to find from where the issues are. I found that this is in the file TodoistRestClient.cs, line78 in the method PostAsync.

My Class is using Framework 4.8, I upgrading yours that seems to be 4.5 to 4.8, same result.

I've seen that you have just upgrading your project to version 5, same result.

Find attached all screenshots I could do to show the error.

Can you help ?

First exception (in French, I translated it) :
image

Second exception :
image

Exception with your source code :
image

My project settings :
image

Upgraded your project to 4.8 (but same error) :
image
image

Suggestion to add support for dependency injection's `IHttpClientFactory`.

When this library is used by applications with dependency injection configured, HttpClients should be created with the IHttpClientFactory. But our TodoistRestClient keeps creating new instances of HttpClient every time it is instantiated, which is not recommended.

I suggest then we add the option to use IHttpClientFactory when it's available by making the following changes:

  1. Add a constructor overload for TodoistRestClient that takes an HttpClient instance as an argument:
public TodoistRestClient(string token, HttpClient httpClient)
{
    _httpClient = httpClient;

    _httpClient.BaseAddress = new Uri("https://api.todoist.com/sync/v9/");
    if (!string.IsNullOrEmpty(token))
    {
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    }
}
  1. Create an ITodoistClientFactory interface and TodoistClientFactory implementation to inject into the DI container, and use the provided IHttpClientFactory:
public interface ITodoistClientFactory
{
    TodoistClient CreateClient(string token);
}

internal sealed class TodoistClientFactory : ITodoistClientFactory
{
    private readonly IHttpClientFactory _httpClientFactory;

    public TodoistClientFactory(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public TodoistClient CreateClient(string token)
    {
        var httpClient = _httpClientFactory.CreateClient();
        var todoistRestClient = new TodoistRestClient(token, httpClient);

        return new TodoistClient(todoistRestClient);
    }
}
  1. Add an extension method to inject both the ITodoistClientFactory and IHttpClientFactory:
public static class TodoistServiceCollectionExtensions
{
    public static IServiceCollection AddTodoistClient(this IServiceCollection services)
    {
        services.AddHttpClient();
        services.AddSingleton<ITodoistClientFactory, TodoistClientFactory>();

        return services;
    }
}

That way, our TodoistClient can be instantiated the same way as HttpClient, using the factory pattern.

Usage:

  • Service configuration (e.g. program.cs):
var builder = WebApplication.CreateBuilder(args);

builder.Service.AddTodoistClient();
  • In other services (e.g. asp.net core controllers):
public class ExampleController : Controller
{
    private readonly ITodoistClientFactory _todoistClientFactory;

    public ExampleController(ITodoistClientFactory todoistClientFactory)
    {
        _todoistClientFactory = todoistClientFactory;
    }

    public IActionResult ExampleAction()
    {
        ITodoistClient client = _todoistClientFactory.CreateClient("API token");

        // Use the client to interact with the Todoist API.
        //...
    }
}

"Invalid temporary id" when adding Item with a Label

When attempting to add a label to a new item, I see a "Invalid temporary id" error when saving the new item. The label already exists in Todoist.

This was the code I ran
var item = new Item("Test"); item.Labels.Add("shopping"); await client.Items.AddAsync(item);

Exception whilst adding relative reminder to newly created item

Hello, I'm getting an exception whilst adding a relative reminder to a newly created item. I can't seem to figure out what I'm doing wrong here. Any help would be appreciated.

The item is created and DueDate is correctly set, I also await the item creation. The exception is on the manager.Reminders.AddAsync async line.

Exception

System.AggregateException
  HResult=0x80131500
  Message=One or more errors occurred. (Invalid argument value)
  Source=Todoist.Net
  StackTrace:
   at Todoist.Net.TodoistClient.ThrowIfErrors(SyncResponse syncResponse)
   at Todoist.Net.TodoistClient.<Todoist-Net-IAdvancedTodoistClient-ExecuteCommandsAsync>d__55.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Todoist.Net.Services.CommandServiceBase.<ExecuteCommandAsync>d__8.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Todoist.Net.Services.RemindersCommandService.<AddAsync>d__2.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Program.<<Main>$>d__0.MoveNext() in C:\Users\KLich\Source\Repos\KoertLichtendonk\TraktCalendarToTodoist\TraktCalendarToTodoist\Program.cs:line 42

Inner Exception 1:
TodoistException: Invalid argument value

My code

        Item calendarItem = new Item(show.Title)
        {
            DueDate = new DueDate(show.PublishDate.DateTime, false, _config.timezone)
        };

        ComplexId task = await todoistFactory.CreateTask(manager, calendarItem);

        if(!task.IsEmpty)
        {
            await manager.Reminders.AddAsync(new Reminder(task) { Type = ReminderType.Relative, MinuteOffset = 60 });
        }

Adding duration property for Item model.

The new duration feature was announced in July this year, and since then, tasks returned by the API include a duration property for paid users like this:

"duration": {
    "amount": 15,
    "unit": "minute"
}

I think it's easy to support (correct me if I'm wrong). If we add a new property in the Item model then JSON mapping will probably do the rest. Similar to the DueDate object.

Sub-tasks

Hi is there any way of extending this to add sub tasks to a task ?

UTC issue

Regarding the property Item.DueDateUtc, it does not seem to be in UTC when sent to Todoist. Here is your code for the converter:

public UtcDateTimeConverter()
{
    DateTimeFormat = "yyyy-MM-ddTHH:mm";
}

I think it is missing Z at the end: https://www.w3.org/TR/NOTE-datetime.

Even though I specifically assign a DateTime that has DateTimeKind set to Utc and hour=0 and minutes=0, it gets converted to 01:00, because the UtcDateTimeConverter does not specify UTC (missing Z).

Invalid CSRF token on v3.0.1

Hello. Happy Christmas (or happy holidays)!

Since Todoist.net have deprecated v8 of the API, moving to v9, I've updated from v2.2.2 to v3.0.1.

My code hasn't changed since both versions, but now, every time, I'm receiving Invalid CSRF token:

Screenshot 2022-12-27 at 11 02 26

I've created a test-repo with the simplest code to show the issue:

using Todoist.Net;

var todoistKey = Environment.GetEnvironmentVariable("TODOIST_APIKEY");
var client = new TodoistClient(todoistKey);
var result = await client.Projects.GetAsync(); // errors here: 409 Invalid CSRF token

I hit this error running in the cloud: GitHub code-spaces, Azure functions and Azure Container apps. It's definitely code related.
Can you / someone please pull the repo and check if you get the same thing?

[JsonConverter(typeof(UtcDateTimeConverter))] may no longer be needed on Item DueDateUtc?

Not sure if Todoist changed the format on due_date_utc, but I was getting DataTime conversion errors when requesting Items with a due date. I commented the JsonConverter decorator on DueDateUtc in the Item model and I could get Items with due dates without error.

Results from curl requesting an Item with due_date_utc populated:
date_added : Sat 12 May 2018 02:50:19 +0000
due_date_utc : Tue 12 Jun 2018 06:59:59 +0000

TodoistTokenlessClient endpoints will be deprecated soon.

It has been announced in the Todoist API mailing list that "registration" endpoints will be removed from the API by October 23, 2023.

This deprecation specifically affects the usage of our "user/register" and "user/delete" endpoints that have been available via our Sync API.

The endpoints in question have largely gone unused for quite some time and so as part of efforts to increase system stability and security, these will be removed by October 23, 2023.

This change will break TodoistTokenlessClient (typically making it useless) resulting in tests like UsersServiceTests.RegisterUpdateSettingsAndDeleteUser_Success to fail.

So, it would make sense to remove the TodoistTokenlessClient starting with the next major version.

Two dev questions about your Todoist.Net library

Hi, Oleg!

I have two dev questions about your Todoist.Net library:

  1. Is Todoist.Net lib compatible with .NET Standard 1.1 /1.3 / 1.4 ?

  2. Is there some good C# app list used your Todoist.Net lib?

My dream is to recover Todoist for Win10Mobile (and Windows 10 Homeof cause)
Now the official Todoist UWP app is obsolete: new task adding disabled (specially blocked? idk).

I'm fan of windowsphone...and all my family too ))) We want to have some lite version of Todoist on our Lumia phones :)
Please help with this issue if you can.

P.S. I have old version of Todoist.Net... and it is NET Standard 1.1-compatible.
But im not sure if it works with Todoist Sync API in 2022...

Best wishes,
Andrey Z. aka MediaExplorer74

Business Admin list all Projects and Items

Is it possible to list all Projects and Items (including completed items) from all users in a Business Account?

From what I can tell the API only allows access via a single users API Token. Is there a global token that you are aware of?

Inconsistency in conversion between the DueDate.StringDate and DueDate.Date properties

The following integration tests will fail when they are run on a machine with a GMT+2 time zone, they pretty much explain the problem:

  1. When we get a recently added task, or any task in general, the DueDate.Date property works as expected but the DueDate.StringDate property shifts time by the local time zone offset.
[Fact]
public async Task CreatedTask_ShouldReturnMatchingDueDate_WhenWeGetItById()
{
    // Arrange
    var client = new TodoistClient(TodoistAccessToken);
    var item = new Item("Task with time")
    {
        DueDate = new DueDate("22 Dec 2021 at 9:15", language: Language.English)
    };

    // Act
    await client.Items.AddAsync(item);
    var itemInfo = await client.Items.GetAsync(item.Id);

    try
    {
        // Assert
        Assert.Equal(new DateTime(2021, 12, 22, 9, 15, 0), itemInfo.Item.DueDate.Date); // Succeeds
        Assert.Equal("2021-12-22T09:15:00", itemInfo.Item.DueDate.StringDate); // Fails: StringDate returns 2021-12-22T07:15:00
    }
    finally
    {
        // Cleanup
        await client.Items.DeleteAsync(item.Id);
    }
}
  1. When we get a task and then update it with no changes at all, the due date will change the same way that DueDate.StringDate did in the last test, and again, DueDate.StringDate will shift time by additional two hours.
[Fact]
public async Task UpdatedTask_ShouldReturnMatchingDueDate_WhenNoChangesAreMade()
{
    // Arrange
    var client = new TodoistClient(TodoistAccessToken);
    var item = new Item("Task with time")
    {
        DueDate = new DueDate("22 Dec 2021 at 9:15", language: Language.English)
    };

    // Act
    await client.Items.AddAsync(item);
    var itemInfo = await client.Items.GetAsync(item.Id);

    await client.Items.UpdateAsync(itemInfo.Item);
    itemInfo = await client.Items.GetAsync(item.Id);

    try
    {
        // Assert
        Assert.Equal(new DateTime(2021, 12, 22, 9, 15, 0), itemInfo.Item.DueDate.Date); // Fails: Date returns 22/12/2021 07:15:00
        Assert.Equal("2021-12-22T09:15:00", itemInfo.Item.DueDate.StringDate); // Fails: StringDate returns 2021-12-22T05:15:00
    }
    finally
    {
        // Cleanup
        await client.Items.DeleteAsync(item.Id);
    }
}

This problem occurs in general when the task contains a floating due date, the date is treated like a local time when being converted from DateTime to string in the DueDate.StringDate property getter, hence, its string value changes from what it was given by the API.

In the previous tests, the value "2021-12-22T09:15:00" fetched from the API was converted to a DateTime of kind DateTimeKind.Unspecified, and then converted back to "2021-12-22T07:15:00" when sent back to the API for update.
That results in an unwanted update on the task without even changing any field.

Migrate from Newtonsoft.Json to System.Text.Json

Hello,

I'm trying to use this library in a .NET8 AOT app, but it seems the Newtonsoft.Json is not compatible with AOT.
Is a migration planned in the netstandard2 version of the library, to use the more recent System.Text.Json ?

I took a quick look, and there are a few uses of JsonConverter and DefaultContractResolver that can't be migrated as is.

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.