Skip to content

Instantly share code, notes, and snippets.

@inertia186
Last active July 31, 2022 06:17
Show Gist options
  • Save inertia186/61bcc2b821aa5acb24f7fc88921950c7 to your computer and use it in GitHub Desktop.
Save inertia186/61bcc2b821aa5acb24f7fc88921950c7 to your computer and use it in GitHub Desktop.

Revisions

  1. Anthony Martin revised this gist Jul 31, 2022. 1 changed file with 10 additions and 10 deletions.
    20 changes: 10 additions & 10 deletions Gemfile.lock
    Original file line number Diff line number Diff line change
    @@ -2,33 +2,33 @@ GEM
    remote: https://rubygems.org/
    specs:
    base58 (0.2.3)
    bindata (2.4.8)
    bindata (2.4.10)
    bitcoin-ruby (0.0.20)
    eventmachine
    ffi
    scrypt
    eventmachine (1.2.7)
    ffi (1.13.1)
    ffi (1.15.5)
    ffi-compiler (1.0.1)
    ffi (>= 1.0.0)
    rake
    hashie (3.6.0)
    hive-ruby (1.0.1)
    hashie (5.0.0)
    hive-ruby (1.0.4)
    base58 (~> 0.2, >= 0.2.3)
    bindata (~> 2.4, >= 2.4.4)
    bitcoin-ruby (~> 0.0, = 0.0.20)
    ffi (~> 1.9, >= 1.9.23)
    hashie (~> 3.5, >= 3.5.7)
    hashie (>= 3.5)
    json (~> 2.1, >= 2.1.0)
    logging (~> 2.2, >= 2.2.0)
    json (2.3.1)
    json (2.6.2)
    little-plugger (1.1.4)
    logging (2.3.0)
    logging (2.3.1)
    little-plugger (~> 1.1)
    multi_json (~> 1.14)
    multi_json (1.15.0)
    rake (13.0.1)
    redis (4.1.3)
    rake (13.0.6)
    redis (4.7.1)
    scrypt (3.0.7)
    ffi-compiler (>= 1.0, < 2.0)

    @@ -40,4 +40,4 @@ DEPENDENCIES
    redis

    BUNDLED WITH
    1.17.3
    2.3.19
  2. inertia186 revised this gist Oct 16, 2020. No changes.
  3. inertia186 revised this gist Oct 16, 2020. 5 changed files with 51 additions and 40 deletions.
    2 changes: 1 addition & 1 deletion Gemfile
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    source 'https://rubygems.org'

    gem 'steem-ruby', require: 'steem'
    gem 'hive-ruby', require: 'hive'
    gem 'redis'

    # Mostly for troubleshooting:
    38 changes: 24 additions & 14 deletions Gemfile.lock
    Original file line number Diff line number Diff line change
    @@ -2,32 +2,42 @@ GEM
    remote: https://rubygems.org/
    specs:
    base58 (0.2.3)
    bindata (2.4.4)
    bitcoin-ruby (0.0.18)
    ffi (1.10.0)
    bindata (2.4.8)
    bitcoin-ruby (0.0.20)
    eventmachine
    ffi
    scrypt
    eventmachine (1.2.7)
    ffi (1.13.1)
    ffi-compiler (1.0.1)
    ffi (>= 1.0.0)
    rake
    hashie (3.6.0)
    json (2.1.0)
    little-plugger (1.1.4)
    logging (2.2.2)
    little-plugger (~> 1.1)
    multi_json (~> 1.10)
    multi_json (1.13.1)
    redis (4.1.0)
    steem-ruby (0.9.4)
    hive-ruby (1.0.1)
    base58 (~> 0.2, >= 0.2.3)
    bindata (~> 2.4, >= 2.4.4)
    bitcoin-ruby (~> 0.0, >= 0.0.18)
    bitcoin-ruby (~> 0.0, = 0.0.20)
    ffi (~> 1.9, >= 1.9.23)
    hashie (~> 3.5, >= 3.5.7)
    json (~> 2.1, >= 2.1.0)
    logging (~> 2.2, >= 2.2.0)
    json (2.3.1)
    little-plugger (1.1.4)
    logging (2.3.0)
    little-plugger (~> 1.1)
    multi_json (~> 1.14)
    multi_json (1.15.0)
    rake (13.0.1)
    redis (4.1.3)
    scrypt (3.0.7)
    ffi-compiler (>= 1.0, < 2.0)

    PLATFORMS
    ruby

    DEPENDENCIES
    hive-ruby
    redis
    steem-ruby

    BUNDLED WITH
    1.16.5
    1.17.3
    19 changes: 10 additions & 9 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,12 +1,11 @@
    * Title: drphil.rb - Voting Bot
    * Tags: utopian-io development ruby steem curation
    * Tags: utopian-io development ruby hive curation
    * Notes:

    #### New Features

    * Gem update.
    * Switched to [`steem-ruby`](https://github.com/steemit/steem-ruby)
    * Added optional support for [`meeseeker`](https://github.com/inertia186/meeseeker).
    * Eclipse Enabled.

    #### Features

    @@ -42,7 +41,7 @@
    * `skip_tags`
    * `flag_signals`
    * `vote_signals`
    * `only_fully_powered_up` which will only vote for posts that receive 100% STEEM Power author rewards.
    * `only_fully_powered_up` which will only vote for posts that receive 100% HIVE Power author rewards.
    * Skip posts with declined payout.
    * Skip posts that already have votes from external scripts and posts that were edited.
    * Argument called `replay:` allows a replay of *n* blocks allowing you to catch up to the present.
    @@ -62,6 +61,8 @@
    * Now 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 new thread, thus saving memory.
    * Argument called `stream:false` will exit without streaming the blockchain. Useful in situations where you only want to `replay:` and exit.
    * Use `account_history` instead of previous internal timer for improved accuracy between runs.
    * Switched to [`hive-ruby`](https://gitlab.syncad.com/hive/hive-ruby)
    * Added optional support for [`meeseeker`](https://github.com/inertia186/meeseeker).

    #### Overview

    @@ -75,7 +76,7 @@ If the complaint about Winfrey is blockchain bloat, Dr. Phil prescribes weight l

    #### Install

    To use this ~~[Radiator](https://steemit.com/steem/@inertia/radiator-steem-ruby-api-client)~~ [steem-ruby](https://github.com/steemit/steem-ruby) bot:
    To use this ~~[Radiator](https://hive.blog/@inertia/radiator-steem-ruby-api-client)~~ [hive-ruby](https://gitlab.syncad.com/hive/) bot:

    ##### Linux

    @@ -175,22 +176,22 @@ Make sure the `.yml` file `voter` items have the account name, followed by a spa

    Is there a list of nodes?

    ##### Solution: Yes, special thanks to @jamzed.
    ##### Solution: Yes, special thanks to @holger80.

    https://geo.steem.pl/
    https://hive.blog/@fullnodeupdate

    ---

    <center>
    <img src="http://i.imgur.com/qUZYLiQ.png" />
    </center>

    See some of my previous Ruby How To posts in: [#radiator](https://steemit.com/created/radiator) [#ruby](https://steemit.com/created/ruby)
    See some of my previous Ruby How To posts in: [#radiator](https://hive.blog/created/radiator) [#ruby](https://hive.blog/created/ruby)


    ## Get in touch!

    If you're using Dr. Phil, I'd love to hear from you. Drop me a line and tell me what you think! I'm @inertia on STEEM and [SteemSpeak](http://discord.steemspeak.com).
    If you're using Dr. Phil, I'd love to hear from you. Drop me a line and tell me what you think! I'm @inertia on Hive and [The Pulse](https://discord.gg/uNanhh5).

    ## License

    23 changes: 12 additions & 11 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,7 @@
    # 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://steemit.com/radiator/@inertia/drphil-rb-voting-bot
    # See: https://hive.blog/radiator/@inertia/drphil-rb-voting-bot

    require 'rubygems'
    require 'bundler/setup'
    @@ -357,7 +357,7 @@ def skip?(comment, voters)
    end

    if !!@voting_rules.only_fully_powered_up
    unless comment.percent_steem_dollars == 0
    unless comment.percent_hbd == 0
    puts "Skipped, reward not fully powered up:\n\t@#{comment.author}/#{comment.permlink}"
    return true
    end
    @@ -593,7 +593,7 @@ def vote(comment, wait_offset = 0)
    }

    begin
    Steem::Broadcast.vote(vote_options) do |result|
    Hive::Broadcast.vote(vote_options) do |result|
    puts "\tSuccess: #{result.to_json}"
    votes_cast += 1

    @@ -613,7 +613,7 @@ def vote(comment, wait_offset = 0)
    # The drphil mode only votes with one key per post.
    #break
    end
    rescue Steem::UnknownError => e
    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]
    @@ -622,7 +622,7 @@ def vote(comment, wait_offset = 0)

    puts "Unhandled error: #{e}"
    next
    rescue Steem::DuplicateTransactionError
    rescue Hive::DuplicateTransactionError
    puts "\tFailed: duplicate vote (duplicate transaction error)."
    voters -= [voter]
    next
    @@ -656,9 +656,9 @@ def vote(comment, wait_offset = 0)

    if replay > 0
    replay_threads << Thread.new do
    @api = Steem::CondenserApi.new(@chain_options)
    @block_api = Steem::BlockApi.new(@chain_options)
    @stream = Steem::Stream.new(@chain_options)
    @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
    @@ -692,8 +692,8 @@ def vote(comment, wait_offset = 0)
    end

    loop do
    @api = Steem::CondenserApi.new(@chain_options)
    @stream = Steem::Stream.new(@chain_options)
    @api = Hive::CondenserApi.new(@chain_options)
    @stream = Hive::Stream.new(@chain_options)
    op_idx = 0

    begin
    @@ -704,7 +704,7 @@ def vote(comment, wait_offset = 0)

    ctx = Redis.new(url: @meeseeker_options[:url])

    Redis.new(url: @meeseeker_options[:url]).subscribe('steem:op:comment') do |on|
    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
    @@ -734,6 +734,7 @@ def vote(comment, wait_offset = 0)
    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
    9 changes: 4 additions & 5 deletions drphil.yml
    Original file line number Diff line number Diff line change
    @@ -9,8 +9,8 @@ voting_rules:
    enable_comments: false
    only_first_posts: false
    only_fully_powered_up: false
    min_wait: 18
    max_wait: 30
    min_wait: 0
    max_wait: 0
    min_rep: 25.0
    max_rep: 99.9
    min_voting_power: 25.00 %
    @@ -19,7 +19,6 @@ voting_rules:

    voters:
    - social 5JrvPrQeBBvCRdjv29iDvkwn3EQYZ9jqfAHzrCyUvfbEbRkrYFC
    - bad.account 5XXXBadWifXXXdjv29iDvkwn3EQYZ9jqfAHzrCyUvfbEbRkrYFC

    favorite_accounts: inertia banjo
    skip_accounts: leeroy.jenkins the.masses danlarimer ned-reddit-login
    @@ -39,5 +38,5 @@ vote_signals:
    # :url: redis://127.0.0.1:6379/0

    :chain_options:
    :chain: steem
    :url: https://api.steemit.com
    :chain: hive
    :url: https://api.openhive.network
  4. inertia186 revised this gist Apr 13, 2020. 1 changed file with 4 additions and 5 deletions.
    9 changes: 4 additions & 5 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -586,8 +586,8 @@ def vote(comment, wait_offset = 0)
    vote_options = {
    app_base: false,
    database_api: @api,
    block_api: @block_api,
    network_broadcast_api: @network_broadcast_api,
    block_api: @api,
    network_broadcast_api: @api,
    wif: wif,
    params: params
    }
    @@ -656,9 +656,8 @@ def vote(comment, wait_offset = 0)

    if replay > 0
    replay_threads << Thread.new do
    @api = Steem::Api.new(@chain_options)
    @api = Steem::CondenserApi.new(@chain_options)
    @block_api = Steem::BlockApi.new(@chain_options)
    @network_broadcast_api = Steem::NetworkBroadcastApi.new(@chain_options)
    @stream = Steem::Stream.new(@chain_options)

    properties = @api.get_dynamic_global_properties.result
    @@ -693,7 +692,7 @@ def vote(comment, wait_offset = 0)
    end

    loop do
    @api = Steem::Api.new(@chain_options)
    @api = Steem::CondenserApi.new(@chain_options)
    @stream = Steem::Stream.new(@chain_options)
    op_idx = 0

  5. inertia186 revised this gist Apr 13, 2020. 1 changed file with 11 additions and 1 deletion.
    12 changes: 11 additions & 1 deletion drphil.rb
    Original file line number Diff line number Diff line change
    @@ -582,9 +582,18 @@ def vote(comment, wait_offset = 0)
    permlink: permlink,
    weight: weight
    }

    vote_options = {
    app_base: false,
    database_api: @api,
    block_api: @block_api,
    network_broadcast_api: @network_broadcast_api,
    wif: wif,
    params: params
    }

    begin
    Steem::Broadcast.vote(wif: wif, params: params) do |result|
    Steem::Broadcast.vote(vote_options) do |result|
    puts "\tSuccess: #{result.to_json}"
    votes_cast += 1

    @@ -649,6 +658,7 @@ def vote(comment, wait_offset = 0)
    replay_threads << Thread.new do
    @api = Steem::Api.new(@chain_options)
    @block_api = Steem::BlockApi.new(@chain_options)
    @network_broadcast_api = Steem::NetworkBroadcastApi.new(@chain_options)
    @stream = Steem::Stream.new(@chain_options)

    properties = @api.get_dynamic_global_properties.result
  6. inertia186 revised this gist Jan 29, 2019. 5 changed files with 143 additions and 162 deletions.
    9 changes: 8 additions & 1 deletion Gemfile
    Original file line number Diff line number Diff line change
    @@ -1,2 +1,9 @@
    source 'https://rubygems.org'
    gem 'radiator'

    gem 'steem-ruby', require: 'steem'
    gem 'redis'

    # Mostly for troubleshooting:

    # gem 'pry'
    # gem 'rb-readline'
    30 changes: 15 additions & 15 deletions Gemfile.lock
    Original file line number Diff line number Diff line change
    @@ -1,33 +1,33 @@
    GEM
    remote: https://rubygems.org/
    specs:
    awesome_print (1.8.0)
    base58 (0.2.3)
    bindata (2.4.4)
    bitcoin-ruby (0.0.18)
    connection_pool (2.2.1)
    ffi (1.9.23)
    hashie (3.5.7)
    ffi (1.10.0)
    hashie (3.6.0)
    json (2.1.0)
    little-plugger (1.1.4)
    logging (2.2.2)
    little-plugger (~> 1.1)
    multi_json (~> 1.10)
    multi_json (1.13.1)
    net-http-persistent (3.0.0)
    connection_pool (~> 2.2)
    radiator (0.4.1)
    awesome_print (~> 1.7, >= 1.7.0)
    bitcoin-ruby (~> 0.0, >= 0.0.11)
    ffi (~> 1.9, >= 1.9.18)
    hashie (~> 3.5, >= 3.5.5)
    json (~> 2.0, >= 2.0.2)
    redis (4.1.0)
    steem-ruby (0.9.4)
    base58 (~> 0.2, >= 0.2.3)
    bindata (~> 2.4, >= 2.4.4)
    bitcoin-ruby (~> 0.0, >= 0.0.18)
    ffi (~> 1.9, >= 1.9.23)
    hashie (~> 3.5, >= 3.5.7)
    json (~> 2.1, >= 2.1.0)
    logging (~> 2.2, >= 2.2.0)
    net-http-persistent (>= 2.5.2)

    PLATFORMS
    ruby

    DEPENDENCIES
    radiator
    redis
    steem-ruby

    BUNDLED WITH
    1.16.1
    1.16.5
    35 changes: 24 additions & 11 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,14 +1,12 @@
    * Title: drphil.rb - Voting Bot
    * Tags: radiator ruby steem steemdev curation
    * Tags: utopian-io development ruby steem curation
    * Notes:

    #### New Features

    * Bug/console output fixes.
    * Improved `unique_author` logic to use `account_history` instead of internal timer for improved accuracy between runs.
    * Gem update.
    * Additional error handling.
    * Handling bad json_metadata.
    * Switched to [`steem-ruby`](https://github.com/steemit/steem-ruby)
    * Added optional support for [`meeseeker`](https://github.com/inertia186/meeseeker).

    #### Features

    @@ -63,20 +61,21 @@
    * Formatted as: account:weight (e.g.: `inertia:100.00`)
    * Now 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 new thread, thus saving memory.
    * Argument called `stream:false` will exit without streaming the blockchain. Useful in situations where you only want to `replay:` and exit.
    * Use `account_history` instead of previous internal timer for improved accuracy between runs.

    #### Overview

    Dr. Phil (`drphil.rb`) is reimplementation of the "Winfrey" voting bot specification. The goal is to give everyone an upvote.

    One optional improvement is that instead of voting 1% by 100 accounts like the Winfrey bot spec, this script can vote 100% with 1 randomly chosen account.

    If the complaint about Winfrey is blockchain bloat, Dr. Phil prescribes weight loss to address this. But this feature would only work if there are enough voters defined in the script. If you plan to use this script for one or two accounts, you'll probably want to adjust the `VOTE_WEIGHT` constant to something a bit lower.
    If the complaint about Winfrey is blockchain bloat, Dr. Phil prescribes weight loss to address this. But this feature would only work if there are enough voters defined in the script. If you plan to use this script for one or two accounts, you'll probably want to adjust the `vote_weight` constant to something a bit lower.

    ---

    #### Install

    To use this [Radiator](https://steemit.com/steem/@inertia/radiator-steem-ruby-api-client) bot:
    To use this ~~[Radiator](https://steemit.com/steem/@inertia/radiator-steem-ruby-api-client)~~ [steem-ruby](https://github.com/steemit/steem-ruby) bot:

    ##### Linux

    @@ -113,10 +112,23 @@ $ ruby drphil.rb

    Dr. Phil will now do it's thing. Check here to see an updated version of this bot:

    https://gist.github.com/inertia186/61bcc2b821aa5acb24f7fc88921950c7
    Repository: https://gist.github.com/inertia186/61bcc2b821aa5acb24f7fc88921950c7

    ---

    #### Meeseeker Support

    If you are running a `meeseeker sync`, you can enable this option for streaming by uncommenting the `:meeseeker:` config group of `drphil.yml`. If the `meeseeker sync` is running on another host, update the `:url:` line with the correct address.

    See: https://github.com/inertia186/meeseeker

    Example:

    ```yaml
    :meeseeker_options:
    :url: redis://127.0.0.1:6379/0
    ```
    #### Upgrade
    Typically, you can upgrade to the latest version by this command, from the original directory you cloned into:
    @@ -163,17 +175,18 @@ Make sure the `.yml` file `voter` items have the account name, followed by a spa

    Is there a list of nodes?

    ##### Solution: Yes, special thanks to @ripplerm.
    ##### Solution: Yes, special thanks to @jamzed.

    https://ripplerm.github.io/steem-servers/
    https://geo.steem.pl/

    ---

    <center>
    <img src="http://i.imgur.com/qUZYLiQ.png" />
    </center>

    See my previous Ruby How To posts in: [/f/ruby](https://chainbb.com/f/ruby)
    See some of my previous Ruby How To posts in: [#radiator](https://steemit.com/created/radiator) [#ruby](https://steemit.com/created/ruby)


    ## Get in touch!

    223 changes: 88 additions & 135 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -123,6 +123,8 @@ def parse_list(list)
    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'))
    @@ -158,12 +160,7 @@ def to_rep(raw)

    def poll_voting_power
    @semaphore.synchronize do
    @api.get_accounts(@voters.keys) do |accounts, error|
    if !!error
    puts "Unable to query voters #{@voters.keys.join(', ')}: #{error}"
    return
    end

    @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')
    @@ -258,12 +255,7 @@ def voted_for_authors

    @semaphore.synchronize do
    @voters.keys.each do |voter|
    @api.get_account_history(voter, -limit, limit) do |result, error|
    if !!error
    puts "Unable to get account hitory for #{voter}: #{error}"
    next
    end

    @api.get_account_history(voter, -limit, limit) do |result|
    result.reverse.each do |i, tx|
    op = tx['op']
    next unless op[0] == 'vote'
    @@ -324,9 +316,7 @@ def min_trending_rep(limit)
    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, error|
    raise error.message if !!error

    @api.get_discussions_by_trending(tag: '', limit: limit) do |trending|
    @min_trending_rep = trending.map do |c|
    c.author_reputation.to_i
    end.min
    @@ -353,12 +343,7 @@ def skip?(comment, voters)
    if !!@voting_rules.only_first_posts
    begin
    @semaphore.synchronize do
    @api.get_accounts([comment.author]) do |account, error|
    if !!error
    puts "Unable to find first post for #{comment.author}: #{error}"
    return true
    end

    @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
    @@ -416,7 +401,7 @@ def skip?(comment, voters)
    v.voter if v.percent < 0
    end.compact

    if (signal = downvoters & @flag_signals).any?
    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
    @@ -457,19 +442,10 @@ def following?(voter, author)
    if following.empty?
    until count == following.size
    count = following.size
    following_options = {
    account: voter,
    start: following.last,
    type: 'blog',
    limit: 100
    }
    following_options = [voter, following.last, 'blog', 100]

    @follow_api.get_following(following_options) do |result, error|
    if !!error
    puts "Unable to get follows for #{voter}: #{error}"
    end

    following += result.map(&:following)
    @api.get_following(*following_options) do |result|
    following += result.map{ |f| f['following'] } rescue []
    following = following.uniq
    end
    end
    @@ -490,19 +466,10 @@ def follower?(voter, author)
    if followers.empty?
    until count == followers.size
    count = followers.size
    followers_options = {
    account: voter,
    start: followers.last,
    type: 'blog',
    limit: 100
    }
    followers_options = [voter, followers.last, 'blog', 100]

    @follow_api.get_followers(followers_options) do |result, error|
    if !!error
    puts "Unable to get followers for #{voter}: #{error}"
    end

    followers += result.map(&:follower)
    @api.get_followers(*followers_options) do |result|
    followers += result.map{ |f| f['follower'] } rescue []
    followers = followers.uniq
    end
    end
    @@ -555,12 +522,7 @@ def vote(comment, wait_offset = 0)
    end

    @threads[slug] = Thread.new do
    comment = @api.get_content(comment.author, comment.permlink) do |comment, error|
    if !!error
    puts error.message
    return
    end

    comment = @api.get_content(comment.author, comment.permlink) do |comment|
    comment
    end

    @@ -570,7 +532,7 @@ def vote(comment, wait_offset = 0)
    @voters.keys
    end - voters_recharging

    return if skip?(comment, voters)
    Thread.exit if skip?(comment, voters)

    if wait_offset == 0
    timestamp = Time.parse(comment.created + ' Z')
    @@ -582,12 +544,8 @@ def vote(comment, 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, error|
    if !!error
    puts "Unable to get comment @#{comment.author}/#{comment.permlink}: #{error}"
    end

    return if skip?(comment, voters)
    @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}"
    @@ -615,77 +573,55 @@ def vote(comment, wait_offset = 0)
    end
    end

    wif = @voters[voter]
    tx = Radiator::Transaction.new(@chain_options.merge(wif: wif))

    puts "#{voter} voting for #{slug}"

    vote = {
    type: :vote,
    wif = @voters[voter]
    params = {
    voter: voter,
    author: author,
    permlink: permlink,
    weight: weight
    }

    op = Radiator::Operation.new(vote)
    tx.operations << op
    response = tx.process(true)

    if !!response.error
    message = response.error.message
    if message.to_s =~ /You have already voted in a similar way./
    puts "\tFailed: duplicate vote."
    voters -= [voter]
    next
    elsif message.to_s =~ /Can only vote once every 3 seconds./
    if winfrey? || voters.size == 1
    puts "\tRetrying: voting too quickly."
    sleep 3
    else
    puts "\tSkipped: voting too quickly."
    voters -= [voter]
    begin
    Steem::Broadcast.vote(wif: wif, params: params) 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

    next
    elsif message.to_s =~ /Voting weight is too small, please accumulate more voting power or steem power./
    puts "\tFailed: voting weight too small"
    voters -= [voter]
    next
    elsif message.to_s =~ /STEEMIT_UPVOTE_LOCKOUT_HF17/
    puts "\tFailed: upvote lockout (last twelve hours before payout)"
    break
    elsif message.to_s =~ /tapos_block_summary/
    puts "Retrying: tapos_block_summary (?)"
    redo
    elsif message.to_s =~ /now < trx.expiration/
    puts "Retrying: now < trx.expiration (?)"
    redo
    elsif message.to_s =~ /signature is not canonical/
    puts "\tRetrying: signature was not canonical (bug in Radiator?)"
    redo
    # The drphil mode only votes with one key per post.
    #break
    end
    raise message
    end

    puts "\tSuccess: #{response.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
    rescue Steem::UnknownError => e
    if e.to_s =~ /Your current vote on this comment is identical to this vote./
    puts "\tFailed: duplicate vote."
    voters -= [voter]
    next
    else
    puts "Max votes per post reached."
    break
    end

    puts "Unhandled error: #{e}"
    next
    rescue Steem::DuplicateTransactionError
    puts "\tFailed: duplicate vote (duplicate transaction error)."
    voters -= [voter]
    next
    rescue => e
    puts e.inspect
    voters -= [voter]
    next
    end

    # The drphil mode only votes with one key per post.
    break
    rescue => e
    puts "Pausing #{backoff} :: Unable to vote with #{voter}. #{e}"
    voters -= [voter]
    @@ -711,17 +647,17 @@ def vote(comment, wait_offset = 0)

    if replay > 0
    replay_threads << Thread.new do
    @api = Radiator::Api.new(@chain_options)
    @follow_api = Radiator::FollowApi.new(@chain_options)
    @stream = Radiator::Stream.new(@chain_options)
    @api = Steem::Api.new(@chain_options)
    @block_api = Steem::BlockApi.new(@chain_options)
    @stream = Steem::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} ..."

    @api.get_blocks(block_number..last_irreversible_block_num) do |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')
    @@ -730,12 +666,12 @@ def vote(comment, wait_offset = 0)

    block.transactions.each do |tx|
    tx.operations.each do |type, op|
    vote(op, elapsed.to_i) if type == 'comment' && may_vote?(op)
    vote(op, elapsed.to_i) if type == 'comment_operation' && may_vote?(op)
    end
    end
    end

    sleep 3
    # sleep 3
    puts "Done replaying."
    end
    end
    @@ -746,31 +682,48 @@ def vote(comment, wait_offset = 0)
    exit
    end

    puts "Now waiting for new posts."

    loop do
    @api = Radiator::Api.new(@chain_options)
    @follow_api = Radiator::FollowApi.new(@chain_options)
    @stream = Radiator::Stream.new(@chain_options)
    @api = Steem::Api.new(@chain_options)
    @stream = Steem::Stream.new(@chain_options)
    op_idx = 0

    begin
    puts summary_voting_power

    @stream.operations(:comment) do |comment|
    next unless may_vote? comment
    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('steem: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

    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

    puts "Recharging vote power (currently too low: #{('%.3f' % vp)} %)"
    vote(comment)
    puts summary_voting_power
    end

    vote(comment)
    puts summary_voting_power
    end
    rescue => e
    @api.shutdown
    puts "Unable to stream on current node. Retrying in 5 seconds. Error: #{e}"
    sleep 5
    end
    8 changes: 8 additions & 0 deletions drphil.yml
    Original file line number Diff line number Diff line change
    @@ -30,6 +30,14 @@ skip_tags: nsfw test
    flag_signals: cheetah steemcleaners
    vote_signals:

    # If you are running a meeseeker sync, you can enable that for streaming by
    # uncommenting this group. If the meeseeker sync is running on another host,
    # update the :url: line with the correct address.
    #
    # See: https://github.com/inertia186/meeseeker
    # :meeseeker_options:
    # :url: redis://127.0.0.1:6379/0

    :chain_options:
    :chain: steem
    :url: https://api.steemit.com
  7. inertia186 revised this gist May 10, 2018. 2 changed files with 21 additions and 5 deletions.
    6 changes: 3 additions & 3 deletions Gemfile.lock
    Original file line number Diff line number Diff line change
    @@ -2,9 +2,9 @@ GEM
    remote: https://rubygems.org/
    specs:
    awesome_print (1.8.0)
    bitcoin-ruby (0.0.17)
    bitcoin-ruby (0.0.18)
    connection_pool (2.2.1)
    ffi (1.9.21)
    ffi (1.9.23)
    hashie (3.5.7)
    json (2.1.0)
    little-plugger (1.1.4)
    @@ -14,7 +14,7 @@ GEM
    multi_json (1.13.1)
    net-http-persistent (3.0.0)
    connection_pool (~> 2.2)
    radiator (0.3.15)
    radiator (0.4.1)
    awesome_print (~> 1.7, >= 1.7.0)
    bitcoin-ruby (~> 0.0, >= 0.0.11)
    ffi (~> 1.9, >= 1.9.18)
    20 changes: 18 additions & 2 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -10,6 +10,8 @@

    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

    @@ -455,7 +457,14 @@ def following?(voter, author)
    if following.empty?
    until count == following.size
    count = following.size
    @follow_api.get_following(voter, following.last, 'blog', 100) do |result, error|
    following_options = {
    account: voter,
    start: following.last,
    type: 'blog',
    limit: 100
    }

    @follow_api.get_following(following_options) do |result, error|
    if !!error
    puts "Unable to get follows for #{voter}: #{error}"
    end
    @@ -481,7 +490,14 @@ def follower?(voter, author)
    if followers.empty?
    until count == followers.size
    count = followers.size
    @follow_api.get_followers(voter, followers.last, 'blog', 100) do |result, error|
    followers_options = {
    account: voter,
    start: followers.last,
    type: 'blog',
    limit: 100
    }

    @follow_api.get_followers(followers_options) do |result, error|
    if !!error
    puts "Unable to get followers for #{voter}: #{error}"
    end
  8. Anthony Martin revised this gist Feb 17, 2018. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions Gemfile.lock
    Original file line number Diff line number Diff line change
    @@ -2,16 +2,16 @@ GEM
    remote: https://rubygems.org/
    specs:
    awesome_print (1.8.0)
    bitcoin-ruby (0.0.13)
    bitcoin-ruby (0.0.17)
    connection_pool (2.2.1)
    ffi (1.9.18)
    hashie (3.5.6)
    ffi (1.9.21)
    hashie (3.5.7)
    json (2.1.0)
    little-plugger (1.1.4)
    logging (2.2.2)
    little-plugger (~> 1.1)
    multi_json (~> 1.10)
    multi_json (1.12.2)
    multi_json (1.13.1)
    net-http-persistent (3.0.0)
    connection_pool (~> 2.2)
    radiator (0.3.15)
    @@ -30,4 +30,4 @@ DEPENDENCIES
    radiator

    BUNDLED WITH
    1.16.0.pre.3
    1.16.1
  9. Anthony Martin revised this gist Feb 17, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -233,7 +233,7 @@ def only_tags_intersection?(json_metadata)

    def skip_app?(json_metadata)
    metadata = JSON[json_metadata || '{}'] rescue {}
    app = metadata['app'].to_s.split('/').first
    app = metadata['app'].to_s.split('/').first rescue 'unknown'

    @skip_apps.include? app
    end
    @@ -242,7 +242,7 @@ def only_app?(json_metadata)
    return true if @only_apps.none?

    metadata = JSON[json_metadata || '{}'] rescue {}
    app = metadata['app'].to_s.split('/').first
    app = metadata['app'].to_s.split('/').first rescue 'unknown'

    @only_apps.include? app
    end
  10. inertia186 revised this gist Dec 12, 2017. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions drphil.golos.yml
    Original file line number Diff line number Diff line change
    @@ -28,6 +28,6 @@ skip_tags: nsfw test ru--mat bm-open
    flag_signals: cheetah kulturagolosa
    vote_signals:

    chain_options:
    chain: golos
    url: https://ws.golos.io
    :chain_options:
    :chain: golos
    :url: https://ws.golos.io
  11. inertia186 revised this gist Dec 11, 2017. 1 changed file with 7 additions and 2 deletions.
    9 changes: 7 additions & 2 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,11 @@

    #### New Features

    * Argument called `stream:false` will exit without streaming the blockchain. Useful in situations where you only want to `replay:` and exit.
    * Bug/console output fixes.
    * Improved `unique_author` logic to use `account_history` instead of internal timer for improved accuracy between runs.
    * Gem update.
    * Additional error handling.
    * Handling bad json_metadata.

    #### Features

    @@ -58,6 +62,7 @@
    * Favorites (`favorite_accounts`) can now have individual vote percent.
    * Formatted as: account:weight (e.g.: `inertia:100.00`)
    * Now 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 new thread, thus saving memory.
    * Argument called `stream:false` will exit without streaming the blockchain. Useful in situations where you only want to `replay:` and exit.

    #### Overview

    @@ -168,7 +173,7 @@ https://ripplerm.github.io/steem-servers/
    <img src="http://i.imgur.com/qUZYLiQ.png" />
    </center>

    See my previous Ruby How To posts in: [#radiator](https://steemit.com/created/radiator) [#ruby](https://steemit.com/created/ruby)
    See my previous Ruby How To posts in: [/f/ruby](https://chainbb.com/f/ruby)

    ## Get in touch!

  12. inertia186 revised this gist Dec 11, 2017. 2 changed files with 186 additions and 162 deletions.
    342 changes: 183 additions & 159 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    # 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://steemit.com/radiator/@inertia/drphil-rb-voting-bot

    require 'rubygems'
    @@ -29,27 +29,27 @@ 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
    @@ -59,11 +59,11 @@ 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(' ')
    @@ -112,7 +112,7 @@ def parse_list(list)
    @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
    @@ -121,11 +121,9 @@ def parse_list(list)
    account.split(':').first
    end

    @options = {
    chain: @config['chain_options']['chain'].to_sym,
    url: @config['chain_options']['url'],
    logger: Logger.new(__FILE__.sub(/\.rb$/, '.log'))
    }
    @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
    @@ -158,50 +156,54 @@ def to_rep(raw)

    def poll_voting_power
    @semaphore.synchronize do
    response = @api.get_accounts(@voters.keys)
    accounts = response.result

    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
    @api.get_accounts(@voters.keys) do |accounts, error|
    if !!error
    puts "Unable to query voters #{@voters.keys.join(', ')}: #{error}"
    return
    end

    if wasted_voting_power > 0
    puts "\t#{account.name} wasted voting power: #{('%.2f' % wasted_voting_power)} %"
    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

    @voting_power[account.name] = current_voting_power
    @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

    @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

    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

    @@ -215,33 +217,33 @@ 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

    @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

    @only_apps.include? app
    end

    @@ -251,42 +253,47 @@ def voted_for_authors
    else
    300
    end

    @semaphore.synchronize do
    @voters.keys.each do |voter|
    response = @api.get_account_history(voter, -limit, limit)
    result = response.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']]
    @api.get_account_history(voter, -limit, limit) do |result, error|
    if !!error
    puts "Unable to get account hitory for #{voter}: #{error}"
    next
    end

    if latest.nil? || latest < timestamp
    @voted_for_authors[op[1]['author']] = timestamp
    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

    @@ -298,7 +305,7 @@ def may_vote?(comment)
    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.
    @@ -314,22 +321,22 @@ def min_trending_rep(limit)
    @semaphore.synchronize do
    if @min_trending_rep.nil? || Random.rand(0..limit) == 13
    puts "Looking up trending up to #{limit} posts."
    response = @api.get_discussions_by_trending(tag: '', limit: limit)
    raise response.error.message if !!response.error
    trending = response.result
    @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))}"

    @api.get_discussions_by_trending(tag: '', limit: limit) do |trending, error|
    raise error.message if !!error

    @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

    @@ -340,45 +347,49 @@ def skip?(comment, voters)
    return true
    end
    end

    if !!@voting_rules.only_first_posts
    begin
    @semaphore.synchronize do
    response = @api.get_accounts([comment.author])
    account = response.result.last

    if account.post_count > 1
    puts "Skipped, not first post:\n\t@#{comment.author}/#{comment.permlink}"
    return true
    @api.get_accounts([comment.author]) do |account, error|
    if !!error
    puts "Unable to find first post for #{comment.author}: #{error}"
    return true
    end

    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_steem_dollars == 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}"
    @@ -391,69 +402,74 @@ def skip?(comment, voters)
    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 (signal = 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
    response = @follow_api.get_following(voter, following.last, 'blog', 100)
    following += response.result.map(&:following)
    following = following.uniq
    @follow_api.get_following(voter, following.last, 'blog', 100) do |result, error|
    if !!error
    puts "Unable to get follows for #{voter}: #{error}"
    end

    following += result.map(&:following)
    following = following.uniq
    end
    end

    @voters_following[voter] = following
    end

    @voters_following[voter] = nil if Random.rand(0..999) == 13

    following.include? author
    end

    @@ -465,16 +481,21 @@ def follower?(voter, author)
    if followers.empty?
    until count == followers.size
    count = followers.size
    response = @follow_api.get_followers(voter, followers.last, 'blog', 100)
    followers += response.result.map(&:follower)
    followers = followers.uniq
    @follow_api.get_followers(voter, followers.last, 'blog', 100) do |result, error|
    if !!error
    puts "Unable to get followers for #{voter}: #{error}"
    end

    followers += result.map(&:follower)
    followers = followers.uniq
    end
    end

    @voters_followers[voter] = nil if Random.rand(0..999) == 13

    @voters_followers[voter] = followers
    end

    followers.include? author
    end

    @@ -500,98 +521,101 @@ 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
    response = @api.get_content(comment.author, comment.permlink)

    if !!response.error
    puts response.error.message
    return
    comment = @api.get_content(comment.author, comment.permlink) do |comment, error|
    if !!error
    puts error.message
    return
    end

    comment
    end

    comment = response.result


    voters = if winfrey?
    @voters.keys - comment.active_votes.map(&:voter) - voters_recharging
    else
    @voters.keys
    end - voters_recharging

    return 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

    response = @api.get_content(comment.author, comment.permlink)
    comment = response.result

    return if skip?(comment, voters)

    @api.get_content(comment.author, comment.permlink) do |comment, error|
    if !!error
    puts "Unable to get comment @#{comment.author}/#{comment.permlink}: #{error}"
    end

    return 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

    wif = @voters[voter]
    tx = Radiator::Transaction.new(@options.dup.merge(wif: wif))
    tx = Radiator::Transaction.new(@chain_options.merge(wif: wif))

    puts "#{voter} voting for #{slug}"

    vote = {
    type: :vote,
    voter: voter,
    author: author,
    permlink: permlink,
    weight: weight
    }

    op = Radiator::Operation.new(vote)
    tx.operations << op
    response = tx.process(true)

    if !!response.error
    message = response.error.message
    if message.to_s =~ /You have already voted in a similar way./
    @@ -606,7 +630,7 @@ def vote(comment, wait_offset = 0)
    puts "\tSkipped: voting too quickly."
    voters -= [voter]
    end

    next
    elsif message.to_s =~ /Voting weight is too small, please accumulate more voting power or steem power./
    puts "\tFailed: voting weight too small"
    @@ -627,14 +651,14 @@ def vote(comment, wait_offset = 0)
    end
    raise message
    end

    puts "\tSuccess: #{response.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
    @@ -643,7 +667,7 @@ def vote(comment, wait_offset = 0)
    break
    end
    end

    # The drphil mode only votes with one key per post.
    break
    rescue => e
    @@ -659,7 +683,7 @@ def vote(comment, wait_offset = 0)
    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
    @@ -671,30 +695,30 @@ def vote(comment, wait_offset = 0)

    if replay > 0
    replay_threads << Thread.new do
    @api = Radiator::Api.new(@options.dup)
    @follow_api = Radiator::FollowApi.new(@options.dup)
    @stream = Radiator::Stream.new(@options.dup)
    @api = Radiator::Api.new(@chain_options)
    @follow_api = Radiator::FollowApi.new(@chain_options)
    @stream = Radiator::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} ..."

    @api.get_blocks(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' && may_vote?(op)
    end
    end
    end

    sleep 3
    puts "Done replaying."
    end
    @@ -709,23 +733,23 @@ def vote(comment, wait_offset = 0)
    puts "Now waiting for new posts."

    loop do
    @api = Radiator::Api.new(@options.dup)
    @follow_api = Radiator::FollowApi.new(@options.dup)
    @stream = Radiator::Stream.new(@options.dup)
    @api = Radiator::Api.new(@chain_options)
    @follow_api = Radiator::FollowApi.new(@chain_options)
    @stream = Radiator::Stream.new(@chain_options)
    op_idx = 0

    begin
    puts summary_voting_power

    @stream.operations(:comment) do |comment|
    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
    6 changes: 3 additions & 3 deletions drphil.yml
    Original file line number Diff line number Diff line change
    @@ -30,6 +30,6 @@ skip_tags: nsfw test
    flag_signals: cheetah steemcleaners
    vote_signals:

    chain_options:
    chain: steem
    url: https://steemd.steemit.com
    :chain_options:
    :chain: steem
    :url: https://api.steemit.com
  13. inertia186 revised this gist Dec 11, 2017. 3 changed files with 13 additions and 19 deletions.
    1 change: 0 additions & 1 deletion Gemfile
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,2 @@
    source 'https://rubygems.org'
    gem 'radiator'
    gem 'pry'
    30 changes: 13 additions & 17 deletions Gemfile.lock
    Original file line number Diff line number Diff line change
    @@ -1,37 +1,33 @@
    GEM
    remote: https://rubygems.org/
    specs:
    bitcoin-ruby (0.0.11)
    coderay (1.1.1)
    awesome_print (1.8.0)
    bitcoin-ruby (0.0.13)
    connection_pool (2.2.1)
    ffi (1.9.18)
    hashie (3.5.6)
    json (1.8.6)
    json (2.1.0)
    little-plugger (1.1.4)
    logging (2.2.2)
    little-plugger (~> 1.1)
    multi_json (~> 1.10)
    method_source (0.8.2)
    multi_json (1.12.2)
    net-http-persistent (2.9.4)
    pry (0.10.4)
    coderay (~> 1.1.0)
    method_source (~> 0.8.1)
    slop (~> 3.4)
    radiator (0.2.3)
    bitcoin-ruby (= 0.0.11)
    ffi (= 1.9.18)
    net-http-persistent (3.0.0)
    connection_pool (~> 2.2)
    radiator (0.3.15)
    awesome_print (~> 1.7, >= 1.7.0)
    bitcoin-ruby (~> 0.0, >= 0.0.11)
    ffi (~> 1.9, >= 1.9.18)
    hashie (~> 3.5, >= 3.5.5)
    json (~> 1.8, >= 1.8.6)
    json (~> 2.0, >= 2.0.2)
    logging (~> 2.2, >= 2.2.0)
    net-http-persistent (~> 2.9, >= 2.9.4)
    slop (3.6.0)
    net-http-persistent (>= 2.5.2)

    PLATFORMS
    ruby

    DEPENDENCIES
    pry
    radiator

    BUNDLED WITH
    1.15.4
    1.16.0.pre.3
    1 change: 0 additions & 1 deletion drphil.rb
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,6 @@
    require 'rubygems'
    require 'bundler/setup'
    require 'yaml'
    require 'pry'

    Bundler.require

  14. inertia186 revised this gist Oct 12, 2017. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -213,7 +213,7 @@ def voters_recharging
    end

    def skip_tags_intersection?(json_metadata)
    metadata = JSON[json_metadata || '{}']
    metadata = JSON[json_metadata || '{}'] rescue {}
    tags = metadata['tags'] || [] rescue []
    tags = [tags].flatten

    @@ -223,15 +223,15 @@ def skip_tags_intersection?(json_metadata)
    def only_tags_intersection?(json_metadata)
    return true if @only_tags.none? # not set, assume all tags intersect

    metadata = JSON[json_metadata || '{}']
    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 || '{}']
    metadata = JSON[json_metadata || '{}'] rescue {}
    app = metadata['app'].to_s.split('/').first

    @skip_apps.include? app
    @@ -240,7 +240,7 @@ def skip_app?(json_metadata)
    def only_app?(json_metadata)
    return true if @only_apps.none?

    metadata = JSON[json_metadata || '{}']
    metadata = JSON[json_metadata || '{}'] rescue {}
    app = metadata['app'].to_s.split('/').first

    @only_apps.include? app
  15. inertia186 revised this gist Oct 4, 2017. 2 changed files with 7 additions and 7 deletions.
    10 changes: 5 additions & 5 deletions Gemfile.lock
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    GEM
    remote: https://rubygems.org/
    specs:
    bitcoin-ruby (0.0.10)
    bitcoin-ruby (0.0.11)
    coderay (1.1.1)
    ffi (1.9.18)
    hashie (3.5.6)
    @@ -11,14 +11,14 @@ GEM
    little-plugger (~> 1.1)
    multi_json (~> 1.10)
    method_source (0.8.2)
    multi_json (1.12.1)
    multi_json (1.12.2)
    net-http-persistent (2.9.4)
    pry (0.10.4)
    coderay (~> 1.1.0)
    method_source (~> 0.8.1)
    slop (~> 3.4)
    radiator (0.2.2)
    bitcoin-ruby (= 0.0.10)
    radiator (0.2.3)
    bitcoin-ruby (= 0.0.11)
    ffi (= 1.9.18)
    hashie (~> 3.5, >= 3.5.5)
    json (~> 1.8, >= 1.8.6)
    @@ -34,4 +34,4 @@ DEPENDENCIES
    radiator

    BUNDLED WITH
    1.14.6
    1.15.4
    4 changes: 2 additions & 2 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -617,10 +617,10 @@ def vote(comment, wait_offset = 0)
    puts "\tFailed: upvote lockout (last twelve hours before payout)"
    break
    elsif message.to_s =~ /tapos_block_summary/
    warning "Retrying: tapos_block_summary (?)"
    puts "Retrying: tapos_block_summary (?)"
    redo
    elsif message.to_s =~ /now < trx.expiration/
    warning "Retrying: now < trx.expiration (?)"
    puts "Retrying: now < trx.expiration (?)"
    redo
    elsif message.to_s =~ /signature is not canonical/
    puts "\tRetrying: signature was not canonical (bug in Radiator?)"
  16. inertia186 revised this gist Oct 4, 2017. 2 changed files with 7 additions and 7 deletions.
    10 changes: 5 additions & 5 deletions Gemfile.lock
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    GEM
    remote: https://rubygems.org/
    specs:
    bitcoin-ruby (0.0.10)
    bitcoin-ruby (0.0.11)
    coderay (1.1.1)
    ffi (1.9.18)
    hashie (3.5.6)
    @@ -11,14 +11,14 @@ GEM
    little-plugger (~> 1.1)
    multi_json (~> 1.10)
    method_source (0.8.2)
    multi_json (1.12.1)
    multi_json (1.12.2)
    net-http-persistent (2.9.4)
    pry (0.10.4)
    coderay (~> 1.1.0)
    method_source (~> 0.8.1)
    slop (~> 3.4)
    radiator (0.2.2)
    bitcoin-ruby (= 0.0.10)
    radiator (0.2.3)
    bitcoin-ruby (= 0.0.11)
    ffi (= 1.9.18)
    hashie (~> 3.5, >= 3.5.5)
    json (~> 1.8, >= 1.8.6)
    @@ -34,4 +34,4 @@ DEPENDENCIES
    radiator

    BUNDLED WITH
    1.14.6
    1.15.4
    4 changes: 2 additions & 2 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -579,10 +579,10 @@ def vote(comment, wait_offset = 0)
    puts "\tFailed: upvote lockout (last twelve hours before payout)"
    break
    elsif message.to_s =~ /tapos_block_summary/
    warning "Retrying: tapos_block_summary (?)"
    puts "Retrying: tapos_block_summary (?)"
    redo
    elsif message.to_s =~ /now < trx.expiration/
    warning "Retrying: now < trx.expiration (?)"
    puts "Retrying: now < trx.expiration (?)"
    redo
    elsif message.to_s =~ /signature is not canonical/
    puts "\tRetrying: signature was not canonical (bug in Radiator?)"
  17. inertia186 revised this gist Aug 23, 2017. 1 changed file with 37 additions and 7 deletions.
    44 changes: 37 additions & 7 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -246,16 +246,47 @@ def only_app?(json_metadata)
    @only_apps.include? app
    end

    def already_voted_for?(author)
    return false if @voting_rules.unique_author.nil?
    def voted_for_authors
    limit = if @voted_for_authors.empty?
    10000
    else
    300
    end

    @semaphore.synchronize do
    @voters.keys.each do |voter|
    response = @api.get_account_history(voter, -limit, limit)
    result = response.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

    @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 Time.now.utc - vote_at > @voting_rules.unique_author
    @voted_for_authors[author] = nil
    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_for_authors.keys.include? author
    return true if voted_in_threshold.include? author

    false
    end
    @@ -599,7 +630,6 @@ def vote(comment, wait_offset = 0)
    end

    puts "\tSuccess: #{response.result.to_json}"
    @voted_for_authors[author] = Time.now.utc
    votes_cast += 1

    if winfrey?
  18. inertia186 revised this gist Aug 23, 2017. 1 changed file with 6 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion drphil.rb
    Original file line number Diff line number Diff line change
    @@ -167,7 +167,12 @@ def poll_voting_power
    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)
    current_voting_power = [10000, current_voting_power].min.to_i * 100
    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
  19. inertia186 revised this gist Aug 16, 2017. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion drphil.rb
    Original file line number Diff line number Diff line change
    @@ -245,7 +245,9 @@ def already_voted_for?(author)
    return false if @voting_rules.unique_author.nil?

    @voted_for_authors.each do |author, vote_at|
    Time.now.utc - vote_at < @voting_rules.unique_author or @voted_for_authors[author] = nil
    if Time.now.utc - vote_at > @voting_rules.unique_author
    @voted_for_authors[author] = nil
    end
    end

    return true if @voted_for_authors.keys.include? author
  20. inertia186 revised this gist Aug 10, 2017. 2 changed files with 19 additions and 8 deletions.
    15 changes: 8 additions & 7 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -4,13 +4,7 @@

    #### New Features

    * Added `unique_author` (optional) which takes an integer in minutes. This will limit voting to 1 vote per period. E.g.: Set it to 1440 to only vote for each author once a day.
    * Added `max_votes_per_post` (optional) which only votes *n* times per post (`winfrey` mode only).
    * Added `only_tags` (optional) which only votes on posts that include these tags.
    * Alternative voting weights all inherit from `vote_weight` if not present.
    * Favorites (`favorite_accounts`) can now have individual vote percent.
    * Formatted as: account:weight (e.g.: `inertia:100.00`)
    * Now 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 new thread, thus saving memory.
    * Argument called `stream:false` will exit without streaming the blockchain. Useful in situations where you only want to `replay:` and exit.

    #### Features

    @@ -57,6 +51,13 @@
    * Now streaming on Last Irreversible Block Number, just to be fancy.
    * Now checking for new HF18 `cashout_time` value (if present).
    * This will skip voting when authors edit their old archived posts.
    * Added `unique_author` (optional) which takes an integer in minutes. This will limit voting to 1 vote per period. E.g.: Set it to 1440 to only vote for each author once a day.
    * Added `max_votes_per_post` (optional) which only votes *n* times per post (`winfrey` mode only).
    * Added `only_tags` (optional) which only votes on posts that include these tags.
    * Alternative voting weights all inherit from `vote_weight` if not present.
    * Favorites (`favorite_accounts`) can now have individual vote percent.
    * Formatted as: account:weight (e.g.: `inertia:100.00`)
    * Now 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 new thread, thus saving memory.

    #### Overview

    12 changes: 11 additions & 1 deletion drphil.rb
    Original file line number Diff line number Diff line change
    @@ -622,15 +622,19 @@ def vote(comment, wait_offset = 0)

    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
    Thread.new do
    replay_threads << Thread.new do
    @api = Radiator::Api.new(@options.dup)
    @follow_api = Radiator::FollowApi.new(@options.dup)
    @stream = Radiator::Stream.new(@options.dup)
    @@ -660,6 +664,12 @@ def vote(comment, wait_offset = 0)
    end
    end

    unless stream
    replay_threads.map(&:join)
    @threads.values.map(&:join)
    exit
    end

    puts "Now waiting for new posts."

    loop do
  21. inertia186 revised this gist Aug 4, 2017. 3 changed files with 13 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions Gemfile.lock
    Original file line number Diff line number Diff line change
    @@ -3,8 +3,8 @@ GEM
    specs:
    bitcoin-ruby (0.0.10)
    coderay (1.1.1)
    ffi (1.9.17)
    hashie (3.5.5)
    ffi (1.9.18)
    hashie (3.5.6)
    json (1.8.6)
    little-plugger (1.1.4)
    logging (2.2.2)
    @@ -17,9 +17,9 @@ GEM
    coderay (~> 1.1.0)
    method_source (~> 0.8.1)
    slop (~> 3.4)
    radiator (0.2.1)
    radiator (0.2.2)
    bitcoin-ruby (= 0.0.10)
    ffi (= 1.9.17)
    ffi (= 1.9.18)
    hashie (~> 3.5, >= 3.5.5)
    json (~> 1.8, >= 1.8.6)
    logging (~> 2.2, >= 2.2.0)
    3 changes: 3 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -10,6 +10,7 @@
    * Alternative voting weights all inherit from `vote_weight` if not present.
    * Favorites (`favorite_accounts`) can now have individual vote percent.
    * Formatted as: account:weight (e.g.: `inertia:100.00`)
    * Now 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 new thread, thus saving memory.

    #### Features

    @@ -74,7 +75,9 @@ To use this [Radiator](https://steemit.com/steem/@inertia/radiator-steem-ruby-ap
    ##### Linux

    ```bash
    $ sudo apt-get update
    $ sudo apt-get install ruby-full git openssl libssl1.0.0 libssl-dev
    $ sudo apt-get upgrade
    $ gem install bundler
    ```

    6 changes: 6 additions & 0 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -578,6 +578,12 @@ def vote(comment, wait_offset = 0)
    elsif message.to_s =~ /STEEMIT_UPVOTE_LOCKOUT_HF17/
    puts "\tFailed: upvote lockout (last twelve hours before payout)"
    break
    elsif message.to_s =~ /tapos_block_summary/
    warning "Retrying: tapos_block_summary (?)"
    redo
    elsif message.to_s =~ /now < trx.expiration/
    warning "Retrying: now < trx.expiration (?)"
    redo
    elsif message.to_s =~ /signature is not canonical/
    puts "\tRetrying: signature was not canonical (bug in Radiator?)"
    redo
  22. inertia186 revised this gist Aug 3, 2017. 1 changed file with 8 additions and 1 deletion.
    9 changes: 8 additions & 1 deletion drphil.rb
    Original file line number Diff line number Diff line change
    @@ -262,7 +262,14 @@ def may_vote?(comment)
    return false if skip_app? comment.json_metadata
    return false unless only_app? comment.json_metadata

    true
    # 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)
  23. inertia186 revised this gist Aug 3, 2017. 1 changed file with 16 additions and 34 deletions.
    50 changes: 16 additions & 34 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -14,6 +14,11 @@
    # 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
    @@ -158,12 +163,18 @@ def poll_voting_power
    accounts = response.result

    accounts.each do |account|
    @voting_power[account.name] = account.voting_power
    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)
    current_voting_power = [10000, current_voting_power].min.to_i * 100

    @voting_power[account.name] = current_voting_power
    end

    @min_voting_power = accounts.map(&:voting_power).min
    @max_voting_power = accounts.map(&:voting_power).max
    @average_voting_power = accounts.map(&:voting_power).inject(:+) / accounts.size
    @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

    @@ -196,29 +207,6 @@ def voters_recharging
    end.compact
    end

    def voters_check_charging
    @semaphore.synchronize do
    return [] if (Time.now.utc.to_i - @voters_check_charging_at.to_i) < 300

    @voters_check_charging_at = Time.now.utc

    @voting_power.map do |voter, power|
    if power < @voting_rules.min_voting_power
    check_time = 4320 # TODO Make this dynamic based on effective voting power
    response = @api.get_account_votes(voter)
    votes = response.result
    latest_vote_at = if votes.any? && !!(time = votes.last.time)
    Time.parse(time + 'Z')
    end

    elapsed = Time.now.utc.to_i - latest_vote_at.to_i

    voter if elapsed > check_time
    end
    end.compact
    end
    end

    def skip_tags_intersection?(json_metadata)
    metadata = JSON[json_metadata || '{}']
    tags = metadata['tags'] || [] rescue []
    @@ -494,13 +482,12 @@ def vote(comment, wait_offset = 0)
    end

    comment = response.result
    check_charging = voters_check_charging

    voters = if winfrey?
    @voters.keys - comment.active_votes.map(&:voter) - voters_recharging
    else
    @voters.keys
    end - voters_recharging + check_charging
    end - voters_recharging

    return if skip?(comment, voters)

    @@ -542,11 +529,6 @@ def vote(comment, wait_offset = 0)
    else
    puts "Recharging vote power (currently too low: #{('%.3f' % vp)} %)"
    end

    unless check_charging.include? voter
    voters -= [voter]
    next
    end
    end

    wif = @voters[voter]
  24. inertia186 revised this gist May 26, 2017. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -222,6 +222,7 @@ def voters_check_charging
    def skip_tags_intersection?(json_metadata)
    metadata = JSON[json_metadata || '{}']
    tags = metadata['tags'] || [] rescue []
    tags = [tags].flatten

    (@skip_tags & tags).any?
    end
    @@ -231,6 +232,7 @@ def only_tags_intersection?(json_metadata)

    metadata = JSON[json_metadata || '{}']
    tags = metadata['tags'] || [] rescue []
    tags = [tags].flatten

    (@only_tags & tags).any?
    end
  25. inertia186 revised this gist May 26, 2017. 3 changed files with 23 additions and 1 deletion.
    2 changes: 1 addition & 1 deletion Gemfile.lock
    Original file line number Diff line number Diff line change
    @@ -17,7 +17,7 @@ GEM
    coderay (~> 1.1.0)
    method_source (~> 0.8.1)
    slop (~> 3.4)
    radiator (0.2.0c)
    radiator (0.2.1)
    bitcoin-ruby (= 0.0.10)
    ffi (= 1.9.17)
    hashie (~> 3.5, >= 3.5.5)
    20 changes: 20 additions & 0 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -100,6 +100,8 @@ def parse_list(list)
    @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'])

    @@ -233,6 +235,22 @@ def only_tags_intersection?(json_metadata)
    (@only_tags & tags).any?
    end

    def skip_app?(json_metadata)
    metadata = JSON[json_metadata || '{}']
    app = metadata['app'].to_s.split('/').first

    @skip_apps.include? app
    end

    def only_app?(json_metadata)
    return true if @only_apps.none?

    metadata = JSON[json_metadata || '{}']
    app = metadata['app'].to_s.split('/').first

    @only_apps.include? app
    end

    def already_voted_for?(author)
    return false if @voting_rules.unique_author.nil?

    @@ -251,6 +269,8 @@ def may_vote?(comment)
    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

    true
    end
    2 changes: 2 additions & 0 deletions drphil.yml
    Original file line number Diff line number Diff line change
    @@ -25,6 +25,8 @@ favorite_accounts: inertia banjo
    skip_accounts: leeroy.jenkins the.masses danlarimer ned-reddit-login
    skip_tags: nsfw test
    # only_tags: steemit
    # only_apps: steemit esteem streemian pysteem steepshot busy chainbb banjo_bot chronicle steemq
    # skip_apps: piston
    flag_signals: cheetah steemcleaners
    vote_signals:

  26. inertia186 revised this gist May 8, 2017. 1 changed file with 6 additions and 2 deletions.
    8 changes: 6 additions & 2 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -4,8 +4,12 @@

    #### New Features

    * Added 'unique_author' (optional) which takes an integer in minutes. This will limit voting to 1 vote per period. E.g.: Set it to 1440 to only vote for each author once a day.
    * Added 'max_votes_per_post' (optional) which only votes *n* times per post (`winfrey` mode only).
    * Added `unique_author` (optional) which takes an integer in minutes. This will limit voting to 1 vote per period. E.g.: Set it to 1440 to only vote for each author once a day.
    * Added `max_votes_per_post` (optional) which only votes *n* times per post (`winfrey` mode only).
    * Added `only_tags` (optional) which only votes on posts that include these tags.
    * Alternative voting weights all inherit from `vote_weight` if not present.
    * Favorites (`favorite_accounts`) can now have individual vote percent.
    * Formatted as: account:weight (e.g.: `inertia:100.00`)

    #### Features

  27. inertia186 revised this gist May 8, 2017. 1 changed file with 17 additions and 1 deletion.
    18 changes: 17 additions & 1 deletion drphil.rb
    Original file line number Diff line number Diff line change
    @@ -102,7 +102,19 @@ def parse_list(list)
    @only_tags = parse_list(@config['only_tags'])
    @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

    @options = {
    chain: @config['chain_options']['chain'].to_sym,
    url: @config['chain_options']['url'],
    @@ -415,7 +427,11 @@ def follower?(voter, author)
    def vote_weight(author, voter)
    @semaphore.synchronize do
    if @favorite_accounts.include? author
    @voting_rules.favorites_vote_weight
    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
  28. inertia186 revised this gist May 8, 2017. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -72,9 +72,9 @@ def parse_list(list)
    @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'] || '100.0 %').to_f) * 100).to_i,
    following_vote_weight: (((rules['following_vote_weight'] || '100.0 %').to_f) * 100).to_i,
    followers_vote_weight: (((rules['followers_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'],
  29. inertia186 revised this gist May 8, 2017. 3 changed files with 15 additions and 2 deletions.
    1 change: 1 addition & 0 deletions drphil.golos.yml
    Original file line number Diff line number Diff line change
    @@ -24,6 +24,7 @@ voters:
    favorite_accounts: inertia banjo
    skip_accounts: leeroy.jenkins the.masses danlarimer ned-reddit-login
    skip_tags: nsfw test ru--mat bm-open
    # only_tags: golos
    flag_signals: cheetah kulturagolosa
    vote_signals:

    15 changes: 13 additions & 2 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -99,6 +99,7 @@ def parse_list(list)
    @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'])
    @flag_signals = parse_list(@config['flag_signals'])
    @vote_signals = parse_list(@config['vote_signals'])

    @@ -204,13 +205,22 @@ def voters_check_charging
    end
    end

    def tags_intersection?(json_metadata)
    def skip_tags_intersection?(json_metadata)
    metadata = JSON[json_metadata || '{}']
    tags = metadata['tags'] || [] rescue []

    (@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 || '{}']
    tags = metadata['tags'] || [] rescue []

    (@only_tags & tags).any?
    end

    def already_voted_for?(author)
    return false if @voting_rules.unique_author.nil?

    @@ -226,7 +236,8 @@ def already_voted_for?(author)
    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 tags_intersection? comment.json_metadata
    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

    true
    1 change: 1 addition & 0 deletions drphil.yml
    Original file line number Diff line number Diff line change
    @@ -24,6 +24,7 @@ voters:
    favorite_accounts: inertia banjo
    skip_accounts: leeroy.jenkins the.masses danlarimer ned-reddit-login
    skip_tags: nsfw test
    # only_tags: steemit
    flag_signals: cheetah steemcleaners
    vote_signals:

  30. inertia186 revised this gist May 4, 2017. 4 changed files with 51 additions and 8 deletions.
    4 changes: 3 additions & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,8 @@

    #### New Features

    * Added `only_fully_powered_up` which will only vote for posts that receive 100% STEEM Power author rewards.
    * Added 'unique_author' (optional) which takes an integer in minutes. This will limit voting to 1 vote per period. E.g.: Set it to 1440 to only vote for each author once a day.
    * Added 'max_votes_per_post' (optional) which only votes *n* times per post (`winfrey` mode only).

    #### Features

    @@ -40,6 +41,7 @@
    * `skip_tags`
    * `flag_signals`
    * `vote_signals`
    * `only_fully_powered_up` which will only vote for posts that receive 100% STEEM Power author rewards.
    * Skip posts with declined payout.
    * Skip posts that already have votes from external scripts and posts that were edited.
    * Argument called `replay:` allows a replay of *n* blocks allowing you to catch up to the present.
    3 changes: 3 additions & 0 deletions drphil.golos.yml
    Original file line number Diff line number Diff line change
    @@ -8,11 +8,14 @@ voting_rules:
    followers_vote_weight: 100.00 %
    enable_comments: false
    only_first_posts: false
    only_fully_powered_up: false
    min_wait: 18
    max_wait: 30
    min_rep: 25.0
    max_rep: 99.9
    min_voting_power: 25.00 %
    # unique_author: 1440
    # max_votes_per_post: 10

    voters:
    - social 5JrvPrQeBBvCRdjv29iDvkwn3EQYZ9jqfAHzrCyUvfbEbRkrYFC
    50 changes: 43 additions & 7 deletions drphil.rb
    Original file line number Diff line number Diff line change
    @@ -82,7 +82,9 @@ def parse_list(list)
    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
    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]]
    @@ -119,6 +121,8 @@ def seinfeld?; @voting_rules.mode == 'seinfeld'; end
    @voting_rules.mode = 'seinfeld'
    end

    @voted_for_authors = {}
    @voting_power = {}
    @threads = {}
    @semaphore = Mutex.new

    @@ -138,8 +142,6 @@ def poll_voting_power
    response = @api.get_accounts(@voters.keys)
    accounts = response.result

    @voting_power = {}

    accounts.each do |account|
    @voting_power[account.name] = account.voting_power
    end
    @@ -209,6 +211,18 @@ def tags_intersection?(json_metadata)
    (@skip_tags & tags).any?
    end

    def already_voted_for?(author)
    return false if @voting_rules.unique_author.nil?

    @voted_for_authors.each do |author, vote_at|
    Time.now.utc - vote_at < @voting_rules.unique_author or @voted_for_authors[author] = nil
    end

    return true if @voted_for_authors.keys.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
    @@ -336,6 +350,12 @@ def skip?(comment, voters)
    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

    @@ -396,6 +416,7 @@ def vote_weight(author, voter)
    end

    def vote(comment, wait_offset = 0)
    votes_cast = 0
    backoff = 0.2
    slug = "@#{comment.author}/#{comment.permlink}"

    @@ -516,19 +537,34 @@ def vote(comment, wait_offset = 0)
    puts "\tFailed: voting weight too small"
    voters -= [voter]
    next
    elsif message.to_s =~ /STEEMIT_UPVOTE_LOCKOUT_HF17/
    puts "\tFailed: upvote lockout (last twelve hours before payout)"
    break
    elsif message.to_s =~ /signature is not canonical/
    puts "\tRetrying: signature was not canonical (bug in Radiator?)"
    redo
    end
    raise message
    end

    puts "\tSuccess: #{response.result.to_json}"
    @voted_for_authors[author] = Time.now.utc
    votes_cast += 1

    if winfrey?
    # The winfrey mode keeps voting until there are no more voters.
    voters -= [voter]
    next
    # 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.
    # The drphil mode only votes with one key per post.
    break
    rescue => e
    puts "Pausing #{backoff} :: Unable to vote with #{voter}. #{e}"
    2 changes: 2 additions & 0 deletions drphil.yml
    Original file line number Diff line number Diff line change
    @@ -14,6 +14,8 @@ voting_rules:
    min_rep: 25.0
    max_rep: 99.9
    min_voting_power: 25.00 %
    # unique_author: 1440
    # max_votes_per_post: 10

    voters:
    - social 5JrvPrQeBBvCRdjv29iDvkwn3EQYZ9jqfAHzrCyUvfbEbRkrYFC