wunderlist-rb 0.0.1
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.
- data/COPYING +14 -0
- data/README.rdoc +27 -0
- data/lib/wunderlist.rb +27 -0
- data/lib/wunderlist/list.rb +65 -0
- data/lib/wunderlist/sync.rb +152 -0
- data/lib/wunderlist/task.rb +76 -0
- metadata +70 -0
data/COPYING
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Copyright (C) 2010-2011 by 6 Wunderkinder (6 Wunderkinder GmbH, Germany)
|
2
|
+
Copyright (C) 2011 by Fred Emmott (copyright@fredemmott.co.uk)
|
3
|
+
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
you may not use this file except in compliance with the License.
|
6
|
+
You may obtain a copy of the License at
|
7
|
+
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
See the License for the specific language governing permissions and
|
14
|
+
limitations under the License.
|
data/README.rdoc
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
= Overview
|
2
|
+
|
3
|
+
This is a *very* rough work in progress. 'Sync' is not yet accurate - it's
|
4
|
+
only reliable for a one-off fetch.
|
5
|
+
|
6
|
+
= Usage
|
7
|
+
|
8
|
+
cd lib
|
9
|
+
irb
|
10
|
+
ruby-1.9.2-p180 :001 > email = 'foo'
|
11
|
+
=> 'foo'
|
12
|
+
ruby-1.9.2-p180 :002 > password = 'bar'
|
13
|
+
=> "bar"
|
14
|
+
ruby-1.9.2-p180 :003 > $LOAD_PATH.push('.')
|
15
|
+
=> [...]
|
16
|
+
ruby-1.9.2-p180 :004 > require 'wunderlist'
|
17
|
+
=> true
|
18
|
+
ruby-1.9.2-p180 :005 > wl = Wunderlist::Sync.new(email, password)
|
19
|
+
[...]/wunderlist-rb/lib/wunderlist/sync.rb:20: Please set :app_name and :app_version to non-default values.
|
20
|
+
=> #<Wunderlist::Sync:0x00000000a69e60 [...] >
|
21
|
+
ruby-1.9.2-p180 :006 > wl.sync
|
22
|
+
=> nil
|
23
|
+
ruby-1.9.2-p180 :007 > wl.tasks.size
|
24
|
+
=> 13
|
25
|
+
ruby-1.9.2-p180 :008 > wl.lists.size
|
26
|
+
=> 3
|
27
|
+
ruby-1.9.2-p180 :009 >
|
data/lib/wunderlist.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Wunderlist
|
2
|
+
DEFAULTS = {
|
3
|
+
:sync_url => 'https://sync.wunderlist.net/1.2.0',
|
4
|
+
:app_name => 'wunderlist-rb',
|
5
|
+
:app_version => '0.0.1',
|
6
|
+
}
|
7
|
+
|
8
|
+
# *Not* returned as HTTP status codes.
|
9
|
+
#
|
10
|
+
# These are returned as a 'code' attribute in a JSON object returned by
|
11
|
+
# the server.
|
12
|
+
module StatusCodes
|
13
|
+
SUCCESS = 300
|
14
|
+
FAILURE = 301
|
15
|
+
DENIED = 302
|
16
|
+
NOT_EXIST = 303
|
17
|
+
end
|
18
|
+
|
19
|
+
class Error < RuntimeError; end
|
20
|
+
class FailureError < Wunderlist::Error; end
|
21
|
+
class DeniedError < Wunderlist::Error; end
|
22
|
+
class NotExistError < Wunderlist::Error; end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'wunderlist/list'
|
26
|
+
require 'wunderlist/task'
|
27
|
+
require 'wunderlist/sync'
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Wunderlist
|
2
|
+
class List
|
3
|
+
ATTRIBUTES = [
|
4
|
+
:deleted,
|
5
|
+
:inbox,
|
6
|
+
:name,
|
7
|
+
:online_id,
|
8
|
+
:position,
|
9
|
+
:shared,
|
10
|
+
:user_id,
|
11
|
+
:version,
|
12
|
+
]
|
13
|
+
attr_accessor *ATTRIBUTES
|
14
|
+
alias :deleted? :deleted
|
15
|
+
alias :inbox? :inbox
|
16
|
+
alias :shared? :shared
|
17
|
+
def online_id?
|
18
|
+
online_id && online_id != 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize data = {}
|
22
|
+
data.each do |k,v|
|
23
|
+
self.send("%s=" % k, v) if ATTRIBUTES.include? k
|
24
|
+
end
|
25
|
+
self.version ||= 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def hash
|
29
|
+
data = Hash.new
|
30
|
+
ATTRIBUTES.each do |key|
|
31
|
+
data[key] = self.send(key)
|
32
|
+
end
|
33
|
+
data
|
34
|
+
end
|
35
|
+
|
36
|
+
def sync_data
|
37
|
+
data = self.hash
|
38
|
+
web_data = Hash.new
|
39
|
+
data.each do |k,v|
|
40
|
+
case k
|
41
|
+
when :deleted, :inbox, :shared
|
42
|
+
web_data[k] = v ? 1 : 0
|
43
|
+
else
|
44
|
+
web_data[k] = v
|
45
|
+
end
|
46
|
+
end
|
47
|
+
web_data
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.from_sync_data web_data
|
51
|
+
data = Hash.new
|
52
|
+
web_data.each do |k,v|
|
53
|
+
case k.to_s
|
54
|
+
when 'deleted', 'inbox', 'shared'
|
55
|
+
data[k.to_sym] = v != '0'
|
56
|
+
when 'name'
|
57
|
+
data[:name] = v.to_s
|
58
|
+
else
|
59
|
+
data[k.to_sym] = v.to_i
|
60
|
+
end
|
61
|
+
end
|
62
|
+
Wunderlist::List.new(data)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'curb'
|
2
|
+
require 'json'
|
3
|
+
require 'openssl'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Wunderlist
|
7
|
+
# Strongly based on js/backend/wunderlist.sync.js
|
8
|
+
#
|
9
|
+
# This is where the 'steps' come from.
|
10
|
+
class Sync
|
11
|
+
# Debug only, may be removed without warning
|
12
|
+
attr_reader :step_1_response
|
13
|
+
attr_reader :user_id
|
14
|
+
|
15
|
+
def initialize email, password, options = {}
|
16
|
+
@options = Wunderlist::DEFAULTS.merge(options)
|
17
|
+
|
18
|
+
if @options[:app_name] == Wunderlist::DEFAULTS[:app_name]
|
19
|
+
STDERR.write "%s:%d: %s\n" % [
|
20
|
+
File.expand_path(__FILE__), __LINE__,
|
21
|
+
'Please set :app_name and :app_version to non-default values.'
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
@email = email
|
26
|
+
if options[:hashed_password]
|
27
|
+
@password_md5 = password
|
28
|
+
else
|
29
|
+
@password_md5 = OpenSSL::Digest.hexdigest('md5', password)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def sync
|
34
|
+
# Sends:
|
35
|
+
# - Tasks & lists: already synced, id & version
|
36
|
+
# - TODO: New lists
|
37
|
+
# Receives:
|
38
|
+
# - New and updated tasks
|
39
|
+
# - New and updated lists
|
40
|
+
# - TODO: delete_tasks
|
41
|
+
# - Current user id
|
42
|
+
run_step_1
|
43
|
+
# Process step 1 data
|
44
|
+
run_step_2
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def lists
|
49
|
+
@lists ||= Array.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def tasks
|
53
|
+
@tasks ||= Array.new
|
54
|
+
end
|
55
|
+
attr_writer :lists, :tasks
|
56
|
+
|
57
|
+
protected
|
58
|
+
def run_step_1
|
59
|
+
data = {
|
60
|
+
:step => 1,
|
61
|
+
:sync_table => {}
|
62
|
+
}
|
63
|
+
|
64
|
+
data[:sync_table][:lists] = lists.select do |list|
|
65
|
+
list.online_id?
|
66
|
+
end.map do |list|
|
67
|
+
{
|
68
|
+
:online_id => list.online_id,
|
69
|
+
:version => list.version,
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
[:lists, :tasks].each do |table|
|
74
|
+
data[:sync_table][table] = web_data_list(
|
75
|
+
self.send(table),
|
76
|
+
[:online_id, :version]
|
77
|
+
){|it| it.online_id?}
|
78
|
+
end
|
79
|
+
|
80
|
+
response = make_call(data)
|
81
|
+
@step_1_response = response
|
82
|
+
end
|
83
|
+
|
84
|
+
# Just process the data we received in step 1
|
85
|
+
def run_step_2
|
86
|
+
response = @step_1_response
|
87
|
+
@user_id = response['user_id'].to_i
|
88
|
+
# FIXME: sync_table (and subs) might not be defined
|
89
|
+
# FIXME: need to merge; this incldues updated lists + tasks too
|
90
|
+
response['sync_table']['new_lists'].each do |list|
|
91
|
+
lists.push Wunderlist::List.from_sync_data(list)
|
92
|
+
end
|
93
|
+
response['sync_table']['new_tasks'].each do |task|
|
94
|
+
tasks.push Wunderlist::Task.from_sync_data(task)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def make_call data
|
99
|
+
post_data = URI.encode_www_form(formify_keys({
|
100
|
+
:email => @email,
|
101
|
+
:password => @password_md5,
|
102
|
+
:device => @options[:app_name],
|
103
|
+
:version => @options[:app_version],
|
104
|
+
}.merge(data)))
|
105
|
+
|
106
|
+
response = JSON.parse(
|
107
|
+
Curl::Easy.http_post(
|
108
|
+
@options[:sync_url],
|
109
|
+
post_data
|
110
|
+
).body_str
|
111
|
+
)
|
112
|
+
case response['code'].to_i
|
113
|
+
when Wunderlist::StatusCodes::SUCCESS
|
114
|
+
response
|
115
|
+
when Wunderlist::StatusCodes::FAILURE
|
116
|
+
raise FailureError.new(response)
|
117
|
+
when Wunderlist::StatusCodes::DENIED
|
118
|
+
raise DeniedError.new(response)
|
119
|
+
when Wunderlist::StatusCodes::NOT_EXIST
|
120
|
+
raise NotExistError.new(response)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def web_data_list values, keys = nil, &test
|
126
|
+
values.select{|x| test.call(x)}.map do |x|
|
127
|
+
if keys
|
128
|
+
test.sync_data.select{|k,v| keys.include? k}
|
129
|
+
else
|
130
|
+
test.sync_data
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def formify_keys data_in, pattern = '%s'
|
136
|
+
data_out = Hash.new
|
137
|
+
data_in.each do |k,v|
|
138
|
+
unless v.is_a? Hash
|
139
|
+
data_out[pattern % k] = v
|
140
|
+
else
|
141
|
+
data_out.merge!(
|
142
|
+
formify_keys(
|
143
|
+
v,
|
144
|
+
"%s[%%s]" % [pattern % k]
|
145
|
+
)
|
146
|
+
)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
data_out
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Wunderlist
|
2
|
+
class Task
|
3
|
+
ATTRIBUTES = [
|
4
|
+
:date,
|
5
|
+
:deleted,
|
6
|
+
:done,
|
7
|
+
:done_date,
|
8
|
+
:important,
|
9
|
+
:list_id,
|
10
|
+
:name,
|
11
|
+
:note,
|
12
|
+
:online_id,
|
13
|
+
:position,
|
14
|
+
:push,
|
15
|
+
:push_ts,
|
16
|
+
:user_id,
|
17
|
+
:version,
|
18
|
+
]
|
19
|
+
attr_accessor *ATTRIBUTES
|
20
|
+
alias :deleted? :deleted
|
21
|
+
alias :done? :done
|
22
|
+
alias :important? :important
|
23
|
+
|
24
|
+
def online_id?
|
25
|
+
online_id && online_id != 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize data = {}
|
29
|
+
data.each do |k,v|
|
30
|
+
self.send("%s=" % k, v) if ATTRIBUTES.include? k
|
31
|
+
end
|
32
|
+
self.version ||= 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def hash
|
36
|
+
data = Hash.new
|
37
|
+
ATTRIBUTES.each do |key|
|
38
|
+
data[key] = self.send(key)
|
39
|
+
end
|
40
|
+
data
|
41
|
+
end
|
42
|
+
|
43
|
+
def sync_data
|
44
|
+
data = self.hash
|
45
|
+
web_data = Hash.new
|
46
|
+
data.each do |k,v|
|
47
|
+
case k
|
48
|
+
when :deleted, :done, :important, :shared
|
49
|
+
web_data[k] = v ? 1 : 0
|
50
|
+
when :done_date
|
51
|
+
web_data[k] = v ? v.to_i : 0
|
52
|
+
else
|
53
|
+
web_data[k] = v
|
54
|
+
end
|
55
|
+
end
|
56
|
+
web_data
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.from_sync_data web_data
|
60
|
+
data = Hash.new
|
61
|
+
web_data.each do |k,v|
|
62
|
+
case k.to_s
|
63
|
+
when 'deleted', 'done', 'important', 'shared'
|
64
|
+
data[k.to_sym] = v != '0'
|
65
|
+
when 'done_date'
|
66
|
+
data[k.to_sym] = v == '0' ? nil : Time.at(v.to_i)
|
67
|
+
when 'name', 'note'
|
68
|
+
data[k.to_sym] = v.to_s
|
69
|
+
else
|
70
|
+
data[k.to_sym] = v.to_i
|
71
|
+
end
|
72
|
+
end
|
73
|
+
Wunderlist::Task.new(data)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wunderlist-rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Fred Emmott
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-08-23 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: curb
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
description: I'll find something to put here
|
27
|
+
email:
|
28
|
+
- gems@fredemmott.co.uk
|
29
|
+
executables: []
|
30
|
+
|
31
|
+
extensions: []
|
32
|
+
|
33
|
+
extra_rdoc_files: []
|
34
|
+
|
35
|
+
files:
|
36
|
+
- COPYING
|
37
|
+
- README.rdoc
|
38
|
+
- lib/wunderlist.rb
|
39
|
+
- lib/wunderlist/sync.rb
|
40
|
+
- lib/wunderlist/task.rb
|
41
|
+
- lib/wunderlist/list.rb
|
42
|
+
homepage: https://github.com/fredemmott/wunderlist-rb
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.8.6
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: WIP gem for interacting with Wunderlist
|
69
|
+
test_files: []
|
70
|
+
|