powerball 0.0.2

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e6306315e116fab566309e8bc942673d43adafab
4
+ data.tar.gz: 8de5d6dc9814d3ede70f1e5786a79f6698a73650
5
+ SHA512:
6
+ metadata.gz: 13910ff1c18a8b95aaa17037bc40318758659e8a2a9e541dfcdc4b9ba6fb7c2a7eb856ea800a4c9078cced069f63c4f638809a2b24406e60c4c743e8f4732527
7
+ data.tar.gz: d23f59b32a778b4ed6b1467098ba28a208699a74ec1fcb323d2e48b0ba64ba55086bb29db8698866960f905263a8cd47e2b9cb8d41b7fa262f85a84d1449399f
@@ -0,0 +1,60 @@
1
+ # powerball
2
+ ## The stupid simple Slack lotto bot.
3
+
4
+ This is a stupid simple Slack bot that just draws a winner each hour from
5
+ a CSV specified list. It's single purpose in design, so it's quite unlikely
6
+ to be of any use to you.
7
+
8
+ ### Configuration
9
+
10
+ `powerball` reads its configuration from `config.yaml` in the current directory. The
11
+ format looks something like this:
12
+
13
+ ``` Yaml
14
+ ---
15
+ :admins:
16
+ - binford2k
17
+ - meg
18
+ - anna
19
+ :channel: apitesting
20
+ :token: "<my slack api token>"
21
+ :starting: "2017-12-07 13:00 UTC"
22
+ ```
23
+
24
+ The attendee input comes (by default) from `attendees.csv` in the current directory. Its
25
+ format is that of an attendee report dumped from EventBrite. The fields we care about can
26
+ be seen in the [source code](https://github.com/binford2k/powerball/blob/master/lib/powerball/lottery.rb).
27
+
28
+ ### Usage
29
+
30
+ All the options from `config.yaml` can also be specified on the command line. Use the `--help`
31
+ flag for usage.
32
+
33
+ ```
34
+ $ powerball --help
35
+
36
+ Usage : powerball [-t <token>] [-c <channel>] [-a <admins>] [--starting '2017-12-07 13:00 UTC']
37
+
38
+ -- Starts the Powerball lotto bot.
39
+ -t, --token TOKEN Slack token.
40
+ -c, --channel CHANNEL Slack channel.
41
+ -a, --admins ADMINS Comma separated list of admin Slack usernames
42
+ --attendees PATH Path to CSV file from EventBrite containing attendees.
43
+ --winners PATH Path to CSV file to save the winners in.
44
+ --starting TIME Time to start the drawing.
45
+
46
+ -h, --help Displays this help
47
+ ```
48
+
49
+ ## Limitations
50
+
51
+ This is super early in development and has not yet been battle tested. It might eat your kitten.
52
+
53
+ ## Disclaimer
54
+
55
+ I take no liability for the use of this tool.
56
+
57
+ Contact
58
+ -------
59
+
60
+ binford2k@gmail.com
@@ -0,0 +1,81 @@
1
+ #! /usr/bin/env ruby
2
+ require 'powerball'
3
+ require 'optparse'
4
+ require 'yaml'
5
+
6
+ options = {
7
+ :attendees => 'attendees.csv',
8
+ :winners => 'winners.csv',
9
+ :channel => 'apitesting',
10
+ }
11
+ options.merge!(YAML.load_file('config.yaml')) rescue nil
12
+ OptionParser.new { |opts|
13
+ opts.banner = "Usage : powerball [-t <token>] [-c <channel>] [-a <admins>] [--starting '2017-12-07 13:00 UTC']
14
+
15
+ -- Starts the Powerball lotto bot.
16
+ "
17
+
18
+ opts.on("-t TOKEN", "--token TOKEN", "Slack token.") do |arg|
19
+ options[:token] = arg
20
+ end
21
+
22
+ opts.on("-c CHANNEL", "--channel CHANNEL", "Slack channel.") do |arg|
23
+ options[:channel] = arg
24
+ end
25
+
26
+ opts.on("-a ADMINS", "--admins ADMINS", "Comma separated list of admin Slack usernames") do |arg|
27
+ options[:admins] = arg.split
28
+ end
29
+
30
+ opts.on("--attendees PATH", "Path to CSV file from EventBrite containing attendees.") do |arg|
31
+ options[:attendees] = arg
32
+ end
33
+
34
+ opts.on("--winners PATH", "Path to CSV file to save the winners in.") do |arg|
35
+ options[:winners] = arg
36
+ end
37
+
38
+ opts.on("--starting TIME", "Time to start the drawing.") do |arg|
39
+ options[:starting] = arg
40
+ end
41
+
42
+ opts.separator('')
43
+
44
+ opts.on("-h", "--help", "Displays this help") do
45
+ puts
46
+ puts opts
47
+ puts
48
+ exit
49
+ end
50
+ }.parse!
51
+
52
+ lottery = Powerball::Lottery.new(options[:attendees], options[:winners])
53
+ slack = Powerball::Slack.new(options[:token], options[:channel], lottery, options[:admins])
54
+
55
+ Thread.new do
56
+ slack.start!
57
+ end
58
+
59
+ if options[:starting]
60
+ time = [Time.parse(options[:starting]) - Time.now, 0].max
61
+
62
+ if time > 86400
63
+ puts "Drawings start in about #{(time/86400).round} days..."
64
+ elsif time > 3600
65
+ puts "Drawings start in about #{(time/3600).round} hours..."
66
+ elsif time > 60
67
+ puts "Drawings start in about #{(time/60).round} minutes..."
68
+ elsif time > 0
69
+ puts "Drawings start in about #{time} seconds..."
70
+ else
71
+ puts "Starting time expired; drawings commence now!"
72
+ end
73
+
74
+ sleep(time)
75
+ end
76
+
77
+ Clockwork.lottery = lottery
78
+ Clockwork::run
79
+
80
+ Thread.join
81
+
@@ -0,0 +1,5 @@
1
+ class Powerball
2
+ require 'powerball/slack'
3
+ require 'powerball/lottery'
4
+ require 'powerball/scheduler'
5
+ end
@@ -0,0 +1,77 @@
1
+ require 'csv'
2
+
3
+ class Powerball::Lottery
4
+ # first = item[2]
5
+ # last = item[3]
6
+ # email = item[4]
7
+ # slack = item[13]
8
+ # company = item[16]
9
+ # addr1 = item[17]
10
+ # addr2 = item[18]
11
+ # city = item[19]
12
+ # state = item[20]
13
+ # zip = item[21]
14
+ # country = item[22]
15
+ # phone = item[23]
16
+
17
+ def initialize(attendees = 'attendees.csv', winners = 'winners.csv')
18
+ @winners = CSV.read(winners) rescue []
19
+ @attendees = CSV.read(attendees) rescue []
20
+ @header = @attendees.shift # remove header
21
+
22
+ @attendees.reject! do |attendee|
23
+ email = attendee[4].strip.downcase
24
+ company = attendee[16].strip.downcase
25
+
26
+ (company == 'puppet' \
27
+ || email.end_with?('@puppet.com') \
28
+ || email.end_with?('@puppetlabs.com'))
29
+ end
30
+
31
+ @attendees.map! do |attendee|
32
+ attendee[13].strip!
33
+ attendee[13].slice!(0) if attendee[13].start_with?('@')
34
+
35
+ attendee
36
+ end
37
+
38
+ puts "INFO: #{@attendees.size} eligible attendees"
39
+ end
40
+
41
+ def chatroom=(chatroom)
42
+ @chatroom = chatroom
43
+ end
44
+
45
+ def drawing
46
+ @chatroom.start_drawing
47
+
48
+ active = @chatroom.active_members
49
+ pool = @attendees.select do |attendee|
50
+ active.include? attendee[13]
51
+ end
52
+
53
+ puts "INFO: eligible and active attendees: #{pool.map {|a| "#{a[2]} #{a[3]} (#{a[13]})" }.inspect}"
54
+
55
+ if pool.size > 0
56
+ winner = pool.sample
57
+ @winners << winner
58
+ @attendees.delete winner
59
+
60
+ @chatroom.post_winner(winner[13])
61
+ @chatroom.alert_admins("New winner: #{winner[2]} #{winner[3]} (#{winner[13]})")
62
+ write_winners
63
+ else
64
+ @chatroom.alert_admins("WARNING: there are no currently eligible active members.")
65
+ end
66
+ end
67
+
68
+ def write_winners
69
+ CSV.open('winners.csv', "wb") do |csv|
70
+ csv << @header # copy the input header
71
+ @winners.each do |row|
72
+ csv << row
73
+ end
74
+ end
75
+ end
76
+
77
+ end
@@ -0,0 +1,15 @@
1
+ require 'clockwork'
2
+ require 'time'
3
+
4
+ module Clockwork
5
+ class << self;
6
+ attr_accessor :lottery
7
+ end
8
+
9
+ handler do |job|
10
+ @lottery.drawing
11
+ end
12
+
13
+ #every(30.seconds, 'frequent.job')
14
+ every(1.hour, 'drawing', :at => '**:00')
15
+ end
@@ -0,0 +1,136 @@
1
+ require 'slack-ruby-client'
2
+
3
+ class Powerball::Slack
4
+
5
+ def initialize(token, channel, lottery, admins = [] )
6
+ Slack.configure do |config|
7
+ config.token = token
8
+ end
9
+
10
+ @client = Slack::RealTime::Client.new
11
+ @lottery = lottery
12
+ @channel = channel
13
+ @admins = admins.map{|a| "@#{a}" }
14
+ @messages = [
15
+ "Yay, it's drawing time!",
16
+ "Oh boy, I get to draw a winner!",
17
+ "You mean I get to pick another winner?! :allthethings:",
18
+ "It's time for another one. :confetti_ball:",
19
+ "Another drawing :game_die:, who's it gonna be?",
20
+ "Party on, Wayne :metal:. Draw us a winner.",
21
+ ]
22
+
23
+ @images = [
24
+ { title: "Powerball!",
25
+ image_url: "https://i.giphy.com/media/Ps8XflhsT5EVa/giphy.gif" },
26
+ { title: "Who's it gonna be?",
27
+ image_url: "http://www.thinkgeek.com/images/products/zoom/2063_critical_hit_led_dice_set.gif" },
28
+ { title: "Let's roll the dice!",
29
+ image_url: "https://studio.code.org/v3/assets/GBhvGLEcbJGFHdJfHkChqw/8TEb9oxGc.gif" },
30
+ { title: "Draw a card, any card...",
31
+ image_url: "https://c1.staticflickr.com/2/1262/1267623453_99002a752a_m.jpg" },
32
+ { title: "Powerball!",
33
+ image_url: "http://a.abcnews.com/images/US/RT_Powerball_Machine_ER_160112_4x3_992.jpg" },
34
+ ]
35
+
36
+ @lottery.chatroom = self
37
+
38
+ @client.on :hello do
39
+ puts "Successfully connected '#{@client.self.name}' to the '#{@client.team.name}' team at https://#{@client.team.domain}.slack.com."
40
+ # bots cannot join channels. They must be invited
41
+ #@client.web_client.channels_join(:name => @channel)
42
+ end
43
+
44
+ @client.on :close do |_data|
45
+ puts 'Connection closing, exiting.'
46
+ end
47
+
48
+ @client.on :closed do |_data|
49
+ puts 'Connection has been disconnected.'
50
+ end
51
+
52
+ @client.on :message do |data|
53
+ puts data
54
+
55
+ case data.text
56
+ when "<@#{@client.self.id}> hi", 'powerball hi', "hi <@#{@client.self.id}>", 'hi powerball' then
57
+ @client.message channel: data.channel, text: "Hi <@#{data.user}>!"
58
+
59
+ when "<@#{@client.self.id}> drawing", 'powerball drawing' then
60
+ username = @client.web_client.users_info(user: data.user)['user']['name']
61
+ next unless admins.include? username
62
+
63
+ @lottery.drawing
64
+ end
65
+ end
66
+ end
67
+
68
+ def start!
69
+ @client.start!
70
+ end
71
+
72
+ def active_members
73
+ begin
74
+ # The api will let you use ID or name, but only if the channel is public!
75
+ list = @client.web_client.conversations_list(:types => 'public_channel, private_channel')
76
+ chan = list['channels'].select { |c| c['name'] == @channel }.first['id']
77
+
78
+ data = @client.web_client.conversations_members( :channel => chan, :limit => 500 )
79
+ data['members'].map do |user|
80
+ # we only want active users!
81
+ next unless @client.web_client.users_getPresence(:user => user)['presence'] == 'active'
82
+
83
+ @client.web_client.users_info(:user => user)['user']['name'] rescue nil
84
+ end.compact
85
+ rescue => e
86
+ puts 'ERROR: cannot list channel members'
87
+ puts e.message
88
+ puts e.backtrace.join "\n"
89
+ []
90
+ end
91
+ end
92
+
93
+ def start_drawing
94
+ begin
95
+ @client.web_client.chat_postMessage({
96
+ channel: @channel,
97
+ text: @messages.sample,
98
+ attachments: [@images.sample],
99
+ as_user: true,
100
+ })
101
+ rescue => e
102
+ puts 'ERROR: cannot start drawing'
103
+ puts e.message
104
+ puts e.backtrace.join "\n"
105
+ end
106
+ end
107
+
108
+ def post_winner(winner)
109
+ begin
110
+ slack = @client.web_client.users_info(user: "@#{winner}")['user']['id']
111
+ @client.web_client.chat_postMessage({
112
+ channel: @channel,
113
+ text: "Congratulations <@#{slack}>, you're our latest winner! :tada: :clap:",
114
+ as_user: true,
115
+ })
116
+ return true
117
+ rescue => e
118
+ puts 'ERROR: drawing incomplete'
119
+ puts e.message
120
+ puts e.backtrace.join "\n"
121
+ return false
122
+ end
123
+ end
124
+
125
+ def alert_admins(message)
126
+ @admins.each do |admin|
127
+ begin
128
+ channel = @client.web_client.im_open(:user => admin)['channel']['id']
129
+ @client.web_client.chat_postMessage({ channel: channel, text: message, as_user: true })
130
+ rescue => e
131
+ puts "ERROR: could not send message to #{admin}"
132
+ end
133
+ end
134
+ end
135
+
136
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: powerball
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Ben Ford
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-12-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: clockwork
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: slack-ruby-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: |2
42
+ This is a stupid simple Slack bot that just draws a winner each hour from
43
+ a CSV specified list. It's single purpose in design, so it's quite unlikely
44
+ to be of any use to you.
45
+ email: binford2k@gmail.com
46
+ executables:
47
+ - powerball
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - README.md
52
+ - bin/powerball
53
+ - lib/powerball.rb
54
+ - lib/powerball/lottery.rb
55
+ - lib/powerball/scheduler.rb
56
+ - lib/powerball/slack.rb
57
+ homepage: https://github.com/binford2k/powerball/
58
+ licenses:
59
+ - Apache-2.0
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 2.6.10
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Slack lotto bot.
81
+ test_files: []