CSApi 0.0.4 → 0.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 37402553ba84503cfbe5ee0b2548551865a9b765
4
- data.tar.gz: 285cad08bf766155b0acefd66bdb3110a5b0e562
3
+ metadata.gz: 155eb0a39f7dbf9a5c75756e9f50ee73b0d1b42c
4
+ data.tar.gz: a3c35ded375372df4e48d1852dafbfca56b6e0e0
5
5
  SHA512:
6
- metadata.gz: 28a155a0b139bd9536ce28251928e04d059bea187e34e0a88bfc1a1c776b370e49b8b32b3020ef18dad4454a2040e250686a708796bac92ed5401160f52b72dc
7
- data.tar.gz: 6402789e62c0237709a7512a25c9d43bb642f7de80b415ad849f84dcad136c8eab5bb63bc16181eed7237058c73c4c62c43d4dd395061b8750e6f24323e4f325
6
+ metadata.gz: 83a322ecb1cc8cca2d497a530ceb95e2f1c6048eb22d2a278b9cce089c711562890da1953f19eef3874c26083c1eb8dceae4d9e675bfd33c09806276c4b7cfe2
7
+ data.tar.gz: 5ab3947319ac8b2ac0b5adc4568a4101bcf905a82a43e845685d1c65714406b87491ab36e9349555da8d0f96e2e3378cc77abd162dd272a2e9e434a270b036be
@@ -0,0 +1,2 @@
1
+ Peter Nosko - Search
2
+ Davit-gh - Messages
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in csapi.gemspec
4
4
  gemspec
5
+
6
+ gem 'nokogiri', '~>1.6.1'
7
+ gem 'httparty', '~>0.13.1'
8
+ gem 'json', '~>1.8.1'
File without changes
@@ -1,11 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
  #encoding: utf-8
3
3
 
4
- require './lib/CSApi.rb'
4
+ require_relative '../lib/CSApi.rb'
5
5
  require 'pp'
6
6
 
7
+ user = ENV['USER'] || 'user'
8
+ password = ENV['PASSWORD'] || 'password'
9
+
7
10
  begin
8
- api = CS::Api.new('username','password')
11
+ api = CS::Api.new(user,password)
9
12
  rescue CS::AuthError
10
13
  puts "Incorrect username or password"
11
14
  exit
@@ -16,7 +19,6 @@ end
16
19
  # ===
17
20
  profile = api.profile('205974')
18
21
 
19
-
20
22
  ## ===
21
23
  ## Get a user's photos, by default ours
22
24
  ## ===
@@ -41,17 +43,17 @@ requests = api.requests(limit)
41
43
  # ===
42
44
  # Get the current user's inbox messages
43
45
  # ===
44
- m = api.messages('inbox', 1)
45
- pp m.count
46
+ messages = api.messages('inbox', 1)
46
47
 
47
- m.each do |m|
48
- pp m
48
+ messages.each do |m|
49
+ puts m.to_h
49
50
  end
50
51
 
51
- if m.has_more?
52
- pp m.more.count
52
+
53
+ if messages.has_more?
54
+ p messages.more.count
53
55
  else
54
- pp "end of messages"
56
+ p "end of messages"
55
57
  end
56
58
 
57
59
 
@@ -61,25 +63,25 @@ end
61
63
  details = {
62
64
  subject: 'This is my request subject',
63
65
  number: 1, #How many people travel with you
64
- arrival: 1339543920, #a Unix Timestamp with your arrival date
65
- departure: 1339643920, #a Unix Timestamp with your departure date
66
+ arrival: Time.at(1339543920), #a Time instance
67
+ departure: Time.at(1339643920), #a Time instance
66
68
  arrival_flexible: true,
67
69
  departure_flexible: false,
68
70
  is_open_couchrequest: false,
69
71
  to: 12345, #a numeric user id, I guess, have yet to figure this out ,
70
72
  message: 'This is my request message' #I've yet to figure out how to do the multi-part requests
71
73
  }
72
- couch_request = CS::Request.new(details)
74
+ #couch_request = CS::Request.new(details)
73
75
 
74
76
  api.requests(1).each do |key, value|
75
77
  pp value
76
78
  end
77
- #
79
+
78
80
  ## ===
79
81
  ## Search for people in a city with various search constraints
80
82
  ## ===
81
83
  options = {
82
- location: 'venice',
84
+ location: 'mexico city',
83
85
  gender: 'female',
84
86
  :'has-photo' => false,
85
87
  :'member-type' => 'host' ,
@@ -89,7 +91,11 @@ options = {
89
91
  :'min-age' => nil,
90
92
  :'max-age' => nil,
91
93
  }
92
- results = api.search(options)
94
+ search = CS::CouchSearch.new(options)
95
+ results = search.execute
96
+
93
97
  results.each do |id, user|
94
98
  puts "Found (UID:#{id}) #{user[:name]} in #{user[:location]} with a couch status of #{user[:status]} and a photo #{user[:pic]}\n\n"
95
99
  end
100
+
101
+ puts results.more.count
@@ -10,18 +10,41 @@ Copyright (c) 2012 Partido Surrealista Mexicano
10
10
  require 'httparty'
11
11
  require 'json'
12
12
  require 'nokogiri'
13
+
14
+ require_relative 'csapi/version.rb'
15
+ require_relative 'csapi/errors.rb'
16
+ require_relative 'csapi/messages.rb'
17
+ require_relative 'csapi/request.rb'
18
+ require_relative 'csapi/search.rb'
19
+
13
20
  module CS
14
-
15
- class Api
21
+
22
+ @@instance = nil
23
+
24
+ def self.instance
25
+ @@instance
26
+ end
27
+
28
+ def self.instance= instance
29
+ @@instance = instance
30
+ end
31
+
32
+ class HTTP
16
33
  include HTTParty
17
34
  base_uri 'https://api.couchsurfing.org'
18
35
  headers "Content-Type" => 'application/json'
19
36
  follow_redirects false
20
- @uid = '0'
37
+ #debug_output $stderr
38
+ end
21
39
 
40
+ class Api
41
+
42
+ attr_accessor :uid;
43
+ @uid = '0'
44
+
22
45
  def initialize(username, password)
23
46
  @username = username
24
- r = self.class.post('/sessions', body:{username: username, password: password}.to_json)
47
+ r = HTTP.post('/sessions', body:{username: username, password: password}.to_json)
25
48
 
26
49
  raise CS::AuthError.new("Could not login") if r.code != 200
27
50
 
@@ -33,20 +56,17 @@ module CS
33
56
  @uid = data['url'].gsub(/[^\d]/, '')
34
57
  @profile = data.keep_if {|k,v| ['realname', 'username', 'profile_image', 'gender', 'address'].include?(k)}
35
58
  @profile['uid'] = @uid
36
- self.class.headers 'Cookie' => @cookies
37
- @@instance = self
59
+ HTTP.headers 'Cookie' => @cookies
60
+ CS.instance = self
38
61
  end
39
62
 
40
- def CS::instance
41
- @@instance
42
- end
43
63
 
44
64
  def requests(limit=10)
45
65
  url = "/users/#{@uid}/couchrequests"
46
66
  q = {
47
67
  limit: limit
48
68
  }
49
- r = self.class.get(url, query:q)
69
+ r = HTTP.get(url, query:q)
50
70
  requests = {}
51
71
  response = JSON.parse r.body
52
72
  response['object'].each do |req|
@@ -59,67 +79,57 @@ module CS
59
79
 
60
80
  def request(id)
61
81
  url = "/couchrequests/#{id}"
62
- r = self.class.get(url)
82
+ r = HTTP.get(url)
63
83
  JSON.parse r.body
64
84
  end
65
85
 
66
86
 
67
- def messages(type='inbox', limit=5, start=nil)
68
-
69
- types = ['inbox', 'sent'];
70
- throw ArgumentError.new("Can't fetch messages of kind #{type}") if !types.include? type;
71
-
72
- url = "/users/#{@uid}/messages"
73
- q = {
74
- type: type,
75
- limit: limit
76
- }
77
-
78
- if (start)
79
- q[:start] = start
80
- end
81
-
82
- r = self.class.get(url, query:q);
83
- object = JSON.parse r.body
84
-
85
- return CS::Messages.new(object, q, self);
87
+ def messages(*args)
88
+ CS::Messages.getMessages(*args)
86
89
  end
87
-
90
+
91
+
88
92
  def message(url)
89
- r = self.class.get(url)
93
+ r = HTTP.get(url)
90
94
  JSON.parse r.body
91
95
  end
92
96
 
97
+
93
98
  def userdata
94
99
  @profile
95
100
  end
96
101
 
102
+
97
103
  def profile(user=@uid)
98
104
  url = "/users/#{user}/profile"
99
- r = self.class.get(url)
105
+ r = HTTP.get(url)
100
106
  JSON.parse r.body
101
107
  end
102
108
 
109
+
103
110
  def photos(user=@uid)
104
111
  url = "/users/#{user}/photos"
105
- r = self.class.get(url)
112
+ r = HTTP.get(url)
106
113
  JSON.parse r.body
107
114
  end
108
115
 
116
+
109
117
  def friends(user=@uid)
110
118
  url = "/users/#{user}/friends"
111
- r = self.class.get(url)
119
+ r = HTTP.get(url)
112
120
  JSON.parse r.body
113
121
  end
114
122
 
123
+
115
124
  def references(user=@uid)
116
125
  url = "/users/#{user}/references"
117
- r = self.class.get(url)
126
+ r = HTTP.get(url)
118
127
  JSON.parse r.body
119
128
  end
120
129
 
130
+
121
131
  def search(options)
122
-
132
+
123
133
  defaults = {
124
134
  location: nil,
125
135
  gender: nil,
@@ -133,113 +143,7 @@ module CS
133
143
  :platform => 'android'
134
144
  }
135
145
 
136
- options = defaults.merge(options)
137
- html = self.class.get('/msearch', :query => options)
138
- doc = Nokogiri::HTML(html);
139
- users = {}
140
- statuses = {
141
- 'M' => 'maybe',
142
- 'T' => 'travelling',
143
- 'Y' => 'available',
144
- 'N' => 'unavailable'
145
- }
146
- doc.xpath('//article').each do |article|
147
- id = article.at_css('a').attr('href').split('/').last
148
- user = {
149
- name: article.children.at_css("h2").content,
150
- location: article.children.at_css("div.location").content,
151
- status: statuses[article['class'].match(/couch-([A-Z])/)[1]],
152
- pic: article.at_css('img').attr('src')
153
- }
154
- users[id] = user
155
- end
156
-
157
- users;
158
- end
159
- end
160
-
161
-
162
- class AuthError < StandardError
163
- end
164
-
165
- class APIError < StandardError
166
- end
167
-
168
- class Messages
169
-
170
- @after = nil
171
- @q = {}
172
- @data = []
173
-
174
- def initialize(object, q, ref)
175
146
 
176
- @after = object['after'] if object.include? 'after';
177
- @q = q;
178
- @ref = ref
179
- @data = object['object']
180
- end
181
-
182
- def count
183
- return @data.count
184
- end
185
-
186
- def has_more?
187
- return @after != nil
188
- end
189
-
190
- def each
191
- @data.each do |url|
192
- yield @ref.message(url)
193
- end
194
- end
195
-
196
- def more
197
- more = @ref.messages(@q[:type], @q[:limit], @after)
198
- @data = more['object']
199
- @after = more.include?('after') ? more['after'] : nil;
200
- return self
201
- end
202
-
203
- private
204
- @ref = nil
205
-
206
- end
207
-
208
-
209
- class Request
210
- @api = nil
211
-
212
- def initialize(options={})
213
- if options[:username] && options[:password]
214
- api = CS.new(options[:username], options[:password])
215
- options.del(:username)
216
- options.del(:password)
217
- else
218
- api = CS::instance
219
- if api==nil
220
- raise CS::APIError('You have not authenticated with the service or did not provide a :username and :password')
221
- end
222
- end
223
-
224
- #pp api.userdata
225
- options[:subject] = options[:subject] || "#{api.userdata['realname']} from #{api.userdata['address']['country']} sent you a new CouchRequest!"
226
- options[:number] = options[:number] || 1
227
- options[:arrival_flexible] = options[:arrival_flexible] || false
228
- options[:departure_flexible] = options[:departure_flexible] || false
229
- options[:is_open_couchrequest] = options[:is_open_couchrequest] || false
230
- options[:from] = api.userdata['uid']
231
- options[:to] = options[:to] || api.userdata['uid']
232
- options[:arrival] = Time.at(options[:arrival]).strftime("%FT%TZ") || (Time.now()+86400).strftime("%FT%TZ")
233
- options[:departure] = Time.at(options[:departure]).strftime("%FT%TZ") || (Time.now()+86400*3).strftime("%FT%TZ")
234
- options[:message] = options[:message] || "I'm to lazy to write a proper couch request. HOST ME PLZ?"
235
- #puts options.to_json
236
-
237
- url = "/couchrequests"
238
- api.post(url, body:options.to_json)
239
-
240
- #pp response.code
241
- #pp response.body
242
-
243
147
  end
244
148
  end
245
149
 
@@ -0,0 +1,9 @@
1
+ module CS
2
+
3
+ class AuthError < StandardError
4
+ end
5
+
6
+ class APIError < StandardError
7
+ end
8
+
9
+ end
@@ -0,0 +1,101 @@
1
+ module CS
2
+
3
+ class Messages
4
+ include Enumerable
5
+
6
+ attr_accessor :data, :after, :q
7
+
8
+ def self.getMessages(type='inbox', limit=5, start=nil)
9
+ types = ['inbox', 'sent'];
10
+ throw ArgumentError.new("Can't fetch messages of kind #{type}") unless types.include? type;
11
+
12
+ url = "/users/#{CS::instance.uid}/messages"
13
+ q = {
14
+ type: type,
15
+ limit: limit
16
+ }
17
+
18
+ if (start)
19
+ q[:start] = start
20
+ end
21
+
22
+ r = HTTP.get(url, query:q);
23
+ object = JSON.parse r.body
24
+
25
+ CS::Messages.new(object, q);
26
+ end
27
+
28
+
29
+ def initialize(object, q)
30
+ @after = object['after'] if object.include? 'after';
31
+ @q = q;
32
+ @data = object['object'].map {|u| Message.new(u) }
33
+ end
34
+
35
+
36
+ def method_missing meth, *args, &block
37
+ @data.send(meth.to_sym, *args, &block)
38
+ end
39
+
40
+
41
+ def has_more?
42
+ return @after != nil
43
+ end
44
+
45
+
46
+ def more limit=nil
47
+ Messages.getMessages(@q[:type], (limit || @q[:limit]), @after)
48
+ end
49
+
50
+ end
51
+
52
+ class Message
53
+
54
+ @url = nil
55
+ @fetched = false
56
+ @data = nil
57
+ @vars = [:title, :message, :date, :user_is_sender, :is_unread, :user, :url, :couch_request]
58
+ #attr_accessor *@vars
59
+
60
+ def initialize(url)
61
+ @url = url
62
+ @fetched = false
63
+ end
64
+
65
+ def fetch
66
+ req = HTTP.get(@url)
67
+ @data = JSON.parse req.body
68
+ end
69
+
70
+ def to_h
71
+ fetch unless @fetched
72
+ @data
73
+ end
74
+
75
+ def date
76
+ fetch unless @fetched
77
+ Date.parse(@date)
78
+ end
79
+
80
+ def unread?
81
+ fetch unless @fetched
82
+ @is_unread
83
+ end
84
+
85
+ def is_sender?
86
+ fetch unless @fetched
87
+ @user_is_sender
88
+ end
89
+
90
+ def method_missing meth
91
+ if vars.include? meth
92
+ fetch unless @fetched
93
+ @data[meth.to_s]
94
+ else
95
+ raise ArgumentError.new
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ end
@@ -0,0 +1,68 @@
1
+ module CS
2
+
3
+ class Request
4
+ @api = nil
5
+
6
+ attr_accessor :subject, :number, :arrival_flexible, :departure_flexible, :is_open_couchrequest, :from, :to, :arrival, :departure, :message
7
+
8
+ @options = [:subject, :number, :arrival_flexible, :departure_flexible, :is_open_couchrequest, :from, :to, :arrival, :departure, :message]
9
+
10
+ @defaults = {
11
+ subject: "A ruby programmer wants to surf your couch!",
12
+ number: 1,
13
+ arrival_flexible: false,
14
+ departure_flexible: false,
15
+ is_open_couchrequest: false,
16
+ from: nil,
17
+ to: nil,
18
+ arrival: Time.now,
19
+ departure: (Time.now+3600),
20
+ message: "I'm to lazy to write a proper couch request. HOST ME PLZ?"
21
+ }
22
+
23
+
24
+ def initialize options={}
25
+ options = @defaults.merge(options)
26
+ options.each do |k,v|
27
+ self.send "@#{k}=", val
28
+ end
29
+ end
30
+
31
+
32
+ def arrival= date
33
+ raise ArgumentError.new("This does not seem like a Time instance") unless date.is_a? Time
34
+ @arrival = date
35
+ end
36
+
37
+
38
+ def departure= date
39
+ raise ArgumentError.new("This does not seem like a Time instance") unless date.is_a? Time
40
+ @departure = date
41
+ end
42
+
43
+
44
+ def to_h
45
+ params = {}
46
+ @options.each {|key| params[key.to_sym] = instance_variable_get("@#{key}") }
47
+ params
48
+ end
49
+
50
+
51
+ def send!
52
+ raise CS::APIError('You have not authenticated with the service or did not provide a :username and :password') unless CS.instance
53
+
54
+ data = self.to_h
55
+ data[:arrival] = data[:arrival].strftime("%FT%TZ")
56
+ data[:departure] = data[:departure].strftime("%FT%TZ")
57
+
58
+ me = CS.instance.userdata['uid']
59
+ data[:from] ||= me
60
+ data[:to] ||= me
61
+
62
+ CS::HTTP.post('/couchrequests', body: data)
63
+
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,85 @@
1
+ module CS
2
+
3
+ class CouchSearch
4
+ attr_accessor :results, :options
5
+
6
+ @results = nil
7
+ @@instance = nil
8
+
9
+ def self.instance
10
+ @@instance
11
+ end
12
+
13
+ def initialize options={}
14
+ defaults = {
15
+ location: nil,
16
+ gender: nil,
17
+ :'has-photo' => nil,
18
+ :'member-type' => 'host' ,
19
+ vouched: nil,
20
+ verified: nil,
21
+ network: nil,
22
+ :'min-age' => nil,
23
+ :'max-age' => nil,
24
+ :platform => 'android'
25
+ }
26
+
27
+ @options = options.merge(defaults)
28
+ @@instance = self
29
+
30
+ end
31
+
32
+
33
+ def execute
34
+ html = HTTP.get('/msearch', :query => @options)
35
+ doc = Nokogiri::HTML(html);
36
+ users = {}
37
+ statuses = {
38
+ 'M' => 'maybe',
39
+ 'T' => 'travelling',
40
+ 'Y' => 'available',
41
+ 'N' => 'unavailable'
42
+ }
43
+ doc.xpath('//article').each do |article|
44
+ id = article.at_css('a').attr('href').split('/').last
45
+ user = {
46
+ string_id: article.attr('rel'),
47
+ name: article.children.at_css("h2").content,
48
+ location: article.children.at_css("div.location").content,
49
+ status: statuses[article['class'].match(/couch-([A-Z])/)[1]],
50
+ pic: article.at_css('img').attr('src')
51
+ }
52
+ users[id] = user
53
+ end
54
+
55
+ @results = CS::SearchResults.new(users)
56
+ @results
57
+ end
58
+
59
+
60
+ def more
61
+ @options[:page] = (@options[:page]||0)+1
62
+ @options[:exclude_ids] = results.collect {|k, u| u[:string_id]}
63
+ results
64
+ end
65
+
66
+ end
67
+
68
+ class SearchResults
69
+ include Enumerable
70
+ attr_accessor :data
71
+
72
+ def initialize(data)
73
+ @data = data
74
+ end
75
+
76
+ def method_missing meth, *args, &block
77
+ @data.send(meth.to_sym, *args, &block)
78
+ end
79
+
80
+ def more
81
+ CS::CouchSearch.instance.more
82
+ end
83
+
84
+ end
85
+ end
@@ -1,3 +1,3 @@
1
1
  module CS
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
data/readme.md CHANGED
@@ -1,7 +1,11 @@
1
1
  This is a simple CouchSurfing API
2
2
  =================================
3
3
 
4
- This used to be a PHP app, but I just changed my mind and went with Ruby instead. I'll probably make a PHP version soon
4
+ This a unofficial CS API client written in Ruby.
5
+
6
+ Using this client will likely result in a **violation of [Couchsurfing's TOS](https://www.couchsurfing.org/n/terms)**. I have contacted the [tech team](https://support.couchsurfing.org/hc/en-us/requests/new?category=support) as well as the [twitter](https://twitter.com/couchsurfing) folks to ask for permission of any sort to use the API, but I've yet to hear back from them.
7
+
8
+ The API was reverse-engineered, and if you'd like some pointers on that, please read [unRob/CouchSurfing-API#2](https://github.com/unRob/CouchSurfing-API/pull/2#issuecomment-16404056).
5
9
 
6
10
  ##Example
7
11
  See [example.rb](https://github.com/unRob/CouchSurfing-API/blob/master/examples/example.rb)
@@ -53,4 +57,4 @@ as the name is changed.
53
57
  DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
54
58
  TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
55
59
 
56
- 0. You just DO WHAT THE FUCK YOU WANT TO.
60
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: CSApi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roberto Hidalgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-04-16 00:00:00.000000000 Z
11
+ date: 2014-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -61,12 +61,17 @@ extensions: []
61
61
  extra_rdoc_files: []
62
62
  files:
63
63
  - .gitignore
64
+ - CONTRIBUTORS
64
65
  - Gemfile
65
- - LICENSE.txt
66
+ - LICENSE
66
67
  - Rakefile
67
68
  - csapi.gemspec
68
69
  - examples/example.rb
69
70
  - lib/csapi.rb
71
+ - lib/csapi/errors.rb
72
+ - lib/csapi/messages.rb
73
+ - lib/csapi/request.rb
74
+ - lib/csapi/search.rb
70
75
  - lib/csapi/version.rb
71
76
  - readme.md
72
77
  homepage: https://github.com/unRob/CouchSurfing-API
@@ -90,8 +95,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
95
  version: '0'
91
96
  requirements: []
92
97
  rubyforge_project:
93
- rubygems_version: 2.0.3
98
+ rubygems_version: 2.2.2
94
99
  signing_key:
95
100
  specification_version: 4
96
101
  summary: I have no idea what is this
97
102
  test_files: []
103
+ has_rdoc: false