class RedditBot::Bot
Attributes
name[R]
Public Class Methods
new(secrets, **kwargs)
click to toggle source
# File lib/reddit_bot.rb, line 18 def initialize secrets, **kwargs @name, @secret_password, @user_agent, *@secret_auth = secrets.values_at *%i{ login password user_agent client_id client_secret } @subreddit = kwargs[:subreddit] end
Public Instance Methods
each_comment_of_the_post_thread(article)
click to toggle source
# 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_comments() { |post, t["data"]["children"].map{ |child| fail "unknown type child: #{child}"| ... }
click to toggle source
# 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 = [])
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# [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
# 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)
click to toggle source
# 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)
click to toggle source
# 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
Private Instance Methods
reddit_resp(*args)
click to toggle source
# File lib/reddit_bot.rb, line 249 def reddit_resp *args mtd, url, form, headers, basic_auth = *args headers["Cookie:"] = "over18=1" begin NetHTTPUtils.request_data url, mtd, form: form, header: headers, auth: basic_auth rescue NetHTTPUtils::Error => e sleep 5 raise unless e.code.to_s.start_with? "50" Module.nesting[1].logger.error "API ERROR 50*" retry end end
resp_with_token(mtd, path, form)
click to toggle source
def update_captcha
return if @ignore_captcha pp iden_json = json(:post, "/api/new_captcha") iden = iden_json["json"]["data"]["iden"] # return @iden_and_captcha = [iden, "\n"] if @ignore_captcha # pp resp_with_token(:get, "/captcha/#{iden_json["json"]["data"]["iden"]}", {}) puts "CAPTCHA: https://reddit.com/captcha/#{iden}" @iden_and_captcha = [iden, gets.strip]
end
# File lib/reddit_bot.rb, line 231 def resp_with_token mtd, path, form fail unless path.start_with? ?/ timeout = 5 begin reddit_resp mtd, "https://oauth.reddit.com" + path, form, { "Authorization" => "bearer #{token}", "User-Agent" => "bot/#{@user_agent || @name}/#{Gem::Specification::load("#{__dir__}/../reddit_bot.gemspec").version} by /u/nakilon", } rescue NetHTTPUtils::Error => e raise unless e.code == 401 sleep timeout Module.nesting[1].logger.info "sleeping #{timeout} seconds because of #{e.code}" timeout *= 2 @token_cached = nil retry end end
token()
click to toggle source
# File lib/reddit_bot.rb, line 201 def token return @token_cached if @token_cached # TODO handle with nive error message if we get 403 -- it's probably because of bad user agent response = JSON.load reddit_resp :post, "https://www.reddit.com/api/v1/access_token", { grant_type: "password", username: @name, password: @secret_password, }, { "User-Agent" => "bot/#{@user_agent || @name}/#{Gem::Specification::load("#{__dir__}/../reddit_bot.gemspec").version} by /u/nakilon", }, @secret_auth unless @token_cached = response["access_token"] fail "bot #{@name} isn't a 'developer' of app at https://www.reddit.com/prefs/apps/" if response == {"error"=>"invalid_grant"} fail response.inspect end Module.nesting[1].logger.info "new token is: #{@token_cached}" # update_captcha if "true" == resp_with_token(:get, "/api/needs_captcha", {}) @token_cached end