zmb 0.3.1 → 0.3.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.
- 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
|