Module: Snooby

Defined in:
lib/snoobyplus.rb,
lib/snooby/post.rb,
lib/snooby/user.rb,
lib/snooby/client.rb,
lib/snooby/domain.rb,
lib/snooby/actions.rb,
lib/snooby/comment.rb,
lib/snooby/subreddit.rb

Overview

reddit API (Snoo) + happy programming (Ruby) = Snooby

Defined Under Namespace

Modules: About, Comments, Compose, Delete, Posts, Reply, Voting Classes: Client, Comment, Config, Domain, Post, RedditError, Subreddit, User

Constant Summary collapse

Conn =

Opens a persistent connection that provides a significant speed improvement during repeated calls; reddit’s two-second rule pretty much nullifies this, but it’s still a great library and persistent connections are a Good Thing.

Net::HTTP::Persistent.new 'Snooby'
Paths =

Provides a mapping of things and actions to their respective URL fragments.

paths.merge(paths) { |act, path| "http://www.reddit.com/#{path}" }
Fields =

Provides a mapping of things to a list of all the attributes present in the relevant JSON object. A lot of these probably won’t get used too often, but might as well grab all available data (except body_html and selftext_html).

{
  :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],
  :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]
}

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.activeObject

Returns the value of attribute active.



19
20
21
# File 'lib/snoobyplus.rb', line 19

def active
  @active
end

.configObject

Returns the value of attribute config.



19
20
21
# File 'lib/snoobyplus.rb', line 19

def config
  @config
end

Class Method Details

.build(object, path, which, count) ⇒ Object

The crux of Snooby. Generates an array of structs from the Paths and Fields hashes defined above. In addition to just being a very neat container, this allows accessing the returned JSON values using thing.attribute, as opposed to thing[‘attribute’]. Only used for listings of posts and comments at the moment, but I imagine it’ll be used for moderation down the road. Having to explicitly pass the path isn’t very DRY, but deriving it from the object (say, Snooby::Comment) doesn’t expose which kind of comment it is.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/snoobyplus.rb', line 137

def self.build(object, path, which, count)
  # A bit of string manipulation to determine which fields to populate the
  # generated structs with. There might be a less fragile way to go about it,
  # but it shouldn't be a problem as long as naming remains consistent.
  kind = object.to_s.split('::')[1].downcase.to_sym

  # Set limit to the maximum of 100 if we're grabbing more than that, give
  # after a truthy value since we stop when it's no longer so, and initialize
  # an empty result set that the generated structs will be pushed into.
  limit, after, results = [count, 100].min, '', []

  while results.size < count && after
    uri = Paths[path] % which + "?limit=#{limit}&after=#{after}"
    json = request uri
    json = json[1] if path == :post_comments # skip over the post's data
    json['data']['children'].each do |child|
      # Converts each child's JSON data into the relevant struct based on the
      # kind of object being built. The symbols of a struct definition are
      # ordered, but Python dictionaries are not, so #values is insufficient.
      # Preliminary testing showed that appending one at a time is slightly
      # faster than concatenating the entire page of results and then taking
      # a slice at the end. This also allows for premature stopping if the
      # count is reached before all results have been processed.
      results << object.new(*child['data'].values_at(*Fields[kind]))
      return results if results.size == count
    end
    after = json['data']['after']
  end
  results
end

.request(uri, data = nil) ⇒ Object

Wraps the connection for all requests to ensure that the two-second rule is adhered to. The uri parameter comes in as a string because it may have been susceptible to variables, so it gets turned into an actual URI here instead of all over the place. Since it’s required for all POST requests other than logging in, the client’s modhash is sent along by default.

Raises:

  • (ArgumentError)


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/snoobyplus.rb', line 105

def self.request(uri, data = nil)
  uri = URI uri
  if data && data != 'html'
    unless active.uh || data[:passwd]
      raise RedditError, 'You must be logged in to make POST requests.'
    end
    post = Net::HTTP::Post.new uri.path
    post.set_form_data data.merge!(:uh => active.uh, :api_type => 'json')
  end
  wait if @last_request && Time.now - @last_request < @config['delay']
  @last_request = Time.now

  resp = Conn.request uri, post
  raise ArgumentError, resp.code_type unless resp.code == '200'

  # Raw HTML is parsed to obtain the user's trophy data and karma breakdown.
  return resp.body if data == 'html'
  
  json = JSON.parse resp.body, :max_nesting => 100
  if (resp = json['json']) && (errors = resp['errors'])
    raise RedditError, jj(json) unless errors.empty?
  end
  json
end

.waitObject

Called whenever respecting the API is required.



169
170
171
# File 'lib/snoobyplus.rb', line 169

def self.wait
  sleep @config['delay']
end