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