snooby 0.1.0 → 0.1.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.
- data/LICENSE +14 -0
- data/README.md +27 -15
- data/lib/snooby.rb +68 -24
- data/lib/snooby/actions.rb +59 -50
- data/lib/snooby/client.rb +71 -30
- data/lib/snooby/comment.rb +10 -0
- data/lib/snooby/post.rb +10 -0
- data/lib/snooby/subreddit.rb +26 -0
- data/lib/snooby/user.rb +46 -0
- metadata +11 -7
- data/lib/snooby/objects.rb +0 -37
data/LICENSE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Copyright (C) 2012 Donnie Akers
|
|
2
|
+
|
|
3
|
+
This program is free software: you can redistribute it and/or modify
|
|
4
|
+
it under the terms of the GNU General Public License as published by
|
|
5
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
(at your option) any later version.
|
|
7
|
+
|
|
8
|
+
This program is distributed in the hope that it will be useful,
|
|
9
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
GNU General Public License for more details.
|
|
12
|
+
|
|
13
|
+
You should have received a copy of the GNU General Public License
|
|
14
|
+
along with this program. If not, see http://www.gnu.org/licenses.
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Snooby is a wrapper around the reddit API written in Ruby. It aims to make autom
|
|
|
4
4
|
## Install
|
|
5
5
|
gem install snooby
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Examples
|
|
8
8
|
|
|
9
9
|
Here's one way you might go about implementing a very simple bot that constantly monitors new comments to scold users of crass language.
|
|
10
10
|
|
|
@@ -13,30 +13,42 @@ require 'snooby'
|
|
|
13
13
|
|
|
14
14
|
probot = Snooby::Client.new('ProfanityBot, v1.0')
|
|
15
15
|
probot.authorize!('ProfanityBot', 'hunter2')
|
|
16
|
+
|
|
16
17
|
while true
|
|
17
|
-
|
|
18
|
-
sleep 2 # Respecting the API is currently manual, will be fixed in future.
|
|
19
|
-
new_comments.each do |com|
|
|
18
|
+
probot.r('all').comments.each do |com|
|
|
20
19
|
if com.body =~ /(vile|rotten|words)/
|
|
21
20
|
com.reply("#{$&.capitalize} is a terrible word, #{com.author}!")
|
|
22
|
-
sleep 2
|
|
23
21
|
end
|
|
24
22
|
end
|
|
25
|
-
sleep 2
|
|
26
23
|
end
|
|
27
24
|
```
|
|
28
|
-
## Features
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
That covers most of the core features, but here's a look at a few more in closer detail.
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
reddit = Snooby::Client.new
|
|
30
|
+
|
|
31
|
+
reddit.user('andrewsmith1986').about['comment_karma'] # => 548027
|
|
32
|
+
reddit.u('violentacrez').trophies.size # => 46
|
|
33
|
+
|
|
34
|
+
reddit.subreddit('askscience').posts[0].selftext # => We see lots of people...
|
|
35
|
+
reddit.r('pics').message('Ban imgur.', "Wouldn't that be lulzy?")
|
|
36
|
+
|
|
37
|
+
frontpage = reddit.r # note the lack of parameters
|
|
38
|
+
frontpage.posts[-1].reply('Welcome to the front page.')
|
|
39
|
+
|
|
40
|
+
# Downvote everything I've ever said. (Note: most of your votes won't count.)
|
|
41
|
+
reddit.u('HazierPhonics').comments(1000).each { |c| c.downvote }
|
|
42
|
+
|
|
43
|
+
# Similarly, upvote everything on the front page of /r/askscience. (Same disclaimer.)
|
|
44
|
+
reddit.r('askscience').posts.each { |p| p.upvote }
|
|
45
|
+
```
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
* grab about data for users and subreddits
|
|
34
|
-
* grab trophy data
|
|
35
|
-
* reply to comments and posts
|
|
47
|
+
The code is thoroughly documented and is the best place to start with questions.
|
|
36
48
|
|
|
37
49
|
## TODO
|
|
38
50
|
|
|
39
|
-
*
|
|
40
|
-
* Flesh out errors
|
|
51
|
+
* Moderation
|
|
41
52
|
* Much more thorough configuration file
|
|
42
|
-
* Granular caching
|
|
53
|
+
* Granular caching
|
|
54
|
+
* Elegant solution to the "more comments" problem
|
data/lib/snooby.rb
CHANGED
|
@@ -2,79 +2,123 @@
|
|
|
2
2
|
|
|
3
3
|
module Snooby
|
|
4
4
|
# Opens a persistent connection that provides a significant speed improvement
|
|
5
|
-
# during repeated calls; reddit's
|
|
6
|
-
# but it's still a
|
|
5
|
+
# during repeated calls; reddit's two-second rule pretty much nullifies this,
|
|
6
|
+
# but it's still a great library and persistent connections are a Good Thing.
|
|
7
7
|
Conn = Net::HTTP::Persistent.new('snooby')
|
|
8
8
|
|
|
9
9
|
# Provides a mapping of things and actions to their respective URL fragments.
|
|
10
10
|
# A path is eventually used as a complete URI, thus the merge.
|
|
11
11
|
paths = {
|
|
12
12
|
:comment => 'api/comment',
|
|
13
|
+
:compose => 'api/compose',
|
|
14
|
+
:delete => 'api/del',
|
|
15
|
+
:disliked => 'user/%s/disliked.json',
|
|
16
|
+
:friend => 'api/friend',
|
|
17
|
+
:hidden => 'user/%s/hidden.json',
|
|
18
|
+
:liked => 'user/%s/liked.json',
|
|
13
19
|
:login => 'api/login/%s',
|
|
14
20
|
:me => 'api/me.json',
|
|
21
|
+
:post_comments => 'comments/%s.json',
|
|
22
|
+
:reddit => '.json',
|
|
23
|
+
:saved => 'saved.json',
|
|
15
24
|
:subreddit_about => 'r/%s/about.json',
|
|
16
25
|
:subreddit_comments => 'r/%s/comments.json',
|
|
17
26
|
:subreddit_posts => 'r/%s.json',
|
|
27
|
+
:subscribe => 'api/subscribe',
|
|
28
|
+
:unfriend => 'api/unfriend',
|
|
18
29
|
:user => 'user/%s',
|
|
19
30
|
:user_about => 'user/%s/about.json',
|
|
20
31
|
:user_comments => 'user/%s/comments.json',
|
|
21
32
|
:user_posts => 'user/%s/submitted.json',
|
|
33
|
+
:vote => 'api/vote'
|
|
22
34
|
}
|
|
23
|
-
Paths = paths.merge(paths) { |k, v|
|
|
35
|
+
Paths = paths.merge(paths) { |k, v| "http://www.reddit.com/#{v}" }
|
|
24
36
|
|
|
25
37
|
# Provides a mapping of things to a list of all the attributes present in the
|
|
26
38
|
# relevant JSON object. A lot of these probably won't get used too often, but
|
|
27
|
-
# might as well
|
|
39
|
+
# might as well grab all available data (except body_html and selftext_html).
|
|
28
40
|
Fields = {
|
|
29
|
-
:comment => %w[author author_flair_css_class author_flair_text body
|
|
30
|
-
:post => %w[author author_flair_css_class author_flair_text clicked created created_utc domain downs hidden id is_self likes media media_embed name num_comments over_18 permalink saved score selftext
|
|
41
|
+
:comment => %w[author author_flair_css_class author_flair_text body created created_utc downs id likes link_id link_title name parent_id replies subreddit subreddit_id ups],
|
|
42
|
+
:post => %w[author author_flair_css_class author_flair_text clicked created created_utc domain downs hidden id is_self likes media media_embed name num_comments over_18 permalink saved score selftext subreddit subreddit_id thumbnail title ups url]
|
|
31
43
|
}
|
|
32
44
|
|
|
45
|
+
# Wraps the connection created above for both POST and GET requests to ensure
|
|
46
|
+
# that the two-second rule is adhered to. The uri parameter is turned into an
|
|
47
|
+
# actual URI once here instead of all over the place. The client's modhash is
|
|
48
|
+
# always required for POST requests, so it is passed along by default.
|
|
49
|
+
def self.request(uri, data = nil)
|
|
50
|
+
uri = URI(uri)
|
|
51
|
+
if data
|
|
52
|
+
data.merge!(:uh => Snooby.active.uh) if Snooby.active
|
|
53
|
+
post = Net::HTTP::Post.new(uri.path)
|
|
54
|
+
post.set_form_data(data)
|
|
55
|
+
end
|
|
56
|
+
Snooby.wait if @last_request && Time.now - @last_request < 2
|
|
57
|
+
@last_request = Time.now
|
|
58
|
+
Conn.request(uri, post).body
|
|
59
|
+
end
|
|
60
|
+
|
|
33
61
|
# The crux of Snooby. Generates an array of structs from the Paths and Fields
|
|
34
62
|
# hashes defined above. In addition to just being a very neat container, this
|
|
35
63
|
# allows accessing the returned JSON values using thing.attribute, as opposed
|
|
36
64
|
# to thing['data']['attribute']. Only used for listings of posts and comments
|
|
37
65
|
# at the moment, but I imagine it'll be used for moderation down the road.
|
|
38
|
-
|
|
66
|
+
# Having to explicitly pass the path isn't very DRY, but deriving it from the
|
|
67
|
+
# object (say, Snooby::Comment) doesn't expose which kind of comment it is.
|
|
68
|
+
def self.build(object, path, which, count)
|
|
39
69
|
# A bit of string manipulation to determine which fields to populate the
|
|
40
70
|
# generated struct with. There might be a less fragile way to go about it,
|
|
41
71
|
# but it shouldn't be a problem as long as naming remains consistent.
|
|
42
72
|
kind = object.to_s.split('::')[1].downcase.to_sym
|
|
43
73
|
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
|
|
74
|
+
# Set limit to the maximum of 100 if we're grabbing more than that, give
|
|
75
|
+
# after a truthy value since we stop when it's no longer so, and initialize
|
|
76
|
+
# an empty result set that the generated structs will be pushed into.
|
|
77
|
+
limit, after, results = [count, 100].min, '', []
|
|
48
78
|
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
79
|
+
# Fetch data until we've met the count or reached the end of the results.
|
|
80
|
+
while results.size < count && after
|
|
81
|
+
uri = Paths[path] % which + "?limit=#{limit}&after=#{after}"
|
|
82
|
+
json = JSON.parse(Snooby.request(uri), :max_nesting => 100)
|
|
83
|
+
json = json[1] if path == :post_comments # skip over the post's data
|
|
84
|
+
json['data']['children'].each do |child|
|
|
85
|
+
# Converts each child's JSON data into the relevant struct based on the
|
|
86
|
+
# kind of object being built. The symbols of a struct definition are
|
|
87
|
+
# ordered, but Python dictionaries are not, so #values is insufficient.
|
|
88
|
+
# Preliminary testing showed that appending one at a time is slightly
|
|
89
|
+
# faster than concatenating the entire page of results and then taking
|
|
90
|
+
# a slice at the end. This also allows for premature stopping if the
|
|
91
|
+
# count is reached before all results have been processed.
|
|
92
|
+
results << object.new(*child['data'].values_at(*Fields[kind]))
|
|
93
|
+
return results if results.size == count
|
|
94
|
+
end
|
|
95
|
+
after = json['data']['after']
|
|
56
96
|
end
|
|
97
|
+
results
|
|
57
98
|
end
|
|
58
99
|
|
|
59
100
|
class << self
|
|
60
|
-
attr_accessor :config, :
|
|
101
|
+
attr_accessor :config, :active
|
|
61
102
|
end
|
|
62
103
|
|
|
63
104
|
# Used for permanent storage of preferences and authorization data.
|
|
64
105
|
# Each client should have its own directory to prevent pollution.
|
|
65
106
|
@config = JSON.parse(File.read('.snooby')) rescue {'auth' => {}}
|
|
66
107
|
|
|
108
|
+
# Called whenever respecting the API is required.
|
|
109
|
+
def self.wait
|
|
110
|
+
sleep 2
|
|
111
|
+
end
|
|
112
|
+
|
|
67
113
|
# Raised with a pretty print of the relevant JSON object whenever an API call
|
|
68
|
-
# returns a non-empty "errors"
|
|
69
|
-
#
|
|
70
|
-
# is displayed instead, typically to inform the user either that they've been
|
|
71
|
-
# rate-limited or that they lack the necessary authorization.
|
|
114
|
+
# returns a non-empty "errors" array, typically in cases of rate limiting and
|
|
115
|
+
# missing or inaccurate authorization.
|
|
72
116
|
class RedditError < StandardError; end
|
|
73
117
|
end
|
|
74
118
|
|
|
75
119
|
# Snooby's parts are required down here, after its initial declaration, because
|
|
76
120
|
# Post and Comment are structs whose definitions are taken from the Fields hash
|
|
77
121
|
# above, and related bits might as well be kept together.
|
|
78
|
-
%w[client actions
|
|
122
|
+
%w[client actions user subreddit post comment].each do |d|
|
|
79
123
|
require "snooby/#{d}"
|
|
80
124
|
end
|
data/lib/snooby/actions.rb
CHANGED
|
@@ -1,69 +1,78 @@
|
|
|
1
1
|
module Snooby
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
# are called, but hopefully the intent is to use Snooby sensibly. I realize
|
|
5
|
-
# this is probably bad design, but it's just so damned clean.
|
|
6
|
-
module Actions
|
|
7
|
-
# Returns a hash containing the values supplied by about.json, so long as
|
|
8
|
-
# the calling object is a User or Subreddit.
|
|
2
|
+
module About
|
|
3
|
+
# Returns a hash containing the calling object's about.json data.
|
|
9
4
|
def about
|
|
10
|
-
if !['user', 'subreddit'].include?(@kind)
|
|
11
|
-
raise RedditError, 'Only users and subreddits have about pages.'
|
|
12
|
-
end
|
|
13
|
-
|
|
14
5
|
uri = URI(Paths[:"#{@kind}_about"] % @name)
|
|
15
|
-
JSON.parse(
|
|
6
|
+
JSON.parse(Snooby.request(uri))['data']
|
|
16
7
|
end
|
|
8
|
+
end
|
|
17
9
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
10
|
+
module Posts
|
|
11
|
+
# Returns an array of structs containing the calling object's posts.
|
|
12
|
+
def posts(count = 25)
|
|
13
|
+
path = @name ? :"#{@kind}_posts" : :reddit
|
|
14
|
+
Snooby.build(Post, path, @name, count)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
23
17
|
|
|
24
|
-
|
|
18
|
+
module Comments
|
|
19
|
+
# Returns an array of structs containing the calling object's comments.
|
|
20
|
+
# TODO: return more than just top-level comments for posts.
|
|
21
|
+
def comments(count = 25)
|
|
22
|
+
# @name suffices for users and subreddits, but a post's name is obtained
|
|
23
|
+
# from its struct; the "t3_" must be removed before making the API call.
|
|
24
|
+
@name ||= self.name[3..-1]
|
|
25
|
+
Snooby.build(Comment, :"#{@kind}_comments", @name, count)
|
|
25
26
|
end
|
|
27
|
+
end
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
module Reply
|
|
30
|
+
# Posts a reply to the calling object, which is either a post or a comment.
|
|
31
|
+
def reply(text)
|
|
32
|
+
raise RedditError, 'You are not logged in.' unless Snooby.active
|
|
33
|
+
|
|
34
|
+
data = {:parent => self.name, :text => text, :api_type => 'json'}
|
|
35
|
+
resp = Snooby.request(Paths[:comment], data)
|
|
36
|
+
json = JSON.parse(resp)['json']
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
raise RedditError, jj(json) unless json['errors'].empty?
|
|
34
39
|
end
|
|
40
|
+
end
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
# the
|
|
38
|
-
#
|
|
39
|
-
def
|
|
40
|
-
raise RedditError, '
|
|
42
|
+
module Delete
|
|
43
|
+
# Deletes the calling object, which is either a post or a comment, as long
|
|
44
|
+
# as it belongs to the currently authorized user.
|
|
45
|
+
def delete
|
|
46
|
+
raise RedditError, 'You are not logged in.' unless Snooby.active
|
|
47
|
+
unless self.author == Snooby.active.username
|
|
48
|
+
#raise CommonDecencyError
|
|
49
|
+
raise RedditError, "You can't delete somebody else's content."
|
|
50
|
+
end
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
html = Conn.request(URI(Paths[:user] % @name) + '?limit=1').body
|
|
44
|
-
# Entry-level black magic.
|
|
45
|
-
html.scan(/"trophy-name">(.+?)<.+?"\s?>([^<]*)</)
|
|
52
|
+
Snooby.request(Paths[:delete], :id => self.name)
|
|
46
53
|
end
|
|
54
|
+
end
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
module Compose
|
|
57
|
+
# Sends a message to the calling object, which is either a subreddit or a
|
|
58
|
+
# user; in the case of the former, this behaves like moderator mail.
|
|
59
|
+
def compose(subject, text)
|
|
60
|
+
raise RedditError, 'You are not logged in.' unless Snooby.active
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
data = {:parent => self.name, :text => text, :uh => Snooby.auth}
|
|
60
|
-
post.set_form_data(data)
|
|
61
|
-
json = JSON.parse(Conn.request(uri, post).body)['jquery']
|
|
62
|
+
to = (@kind == 'user' ? '' : '#') + @name
|
|
63
|
+
data = {:to => to, :subject => subject, :text => text}
|
|
64
|
+
Snooby.request(Paths[:compose], data)
|
|
65
|
+
end
|
|
66
|
+
alias :message :compose
|
|
67
|
+
end
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
raise RedditError, json[14][3] if json.size == 17
|
|
69
|
+
module Voting
|
|
70
|
+
def vote(dir)
|
|
71
|
+
Snooby.request(Paths[:vote], :id => self.name, :dir => dir)
|
|
67
72
|
end
|
|
73
|
+
|
|
74
|
+
def upvote; vote 1; end
|
|
75
|
+
def rescind; vote 0; end
|
|
76
|
+
def downvote; vote -1; end
|
|
68
77
|
end
|
|
69
78
|
end
|
data/lib/snooby/client.rb
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
module Snooby
|
|
2
2
|
# Interface through which Snooby does all of its interacting with the API.
|
|
3
3
|
class Client
|
|
4
|
-
|
|
4
|
+
# Exposes username to raise a proper error in case of an attempt to delete
|
|
5
|
+
# another user's content, modhash (uh) for sending along in the headers on
|
|
6
|
+
# API calls that change state, and id for (un)friending.
|
|
7
|
+
attr_reader :username, :uh, :id
|
|
8
|
+
|
|
9
|
+
def initialize(user_agent = "Snooby, #{rand}")
|
|
5
10
|
# Net::HTTP's default User-Agent, "Ruby", is banned on reddit due to its
|
|
6
11
|
# frequent improper use; cheers to Eric Hodel (drbrain) for implementing
|
|
7
12
|
# this workaround for net-http-persistent.
|
|
@@ -12,54 +17,90 @@ module Snooby
|
|
|
12
17
|
# GET operations do not need to be authorized, so if your intent is simply
|
|
13
18
|
# to gather data, feel free to disregard this method entirely.
|
|
14
19
|
def authorize!(user, passwd, force_update = false)
|
|
20
|
+
@username = user
|
|
21
|
+
|
|
15
22
|
if Snooby.config['auth'][user] && !force_update
|
|
16
23
|
# Authorization data exists, skip login and potential rate-limiting.
|
|
17
|
-
@
|
|
24
|
+
@uh, @cookie, @id = Snooby.config['auth'][user]
|
|
18
25
|
else
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
post.set_form_data(data)
|
|
23
|
-
json = JSON.parse(Conn.request(uri, post).body)['json']
|
|
26
|
+
data = {:user => user, :passwd => passwd, :api_type => 'json'}
|
|
27
|
+
resp = Snooby.request(Paths[:login] % user, data)
|
|
28
|
+
json = JSON.parse(resp)['json']
|
|
24
29
|
|
|
25
30
|
# Will fire for incorrect login credentials and when rate-limited.
|
|
26
|
-
raise RedditError, jj(json)
|
|
27
|
-
|
|
28
|
-
# Parse authorization data
|
|
29
|
-
|
|
30
|
-
# authorization data is immortal unless the password has changed. The
|
|
31
|
-
# force_update parameter should be enabled if such is the case.
|
|
32
|
-
@modhash, @cookie = json['data'].values
|
|
33
|
-
Snooby.config['auth'][user] = [@modhash, @cookie]
|
|
34
|
-
File.open('.snooby', 'w') { |f| f << Snooby.config.to_json }
|
|
31
|
+
raise RedditError, jj(json) unless json['errors'].empty?
|
|
32
|
+
|
|
33
|
+
# Parse authorization data.
|
|
34
|
+
@uh, @cookie = json['data'].values
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# Sets the reddit_session cookie required for API calls to be recognized
|
|
38
|
-
# as coming from the intended user
|
|
38
|
+
# as coming from the intended user. Uses override_headers to allow for
|
|
39
39
|
# switching the current user mid-execution, if so desired.
|
|
40
|
-
Conn.
|
|
40
|
+
Conn.override_headers['Cookie'] = "reddit_session=#{@cookie}"
|
|
41
41
|
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
# A second call is made, if required, to grab the client's id, which is
|
|
43
|
+
# necessary for (un)friending.
|
|
44
|
+
@id ||= "t2_#{me['id']}"
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
# Updates the config file to faciliate one-time authorization. This works
|
|
47
|
+
# because the authorization data is immortal unless the password has been
|
|
48
|
+
# changed; enable the force_update parameter if such is the case.
|
|
49
|
+
Snooby.config['auth'][user] = [@uh, @cookie, @id]
|
|
50
|
+
File.open('.snooby', 'w') { |f| f << Snooby.config.to_json }
|
|
51
|
+
|
|
52
|
+
# Allows Snooby's classes to access the currently authorized client.
|
|
53
|
+
Snooby.active = self
|
|
50
54
|
end
|
|
51
55
|
|
|
52
|
-
# Returns a User object
|
|
56
|
+
# Returns a User object through which all relevant data is accessed.
|
|
53
57
|
def user(name)
|
|
54
58
|
User.new(name)
|
|
55
59
|
end
|
|
60
|
+
alias :u :user
|
|
56
61
|
|
|
57
|
-
#
|
|
58
|
-
def subreddit(name)
|
|
62
|
+
# Returns a Subreddit object through which all relevant data is accessed.
|
|
63
|
+
def subreddit(name = nil)
|
|
59
64
|
Subreddit.new(name)
|
|
60
65
|
end
|
|
61
|
-
|
|
62
|
-
alias :u :user
|
|
63
66
|
alias :r :subreddit
|
|
67
|
+
|
|
68
|
+
# Returns a hash containing the values given by me.json, used internally to
|
|
69
|
+
# obtain the client's id, but also the most efficient way to check whether
|
|
70
|
+
# or not the client has mail.
|
|
71
|
+
def me
|
|
72
|
+
JSON.parse(Snooby.request(Paths[:me]))['data']
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns an array of structs containing the current client's saved posts.
|
|
76
|
+
def saved(count = 25)
|
|
77
|
+
Snooby.build(Post, :saved, nil, count)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Convenience methods.
|
|
81
|
+
|
|
82
|
+
def friend(name)
|
|
83
|
+
User.new(name).friend
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def unfriend(name)
|
|
87
|
+
User.new(name).unfriend
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def subscribe(name)
|
|
91
|
+
Subreddit.new(name).subscribe
|
|
92
|
+
end
|
|
93
|
+
alias :sub :subscribe
|
|
94
|
+
|
|
95
|
+
def unsubscribe(name)
|
|
96
|
+
Subreddit.new(name).unsubscribe
|
|
97
|
+
end
|
|
98
|
+
alias :unsub :unsubscribe
|
|
99
|
+
|
|
100
|
+
def compose(to, subject, text)
|
|
101
|
+
data = {:to => to, :subject => subject, :text => text}
|
|
102
|
+
Snooby.request(Paths[:compose], data)
|
|
103
|
+
end
|
|
104
|
+
alias :message :compose
|
|
64
105
|
end
|
|
65
106
|
end
|
data/lib/snooby/post.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Snooby
|
|
2
|
+
class Subreddit
|
|
3
|
+
include About, Posts, Comments, Compose
|
|
4
|
+
|
|
5
|
+
def initialize(name)
|
|
6
|
+
@name = name
|
|
7
|
+
@kind = 'subreddit'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Alas, (un)subscribing by name alone doesn't work, so a separate call must
|
|
11
|
+
# be made to obtain the subreddit's id, thus the wait. Maybe cache this?
|
|
12
|
+
def subscribe
|
|
13
|
+
sr = about['name']
|
|
14
|
+
Snooby.wait
|
|
15
|
+
Snooby.request(Paths[:subscribe], :action => 'sub', :sr => sr)
|
|
16
|
+
end
|
|
17
|
+
alias :sub :subscribe
|
|
18
|
+
|
|
19
|
+
def unsubscribe
|
|
20
|
+
sr = about['name']
|
|
21
|
+
Snooby.wait
|
|
22
|
+
Snooby.request(Paths[:subscribe], :action => 'unsub', :sr => sr)
|
|
23
|
+
end
|
|
24
|
+
alias :unsub :unsubscribe
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/snooby/user.rb
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Snooby
|
|
2
|
+
class User
|
|
3
|
+
include About, Posts, Comments, Compose
|
|
4
|
+
|
|
5
|
+
def initialize(name)
|
|
6
|
+
@name = name
|
|
7
|
+
@kind = 'user'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Returns an array of 2-tuples containing the user's trophy information in
|
|
11
|
+
# the form of [name, description], the latter containing the empty string
|
|
12
|
+
# if inapplicable.
|
|
13
|
+
def trophies
|
|
14
|
+
# Only interested in trophies; request the minimum amount of content.
|
|
15
|
+
html = Snooby.request(URI(Paths[:user] % @name) + '?limit=1')
|
|
16
|
+
# Entry-level black magic.
|
|
17
|
+
html.scan(/"trophy-name">(.+?)<.+?"\s?>([^<]*)</)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def liked(count = 25)
|
|
21
|
+
Snooby.build(Post, :liked, @name, count)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def disliked(count = 25)
|
|
25
|
+
Snooby.build(Post, :disliked, @name, count)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def hidden(count = 25)
|
|
29
|
+
Snooby.build(Post, :hidden, @name, count)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def friend
|
|
33
|
+
raise RedditError, 'You are not logged in.' unless Snooby.active
|
|
34
|
+
|
|
35
|
+
data = {:name => @name, :type => 'friend', :container => Snooby.active.id}
|
|
36
|
+
Snooby.request(Paths[:friend], data)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def unfriend
|
|
40
|
+
raise RedditError, 'You are not logged in.' unless Snooby.active
|
|
41
|
+
|
|
42
|
+
data = {:name => @name, :type => 'friend', :container => Snooby.active.id}
|
|
43
|
+
Snooby.request(Paths[:unfriend], data)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: snooby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,11 +9,11 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2012-02-
|
|
12
|
+
date: 2012-02-21 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: json
|
|
16
|
-
requirement: &
|
|
16
|
+
requirement: &72574440 !ruby/object:Gem::Requirement
|
|
17
17
|
none: false
|
|
18
18
|
requirements:
|
|
19
19
|
- - ! '>='
|
|
@@ -21,10 +21,10 @@ dependencies:
|
|
|
21
21
|
version: '0'
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
|
-
version_requirements: *
|
|
24
|
+
version_requirements: *72574440
|
|
25
25
|
- !ruby/object:Gem::Dependency
|
|
26
26
|
name: net-http-persistent
|
|
27
|
-
requirement: &
|
|
27
|
+
requirement: &72574180 !ruby/object:Gem::Requirement
|
|
28
28
|
none: false
|
|
29
29
|
requirements:
|
|
30
30
|
- - ! '>='
|
|
@@ -32,7 +32,7 @@ dependencies:
|
|
|
32
32
|
version: '2.5'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
|
-
version_requirements: *
|
|
35
|
+
version_requirements: *72574180
|
|
36
36
|
description:
|
|
37
37
|
email:
|
|
38
38
|
- andkerosine@gmail.com
|
|
@@ -41,12 +41,16 @@ extensions: []
|
|
|
41
41
|
extra_rdoc_files: []
|
|
42
42
|
files:
|
|
43
43
|
- Gemfile
|
|
44
|
+
- LICENSE
|
|
44
45
|
- README.md
|
|
45
46
|
- Rakefile
|
|
46
47
|
- lib/snooby.rb
|
|
47
48
|
- lib/snooby/actions.rb
|
|
48
49
|
- lib/snooby/client.rb
|
|
49
|
-
- lib/snooby/
|
|
50
|
+
- lib/snooby/comment.rb
|
|
51
|
+
- lib/snooby/post.rb
|
|
52
|
+
- lib/snooby/subreddit.rb
|
|
53
|
+
- lib/snooby/user.rb
|
|
50
54
|
homepage: https://github.com/andkerosine/snooby
|
|
51
55
|
licenses: []
|
|
52
56
|
post_install_message:
|
data/lib/snooby/objects.rb
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
module Snooby
|
|
2
|
-
class User
|
|
3
|
-
include Actions
|
|
4
|
-
|
|
5
|
-
def initialize(name)
|
|
6
|
-
@name = name
|
|
7
|
-
@kind = 'user'
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
class Subreddit
|
|
12
|
-
include Actions
|
|
13
|
-
|
|
14
|
-
def initialize(name)
|
|
15
|
-
@name = name
|
|
16
|
-
@kind = 'subreddit'
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
class Post < Struct.new(*Fields[:post].map(&:to_sym))
|
|
21
|
-
include Actions
|
|
22
|
-
|
|
23
|
-
def initialize(*)
|
|
24
|
-
super
|
|
25
|
-
@kind = 'post'
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
class Comment < Struct.new(*Fields[:comment].map(&:to_sym))
|
|
30
|
-
include Actions
|
|
31
|
-
|
|
32
|
-
def initialize(*)
|
|
33
|
-
super
|
|
34
|
-
@kind = 'comment'
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|