wunderlist-rb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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.
@@ -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 >
@@ -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
+