zmb 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +6 -4
- data/VERSION +1 -1
- data/bin/zmb +94 -64
- data/lib/zmb.rb +100 -9
- data/lib/zmb/plugin.rb +4 -5
- data/lib/zmb/settings.rb +2 -0
- data/lib/zmb/timer.rb +2 -2
- data/lib/zmb/utils.rb +4 -2
- data/plugins/alias.rb +1 -0
- data/plugins/autovoice.rb +38 -0
- data/plugins/commands.rb +18 -7
- data/plugins/dns.rb +12 -3
- data/plugins/file.rb +1 -0
- data/plugins/irc.rb +5 -1
- data/plugins/linecount.rb +1 -0
- data/plugins/log.rb +20 -3
- data/plugins/random.rb +9 -5
- data/plugins/rss.rb +4 -2
- data/plugins/translate.rb +3 -2
- data/plugins/url.rb +4 -5
- data/plugins/usermodes.rb +1 -1
- data/plugins/users.rb +17 -3
- data/plugins/vouch.rb +1 -0
- data/plugins/weather.rb +24 -16
- data/zmb.gemspec +3 -2
- metadata +3 -2
data/README.markdown
CHANGED
@@ -13,7 +13,7 @@ zmb is a complete messenger bot supporting irc, and command line interface.
|
|
13
13
|
|
14
14
|
This command will use the default settings location of ~/.zmb, you can pass `-s <PATH>` to change this.
|
15
15
|
|
16
|
-
zmb --
|
16
|
+
zmb --wizard
|
17
17
|
|
18
18
|
### Launching the bot
|
19
19
|
zmb --daemon
|
@@ -27,7 +27,7 @@ You can run zmb in a shell mode to test plugins without even connecting to any i
|
|
27
27
|
### Included plugins
|
28
28
|
|
29
29
|
- IRC - Connect to a IRC server
|
30
|
-
- Quote
|
30
|
+
- Quote
|
31
31
|
- Poll - Voting system
|
32
32
|
- Relay - Relay between servers and/or channels
|
33
33
|
- Users - User management
|
@@ -39,6 +39,10 @@ You can run zmb in a shell mode to test plugins without even connecting to any i
|
|
39
39
|
- Security - Hashes, rot13, and morse code
|
40
40
|
- Random - Pick a random value from a list, yes or no, coinflip
|
41
41
|
- URL - Dpaste, pastie, bitly, tinyurl, isgd
|
42
|
+
- Translate - Translate a message into another language
|
43
|
+
- RSS - Subscribe and watch RSS/ATOM feeds
|
44
|
+
- Usermodes - Auto opping, voicing users in a channel
|
45
|
+
- Weather - Get the weather for a town/city
|
42
46
|
- Bank - Points system
|
43
47
|
|
44
48
|
#### Other features
|
@@ -50,5 +54,3 @@ You can run zmb in a shell mode to test plugins without even connecting to any i
|
|
50
54
|
### Support
|
51
55
|
|
52
56
|
You can find support at #zmb @ efnet.
|
53
|
-
|
54
|
-
For complete documentation please visit [Documentation](http://kylefuller.co.uk/projects/zmb/)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.
|
1
|
+
0.3.2
|
data/bin/zmb
CHANGED
@@ -6,25 +6,11 @@ require 'zmb'
|
|
6
6
|
require 'optparse'
|
7
7
|
|
8
8
|
class AdminUser
|
9
|
-
|
10
|
-
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
@permissions = ['admin']
|
15
|
-
end
|
16
|
-
|
17
|
-
def admin?
|
18
|
-
true
|
19
|
-
end
|
20
|
-
|
21
|
-
def permission?(perm)
|
22
|
-
true
|
23
|
-
end
|
24
|
-
|
25
|
-
def authenticated?
|
26
|
-
true
|
27
|
-
end
|
9
|
+
def username; 'admin'; end
|
10
|
+
def userhost; 'admin@zmb'; end
|
11
|
+
def admin?; true; end
|
12
|
+
def permission?(perm); true; end
|
13
|
+
def authenticated?; true; end
|
28
14
|
end
|
29
15
|
|
30
16
|
class Event
|
@@ -32,34 +18,63 @@ class Event
|
|
32
18
|
|
33
19
|
def initialize(message)
|
34
20
|
@message = message
|
35
|
-
@user = AdminUser.new
|
36
|
-
end
|
37
|
-
|
38
|
-
def message?
|
39
|
-
true
|
40
|
-
end
|
41
|
-
|
42
|
-
def private?
|
43
|
-
true
|
44
|
-
end
|
45
|
-
|
46
|
-
def reply(msg)
|
47
|
-
puts "> #{msg}"
|
48
21
|
end
|
49
22
|
|
50
|
-
def
|
51
|
-
|
23
|
+
def message?; true; end
|
24
|
+
def private?; true; end
|
25
|
+
def name; "admin"; end
|
26
|
+
def user; AdminUser.new; end
|
27
|
+
def reply(msg); notice "#{msg}"; end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Tty
|
31
|
+
class <<self
|
32
|
+
def blue; color 34; end
|
33
|
+
def red; color 31; end
|
34
|
+
def yellow; color 33 ; end
|
35
|
+
def reset; escape 0; end
|
36
|
+
|
37
|
+
private
|
38
|
+
def color n
|
39
|
+
escape "0;#{n}"
|
40
|
+
end
|
41
|
+
def bold n
|
42
|
+
escape "1;#{n}"
|
43
|
+
end
|
44
|
+
def underline n
|
45
|
+
escape "4;#{n}"
|
46
|
+
end
|
47
|
+
def escape n
|
48
|
+
"\033[#{n}m" if $stdout.tty?
|
49
|
+
end
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
55
|
-
def
|
56
|
-
|
53
|
+
def question(message)
|
54
|
+
print "#{Tty.blue}[ #{Tty.yellow}?? #{Tty.blue}]#{Tty.reset} #{message}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def warning(message)
|
58
|
+
puts "#{Tty.blue}[ #{Tty.yellow}** #{Tty.blue}]#{Tty.reset} #{message}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def error(message)
|
62
|
+
puts "#{Tty.blue}[ #{Tty.red}!! #{Tty.blue}]#{Tty.reset} #{message}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def notice(message)
|
66
|
+
puts "#{Tty.blue}[#{Tty.reset} ok #{Tty.blue}]#{Tty.reset} #{message}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def question_bool(message)
|
70
|
+
question(message + ' (yes/no): ')
|
57
71
|
answer = gets.chomp
|
58
72
|
answer == 'yes' or answer == 'y'
|
59
73
|
end
|
60
74
|
|
61
|
-
def
|
62
|
-
|
75
|
+
def question_string(message='')
|
76
|
+
message += ': ' unless message == ''
|
77
|
+
question(message)
|
63
78
|
answer = gets.chomp
|
64
79
|
|
65
80
|
return nil if answer == ''
|
@@ -69,15 +84,15 @@ end
|
|
69
84
|
def wizard(zmb, plugin)
|
70
85
|
STDOUT.flush
|
71
86
|
|
72
|
-
if
|
87
|
+
if question_bool("Would you like to add the #{plugin.name} plugin? #{plugin.description}") then
|
73
88
|
if plugin.multi_instances? then
|
74
|
-
instance =
|
89
|
+
instance = question_string("What would you like to name this instance of #{plugin.name}?")
|
75
90
|
else
|
76
91
|
instance = plugin.name
|
77
92
|
end
|
78
93
|
|
79
94
|
if not instance then
|
80
|
-
|
95
|
+
notice "Must supply instance name, if this plugin should only be loaded once such as commands or users then you can call it that."
|
81
96
|
return wizard(zmb, plugin)
|
82
97
|
end
|
83
98
|
|
@@ -89,7 +104,7 @@ def wizard(zmb, plugin)
|
|
89
104
|
|
90
105
|
obj.wizard.each do |key, value|
|
91
106
|
if value.has_key?('help') then
|
92
|
-
set =
|
107
|
+
set = question_string("#{value['help']} (default=#{value['default']})")
|
93
108
|
settings[key] = set if set
|
94
109
|
end
|
95
110
|
end
|
@@ -105,6 +120,11 @@ options = {}
|
|
105
120
|
optparse = OptionParser.new do |opts|
|
106
121
|
opts.banner = "Usage: zmb [options]"
|
107
122
|
|
123
|
+
opts.on('-h', '--help', 'Displays this usage screen') do
|
124
|
+
puts optparse
|
125
|
+
exit
|
126
|
+
end
|
127
|
+
|
108
128
|
options[:settings] = nil
|
109
129
|
opts.on('-s', '--settings SETTING', 'Use a settings folder') do |settings|
|
110
130
|
options[:settings] = settings
|
@@ -115,13 +135,13 @@ optparse = OptionParser.new do |opts|
|
|
115
135
|
options[:daemon] = true
|
116
136
|
end
|
117
137
|
|
118
|
-
options[:
|
119
|
-
opts.on('-
|
120
|
-
options[:
|
138
|
+
options[:wizard] = false
|
139
|
+
opts.on('-w', '--wizard', 'Interactively create a new config file') do
|
140
|
+
options[:wizard] = true
|
121
141
|
end
|
122
142
|
|
123
143
|
options[:shell] = false
|
124
|
-
opts.on('-
|
144
|
+
opts.on('-S', '--shell', 'Create a commands shell') do
|
125
145
|
options[:shell] = true
|
126
146
|
end
|
127
147
|
|
@@ -130,9 +150,15 @@ optparse = OptionParser.new do |opts|
|
|
130
150
|
options[:command] = line
|
131
151
|
end
|
132
152
|
|
133
|
-
options[:
|
134
|
-
opts.on('-f', '--
|
135
|
-
options[:
|
153
|
+
options[:foreground] = false
|
154
|
+
opts.on('-f', '--foreground', 'Don\'t fork into the background') do
|
155
|
+
options[:foreground] = true
|
156
|
+
end
|
157
|
+
|
158
|
+
options[:debug] = false
|
159
|
+
opts.on('-D', '--daemon', 'Output debugging information (Implies -f)') do
|
160
|
+
options[:foreground] = true
|
161
|
+
options[:debug] = true
|
136
162
|
end
|
137
163
|
end
|
138
164
|
|
@@ -140,33 +166,33 @@ optparse.parse!
|
|
140
166
|
|
141
167
|
if not options[:settings] then
|
142
168
|
options[:settings] = File.expand_path('~/.zmb')
|
143
|
-
|
169
|
+
notice "No settings file specified, will use #{options[:settings]}"
|
144
170
|
end
|
145
171
|
|
146
172
|
zmb = Zmb.new(options[:settings])
|
147
173
|
|
148
|
-
if options[:
|
174
|
+
if options[:wizard] then
|
149
175
|
STDOUT.flush
|
150
176
|
|
151
177
|
zmb.save
|
152
178
|
|
153
|
-
while
|
154
|
-
source =
|
179
|
+
while question_bool('Would you like to add additional plugin sources?')
|
180
|
+
source = question_string('Which path?')
|
155
181
|
if source and File.exists?(source) then
|
156
182
|
zmb.plugin_manager.add_plugin_source source
|
157
|
-
|
183
|
+
notice "Source added"
|
158
184
|
zmb.save
|
159
185
|
else
|
160
|
-
|
186
|
+
warning "Invalid source directory, does this folder exist?"
|
161
187
|
end
|
162
188
|
end
|
163
189
|
|
164
190
|
zmb.plugin_manager.plugins.reject{ |plugin| zmb.instances.has_key? plugin.name }.each{ |plugin| wizard(zmb, plugin) }
|
165
191
|
|
166
|
-
if zmb.instances.has_key?('users') and
|
167
|
-
username =
|
168
|
-
password =
|
169
|
-
userhost =
|
192
|
+
if zmb.instances.has_key?('users') and question_bool('Would you like to add a admin user?') then
|
193
|
+
username = question_string('Username:')
|
194
|
+
password = question_string('Password:')
|
195
|
+
userhost = question_string('Userhost: (Leave blank for none)')
|
170
196
|
zmb.instances['users'].create_user(username, password, userhost).permit('admin')
|
171
197
|
end
|
172
198
|
|
@@ -183,18 +209,22 @@ if options[:shell] then
|
|
183
209
|
|
184
210
|
begin
|
185
211
|
while 1
|
186
|
-
zmb.event(nil, Event.new(
|
212
|
+
zmb.event(nil, Event.new(question_string))
|
187
213
|
end
|
188
214
|
rescue Interrupt
|
189
215
|
zmb.save
|
216
|
+
puts
|
217
|
+
warning "Exiting"
|
190
218
|
end
|
191
219
|
end
|
192
220
|
|
221
|
+
zmb.debug = options[:debug]
|
222
|
+
|
193
223
|
if options[:daemon] then
|
194
|
-
if options[:
|
195
|
-
Process.detach(fork { STDOUT.reopen(File.open('/dev/null','w')); zmb.run })
|
196
|
-
puts "ZMB detached"
|
197
|
-
else
|
224
|
+
if options[:foreground] then
|
198
225
|
zmb.run
|
226
|
+
warning "Exiting"
|
227
|
+
else
|
228
|
+
notice "Forking into the background (pid: #{zmb.run_fork})"
|
199
229
|
end
|
200
230
|
end
|
data/lib/zmb.rb
CHANGED
@@ -14,16 +14,21 @@ require 'zmb/event'
|
|
14
14
|
require 'zmb/timer'
|
15
15
|
|
16
16
|
class Zmb
|
17
|
-
attr_accessor :instances, :plugin_manager, :settings_manager, :plugin_sources
|
17
|
+
attr_accessor :instances, :plugin_manager, :settings_manager, :plugin_sources, :debug
|
18
18
|
|
19
19
|
def plugin
|
20
20
|
'zmb'
|
21
21
|
end
|
22
22
|
|
23
|
+
def instance
|
24
|
+
'zmb'
|
25
|
+
end
|
26
|
+
|
23
27
|
def initialize(config_dir)
|
24
|
-
@debug =
|
28
|
+
@debug = false
|
29
|
+
@running = false
|
25
30
|
|
26
|
-
@plugin_manager = PluginManager.new
|
31
|
+
@plugin_manager = PluginManager.new(self)
|
27
32
|
@settings_manager = Settings.new(config_dir)
|
28
33
|
|
29
34
|
@instances = {'zmb' => self}
|
@@ -45,8 +50,9 @@ class Zmb
|
|
45
50
|
@plugin_manager.add_plugin_source plugin_dir
|
46
51
|
|
47
52
|
@settings_manager.get('zmb', 'plugin_instances', []).each{|instance| load instance}
|
53
|
+
@debug = @settings_manager.get('zmb', 'debug', false)
|
48
54
|
|
49
|
-
@
|
55
|
+
trap("HUP") { @plugin_manager.refresh_plugin_sources; load "commands"; load "users" }
|
50
56
|
end
|
51
57
|
|
52
58
|
def running?
|
@@ -57,13 +63,39 @@ class Zmb
|
|
57
63
|
{
|
58
64
|
'plugin_sources' => @plugin_sources,
|
59
65
|
'plugin_instances' => @instances.keys,
|
66
|
+
'debug' => @debug,
|
60
67
|
}
|
61
68
|
end
|
62
69
|
|
63
70
|
def save
|
71
|
+
debug(self, "Saving settings")
|
64
72
|
@instances.each{ |k,v| @settings_manager.save(k, v) }
|
65
73
|
end
|
66
74
|
|
75
|
+
def debug(sender, message, exception=nil)
|
76
|
+
return unless @debug
|
77
|
+
line = Array.new
|
78
|
+
|
79
|
+
if sender then
|
80
|
+
if sender.respond_to?('instance') then
|
81
|
+
line << "(#{sender.instance})"
|
82
|
+
elsif sender == self
|
83
|
+
line << "(core)"
|
84
|
+
elsif sender == plugin_manager
|
85
|
+
line << "(plugins)"
|
86
|
+
else
|
87
|
+
line << "(#{sender})"
|
88
|
+
end
|
89
|
+
else
|
90
|
+
line << "(unknown)"
|
91
|
+
end
|
92
|
+
|
93
|
+
line << message
|
94
|
+
line << exception if exception
|
95
|
+
|
96
|
+
puts line.join(' ')
|
97
|
+
end
|
98
|
+
|
67
99
|
def load(key)
|
68
100
|
return true if @instances.has_key?(key)
|
69
101
|
|
@@ -91,6 +123,7 @@ class Zmb
|
|
91
123
|
end
|
92
124
|
|
93
125
|
def run
|
126
|
+
debug(self, 'Start runloop')
|
94
127
|
post! :running, self
|
95
128
|
|
96
129
|
@running = true
|
@@ -100,10 +133,30 @@ class Zmb
|
|
100
133
|
timer_run
|
101
134
|
end
|
102
135
|
rescue Interrupt
|
136
|
+
debug(self, 'Runloop interrupted')
|
103
137
|
save
|
104
138
|
end
|
105
139
|
end
|
106
140
|
|
141
|
+
def run_fork
|
142
|
+
@running = false
|
143
|
+
pid = fork {
|
144
|
+
STDIN.reopen(File.open('/dev/null','r'))
|
145
|
+
STDOUT.reopen(File.open('/dev/null','w'))
|
146
|
+
STDERR.reopen(File.open('/dev/null','w'))
|
147
|
+
run
|
148
|
+
}
|
149
|
+
|
150
|
+
Process.detach(pid)
|
151
|
+
debug(self, 'zmb Forked')
|
152
|
+
pid
|
153
|
+
end
|
154
|
+
|
155
|
+
def fork_command(e=nil)
|
156
|
+
run_fork
|
157
|
+
"Forked"
|
158
|
+
end
|
159
|
+
|
107
160
|
def timeout
|
108
161
|
if timer_timeout > @maximum_timeout
|
109
162
|
if @sockets.size < 1 then
|
@@ -119,6 +172,7 @@ class Zmb
|
|
119
172
|
end
|
120
173
|
|
121
174
|
def socket_add(delegate, socket)
|
175
|
+
debug(delegate, "Socked added")
|
122
176
|
@sockets[socket] = delegate
|
123
177
|
end
|
124
178
|
|
@@ -139,7 +193,9 @@ class Zmb
|
|
139
193
|
result[0].select{|sock| @sockets.has_key?(sock)}.each do |sock|
|
140
194
|
if sock.eof? then
|
141
195
|
@sockets[sock].disconnected(self, sock) if @sockets[sock].respond_to?('disconnected')
|
196
|
+
sock.close
|
142
197
|
socket_delete sock
|
198
|
+
debug(@sockets[sock], "Socket EOF")
|
143
199
|
else
|
144
200
|
@sockets[sock].received(self, sock, sock.gets()) if @sockets[sock].respond_to?('received')
|
145
201
|
end
|
@@ -148,6 +204,7 @@ class Zmb
|
|
148
204
|
end
|
149
205
|
|
150
206
|
def timer_add(timer)
|
207
|
+
debug(timer.delegate, "Timer added (#{timer.symbol})")
|
151
208
|
@timers << timer
|
152
209
|
end
|
153
210
|
|
@@ -161,14 +218,23 @@ class Zmb
|
|
161
218
|
end
|
162
219
|
|
163
220
|
def timer_run
|
164
|
-
@timers.select{|timer| timer.timeout <= 0.0 and timer.respond_to?("fire") }.each
|
221
|
+
@timers.select{|timer| timer.timeout <= 0.0 and timer.respond_to?("fire") }.each do |timer|
|
222
|
+
debug(timer.delegate, "Timer #{timer.symbol} fired")
|
223
|
+
timer.fire(self)
|
224
|
+
end
|
165
225
|
end
|
166
226
|
|
167
227
|
def post(signal, *args)
|
168
228
|
results = Array.new
|
169
229
|
|
170
230
|
@instances.select{|name, instance| instance.respond_to?(signal)}.each do |name, instance|
|
171
|
-
|
231
|
+
begin
|
232
|
+
result = instance.send(signal, *args)
|
233
|
+
break if result == :halt
|
234
|
+
results << result
|
235
|
+
rescue Exception
|
236
|
+
debug(instance, "Sending signal `#{signal}` failed", $!)
|
237
|
+
end
|
172
238
|
end
|
173
239
|
|
174
240
|
results
|
@@ -176,7 +242,11 @@ class Zmb
|
|
176
242
|
|
177
243
|
def post!(signal, *args) # This will exclude the plugin manager
|
178
244
|
@instances.select{|name, instance| instance.respond_to?(signal) and instance != self}.each do |name, instance|
|
179
|
-
|
245
|
+
begin
|
246
|
+
break if instance.send(signal, *args) == :halt
|
247
|
+
rescue Exception
|
248
|
+
debug(instance, "Sending signal `#{signal}` failed", $!)
|
249
|
+
end
|
180
250
|
end
|
181
251
|
end
|
182
252
|
|
@@ -198,8 +268,6 @@ class Zmb
|
|
198
268
|
end
|
199
269
|
|
200
270
|
def event(sender, e)
|
201
|
-
puts e.line if @debug and e.respond_to?('line')
|
202
|
-
|
203
271
|
Thread.new do
|
204
272
|
post! :pre_event, sender, e
|
205
273
|
post! :event, sender, e
|
@@ -221,6 +289,14 @@ class Zmb
|
|
221
289
|
'addsource' => [:addource_command, 1, { :permission => 'admin' }],
|
222
290
|
'refresh' => [:refresh_command, 1, { :permission => 'admin' }],
|
223
291
|
'quit' => [:quit_command, 0, { :permission => 'admin' }],
|
292
|
+
'debug' => [:debug_command, 0, {
|
293
|
+
:permission => 'admin',
|
294
|
+
:help => 'Toggle debug' }],
|
295
|
+
'stdout' => [:stdout_command, {
|
296
|
+
:permission => 'admin',
|
297
|
+
:usage => '/dev/null' }],
|
298
|
+
'fork' => [:fork_command, 0, {
|
299
|
+
:permission => 'admin' }],
|
224
300
|
}
|
225
301
|
end
|
226
302
|
|
@@ -331,4 +407,19 @@ class Zmb
|
|
331
407
|
@running = false
|
332
408
|
instances.keys.each{ |i| unload(i) }
|
333
409
|
end
|
410
|
+
|
411
|
+
def debug_command(e)
|
412
|
+
@debug = (not @debug)
|
413
|
+
|
414
|
+
if @debug then
|
415
|
+
"Debugging enabled"
|
416
|
+
else
|
417
|
+
"Debugging disabled"
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def stdout_command(e, out)
|
422
|
+
STDOUT.reopen(File.open(out,'w'))
|
423
|
+
"STDOUT set to #{out}"
|
424
|
+
end
|
334
425
|
end
|
data/lib/zmb/plugin.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
class PluginManager
|
2
2
|
attr_accessor :plugin_sources, :plugins
|
3
3
|
|
4
|
-
def initialize
|
4
|
+
def initialize(delegate=nil)
|
5
|
+
@delegate = delegate
|
5
6
|
@plugins = Array.new
|
6
7
|
@plugin_sources = Array.new
|
7
8
|
end
|
@@ -22,7 +23,7 @@ class PluginManager
|
|
22
23
|
definition.definitition_file = File.expand_path(file)
|
23
24
|
@plugins << definition
|
24
25
|
rescue Exception
|
25
|
-
|
26
|
+
@delegate.debug(self, "Cannot load source `#{file}`", $!)
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
@@ -35,9 +36,7 @@ class PluginManager
|
|
35
36
|
]
|
36
37
|
|
37
38
|
definition_files.map do |file|
|
38
|
-
|
39
|
-
load_plugin_source(file)
|
40
|
-
end
|
39
|
+
load_plugin_source(file)
|
41
40
|
end
|
42
41
|
end
|
43
42
|
|
data/lib/zmb/settings.rb
CHANGED
data/lib/zmb/timer.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Timer
|
2
2
|
require 'date'
|
3
3
|
|
4
|
-
attr_accessor :delegate
|
4
|
+
attr_accessor :delegate, :symbol
|
5
5
|
|
6
6
|
def initialize(delegate, symbol, interval, repeat=false, data=nil) # interval is in seconds (decimals accepted)
|
7
7
|
@delegate = delegate
|
@@ -21,7 +21,7 @@ class Timer
|
|
21
21
|
@delegate.send @symbol
|
22
22
|
end
|
23
23
|
rescue Exception
|
24
|
-
|
24
|
+
sender.debug(@delegate, "Timer #{@symbol} failed", $!)
|
25
25
|
end
|
26
26
|
|
27
27
|
if not @repeat
|
data/lib/zmb/utils.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
1
3
|
class String
|
2
4
|
def self.random(len=8)
|
3
5
|
characters = ('a'..'z').to_a + ('1'..'9').to_a
|
@@ -112,10 +114,10 @@ class Hash
|
|
112
114
|
map { |k, v|
|
113
115
|
if v.instance_of?(Hash)
|
114
116
|
v.map { |sk, sv|
|
115
|
-
"#{k}[#{sk}]=#{sv}"
|
117
|
+
"#{k}[#{sk}]=#{CGI.escape(sv)}"
|
116
118
|
}.join('&')
|
117
119
|
else
|
118
|
-
"#{k}=#{v}"
|
120
|
+
"#{k}=#{CGI.escape(v)}"
|
119
121
|
end
|
120
122
|
}.join('&')
|
121
123
|
end
|
data/plugins/alias.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
class AutoVoice
|
2
|
+
def initialize(sender, s)
|
3
|
+
@settings = s
|
4
|
+
@settings['noperm'] = true unless @settings.has_key?('noperm')
|
5
|
+
end
|
6
|
+
|
7
|
+
def event(sender, e)
|
8
|
+
if e.command == 'join' and e.respond_to?('user') then
|
9
|
+
if @settings['noperm'] or e.user.permission?('autovoice') then
|
10
|
+
e.delegate.write "MODE #{e.channel} +v #{e.name}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def commands
|
16
|
+
{
|
17
|
+
'autovoice' => [:autovoice, 0, {
|
18
|
+
:permission => 'admin',
|
19
|
+
:help => 'Toggle the permission setting for autovoice'
|
20
|
+
}]
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def autovoice(e)
|
25
|
+
@settings['noperm'] = (not @settings['noperm'])
|
26
|
+
if @settings['noperm'] then
|
27
|
+
"No permissions are required to be autovoiced"
|
28
|
+
else
|
29
|
+
"The permission `autovoice` is required to be autovoiced"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Plugin.define do
|
35
|
+
name 'autovoice'
|
36
|
+
description 'Auto voice everyone joining a channel where zmb has op'
|
37
|
+
object AutoVoice
|
38
|
+
end
|
data/plugins/commands.rb
CHANGED
@@ -125,11 +125,14 @@ class Commands
|
|
125
125
|
elsif c.has_key?(:proc) then
|
126
126
|
c[:proc].call(e, *args)
|
127
127
|
else
|
128
|
+
delegate(self, "Bad command definition (#{cmd})")
|
128
129
|
"Bad command definition"
|
129
130
|
end
|
130
131
|
rescue ArgumentError
|
131
132
|
'incorrect arguments'
|
132
133
|
rescue Exception
|
134
|
+
@delegate.debug(c.has_key?(:instance) ? c[:instance] : self, "Command #{cmd} failed", $!)
|
135
|
+
|
133
136
|
if e.respond_to?('user') and e.user.admin? and e.private? then
|
134
137
|
"#{$!.message}\n#{$!.inspect}\n#{$!.backtrace[0..2].join("\n")}"
|
135
138
|
else
|
@@ -172,13 +175,21 @@ class Commands
|
|
172
175
|
'help' => :help,
|
173
176
|
'instance' => [:instance_command, { :help => 'List all commands availible for a instance.'}],
|
174
177
|
'which' => [:which, { :help => 'Find which plugin handles a command' }],
|
175
|
-
'cc' => [:control_command, {
|
176
|
-
|
177
|
-
|
178
|
-
'
|
179
|
-
|
180
|
-
|
181
|
-
'
|
178
|
+
'cc' => [:control_command, {
|
179
|
+
:permission => 'admin',
|
180
|
+
:help => 'Set the control character for commands' }],
|
181
|
+
'eval' => [:evaluate, {
|
182
|
+
:permission => 'admin',
|
183
|
+
:help => 'Evaluate ruby code' }],
|
184
|
+
'ieval' => [:instance_evaluate, 2, {
|
185
|
+
:permission => 'admin',
|
186
|
+
:help => 'Evaluate ruby on on a instance',
|
187
|
+
:usage => 'commands @cc' }],
|
188
|
+
'count' => [lambda { |e, data| "#{data.split_seperators.size}" }, {
|
189
|
+
:help => 'Count the amount of items in a list' } ],
|
190
|
+
'grep' => [:grep, 2, { :help => 'print lines matching a pattern' } ],
|
191
|
+
'not' => [:not_command, 2, { :help => 'Opposite to grep' } ],
|
192
|
+
'tail' => [:tail, { :help => 'List the last three items in a list' }],
|
182
193
|
'echo' => [:echo, { :example => 'Hello, {username}' }],
|
183
194
|
'reverse' => lambda { |e, data| data.reverse },
|
184
195
|
'first' => lambda { |e, data| data.split_seperators.first },
|
data/plugins/dns.rb
CHANGED
@@ -6,9 +6,18 @@ class DNS
|
|
6
6
|
|
7
7
|
def commands
|
8
8
|
{
|
9
|
-
'dns' => lambda { |e, host| Resolv.new.getaddress(host) },
|
10
|
-
|
11
|
-
|
9
|
+
'dns' => [lambda { |e, host| Resolv.new.getaddress(host) }, {
|
10
|
+
:help => 'Lookup the ip address for a domain',
|
11
|
+
:usage => 'domain',
|
12
|
+
:example => 'apple.com' }],
|
13
|
+
'rdns' => [:rdns, {
|
14
|
+
:help => 'Perform a reverse dns on a host',
|
15
|
+
:usage => 'ip',
|
16
|
+
:example => '17.149.160.49' }],
|
17
|
+
'whois' => [:whois, 1, {
|
18
|
+
:help => 'perform a whois on a domain',
|
19
|
+
:usage => 'domain',
|
20
|
+
:example => 'apple.com' }],
|
12
21
|
}
|
13
22
|
end
|
14
23
|
|
data/plugins/file.rb
CHANGED
data/plugins/irc.rb
CHANGED
@@ -4,6 +4,7 @@ require 'zmb/timer'
|
|
4
4
|
|
5
5
|
class Event
|
6
6
|
attr_accessor :delegate, :command, :args, :name, :userhost, :message, :channel, :line
|
7
|
+
attr_accessor :nick
|
7
8
|
|
8
9
|
def initialize(sender, line)
|
9
10
|
@line = line
|
@@ -34,7 +35,7 @@ class Event
|
|
34
35
|
@name, @userhost = @userhost.split('!', 2)
|
35
36
|
when 'kick'
|
36
37
|
@userhost, @channel, @name, @message = args.split(' ', 4)
|
37
|
-
nick, @userhost = @userhost.split('!', 2)
|
38
|
+
@nick, @userhost = @userhost.split('!', 2)
|
38
39
|
@message = @message[1..-1]
|
39
40
|
end
|
40
41
|
end
|
@@ -206,8 +207,10 @@ class IrcConnection
|
|
206
207
|
|
207
208
|
def write(line)
|
208
209
|
begin
|
210
|
+
@delegate.debug(self, "> #{line}")
|
209
211
|
@socket.write line + "\r\n" if @socket
|
210
212
|
rescue
|
213
|
+
@delegate.debug(self, "Disconnected at write")
|
211
214
|
disconnected(self, nil)
|
212
215
|
end
|
213
216
|
end
|
@@ -235,6 +238,7 @@ class IrcConnection
|
|
235
238
|
@buffer = '' if @buffer == nil
|
236
239
|
|
237
240
|
while line != nil do
|
241
|
+
@delegate.debug(self, "< #{line}")
|
238
242
|
e = Event.new(self, line)
|
239
243
|
|
240
244
|
# Catch some events
|
data/plugins/linecount.rb
CHANGED
data/plugins/log.rb
CHANGED
@@ -11,15 +11,32 @@ class Log
|
|
11
11
|
File.join(path, "#{channel}-#{Time.now.strftime('%d%m%Y')}.log")
|
12
12
|
end
|
13
13
|
|
14
|
+
def time
|
15
|
+
t = Time.now
|
16
|
+
"#{sprintf("%.2i", t.hour)}:#{sprintf("%.2i", t.min)}:#{sprintf("%.2i", t.sec)}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def log(instance, channel, message)
|
20
|
+
File.open(log_file(instance, channel), 'a+') { |f| f.write("[#{time}] #{message}" + "\n") }
|
21
|
+
end
|
22
|
+
|
14
23
|
def event(sender, e)
|
15
|
-
if e.respond_to?('channel') and e.
|
16
|
-
|
24
|
+
if e.respond_to?('channel') and e.channel then
|
25
|
+
if e.command == 'join' then
|
26
|
+
log(e.delegate.instance, e.channel, "*** Joins: #{e.name} (#{e.userhost})")
|
27
|
+
elsif e.command == 'part'
|
28
|
+
log(e.delegate.instance, e.channel, "*** Parts: #{e.name} (#{e.userhost})")
|
29
|
+
elsif e.command == 'kick'
|
30
|
+
log(e.delegate.instance, e.channel, "*** #{e.nick} was kicked by #{e.name} (#{e.message})")
|
31
|
+
elsif e.message?
|
32
|
+
log(e.delegate.instance, e.channel, "#{e.name}: #{e.message}")
|
33
|
+
end
|
17
34
|
end
|
18
35
|
end
|
19
36
|
end
|
20
37
|
|
21
38
|
Plugin.define do
|
22
39
|
name 'log'
|
23
|
-
description '
|
40
|
+
description 'Log every message to a log file'
|
24
41
|
object Log
|
25
42
|
end
|
data/plugins/random.rb
CHANGED
@@ -3,11 +3,14 @@ class Random
|
|
3
3
|
|
4
4
|
def commands
|
5
5
|
{
|
6
|
-
'random' => :random,
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
'
|
6
|
+
'random' => [:random, {
|
7
|
+
:help => 'Select a item randomly from a list',
|
8
|
+
:usage => 'item 1, item 2, item 3',
|
9
|
+
:example => 'egg, tomatoe, sausage' }],
|
10
|
+
'yesno' => [:yesno, 0, { :help => 'yes or no?' }],
|
11
|
+
'headstails' => [:coinflip, 0, { :help => 'Flip a coin' }],
|
12
|
+
'coinflip' => [:coinflip, 0, { :help => 'Flip a coin' }],
|
13
|
+
'dice' => [lambda { |e| "#{rand(6) + 1}" }, 0, { :help => 'Roll a dice' }],
|
11
14
|
}
|
12
15
|
end
|
13
16
|
|
@@ -27,5 +30,6 @@ end
|
|
27
30
|
|
28
31
|
Plugin.define do
|
29
32
|
name 'random'
|
33
|
+
description 'Commands for coinflips, dice, yesno, random'
|
30
34
|
object Random
|
31
35
|
end
|
data/plugins/rss.rb
CHANGED
@@ -27,16 +27,17 @@ class Feeds
|
|
27
27
|
rss = RSS::Parser.parse(feed['feed'].get().body, false)
|
28
28
|
rss.items.each do |item|
|
29
29
|
link = item.link.to_s
|
30
|
-
link = $1 if link =~
|
30
|
+
link = $1 if link =~ /\b(([\w-]+:\/\/?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|\/)))/
|
31
31
|
|
32
32
|
break if feed['link'] == link
|
33
33
|
@delegate.instances[feed['instance']].message(feed['sender'], "RSS: #{item.title.to_s.gsub(/<\/?[^>]*>/, "")} (#{link})")
|
34
34
|
end
|
35
35
|
|
36
36
|
link = rss.items[0].link.to_s
|
37
|
-
link = $1 if link =~
|
37
|
+
link = $1 if link =~ /\b(([\w-]+:\/\/?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|\/)))/
|
38
38
|
feed['link'] = link
|
39
39
|
rescue Exception
|
40
|
+
@delegate.debug(self, "Feed #{feed['feed']} failed", $!)
|
40
41
|
@delegate.instances[feed['instance']].message(feed['sender'], "RSS: #{feed['feed']} failed")
|
41
42
|
end
|
42
43
|
end
|
@@ -112,5 +113,6 @@ end
|
|
112
113
|
|
113
114
|
Plugin.define do
|
114
115
|
name 'rss'
|
116
|
+
description 'Subscribe and watch RSS/ATOM feeds'
|
115
117
|
object Feeds
|
116
118
|
end
|
data/plugins/translate.rb
CHANGED
@@ -13,8 +13,8 @@ class Translate
|
|
13
13
|
def translate(e, to, from, message)
|
14
14
|
request = JSON.parse("http://ajax.googleapis.com/ajax/services/language/translate".get({
|
15
15
|
:v => '1.0',
|
16
|
-
:key => '
|
17
|
-
:langpair => "#{to}
|
16
|
+
:key => 'ABQIAAAAtWAKkpCW6vL4tULVhqm_ZxQVlmwHIG1k2CM6GldPK9kOhyhAchSCYvJg4eFXVQuYYE7r4s1oNbga9A',
|
17
|
+
:langpair => "#{to}|#{from}",
|
18
18
|
:q => message
|
19
19
|
}).body)
|
20
20
|
|
@@ -30,5 +30,6 @@ end
|
|
30
30
|
|
31
31
|
Plugin.define do
|
32
32
|
name 'translate'
|
33
|
+
description 'Translate a message into another language'
|
33
34
|
object Translate
|
34
35
|
end
|
data/plugins/url.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'uri'
|
2
2
|
require 'net/http'
|
3
|
-
require 'cgi'
|
4
3
|
|
5
4
|
class URL
|
6
5
|
def initialize(sender, s) ;end
|
@@ -9,9 +8,9 @@ class URL
|
|
9
8
|
{
|
10
9
|
'head' => :head,
|
11
10
|
'url' => [:get, { :permission => 'admin' }],
|
12
|
-
'bitly' => :bitly,
|
13
|
-
'isgd' => :isgd,
|
14
|
-
'tinyurl' => :tinyurl,
|
11
|
+
'bitly' => [:bitly, { :help => 'Shorten a URL' }],
|
12
|
+
'isgd' => [:isgd, { :help => 'Shorten a URL' }],
|
13
|
+
'tinyurl' => [:tinyurl, { :help => 'Shorten a URL' }],
|
15
14
|
'dpaste' => :dpaste,
|
16
15
|
'pastie' => :pastie_command,
|
17
16
|
'ppastie' => [:private_pastie_command, { :help => 'Create a private pastie' }],
|
@@ -54,7 +53,7 @@ class URL
|
|
54
53
|
|
55
54
|
def pastie(data, is_private=false, format='plaintext')
|
56
55
|
resp, body = 'http://pastie.org/pastes'.post({ :paste => {
|
57
|
-
:body =>
|
56
|
+
:body => data,
|
58
57
|
:parser => format,
|
59
58
|
:restricted => is_private,
|
60
59
|
:authorization => 'burger'
|
data/plugins/usermodes.rb
CHANGED
@@ -27,7 +27,7 @@ class Usermodes
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def event(sender, e)
|
30
|
-
if e.command == 'join' and e.respond_to?('user') then
|
30
|
+
if e.command == 'join' and e.respond_to?('user') and not e.user.anonymous? then
|
31
31
|
if @usermodes.has_key?(k = e.delegate.instance + ':' + e.channel) then
|
32
32
|
if @usermodes[k].has_key?(e.user.username) then
|
33
33
|
@usermodes[k][e.user.username].each{ |mode| e.delegate.write "MODE #{e.channel} +#{mode} #{e.name}" }
|
data/plugins/users.rb
CHANGED
@@ -238,7 +238,7 @@ class Users
|
|
238
238
|
def pre_event(sender, e)
|
239
239
|
e.users = self
|
240
240
|
e.user = user(e.userhost, true) if not e.user and e.respond_to?('userhost')
|
241
|
-
e.user.saw(e)
|
241
|
+
e.user.saw(e) unless e.user.anonymous?
|
242
242
|
end
|
243
243
|
|
244
244
|
def commands
|
@@ -322,7 +322,11 @@ class Users
|
|
322
322
|
'networks' => [:networks, 0, { :help => 'List all availible networks' }],
|
323
323
|
'profile' => [:profile, 1, {
|
324
324
|
:permission => 'authenticated' }],
|
325
|
-
'message' => [:message, 2, { :permission => 'authenticated' }]
|
325
|
+
'message' => [:message, 2, { :permission => 'authenticated' }],
|
326
|
+
'broadcast' => [:broadcast, {
|
327
|
+
:permission => 'admin',
|
328
|
+
:help => 'Broadcast a message to every user',
|
329
|
+
:usage => 'message' }],
|
326
330
|
}
|
327
331
|
end
|
328
332
|
|
@@ -455,7 +459,9 @@ class Users
|
|
455
459
|
end
|
456
460
|
|
457
461
|
def seen(e, username)
|
458
|
-
if
|
462
|
+
if username == e.user.username then
|
463
|
+
"Are you looking for yourself?"
|
464
|
+
elsif user = user!(username) and user.seen then
|
459
465
|
"#{username} last seen #{user.seen.since_words}"
|
460
466
|
else
|
461
467
|
"#{username} has never been seen"
|
@@ -558,6 +564,14 @@ class Users
|
|
558
564
|
"no such user"
|
559
565
|
end
|
560
566
|
end
|
567
|
+
|
568
|
+
def broadcast(e, message)
|
569
|
+
@users.each do |user|
|
570
|
+
user.send(message)
|
571
|
+
end
|
572
|
+
|
573
|
+
"Message broadcasted to all #{@users.count} users"
|
574
|
+
end
|
561
575
|
end
|
562
576
|
|
563
577
|
Plugin.define do
|
data/plugins/vouch.rb
CHANGED
data/plugins/weather.rb
CHANGED
@@ -6,7 +6,10 @@ class Weather
|
|
6
6
|
|
7
7
|
def commands
|
8
8
|
{
|
9
|
-
'weather' => [:weather, 1
|
9
|
+
'weather' => [:weather, 1, {
|
10
|
+
:help => 'Check the weather',
|
11
|
+
:usage => 'location',
|
12
|
+
:example => 'Cupertino' }],
|
10
13
|
}
|
11
14
|
end
|
12
15
|
|
@@ -21,28 +24,33 @@ class Weather
|
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
24
|
-
location = location.sub(' ', '+')
|
25
27
|
xml_data = 'http://www.google.com/ig/api'.get({ :weather => location }).body
|
26
28
|
doc = REXML::Document.new(xml_data)
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
30
|
+
begin
|
31
|
+
info = doc.root.elements['weather/forecast_information']
|
32
|
+
city = info.elements['city'].attributes['data']
|
33
|
+
|
34
|
+
current = doc.root.elements['weather/current_conditions']
|
35
|
+
condition = current.elements['condition'].attributes['data']
|
36
|
+
temp_c = current.elements['temp_c'].attributes['data']
|
37
|
+
temp_f = current.elements['temp_f'].attributes['data']
|
38
|
+
humidity = current.elements['humidity'].attributes['data']
|
39
|
+
wind_condition = current.elements['wind_condition'].attributes['data']
|
40
|
+
|
41
|
+
tomorrow = doc.root.elements['weather/forecast_conditions']
|
42
|
+
tomorrow_cond = tomorrow.elements['condition'].attributes['data']
|
43
|
+
|
44
|
+
"#{condition} #{temp_c}C/#{temp_f}F #{humidity} #{wind_condition} for #{city}\n"+
|
45
|
+
"Forcast for tomorrow #{tomorrow_cond}"
|
46
|
+
rescue NoMethodError
|
47
|
+
"Command failed, maybe the location is invalid?"
|
48
|
+
end
|
42
49
|
end
|
43
50
|
end
|
44
51
|
|
45
52
|
Plugin.define do
|
46
53
|
name 'weather'
|
54
|
+
description 'Get the weather for a town/city'
|
47
55
|
object Weather
|
48
56
|
end
|
data/zmb.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{zmb}
|
8
|
-
s.version = "0.3.
|
8
|
+
s.version = "0.3.2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["kylef"]
|
12
|
-
s.date = %q{2010-07-
|
12
|
+
s.date = %q{2010-07-18}
|
13
13
|
s.default_executable = %q{zmb}
|
14
14
|
s.description = %q{ZMB, messenger bot}
|
15
15
|
s.email = %q{inbox@kylefuller.co.uk}
|
@@ -35,6 +35,7 @@ Gem::Specification.new do |s|
|
|
35
35
|
"plugins/alias.rb",
|
36
36
|
"plugins/announce.rb",
|
37
37
|
"plugins/autorejoin.rb",
|
38
|
+
"plugins/autovoice.rb",
|
38
39
|
"plugins/bank.rb",
|
39
40
|
"plugins/commands.rb",
|
40
41
|
"plugins/countdown.rb",
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zmb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- kylef
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-07-
|
12
|
+
date: 2010-07-18 00:00:00 +01:00
|
13
13
|
default_executable: zmb
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -58,6 +58,7 @@ files:
|
|
58
58
|
- plugins/alias.rb
|
59
59
|
- plugins/announce.rb
|
60
60
|
- plugins/autorejoin.rb
|
61
|
+
- plugins/autovoice.rb
|
61
62
|
- plugins/bank.rb
|
62
63
|
- plugins/commands.rb
|
63
64
|
- plugins/countdown.rb
|