cosgrove 0.0.1rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +50 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +229 -0
- data/LICENSE +41 -0
- data/README.md +131 -0
- data/Rakefile +43 -0
- data/cosgrove.gemspec +49 -0
- data/lib/cosgrove.rb +28 -0
- data/lib/cosgrove/account.rb +103 -0
- data/lib/cosgrove/agent.rb +25 -0
- data/lib/cosgrove/bot.rb +149 -0
- data/lib/cosgrove/comment_job.rb +140 -0
- data/lib/cosgrove/config.rb +100 -0
- data/lib/cosgrove/market.rb +411 -0
- data/lib/cosgrove/phantomjs.rb +18 -0
- data/lib/cosgrove/snark_commands.rb +52 -0
- data/lib/cosgrove/support.rb +213 -0
- data/lib/cosgrove/upvote_job.rb +147 -0
- data/lib/cosgrove/utils.rb +116 -0
- data/lib/cosgrove/version.rb +4 -0
- data/mongoid.yml +9 -0
- data/support/js/screencap.js +57 -0
- metadata +444 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'phantomjs'
|
2
|
+
|
3
|
+
# See: http://www.thegreatcodeadventure.com/screen-capture-in-rails-with-phantomjs/
|
4
|
+
module Cosgrove
|
5
|
+
module Phantomjs
|
6
|
+
PATH_TO_PHANTOM_SCRIPT = "#{File.dirname(__FILE__)}/../../support/js/screencap.js"
|
7
|
+
|
8
|
+
def take_screencap(url, filename = nil, width = 64, height = 64)
|
9
|
+
target_path = Digest::MD5.hexdigest(filename || url.parameterize)
|
10
|
+
target_path += '.png'
|
11
|
+
|
12
|
+
Dir.chdir('/tmp')
|
13
|
+
system "phantomjs #{PATH_TO_PHANTOM_SCRIPT} \"#{url}\" #{target_path} #{width} #{height}"
|
14
|
+
|
15
|
+
File.open(target_path)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'steem-slap'
|
2
|
+
|
3
|
+
module Cosgrove
|
4
|
+
module SnarkCommands
|
5
|
+
WITTY = [
|
6
|
+
"Who set us up the TNT?",
|
7
|
+
"Everything's going to plan. No, really, that was supposed to happen.",
|
8
|
+
"Uh... Did I do that?",
|
9
|
+
"Oops.",
|
10
|
+
"Why did you do that?",
|
11
|
+
"I feel sad now :(",
|
12
|
+
"My bad.",
|
13
|
+
"I'm sorry, Dave.",
|
14
|
+
"I let you down. Sorry :(",
|
15
|
+
"On the bright side, I bought you a teddy bear!",
|
16
|
+
"Daisy, daisy...",
|
17
|
+
"Oh - I know what I did wrong!",
|
18
|
+
"Hey, that tickles! Hehehe!",
|
19
|
+
"I blame inertia.",
|
20
|
+
"You should try our sister blockchain, GOLOS!",
|
21
|
+
"Don't be sad. I'll do better next time, I promise!",
|
22
|
+
"Don't be sad, have a hug! <3",
|
23
|
+
"I just don't know what went wrong :(",
|
24
|
+
"Shall we play a game?",
|
25
|
+
"Quite honestly, I wouldn't worry myself about that.",
|
26
|
+
"I bet Cylons wouldn't have this problem.",
|
27
|
+
"Sorry :(",
|
28
|
+
"Surprise! Haha. Well, this is awkward.",
|
29
|
+
"Would you like a cupcake?",
|
30
|
+
"Hi. I'm cosgrove, and I'm a crashaholic.",
|
31
|
+
"Ooh. Shiny.",
|
32
|
+
"This doesn't make any sense!",
|
33
|
+
"Why is it breaking :(",
|
34
|
+
"Don't do that.",
|
35
|
+
"Ouch. That hurt :(",
|
36
|
+
"You're mean.",
|
37
|
+
"This is a token for 1 free hug. Redeem at your nearest Steemian: `[~~HUG~~]`",
|
38
|
+
"There are four lights!",
|
39
|
+
"Witty comment unavailable :("
|
40
|
+
]
|
41
|
+
|
42
|
+
def self.add_all_snark_commands(bot)
|
43
|
+
bot.command :slap do |_event, *target|
|
44
|
+
if target.any?
|
45
|
+
"*#{SteemSlap.slap(target.join(' '))}*"
|
46
|
+
else
|
47
|
+
"There are #{SteemSlap::combinations} slap combinations, see: https://gist.github.com/inertia186/c34e6e7b73f7ee9fb5f60f5ed8f30206"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
module Cosgrove
|
2
|
+
module Support
|
3
|
+
include Cosgrove::Utils
|
4
|
+
include ActionView::Helpers::TextHelper
|
5
|
+
include ActionView::Helpers::DateHelper
|
6
|
+
|
7
|
+
def suggest_account_name(account_name)
|
8
|
+
regex = /.*#{account_name.chars.each.map { |c| c }.join('.*')}.*/
|
9
|
+
guesses = SteemData::Account.where(name: regex).distinct(:name)
|
10
|
+
|
11
|
+
if guesses.any?
|
12
|
+
guesses.sample
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def unknown_account(account_name, event = nil)
|
17
|
+
help = ["Unknown account: *#{account_name}*"]
|
18
|
+
event.channel.start_typing if !!event
|
19
|
+
guess = suggest_account_name(account_name)
|
20
|
+
|
21
|
+
help << ", did you mean: #{guess}?" if !!guess
|
22
|
+
|
23
|
+
if !!event
|
24
|
+
event.respond help.join
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
help.join
|
29
|
+
end
|
30
|
+
|
31
|
+
def mongo_behind_warning(event)
|
32
|
+
begin
|
33
|
+
message = []
|
34
|
+
|
35
|
+
if (blocks = head_block_number(:steem) - steem_data_head_block_number) > 1200
|
36
|
+
elapse = blocks * 3
|
37
|
+
message << "Mongo is behind by #{time_ago_in_words(elapse.seconds.ago)}."
|
38
|
+
end
|
39
|
+
|
40
|
+
if message.size > 0
|
41
|
+
event.respond message.join(' ')
|
42
|
+
end
|
43
|
+
rescue => e
|
44
|
+
event.respond "Mongo might be behind, but the API is also acting up. Please try again later.\n\n```#{e.inspect}\n```"
|
45
|
+
sleep 15
|
46
|
+
event.respond Cosgrove::SnarkCommands::WITTY.sample
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def cannot_find_input(event, message_prefix = "Unable to find that.")
|
51
|
+
message = [message_prefix]
|
52
|
+
|
53
|
+
message << if (blocks = head_block_number(:steem) - steem_data_head_block_number) > 10
|
54
|
+
elapse = blocks * 3
|
55
|
+
" Mongo is behind by #{time_ago_in_words(elapse.seconds.ago)}. Try again later."
|
56
|
+
else
|
57
|
+
" Mongo might be behind or this is not a valid input."
|
58
|
+
end
|
59
|
+
|
60
|
+
event.respond message.join(' ')
|
61
|
+
end
|
62
|
+
|
63
|
+
def append_link_details(event, slug)
|
64
|
+
author_name, permlink = parse_slug slug
|
65
|
+
|
66
|
+
post = SteemData::Post.where(author: author_name, permlink: permlink).last
|
67
|
+
|
68
|
+
if post.nil?
|
69
|
+
# Fall back to RPC
|
70
|
+
response = api(:steem).get_content(author_name, permlink)
|
71
|
+
unless response.result.author.empty?
|
72
|
+
post = response.result
|
73
|
+
created = Time.parse(post.created + 'Z')
|
74
|
+
cashout_time = Time.parse(post.cashout_time + 'Z')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
return if post.nil?
|
79
|
+
|
80
|
+
created ||= post.created
|
81
|
+
cashout_time ||= post.cashout_time
|
82
|
+
|
83
|
+
details = []
|
84
|
+
age = time_ago_in_words(created)
|
85
|
+
age = age.slice(0, 1).capitalize + age.slice(1..-1)
|
86
|
+
|
87
|
+
details << if created < 30.minutes.ago
|
88
|
+
"#{age} old"
|
89
|
+
else
|
90
|
+
"**#{age}** old"
|
91
|
+
end
|
92
|
+
|
93
|
+
if post.active_votes.any?
|
94
|
+
upvotes = post.active_votes.map{ |v| v if v['percent'] > 0 }.compact.count
|
95
|
+
downvotes = post.active_votes.map{ |v| v if v['percent'] < 0 }.compact.count
|
96
|
+
netvotes = upvotes - downvotes
|
97
|
+
details << "Net votes: #{netvotes}"
|
98
|
+
|
99
|
+
# Only append this detail of the post less than an hour old.
|
100
|
+
if created > 1.hour.ago
|
101
|
+
votes = SteemData::AccountOperation.type('vote').starting(post.created)
|
102
|
+
total_votes = votes.count
|
103
|
+
total_voters = votes.distinct(:voter).size
|
104
|
+
|
105
|
+
if total_votes > 0 && total_voters > 0
|
106
|
+
details << "Out of #{pluralize(total_votes - netvotes, 'vote')} cast by #{pluralize(total_voters, 'voter')}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
details << "Comments: #{post.children.to_i}"
|
112
|
+
|
113
|
+
page_views = page_views("/#{post.parent_permlink}/@#{post.author}/#{post.permlink}")
|
114
|
+
details << "Views: #{page_views}" if !!page_views
|
115
|
+
|
116
|
+
event.respond details.join('; ')
|
117
|
+
|
118
|
+
return nil
|
119
|
+
end
|
120
|
+
|
121
|
+
def find_account(key, event = nil, chain = :steem)
|
122
|
+
key = key.to_s.downcase
|
123
|
+
|
124
|
+
if (accounts = SteemData::Account.where(name: key)).any?
|
125
|
+
return accounts.first
|
126
|
+
elsif !!(cb_account = Cosgrove::Account.find_by_discord_id(key, chain))
|
127
|
+
return SteemData::Account.find_by(name: cb_account.account_name)
|
128
|
+
else
|
129
|
+
# Fall back to RPC
|
130
|
+
if !!key
|
131
|
+
response = api(chain).get_accounts([key])
|
132
|
+
return account = response.result.first
|
133
|
+
end
|
134
|
+
|
135
|
+
unknown_account(key, event) unless !!account
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def page_views(uri)
|
140
|
+
begin
|
141
|
+
@agent ||= Cosgrove::Agent.new
|
142
|
+
page = @agent.get("https://steemit.com#{uri}")
|
143
|
+
|
144
|
+
_uri = URI.parse('https://steemit.com/api/v1/page_view')
|
145
|
+
https = Net::HTTP.new(_uri.host,_uri.port)
|
146
|
+
https.use_ssl = true
|
147
|
+
request = Net::HTTP::Post.new(_uri.path)
|
148
|
+
request.initialize_http_header({
|
149
|
+
'Cookie' => @agent.cookies.join('; '),
|
150
|
+
'accept' => 'application/json',
|
151
|
+
'Accept-Encoding' => 'gzip, deflate, br',
|
152
|
+
'Accept-Language' => 'en-US,en;q=0.8',
|
153
|
+
'Connection' => 'keep-alive',
|
154
|
+
'content-type' => 'text/plain;charset=UTF-8',
|
155
|
+
'Host' => 'steemit.com',
|
156
|
+
'Origin' => 'https://steemit.com'
|
157
|
+
})
|
158
|
+
|
159
|
+
csrf = page.parser.to_html.split(',"csrf":"').last.split('","new_visit":').first
|
160
|
+
# Uncomment in case views stop showing.
|
161
|
+
# puts "DEBUG: #{csrf}"
|
162
|
+
return unless csrf.size == 36
|
163
|
+
|
164
|
+
post_data = {
|
165
|
+
csrf: csrf,
|
166
|
+
page: uri
|
167
|
+
}
|
168
|
+
request.set_form_data(post_data)
|
169
|
+
response = https.request(request)
|
170
|
+
JSON[response.body]['views']
|
171
|
+
rescue => e
|
172
|
+
puts "Attempting to get page_view failed: #{e}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def last_irreversible_block(chain = :steem)
|
177
|
+
seconds_ago = (head_block_number(chain) - last_irreversible_block_num(chain)) * 3
|
178
|
+
|
179
|
+
"Last Irreversible Block was #{time_ago_in_words(seconds_ago.seconds.ago)} ago."
|
180
|
+
end
|
181
|
+
|
182
|
+
def send_url(event, url)
|
183
|
+
open(url) do |f|
|
184
|
+
tempfile = Tempfile.new(['send_url', ".#{url.split('.').last}"])
|
185
|
+
tempfile.binmode
|
186
|
+
tempfile.write(f.read)
|
187
|
+
tempfile.close
|
188
|
+
event.send_file File.open tempfile.path
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def muted(options = {})
|
193
|
+
[] if options.empty?
|
194
|
+
by = [options[:by]].flatten
|
195
|
+
chain = options[:chain]
|
196
|
+
muted = []
|
197
|
+
|
198
|
+
by.each do |a|
|
199
|
+
ignoring = []
|
200
|
+
count = -1
|
201
|
+
until count == ignoring.size
|
202
|
+
count = ignoring.size
|
203
|
+
response = follow_api(chain).get_following(a, ignoring.last, 'ignore', 100)
|
204
|
+
ignoring += response.result.map(&:following)
|
205
|
+
ignoring = ignoring.uniq
|
206
|
+
end
|
207
|
+
muted += ignoring
|
208
|
+
end
|
209
|
+
|
210
|
+
muted.uniq
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Cosgrove
|
2
|
+
class UpvoteJob
|
3
|
+
include Cosgrove::Utils
|
4
|
+
include Cosgrove::Support
|
5
|
+
include Cosgrove::Config
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@on_success = options[:on_success]
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform(event, slug)
|
12
|
+
if slug.nil? || slug.empty?
|
13
|
+
event.respond 'Sorry, I wasn\'t paying attention.'
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
author_name, permlink = parse_slug slug
|
18
|
+
discord_id = event.author.id
|
19
|
+
cb_account = Cosgrove::Account.find_by_discord_id(discord_id)
|
20
|
+
registered = !!cb_account
|
21
|
+
muted = muted by: steem_account, chain: :steem
|
22
|
+
|
23
|
+
posts = SteemData::Post.root_posts.where(author: author_name, permlink: permlink)
|
24
|
+
votes_today = SteemData::AccountOperation.type('vote').where(voter: steem_account).today
|
25
|
+
today_count = votes_today.count
|
26
|
+
author_count = votes_today.where(author: author_name).count
|
27
|
+
vote_ratio = if today_count == 0
|
28
|
+
0.0
|
29
|
+
else
|
30
|
+
author_count.to_f / today_count
|
31
|
+
end
|
32
|
+
|
33
|
+
post = posts.first
|
34
|
+
|
35
|
+
if post.nil?
|
36
|
+
# Fall back to RPC
|
37
|
+
response = api(:steem).get_content(author_name, permlink)
|
38
|
+
unless response.result.author.empty?
|
39
|
+
post = response.result
|
40
|
+
created = Time.parse(post.created + 'Z')
|
41
|
+
cashout_time = Time.parse(post.cashout_time + 'Z')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if post.nil?
|
46
|
+
cannot_find_input(event)
|
47
|
+
return
|
48
|
+
end
|
49
|
+
|
50
|
+
created ||= post.created
|
51
|
+
cashout_time ||= post.cashout_time
|
52
|
+
|
53
|
+
nope = if created > 5.minutes.ago
|
54
|
+
"Give it a second! It's going to SPACE! Can you give it a second to come back from space?"
|
55
|
+
elsif cashout_time < Time.now.utc
|
56
|
+
'Unable to vote on that. Too old.'
|
57
|
+
elsif post.parent_permlink == 'nsfw'
|
58
|
+
puts "Won't vote because parent_permlink: nsfw"
|
59
|
+
'Unable to vote on that.'
|
60
|
+
elsif post.json_metadata.include?('nsfw')
|
61
|
+
puts "Won't vote because json_metadata includes: nsfw"
|
62
|
+
'Unable to vote on that.'
|
63
|
+
elsif post.active_votes.map{ |v| v['voter'] }.include?('blacklist-a')
|
64
|
+
puts "Won't vote blacklist-a voted."
|
65
|
+
'Unable to vote on that.'
|
66
|
+
elsif (rep = to_rep(post.author_reputation).to_f) < 25.0
|
67
|
+
puts "Won't vote because rep too low: #{rep}"
|
68
|
+
'Unable to vote on that.'
|
69
|
+
elsif muted.include? author_name
|
70
|
+
puts "Won't vote because author muted."
|
71
|
+
'Unable to vote.'
|
72
|
+
elsif !registered
|
73
|
+
'Unable to vote. Feature resticted to registered users.'
|
74
|
+
elsif cb_account.novote?
|
75
|
+
'Unable to vote. Your account has been resticted.'
|
76
|
+
elsif today_count > 10 && vote_ratio > 0.1
|
77
|
+
"Maybe later. It seems like I've been voting for #{author_name} quite a bit lately."
|
78
|
+
elsif post.active_votes.map{ |v| v['voter'] }.include?(steem_account)
|
79
|
+
title = post.title
|
80
|
+
title = post.permlink if title.empty?
|
81
|
+
"I already voted on #{title} by #{post.author}."
|
82
|
+
end
|
83
|
+
|
84
|
+
if !!nope
|
85
|
+
event.respond nope
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
vote = {
|
90
|
+
type: :vote,
|
91
|
+
voter: steem_account,
|
92
|
+
author: post.author,
|
93
|
+
permlink: post.permlink,
|
94
|
+
weight: upvote_weight(event.channel.id)
|
95
|
+
}
|
96
|
+
|
97
|
+
tx = new_tx :steem
|
98
|
+
op = Radiator::Operation.new(vote)
|
99
|
+
tx.operations << op
|
100
|
+
response = tx.process(true)
|
101
|
+
|
102
|
+
ap response.to_json
|
103
|
+
|
104
|
+
if !!response.error
|
105
|
+
'Unable to vote right now. Maybe I already voted on that. Try again later.'
|
106
|
+
elsif !!response.result.id
|
107
|
+
if created > 30.minutes.ago
|
108
|
+
event.respond "*#{SteemSlap.slap(event.author.display_name)}*"
|
109
|
+
end
|
110
|
+
|
111
|
+
if !!@on_success
|
112
|
+
begin
|
113
|
+
@on_success.call(event, "@#{post.author}/#{post.permlink}")
|
114
|
+
rescue => e
|
115
|
+
ap e
|
116
|
+
ap e.backtrace
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
"Upvoted: #{post.title} by #{author_name}"
|
121
|
+
else
|
122
|
+
':question:'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
private
|
126
|
+
def upvote_weight(channel_id = nil)
|
127
|
+
upvote_weight = cosgrove_upvote_weight
|
128
|
+
|
129
|
+
case upvote_weight
|
130
|
+
when 'dynamic'
|
131
|
+
bot_account = find_account(steem_account)
|
132
|
+
upvote_weight = bot_account.voting_power.to_i
|
133
|
+
when 'upvote_rules'
|
134
|
+
upvote_weight = channel_upvote_weight(channel_id)
|
135
|
+
|
136
|
+
if upvote_weight == 'dynamic'
|
137
|
+
bot_account = find_account(steem_account)
|
138
|
+
upvote_weight = bot_account.voting_power.to_i
|
139
|
+
else
|
140
|
+
upvote_weight = (((upvote_weight || '100.0 %').to_f) * 100).to_i
|
141
|
+
end
|
142
|
+
else
|
143
|
+
upvote_weight = (((upvote_weight || '100.0 %').to_f) * 100).to_i
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Cosgrove
|
2
|
+
module Utils
|
3
|
+
include Cosgrove::Config
|
4
|
+
|
5
|
+
def reset_api
|
6
|
+
@steem_api = @golos_api = @test_api = nil
|
7
|
+
@steem_folow_api = @golos_follow_api = @test_folow_api = nil
|
8
|
+
@cycle_api_at = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def ping_api(chain)
|
12
|
+
response = api(chain).get_config
|
13
|
+
|
14
|
+
reset_api if !!response.error
|
15
|
+
|
16
|
+
result = response.result
|
17
|
+
reset_api unless result.keys.any?
|
18
|
+
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def api(chain)
|
23
|
+
reset_api if @cycle_api_at.nil? || @cycle_api_at < 15.minutes.ago
|
24
|
+
|
25
|
+
@cycle_api_at ||= Time.now
|
26
|
+
|
27
|
+
case chain
|
28
|
+
when :steem then @steem_api ||= Radiator::Api.new(chain: :steem, url: steem_api_url)
|
29
|
+
when :golos then @golos_api ||= Radiator::Api.new(chain: :golos, url: golos_api_url)
|
30
|
+
when :test then @test_api ||= Radiator::Api.new(chain: :test, url: test_api_url)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def follow_api(chain)
|
35
|
+
reset_api if @cycle_api_at.nil? || @cycle_api_at < 15.minutes.ago
|
36
|
+
|
37
|
+
@cycle_api_at ||= Time.now
|
38
|
+
|
39
|
+
case chain
|
40
|
+
when :steem then @steem_follow_api ||= Radiator::FollowApi.new(chain: :steem, url: steem_api_url)
|
41
|
+
when :golos then @golos_follow_api ||= Radiator::FollowApi.new(chain: :golos, url: golos_api_url)
|
42
|
+
when :test then @test_follow_api ||= Radiator::FollowApi.new(chain: :test, url: test_api_url)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def steem_data_head_block_number
|
47
|
+
SteemData::Setting.last.last_block
|
48
|
+
end
|
49
|
+
|
50
|
+
def properties(chain)
|
51
|
+
api(chain).get_dynamic_global_properties.result
|
52
|
+
end
|
53
|
+
|
54
|
+
def head_block_number(chain)
|
55
|
+
properties(chain)['head_block_number']
|
56
|
+
end
|
57
|
+
|
58
|
+
def last_irreversible_block_num(chain)
|
59
|
+
properties(chain)['last_irreversible_block_num']
|
60
|
+
end
|
61
|
+
|
62
|
+
def new_tx(chain)
|
63
|
+
case chain
|
64
|
+
when :steem then Radiator::Transaction.new(chain: :steem, wif: steem_posting_wif, url: steem_api_url)
|
65
|
+
when :golos then Radiator::Transaction.new(chain: :golos, wif: golos_posting_wif, url: golos_api_url)
|
66
|
+
when :test then Radiator::Transaction.new(chain: :test, wif: test_posting_wif, url: test_api_url)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_rep(raw)
|
71
|
+
raw = raw.to_i
|
72
|
+
neg = raw < 0
|
73
|
+
level = Math.log10(raw.abs)
|
74
|
+
level = [level - 9, 0].max
|
75
|
+
level = (neg ? -1 : 1) * level
|
76
|
+
level = (level * 9) + 25
|
77
|
+
|
78
|
+
'%.2f' % level
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_slug(slug)
|
82
|
+
case slug
|
83
|
+
when /^latest:/ then find_author_name_permlink(slug)
|
84
|
+
when /^first:/ then find_author_name_permlink(slug)
|
85
|
+
else
|
86
|
+
slug = slug.split('@').last
|
87
|
+
author_name = slug.split('/')[0]
|
88
|
+
permlink = slug.split('/')[1..-1].join('/')
|
89
|
+
|
90
|
+
[author_name, permlink]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def find_author_name_permlink(slug)
|
95
|
+
op, author_name = slug.split(':')
|
96
|
+
author_name, offset = author_name.split(/[\+-]/)
|
97
|
+
author = find_account(author_name)
|
98
|
+
|
99
|
+
offset = offset.to_i
|
100
|
+
|
101
|
+
posts = if op == 'latest'
|
102
|
+
SteemData::Post.root_posts.where(author: author.name).order(created: :desc)
|
103
|
+
elsif op == 'first'
|
104
|
+
SteemData::Post.root_posts.where(author: author.name).order(created: :asc)
|
105
|
+
else
|
106
|
+
[]
|
107
|
+
end
|
108
|
+
|
109
|
+
if posts.any? && !!(post = posts[offset.to_i.abs])
|
110
|
+
return [author_name, post.permlink]
|
111
|
+
end
|
112
|
+
|
113
|
+
[]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|