cosgrove 0.0.1rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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