stapeluberlauf 0.1.0
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 +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +11 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +50 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/last_unanswered_questions +30 -0
- data/lib/stapeluberlauf/authorizable.rb +39 -0
- data/lib/stapeluberlauf/error.rb +35 -0
- data/lib/stapeluberlauf/request/answer.rb +20 -0
- data/lib/stapeluberlauf/request/behavior.rb +13 -0
- data/lib/stapeluberlauf/request/behaviors/acceptable.rb +27 -0
- data/lib/stapeluberlauf/request/behaviors/commentable.rb +15 -0
- data/lib/stapeluberlauf/request/behaviors/downvotable.rb +27 -0
- data/lib/stapeluberlauf/request/behaviors/editable.rb +27 -0
- data/lib/stapeluberlauf/request/behaviors/favoritable.rb +27 -0
- data/lib/stapeluberlauf/request/behaviors/flagable.rb +27 -0
- data/lib/stapeluberlauf/request/behaviors/upvotable.rb +27 -0
- data/lib/stapeluberlauf/request/comment.rb +9 -0
- data/lib/stapeluberlauf/request/question.rb +124 -0
- data/lib/stapeluberlauf/request/site.rb +45 -0
- data/lib/stapeluberlauf/request.rb +300 -0
- data/lib/stapeluberlauf/response.rb +44 -0
- data/lib/stapeluberlauf/version.rb +3 -0
- data/lib/stapeluberlauf.rb +33 -0
- data/stapeluberlauf.gemspec +22 -0
- metadata +113 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 54382d58bda064e906f460acc85dcb69cd170a9e
|
4
|
+
data.tar.gz: 0536c70cda40779c6afad7d5c641ae31f0275513
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 56b7cf3260c7f15a00800e3f620a0bb3c3b588cedb813f3659d27ebd5450b6fad8650688e4140dc1f8e04d62649ce9cef84ba128146013a882107fee4f225a96
|
7
|
+
data.tar.gz: 809ea489f06cc2edf5e5d05461d3977e6464877012e55458c39cdc77a4cbdb1da2c646e792f37b4495f95a3ac2971bc23f819fd82a9f50681c8aa385bf76c4fd
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Marius Rackwitz
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
stapeluberlauf
|
2
|
+
==============
|
3
|
+
### /ˈʃtaːpl̩ ˈyːbɐˌlaʊ̯f/ _noun_, german word for stack overflow
|
4
|
+
|
5
|
+
[](https://travis-ci.org/mrackwitz/stapeluberlauf)
|
6
|
+
[](http://badge.fury.io/rb/stapeluberlauf)
|
7
|
+
[](https://codeclimate.com/github/mrackwitz/stapeluberlauf)
|
8
|
+
|
9
|
+
This gem provides a light-weight API wrapper to access the StackExchange API a little more conveniently.
|
10
|
+
|
11
|
+
It consists mainly out of simple DSL, which allows to describe requests and abstracts the implementation of basic
|
12
|
+
concepts like filters and paging.
|
13
|
+
|
14
|
+
It supports authenticated requests and is aware when a request requires authorization, so that it can fail preemptive
|
15
|
+
if this is not provided without a round-trip to the API.
|
16
|
+
|
17
|
+
It does provide a simple model for the response wrapper. But this covers just a primitive mapping of the JSON attributes
|
18
|
+
used for further communication negotiation. These include error indication, paging and rate limiting (quota).
|
19
|
+
|
20
|
+
It doesn't provide wrappers for the retrieved model classes yet. These have to be accessed through the envelope as raw
|
21
|
+
hashes.
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
This example fetches the first 10 unanswered questions tagged as `ruby`.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
stackoverflow = Stapeluberlauf.site('stackoverflow').use_filter('withbody')
|
29
|
+
questions = stackoverflow.questions(tagged: 'ruby').unanswered.execute
|
30
|
+
```
|
31
|
+
|
32
|
+
## Installation
|
33
|
+
|
34
|
+
Add this line to your application's Gemfile:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
gem 'stapeluberlauf'
|
38
|
+
```
|
39
|
+
|
40
|
+
And then execute:
|
41
|
+
|
42
|
+
$ bundle
|
43
|
+
|
44
|
+
Or install it yourself as:
|
45
|
+
|
46
|
+
$ gem install stapeluberlauf
|
47
|
+
|
48
|
+
## Disclaimer
|
49
|
+
|
50
|
+
This is not an official gem by StackExchange / StackOverflow.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "stackexchange"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'stapeluberlauf'
|
5
|
+
|
6
|
+
if ARGV.count != 1
|
7
|
+
puts "Usage: #{File.basename($0)} tagged"
|
8
|
+
exit 1
|
9
|
+
end
|
10
|
+
tagged = ARGV.shift
|
11
|
+
|
12
|
+
stackoverflow = Stapeluberlauf.site('stackoverflow').use_filter('withbody')
|
13
|
+
questions = stackoverflow.questions(tagged: tagged).unanswered
|
14
|
+
questions.page_size = 10
|
15
|
+
|
16
|
+
result = questions.execute
|
17
|
+
result.items.each do |question|
|
18
|
+
puts "❓ #{question['title']}"
|
19
|
+
puts "🔗 #{question['link']}"
|
20
|
+
puts question['body']
|
21
|
+
puts "-- @#{question['owner']['display_name']}"
|
22
|
+
comments = stackoverflow.question(question['question_id']).comments.execute.items
|
23
|
+
next if comments.nil?
|
24
|
+
puts '-' * 10
|
25
|
+
comments.each do |comment|
|
26
|
+
puts " #{comment['body']}"
|
27
|
+
puts " -- @#{comment['owner']['display_name']}"
|
28
|
+
end
|
29
|
+
puts '*' * 80
|
30
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Stapeluberlauf
|
2
|
+
module Authorizable
|
3
|
+
# @return [String]
|
4
|
+
# The secret key of the OAuth app which allows to authorize to increased throttle
|
5
|
+
# quota and privileged access to user data.
|
6
|
+
# Must be set in combination with an #acess_token.
|
7
|
+
# If you don't have a request key you can obtain one by [registering your
|
8
|
+
# application on Stack Apps](http://stackapps.com/apps/oauth/register).
|
9
|
+
attr_accessor :key
|
10
|
+
|
11
|
+
# @return [String]
|
12
|
+
# The OAuth access token which allows to authorize to increased throttle
|
13
|
+
# quota and privileged access to user data.
|
14
|
+
# Must be set in combination with a #key.
|
15
|
+
#
|
16
|
+
attr_accessor :access_token
|
17
|
+
|
18
|
+
# Authorize a request with an access token and key.
|
19
|
+
#
|
20
|
+
# @param [String] key @see #key
|
21
|
+
#
|
22
|
+
# @param [String] access_token @see #access_token
|
23
|
+
#
|
24
|
+
# @return [Self] returns the receiver to support a floating API
|
25
|
+
#
|
26
|
+
def authorize(key, access_token)
|
27
|
+
self.key = key
|
28
|
+
self.access_token = access_token
|
29
|
+
return self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns whether all credentials are sufficiently set
|
33
|
+
# to execute authorized requests.
|
34
|
+
#
|
35
|
+
def authorized?
|
36
|
+
!key.nil? && !access_token.nil?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Stapeluberlauf
|
2
|
+
class Error < StandardError
|
3
|
+
# @return [Request] the request which resulted in the error
|
4
|
+
attr_accessor :request
|
5
|
+
|
6
|
+
# @return [Response] the erroneous response
|
7
|
+
attr_accessor :response
|
8
|
+
|
9
|
+
# Initialize a new error from a request and its response
|
10
|
+
#
|
11
|
+
# @param [Request] request @see #request
|
12
|
+
#
|
13
|
+
# @param [Response] response @see #response
|
14
|
+
#
|
15
|
+
def initialize(request, response)
|
16
|
+
super(response.error_message)
|
17
|
+
self.request = request
|
18
|
+
self.response = response
|
19
|
+
end
|
20
|
+
|
21
|
+
# Refers to an error type returned by the API
|
22
|
+
#
|
23
|
+
# @return [Int]
|
24
|
+
def id
|
25
|
+
response.error_id
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the name of the error
|
29
|
+
#
|
30
|
+
# @return [String]
|
31
|
+
def name
|
32
|
+
response.error_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Stapeluberlauf
|
2
|
+
class Request
|
3
|
+
class Answer < Request
|
4
|
+
include Behavior::Acceptable
|
5
|
+
include Behavior::Commentable
|
6
|
+
include Behavior::Editable
|
7
|
+
include Behavior::Flagable
|
8
|
+
include Behavior::Upvotable
|
9
|
+
include Behavior::Downvotable
|
10
|
+
|
11
|
+
# Gets all questions the identified answers are on.
|
12
|
+
#
|
13
|
+
# @return [Request]
|
14
|
+
#
|
15
|
+
def questions
|
16
|
+
request('questions')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Stapeluberlauf
|
2
|
+
class Request
|
3
|
+
module Behavior
|
4
|
+
autoload :Acceptable, 'stapeluberlauf/request/behaviors/acceptable.rb'
|
5
|
+
autoload :Commentable, 'stapeluberlauf/request/behaviors/commentable.rb'
|
6
|
+
autoload :Downvotable, 'stapeluberlauf/request/behaviors/downvotable.rb'
|
7
|
+
autoload :Editable, 'stapeluberlauf/request/behaviors/editable.rb'
|
8
|
+
autoload :Favoritable, 'stapeluberlauf/request/behaviors/favoritable.rb'
|
9
|
+
autoload :Flagable, 'stapeluberlauf/request/behaviors/flagable.rb'
|
10
|
+
autoload :Upvotable, 'stapeluberlauf/request/behaviors/upvotable.rb'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Stapeluberlauf
|
2
|
+
class Request
|
3
|
+
module Behavior
|
4
|
+
module Acceptable
|
5
|
+
# Casts an accept vote.
|
6
|
+
#
|
7
|
+
# @note auth required
|
8
|
+
#
|
9
|
+
# @return [Request]
|
10
|
+
#
|
11
|
+
def accept
|
12
|
+
request('accept').auth_required!
|
13
|
+
end
|
14
|
+
|
15
|
+
# Undoes an accept vote.
|
16
|
+
#
|
17
|
+
# @note auth required
|
18
|
+
#
|
19
|
+
# @return [Request]
|
20
|
+
#
|
21
|
+
def undo_accept
|
22
|
+
request('accept/undo').auth_required!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Stapeluberlauf
|
2
|
+
class Request
|
3
|
+
module Behavior
|
4
|
+
module Downvotable
|
5
|
+
# Casts a downvote on the given resource.
|
6
|
+
#
|
7
|
+
# @note auth required
|
8
|
+
#
|
9
|
+
# @return [Request]
|
10
|
+
#
|
11
|
+
def downvote
|
12
|
+
request('downvote').auth_required!
|
13
|
+
end
|
14
|
+
|
15
|
+
# Undoes a downvote on the given resource
|
16
|
+
#
|
17
|
+
# @note auth required
|
18
|
+
#
|
19
|
+
# @return [Request]
|
20
|
+
#
|
21
|
+
def undo_downvote
|
22
|
+
request('downvote/undo').auth_required!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Stapeluberlauf
|
2
|
+
class Request
|
3
|
+
module Behavior
|
4
|
+
module Editable
|
5
|
+
# Edits the given resource.
|
6
|
+
#
|
7
|
+
# @note auth required
|
8
|
+
#
|
9
|
+
# @return [Request]
|
10
|
+
#
|
11
|
+
def edit
|
12
|
+
request('edit').auth_required!
|
13
|
+
end
|
14
|
+
|
15
|
+
# Deletes the given resource.
|
16
|
+
#
|
17
|
+
# @note auth required
|
18
|
+
#
|
19
|
+
# @return [Request]
|
20
|
+
#
|
21
|
+
def delete
|
22
|
+
request('delete').auth_required!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Stapeluberlauf
|
2
|
+
class Request
|
3
|
+
module Behavior
|
4
|
+
module Favoritable
|
5
|
+
# Favorites the given resource.
|
6
|
+
#
|
7
|
+
# @note auth required
|
8
|
+
#
|
9
|
+
# @return [Request]
|
10
|
+
#
|
11
|
+
def favorite
|
12
|
+
request('favorite').auth_required!
|
13
|
+
end
|
14
|
+
|
15
|
+
# Undoes favoriting the given resouce.
|
16
|
+
#
|
17
|
+
# @note auth required
|
18
|
+
#
|
19
|
+
# @return [Request]
|
20
|
+
#
|
21
|
+
def undo_favorite
|
22
|
+
request('favorite/undo').auth_required!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Stapeluberlauf
|
2
|
+
class Request
|
3
|
+
module Behavior
|
4
|
+
module Flagable
|
5
|
+
# Casts a flag on the given resource.
|
6
|
+
#
|
7
|
+
# @note auth required
|
8
|
+
#
|
9
|
+
# @return [Request]
|
10
|
+
#
|
11
|
+
def add_flag
|
12
|
+
request('flags/add').auth_required!
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns valid flag options for the given resource.
|
16
|
+
#
|
17
|
+
# @note auth required
|
18
|
+
#
|
19
|
+
# @return [Request]
|
20
|
+
#
|
21
|
+
def flag_options
|
22
|
+
request('flags/options').auth_required!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Stapeluberlauf
|
2
|
+
class Request
|
3
|
+
module Behavior
|
4
|
+
module Upvotable
|
5
|
+
# Casts an upvote on the given resource.
|
6
|
+
#
|
7
|
+
# @note auth required
|
8
|
+
#
|
9
|
+
# @return [Request]
|
10
|
+
#
|
11
|
+
def upvote
|
12
|
+
request('upvote').auth_required!
|
13
|
+
end
|
14
|
+
|
15
|
+
# Undoes an upvote on the given resource
|
16
|
+
#
|
17
|
+
# @note auth required
|
18
|
+
#
|
19
|
+
# @return [Request]
|
20
|
+
#
|
21
|
+
def undo_upvote
|
22
|
+
request('upvote/undo').auth_required!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Stapeluberlauf
|
2
|
+
class Request
|
3
|
+
class Question < Site
|
4
|
+
include Behavior::Commentable
|
5
|
+
include Behavior::Editable
|
6
|
+
include Behavior::Favoritable
|
7
|
+
include Behavior::Flagable
|
8
|
+
include Behavior::Upvotable
|
9
|
+
include Behavior::Downvotable
|
10
|
+
|
11
|
+
# Renders a hypothetical question.
|
12
|
+
#
|
13
|
+
# @note auth required
|
14
|
+
#
|
15
|
+
# @return [Request]
|
16
|
+
#
|
17
|
+
def render
|
18
|
+
request('render').auth_required!
|
19
|
+
end
|
20
|
+
|
21
|
+
# Creates a new question.
|
22
|
+
#
|
23
|
+
# @note auth required
|
24
|
+
#
|
25
|
+
# @return [Request]
|
26
|
+
#
|
27
|
+
def add
|
28
|
+
request('add').auth_required!
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the answers to the questions
|
32
|
+
#
|
33
|
+
# @return [Request]
|
34
|
+
#
|
35
|
+
def answers
|
36
|
+
request('answers')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Creates an answer on the given question. auth required
|
40
|
+
#
|
41
|
+
# @return [Request]
|
42
|
+
#
|
43
|
+
def add_answer
|
44
|
+
request('answers/add')
|
45
|
+
end
|
46
|
+
|
47
|
+
# Renders a hypothetical answer to a question.
|
48
|
+
#
|
49
|
+
# @return [Request]
|
50
|
+
#
|
51
|
+
def render_answer
|
52
|
+
request('answers/render')
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns valid flag options which are also close reasons for the given question.
|
56
|
+
#
|
57
|
+
# @note auth required
|
58
|
+
#
|
59
|
+
# @return [Request]
|
60
|
+
#
|
61
|
+
def close_options
|
62
|
+
request('close/options').auth_required!
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get the questions that link to the questions.
|
66
|
+
#
|
67
|
+
# @return [Request]
|
68
|
+
#
|
69
|
+
def linked
|
70
|
+
request('linked')
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get the questions that are related to the questions.
|
74
|
+
#
|
75
|
+
# @return [Request]
|
76
|
+
#
|
77
|
+
def related
|
78
|
+
request('related')
|
79
|
+
end
|
80
|
+
|
81
|
+
# Get the timelines of the questions.
|
82
|
+
#
|
83
|
+
# @return [Request]
|
84
|
+
#
|
85
|
+
def timeline
|
86
|
+
request('timeline')
|
87
|
+
end
|
88
|
+
|
89
|
+
# Get all questions on the site with active bounties.
|
90
|
+
#
|
91
|
+
# @return [Request]
|
92
|
+
#
|
93
|
+
def featured
|
94
|
+
request('featured')
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get all questions on the site with *no* answers.
|
98
|
+
#
|
99
|
+
# @return [Request]
|
100
|
+
#
|
101
|
+
def no_answers
|
102
|
+
request('no-answers')
|
103
|
+
end
|
104
|
+
|
105
|
+
# Get all questions the site considers unanswered.
|
106
|
+
#
|
107
|
+
# @return [Request]
|
108
|
+
#
|
109
|
+
def unanswered
|
110
|
+
request('unanswered')
|
111
|
+
end
|
112
|
+
|
113
|
+
# Get questions the site considers unanswered within a user's favorite or interesting tags.
|
114
|
+
#
|
115
|
+
# @note auth required
|
116
|
+
#
|
117
|
+
# @return [Request]
|
118
|
+
#
|
119
|
+
def unanswered_with_my_tags
|
120
|
+
request('unanswered/my-tags').auth_required!
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'stapeluberlauf/authorizable.rb'
|
2
|
+
require 'stapeluberlauf/request.rb'
|
3
|
+
|
4
|
+
module Stapeluberlauf
|
5
|
+
class Request
|
6
|
+
class Site < Request
|
7
|
+
# Gets questions on the site.
|
8
|
+
#
|
9
|
+
# @param [String|Array] ids one or multiple ids
|
10
|
+
#
|
11
|
+
# @param [Hash] params the parameter
|
12
|
+
#
|
13
|
+
# @option params [String] tagged one or multiple tags
|
14
|
+
#
|
15
|
+
# @return [Request]
|
16
|
+
#
|
17
|
+
def questions(ids=nil, **params)
|
18
|
+
resource_request(Question, ids, 'questions', params)
|
19
|
+
end
|
20
|
+
alias_method :question, :questions
|
21
|
+
|
22
|
+
# Gets answers on the site.
|
23
|
+
#
|
24
|
+
# @param [String|Array] ids one or multiple ids
|
25
|
+
#
|
26
|
+
# @return [Request]
|
27
|
+
#
|
28
|
+
def answers(ids=nil, **params)
|
29
|
+
resource_request(Answer, ids, 'answers', params)
|
30
|
+
end
|
31
|
+
alias_method :answer, :answers
|
32
|
+
|
33
|
+
# Gets comments on the site.
|
34
|
+
#
|
35
|
+
# @param [String|Array] ids one or multiple ids
|
36
|
+
#
|
37
|
+
# @return [Request]
|
38
|
+
#
|
39
|
+
def comments(ids=nil, **params)
|
40
|
+
resource_request(Comment, ids, 'comments', params)
|
41
|
+
end
|
42
|
+
alias_method :comment, :comments
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
require 'stapeluberlauf/authorizable.rb'
|
5
|
+
|
6
|
+
module Stapeluberlauf
|
7
|
+
class Request
|
8
|
+
autoload :Answer, 'stapeluberlauf/request/answer'
|
9
|
+
autoload :Behavior, 'stapeluberlauf/request/behavior'
|
10
|
+
autoload :Comment, 'stapeluberlauf/request/comment'
|
11
|
+
autoload :Question, 'stapeluberlauf/request/question'
|
12
|
+
autoload :Site, 'stapeluberlauf/request/site'
|
13
|
+
|
14
|
+
include Stapeluberlauf::Authorizable
|
15
|
+
|
16
|
+
# @return [Int] the maxium number of items on a page
|
17
|
+
MAX_PAGE_SIZE = 100.freeze
|
18
|
+
|
19
|
+
# @return [Net::HTTP] a HTTP client
|
20
|
+
attr_accessor :client
|
21
|
+
|
22
|
+
# @return [String] the path of the endpoint
|
23
|
+
attr_accessor :endpoint
|
24
|
+
|
25
|
+
# @return [Hash<Symbol, String>] the parameter
|
26
|
+
attr_accessor :params
|
27
|
+
|
28
|
+
# @return [Site] the site
|
29
|
+
attr_accessor :site
|
30
|
+
|
31
|
+
# @return [Int] the specific page to fetch, starts at and defaults to 1
|
32
|
+
attr_accessor :page
|
33
|
+
|
34
|
+
# @return [Int] any value between 0 and 100, defaults to 30 on API-site,
|
35
|
+
# initialized by default to 100
|
36
|
+
attr_accessor :page_size
|
37
|
+
|
38
|
+
# Initialize a new instance
|
39
|
+
#
|
40
|
+
# @param [Net::HTTP] client @see #client
|
41
|
+
#
|
42
|
+
# @param [Site] site @see #site
|
43
|
+
#
|
44
|
+
# @param [String] endpoint @see #endpoint
|
45
|
+
#
|
46
|
+
# @param [Hash<Symbol, String>] params @see #params
|
47
|
+
#
|
48
|
+
def initialize(client=nil, site=nil, endpoint='', params={})
|
49
|
+
@client = client
|
50
|
+
@site = site
|
51
|
+
@endpoint = endpoint
|
52
|
+
@params = params
|
53
|
+
@page = 1
|
54
|
+
@page_size = MAX_PAGE_SIZE
|
55
|
+
end
|
56
|
+
|
57
|
+
# Create a new request based on another instance.
|
58
|
+
#
|
59
|
+
# @param [Request] request
|
60
|
+
# takes over all values from the given request
|
61
|
+
#
|
62
|
+
def self.based_on(base_request)
|
63
|
+
new.tap do |instance|
|
64
|
+
base_request.instance_variables.each do |var|
|
65
|
+
value = base_request.instance_variable_get(var)
|
66
|
+
method_sym = "#{var.to_s[1..-1]}="
|
67
|
+
instance.send(method_sym, value.is_a?(Fixnum) ? value : value.dup)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Request for items filtered by their creation time.
|
73
|
+
#
|
74
|
+
# @param [#to_time] after
|
75
|
+
# Define a lower limit for the range of `creation_date`.
|
76
|
+
#
|
77
|
+
# @param [#to_time] before
|
78
|
+
# Define an upper limit for the range of `creation_date`.
|
79
|
+
#
|
80
|
+
# @return [Request] returns the receiver to support a floating API
|
81
|
+
#
|
82
|
+
def created(after: nil, before: nil)
|
83
|
+
@params.merge!({
|
84
|
+
fromdate: from != nil ? before.to_time.to_i : nil,
|
85
|
+
todate: to != nil ? after.to_time.to_i : nil,
|
86
|
+
})
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
# Request for sorted results by a sort criteria.
|
91
|
+
#
|
92
|
+
# Calling this method multiple times on the same request instance
|
93
|
+
# will not add a further level of sorting, but override the previous
|
94
|
+
# defined sort order.
|
95
|
+
#
|
96
|
+
# @param [Symbol] sort_by the sort order
|
97
|
+
# Valid for posts (questions, answers) and comments:
|
98
|
+
# - :creation: creation_date
|
99
|
+
# - :votes: score
|
100
|
+
#
|
101
|
+
# Valid for posts (questions, answers):
|
102
|
+
# - :activity: last_activity_date
|
103
|
+
#
|
104
|
+
# Valid for questions (without prefilter as e.g. featured, unanswered, etc.):
|
105
|
+
# - :hot: by the formula ordering the hot tab
|
106
|
+
# - :week: by the formula ordering the week tab
|
107
|
+
# - :month: by the formula ordering the month tab
|
108
|
+
#
|
109
|
+
# Valid for badges:
|
110
|
+
# - :rank: by rank (default)
|
111
|
+
# - :name: by name
|
112
|
+
# - :type: by type
|
113
|
+
#
|
114
|
+
# @param [String, nil] min
|
115
|
+
# Define a lower limit for the range of the sort criteria.
|
116
|
+
#
|
117
|
+
# @param [String, nil] max
|
118
|
+
# Define an upper limit for the range of the sort criteria.
|
119
|
+
#
|
120
|
+
# @return [Request] returns the receiver to support a floating API
|
121
|
+
#
|
122
|
+
def sort_by(sort_by, min: nil, max: nil)
|
123
|
+
@params.merge!({
|
124
|
+
sort: sort_by.to_s,
|
125
|
+
min: min,
|
126
|
+
max: max,
|
127
|
+
})
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
# Select a filter, which enables certain includes and excludes so that the specific
|
132
|
+
# subset of data that is necessary for the application use are returned at once.
|
133
|
+
#
|
134
|
+
# @param [String] identifier
|
135
|
+
# This might be either the id of a filter retrieved after creating it
|
136
|
+
# or one of the pre-defined filters.
|
137
|
+
# - `default` returns the documented default fields
|
138
|
+
# - `withbody` returns the default plus the *.body fields
|
139
|
+
# - `none` is empty
|
140
|
+
# - `total` includes just .total
|
141
|
+
#
|
142
|
+
# @return [Request] returns the receiver to support a floating API
|
143
|
+
#
|
144
|
+
def use_filter(identifier)
|
145
|
+
@params.merge!({
|
146
|
+
filter: identifier
|
147
|
+
})
|
148
|
+
self
|
149
|
+
end
|
150
|
+
|
151
|
+
# Execute the request.
|
152
|
+
#
|
153
|
+
# @return [Result]
|
154
|
+
#
|
155
|
+
def execute
|
156
|
+
if is_auth_required? && !authorized?
|
157
|
+
raise "Request to #{self.endpoint} is not authorized, but authentication is required!"
|
158
|
+
end
|
159
|
+
http_res = client.get(absolute_uri)
|
160
|
+
json = JSON.parse(http_res.body)
|
161
|
+
res = Response.new
|
162
|
+
json.map do |key, value|
|
163
|
+
res.send("#{key}=", value)
|
164
|
+
end
|
165
|
+
unless res.error_name.nil?
|
166
|
+
raise Stapeluberlauf::Error.new(self, res)
|
167
|
+
end
|
168
|
+
res
|
169
|
+
end
|
170
|
+
|
171
|
+
# Allows to paginate
|
172
|
+
#
|
173
|
+
# @return [Enumerator]
|
174
|
+
#
|
175
|
+
def pages
|
176
|
+
Enumerator.new do |yielder|
|
177
|
+
response = self.execute
|
178
|
+
yielder << response
|
179
|
+
page_request = self.dup
|
180
|
+
while response.has_more
|
181
|
+
page_request.page += 1
|
182
|
+
response = page_request.execute
|
183
|
+
yielder << response
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Returns all items
|
189
|
+
#
|
190
|
+
# @return [Array<Hash>]
|
191
|
+
#
|
192
|
+
def all_items
|
193
|
+
pages.flat_map(&:items)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Checks whether authentication is required.
|
197
|
+
#
|
198
|
+
# @return [Bool]
|
199
|
+
#
|
200
|
+
def is_auth_required?
|
201
|
+
@auth_required
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns the composed request URI with encoded parameters
|
205
|
+
#
|
206
|
+
# @return [URI]
|
207
|
+
#
|
208
|
+
def absolute_uri
|
209
|
+
URI.join(BASE_URI, relative_uri)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Returns the composed request URI with encoded parameters
|
213
|
+
#
|
214
|
+
# @return [URI]
|
215
|
+
#
|
216
|
+
def relative_uri
|
217
|
+
uri = URI(endpoint)
|
218
|
+
uri.query = URI.encode_www_form(merged_params)
|
219
|
+
uri
|
220
|
+
end
|
221
|
+
|
222
|
+
protected
|
223
|
+
|
224
|
+
# Define that authentication is required for this endpoint.
|
225
|
+
#
|
226
|
+
# @return [Result]
|
227
|
+
#
|
228
|
+
def auth_required!
|
229
|
+
@auth_required = true
|
230
|
+
self
|
231
|
+
end
|
232
|
+
|
233
|
+
# Create a new request based on the current instance.
|
234
|
+
#
|
235
|
+
# @param [String] endpoint @see #endpoint
|
236
|
+
#
|
237
|
+
# @param [Hash<Symbol, String>] params @see #params
|
238
|
+
#
|
239
|
+
# @return [Request]
|
240
|
+
#
|
241
|
+
def request(endpoint, params={})
|
242
|
+
Request.based_on(self).tap do |instance|
|
243
|
+
instance.endpoint += "/#{endpoint}"
|
244
|
+
instance.params.merge! params
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Create a new request for a collection based on the current instance.
|
249
|
+
#
|
250
|
+
# @param [Class] request_class the class of the request, if {ids} is not nil
|
251
|
+
#
|
252
|
+
# @param [String|Array] ids one or multiple ids
|
253
|
+
#
|
254
|
+
# @param [String] endpoint @see #endpoint
|
255
|
+
#
|
256
|
+
# @param [Hash<Symbol, String>] params @see #params
|
257
|
+
#
|
258
|
+
# @return [Request]
|
259
|
+
#
|
260
|
+
def resource_request(request_class, ids, endpoint, params={})
|
261
|
+
request_class ||= self.class
|
262
|
+
request_class.based_on(self).tap do |instance|
|
263
|
+
instance.endpoint += "/#{endpoint}"
|
264
|
+
instance.params.merge! params
|
265
|
+
unless ids.nil?
|
266
|
+
instance.endpoint += "/#{process_ids(ids)}"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Serialize a list of ids if necessary
|
272
|
+
#
|
273
|
+
# @param [String|Array] ids the ids of one or multiple resources
|
274
|
+
#
|
275
|
+
# @return [String]
|
276
|
+
#
|
277
|
+
def process_ids(ids)
|
278
|
+
if ids.is_a? Array
|
279
|
+
raise "Not more than 100 ids are allowed!" if ids.length > 100
|
280
|
+
ids.join(';')
|
281
|
+
else
|
282
|
+
ids
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns the merged parameter from different attributes
|
287
|
+
#
|
288
|
+
# @return [Hash<Symbol, String>]
|
289
|
+
#
|
290
|
+
def merged_params
|
291
|
+
params.merge({
|
292
|
+
site: site,
|
293
|
+
page: page != 1 ? page : nil,
|
294
|
+
pagesize: page_size != MAX_PAGE_SIZE ? page_size : nil,
|
295
|
+
key: key,
|
296
|
+
access_token: access_token,
|
297
|
+
}).reject { |_,v| v.nil? }
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Stapeluberlauf
|
2
|
+
class Response
|
3
|
+
# @return [Int, nil] is only set when the API detects the request took an unusually long time to run.
|
4
|
+
# When it is set an application must wait that number of seconds before calling that method again.
|
5
|
+
attr_accessor :backoff
|
6
|
+
|
7
|
+
# @return [Int, nil] refers to an error type returned by the API
|
8
|
+
attr_accessor :error_id
|
9
|
+
|
10
|
+
# @return [String, nil] message of the error referred by #error_id
|
11
|
+
attr_accessor :error_message
|
12
|
+
|
13
|
+
# @return [String, nil] name of the error referred by #error_name
|
14
|
+
attr_accessor :error_name
|
15
|
+
|
16
|
+
# @return [Boolean] whether there are more pages
|
17
|
+
attr_accessor :has_more
|
18
|
+
|
19
|
+
# @return [Array] an array of the type found in #type
|
20
|
+
attr_accessor :items
|
21
|
+
|
22
|
+
# @return [Int, nil] the requested page
|
23
|
+
# @note Not included in the `default` filter
|
24
|
+
attr_accessor :page
|
25
|
+
|
26
|
+
# @return [Int, nil] the size of the requested page
|
27
|
+
# @note Not included in the `default` filter
|
28
|
+
attr_accessor :page_size
|
29
|
+
|
30
|
+
# @return [Int] the maximum allowed quota bound to the credentials, if the requested was authorized
|
31
|
+
attr_accessor :quota_max
|
32
|
+
|
33
|
+
# @return [Int] the remaining quota bound to the credentials, if the requested was authorized
|
34
|
+
attr_accessor :quota_remaining
|
35
|
+
|
36
|
+
# @return [Int, nil] the total number of items
|
37
|
+
# @note Not included in the `default` filter
|
38
|
+
attr_accessor :total
|
39
|
+
|
40
|
+
# @return [String] the name of the entities returned in #items
|
41
|
+
# @note Not included in the `default` filter
|
42
|
+
attr_accessor :type
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'faraday'
|
3
|
+
|
4
|
+
module Stapeluberlauf
|
5
|
+
require 'stapeluberlauf/version'
|
6
|
+
autoload :Error, 'stapeluberlauf/error'
|
7
|
+
autoload :Request, 'stapeluberlauf/request'
|
8
|
+
autoload :Response, 'stapeluberlauf/response'
|
9
|
+
|
10
|
+
BASE_URI = URI.parse('https://api.stackexchange.com/2.2').freeze
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# @return [Net::HTTP|Faraday]
|
14
|
+
#
|
15
|
+
attr_accessor :default_client
|
16
|
+
|
17
|
+
def default_client
|
18
|
+
@default_client ||= Faraday.new(url: BASE_URI.dup) do |builder|
|
19
|
+
builder.adapter Faraday.default_adapter
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get access to a specific site by its name
|
25
|
+
#
|
26
|
+
# @param [String] name @see Site#name
|
27
|
+
#
|
28
|
+
# @return [Site]
|
29
|
+
#
|
30
|
+
def self.site(name)
|
31
|
+
Stapeluberlauf::Request::Site.new(default_client, name)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'stapeluberlauf/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "stapeluberlauf"
|
8
|
+
spec.version = Stapeluberlauf::VERSION
|
9
|
+
spec.authors = ["Marius Rackwitz"]
|
10
|
+
spec.email = ["git@mariusrackwitz.de"]
|
11
|
+
|
12
|
+
spec.summary = %q{API wrapper to access the StackExchange API conveniently.}
|
13
|
+
spec.homepage = "https://github.com/mrackwitz/stapeluberlauf"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.require_paths = ["lib"]
|
17
|
+
|
18
|
+
spec.add_dependency "faraday", "~> 0.9.2"
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
21
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stapeluberlauf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marius Rackwitz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.9.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.9.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- git@mariusrackwitz.de
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/console
|
69
|
+
- bin/last_unanswered_questions
|
70
|
+
- lib/stapeluberlauf.rb
|
71
|
+
- lib/stapeluberlauf/authorizable.rb
|
72
|
+
- lib/stapeluberlauf/error.rb
|
73
|
+
- lib/stapeluberlauf/request.rb
|
74
|
+
- lib/stapeluberlauf/request/answer.rb
|
75
|
+
- lib/stapeluberlauf/request/behavior.rb
|
76
|
+
- lib/stapeluberlauf/request/behaviors/acceptable.rb
|
77
|
+
- lib/stapeluberlauf/request/behaviors/commentable.rb
|
78
|
+
- lib/stapeluberlauf/request/behaviors/downvotable.rb
|
79
|
+
- lib/stapeluberlauf/request/behaviors/editable.rb
|
80
|
+
- lib/stapeluberlauf/request/behaviors/favoritable.rb
|
81
|
+
- lib/stapeluberlauf/request/behaviors/flagable.rb
|
82
|
+
- lib/stapeluberlauf/request/behaviors/upvotable.rb
|
83
|
+
- lib/stapeluberlauf/request/comment.rb
|
84
|
+
- lib/stapeluberlauf/request/question.rb
|
85
|
+
- lib/stapeluberlauf/request/site.rb
|
86
|
+
- lib/stapeluberlauf/response.rb
|
87
|
+
- lib/stapeluberlauf/version.rb
|
88
|
+
- stapeluberlauf.gemspec
|
89
|
+
homepage: https://github.com/mrackwitz/stapeluberlauf
|
90
|
+
licenses: []
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.4.5
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: API wrapper to access the StackExchange API conveniently.
|
112
|
+
test_files: []
|
113
|
+
has_rdoc:
|