bitbckt-botbckt 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|