cosgrove 0.0.1rc1

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