CSApi 0.0.4 → 0.0.5

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