butler 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +4 -0
- data/GPL.txt +340 -0
- data/LICENSE.txt +52 -0
- data/README +37 -0
- data/Rakefile +334 -0
- data/bin/botcontrol +230 -0
- data/data/butler/config_template.yaml +4 -0
- data/data/butler/dialogs/backup.rb +19 -0
- data/data/butler/dialogs/botcontrol.rb +4 -0
- data/data/butler/dialogs/config.rb +1 -0
- data/data/butler/dialogs/create.rb +53 -0
- data/data/butler/dialogs/delete.rb +3 -0
- data/data/butler/dialogs/en/backup.yaml +6 -0
- data/data/butler/dialogs/en/botcontrol.yaml +5 -0
- data/data/butler/dialogs/en/create.yaml +11 -0
- data/data/butler/dialogs/en/delete.yaml +2 -0
- data/data/butler/dialogs/en/help.yaml +17 -0
- data/data/butler/dialogs/en/info.yaml +13 -0
- data/data/butler/dialogs/en/list.yaml +4 -0
- data/data/butler/dialogs/en/notyetimplemented.yaml +2 -0
- data/data/butler/dialogs/en/rename.yaml +3 -0
- data/data/butler/dialogs/en/start.yaml +3 -0
- data/data/butler/dialogs/en/sync_plugins.yaml +3 -0
- data/data/butler/dialogs/en/uninstall.yaml +5 -0
- data/data/butler/dialogs/en/unknown_command.yaml +2 -0
- data/data/butler/dialogs/help.rb +11 -0
- data/data/butler/dialogs/info.rb +27 -0
- data/data/butler/dialogs/interactive.rb +1 -0
- data/data/butler/dialogs/list.rb +10 -0
- data/data/butler/dialogs/notyetimplemented.rb +1 -0
- data/data/butler/dialogs/rename.rb +4 -0
- data/data/butler/dialogs/selectbot.rb +2 -0
- data/data/butler/dialogs/start.rb +5 -0
- data/data/butler/dialogs/sync_plugins.rb +30 -0
- data/data/butler/dialogs/uninstall.rb +17 -0
- data/data/butler/dialogs/unknown_command.rb +1 -0
- data/data/butler/plugins/core/logout.rb +41 -0
- data/data/butler/plugins/core/plugins.rb +134 -0
- data/data/butler/plugins/core/privilege.rb +103 -0
- data/data/butler/plugins/core/user.rb +166 -0
- data/data/butler/plugins/dev/eval.rb +64 -0
- data/data/butler/plugins/dev/nometa.rb +14 -0
- data/data/butler/plugins/dev/onhandlers.rb +93 -0
- data/data/butler/plugins/dev/raw.rb +36 -0
- data/data/butler/plugins/dev/rawlog.rb +77 -0
- data/data/butler/plugins/games/eightball.rb +54 -0
- data/data/butler/plugins/games/mastermind.rb +174 -0
- data/data/butler/plugins/irc/action.rb +36 -0
- data/data/butler/plugins/irc/join.rb +38 -0
- data/data/butler/plugins/irc/notice.rb +36 -0
- data/data/butler/plugins/irc/part.rb +38 -0
- data/data/butler/plugins/irc/privmsg.rb +36 -0
- data/data/butler/plugins/irc/quit.rb +36 -0
- data/data/butler/plugins/operator/deop.rb +41 -0
- data/data/butler/plugins/operator/devoice.rb +41 -0
- data/data/butler/plugins/operator/limit.rb +47 -0
- data/data/butler/plugins/operator/op.rb +41 -0
- data/data/butler/plugins/operator/voice.rb +41 -0
- data/data/butler/plugins/public/help.rb +69 -0
- data/data/butler/plugins/public/login.rb +72 -0
- data/data/butler/plugins/public/usage.rb +49 -0
- data/data/butler/plugins/service/clones.rb +56 -0
- data/data/butler/plugins/service/define.rb +47 -0
- data/data/butler/plugins/service/log.rb +183 -0
- data/data/butler/plugins/service/svn.rb +91 -0
- data/data/butler/plugins/util/cycle.rb +98 -0
- data/data/butler/plugins/util/load.rb +41 -0
- data/data/butler/plugins/util/pong.rb +29 -0
- data/data/butler/strings/random/acknowledge.en.yaml +5 -0
- data/data/butler/strings/random/gratitude.en.yaml +3 -0
- data/data/butler/strings/random/hello.en.yaml +4 -0
- data/data/butler/strings/random/ignorance.en.yaml +7 -0
- data/data/butler/strings/random/ignorance_about.en.yaml +3 -0
- data/data/butler/strings/random/insult.en.yaml +3 -0
- data/data/butler/strings/random/rejection.en.yaml +12 -0
- data/data/man/botcontrol.1 +17 -0
- data/lib/access.rb +187 -0
- data/lib/access/admin.rb +16 -0
- data/lib/access/privilege.rb +122 -0
- data/lib/access/role.rb +102 -0
- data/lib/access/savable.rb +18 -0
- data/lib/access/user.rb +180 -0
- data/lib/access/yamlbase.rb +126 -0
- data/lib/butler.rb +188 -0
- data/lib/butler/bot.rb +247 -0
- data/lib/butler/control.rb +93 -0
- data/lib/butler/dialog.rb +64 -0
- data/lib/butler/initialvalues.rb +40 -0
- data/lib/butler/irc/channel.rb +135 -0
- data/lib/butler/irc/channels.rb +96 -0
- data/lib/butler/irc/client.rb +351 -0
- data/lib/butler/irc/hostmask.rb +53 -0
- data/lib/butler/irc/message.rb +184 -0
- data/lib/butler/irc/parser.rb +125 -0
- data/lib/butler/irc/parser/commands.rb +83 -0
- data/lib/butler/irc/parser/generic.rb +343 -0
- data/lib/butler/irc/socket.rb +378 -0
- data/lib/butler/irc/string.rb +186 -0
- data/lib/butler/irc/topic.rb +15 -0
- data/lib/butler/irc/user.rb +265 -0
- data/lib/butler/irc/users.rb +112 -0
- data/lib/butler/plugin.rb +249 -0
- data/lib/butler/plugin/configproxy.rb +35 -0
- data/lib/butler/plugin/mapper.rb +85 -0
- data/lib/butler/plugin/matcher.rb +55 -0
- data/lib/butler/plugin/onhandlers.rb +70 -0
- data/lib/butler/plugin/trigger.rb +58 -0
- data/lib/butler/plugins.rb +147 -0
- data/lib/butler/version.rb +17 -0
- data/lib/cloptions.rb +217 -0
- data/lib/cloptions/adapters.rb +24 -0
- data/lib/cloptions/switch.rb +132 -0
- data/lib/configuration.rb +223 -0
- data/lib/dialogline.rb +296 -0
- data/lib/dialogline/localizations.rb +24 -0
- data/lib/durations.rb +57 -0
- data/lib/event.rb +295 -0
- data/lib/event/at.rb +64 -0
- data/lib/event/every.rb +56 -0
- data/lib/event/timed.rb +112 -0
- data/lib/installer.rb +75 -0
- data/lib/iterator.rb +34 -0
- data/lib/log.rb +68 -0
- data/lib/log/comfort.rb +85 -0
- data/lib/log/converter.rb +23 -0
- data/lib/log/entry.rb +152 -0
- data/lib/log/fakeio.rb +55 -0
- data/lib/log/file.rb +54 -0
- data/lib/log/filereader.rb +81 -0
- data/lib/log/forward.rb +49 -0
- data/lib/log/methods.rb +39 -0
- data/lib/log/nolog.rb +18 -0
- data/lib/log/splitter.rb +26 -0
- data/lib/ostructfixed.rb +26 -0
- data/lib/ruby/array/columnize.rb +38 -0
- data/lib/ruby/dir/mktree.rb +28 -0
- data/lib/ruby/enumerable/join.rb +13 -0
- data/lib/ruby/exception/detailed.rb +24 -0
- data/lib/ruby/file/append.rb +11 -0
- data/lib/ruby/file/write.rb +11 -0
- data/lib/ruby/hash/zip.rb +15 -0
- data/lib/ruby/kernel/bench.rb +15 -0
- data/lib/ruby/kernel/daemonize.rb +42 -0
- data/lib/ruby/kernel/non_verbose.rb +17 -0
- data/lib/ruby/kernel/safe_fork.rb +18 -0
- data/lib/ruby/range/stepped.rb +11 -0
- data/lib/ruby/string/arguments.rb +72 -0
- data/lib/ruby/string/chunks.rb +15 -0
- data/lib/ruby/string/post_arguments.rb +44 -0
- data/lib/ruby/string/unescaped.rb +17 -0
- data/lib/scheduler.rb +164 -0
- data/lib/scriptfile.rb +101 -0
- data/lib/templater.rb +86 -0
- data/test/cloptions.rb +134 -0
- data/test/cv.rb +28 -0
- data/test/irc/client.rb +85 -0
- data/test/irc/client_login.txt +53 -0
- data/test/irc/client_subscribe.txt +8 -0
- data/test/irc/message.rb +30 -0
- data/test/irc/messages.txt +64 -0
- data/test/irc/parser.rb +13 -0
- data/test/irc/profile_parser.rb +12 -0
- data/test/irc/users.rb +28 -0
- metadata +256 -0
@@ -0,0 +1,184 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
#require 'ruby/string' # we need the transcode! method
|
10
|
+
|
11
|
+
class Butler
|
12
|
+
module IRC
|
13
|
+
|
14
|
+
# Butler::IRC::Message represents messages received by the server.
|
15
|
+
# It provides convenience methods that allow to access information about
|
16
|
+
# those messages easier, e.g. who (as Butler::IRC::User object) sent the
|
17
|
+
# message in which channel (IRC::Channel object) with what text.
|
18
|
+
# Raw message and raw parsed data are still available though.
|
19
|
+
#
|
20
|
+
# =FIXME
|
21
|
+
class Message
|
22
|
+
# the command-symbol, see COMMANDS (e.g. :PRIVMSG, :JOIN, ...)
|
23
|
+
attr_reader :symbol
|
24
|
+
|
25
|
+
# a string containing the raw message as received
|
26
|
+
attr_reader :raw
|
27
|
+
|
28
|
+
# the prefix, normally either the sending user or your irc-server
|
29
|
+
attr_reader :prefix
|
30
|
+
# the raw command (e.g. "352", "notice", ...)
|
31
|
+
attr_reader :command
|
32
|
+
# the parameter part
|
33
|
+
attr_reader :params
|
34
|
+
|
35
|
+
def initialize(client, symbol, raw, prefix, command, params)
|
36
|
+
@client = client
|
37
|
+
|
38
|
+
#raw message
|
39
|
+
@raw = raw
|
40
|
+
|
41
|
+
#parsed data
|
42
|
+
@prefix = prefix
|
43
|
+
@command = command
|
44
|
+
@params = params
|
45
|
+
@symbol = symbol
|
46
|
+
|
47
|
+
#specific data
|
48
|
+
@fields = Hash.new { |h,k| raise IndexError, "No member '#{k}' available." }.merge({
|
49
|
+
:raw => @raw,
|
50
|
+
:prefix => @prefix,
|
51
|
+
:command => @command,
|
52
|
+
:params => @params,
|
53
|
+
:symbol => @symbol,
|
54
|
+
:from => nil,
|
55
|
+
:for => nil,
|
56
|
+
:channel => nil,
|
57
|
+
:text => nil,
|
58
|
+
})
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize_copy(original) #:nodoc:
|
62
|
+
super
|
63
|
+
@client = original.instance_variable_get(:@client)
|
64
|
+
@fields = original.to_hash
|
65
|
+
@raw = @fields[:raw]
|
66
|
+
@prefix = @fields[:prefix]
|
67
|
+
@command = @fields[:command]
|
68
|
+
@params = @fields[:params]
|
69
|
+
@symbol = @fields[:symbol]
|
70
|
+
end
|
71
|
+
|
72
|
+
def answer(text)
|
73
|
+
reply = channel || from
|
74
|
+
case @symbol
|
75
|
+
when :PRIVMSG: @client.irc.privmsg(text, reply)
|
76
|
+
when :NOTICE: @client.irc.notice(text, reply)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def from
|
81
|
+
@fields[:from]
|
82
|
+
end
|
83
|
+
|
84
|
+
def for
|
85
|
+
@fields[:for]
|
86
|
+
end
|
87
|
+
|
88
|
+
def channel
|
89
|
+
@fields[:channel]
|
90
|
+
end
|
91
|
+
|
92
|
+
def text
|
93
|
+
@fields[:text]
|
94
|
+
end
|
95
|
+
|
96
|
+
# access to raw parsed data
|
97
|
+
def [](index)
|
98
|
+
@fields[index.to_sym]
|
99
|
+
end
|
100
|
+
|
101
|
+
def []=(index, value)
|
102
|
+
raise NoMethodError, "Can't write to #{index}" unless respond_to?("#{index}=")
|
103
|
+
index = index.to_sym
|
104
|
+
@fields[index] = value
|
105
|
+
end
|
106
|
+
|
107
|
+
def has_key?(key)
|
108
|
+
@fields.has_key?(key.to_sym)
|
109
|
+
end
|
110
|
+
|
111
|
+
def create_method(name, &code)
|
112
|
+
singleton = class << self; self; end
|
113
|
+
singleton.send(:define_method, name, &code)
|
114
|
+
name
|
115
|
+
end
|
116
|
+
|
117
|
+
# create a member with it's value, if writable is true, it creates a member= method too
|
118
|
+
def create_member(index, value=nil, writable=false)
|
119
|
+
index = index.to_sym
|
120
|
+
raise IndexError, "Member '#{index}' exists already" if @fields.has_key?(index)
|
121
|
+
singleton = class << self; self; end
|
122
|
+
singleton.send(:define_method, index) { @fields[index] }
|
123
|
+
singleton.send(:define_method, "#{index}=") { |val| @fields[index] = val } if writable
|
124
|
+
@fields[index] = value
|
125
|
+
end
|
126
|
+
|
127
|
+
# alter a member value with it's value
|
128
|
+
# should not be used other than by Butler::IRC::Parser
|
129
|
+
def alter_member(index, value=nil, create=false)
|
130
|
+
index = index.to_sym
|
131
|
+
if @fields.has_key?(index)
|
132
|
+
@fields[index] = value
|
133
|
+
elsif create then
|
134
|
+
create_member(index, value)
|
135
|
+
else
|
136
|
+
raise IndexError, "No member '#{index}' available."
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# delete a field
|
141
|
+
def delete_member(index)
|
142
|
+
index = index.to_sym
|
143
|
+
raise IndexError, "No member '#{index}' available." unless @fields.has_key?(index)
|
144
|
+
remove_method(index)
|
145
|
+
remove_method("#{index}=") if respond_to?("#{index}=")
|
146
|
+
@fields.delete(index)
|
147
|
+
end
|
148
|
+
|
149
|
+
# change charset encoding
|
150
|
+
def transcode!(from, to)
|
151
|
+
end
|
152
|
+
|
153
|
+
def ===(other)
|
154
|
+
case other
|
155
|
+
when Symbol: @symbol == other
|
156
|
+
when Regexp: @text && @text =~ other
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# return a hash of the fields
|
161
|
+
def to_hash
|
162
|
+
@fields.dup
|
163
|
+
end
|
164
|
+
|
165
|
+
def hash #:nodoc:
|
166
|
+
@raw.hash
|
167
|
+
end
|
168
|
+
|
169
|
+
def eql?(other) #:nodoc:
|
170
|
+
@raw == other.raw
|
171
|
+
end
|
172
|
+
|
173
|
+
def inspect #:nodoc:
|
174
|
+
fields = @fields.dup
|
175
|
+
#[:raw, :prefix, :command, :params, :command_raw]
|
176
|
+
[:raw, :command, :prefix, :params].each { |field| fields.delete(field) }
|
177
|
+
fields = fields.map { |k,v| "#{k}=#{v.inspect}" }.join(" ")
|
178
|
+
"#<%s:0x%x\n%s\n%s>" % [self.class, object_id, @raw.inspect, fields]
|
179
|
+
end
|
180
|
+
|
181
|
+
alias to_s inspect
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
require 'butler/irc/channels'
|
10
|
+
require 'butler/irc/hostmask'
|
11
|
+
require 'butler/irc/message'
|
12
|
+
require 'butler/irc/parser/commands'
|
13
|
+
require 'butler/irc/string'
|
14
|
+
require 'butler/irc/users'
|
15
|
+
require 'ruby/exception/detailed'
|
16
|
+
require 'ruby/hash/zip'
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
class Butler
|
21
|
+
module IRC
|
22
|
+
# Parses messages, automatically converts
|
23
|
+
# provides a parser that automatically connects users and channels
|
24
|
+
# regarding who myself is (out_of_sight, back_in_sight for users)
|
25
|
+
# allows creation of dialogs from privmsg and notice messages
|
26
|
+
class Parser
|
27
|
+
# RFC 2812, Parser itself uses a simplified matching
|
28
|
+
module Expressions
|
29
|
+
Special = /[\[\]\\`_^{|}]/
|
30
|
+
Letter = /[A-Za-z]/
|
31
|
+
Hex = /[\dA-Fa-f]/
|
32
|
+
ChannelID = /[A-Z\d]{5}/
|
33
|
+
Chanstring = /[^\x00\x07\x10\x0D\x20,:]/
|
34
|
+
Channel = /(?:[#+&]|!#{ChannelID})#{Chanstring}(?::#{Chanstring})?/
|
35
|
+
User = /[^\x00\x10\x0D\x20@]/
|
36
|
+
Nick = /[A-Za-z\[\]\\`_^{|}][A-Za-z\d\[\]\\`_^{|}-]*/
|
37
|
+
Command = /[A-Za-z]+|\d{3}/
|
38
|
+
IP4addr = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
39
|
+
IP6addr = /[\dA-Fa-f](?::[\dA-Fa-f]){7}|0:0:0:0:0:(?:0|[Ff]{4}):#{IP4addr}/
|
40
|
+
Hostaddr = /#{IP4addr}|#{IP6addr}/
|
41
|
+
Shortname = /[A-Za-z0-9][A-Za-z0-9-]*/
|
42
|
+
Hostname = /#{Shortname}(?:\.#{Shortname})*/
|
43
|
+
Host = /#{Hostname}|#{Hostaddr}/
|
44
|
+
Prefix = /#{Hostname}|#{Nick}(?:(?:!#{User})?@#{Host})?/
|
45
|
+
Params = /.*/ # FIXME
|
46
|
+
Message = /(:#{Prefix} )?#{Command}(#{Params})?/
|
47
|
+
end
|
48
|
+
|
49
|
+
class InvalidMessageFormat < RuntimeError; end
|
50
|
+
class UnknownCommand < RuntimeError; end
|
51
|
+
module ParseError
|
52
|
+
include Exception::Detailed
|
53
|
+
end
|
54
|
+
|
55
|
+
RMessage = /\A(?::([^ \0]+) )?([A-Za-z\d]+|\d{3})(?: (.*))?\z/
|
56
|
+
RHostmask = /(#{Expressions::Nick})(?:(?:!(#{Expressions::User}))?@(#{Expressions::Host}))?/
|
57
|
+
|
58
|
+
attr_reader :client
|
59
|
+
attr_reader :users
|
60
|
+
attr_reader :channels
|
61
|
+
|
62
|
+
def initialize(client, users, channels)
|
63
|
+
@client = client
|
64
|
+
@users = users
|
65
|
+
@channels = channels
|
66
|
+
@commands = Commands.new
|
67
|
+
end
|
68
|
+
|
69
|
+
# parses an incomming message and returns a Message object from which you
|
70
|
+
# can easily access parsed data.
|
71
|
+
# Expects the newlines to be already chomped off.
|
72
|
+
def server_message(raw)
|
73
|
+
prefix, command, params, symbol, from = nil
|
74
|
+
|
75
|
+
# Basic analysis of the message
|
76
|
+
raise InvalidMessageFormat, raw unless matched = raw.match(RMessage)
|
77
|
+
prefix, command, params = *matched.captures
|
78
|
+
command.downcase!
|
79
|
+
|
80
|
+
# Parse prefix if possible (<nick>!<user>@<host>)
|
81
|
+
from = @users.create(*matched.captures) if prefix and matched = prefix.match(RHostmask)
|
82
|
+
|
83
|
+
# in depth analyzis of the message
|
84
|
+
parser = @commands[command]
|
85
|
+
symbol = parser.symbol
|
86
|
+
message = Message.new(@client, symbol, raw, prefix, command, params)
|
87
|
+
message.alter_member(:from, from)
|
88
|
+
|
89
|
+
parser.create_fields(message)
|
90
|
+
|
91
|
+
if message.for then
|
92
|
+
if message.for.valid_channelname? then
|
93
|
+
channel = @channels.create(message.for)
|
94
|
+
message.alter_member(:channel, channel)
|
95
|
+
message.alter_member(:for, channel)
|
96
|
+
else
|
97
|
+
message.alter_member(:for, @users.create(message.for))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
message.alter_member(:channel, @channels.create(message.channel)) if message.channel
|
101
|
+
|
102
|
+
parser.process(message, self)
|
103
|
+
|
104
|
+
return message
|
105
|
+
rescue IndexError
|
106
|
+
raise UnknownCommand, "Unknown command #{command}: #{raw.inspect}"
|
107
|
+
rescue => e
|
108
|
+
e.extend ParseError
|
109
|
+
e.prepend "Message: #{raw.inspect}"
|
110
|
+
raise e
|
111
|
+
end
|
112
|
+
|
113
|
+
def leave_channel(message, reason1, reason2)
|
114
|
+
if message.from && message.channel then
|
115
|
+
message.from.delete_channel(message.channel, reason1)
|
116
|
+
message.channel.delete_user(message.from, reason1)
|
117
|
+
if message.from == @users.myself then
|
118
|
+
@channels.delete(message.channel)
|
119
|
+
@users.delete_channel(message.channel, reason2)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
require 'butler/irc/parser'
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
class Butler
|
14
|
+
module IRC
|
15
|
+
class Parser
|
16
|
+
class Commands
|
17
|
+
def initialize(specifics=nil)
|
18
|
+
@commands = Hash.new { |h,k| raise IndexError, "Unknown command #{k}" }
|
19
|
+
file = "#{File.dirname(__FILE__)}/generic.rb"
|
20
|
+
instance_eval(File.read(file), file)
|
21
|
+
end
|
22
|
+
|
23
|
+
def add(raw, *args, &proc)
|
24
|
+
raise IndexError, "Command #{raw} is already registered. Did you want 'alter'?" if @commands.has_key?(raw)
|
25
|
+
@commands[raw.downcase] = Command.new(raw, *args, &proc)
|
26
|
+
end
|
27
|
+
|
28
|
+
def alter(raw, *args, &proc)
|
29
|
+
raise IndexError, "Command #{raw} is not registered. Did you want 'add'?" unless @commands.has_key?(raw)
|
30
|
+
@commands[raw.downcase] = Command.new(raw, *args, &proc)
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](raw)
|
34
|
+
@commands[raw.downcase]
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect
|
38
|
+
"#<%s:0x%x>" % [self.class, object_id]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Provides parsing information about specific commands.
|
43
|
+
class Command
|
44
|
+
attr_reader :raw
|
45
|
+
attr_reader :symbol
|
46
|
+
attr_reader :matcher
|
47
|
+
attr_reader :mapping
|
48
|
+
attr_reader :processor
|
49
|
+
|
50
|
+
# See Butler::IRC::COMMANDS for samples on instanciation of
|
51
|
+
# Butler::IRC::Command objects.
|
52
|
+
def initialize(raw, symbol, matcher=nil, mapping=nil, &processor)
|
53
|
+
@raw = raw
|
54
|
+
@symbol = symbol
|
55
|
+
@matcher = matcher
|
56
|
+
@mapping = mapping
|
57
|
+
@processor = processor
|
58
|
+
end
|
59
|
+
|
60
|
+
def match(message)
|
61
|
+
@matcher.match(message)
|
62
|
+
end
|
63
|
+
|
64
|
+
def fields(message)
|
65
|
+
return nil unless @matcher and match = @matcher.match(message.params)
|
66
|
+
Hash.zip(@mapping, match.captures)
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_fields(message)
|
70
|
+
if @matcher and match = @matcher.match(message.params) then
|
71
|
+
@mapping.zip(match.captures) { |name, value|
|
72
|
+
message.alter_member(name, value, true)
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def process(message, parser)
|
78
|
+
@processor.call(message, parser) if @processor
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
# A list with all known IRC-Commands and it's parsing instructions
|
10
|
+
# Commands with __ in it are customs, not verified towards any standard.
|
11
|
+
|
12
|
+
#Text based codes
|
13
|
+
add("error", :ERROR) # ERROR :<error-message>
|
14
|
+
|
15
|
+
add("invite", :INVITE, /^([^\s]*) :(.*)/, [:invited, :channel])
|
16
|
+
|
17
|
+
add("join", :JOIN, /^:(.*)/, [:channel]) { |message, parser|
|
18
|
+
if message.from && message.channel then
|
19
|
+
message.from.add_channel(message.channel, :join)
|
20
|
+
message.channel.add_user(message.from, :join)
|
21
|
+
end
|
22
|
+
}
|
23
|
+
|
24
|
+
add("kick", :KICK, /^([^\s]*) ([^\s]*) :(.*)/, [:channel, :for, :text]) { |message, parser|
|
25
|
+
parser.leave_channel(message, :kick, :kicked)
|
26
|
+
}
|
27
|
+
|
28
|
+
add("kill", :KILL, /^([^\s]*) ([^\s]*) (.*)/, [:channel, :for, :text]) { |message, parser|
|
29
|
+
if message.for then
|
30
|
+
message.for.kill
|
31
|
+
parser.channels.delete_user(message.for, :kill)
|
32
|
+
end
|
33
|
+
}
|
34
|
+
|
35
|
+
# FIXME, really ':?' or just plain ':'?
|
36
|
+
add("mode", :MODE, /^([^\s]*) :?(.*)/, [:for, :arguments]) { |message, parser|
|
37
|
+
modifiers = message[:arguments].split(" ")
|
38
|
+
modes = modifiers.shift.split("")
|
39
|
+
flags = {"o" => User::Flags::OP, "v" => User::Flags::VOICE, "u" => User::Flags::UOP}
|
40
|
+
message.create_member(:modes, [])
|
41
|
+
argumentIndex = 0
|
42
|
+
direction = "+"
|
43
|
+
|
44
|
+
modes.each { |mode|
|
45
|
+
# MODEs direction
|
46
|
+
if (["+", "-"].include?(mode)) then
|
47
|
+
direction = mode
|
48
|
+
# MODEs taking an argument
|
49
|
+
elsif (["k", "o", "v", "u"].include?(mode) || (mode == "l" && direction == "+")) then
|
50
|
+
if ["o", "v", "u"].include?(mode) then
|
51
|
+
if (direction == "+") then
|
52
|
+
parser.users[modifiers[argumentIndex]].add_flags(message.channel, flags[mode]) #adding flags to user
|
53
|
+
else
|
54
|
+
parser.users[modifiers[argumentIndex]].delete_flags(message.channel, flags[mode]) #removing flags from user
|
55
|
+
end
|
56
|
+
end
|
57
|
+
message.modes << [direction, mode, modifiers[argumentIndex]]
|
58
|
+
argumentIndex = argumentIndex+1
|
59
|
+
# MODEs without argument
|
60
|
+
else
|
61
|
+
message.modes << [direction, mode, nil]
|
62
|
+
end
|
63
|
+
}
|
64
|
+
}
|
65
|
+
add("nick", :NICK, /^:(.*)/, [:nick]) { |message, parser|
|
66
|
+
message.create_member(:old_nick, message.from.nick)
|
67
|
+
message.from.nick = message.nick if message.from
|
68
|
+
}
|
69
|
+
|
70
|
+
add("notice", :NOTICE, /([^\s]+) :(.*)/, [:for, :text]) { |message, parser|
|
71
|
+
if message.channel then
|
72
|
+
message.create_member(:realm, :public)
|
73
|
+
message.create_method(:private?) { false }
|
74
|
+
message.create_method(:public?) { true }
|
75
|
+
else
|
76
|
+
message.create_member(:realm, :private)
|
77
|
+
message.create_method(:private?) { true }
|
78
|
+
message.create_method(:public?) { false }
|
79
|
+
end
|
80
|
+
}
|
81
|
+
|
82
|
+
add("part", :PART, /^([^\x00\x07\x10\x0D\x20,:]+)(?: :(.*))?/, [:channel, :reason]) { |message, parser|
|
83
|
+
parser.leave_channel(message, :part, :parted)
|
84
|
+
}
|
85
|
+
|
86
|
+
add("ping", :PING, /:(.*)/, [:pong])
|
87
|
+
|
88
|
+
add("pong", :PONG)
|
89
|
+
|
90
|
+
add("privmsg", :PRIVMSG, /([^\s]+) :(.*)/, [:for, :text]) { |message, parser|
|
91
|
+
if message.channel then
|
92
|
+
message.create_member(:realm, :public)
|
93
|
+
message.create_method(:private?) { false }
|
94
|
+
message.create_method(:public?) { true }
|
95
|
+
else
|
96
|
+
message.create_member(:realm, :private)
|
97
|
+
message.create_method(:private?) { true }
|
98
|
+
message.create_method(:public?) { false }
|
99
|
+
end
|
100
|
+
}
|
101
|
+
|
102
|
+
add("quit", :QUIT, /(.*)/, [:text]) { |message, parser|
|
103
|
+
if message.from then
|
104
|
+
message.from.quit
|
105
|
+
parser.channels.delete_user(message.from, :quit)
|
106
|
+
parser.users.delete(message.from, :quit)
|
107
|
+
end
|
108
|
+
}
|
109
|
+
add("topic", :TOPIC, /([^\s]+) :(.*)/, [:channel, :text])
|
110
|
+
|
111
|
+
#Unknown/Non-RFC codes
|
112
|
+
add("007", :UNK_007)
|
113
|
+
add("008", :UNK_008)
|
114
|
+
add("009", :UNK_009)
|
115
|
+
add("307", :RPL_REGISTERED_INFO) #FIXME non2812 "ConferenceRoom" bluewin, sent if a nick is registered
|
116
|
+
add("329", :RPL_CHANNEL_INFO) #channel creation time
|
117
|
+
add("377", :UNK_377)
|
118
|
+
|
119
|
+
#0** Codes
|
120
|
+
add("001", :RPL_WELCOME)
|
121
|
+
add("002", :RPL_YOURHOST)
|
122
|
+
add("003", :RPL_CREATED)
|
123
|
+
add("004", :RPL_MYINFO)
|
124
|
+
add("005", :RPL_ISUPPORT)
|
125
|
+
|
126
|
+
#2** Codes
|
127
|
+
add("200", :RPL_TRACELINK)
|
128
|
+
add("201", :RPL_TRACECONNECTING)
|
129
|
+
add("202", :RPL_TRACEHANDSHAKE)
|
130
|
+
add("203", :RPL_TRACEUNKNOWN)
|
131
|
+
add("204", :RPL_TRACEOPERATOR)
|
132
|
+
add("205", :RPL_TRACEUSER)
|
133
|
+
add("206", :RPL_TRACESERVER)
|
134
|
+
add("207", :RPL_TRACESERVICE)
|
135
|
+
add("208", :RPL_TRACENEWTYPE)
|
136
|
+
add("209", :RPL_TRACECLASS)
|
137
|
+
add("210", :RPL_TRACERECONNECT)
|
138
|
+
add("211", :RPL_STATSLINKINFO)
|
139
|
+
add("212", :RPL_STATSCOMMANDS)
|
140
|
+
add("213", :RPL_STATSCLINE)
|
141
|
+
add("214", :RPL_STATSNLINE)
|
142
|
+
add("215", :RPL_STATSILINE)
|
143
|
+
add("216", :RPL_STATSKLINE)
|
144
|
+
add("217", :RPL_STATSQLINE)
|
145
|
+
add("218", :RPL_STATSYLINE)
|
146
|
+
add("219", :RPL_ENDOFSTATS)
|
147
|
+
add("221", :RPL_UMODEIS)
|
148
|
+
add("231", :RPL_SERVICEINFO)
|
149
|
+
add("232", :RPL_ENDOFSERVICES)
|
150
|
+
add("233", :RPL_SERVICE)
|
151
|
+
add("234", :RPL_SERVLIST)
|
152
|
+
add("235", :RPL_SERVLISTEND)
|
153
|
+
add("240", :RPL_STATSVLINE)
|
154
|
+
add("241", :RPL_STATSLLINE)
|
155
|
+
add("242", :RPL_STATSUPTIME)
|
156
|
+
add("243", :RPL_STATSOLINE)
|
157
|
+
add("244", :RPL_STATSHLINE)
|
158
|
+
add("245", :RPL_STATSSLINE) # RFC 2812 seems to be erroneous, it assigns 244 double
|
159
|
+
add("246", :RPL_STATSPING)
|
160
|
+
add("247", :RPL_STATSBLINE)
|
161
|
+
add("250", :RPL_STATSCONN)
|
162
|
+
add("251", :RPL_LUSERCLIENT)
|
163
|
+
add("252", :RPL_LUSEROP)
|
164
|
+
add("253", :RPL_LUSERUNKNOWN)
|
165
|
+
add("254", :RPL_LUSERCHANNELS)
|
166
|
+
add("255", :RPL_LUSERME)
|
167
|
+
add("256", :RPL_ADMINME)
|
168
|
+
add("257", :RPL_ADMINLOC1)
|
169
|
+
add("258", :RPL_ADMINLOC2)
|
170
|
+
add("259", :RPL_ADMINEMAIL)
|
171
|
+
add("261", :RPL_TRACELOG)
|
172
|
+
add("262", :RPL_TRACEEND)
|
173
|
+
add("263", :RPL_TRYAGAIN)
|
174
|
+
add("265", :RPL_LOCALUSERS)
|
175
|
+
add("266", :RPL_GLOBALUSERS)
|
176
|
+
add("290", :RPL_IDENTIFY_MSG) # FIXME non2812 "hyperion-1.0.2b"
|
177
|
+
|
178
|
+
#3** Codes
|
179
|
+
add("300", :RPL_NONE)
|
180
|
+
add("301", :RPL_AWAY)
|
181
|
+
add("302", :RPL_USERHOST)
|
182
|
+
add("303", :RPL_ISON)
|
183
|
+
add("305", :RPL_UNAWAY)
|
184
|
+
add("306", :RPL_NOWAWAY)
|
185
|
+
add("311", :RPL_WHOISUSER, /^([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) \* :(.*)/, [:for, :nick, :user, :host, :real])
|
186
|
+
add("312", :RPL_WHOISSERVER)
|
187
|
+
add("313", :RPL_WHOISOPERATOR)
|
188
|
+
add("314", :RPL_WHOWASUSER)
|
189
|
+
add("315", :RPL_ENDOFWHO)
|
190
|
+
add("316", :RPL_WHOISCHANOP)
|
191
|
+
add("317", :RPL_WHOISIDLE, /^([^\s]+) ([^\s]+) ([^:]+) :(.*)/, [:for, :nick, :values, :descriptions]) { |message, parser|
|
192
|
+
values = message[:values].split(" ");
|
193
|
+
descriptions = message[:descriptions].split(", ").map { |desc| desc.gsub(" ", "_").to_sym };
|
194
|
+
message.delete(:values)
|
195
|
+
message.delete(:descriptions)
|
196
|
+
0.upto([values.length, descriptions.length].min-1) { |index|
|
197
|
+
message[descriptions[index]] = values[index]
|
198
|
+
}
|
199
|
+
}
|
200
|
+
add("318", :RPL_ENDOFWHOIS)
|
201
|
+
add("319", :RPL_WHOISCHANNELS, /^([^\s]+) ([^\s]+) :(.*)/, [:for, :nick, :channels]) { |message, parser|
|
202
|
+
# FIXME
|
203
|
+
message.alter_member(:channels, message.channels.split(" ").map { |channel| parser.channels.create(channel) })
|
204
|
+
}
|
205
|
+
add("320", :RPL_IDENTIFIED_TO_SERVICES) # possibly hyperion only
|
206
|
+
add("321", :RPL_LISTSTART)
|
207
|
+
add("322", :RPL_LIST, /^([^\s]+) ([^\s]+) (\d+) :(.*)/, [:for, :channelname, :usercount, :topic])
|
208
|
+
add("323", :RPL_LISTEND)
|
209
|
+
add("324", :RPL_CHANNELMODEIS)
|
210
|
+
add("325", :RPL_UNIQOPIS)
|
211
|
+
add("331", :RPL_NOTOPIC)
|
212
|
+
# :irc.server.net 332 YourNickname #channel :Topic
|
213
|
+
add("332", :RPL_TOPIC, /^([^\s]+) ([^\s]+) :(.*)/, [:for, :channel, :topic]) { |message, parser|
|
214
|
+
message.channel.topic.text = message[:topic]
|
215
|
+
}
|
216
|
+
|
217
|
+
# :irc.server.net 333 YourNickname #channel SetByNick 1139902138
|
218
|
+
add("333", :RPL_TOPIC_INFO, /^([^\s]+) ([^\s]+) ([^\s]+) (\d+)/, [:for, :channel, :set_by, :set_at]) { |message, parser|
|
219
|
+
topic = message.channel.topic
|
220
|
+
topic.set_by = message[:set_by]
|
221
|
+
topic.set_at = message[:set_at]
|
222
|
+
}
|
223
|
+
add("341", :RPL_INVITING)
|
224
|
+
add("342", :RPL_SUMMONING)
|
225
|
+
add("343", :RPL__MAINTENANCE) #mainenance notice?
|
226
|
+
add("346", :RPL_INVITELIST)
|
227
|
+
add("347", :RPL_ENDOFINVITELIST)
|
228
|
+
add("348", :RPL_EXCEPTLIST)
|
229
|
+
add("349", :RPL_ENDOFEXCEPTLIST)
|
230
|
+
add("351", :RPL_VERSION)
|
231
|
+
# :irc.server.net 352 YourNickname <channel> <user> <host> <server> <nick> ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] :<hopcount> <real name>
|
232
|
+
add("352", :RPL_WHOREPLY,
|
233
|
+
/([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) ([HG])[*%]{0,2}([@+-]{0,3}) :(\d+) (.*)/,
|
234
|
+
[:for, :channel, :user, :host, :server, :nick, :status, :flags, :hopcount, :real]) { |message, parser|
|
235
|
+
#"for", "channel", "user", "host", "server", "nick", "status", "flags", "hopcount", "real"
|
236
|
+
user = parser.users.create(message[:nick])
|
237
|
+
user.update(message[:user], message[:host], message[:real])
|
238
|
+
user.away = message[:status] == "G"
|
239
|
+
user.add_channel(message.channel, :joined)
|
240
|
+
message.channel.add_user(user, :joined)
|
241
|
+
user.add_flags(message.channel, message[:flags].to_flags)
|
242
|
+
}
|
243
|
+
add("353", :RPL_NAMEREPLY, /([^\s]+) [=@] ([^\s]+) :(.*)/, [:for, :channel, :users]) { |message, parser|
|
244
|
+
users = message[:users]
|
245
|
+
message.alter_member(:users, [])
|
246
|
+
users.split(/ /).each { |nick|
|
247
|
+
user = parser.users.create(nick)
|
248
|
+
message[:users] << user
|
249
|
+
user.add_channel(message.channel, :joined)
|
250
|
+
message.channel.add_user(user, :joined)
|
251
|
+
}
|
252
|
+
}
|
253
|
+
add("361", :RPL_KILLDONE)
|
254
|
+
add("362", :RPL_CLOSING)
|
255
|
+
add("363", :RPL_CLOSEEND)
|
256
|
+
add("364", :RPL_LINKS)
|
257
|
+
add("365", :RPL_ENDOFLINKS)
|
258
|
+
add("366", :RPL_ENDOFNAMES)
|
259
|
+
add("367", :RPL_BANLIST,
|
260
|
+
/([^\s]+) ([^\s]+) ([^\s]+) ([^\s]+) (\d+)/,
|
261
|
+
[:for, :channel, :banmask, :banned_by, :bantime]
|
262
|
+
) { |message, parser| #367 nickname #channel nick!user@host nickname 1140125288
|
263
|
+
message[:bantime] = Time.at(message[:bantime].to_i)
|
264
|
+
message[:banmask] = Hostmask.new(message[:banmask])
|
265
|
+
}
|
266
|
+
|
267
|
+
add("368", :RPL_ENDOFBANLIST)
|
268
|
+
add("369", :RPL_ENDOFWHOWAS)
|
269
|
+
add("371", :RPL_INFO)
|
270
|
+
add("372", :RPL_MOTD)
|
271
|
+
add("373", :RPL_INFOSTART)
|
272
|
+
add("374", :RPL_ENDOFINFO)
|
273
|
+
add("375", :RPL_MOTDSTART)
|
274
|
+
add("376", :RPL_ENDOFMOTD)
|
275
|
+
add("381", :RPL_YOUREOPER)
|
276
|
+
add("382", :RPL_REHASHING)
|
277
|
+
add("383", :RPL_YOURESERVICE)
|
278
|
+
add("384", :RPL_MYPORTIS)
|
279
|
+
add("386", :RPL_PASSWORDACCEPTED) # Custom (chatroom)
|
280
|
+
add("391", :RPL_TIME)
|
281
|
+
add("392", :RPL_USERSSTART)
|
282
|
+
add("393", :RPL_USERS)
|
283
|
+
add("394", :RPL_ENDOFUSERS)
|
284
|
+
add("395", :RPL_NOUSERS)
|
285
|
+
|
286
|
+
#4** Codes
|
287
|
+
add("401", :ERR_NOSUCHNICK)
|
288
|
+
add("402", :ERR_NOSUCHSERVER)
|
289
|
+
add("403", :ERR_NOSUCHCHANNEL)
|
290
|
+
add("404", :ERR_CANNOTSENDTOCHAN)
|
291
|
+
add("405", :ERR_TOOMANYCHANNELS)
|
292
|
+
add("406", :ERR_WASNOSUCHNICK)
|
293
|
+
add("407", :ERR_TOOMANYTARGETS)
|
294
|
+
add("408", :ERR_NOSUCHSERVICE)
|
295
|
+
add("409", :ERR_NOORIGIN)
|
296
|
+
add("411", :ERR_NORECIPIENT)
|
297
|
+
add("412", :ERR_NOTEXTTOSEND)
|
298
|
+
add("413", :ERR_NOTOPLEVEL)
|
299
|
+
add("414", :ERR_WILDTOPLEVEL)
|
300
|
+
add("415", :ERR_BADMASK)
|
301
|
+
add("421", :ERR_UNKNOWNCOMMAND)
|
302
|
+
add("422", :ERR_NOMOTD)
|
303
|
+
add("423", :ERR_NOADMININFO)
|
304
|
+
add("424", :ERR_FILEERROR)
|
305
|
+
add("431", :ERR_NONICKNAMEGIVEN)
|
306
|
+
add("432", :ERR_ERRONEUSNICKNAME)
|
307
|
+
add("433", :ERR_NICKNAMEINUSE)
|
308
|
+
add("436", :ERR_NICKCOLLISION)
|
309
|
+
add("437", :ERR_UNAVAILRESOURCE)
|
310
|
+
add("441", :ERR_USERNOTINCHANNEL)
|
311
|
+
add("442", :ERR_NOTONCHANNEL)
|
312
|
+
add("443", :ERR_USERONCHANNEL)
|
313
|
+
add("444", :ERR_NOLOGIN)
|
314
|
+
add("445", :ERR_SUMMONDISABLED)
|
315
|
+
add("446", :ERR_USERSDISABLED)
|
316
|
+
add("451", :ERR_NOTREGISTERED)
|
317
|
+
add("461", :ERR_NEEDMOREPARAMS)
|
318
|
+
add("462", :ERR_ALREADYREGISTRED)
|
319
|
+
add("463", :ERR_NOPERMFORHOST)
|
320
|
+
add("464", :ERR_PASSWDMISMATCH)
|
321
|
+
add("465", :ERR_YOUREBANNEDCREEP)
|
322
|
+
add("466", :ERR_YOUWILLBEBANNED)
|
323
|
+
add("467", :ERR_KEYSET)
|
324
|
+
add("471", :ERR_CHANNELISFULL)
|
325
|
+
add("472", :ERR_UNKNOWNMODE)
|
326
|
+
add("473", :ERR_INVITEONLYCHAN)
|
327
|
+
add("474", :ERR_BANNEDFROMCHAN)
|
328
|
+
add("475", :ERR_BADCHANNELKEY)
|
329
|
+
add("476", :ERR_BADCHANMASK)
|
330
|
+
add("477", :ERR_NOCHANMODES)
|
331
|
+
add("478", :ERR_BANLISTFULL)
|
332
|
+
add("481", :ERR_NOPRIVILEGES)
|
333
|
+
add("482", :ERR_CHANOPRIVSNEEDED)
|
334
|
+
add("483", :ERR_CANTKILLSERVER)
|
335
|
+
add("484", :ERR_RESTRICTED)
|
336
|
+
add("485", :ERR_UNIQOPPRIVSNEEDED)
|
337
|
+
add("491", :ERR_NOOPERHOST)
|
338
|
+
add("492", :ERR_NOSERVICEHOST)
|
339
|
+
|
340
|
+
#5** Codes
|
341
|
+
add("501", :ERR_UMODEUNKNOWNFLAG)
|
342
|
+
add("502", :ERR_USERSDONTMATCH)
|
343
|
+
add("505", :ERR__NOPRIVMSG)
|