todoist-ruby 0.1.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +95 -51
- data/lib/todoist.rb +4 -2
- data/lib/todoist/client.rb +119 -0
- data/lib/todoist/config.rb +64 -0
- data/lib/todoist/misc/activity.rb +14 -24
- data/lib/todoist/misc/backups.rb +2 -2
- data/lib/todoist/misc/completed.rb +5 -5
- data/lib/todoist/misc/items.rb +4 -4
- data/lib/todoist/misc/projects.rb +4 -4
- data/lib/todoist/misc/quick.rb +2 -2
- data/lib/todoist/misc/templates.rb +5 -5
- data/lib/todoist/misc/uploads.rb +5 -5
- data/lib/todoist/service.rb +10 -0
- data/lib/todoist/sync/filters.rb +6 -6
- data/lib/todoist/sync/items.rb +44 -48
- data/lib/todoist/sync/labels.rb +6 -6
- data/lib/todoist/sync/notes.rb +5 -5
- data/lib/todoist/sync/projects.rb +8 -8
- data/lib/todoist/sync/reminders.rb +6 -6
- data/lib/todoist/util/api_helper.rb +39 -26
- data/lib/todoist/util/network_helper.rb +55 -19
- data/lib/todoist/util/parse_helper.rb +5 -5
- data/lib/todoist/version.rb +1 -1
- data/todoist.gemspec +12 -11
- metadata +44 -32
- data/lib/todoist/misc/query.rb +0 -30
- data/lib/todoist/misc/user.rb +0 -16
- data/lib/todoist/util/command_synchronizer.rb +0 -54
- data/lib/todoist/util/config.rb +0 -78
data/lib/todoist/sync/labels.rb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
module Todoist
|
2
2
|
module Sync
|
3
|
-
class Labels
|
3
|
+
class Labels < Todoist::Service
|
4
4
|
include Todoist::Util
|
5
5
|
|
6
6
|
# Return a Hash of labels where key is the id of a label and value is a label
|
7
7
|
def collection
|
8
|
-
return
|
8
|
+
return @client.api_helper.collection("labels")
|
9
9
|
end
|
10
10
|
|
11
11
|
# Add a label with a given hash of attributes and returns the label id
|
12
12
|
def add(args)
|
13
|
-
return
|
13
|
+
return @client.api_helper.add(args, "label_add")
|
14
14
|
end
|
15
15
|
|
16
16
|
# Update label given a hash of attributes
|
17
17
|
def update(args)
|
18
|
-
return
|
18
|
+
return @client.api_helper.command(args, "label_update")
|
19
19
|
end
|
20
20
|
|
21
21
|
# Delete a label given a label
|
22
22
|
def delete(label)
|
23
23
|
args = {id: label.id}
|
24
|
-
return
|
24
|
+
return @client.api_helper.command(args, "label_delete")
|
25
25
|
end
|
26
26
|
|
27
27
|
# Update orders for an array of labels
|
@@ -31,7 +31,7 @@ module Todoist
|
|
31
31
|
args[label.id] = label.item_order
|
32
32
|
end
|
33
33
|
args = {id_order_mapping: args.to_json}
|
34
|
-
return
|
34
|
+
return @client.api_helper.command(args, "label_update_orders")
|
35
35
|
end
|
36
36
|
|
37
37
|
end
|
data/lib/todoist/sync/notes.rb
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
module Todoist
|
2
2
|
module Sync
|
3
|
-
class Notes
|
3
|
+
class Notes < Todoist::Service
|
4
4
|
include Todoist::Util
|
5
5
|
|
6
6
|
# Return a Hash of notes where key is the id of a note and value is a note
|
7
7
|
def collection
|
8
|
-
return
|
8
|
+
return @client.api_helper.collection("notes")
|
9
9
|
end
|
10
10
|
|
11
11
|
# Add a note with a given hash of attributes and returns the note id.
|
12
12
|
# Please note that item_id or project_id key is required. In addition,
|
13
13
|
# content is also a required key in the hash.
|
14
14
|
def add(args)
|
15
|
-
return
|
15
|
+
return @client.api_helper.add(args, "note_add")
|
16
16
|
end
|
17
17
|
|
18
18
|
# Update a note given a hash of attributes
|
19
19
|
def update(args)
|
20
|
-
return
|
20
|
+
return @client.api_helper.command(args, "note_update")
|
21
21
|
end
|
22
22
|
|
23
23
|
# Delete notes given an a note
|
24
24
|
def delete(note)
|
25
25
|
args = {id: note.id}
|
26
|
-
return
|
26
|
+
return @client.api_helper.command(args, "note_delete")
|
27
27
|
end
|
28
28
|
|
29
29
|
|
@@ -2,43 +2,43 @@ module Todoist
|
|
2
2
|
module Sync
|
3
3
|
|
4
4
|
|
5
|
-
class Projects
|
5
|
+
class Projects < Todoist::Service
|
6
6
|
include Todoist::Util
|
7
7
|
|
8
8
|
# Return a Hash of projects where key is the id of a project and value is a project
|
9
9
|
def collection
|
10
|
-
return
|
10
|
+
return @client.api_helper.collection("projects")
|
11
11
|
end
|
12
12
|
|
13
13
|
# Add a project with a given hash of attributes and returns the project id
|
14
14
|
def add(args)
|
15
|
-
return
|
15
|
+
return @client.api_helper.add(args, "project_add")
|
16
16
|
end
|
17
17
|
|
18
18
|
# Delete projects given an array of projects
|
19
19
|
def delete(projects)
|
20
20
|
project_ids = projects.collect { |project| project.id }
|
21
21
|
args = {ids: project_ids.to_json}
|
22
|
-
return
|
22
|
+
return @client.api_helper.command(args, "project_delete")
|
23
23
|
end
|
24
24
|
|
25
25
|
# Archive projects given an array of projects
|
26
26
|
def archive(projects)
|
27
27
|
project_ids = projects.collect { |project| project.id }
|
28
28
|
args = {ids: project_ids.to_json}
|
29
|
-
return
|
29
|
+
return @client.api_helper.command(args, "project_archive")
|
30
30
|
end
|
31
31
|
|
32
32
|
# Unarchive projects given an array of projects
|
33
33
|
def unarchive(projects)
|
34
34
|
project_ids = projects.collect { |project| project.id }
|
35
35
|
args = {ids: project_ids.to_json}
|
36
|
-
return
|
36
|
+
return @client.api_helper.command(args, "project_unarchive")
|
37
37
|
end
|
38
38
|
|
39
39
|
# Update project given a hash of attributes
|
40
40
|
def update(args)
|
41
|
-
return
|
41
|
+
return @client.api_helper.command(args, "project_update")
|
42
42
|
end
|
43
43
|
|
44
44
|
# Update orders and indents for an array of projects
|
@@ -48,7 +48,7 @@ module Todoist
|
|
48
48
|
tuples[project.id] = [project.item_order, project.indent]
|
49
49
|
end
|
50
50
|
args = {ids_to_orders_indents: tuples.to_json}
|
51
|
-
return
|
51
|
+
return @client.api_helper.command(args, "project_update_orders_indents")
|
52
52
|
end
|
53
53
|
|
54
54
|
end
|
@@ -1,35 +1,35 @@
|
|
1
1
|
module Todoist
|
2
2
|
module Sync
|
3
|
-
class Reminders
|
3
|
+
class Reminders < Todoist::Service
|
4
4
|
include Todoist::Util
|
5
5
|
|
6
6
|
# Return a Hash of reminders where key is the id of a reminder and value is a reminder
|
7
7
|
def collection
|
8
|
-
return
|
8
|
+
return @client.api_helper.collection("reminders")
|
9
9
|
end
|
10
10
|
|
11
11
|
# Add a reminder with a given hash of attributes and returns the reminder id.
|
12
12
|
# Please note that item_id is required as is a date as specific in the
|
13
13
|
# documentation. This method can be tricky to all.
|
14
14
|
def add(args)
|
15
|
-
return
|
15
|
+
return @client.api_helper.add(args, "reminder_add")
|
16
16
|
end
|
17
17
|
|
18
18
|
# Update a reminder given a hash of attributes
|
19
19
|
def update(args)
|
20
|
-
return
|
20
|
+
return @client.api_helper.command(args, "reminder_update")
|
21
21
|
end
|
22
22
|
|
23
23
|
# Delete reminder given an array of reminders
|
24
24
|
def delete(reminder)
|
25
25
|
args = {id: reminder.id}
|
26
|
-
return
|
26
|
+
return @client.api_helper.command(args, "reminder_delete")
|
27
27
|
end
|
28
28
|
|
29
29
|
# Clear locations which is used for location reminders
|
30
30
|
def clear_locations
|
31
31
|
args = {}
|
32
|
-
return
|
32
|
+
return @client.api_helper.command(args, "clear_locations")
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -1,10 +1,9 @@
|
|
1
1
|
require "net/http"
|
2
2
|
require "json"
|
3
|
-
require "todoist/
|
3
|
+
require "todoist/config"
|
4
4
|
require "todoist/util/network_helper"
|
5
5
|
require "todoist/util/parse_helper"
|
6
6
|
require "todoist/util/uuid"
|
7
|
-
require "todoist/util/command_synchronizer"
|
8
7
|
require "ostruct"
|
9
8
|
require 'concurrent'
|
10
9
|
|
@@ -15,18 +14,21 @@ module Todoist
|
|
15
14
|
module Util
|
16
15
|
|
17
16
|
class ApiHelper
|
18
|
-
|
19
|
-
|
17
|
+
def initialize(client)
|
18
|
+
@client = client
|
19
|
+
@object_cache = {"projects" => Concurrent::Hash.new({}), "labels" => Concurrent::Hash.new({}),
|
20
20
|
"items" => Concurrent::Hash.new({}), "notes" => Concurrent::Hash.new({}),
|
21
21
|
"reminders" => Concurrent::Hash.new({}), "filters" => Concurrent::Hash.new({})
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
}
|
23
|
+
@sync_token_cache = Concurrent::Hash.new({"projects" => "*", "labels" => "*",
|
24
|
+
"items" => "*", "notes" => "*", "reminders" => "*", "filters" => "*"})
|
25
|
+
@network_helper = NetworkHelper.new(client)
|
26
|
+
end
|
27
|
+
|
28
|
+
def collection(type)
|
29
|
+
@network_helper.sync
|
28
30
|
|
29
|
-
response =
|
31
|
+
response = @network_helper.get_sync_response({sync_token: sync_token(type), resource_types: "[\"#{type}\"]"})
|
30
32
|
response[type].each do |object_data|
|
31
33
|
object = OpenStruct.new(object_data)
|
32
34
|
objects(type)[object.id] = object
|
@@ -35,23 +37,24 @@ module Todoist
|
|
35
37
|
return objects(type)
|
36
38
|
end
|
37
39
|
|
38
|
-
def
|
40
|
+
def exec(args, command, temporary_resource_id)
|
39
41
|
command_uuid = Uuid.command_uuid
|
40
42
|
commands = {type: command, temp_id: temporary_resource_id, uuid: command_uuid, args: args}
|
41
|
-
response =
|
43
|
+
response = @network_helper.get_sync_response({commands: "[#{commands.to_json}]"})
|
42
44
|
raise RuntimeError, "Response returned is not ok" unless response["sync_status"][command_uuid] == "ok"
|
43
45
|
return response
|
44
46
|
end
|
45
47
|
|
46
|
-
def
|
48
|
+
def command(args, command)
|
47
49
|
temporary_resource_id = Uuid.temporary_resource_id
|
48
50
|
command_uuid = Uuid.command_uuid
|
49
51
|
command = {type: command, temp_id: temporary_resource_id, uuid: command_uuid, args: args}
|
50
|
-
|
52
|
+
|
53
|
+
@network_helper.queue(command)
|
51
54
|
return true
|
52
55
|
end
|
53
56
|
|
54
|
-
def
|
57
|
+
def add(args, command)
|
55
58
|
temporary_resource_id = Uuid.temporary_resource_id
|
56
59
|
command_uuid = Uuid.command_uuid
|
57
60
|
command = {type: command, temp_id: temporary_resource_id, uuid: command_uuid, args: args}
|
@@ -60,28 +63,38 @@ module Todoist
|
|
60
63
|
object.id = temp_id_mappings[temporary_resource_id] if temp_id_mappings[temporary_resource_id]
|
61
64
|
end
|
62
65
|
|
63
|
-
|
66
|
+
@network_helper.queue(command, temp_id_callback)
|
64
67
|
return object
|
65
68
|
end
|
66
69
|
|
67
|
-
def
|
68
|
-
|
70
|
+
def get_response(command, params = {}, token = true)
|
71
|
+
@network_helper.get_response(command, params, token)
|
69
72
|
end
|
70
73
|
|
71
|
-
|
74
|
+
def get_multipart_response(command, params)
|
75
|
+
@network_helper.get_multipart_response(command, params)
|
76
|
+
end
|
72
77
|
|
78
|
+
def multipart_file(file)
|
79
|
+
@network_helper.multipart_file(file)
|
80
|
+
end
|
73
81
|
|
82
|
+
def sync
|
83
|
+
@network_helper.sync
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
74
87
|
|
75
|
-
def
|
76
|
-
|
88
|
+
def objects(type)
|
89
|
+
@object_cache[type]
|
77
90
|
end
|
78
91
|
|
79
|
-
def
|
80
|
-
|
92
|
+
def sync_token(type)
|
93
|
+
@sync_token_cache[type]
|
81
94
|
end
|
82
95
|
|
83
|
-
def
|
84
|
-
|
96
|
+
def set_sync_token(type, value)
|
97
|
+
@sync_token_cache[type] = value
|
85
98
|
end
|
86
99
|
|
87
100
|
end
|
@@ -1,23 +1,33 @@
|
|
1
1
|
require "net/http"
|
2
2
|
require "json"
|
3
|
-
require "todoist/
|
3
|
+
require "todoist/config"
|
4
4
|
require 'net/http/post/multipart'
|
5
5
|
require 'mimemagic'
|
6
|
+
require 'openssl'
|
6
7
|
|
7
8
|
module Todoist
|
8
9
|
module Util
|
9
10
|
class NetworkHelper
|
10
11
|
|
11
|
-
@@last_request_time = 0.0
|
12
12
|
|
13
|
-
|
13
|
+
|
14
|
+
def initialize(client)
|
15
|
+
@client = client
|
16
|
+
@command_cache = Concurrent::Array.new([])
|
17
|
+
@command_mutex = Mutex.new
|
18
|
+
@temp_id_callback_cache = Concurrent::Array.new([])
|
19
|
+
@last_request_time = 0.0
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def configure_http(command)
|
14
24
|
http = Net::HTTP.new(Config.getURI()[command].host, Config.getURI()[command].port)
|
15
25
|
http.use_ssl = true
|
16
26
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
17
27
|
return http
|
18
28
|
end
|
19
29
|
|
20
|
-
def
|
30
|
+
def configure_request(command, params)
|
21
31
|
request = Net::HTTP::Post.new(Config.getURI()[command].request_uri)
|
22
32
|
request.set_form_data(params)
|
23
33
|
return request
|
@@ -25,9 +35,9 @@ module Todoist
|
|
25
35
|
|
26
36
|
# Files need to be of class UploadIO
|
27
37
|
|
28
|
-
def
|
29
|
-
token = {token:
|
30
|
-
http =
|
38
|
+
def get_multipart_response(command, params={})
|
39
|
+
token = {token: @client.token}
|
40
|
+
http = configure_http(command)
|
31
41
|
url = Config.getURI()[command]
|
32
42
|
http.start do
|
33
43
|
req = Net::HTTP::Post::Multipart.new(url, token.merge(params))
|
@@ -40,11 +50,11 @@ module Todoist
|
|
40
50
|
end
|
41
51
|
end
|
42
52
|
|
43
|
-
def
|
44
|
-
token = token ? {token:
|
45
|
-
http =
|
46
|
-
request =
|
47
|
-
retry_after_secs = Todoist::
|
53
|
+
def get_response(command, params ={}, token = true)
|
54
|
+
token = token ? {token: @client.token} : {}
|
55
|
+
http = configure_http(command)
|
56
|
+
request = configure_request(command, token.merge(params))
|
57
|
+
retry_after_secs = Todoist::Config.retry_time
|
48
58
|
# Hack to fix encoding issues with Net:HTTP for login case
|
49
59
|
request.body = request.body.gsub '%40', '@' unless token
|
50
60
|
while true
|
@@ -67,7 +77,7 @@ module Todoist
|
|
67
77
|
when 429
|
68
78
|
puts("Encountered 429 - retry after #{retry_after_secs}")
|
69
79
|
sleep(retry_after_secs)
|
70
|
-
retry_after_secs *= Todoist::
|
80
|
+
retry_after_secs *= Todoist::Config.retry_time
|
71
81
|
when 500
|
72
82
|
raise StandardError, "HTTP 500 Error - The request failed due to a server error."
|
73
83
|
when 503
|
@@ -77,24 +87,50 @@ module Todoist
|
|
77
87
|
|
78
88
|
end
|
79
89
|
|
80
|
-
def
|
81
|
-
time_since_last_request = Time.now.to_f -
|
90
|
+
def throttle_request(http, request)
|
91
|
+
time_since_last_request = Time.now.to_f - @last_request_time
|
82
92
|
|
83
|
-
if (time_since_last_request < Todoist::
|
84
|
-
wait = Todoist::
|
93
|
+
if (time_since_last_request < Todoist::Config.delay_between_requests)
|
94
|
+
wait = Todoist::Config.delay_between_requests - time_since_last_request
|
85
95
|
puts("Throttling request by: #{wait}")
|
86
96
|
sleep(wait)
|
87
97
|
end
|
88
|
-
|
98
|
+
@last_request_time = Time.now.to_f
|
89
99
|
http.request(request)
|
90
100
|
end
|
91
101
|
|
92
102
|
# Prepares a file for multipart upload
|
93
|
-
def
|
103
|
+
def multipart_file(file)
|
94
104
|
filename = File.basename(file)
|
95
105
|
mime_type = MimeMagic.by_path(filename).type
|
96
106
|
return UploadIO.new(file, mime_type, filename)
|
97
107
|
end
|
108
|
+
|
109
|
+
def queue(command, callback = nil)
|
110
|
+
@command_mutex.synchronize do
|
111
|
+
@command_cache.push(command)
|
112
|
+
@temp_id_callback_cache.push(callback) if callback
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
def sync
|
118
|
+
@command_mutex.synchronize do
|
119
|
+
response = get_sync_response({commands: @command_cache.to_json})
|
120
|
+
@command_cache.clear
|
121
|
+
# Process callbacks here
|
122
|
+
temp_id_mappings = response["temp_id_mapping"]
|
123
|
+
@temp_id_callback_cache.each do |callback|
|
124
|
+
callback.(temp_id_mappings)
|
125
|
+
end
|
126
|
+
@temp_id_callback_cache.clear
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def get_sync_response(params)
|
131
|
+
get_response(Config::TODOIST_SYNC_COMMAND, params)
|
132
|
+
end
|
133
|
+
|
98
134
|
end
|
99
135
|
end
|
100
136
|
end
|
@@ -4,11 +4,11 @@ module Todoist
|
|
4
4
|
module Util
|
5
5
|
class ParseHelper
|
6
6
|
|
7
|
-
def self.
|
7
|
+
def self.utc_offset_hours
|
8
8
|
return Time.now.utc_offset/60/60
|
9
9
|
end
|
10
10
|
|
11
|
-
def self.
|
11
|
+
def self.parse_todoist_date(item, key)
|
12
12
|
if item[key]
|
13
13
|
time = Time.parse(item[key])
|
14
14
|
return time.to_datetime
|
@@ -18,17 +18,17 @@ module Todoist
|
|
18
18
|
end
|
19
19
|
|
20
20
|
|
21
|
-
def self.
|
21
|
+
def self.filter_today(item, key)
|
22
22
|
|
23
23
|
now = DateTime.now
|
24
|
-
if
|
24
|
+
if parse_todoist_date(item, key) && parse_todoist_date(item, key) <= DateTime.new(now.year, now.month, now.day, -utc_offset_hours) + 1
|
25
25
|
return true
|
26
26
|
else
|
27
27
|
return false
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
def self.
|
31
|
+
def self.format_time(datetime)
|
32
32
|
datetime.strftime("%Y-%m-%dT%H:%M")
|
33
33
|
end
|
34
34
|
|