|
# Dr. Phil (drphil) is reimplmentation of the winfrey voting bot. The goal is |
|
# to give everyone an upvote. But instead of voting 1% by 100 accounts like |
|
# winfrey, this script will vote 100% with 1 randomly chosen account. |
|
# |
|
# See: https://hive.blog/radiator/@inertia/drphil-rb-voting-bot |
|
|
|
require 'rubygems' |
|
require 'bundler/setup' |
|
require 'yaml' |
|
|
|
Bundler.require |
|
|
|
defined? Thread.report_on_exception and Thread.report_on_exception = true |
|
|
|
# If there are problems, this is the most time we'll wait (in seconds). |
|
MAX_BACKOFF = 12.8 |
|
|
|
VOTE_RECHARGE_PER_DAY = 20.0 |
|
VOTE_RECHARGE_PER_HOUR = VOTE_RECHARGE_PER_DAY / 24 |
|
VOTE_RECHARGE_PER_MINUTE = VOTE_RECHARGE_PER_HOUR / 60 |
|
VOTE_RECHARGE_PER_SEC = VOTE_RECHARGE_PER_MINUTE / 60 |
|
|
|
@config_path = __FILE__.sub(/\.rb$/, '.yml') |
|
|
|
unless File.exist? @config_path |
|
puts "Unable to find: #{@config_path}" |
|
exit |
|
end |
|
|
|
def parse_voters(voters) |
|
case voters |
|
when String |
|
raise "Not found: #{voters}" unless File.exist? voters |
|
|
|
f = File.open(voters) |
|
hash = {} |
|
f.read.each_line do |pair| |
|
key, value = pair.split(' ') |
|
hash[key] = value if !!key && !!hash |
|
end |
|
|
|
hash |
|
when Array |
|
a = voters.map{ |v| v.split(' ')}.flatten.each_slice(2) |
|
|
|
return a.to_h if a.respond_to? :to_h |
|
|
|
hash = {} |
|
|
|
voters.each_with_index do |e| |
|
key, val = e.split(' ') |
|
hash[key] = val |
|
end |
|
|
|
hash |
|
else; raise "Unsupported voters: #{voters}" |
|
end |
|
end |
|
|
|
def parse_list(list) |
|
if !!list && File.exist?(list) |
|
f = File.open(list) |
|
elements = [] |
|
|
|
f.each_line do |line| |
|
elements += line.split(' ') |
|
end |
|
|
|
elements.uniq.reject(&:empty?).reject(&:nil?) |
|
else |
|
list.to_s.split(' ') |
|
end |
|
end |
|
|
|
@config = YAML.load_file(@config_path) |
|
rules = @config['voting_rules'] |
|
|
|
@voting_rules = { |
|
mode: rules['mode'] || 'drphil', |
|
vote_weight: (((rules['vote_weight'] || '100.0 %').to_f) * 100).to_i, |
|
favorites_vote_weight: (((rules['favorites_vote_weight'] || rules['vote_weight'] || '100.0 %').to_f) * 100).to_i, |
|
following_vote_weight: (((rules['following_vote_weight'] || rules['vote_weight'] || '100.0 %').to_f) * 100).to_i, |
|
followers_vote_weight: (((rules['followers_vote_weight'] || rules['vote_weight'] || '100.0 %').to_f) * 100).to_i, |
|
enable_comments: rules['enable_comments'], |
|
only_first_posts: rules['only_first_posts'], |
|
only_fully_powered_up: rules['only_fully_powered_up'], |
|
min_wait: rules['min_wait'].to_i, |
|
max_wait: rules['max_wait'].to_i, |
|
min_rep: (rules['min_rep'] || 25.0), |
|
max_rep: (rules['max_rep'] || 99.9).to_f, |
|
min_voting_power: (((rules['min_voting_power'] || '0.0 %').to_f) * 100).to_i, |
|
unique_author: rules['unique_author'], |
|
max_votes_per_post: rules['max_votes_per_post'], |
|
} |
|
|
|
@voting_rules[:wait_range] = [@voting_rules[:min_wait]..@voting_rules[:max_wait]] |
|
|
|
unless @voting_rules[:min_rep] =~ /dynamic:[0-9]+/ |
|
@voting_rules[:min_rep] = @voting_rules[:min_rep].to_f |
|
end |
|
|
|
@voting_rules = Struct.new(*@voting_rules.keys).new(*@voting_rules.values) |
|
|
|
@voters = parse_voters(@config['voters']) |
|
@favorite_accounts = parse_list(@config['favorite_accounts']) |
|
@skip_accounts = parse_list(@config['skip_accounts']) |
|
@skip_tags = parse_list(@config['skip_tags']) |
|
@only_tags = parse_list(@config['only_tags']) |
|
@skip_apps = parse_list(@config['skip_apps']) |
|
@only_apps = parse_list(@config['only_apps']) |
|
@flag_signals = parse_list(@config['flag_signals']) |
|
@vote_signals = parse_list(@config['vote_signals']) |
|
|
|
@favorite_account_weights = @favorite_accounts.map do |account| |
|
pair = account.split(':') |
|
next unless pair.size == 2 |
|
|
|
pair[1] = (pair[1].to_f * 100).to_i |
|
pair |
|
end.compact.to_h |
|
|
|
@favorite_accounts = @favorite_accounts.map do |account| |
|
account.split(':').first |
|
end |
|
|
|
@meeseeker_options = @config[:meeseeker_options] |
|
|
|
@chain_options = @config[:chain_options] |
|
@chain_options[:chain] = @chain_options[:chain].to_sym |
|
@chain_options[:logger] = Logger.new(__FILE__.sub(/\.rb$/, '.log')) |
|
|
|
def winfrey?; @voting_rules.mode == 'winfrey'; end |
|
def drphil?; @voting_rules.mode == 'drphil'; end |
|
def seinfeld?; @voting_rules.mode == 'seinfeld'; end |
|
|
|
if ( |
|
!seinfeld? && |
|
@voting_rules.vote_weight == 0 && @voting_rules.favorites_vote_weight == 0 && |
|
@voting_rules.following_vote_weight == 0 && @voting_rules.followers_vote_weight == 0 |
|
) |
|
puts "WARNING: All vote weights are zero. This is a bot that does nothing." |
|
@voting_rules.mode = 'seinfeld' |
|
end |
|
|
|
@voted_for_authors = {} |
|
@voting_power = {} |
|
@threads = {} |
|
@semaphore = Mutex.new |
|
|
|
def to_rep(raw) |
|
raw = raw.to_i |
|
neg = raw < 0 |
|
level = Math.log10(raw.abs) |
|
level = [level - 9, 0].max |
|
level = (neg ? -1 : 1) * level |
|
level = (level * 9) + 25 |
|
|
|
level |
|
end |
|
|
|
def poll_voting_power |
|
@semaphore.synchronize do |
|
@api.get_accounts(@voters.keys) do |accounts| |
|
accounts.each do |account| |
|
voting_power = account.voting_power / 100.0 |
|
last_vote_time = Time.parse(account.last_vote_time + 'Z') |
|
voting_elapse = Time.now.utc - last_vote_time |
|
current_voting_power = voting_power + (voting_elapse * VOTE_RECHARGE_PER_SEC) |
|
wasted_voting_power = [current_voting_power - 100.0, 0.0].max |
|
current_voting_power = ([100.0, current_voting_power].min * 100).to_i |
|
|
|
if wasted_voting_power > 0 |
|
puts "\t#{account.name} wasted voting power: #{('%.2f' % wasted_voting_power)} %" |
|
end |
|
|
|
@voting_power[account.name] = current_voting_power |
|
end |
|
|
|
@min_voting_power = @voting_power.values.min |
|
@max_voting_power = @voting_power.values.max |
|
@average_voting_power = @voting_power.values.reduce(0, :+) / accounts.size |
|
end |
|
end |
|
end |
|
|
|
def summary_voting_power |
|
poll_voting_power |
|
vp = @average_voting_power / 100.0 |
|
summary = [] |
|
|
|
summary << if @voting_power.size > 1 |
|
"Average remaining voting power: #{('%.3f' % vp)} %" |
|
else |
|
"Remaining voting power: #{('%.3f' % vp)} %" |
|
end |
|
|
|
if @voting_power.size > 1 && @max_voting_power > @voting_rules.min_voting_power |
|
vp = @max_voting_power / 100.0 |
|
|
|
summary << "highest account: #{('%.3f' % vp)} %" |
|
end |
|
|
|
vp = @voting_rules.min_voting_power / 100.0 |
|
summary << "recharging when below: #{('%.3f' % vp)} %" |
|
|
|
summary.join('; ') |
|
end |
|
|
|
def voters_recharging |
|
@voting_power.map do |voter, power| |
|
voter if power < @voting_rules.min_voting_power |
|
end.compact |
|
end |
|
|
|
def skip_tags_intersection?(json_metadata) |
|
metadata = JSON[json_metadata || '{}'] rescue {} |
|
tags = metadata['tags'] || [] rescue [] |
|
tags = [tags].flatten |
|
|
|
(@skip_tags & tags).any? |
|
end |
|
|
|
def only_tags_intersection?(json_metadata) |
|
return true if @only_tags.none? # not set, assume all tags intersect |
|
|
|
metadata = JSON[json_metadata || '{}'] rescue {} |
|
tags = metadata['tags'] || [] rescue [] |
|
tags = [tags].flatten |
|
|
|
(@only_tags & tags).any? |
|
end |
|
|
|
def skip_app?(json_metadata) |
|
metadata = JSON[json_metadata || '{}'] rescue {} |
|
app = metadata['app'].to_s.split('/').first rescue 'unknown' |
|
|
|
@skip_apps.include? app |
|
end |
|
|
|
def only_app?(json_metadata) |
|
return true if @only_apps.none? |
|
|
|
metadata = JSON[json_metadata || '{}'] rescue {} |
|
app = metadata['app'].to_s.split('/').first rescue 'unknown' |
|
|
|
@only_apps.include? app |
|
end |
|
|
|
def voted_for_authors |
|
limit = if @voted_for_authors.empty? |
|
10000 |
|
else |
|
300 |
|
end |
|
|
|
@semaphore.synchronize do |
|
@voters.keys.each do |voter| |
|
@api.get_account_history(voter, -limit, limit) do |result| |
|
result.reverse.each do |i, tx| |
|
op = tx['op'] |
|
next unless op[0] == 'vote' |
|
|
|
timestamp = Time.parse(tx['timestamp'] + 'Z') |
|
latest = @voted_for_authors[op[1]['author']] |
|
|
|
if latest.nil? || latest < timestamp |
|
@voted_for_authors[op[1]['author']] = timestamp |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
@voted_for_authors |
|
end |
|
|
|
def already_voted_for?(author, unique_author = @voting_rules.unique_author) |
|
return false if unique_author.nil? |
|
|
|
now = Time.now.utc |
|
voted_in_threshold = [] |
|
|
|
voted_for_authors.each do |author, vote_at| |
|
if now - vote_at < unique_author * 60 |
|
voted_in_threshold << author |
|
end |
|
end |
|
|
|
return true if voted_in_threshold.include? author |
|
|
|
false |
|
end |
|
|
|
def may_vote?(comment) |
|
return false if !@voting_rules.enable_comments && !comment.parent_author.empty? |
|
return false if @skip_tags.include? comment.parent_permlink |
|
return false if skip_tags_intersection? comment.json_metadata |
|
return false unless only_tags_intersection? comment.json_metadata |
|
return false if @skip_accounts.include? comment.author |
|
return false if skip_app? comment.json_metadata |
|
return false unless only_app? comment.json_metadata |
|
|
|
# We are checking if any voter can vote at all. If at least one voter has a |
|
# non-zero vote_weight, return true. Otherwise, don't bother to even queue up |
|
# a thread. |
|
if @voters.keys.map { |voter| vote_weight(comment.author, voter) > 0.0 }.include? true |
|
true |
|
else |
|
false |
|
end |
|
end |
|
|
|
def min_trending_rep(limit) |
|
begin |
|
@semaphore.synchronize do |
|
if @min_trending_rep.nil? || Random.rand(0..limit) == 13 |
|
puts "Looking up trending up to #{limit} posts." |
|
|
|
@api.get_discussions_by_trending(tag: '', limit: limit) do |trending| |
|
@min_trending_rep = trending.map do |c| |
|
c.author_reputation.to_i |
|
end.min |
|
|
|
puts "Current minimum dynamic rep: #{('%.3f' % to_rep(@min_trending_rep))}" |
|
end |
|
end |
|
end |
|
rescue => e |
|
puts "Warning: #{e}" |
|
end |
|
|
|
@min_trending_rep || 0 |
|
end |
|
|
|
def skip?(comment, voters) |
|
if comment.respond_to? :cashout_time # HF18 |
|
if (cashout_time = Time.parse(comment.cashout_time + 'Z')) < Time.now.utc |
|
puts "Skipped, cashout time has passed (#{cashout_time}):\n\t@#{comment.author}/#{comment.permlink}" |
|
return true |
|
end |
|
end |
|
|
|
if !!@voting_rules.only_first_posts |
|
begin |
|
@semaphore.synchronize do |
|
@api.get_accounts([comment.author]) do |account| |
|
if account.post_count > 1 |
|
puts "Skipped, not first post:\n\t@#{comment.author}/#{comment.permlink}" |
|
return true |
|
end |
|
end |
|
end |
|
rescue => e |
|
puts "Warning: #{e}" |
|
return true |
|
end |
|
end |
|
|
|
if !!@voting_rules.only_fully_powered_up |
|
unless comment.percent_hbd == 0 |
|
puts "Skipped, reward not fully powered up:\n\t@#{comment.author}/#{comment.permlink}" |
|
return true |
|
end |
|
end |
|
|
|
if comment.max_accepted_payout.split(' ').first == '0.000' |
|
puts "Skipped, payout declined:\n\t@#{comment.author}/#{comment.permlink}" |
|
return true |
|
end |
|
|
|
if voters.empty? && winfrey? |
|
puts "Skipped, everyone already voted:\n\t@#{comment.author}/#{comment.permlink}" |
|
return true |
|
end |
|
|
|
unless @favorite_accounts.include? comment.author |
|
if @voting_rules.min_rep =~ /dynamic:[0-9]+/ |
|
limit = @voting_rules.min_rep.split(':').last.to_i |
|
|
|
if (rep = comment.author_reputation.to_i) < min_trending_rep(limit) |
|
# ... rep too low ... |
|
puts "Skipped, due to low dynamic rep (#{('%.3f' % to_rep(rep))}):\n\t@#{comment.author}/#{comment.permlink}" |
|
return true |
|
end |
|
else |
|
if (rep = to_rep(comment.author_reputation)) < @voting_rules.min_rep |
|
# ... rep too low ... |
|
puts "Skipped, due to low rep (#{('%.3f' % rep)}):\n\t@#{comment.author}/#{comment.permlink}" |
|
return true |
|
end |
|
end |
|
|
|
if (rep = to_rep(comment.author_reputation)) > @voting_rules.max_rep |
|
# ... rep too high ... |
|
puts "Skipped, due to high rep (#{('%.3f' % rep)}):\n\t@#{comment.author}/#{comment.permlink}" |
|
return true |
|
end |
|
end |
|
|
|
downvoters = comment.active_votes.map do |v| |
|
v.voter if v.percent < 0 |
|
end.compact |
|
|
|
if (signals = downvoters & @flag_signals).any? |
|
# ... Got a signal flag ... |
|
puts "Skipped, flag signals (#{signals.join(' ')} flagged):\n\t@#{comment.author}/#{comment.permlink}" |
|
return true |
|
end |
|
|
|
upvoters = comment.active_votes.map do |v| |
|
v.voter if v.percent > 0 |
|
end.compact |
|
|
|
if (signals = upvoters & @vote_signals).any? |
|
# ... Got a signal vote ... |
|
puts "Skipped, vote signals (#{signals.join(' ')} voted):\n\t@#{comment.author}/#{comment.permlink}" |
|
return true |
|
end |
|
|
|
all_voters = comment.active_votes.map(&:voter) |
|
|
|
if (all_voters & voters).any? |
|
# ... Someone already voted (probably because post was edited) ... |
|
puts "Skipped, already voted:\n\t@#{comment.author}/#{comment.permlink}" |
|
return true |
|
end |
|
|
|
if already_voted_for?(comment.author) |
|
# ... Already voted in timeframe ... |
|
puts "Skipped, already voted for @#{comment.author} within #{@voting_rules.unique_author} minutes" |
|
return true |
|
end |
|
|
|
false |
|
end |
|
|
|
def following?(voter, author) |
|
@voters_following ||= {} |
|
following = @voters_following[voter] || [] |
|
count = -1 |
|
|
|
if following.empty? |
|
until count == following.size |
|
count = following.size |
|
following_options = [voter, following.last, 'blog', 100] |
|
|
|
@api.get_following(*following_options) do |result| |
|
following += result.map{ |f| f['following'] } rescue [] |
|
following = following.uniq |
|
end |
|
end |
|
|
|
@voters_following[voter] = following |
|
end |
|
|
|
@voters_following[voter] = nil if Random.rand(0..999) == 13 |
|
|
|
following.include? author |
|
end |
|
|
|
def follower?(voter, author) |
|
@voters_followers ||= {} |
|
followers = @voters_followers[voter] || [] |
|
count = -1 |
|
|
|
if followers.empty? |
|
until count == followers.size |
|
count = followers.size |
|
followers_options = [voter, followers.last, 'blog', 100] |
|
|
|
@api.get_followers(*followers_options) do |result| |
|
followers += result.map{ |f| f['follower'] } rescue [] |
|
followers = followers.uniq |
|
end |
|
end |
|
|
|
@voters_followers[voter] = nil if Random.rand(0..999) == 13 |
|
|
|
@voters_followers[voter] = followers |
|
end |
|
|
|
followers.include? author |
|
end |
|
|
|
def vote_weight(author, voter) |
|
@semaphore.synchronize do |
|
if @favorite_accounts.include? author |
|
if @favorite_account_weights.keys.include? author |
|
@favorite_account_weights[author] |
|
else |
|
@voting_rules.favorites_vote_weight |
|
end |
|
elsif following? voter, author |
|
@voting_rules.following_vote_weight |
|
elsif follower? voter, author |
|
@voting_rules.followers_vote_weight |
|
else |
|
@voting_rules.vote_weight |
|
end |
|
end |
|
end |
|
|
|
def vote(comment, wait_offset = 0) |
|
votes_cast = 0 |
|
backoff = 0.2 |
|
slug = "@#{comment.author}/#{comment.permlink}" |
|
|
|
@threads.each do |k, t| |
|
@threads.delete(k) unless t.alive? |
|
end |
|
|
|
@semaphore.synchronize do |
|
if @threads.size != @last_threads_size |
|
print "Pending votes: #{@threads.size} ... " |
|
@last_threads_size = @threads.size |
|
end |
|
end |
|
|
|
if @threads.keys.include? slug |
|
puts "Skipped, vote already pending:\n\t#{slug}" |
|
return |
|
end |
|
|
|
@threads[slug] = Thread.new do |
|
comment = @api.get_content(comment.author, comment.permlink) do |comment| |
|
comment |
|
end |
|
|
|
voters = if winfrey? |
|
@voters.keys - comment.active_votes.map(&:voter) - voters_recharging |
|
else |
|
@voters.keys |
|
end - voters_recharging |
|
|
|
Thread.exit if skip?(comment, voters) |
|
|
|
if wait_offset == 0 |
|
timestamp = Time.parse(comment.created + ' Z') |
|
now = Time.now.utc |
|
wait_offset = now - timestamp |
|
end |
|
|
|
if (wait = (Random.rand(*@voting_rules.wait_range) * 60) - wait_offset) > 0 |
|
puts "Waiting #{wait.to_i} seconds to vote for:\n\t#{slug}" |
|
sleep wait |
|
|
|
@api.get_content(comment.author, comment.permlink) do |comment| |
|
Thread.exit if skip?(comment, voters) |
|
end |
|
else |
|
puts "Catching up to vote for:\n\t#{slug}" |
|
sleep 3 |
|
end |
|
|
|
loop do |
|
begin |
|
break if voters.empty? |
|
|
|
author = comment.author |
|
permlink = comment.permlink |
|
voter = voters.sample |
|
weight = vote_weight(author, voter) |
|
|
|
break if weight == 0.0 |
|
|
|
if (vp = @voting_power[voter].to_i) < @voting_rules.min_voting_power |
|
vp = vp / 100.0 |
|
|
|
if @voters.size > 1 |
|
puts "Recharging #{voter} vote power (currently too low: #{('%.3f' % vp)} %)" |
|
else |
|
puts "Recharging vote power (currently too low: #{('%.3f' % vp)} %)" |
|
end |
|
end |
|
|
|
puts "#{voter} voting for #{slug}" |
|
|
|
wif = @voters[voter] |
|
params = { |
|
voter: voter, |
|
author: author, |
|
permlink: permlink, |
|
weight: weight |
|
} |
|
|
|
vote_options = { |
|
app_base: false, |
|
database_api: @api, |
|
block_api: @api, |
|
network_broadcast_api: @api, |
|
wif: wif, |
|
params: params |
|
} |
|
|
|
begin |
|
Hive::Broadcast.vote(vote_options) do |result| |
|
puts "\tSuccess: #{result.to_json}" |
|
votes_cast += 1 |
|
|
|
if winfrey? |
|
# The winfrey mode keeps voting until there are no more voters of |
|
# until max_votes_per_post is reached (if set) |
|
|
|
if @voting_rules.max_votes_per_post.nil? || votes_cast < @voting_rules.max_votes_per_post |
|
voters -= [voter] |
|
next |
|
else |
|
puts "Max votes per post reached." |
|
break |
|
end |
|
end |
|
|
|
# The drphil mode only votes with one key per post. |
|
#break |
|
end |
|
rescue Hive::UnknownError => e |
|
if e.to_s =~ /Your current vote on this comment is identical to this vote./ |
|
puts "\tFailed: duplicate vote." |
|
voters -= [voter] |
|
next |
|
end |
|
|
|
puts "Unhandled error: #{e}" |
|
next |
|
rescue Hive::DuplicateTransactionError |
|
puts "\tFailed: duplicate vote (duplicate transaction error)." |
|
voters -= [voter] |
|
next |
|
rescue => e |
|
puts e.inspect |
|
voters -= [voter] |
|
next |
|
end |
|
rescue => e |
|
puts "Pausing #{backoff} :: Unable to vote with #{voter}. #{e}" |
|
voters -= [voter] |
|
sleep backoff |
|
backoff = [backoff * 2, MAX_BACKOFF].min |
|
end |
|
end |
|
end |
|
end |
|
|
|
puts "Current mode: #{@voting_rules.mode}. Accounts voting: #{@voters.size}" |
|
replay = 0 |
|
stream = true |
|
|
|
ARGV.each do |arg| |
|
if arg =~ /replay:[0-9]+/ |
|
replay = arg.split('replay:').last.to_i rescue 0 |
|
end |
|
stream = false if arg == 'stream:false' |
|
end |
|
|
|
replay_threads = [] |
|
|
|
if replay > 0 |
|
replay_threads << Thread.new do |
|
@api = Hive::CondenserApi.new(@chain_options) |
|
@block_api = Hive::BlockApi.new(@chain_options) |
|
@stream = Hive::Stream.new(@chain_options) |
|
|
|
properties = @api.get_dynamic_global_properties.result |
|
last_irreversible_block_num = properties.last_irreversible_block_num |
|
block_number = last_irreversible_block_num - replay |
|
|
|
puts "Replaying from block number #{block_number} ..." |
|
|
|
@block_api.get_blocks(block_range: block_number..last_irreversible_block_num) do |block, number| |
|
next unless !!block |
|
|
|
timestamp = Time.parse(block.timestamp + ' Z') |
|
now = Time.now.utc |
|
elapsed = now - timestamp |
|
|
|
block.transactions.each do |tx| |
|
tx.operations.each do |type, op| |
|
vote(op, elapsed.to_i) if type == 'comment_operation' && may_vote?(op) |
|
end |
|
end |
|
end |
|
|
|
# sleep 3 |
|
puts "Done replaying." |
|
end |
|
end |
|
|
|
unless stream |
|
replay_threads.map(&:join) |
|
@threads.values.map(&:join) |
|
exit |
|
end |
|
|
|
loop do |
|
@api = Hive::CondenserApi.new(@chain_options) |
|
@stream = Hive::Stream.new(@chain_options) |
|
op_idx = 0 |
|
|
|
begin |
|
puts summary_voting_power |
|
|
|
if !!@meeseeker_options |
|
puts 'Now waiting for new posts (streaming with meeseeker).' |
|
|
|
ctx = Redis.new(url: @meeseeker_options[:url]) |
|
|
|
Redis.new(url: @meeseeker_options[:url]).subscribe('hive:op:comment') do |on| |
|
on.message do |_, message| |
|
payload = JSON[message] |
|
comment = Hashie::Mash.new(JSON[ctx.get(payload["key"])]).value |
|
|
|
if may_vote? comment |
|
vote(comment) |
|
puts summary_voting_power |
|
end |
|
end |
|
end |
|
else |
|
puts 'Now waiting for new posts (streaming directly on node).' |
|
|
|
@stream.operations(types: :comment_operation) do |comment| |
|
comment = comment.value |
|
next unless may_vote? comment |
|
|
|
if @max_voting_power < @voting_rules.min_voting_power |
|
vp = @max_voting_power / 100.0 |
|
|
|
puts "Recharging vote power (currently too low: #{('%.3f' % vp)} %)" |
|
end |
|
|
|
vote(comment) |
|
puts summary_voting_power |
|
end |
|
end |
|
rescue => e |
|
puts "Unable to stream on current node. Retrying in 5 seconds. Error: #{e}" |
|
Hive::BlockApi.const_set('MAX_RANGE_SIZE', 1) |
|
sleep 5 |
|
end |
|
end |
I had the syntax wrong. works now