bitbckt-botbckt 0.3.0 → 0.4.0
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.
- data/VERSION.yml +1 -1
- data/bin/botbckt +16 -0
- data/lib/botbckt/bot.rb +87 -10
- data/lib/botbckt/cmd.rb +86 -0
- data/lib/botbckt/command.rb +19 -7
- data/lib/botbckt/commands/gooble.rb +114 -0
- data/lib/botbckt/commands/remind.rb +41 -5
- data/lib/botbckt/commands/star_jar.rb +12 -18
- data/lib/botbckt/commands/weather.rb +1 -2
- data/lib/botbckt/irc.rb +3 -9
- data/lib/botbckt/store.rb +33 -0
- data/lib/botbckt.rb +2 -2
- metadata +9 -5
data/VERSION.yml
CHANGED
data/bin/botbckt
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'pathname'
|
5
|
+
local_lib = Pathname.new(__FILE__).expand_path.dirname.join('..', 'lib', 'botbckt.rb')
|
6
|
+
if local_lib.file?
|
7
|
+
require local_lib
|
8
|
+
else
|
9
|
+
require 'botbckt'
|
10
|
+
end
|
11
|
+
rescue LoadError
|
12
|
+
require 'rubygems'
|
13
|
+
require 'botbckt'
|
14
|
+
end
|
15
|
+
|
16
|
+
Botbckt::Cmd.run(ARGV)
|
data/lib/botbckt/bot.rb
CHANGED
@@ -3,12 +3,14 @@ module Botbckt #:nodoc:
|
|
3
3
|
# Create a new IRC bot. See Bot.start to get started.
|
4
4
|
#
|
5
5
|
class Bot
|
6
|
+
include Singleton
|
7
|
+
include ActiveSupport::BufferedLogger::Severity
|
6
8
|
|
7
9
|
AFFIRMATIVE = ["'Sea, mhuise.", "In Ordnung", "Ik begrijp", "Alles klar", "Ok.", "Roger.", "You don't have to tell me twice.", "Ack. Ack.", "C'est bon!"]
|
8
10
|
NEGATIVE = ["Titim gan éirí ort.", "Gabh mo leithscéal?", "No entiendo", "excusez-moi", "Excuse me?", "Huh?", "I don't understand.", "Pardon?", "It's greek to me."]
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
|
12
|
+
attr_accessor :logger
|
13
|
+
attr_accessor :store
|
12
14
|
|
13
15
|
# ==== Parameters
|
14
16
|
# options<Hash{Symbol => String,Integer}>
|
@@ -21,22 +23,90 @@ module Botbckt #:nodoc:
|
|
21
23
|
# :channels<Array[String]>:: An array of channels to join. Channel names should *not* include the '#' prefix. Required.
|
22
24
|
# :log<String>:: The name of a log file. Defaults to 'botbckt.log'.
|
23
25
|
# :log_level<Integer>:: The minimum severity level to log. Defaults to 1 (INFO).
|
26
|
+
# :pid<String>:: The name of a file to drop the PID. Defaults to 'botbckt.pid'.
|
27
|
+
# :daemonize<Boolean>:: Fork and background the process. Defaults to true.
|
28
|
+
# :backend_host<String>:: The hostname of a Redis store. Optional.
|
29
|
+
# :backend_port<Integer>:: The port used by the Redis store. Optional.
|
24
30
|
#
|
25
31
|
def self.start(options)
|
26
|
-
|
27
|
-
|
32
|
+
|
33
|
+
self.instance.logger = ActiveSupport::BufferedLogger.new options.delete(:log) || 'botbckt.log',
|
34
|
+
options.delete(:log_level) || INFO
|
35
|
+
|
36
|
+
host, port = options.delete(:backend_host), options.delete(:backend_port)
|
37
|
+
daemonize = options.delete(:daemonize)
|
38
|
+
pid = options.delete(:pid) || 'botbckt.pid'
|
39
|
+
|
40
|
+
if daemonize || daemonize.nil?
|
41
|
+
EventMachine::fork_reactor do
|
42
|
+
Botbckt::IRC.connect(options)
|
43
|
+
|
44
|
+
if host && port
|
45
|
+
self.instance.store = Store.new(host, port)
|
46
|
+
end
|
47
|
+
|
48
|
+
if pid
|
49
|
+
File.open(pid, 'w'){ |f| f.write("#{Process.pid}") }
|
50
|
+
at_exit { File.delete(pid) if File.exist?(pid) }
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
else
|
55
|
+
EventMachine::run do
|
56
|
+
Botbckt::IRC.connect(options)
|
57
|
+
end
|
28
58
|
end
|
29
59
|
end
|
30
60
|
|
61
|
+
# Registers a new command.
|
62
|
+
#
|
63
|
+
# ==== Parameters
|
64
|
+
# command<~to_sym>:: Trigger to register. Required.
|
65
|
+
# callable<~call>:: Callback or class with #call to execute. Required.
|
66
|
+
#
|
67
|
+
def register(command, callable)
|
68
|
+
@commands ||= { }
|
69
|
+
@commands[command.to_sym] = callable
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns currently registered commands.
|
73
|
+
#
|
74
|
+
def commands
|
75
|
+
@commands
|
76
|
+
end
|
77
|
+
|
78
|
+
#--
|
79
|
+
# TODO: Forwardable?
|
80
|
+
#++
|
81
|
+
def set(key, value, &block)
|
82
|
+
self.store && self.store.set(key, value, &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
#--
|
86
|
+
# TODO: Forwardable?
|
87
|
+
#++
|
88
|
+
def get(key, &block)
|
89
|
+
self.store && self.store.get(key, &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
#--
|
93
|
+
# TODO: Forwardable?
|
94
|
+
#++
|
95
|
+
def increment!(key, &block)
|
96
|
+
self.store && self.store.increment!(key, &block)
|
97
|
+
end
|
98
|
+
|
31
99
|
# ==== Parameters
|
32
100
|
# command<Symbol>:: The name of a registered command to run. Required.
|
101
|
+
# sender<String>:: The sender (incl. hostmask) of the trigger. Required.
|
102
|
+
# channel<String>:: The channel on which the command was triggered. Required.
|
33
103
|
# *args:: Arguments to be passed to the command. Optional.
|
34
104
|
#
|
35
105
|
#--
|
36
106
|
# TODO: Before/after callbacks?
|
37
107
|
#++
|
38
|
-
def
|
39
|
-
callable =
|
108
|
+
def run(command, sender, channel, *args)
|
109
|
+
callable = commands[command.to_sym]
|
40
110
|
|
41
111
|
if callable.is_a?(Class)
|
42
112
|
# Callables are Singletons; we use #create! as a convention to give
|
@@ -44,7 +114,10 @@ module Botbckt #:nodoc:
|
|
44
114
|
callable = callable.create!(sender, channel, *args)
|
45
115
|
end
|
46
116
|
|
47
|
-
callable.respond_to?(:call) ? callable.call(sender, channel, *args) : say(befuddled, channel)
|
117
|
+
callable.respond_to?(:call) ? callable.call(sender, channel, *args) : say(Bot.befuddled, channel)
|
118
|
+
# TODO: Log me.
|
119
|
+
rescue StandardError => e
|
120
|
+
say Bot.befuddled, channel
|
48
121
|
end
|
49
122
|
|
50
123
|
# Returns a random "affirmative" message. Use to acknowledge user input.
|
@@ -70,10 +143,14 @@ module Botbckt #:nodoc:
|
|
70
143
|
# msg<String>:: A message to send to the channel
|
71
144
|
# channel<String>:: The channel to send the message. Required.
|
72
145
|
#
|
73
|
-
def
|
146
|
+
def say(msg, channel)
|
74
147
|
Botbckt::IRC.connection.say msg, channel
|
75
148
|
end
|
149
|
+
|
150
|
+
def log(msg, level = INFO) #:nodoc:
|
151
|
+
self.logger.add(level, msg)
|
152
|
+
end
|
76
153
|
|
77
154
|
end
|
78
155
|
|
79
|
-
end
|
156
|
+
end
|
data/lib/botbckt/cmd.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module Botbckt #:nodoc:
|
2
|
+
|
3
|
+
class Cmd
|
4
|
+
|
5
|
+
SEVERITY = %w{0 1 2 3 4 5}
|
6
|
+
SEVERITY_ALIASES = { "DEBUG" => 0, "INFO" => 1, "WARN" => 2, "ERROR" => 3, "FATAL" => 4, "UNKNOWN" => 5}
|
7
|
+
|
8
|
+
def self.run(args)
|
9
|
+
Botbckt::Bot.start parse(args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.parse(args)
|
13
|
+
options = {}
|
14
|
+
options[:user] = 'botbckt'
|
15
|
+
options[:port] = 6667
|
16
|
+
options[:channels] = []
|
17
|
+
options[:backend_port] = 6379
|
18
|
+
|
19
|
+
opts = OptionParser.new do |opts|
|
20
|
+
opts.banner = 'Usage: botbckt [options]'
|
21
|
+
|
22
|
+
opts.separator ''
|
23
|
+
opts.separator 'Options:'
|
24
|
+
|
25
|
+
opts.on('-u', '--user [USER]', 'Username to identify as. (default: botbckt)') do |user|
|
26
|
+
options[:user] = user
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on('-p', '--password [PASSWORD]', 'Password to authenticate with.') do |pass|
|
30
|
+
options[:password] = pass
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on('-s', '--server SERVER', 'Address of the IRC server.') do |server|
|
34
|
+
options[:server] = server
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on('-P', '--port [PORT]', Integer, 'Port of the IRC server. (default: 6667)') do |port|
|
38
|
+
options[:port] = port
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('-c', '--channels [x,y,z]', Array,
|
42
|
+
'Comma-separated list of channels to JOIN. Do not include the # prefix.') do |channels|
|
43
|
+
options[:channels] = channels
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on('-l', '--logfile [FILE]', 'Log file. (default botbckt.log)') do |log|
|
47
|
+
options[:log] = log
|
48
|
+
end
|
49
|
+
|
50
|
+
severities = SEVERITY_ALIASES.keys.join(', ')
|
51
|
+
opts.on('-V', '--verbosity [LEVEL]', SEVERITY, SEVERITY_ALIASES, Integer,
|
52
|
+
"Minimum severity level to log.Accepted values are: #{severities}. (default: INFO)") do |level|
|
53
|
+
options[:log_level] = level
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on('-b', '--backend-host [SERVER]', 'Address of a Redis server.') do |backend|
|
57
|
+
options[:backend_host] = backend
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on('-B', '--backend-port [PORT]', Integer, 'Port of a Redis server. (default: 6379)') do |port|
|
61
|
+
options[:backend_port] = port
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on('-D', '--[no-]daemonize', 'Fork and run in the background. (default: true)') do |daemon|
|
65
|
+
options[:daemonize] = daemon
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on('-i', '--pid FILE', 'File to store PID (default: botbckt.pid)') do |file|
|
69
|
+
options[:pid] = file
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on_tail('-h', '--help', 'Show this message.') do |help|
|
73
|
+
puts opts
|
74
|
+
exit
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.parse!(args)
|
79
|
+
options
|
80
|
+
rescue OptionParser::ParseError
|
81
|
+
puts opts
|
82
|
+
exit
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
data/lib/botbckt/command.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
-
require 'singleton'
|
2
|
-
|
3
1
|
module Botbckt #:nodoc:
|
4
2
|
|
5
|
-
# This acts as a kind of abstract class for Botbckt commands.
|
6
|
-
#
|
3
|
+
# This acts as a kind of abstract class for Botbckt commands. Subclass
|
4
|
+
# this class to define new bot commands.
|
7
5
|
#
|
8
|
-
# Command subclasses must (re-)define
|
6
|
+
# Command subclasses must (re-)define call. If any setup is needed, override
|
7
|
+
# create! and return self.instance.
|
9
8
|
#
|
10
9
|
class Command
|
11
10
|
include Utilities
|
@@ -39,9 +38,10 @@ module Botbckt #:nodoc:
|
|
39
38
|
#
|
40
39
|
# ==== Parameters
|
41
40
|
# command<Symbol>:: In-channel trigger for the command. Required.
|
41
|
+
# &block:: An optional block to execute, in lieu of call.
|
42
42
|
#
|
43
43
|
def self.trigger(command, &block)
|
44
|
-
Botbckt::Bot.
|
44
|
+
Botbckt::Bot.instance.register(command, block_given? ? block : self)
|
45
45
|
end
|
46
46
|
|
47
47
|
# ==== Parameters
|
@@ -49,7 +49,7 @@ module Botbckt #:nodoc:
|
|
49
49
|
# channel<String>:: The channel to send the message. Required.
|
50
50
|
#
|
51
51
|
def self.say(msg, channel)
|
52
|
-
Botbckt::Bot.say(msg, channel) if msg
|
52
|
+
Botbckt::Bot.instance.say(msg, channel) if msg
|
53
53
|
end
|
54
54
|
|
55
55
|
# Proxy for Command.say
|
@@ -58,6 +58,18 @@ module Botbckt #:nodoc:
|
|
58
58
|
self.class.say(msg, channel)
|
59
59
|
end
|
60
60
|
|
61
|
+
def set(key, value, &block)
|
62
|
+
Botbckt::Bot.instance.set(key, value, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def get(key, &block)
|
66
|
+
Botbckt::Bot.instance.get(key, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
def increment!(key, &block)
|
70
|
+
Botbckt::Bot.instance.increment!(key, &block)
|
71
|
+
end
|
72
|
+
|
61
73
|
end
|
62
74
|
|
63
75
|
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Botbckt #:nodoc:
|
2
|
+
|
3
|
+
# Sends text repeatedly to Google Translate via the JSON API
|
4
|
+
# to garble the output. Translation starts and ends with
|
5
|
+
# MAIN_LANGUAGE unless a language option is passed in. If
|
6
|
+
# an option is present it is used as the starting language.
|
7
|
+
#
|
8
|
+
#
|
9
|
+
# < user> ~gooble We the People of the United States, in Order to form a more perfect Union
|
10
|
+
# < botbckt> Popular in the United States to create the completed
|
11
|
+
#
|
12
|
+
# With language option:
|
13
|
+
#
|
14
|
+
# < user> ~gooble --german Guten tag
|
15
|
+
# < botbckt> Good day
|
16
|
+
#
|
17
|
+
# or the short version
|
18
|
+
#
|
19
|
+
# < user> ~gooble -de Guten tag
|
20
|
+
# < botbckt> Good day
|
21
|
+
|
22
|
+
class Gooble < Command
|
23
|
+
|
24
|
+
MAIN_LANGUAGE = "en"
|
25
|
+
TRANSLATE_ATTEMPTS = 4
|
26
|
+
# Yeah this is wordy but it's self documenting.
|
27
|
+
LANGUAGES =
|
28
|
+
{
|
29
|
+
"Albanian" => "sq",
|
30
|
+
"Arabic" => "ar",
|
31
|
+
"Bulgarian" => "bg",
|
32
|
+
"Catalan" => "ca",
|
33
|
+
"Chinese" => "zh-CN",
|
34
|
+
"Croatian" => "hr",
|
35
|
+
"Czech" => "cs",
|
36
|
+
"Danish" => "da",
|
37
|
+
"Dutch" => "nl",
|
38
|
+
"English" => "en",
|
39
|
+
"Estonian" => "et",
|
40
|
+
"Filipino" => "tl",
|
41
|
+
"Finnish" => "fi",
|
42
|
+
"French" => "fr",
|
43
|
+
"Galician" => "gl",
|
44
|
+
"German" => "de",
|
45
|
+
"Greek" => "el",
|
46
|
+
"Hebrew" => "iw",
|
47
|
+
"Hindi" => "hi",
|
48
|
+
"Hungarian" => "hu",
|
49
|
+
"Indonesian" => "id",
|
50
|
+
"Italian" => "it",
|
51
|
+
"Japanese" => "ja",
|
52
|
+
"Korean" => "ko",
|
53
|
+
"Latvian" => "lv",
|
54
|
+
"Lithuanian" => "lt",
|
55
|
+
"Maltese" => "mt",
|
56
|
+
"Norwegian" => "no",
|
57
|
+
"Polish" => "pl",
|
58
|
+
"Portuguese" => "pt",
|
59
|
+
"Romanian" => "ro",
|
60
|
+
"Russian" => "ru",
|
61
|
+
"Serbian" => "sr",
|
62
|
+
"Slovak" => "sk",
|
63
|
+
"Slovenian" => "sl",
|
64
|
+
"Spanish" => "es",
|
65
|
+
"Swedish" => "sv",
|
66
|
+
"Thai" => "th",
|
67
|
+
"Turkish" => "tr",
|
68
|
+
"Ukrainian" => "uk",
|
69
|
+
"Vietnamese" => "vi"
|
70
|
+
}
|
71
|
+
|
72
|
+
trigger :gooble do |sender, channel, gooble_string|
|
73
|
+
# start with english unless asked for something else
|
74
|
+
gooble_string =~ /(-\w{2}|--\w+)?(.*)/i
|
75
|
+
option, text = $1, $2
|
76
|
+
clean_option = option.gsub(/-/,'') if option
|
77
|
+
|
78
|
+
start_language = case option
|
79
|
+
when /^--/ then
|
80
|
+
LANGUAGES[clean_option.capitalize!] || MAIN_LANGUAGE
|
81
|
+
when /^-/ then
|
82
|
+
LANGUAGES.value?(clean_option) ? clean_option : MAIN_LANGUAGE
|
83
|
+
else
|
84
|
+
MAIN_LANGUAGE
|
85
|
+
end
|
86
|
+
|
87
|
+
# languages to use for the goobling
|
88
|
+
languages = [start_language]
|
89
|
+
languages << LANGUAGES.values.sort_by{ rand }.slice(0...TRANSLATE_ATTEMPTS)
|
90
|
+
languages << MAIN_LANGUAGE # always end with main
|
91
|
+
|
92
|
+
result = gooble(text, languages.flatten)
|
93
|
+
say result, channel
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
# args
|
99
|
+
# text<String>: text to translate
|
100
|
+
# langs<Array>: languages to translate from/to
|
101
|
+
def self.gooble(text, languages) #:nodoc:
|
102
|
+
if languages.length >= 2
|
103
|
+
pair = "#{languages[0]}|#{languages[1]}"
|
104
|
+
json = open("http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=#{CGI.escape(text)}&langpair=#{CGI.escape(pair)}")
|
105
|
+
response = JSON.parse(json.read) # could check for failed response with response['responseStatus'] != 200
|
106
|
+
languages.shift
|
107
|
+
gooble(response['responseData']['translatedText'], languages)
|
108
|
+
else
|
109
|
+
text
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
@@ -7,6 +7,10 @@ module Botbckt #:nodoc:
|
|
7
7
|
# ... five minutes
|
8
8
|
# < botbckt> user: message
|
9
9
|
#
|
10
|
+
# < user> ~remind at 10:30 with message
|
11
|
+
# ... at 10:30
|
12
|
+
# < botbckt> user: message
|
13
|
+
#
|
10
14
|
class Remind < Command
|
11
15
|
|
12
16
|
SCALES = %w{ minute minutes second seconds hour hours }
|
@@ -14,20 +18,52 @@ module Botbckt #:nodoc:
|
|
14
18
|
trigger :remind
|
15
19
|
|
16
20
|
def call(user, channel, reminder_string)
|
21
|
+
|
22
|
+
case reminder_string
|
23
|
+
when /^in/i
|
24
|
+
msg, time = *relative_reminder(reminder_string)
|
25
|
+
when /^at/i
|
26
|
+
msg, time = *absolute_reminder(reminder_string)
|
27
|
+
else
|
28
|
+
say Botbckt::Bot.befuddled, channel
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
if msg && time
|
33
|
+
remind(freenode_split(user).first, channel, msg, time)
|
34
|
+
else
|
35
|
+
say Botbckt::Bot.befuddled, channel
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def relative_reminder(str) #:nodoc:
|
17
42
|
# Somewhat faster than #match...
|
18
|
-
|
43
|
+
str =~ /in (\d+) (\w+) with (.*)/i
|
19
44
|
num, scale, msg = $1, $2, $3
|
20
45
|
|
21
46
|
if SCALES.include?(scale)
|
22
47
|
time = num.to_i.send(scale.to_sym).seconds
|
23
|
-
|
24
|
-
remind(freenode_split(user).first, channel, msg, time)
|
48
|
+
[ msg, time ]
|
25
49
|
else
|
26
|
-
|
50
|
+
[ ]
|
27
51
|
end
|
28
52
|
end
|
29
53
|
|
30
|
-
|
54
|
+
def absolute_reminder(str) #:nodoc:
|
55
|
+
# Somewhat faster than #match...
|
56
|
+
str =~ /at (.*) with (.*)/i
|
57
|
+
time, msg = $1, $2
|
58
|
+
|
59
|
+
time = Time.parse(time) - Time.now
|
60
|
+
|
61
|
+
[ msg, time ]
|
62
|
+
# TODO: Log me.
|
63
|
+
rescue ArgumentError => e
|
64
|
+
# raised by Time.parse; do nothing, for now...
|
65
|
+
[ ]
|
66
|
+
end
|
31
67
|
|
32
68
|
def remind(user, channel, msg, seconds) #:nodoc:
|
33
69
|
EventMachine::Timer.new(seconds) do
|
@@ -13,22 +13,14 @@ module Botbckt #:nodoc:
|
|
13
13
|
class StarJar < Command
|
14
14
|
|
15
15
|
trigger :star
|
16
|
-
|
17
|
-
attr_accessor :jar
|
18
|
-
|
19
|
-
def self.create!(*args) #:nodoc:
|
20
|
-
self.instance.jar ||= { }
|
21
|
-
self.instance
|
22
|
-
end
|
23
16
|
|
24
17
|
# Adds a star to the jar for the user
|
25
18
|
#
|
26
19
|
# ==== Parameters
|
27
20
|
# user<String>:: The user receiving a star. Required.
|
28
21
|
#
|
29
|
-
def push(user)
|
30
|
-
|
31
|
-
@jar[user] += 1
|
22
|
+
def push(user, &block)
|
23
|
+
increment! "starjar-#{user}", &block
|
32
24
|
end
|
33
25
|
|
34
26
|
# Removes a star from the jar for the user
|
@@ -36,21 +28,23 @@ module Botbckt #:nodoc:
|
|
36
28
|
# ==== Parameters
|
37
29
|
# user<String>:: The user being docked a star. Required.
|
38
30
|
#
|
39
|
-
def pop(user)
|
40
|
-
|
31
|
+
def pop(user, &block)
|
32
|
+
get "starjar-#{user}" do |stars|
|
41
33
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
34
|
+
if stars
|
35
|
+
set "starjar-#{user}", stars - 1, &block
|
36
|
+
else
|
37
|
+
set "starjar-#{user}", 0, &block
|
38
|
+
end
|
46
39
|
end
|
47
40
|
end
|
48
41
|
|
49
42
|
def call(giver, channel, receiver)
|
50
43
|
receiver.split(' ').each do |rcv|
|
51
44
|
if rcv != freenode_split(giver).first
|
52
|
-
|
53
|
-
|
45
|
+
push(rcv) do |total|
|
46
|
+
say "#{rcv}: Gold star for you! (#{total})", channel
|
47
|
+
end
|
54
48
|
else
|
55
49
|
say "#{rcv}: No star for you!", channel
|
56
50
|
end
|
@@ -6,7 +6,6 @@ module Botbckt #:nodoc:
|
|
6
6
|
# < botbckt> Today's Forecast: 90F/65F (Sunny)
|
7
7
|
#
|
8
8
|
# < user> ~conditions Los Angeles, CA
|
9
|
-
# < botbckt> Conditions -
|
10
9
|
# < botbckt> Today: Areas of low clouds in the morning then mostly sunny. Highs in the upper 60s to mid 70s. West winds 10 to 20 mph in the afternoon.
|
11
10
|
# < botbckt> Tonight: Mostly clear. Lows in the mid to upper 50s. West winds 10 to 20 mph in the evening.
|
12
11
|
#
|
@@ -37,7 +36,7 @@ module Botbckt #:nodoc:
|
|
37
36
|
daytime = (xml/'forecastday[1]')
|
38
37
|
evening = (xml/'forecastday[2]')
|
39
38
|
|
40
|
-
"
|
39
|
+
"Today: #{(daytime/'fcttext').inner_html}\nTonight: #{(evening/'fcttext').inner_html}"
|
41
40
|
end
|
42
41
|
|
43
42
|
def self.forecast(query) #:nodoc:
|
data/lib/botbckt/irc.rb
CHANGED
@@ -4,7 +4,6 @@ module Botbckt #:nodoc:
|
|
4
4
|
#
|
5
5
|
class IRC < EventMachine::Connection
|
6
6
|
include EventMachine::Protocols::LineText2
|
7
|
-
include ActiveSupport::BufferedLogger::Severity
|
8
7
|
|
9
8
|
attr_accessor :config
|
10
9
|
cattr_accessor :connection
|
@@ -20,8 +19,6 @@ module Botbckt #:nodoc:
|
|
20
19
|
# :server<String>:: The FQDN of the IRC server. Required.
|
21
20
|
# :port<~to_i>:: The port number of the IRC server. Required.
|
22
21
|
# :channels<Array[String]>:: An array of channels to join. Channel names should *not* include the '#' prefix. Required.
|
23
|
-
# :log<String>:: The name of a log file. Defaults to 'botbckt.log'.
|
24
|
-
# :log_level<Integer>:: The minimum severity level to log. Defaults to 1 (INFO).
|
25
22
|
#
|
26
23
|
def self.connect(options)
|
27
24
|
self.connection = EM.connect(options[:server], options[:port].to_i, self, options)
|
@@ -34,9 +31,6 @@ module Botbckt #:nodoc:
|
|
34
31
|
#++
|
35
32
|
def initialize(options) #:nodoc:
|
36
33
|
self.config = OpenStruct.new(options)
|
37
|
-
|
38
|
-
@logger = ActiveSupport::BufferedLogger.new self.config.log || 'botbckt.log',
|
39
|
-
self.config.log_level || INFO
|
40
34
|
end
|
41
35
|
|
42
36
|
# ==== Parameters
|
@@ -66,7 +60,7 @@ module Botbckt #:nodoc:
|
|
66
60
|
args << $5.squish if $5
|
67
61
|
|
68
62
|
# run args: command, sender, channel, optional args
|
69
|
-
Botbckt::Bot.run($4, *args)
|
63
|
+
Botbckt::Bot.instance.run($4, *args)
|
70
64
|
else
|
71
65
|
log line
|
72
66
|
end
|
@@ -81,8 +75,8 @@ module Botbckt #:nodoc:
|
|
81
75
|
|
82
76
|
private
|
83
77
|
|
84
|
-
def log(msg, level = INFO) #:nodoc:
|
85
|
-
|
78
|
+
def log(msg, level = Botbckt::Bot::INFO) #:nodoc:
|
79
|
+
Botbckt::Bot.instance.log msg, level
|
86
80
|
end
|
87
81
|
|
88
82
|
def command(*cmd) #:nodoc:
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'em-redis'
|
2
|
+
|
3
|
+
module Botbckt #:nodoc:
|
4
|
+
|
5
|
+
# Implements a basic key/value store API for cross-session state storage.
|
6
|
+
#
|
7
|
+
# Currently, this class is Redis-backed, but any key/value store could be
|
8
|
+
# supported, in theory.
|
9
|
+
#
|
10
|
+
class Store
|
11
|
+
|
12
|
+
attr_accessor :backend
|
13
|
+
|
14
|
+
def initialize(host, port)
|
15
|
+
self.backend = EventMachine::Protocols::Redis.connect(host, port)
|
16
|
+
end
|
17
|
+
|
18
|
+
def set(key, value, &block)
|
19
|
+
backend.set(key, value, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get(key, &block)
|
23
|
+
backend.get(key, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def increment!(key, &block)
|
27
|
+
backend.incr(key, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
data/lib/botbckt.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
%w{ rubygems eventmachine activesupport ostruct json open-uri cgi hpricot }.each { |lib| require lib }
|
2
|
-
%w{ irc bot utilities command }.each { |lib| require File.dirname(__FILE__) + "/botbckt/#{ lib }" }
|
1
|
+
%w{ rubygems eventmachine activesupport ostruct json open-uri cgi hpricot singleton optparse }.each { |lib| require lib }
|
2
|
+
%w{ irc store bot utilities command cmd }.each { |lib| require File.dirname(__FILE__) + "/botbckt/#{ lib }" }
|
3
3
|
Dir[File.dirname(__FILE__) + '/botbckt/commands/*'].each { |lib| require lib }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bitbckt-botbckt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brandon Mitchell
|
@@ -9,14 +9,14 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
13
|
-
default_executable:
|
12
|
+
date: 2009-06-23 00:00:00 -07:00
|
13
|
+
default_executable: botbckt
|
14
14
|
dependencies: []
|
15
15
|
|
16
16
|
description: Boredom strikes on Sunday mornings.
|
17
17
|
email: brandon@systemisdown.net
|
18
|
-
executables:
|
19
|
-
|
18
|
+
executables:
|
19
|
+
- botbckt
|
20
20
|
extensions: []
|
21
21
|
|
22
22
|
extra_rdoc_files:
|
@@ -24,9 +24,12 @@ extra_rdoc_files:
|
|
24
24
|
files:
|
25
25
|
- Rakefile
|
26
26
|
- VERSION.yml
|
27
|
+
- bin/botbckt
|
27
28
|
- lib/botbckt.rb
|
28
29
|
- lib/botbckt/bot.rb
|
30
|
+
- lib/botbckt/cmd.rb
|
29
31
|
- lib/botbckt/command.rb
|
32
|
+
- lib/botbckt/commands/gooble.rb
|
30
33
|
- lib/botbckt/commands/google.rb
|
31
34
|
- lib/botbckt/commands/meme.rb
|
32
35
|
- lib/botbckt/commands/ping.rb
|
@@ -36,6 +39,7 @@ files:
|
|
36
39
|
- lib/botbckt/commands/ticker.rb
|
37
40
|
- lib/botbckt/commands/weather.rb
|
38
41
|
- lib/botbckt/irc.rb
|
42
|
+
- lib/botbckt/store.rb
|
39
43
|
- lib/botbckt/utilities.rb
|
40
44
|
- README
|
41
45
|
has_rdoc: true
|