Class: RedditBot::Bot

Inherits:
Object
  • Object
show all
Defined in:
lib/reddit_bot.rb

Constant Summary collapse

@@skip_erroneous_descending_ids =
lambda do |array|
  array.reverse.each_with_object([]) do |item, result|
    unless result.empty?
      a, b = [item["id"], result.first["id"]]
      next if a == b || a.size < b.size || !(a.size > b.size) && a < b
    end
    result.unshift item
  end
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(secrets, **kwargs) ⇒ Bot



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

def initialize secrets, **kwargs
  @name, @secret_password, @user_agent, *@secret_auth = secrets.values_at *i{  password user_agent client_id client_secret }
  @subreddit = kwargs[:subreddit]
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



16
17
18
# File 'lib/reddit_bot.rb', line 16

def name
  @name
end

Instance Method Details

#each_comment_of_the_post_thread(article) ⇒ Object



163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/reddit_bot.rb', line 163

def each_comment_of_the_post_thread article
  Enumerator.new do |e|
    f = lambda do |smth|
      smth["data"]["children"].each do |child|
        f[child["data"]["replies"]] if child["data"]["replies"].is_a? Hash
        fail "unknown type child['kind']: #{child["kind"]}" unless child["kind"] == "t1"
        e << [child["data"]["name"], child["data"]]
      end
    end
    f[ json(:get, "/comments/#{article}", depth: 100500, limit: 100500).tap do |t|
      fail "smth weird about /comments/<id> response" unless t.size == 2
    end[1] ]
  end
end

#each_new_post_with_top_level_commentsObject



150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/reddit_bot.rb', line 150

def each_new_post_with_top_level_comments
  # TODO add keys assertion like in method above?
  json(:get, "/r/#{@subreddit}/new")["data"]["children"].each do |post|
    fail "unknown type post['kind']: #{post["kind"]}" unless post["kind"] == "t3"
    t = json :get, "/comments/#{post["data"]["id"]}", depth: 1, limit: 100500#, sort: "top"
    fail "smth weird about /comments/<id> response" unless t.size == 2
    yield post["data"], t[1]["data"]["children"].map{ |child|
      fail "unknown type child['kind']: #{child["kind"]}" unless child["kind"] == "t1"
      child["data"]
    }.to_enum
  end
end

#json(mtd, path, _form = []) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/reddit_bot.rb', line 23

def json mtd, path, _form = []
  form = _form.to_h
  response = begin
    JSON.load resp_with_token mtd, path, form.merge({api_type: "json"})
  rescue JSON::ParserError
    $!.message.slice! 1000..-1
    raise
  end
  return response unless response.is_a?(Hash) && response["json"] && !response["json"]["errors"].empty?
  # for example, flairlist.json and {"error": 403} do not have it
    Module.nesting[1].logger.error "ERROR OCCURED on #{[mtd, path]}"
    fail "unknown how to handle multiple errors" if 1 < response["json"]["errors"].size
    Module.nesting[1].logger.error "error: #{response["json"]["errors"]}"
    error, description = response["json"]["errors"].first
  case error
      when "ALREADY_SUB" ; Module.nesting[1].logger.warn "was rejected by moderator if you didn't see in dups"
      when "RATELIMIT"
        fail error unless description[/\Ayou are doing that too much\. try again in (\d) minutes\.\z/]
        Module.nesting[1].logger.info "retrying in #{$1.to_i + 1} minutes"
        sleep ($1.to_i + 1) * 60
        return json mtd, path, _form
      else ; fail error
  end
end

#leave_a_comment(thing_id, text) ⇒ Object



82
83
84
85
86
87
88
89
90
# File 'lib/reddit_bot.rb', line 82

def leave_a_comment thing_id, text
  Module.nesting[1].logger.warn "leaving a comment on '#{thing_id}'"
  json(:post, "/api/comment",
    thing_id: thing_id,
    text: text,
  ).tap do |result|
    fail result["json"]["errors"].to_s unless result["json"]["errors"].empty?
  end
end

#new_posts(subreddit = nil, caching = false) ⇒ Object

:yields: JSON objects: [“data”] part of post or self.post



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/reddit_bot.rb', line 112

def new_posts subreddit = nil, caching = false
  cache = lambda do |id, &block|
    next block.call unless caching
    require "fileutils"
    FileUtils.mkdir_p "cache"
    filename = "cache/#{Digest::MD5.hexdigest id.inspect}"
    next YAML.load File.read filename if File.exist? filename
    block.call.tap do |data|
      File.write filename, YAML.dump(data)
    end
  end
  Enumerator.new do |e|
    after = {}
    loop do
      # TODO maybe force lib user to prepend "r/" to @subreddit constructor?
      args = [:get, "/#{subreddit || (@subreddit ? "r/#{@subreddit}" : fail)}/new", {limit: 100}.merge(after)]
      result = cache.call(args){ json *args }
      fail if result.keys != %w{ kind data }
      fail if result["kind"] != "Listing"
      fail result["data"].keys.inspect unless [
                                                %w{ after dist modhash whitelist_status children before },
                                                %w{ modhash dist children after before },
                                                %w{ after dist modhash geo_filter children before },
                                              ].include? result["data"].keys
      @@skip_erroneous_descending_ids[ result["data"]["children"].map do |post|
        fail "unknown type post['kind']: #{post["kind"]}" unless post["kind"] == "t3"
        post["data"].dup.tap do |data|
          data["url"] = "https://www.reddit.com" + data["url"] if /\A\/r\/[0-9a-zA-Z_]+\/comments\/[0-9a-z]{5,6}\// =~ data["url"] if data["crosspost_parent"]
        end
      end ].each do |data|
        e << data
      end
      break unless marker = result["data"]["after"]
      after = {after: marker}
    end
  end
end

#report(reason, thing_id) ⇒ Object

# [subreddit] String subreddit name without “/r” prefix # [page] String page name without “/wiki/” prefix # [text] :nodoc: def wiki_edit subreddit, page, text

puts "editing wiki page '/r/#{subreddit}/wiki/#{page}'"
json :post,
  "/r/#{subreddit}/api/wiki/edit",
  page: page,
  content: text
# ["previous", result["data"]["children"].last["id"]],

end



60
61
62
63
64
65
66
# File 'lib/reddit_bot.rb', line 60

def report reason, thing_id
  Module.nesting[1].logger.warn "reporting '#{thing_id}'"
  json :post, "/api/report",
    reason: "other",
    other_reason: reason,
    thing_id: thing_id
end

#set_post_flair(post, link_flair_css_class, link_flair_text) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/reddit_bot.rb', line 68

def set_post_flair post, link_flair_css_class, link_flair_text
  Module.nesting[1].logger.warn "setting flair '#{link_flair_css_class}' with text '#{link_flair_text}' to post '#{post["name"]}'"
  if {"error"=>403} == @flairselector_choices ||= json(:post, "/r/#{@subreddit}/api/flairselector", link: post["name"])
    Module.nesting[1].logger.error "possibly not enough permissions for /r/#{@subreddit}/api/flairselector"
    return
  end
  json :post, "/api/selectflair",
    link: post["name"],
    text: link_flair_text,
    flair_template_id: @flairselector_choices["choices"].find{ |i| i["flair_css_class"] == link_flair_css_class }.tap{ |flair|
      fail "can't find '#{link_flair_css_class}' flair class at https://www.reddit.com/r/#{@subreddit}/about/flair/#link_templates" unless flair
    }["flair_template_id"]
end

#subreddit_iterate(what, **kwargs) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/reddit_bot.rb', line 178

def subreddit_iterate what, **kwargs
  Enumerator.new do |e|
    after = {}
    loop do
      break unless marker = json(:get, "/r/#{@subreddit}/#{what}", {limit: 100}.merge(after).merge(kwargs)).tap do |result|
        fail if %w{ kind data } != result.keys
        fail if "Listing" != result["kind"]
        fail result["data"].keys.inspect unless result["data"].keys == %w{ after dist modhash whitelist_status children before } ||
                                                result["data"].keys == %w{ modhash dist children after before }
        result["data"]["children"].each do |post|
          fail "unknown type post['kind']: #{post["kind"]}" unless post["kind"] == "t3"
          e << ( post["data"].tap do |data|
            data["url"] = "https://www.reddit.com" + data["url"] if /\A\/r\/[0-9a-zA-Z_]+\/comments\/[0-9a-z]{5,6}\// =~ data["url"] if data["crosspost_parent"]
          end )
        end
      end["data"]["after"]
      after = {after: marker}
    end
  end
end