cosgrove 0.0.1rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,43 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+ require 'yard'
4
+ require 'cosgrove'
5
+
6
+ Encoding.default_external = Encoding::UTF_8
7
+ Encoding.default_internal = Encoding::UTF_8
8
+
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'test'
11
+ t.libs << 'lib'
12
+ t.test_files = FileList['test/**/*_test.rb']
13
+ t.ruby_opts << if ENV['HELL_ENABLED']
14
+ '-W2'
15
+ else
16
+ '-W1'
17
+ end
18
+ end
19
+
20
+ YARD::Rake::YardocTask.new do |t|
21
+ t.files = ['lib/**/*.rb']
22
+ end
23
+
24
+ task default: :test
25
+
26
+ task :console do
27
+ exec "irb -r cosgrove -I ./lib"
28
+ end
29
+
30
+ task :build do
31
+ exec 'gem build cosgrove.gemspec'
32
+ end
33
+
34
+ task :push do
35
+ exec "gem push cosgrove-#{Cosgrove::VERSION}.gem"
36
+ end
37
+
38
+ # We're not going to yank on a regular basis, but this is how it's done if you
39
+ # really want a task for that for some reason.
40
+
41
+ # task :yank do
42
+ # exec "gem yank cosgrove -v #{Cosgrove::VERSION}"
43
+ # end
@@ -0,0 +1,49 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cosgrove/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'cosgrove'
8
+ spec.version = Cosgrove::VERSION
9
+ spec.authors = ['Anthony Martin']
10
+ spec.email = ['cosgrove@martin-studio.com']
11
+
12
+ spec.summary = %q{Cosgrove Discord Bo t}
13
+ spec.description = %q{STEEM centric Discord bot.}
14
+ spec.homepage = 'https://github.com/steem-third-party/cosgrove'
15
+ spec.license = 'CC0 1.0'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test)/}) }
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler', '~> 1.11'
21
+ spec.add_development_dependency 'rake', '~> 11.2.2'
22
+ spec.add_development_dependency 'minitest'
23
+ spec.add_development_dependency 'minitest-line'
24
+ spec.add_development_dependency 'minitest-proveit'
25
+ spec.add_development_dependency 'webmock'
26
+ spec.add_development_dependency 'vcr'
27
+ spec.add_development_dependency 'faraday'
28
+ spec.add_development_dependency 'typhoeus'
29
+ spec.add_development_dependency 'simplecov'
30
+ # spec.add_development_dependency 'codeclimate-test-reporter'
31
+ spec.add_development_dependency 'yard'
32
+ spec.add_development_dependency 'byebug'
33
+ spec.add_development_dependency 'pry'
34
+ spec.add_development_dependency 'awesome_print'
35
+
36
+ spec.add_dependency 'radiator'
37
+ spec.add_dependency 'steemdata-rb'
38
+ spec.add_dependency 'discordrb'
39
+ spec.add_dependency 'ai4r'
40
+ spec.add_dependency 'mongoid'
41
+ spec.add_dependency 'ruby-cleverbot-api'
42
+ spec.add_dependency 'steem-slap'
43
+ spec.add_dependency 'activeview'
44
+ spec.add_dependency 'rdiscount'
45
+ spec.add_dependency 'phantomjs'
46
+ spec.add_dependency 'mechanize'
47
+ spec.add_dependency 'wolfram-alpha'
48
+ spec.add_dependency 'rmagick'
49
+ end
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'yaml'
4
+ require 'action_view'
5
+ require 'radiator'
6
+ require 'awesome_print'
7
+ # require 'pry'
8
+
9
+ Bundler.require
10
+
11
+ SteemData.load
12
+
13
+ module Cosgrove
14
+ PWD = Dir.pwd.freeze
15
+ KNOWN_CHAINS = [:steem, :golos, :test]
16
+
17
+ require 'cosgrove/version'
18
+ require 'cosgrove/phantomjs'
19
+ require 'cosgrove/agent'
20
+ require 'cosgrove/config'
21
+ require 'cosgrove/utils'
22
+ require 'cosgrove/account'
23
+ require 'cosgrove/support'
24
+ require 'cosgrove/market'
25
+ require 'cosgrove/upvote_job'
26
+ require 'cosgrove/comment_job'
27
+ require 'cosgrove/bot'
28
+ end
@@ -0,0 +1,103 @@
1
+ require 'digest/bubblebabble'
2
+
3
+ module Cosgrove
4
+ class Account
5
+ include Cosgrove::Config
6
+
7
+ ACCOUNTS_FILE ||= "#{Cosgrove::PWD}/accounts.yml".freeze
8
+ DISCORD_IDS = 'discord_ids'.freeze
9
+
10
+ attr_accessor :chain, :account_name, :discord_ids
11
+
12
+ def initialize(account_name, chain = :steem)
13
+ raise "Unknown Chain: #{chain}" unless Cosgrove::KNOWN_CHAINS.include? chain.to_sym
14
+
15
+ @account_name = account_name.to_s.downcase
16
+ @chain = chain
17
+ @discord_ids = []
18
+
19
+ if !!details
20
+ @discord_ids = details[DISCORD_IDS] || []
21
+ end
22
+ end
23
+
24
+ def self.find_by_discord_id(discord_id, chain = :steem)
25
+ return if Account.yml[chain.to_s].nil?
26
+
27
+ discord_id = discord_id.to_s.split('@').last.split('>').first.to_i
28
+
29
+ Account.yml[chain.to_s].each do |k, v|
30
+ ids = v[DISCORD_IDS]
31
+ return Account.new(k, chain) if !!ids && ids.include?(discord_id)
32
+ end
33
+
34
+ return nil
35
+ end
36
+
37
+ def self.find_by_memo_key(memo_key, secure, chain = :steem)
38
+ Account.yml[chain.to_s].each do |k, v|
39
+ v[DISCORD_IDS].each do |discord_id|
40
+ return Account.new(k, chain) if Account.gen_memo_key(k, discord_id, chain, secure) == memo_key
41
+ end
42
+ end
43
+
44
+ return nil
45
+ end
46
+
47
+ def chain_data
48
+ @chain_data ||= Account.yml[@chain.to_s] || {}
49
+ end
50
+
51
+ def details
52
+ chain_data[@account_name] ||= {}
53
+ chain_data[@account_name]
54
+ end
55
+
56
+ def hidden?
57
+ !!details['hidden']
58
+ end
59
+
60
+ def novote?
61
+ !!details['novote']
62
+ end
63
+
64
+ def add_discord_id(discord_id)
65
+ details[DISCORD_IDS] ||= []
66
+ details[DISCORD_IDS] << discord_id.to_i
67
+ details[DISCORD_IDS] = details[DISCORD_IDS].uniq
68
+
69
+ Account.save_yml!
70
+ end
71
+
72
+ def memo_key(discord_id)
73
+ Account.gen_memo_key(@account_name, discord_id, cosgrove_secure, @chain)
74
+ end
75
+
76
+ def chain_account
77
+ if !!@account_name
78
+ response = api(chain).get_accounts([@account_name])
79
+ account = response.result.first
80
+ end
81
+ end
82
+ private
83
+ def self.gen_memo_key(account_name, discord_id, secure, chain)
84
+ raise "Unknown Chain: #{chain}" unless Cosgrove::KNOWN_CHAINS.include? chain.to_sym
85
+
86
+ Digest.bubblebabble(Digest::SHA1::hexdigest(account_name + discord_id.to_s + chain.to_s + secure)[8..12])
87
+ end
88
+
89
+ def self.save_yml!
90
+ File.open(ACCOUNTS_FILE, 'w') do |f|
91
+ f.write @yml.to_yaml
92
+ end
93
+ end
94
+
95
+ def self.yml
96
+ @yml = if File.exist?(ACCOUNTS_FILE)
97
+ YAML.load_file(ACCOUNTS_FILE)
98
+ else
99
+ {}
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,25 @@
1
+ require 'mechanize'
2
+
3
+ module Cosgrove
4
+ class Agent < Mechanize
5
+ COOKIE_FILE ||= 'cookies.yml'
6
+
7
+ def initialize
8
+ super('cosgrove')
9
+
10
+ @agent.user_agent = 'Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405'
11
+ @agent.keep_alive = false
12
+ @agent.open_timeout = 10
13
+ @agent.read_timeout = 10
14
+
15
+ # Cookie management, see: https://gist.github.com/makevoid/4282237
16
+ @agent.pre_connect_hooks << Proc.new do
17
+ @agent.cookie_jar.load COOKIE_FILE if ::File.exist?(COOKIE_FILE)
18
+ end
19
+
20
+ @agent.post_connect_hooks << Proc.new do
21
+ @agent.cookie_jar.save COOKIE_FILE
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,149 @@
1
+ require 'discordrb'
2
+
3
+ module Cosgrove
4
+ require 'cosgrove/snark_commands'
5
+
6
+ cattr_accessor :latest_steemit_link, :latest_golos_link
7
+
8
+ @@latest_steemit_link = {}
9
+ @@latest_golosio_link = {}
10
+
11
+ class Bot < Discordrb::Commands::CommandBot
12
+ include Support
13
+ include SnarkCommands
14
+
15
+ def initialize(options = {})
16
+ options[:token] ||= cosgrove_token
17
+ options[:client_id] ||= cosgrove_client_id
18
+ super(options)
19
+
20
+ @on_success_upvote_job = options[:on_success_upvote_job]
21
+
22
+ self.bucket :voting, limit: 4, time_span: 8640, delay: 10
23
+
24
+ add_all_commands
25
+ add_all_messages
26
+ SnarkCommands::add_all_snark_commands(self)
27
+ end
28
+
29
+ def add_all_messages
30
+ # A user typed a link to steemit.com
31
+ self.message(content: /http[s]*:\/\/steemit\.com\/.*/, ignore_bots: false) do |event|
32
+ link = event.content.split(' ').first
33
+ Cosgrove::latest_steemit_link[event.channel.name] = link
34
+ append_link_details(event, link)
35
+ end
36
+
37
+ # A user typed a link to golos.io.
38
+ self.message(content: /http[s]*:\/\/golos\.io\/.*/, ignore_bots: false) do |event|
39
+ link = event.content.split(' ').first
40
+ Cosgrove::latest_golosio_link[event.channel.name] = link
41
+ end
42
+ end
43
+
44
+ def add_all_commands
45
+ self.command :help do |_event, *args|
46
+ help = []
47
+ help << "`$slap [target]` - does a slap on the `target`"
48
+ help << "`$verify <account> [chain]` - check `account` association with Discord users (`chain` default `steem`)"
49
+ help << "`$register <account> [chain]` - associate `account` with your Discord user (`chain` default `steem`)"
50
+ help << "`$upvote [url]` - upvote from #{steem_account}; empty or `^` to upvote last steemit link"
51
+ help.join("\n")
52
+ end
53
+
54
+ self.command :version do |_|
55
+ "cosgrove: #{Cosgrove::VERSION} :: https://github.com/steem-third-party/cosgrove"
56
+ end
57
+
58
+ self.command :verify do |event, key, chain = :steem|
59
+ mongo_behind_warning(event)
60
+ if key.nil?
61
+ event.respond "To create an account: https://steemit.com/enter_email?r=#{steem_account}"
62
+ return
63
+ end
64
+
65
+ account = find_account(key, event)
66
+
67
+ if !!account && account.respond_to?(:name)
68
+ cb_account = Cosgrove::Account.new(account.name, chain)
69
+ else
70
+ discord_id = key.split('@').last.split('>').first.to_i
71
+ cb_account = Cosgrove::Account.find_by_discord_id(discord_id, chain)
72
+
73
+ account = if !!cb_account
74
+ find_account(cb_account.account_name, event)
75
+ end
76
+ end
77
+
78
+ if !!account && !!cb_account && cb_account.discord_ids.any?
79
+ if cb_account.hidden?
80
+ "#{chain.to_s.upcase} account `#{account.name}` has been registered."
81
+ else
82
+ discord_ids = cb_account.discord_ids.map { |id| "<@#{id}>" }
83
+
84
+ "#{chain.to_s.upcase} account `#{account.name}` has been registered with #{discord_ids.to_sentence}."
85
+ end
86
+ elsif !!account
87
+ "#{chain.to_s.upcase} account `#{account.name}` has not been registered with any Discord account. To register:\n`$register #{account.name}`"
88
+ elsif discord_id.to_i > 0
89
+ "<@#{discord_id}> has not been associated with a #{chain.to_s.upcase} account. To register:\n`$register <account>`"
90
+ else
91
+ "No association found. To register:\n`$register <account>`"
92
+ end
93
+ end
94
+
95
+ self.command :register do |event, account_name, chain = :steem|
96
+ mongo_behind_warning(event)
97
+ account = find_account(account_name, event)
98
+ discord_id = event.author.id
99
+
100
+ return nil unless !!account
101
+
102
+ cb_account = Cosgrove::Account.new(account.name, chain)
103
+
104
+ if cb_account.discord_ids.include? discord_id
105
+ event.respond "Already registered `#{account.name}` on `#{chain.upcase}` with <@#{discord_id}>"
106
+ return
107
+ end
108
+
109
+ memo_key = cb_account.memo_key(discord_id)
110
+ op = SteemData::AccountOperation.type('transfer').
111
+ where(account: steem_account, from: account.name, to: steem_account, memo: {'$regex' => ".*#{memo_key}.*"}).last
112
+
113
+ if op.nil?
114
+ # Fall back to RPC. The transaction is so new, SteemData hasn't seen it
115
+ # yet, SteemData is behind, or there is no such transfer.
116
+
117
+ response = api(chain).get_account_history(steem_account, -1000, 1000)
118
+ op = response.result.map do |history|
119
+ e = history.last.op
120
+ type = e.first
121
+ next unless type == 'transfer'
122
+ o = e.last
123
+ next unless o.from == account.name
124
+ next unless o.to == steem_account
125
+ next unless o.memo =~ /.*#{memo_key}.*/
126
+
127
+ o
128
+ end.compact.last
129
+ end
130
+
131
+ if !!op
132
+ cb_account.add_discord_id(discord_id)
133
+ "Ok. #{chain.to_s.upcase} account #{account.name} has been registered with <@#{discord_id}>."
134
+ else
135
+ "To register `#{account.name}` with <@#{discord_id}>, send `0.001 #{chain.upcase}` to `#{steem_account}` with memo: `#{memo_key}`\n\nThen type `$register #{account.name}` again."
136
+ end
137
+ end
138
+
139
+ self.command(:upvote, bucket: :voting, rate_limit_message: 'Sorry, you are in cool-down. Please wait %time% more seconds.') do |event, slug = nil|
140
+ slug = Cosgrove::latest_steemit_link[event.channel.name] if slug.nil? || slug.empty? || slug == '^'
141
+ options = {
142
+ on_success: @on_success_upvote_job
143
+ }
144
+
145
+ Cosgrove::UpvoteJob.new(options).perform(event, slug)
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,140 @@
1
+ require 'rdiscount'
2
+
3
+ module Cosgrove
4
+ class CommentJob
5
+ include Cosgrove::Utils
6
+ include Cosgrove::Support
7
+
8
+ def perform(event, slug, template, message = nil)
9
+ author_name, permlink = parse_slug slug
10
+ muted = muted by: steem_account, chain: :steem
11
+
12
+ posts = SteemData::Post.root_posts.where(author: author_name, permlink: permlink)
13
+
14
+ post = posts.first
15
+ author = SteemData::Account.find_by(name: author_name)
16
+
17
+ if post.nil?
18
+ # Fall back to RPC
19
+ response = api(:steem).get_content(author_name, permlink)
20
+ unless response.result.author.empty?
21
+ post = response.result
22
+ created = Time.parse(post.created + 'Z')
23
+ cashout_time = Time.parse(post.cashout_time + 'Z')
24
+ end
25
+ end
26
+
27
+ if post.nil?
28
+ cannot_find_input(event)
29
+ return
30
+ end
31
+
32
+ created ||= post.created
33
+ cashout_time ||= post.cashout_time
34
+
35
+ nope = if post.nil?
36
+ "Sorry, couldn't find that."
37
+ elsif cashout_time < Time.now.utc
38
+ 'Unable to comment on that. Too old.'
39
+ elsif post.parent_permlink == 'nsfw'
40
+ puts "Won't comment because parent_permlink: nsfw"
41
+ 'Unable to comment on that.'
42
+ elsif post.json_metadata.include?('nsfw')
43
+ puts "Won't comment because json_metadata includes: nsfw"
44
+ 'Unable to comment on that.'
45
+ elsif post.active_votes.map{ |v| v['voter'] }.include?('blacklist-a')
46
+ puts "Won't comment blacklist-a voted."
47
+ 'Unable to comment on that.'
48
+ elsif (rep = to_rep(post.author_reputation).to_f) < 25.0
49
+ puts "Won't comment because rep too low: #{rep}"
50
+ 'Unable to comment on that.'
51
+ elsif muted.include? author_name
52
+ puts "Won't vote because author muted."
53
+ 'Unable to vote.'
54
+ # elsif template == :welcome && author.post_count != 1
55
+ # 'Sorry, this function is intended to welcome new authors.'
56
+ elsif SteemData::Post.where(author: steem_account, parent_permlink: post.permlink).any?
57
+ title = post.title
58
+ title = post.permlink if title.empty?
59
+ "I already commented on #{title} by #{post.author}."
60
+ end
61
+
62
+ if !!nope
63
+ event.respond nope
64
+ return
65
+ end
66
+
67
+ now = Time.now
68
+ comment = {
69
+ type: :comment,
70
+ parent_permlink: post.permlink,
71
+ author: steem_account,
72
+ permlink: "re-#{post.author.gsub(/[^a-z0-9\-]+/, '-')}-#{post.permlink}-#{now.utc.strftime('%Y%m%dt%H%M%S%Lz')}", # e.g.: 20170225t235138025z
73
+ title: '',
74
+ body: merge(template, message, event.author.username),
75
+ json_metadata: "{\"tags\":[\"#{post.parent_permlink}\"],\"app\":\"#{Cosgrove::AGENT_ID}\"}",
76
+ parent_author: post.author
77
+ }
78
+
79
+ ap comment
80
+
81
+ tx = new_tx :steem
82
+ op = Radiator::Operation.new(comment)
83
+ tx.operations << op
84
+
85
+ response = tx.process(true)
86
+
87
+ ap response.inspect
88
+
89
+ if !!response.error
90
+ 'Unable to comment right now. Maybe I already commented on that. Try again later.'
91
+ elsif !!response.result.id
92
+ "Wrote #{template} comment on: #{post.title} by #{author_name}"
93
+ else
94
+ ':question:'
95
+ end
96
+ end
97
+
98
+ def merge(template, message = nil, username = 'someone', markup = :html)
99
+ @comment_md ||= "support/#{template}.md"
100
+ @comment_body ||= {}
101
+ @comment_body[template] ||= if File.exist?(@comment_md)
102
+ File.read(@comment_md)
103
+ end
104
+
105
+ raise "Cannot read #{template} template or template is empty." unless @comment_body[template].present?
106
+
107
+ merged = @comment_body[template]
108
+ merged = merged.gsub('${message}', message) unless message.nil?
109
+ merged = merged.gsub('${username}', username) unless username.nil?
110
+
111
+ case markup
112
+ when :html then RDiscount.new(merged).to_html
113
+ when :markdown then merged
114
+ end
115
+ end
116
+
117
+ def preview(template, event, message)
118
+ m = "Your Message Here"
119
+ discord_id = event.author.id
120
+
121
+ @author = if (a = Cosgrove::Account.find_by_discord_id(discord_id)).nil?
122
+ event.author.username
123
+ else
124
+ "@#{a.account_name}"
125
+ end
126
+
127
+ if message.present?
128
+ @message = message
129
+ end
130
+
131
+ preview = []
132
+ preview << "Set by **#{@author}**" if @author.present?
133
+ preview << "```md\n"
134
+ preview << merge(template, @message || m, @author, :markdown)
135
+ preview << "\n```"
136
+
137
+ preview.join('')
138
+ end
139
+ end
140
+ end