butler 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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,378 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# See LICENSE.txt for permissions.
|
|
5
|
+
#++
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
require 'thread'
|
|
10
|
+
require 'socket'
|
|
11
|
+
require 'ostructfixed'
|
|
12
|
+
require 'log/comfort'
|
|
13
|
+
require 'ruby/string/chunks'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Butler
|
|
18
|
+
module IRC
|
|
19
|
+
|
|
20
|
+
# ==Description
|
|
21
|
+
# Butler::IRC::Socket is a TCPSocket, retrofitted for communication with
|
|
22
|
+
# IRC-Servers.
|
|
23
|
+
# It provides specialized methods for sending messages to IRC-Server.
|
|
24
|
+
# All methods are safe to be used with Butler::IRC::* Objects (e.g. all
|
|
25
|
+
# parameters expecting a nickname will accept an Butler::IRC::User as well).
|
|
26
|
+
# It will adhere to its limit-settings, which will prevent from sending too
|
|
27
|
+
# many messages in a too short time to avoid excess flooding.
|
|
28
|
+
# Butler::IRC::Socket#write is the only synchronized method, since all other
|
|
29
|
+
# methods build up on it, IRC::Socket should be safe in threaded environments.
|
|
30
|
+
# Butler::IRC::Socket#read is NOT synchronized, so unless you read from only
|
|
31
|
+
# a single thread, statistics might get messed up.
|
|
32
|
+
# Length limits can only be safely guaranteed by specialized write methods,
|
|
33
|
+
# Butler::IRC::Socket#write will just warn and send the overlength message.
|
|
34
|
+
# If you are looking for queries (commands that get an answer from the server)
|
|
35
|
+
# take a look at Butler::IRC::Client.
|
|
36
|
+
#
|
|
37
|
+
# ==Synopsis
|
|
38
|
+
# irc = Butler::IRC::Socket.new('irc.freenode.org', :port => 6667, :charset => 'ISO-8859-1')
|
|
39
|
+
# irc.connect
|
|
40
|
+
# irc.login('your_nickname', 'YourUser', 'Your realname', ["#channel1", "#channel2"])
|
|
41
|
+
# irc.join("#channel3")
|
|
42
|
+
# irc.part("#channel3")
|
|
43
|
+
# irc.privmsg("Hi all of you in #channel1!", "#channel1")
|
|
44
|
+
# irc.close
|
|
45
|
+
#
|
|
46
|
+
# ==Notes
|
|
47
|
+
# Errno::EHOSTUNREACH: server not reached
|
|
48
|
+
# Errno::ECONNREFUSED: server is up, but refuses connection
|
|
49
|
+
# Errno::ECONNRESET: connection works, server did not yet accept connection, resets after
|
|
50
|
+
# Errno::EPIPE: writing to a server-side closed connection, nil on gets, connection was terminated
|
|
51
|
+
#
|
|
52
|
+
# ==FIXME
|
|
53
|
+
# mode commands don't test for length and split up
|
|
54
|
+
#
|
|
55
|
+
class Socket
|
|
56
|
+
VERSION = "1.0.0"
|
|
57
|
+
|
|
58
|
+
include Log::Comfort
|
|
59
|
+
|
|
60
|
+
# server the instance is linked with
|
|
61
|
+
attr_reader :server
|
|
62
|
+
# port used for connection
|
|
63
|
+
attr_reader :port
|
|
64
|
+
# the own host (nil if not supported)
|
|
65
|
+
attr_reader :host
|
|
66
|
+
# end-of-line used for communication
|
|
67
|
+
attr_reader :eol
|
|
68
|
+
|
|
69
|
+
# contains various counters, such as :received, :sent (lines)
|
|
70
|
+
attr_reader :count
|
|
71
|
+
|
|
72
|
+
# contains limits for the protocol, burst times/counts etc.
|
|
73
|
+
attr_reader :limit
|
|
74
|
+
|
|
75
|
+
OptionsDefault = {
|
|
76
|
+
:port => 6667,
|
|
77
|
+
:eol => "\r\n",
|
|
78
|
+
:host => nil,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Initialize properties, doesn't connect automatically
|
|
82
|
+
# options:
|
|
83
|
+
# * :server: ip/domain of server (overrides a given server parameter)
|
|
84
|
+
# * :port: port to connect on, defaults to 6667
|
|
85
|
+
# * :eol: what character sequence terminates messages, defaults to \r\n
|
|
86
|
+
# * :host: what host address to bind to, defaults to nil
|
|
87
|
+
#
|
|
88
|
+
def initialize(server, options={})
|
|
89
|
+
options = OptionsDefault.merge(options)
|
|
90
|
+
@log_device = options.delete(:log)
|
|
91
|
+
@server = server # options.delete(:server)
|
|
92
|
+
@port = options.delete(:port)
|
|
93
|
+
@eol = options.delete(:eol).dup.freeze
|
|
94
|
+
@host = options[:host] ? options.delete(:host).dup.freeze : options.delete(:host)
|
|
95
|
+
@last_sent = Time.new()
|
|
96
|
+
@count = Hash.new(0)
|
|
97
|
+
@limit = OpenStruct.new({
|
|
98
|
+
:message_length => 300, # max. length of a text message (e.g. in notice, privmsg) sent to server
|
|
99
|
+
:raw_length => 400, # max. length of a raw message sent to server
|
|
100
|
+
:burst => 4, # max. messages that can be sent with send_delay (0 = infinite)
|
|
101
|
+
:burst2 => 20, # max. messages that can be sent with send_delay (0 = infinite)
|
|
102
|
+
:send_delay => 0.1, # minimum delay between each message
|
|
103
|
+
:burst_delay => 1.5, # delay after a burst
|
|
104
|
+
:burst2_delay => 15, # delay after a burst2
|
|
105
|
+
})
|
|
106
|
+
@limit.each { |key, default|
|
|
107
|
+
@limit[key] = options.delete(key) if options.has_key?(key)
|
|
108
|
+
}
|
|
109
|
+
@mutex = Mutex.new
|
|
110
|
+
@socket = nil
|
|
111
|
+
@connected = false
|
|
112
|
+
raise ArgumentError, "Unknown arguments: #{options.keys.inspect}" unless options.empty?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# connects to the server
|
|
116
|
+
def connect
|
|
117
|
+
@socket = TCPSocket.open(@server, @port) #, @host)
|
|
118
|
+
info("Connected to #{@server}:#{@port} from #{@host || '<default>'}")
|
|
119
|
+
rescue ArgumentError => error
|
|
120
|
+
if @host then
|
|
121
|
+
warn("host-parameter is not supported by your ruby version. Parameter discarted.")
|
|
122
|
+
@host = nil
|
|
123
|
+
retry
|
|
124
|
+
else
|
|
125
|
+
raise
|
|
126
|
+
end
|
|
127
|
+
rescue Exception
|
|
128
|
+
error("Connection failed.")
|
|
129
|
+
raise
|
|
130
|
+
else
|
|
131
|
+
@connected = true
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# get next message (eol already chomped) from server, blocking, returns nil if closed
|
|
135
|
+
def read
|
|
136
|
+
@count[:read] += 1
|
|
137
|
+
if m = @socket.gets(@eol) then
|
|
138
|
+
m.chomp(@eol)
|
|
139
|
+
else
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Send a raw message to irc, eol will be appended
|
|
145
|
+
# Use specialized methods instead if possible since they will releave
|
|
146
|
+
# you from several tasks like translating newlines, take care of overlength
|
|
147
|
+
# messages etc.
|
|
148
|
+
# FIXME, wrong methodname, write implies nothing is appended
|
|
149
|
+
def write(data)
|
|
150
|
+
@mutex.synchronize {
|
|
151
|
+
warn("Raw too long (#{data.length} instead of #{@limit[:raw_length]})") if (data.length > @limit.raw_length)
|
|
152
|
+
now = Time.now
|
|
153
|
+
|
|
154
|
+
# keep delay between single (bursted) messages
|
|
155
|
+
sleeptime = @limit.send_delay-(now-@last_sent)
|
|
156
|
+
if sleeptime > 0 then
|
|
157
|
+
sleep(sleeptime)
|
|
158
|
+
now += sleeptime
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# keep delay after a burst (1)
|
|
162
|
+
if (@count[:burst] >= @limit[:burst]) then
|
|
163
|
+
sleeptime = @limit.burst_delay-(now-@last_sent)
|
|
164
|
+
if sleeptime > 0 then
|
|
165
|
+
sleep(sleeptime)
|
|
166
|
+
now += sleeptime
|
|
167
|
+
end
|
|
168
|
+
@count[:burst] = 0
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# keep delay after a burst (2)
|
|
172
|
+
if (@count[:burst2] >= @limit[:burst2]) then
|
|
173
|
+
sleeptime = @limit.burst2_delay-(now-@last_sent)
|
|
174
|
+
if sleeptime > 0 then
|
|
175
|
+
sleep(sleeptime)
|
|
176
|
+
now += sleeptime
|
|
177
|
+
end
|
|
178
|
+
@count[:burst2] = 0
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# send data and update data
|
|
182
|
+
@last_sent = Time.new
|
|
183
|
+
@socket.write(data+@eol)
|
|
184
|
+
@count[:burst] += 1
|
|
185
|
+
@count[:burst2] += 1
|
|
186
|
+
@count[:sent] += 1
|
|
187
|
+
}
|
|
188
|
+
rescue IOError
|
|
189
|
+
error("Writing #{data.inspect} failed")
|
|
190
|
+
raise
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# log into the irc-server (and connect if necessary)
|
|
194
|
+
def login(nickname, username, realname)
|
|
195
|
+
connect unless @connected
|
|
196
|
+
write("NICK #{nickname}")
|
|
197
|
+
write("USER #{username} 0 * :#{realname}")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# identify nickname to nickserv
|
|
201
|
+
# FIXME, figure out what the server supports, possibly requires it
|
|
202
|
+
# to be moved to Butler::IRC::Client (to allow ghosting, nickchange, identify)
|
|
203
|
+
def identify(password)
|
|
204
|
+
write("NS :IDENTIFY #{password}")
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# FIXME, figure out what the server supports, possibly requires it
|
|
208
|
+
def ghost(nickname, password)
|
|
209
|
+
write("NS :GHOST #{nickname} #{password}")
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def normalize_message(message, limit=:message_length)
|
|
213
|
+
messages = []
|
|
214
|
+
message.split(/\n/).each { |line|
|
|
215
|
+
messages.concat(line.chunks(@limit[limit]))
|
|
216
|
+
}
|
|
217
|
+
messages
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# sends a privmsg to given user or channel (or multiple)
|
|
221
|
+
# messages containing newline or exceeding @limit[:message_length] are automatically splitted
|
|
222
|
+
# into multiple messages.
|
|
223
|
+
def privmsg(message, *recipients)
|
|
224
|
+
normalize_message(message).each { |message|
|
|
225
|
+
recipients.each { |recipient|
|
|
226
|
+
write("PRIVMSG #{recipient} :#{message}")
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# same as privmsg except it's formatted for ACTION
|
|
232
|
+
def action(message, *recipients)
|
|
233
|
+
normalize_message(message).each { |message|
|
|
234
|
+
recipients.each { |recipient|
|
|
235
|
+
write("PRIVMSG #{recipient} :"+(1.chr)+"ACTION "+message+(1.chr))
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# sends a notice to receiver (or multiple if receiver is array of receivers)
|
|
241
|
+
# formatted=true allows usage of ![]-format commands (see IRCmessage.getFormatted)
|
|
242
|
+
# messages containing newline automatically get splitted up into multiple messages.
|
|
243
|
+
# Too long messages will be tokenized into fitting sized messages (see @limit[:message_length])
|
|
244
|
+
def notice(message, *recipients)
|
|
245
|
+
normalize_message(message).each { |message|
|
|
246
|
+
recipients.each { |recipient|
|
|
247
|
+
write("NOTICE #{recipient} :#{message}")
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# send a pong
|
|
253
|
+
def pong(*args)
|
|
254
|
+
if args.empty? then
|
|
255
|
+
write("PONG")
|
|
256
|
+
else
|
|
257
|
+
write("PONG #{args.join(' ')}")
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# join specified channels
|
|
262
|
+
# use an array [channel, password] to join password-protected channels
|
|
263
|
+
# returns the channels joined.
|
|
264
|
+
def join(*channels)
|
|
265
|
+
channels.map { |channel, password|
|
|
266
|
+
if password then
|
|
267
|
+
write("JOIN #{channel} #{password}")
|
|
268
|
+
else
|
|
269
|
+
write("JOIN #{channel}")
|
|
270
|
+
end
|
|
271
|
+
channel
|
|
272
|
+
}
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# part specified channels
|
|
276
|
+
# FIXME, better way to implement the reason? use a block (yay)?
|
|
277
|
+
# returns the channels parted from.
|
|
278
|
+
def part(reason=nil, *channels)
|
|
279
|
+
if channels.empty?
|
|
280
|
+
channels = [reason]
|
|
281
|
+
reason = nil
|
|
282
|
+
end
|
|
283
|
+
reason ||= "leaving"
|
|
284
|
+
|
|
285
|
+
# some servers still can't process lists of channels in part
|
|
286
|
+
channels.each { |channel|
|
|
287
|
+
write("PART #{channel} #{reason}")
|
|
288
|
+
}
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# set your own nick
|
|
292
|
+
# does NO verification/validation of any kind
|
|
293
|
+
def nick(nick)
|
|
294
|
+
write("NICK #{nick}")
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# set your status to away with reason 'reason'
|
|
298
|
+
def away(reason="")
|
|
299
|
+
return back if reason.empty?
|
|
300
|
+
write("AWAY :#{reason}")
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# reset your away status to back
|
|
304
|
+
def back
|
|
305
|
+
write("AWAY")
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# kick user in channel with reason
|
|
309
|
+
def kick(user, channel, reason)
|
|
310
|
+
write("KICK #{channel} #{user} :#{reason}")
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Give Op to user in channel
|
|
314
|
+
# User can be a nick or IRC::User, either one or an array.
|
|
315
|
+
def op(channel, *users)
|
|
316
|
+
write("MODE #{channel} +#{'o'*users.length} #{users*' '}")
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Take Op from user in channel
|
|
320
|
+
# User can be a nick or IRC::User, either one or an array.
|
|
321
|
+
def deop(channel, *users)
|
|
322
|
+
write("MODE #{channel} -#{'o'*users.length} #{users*' '}")
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Give voice to user in channel
|
|
326
|
+
# User can be a nick or IRC::User, either one or an array.
|
|
327
|
+
def voice(channel, *users)
|
|
328
|
+
write("MODE #{channel} +#{'v'*users.length} #{users*' '}")
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Take voice from user in channel.
|
|
332
|
+
# User can be a nick or IRC::User, either one or an array.
|
|
333
|
+
def devoice(channel, *users)
|
|
334
|
+
write("MODE #{channel} -#{'v'*users.length} #{users*' '}")
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Set ban in channel to mask
|
|
338
|
+
def ban(mask, channel)
|
|
339
|
+
write("MODE #{channel} +b #{mask}")
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Send a "who" to channel
|
|
343
|
+
def who(channel)
|
|
344
|
+
write("WHO #{channel}")
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Send a "whois" to server
|
|
348
|
+
def whois(nick)
|
|
349
|
+
write("WHOIS #{nick}")
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# send the quit message to the server
|
|
353
|
+
# if you set close to true it will also close the socket
|
|
354
|
+
def quit(reason="leaving", close=false)
|
|
355
|
+
write("QUIT :#{reason}")
|
|
356
|
+
close() if close
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# closes the connection to the irc-server
|
|
360
|
+
def close
|
|
361
|
+
raise "Socket not open" unless @socket
|
|
362
|
+
@socket.close unless @socket.closed?
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def inspect # :nodoc:
|
|
366
|
+
"#<%s:0x%08x %s:%s from %s using '%s', stats: %s>" % [
|
|
367
|
+
self.class,
|
|
368
|
+
object_id << 1,
|
|
369
|
+
@server,
|
|
370
|
+
@port,
|
|
371
|
+
@host || "<default>",
|
|
372
|
+
@eol.inspect[1..-2],
|
|
373
|
+
@count.inspect
|
|
374
|
+
]
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# See LICENSE.txt for permissions.
|
|
5
|
+
#++
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# provides IRC-related methods for string-class
|
|
10
|
+
class String
|
|
11
|
+
# Colors Lookup-table for mirc_translated_color
|
|
12
|
+
COLORS = {
|
|
13
|
+
'white' => 0,
|
|
14
|
+
'black' => 1,
|
|
15
|
+
'blue' => 2,
|
|
16
|
+
'green' => 3,
|
|
17
|
+
'red' => 4,
|
|
18
|
+
'brown' => 5,
|
|
19
|
+
'purple' => 6,
|
|
20
|
+
'orange' => 7,
|
|
21
|
+
'yellow' => 8,
|
|
22
|
+
'ltgreen' => 9,
|
|
23
|
+
'teal' => 10,
|
|
24
|
+
'ltcyan' => 11,
|
|
25
|
+
'ltblue' => 12,
|
|
26
|
+
'pink' => 13,
|
|
27
|
+
'grey' => 14,
|
|
28
|
+
'ltgrey' => 15
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# returns whether or not a string represents a valid channelname
|
|
32
|
+
def valid_channelname?
|
|
33
|
+
self =~ /\A[&#!\+][^\x07\x0A\x0D,: ]{1,50}\z/
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# returns if the the string represents a valid nickname
|
|
37
|
+
# this method does not take care of prefixes lik "@", "+", "-"
|
|
38
|
+
# see valid_user? for this funktionality or strip_user_prefixes
|
|
39
|
+
def valid_nickname?
|
|
40
|
+
#self =~ /\A[0-9A-Za-z_][0-9A-Za-z_\-\|\\\[\]\{\}\^\`]*\z/
|
|
41
|
+
self =~ /\A[0-9A-Za-z_\-\|\\\[\]\{\}\^\`]+\z/
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# the same as valid_nickname? except that preceding @, + or - are ignored
|
|
45
|
+
def valid_user?
|
|
46
|
+
strip_user_prefixes.valid_nickname?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# removes indicators from nicknames and channelnames
|
|
50
|
+
def strip_user_prefixes
|
|
51
|
+
prefixes = Butler::IRC::User::PREFIXES.dup
|
|
52
|
+
index = 0
|
|
53
|
+
while (prefixes.has_key?(self[index,1]))
|
|
54
|
+
prefixes.delete(self[index,1])
|
|
55
|
+
index += 1
|
|
56
|
+
end
|
|
57
|
+
return self[index..-1]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# returns prefixes found in front of a nickname
|
|
61
|
+
# Sequential parsing since @@nickname is not valid.
|
|
62
|
+
def user_prefixes
|
|
63
|
+
prefixes = Butler::IRC::User::PREFIXES.dup
|
|
64
|
+
index = 0
|
|
65
|
+
found = 0
|
|
66
|
+
while (prefixes.has_key?(self[index,1]))
|
|
67
|
+
found |= prefixes.delete(self[index,1])
|
|
68
|
+
index += 1
|
|
69
|
+
end
|
|
70
|
+
return found
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Converts string representation of user-prefixes to binary flags
|
|
74
|
+
def to_flags
|
|
75
|
+
result = 0
|
|
76
|
+
0.upto(self.length) { |index|
|
|
77
|
+
result |= Butler::IRC::User::PREFIXES[self[index,1]]
|
|
78
|
+
}
|
|
79
|
+
return result
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# returns a string with formating codes in mirc-format stripped
|
|
83
|
+
def mirc_stripped
|
|
84
|
+
return self.gsub(/(?:[\x02\x0f\x12\x1f\x1d\x09]|\cc\d{1,2}(?:,\d{1,2})?)/, "")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
=begin rdoc
|
|
88
|
+
provides mirc formatting:
|
|
89
|
+
![b]: bold
|
|
90
|
+
![i]: italic
|
|
91
|
+
![u]: underline
|
|
92
|
+
![r]: reverse
|
|
93
|
+
![c]: reset color
|
|
94
|
+
![cm]: set color, m=0-15 or (COLOR_CONSTANT)
|
|
95
|
+
![cm,n]: set color, m is foreground, n is background, m,n=0-15 or (COLOR_CONSTANT)
|
|
96
|
+
![o]: reset all effects
|
|
97
|
+
|
|
98
|
+
Samples:
|
|
99
|
+
![bc(blue)]Bold blue text![o] and normal again
|
|
100
|
+
![bc2]Bold blue text![o] and normal again (same as above)
|
|
101
|
+
![bc(white),(black)]White bold text on black background![o]
|
|
102
|
+
![bi]Bold italic text![o]
|
|
103
|
+
|
|
104
|
+
Valid colors are:
|
|
105
|
+
white (mirc-code: 0)
|
|
106
|
+
black (mirc-code: 1)
|
|
107
|
+
blue (mirc-code: 2)
|
|
108
|
+
green (mirc-code: 3)
|
|
109
|
+
red (mirc-code: 4)
|
|
110
|
+
brown (mirc-code: 5)
|
|
111
|
+
purple (mirc-code: 6)
|
|
112
|
+
orange (mirc-code: 7)
|
|
113
|
+
yellow (mirc-code: 8)
|
|
114
|
+
ltgreen (mirc-code: 9)
|
|
115
|
+
teal (mirc-code: 10)
|
|
116
|
+
ltcyan (mirc-code: 11)
|
|
117
|
+
ltblue (mirc-code: 12)
|
|
118
|
+
pink (mirc-code: 13)
|
|
119
|
+
grey (mirc-code: 14)
|
|
120
|
+
ltgrey (mirc-code: 15)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
Note: not every font/size combination displays bold/italic text.
|
|
124
|
+
=end
|
|
125
|
+
def mirc_formatted # FIXME - copied code, ugly & slow
|
|
126
|
+
self.gsub( /!\[(.*?)\]/ ) do |match|
|
|
127
|
+
codes = $1.downcase
|
|
128
|
+
repl = ""
|
|
129
|
+
i = 0
|
|
130
|
+
while i < codes.length
|
|
131
|
+
case codes[i].chr
|
|
132
|
+
when 'b'
|
|
133
|
+
repl << 2.chr
|
|
134
|
+
when 'o'
|
|
135
|
+
repl << 15.chr
|
|
136
|
+
when 'r'
|
|
137
|
+
repl << 18.chr
|
|
138
|
+
when 'u'
|
|
139
|
+
repl << 31.chr
|
|
140
|
+
when 'i'
|
|
141
|
+
repl << 29.chr
|
|
142
|
+
when '|'
|
|
143
|
+
repl << 9.chr
|
|
144
|
+
when 'c'
|
|
145
|
+
bg = nil
|
|
146
|
+
|
|
147
|
+
i, fg = mirc_translated_color( i+1, codes )
|
|
148
|
+
i, bg = mirc_translated_color( i+1, codes ) if i < codes.length && codes[i].chr == ','
|
|
149
|
+
|
|
150
|
+
repl << "" << ( fg || "" )
|
|
151
|
+
repl << "," << bg if bg
|
|
152
|
+
|
|
153
|
+
i -= 1
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
i += 1
|
|
157
|
+
end
|
|
158
|
+
repl
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# helper method for mirc_formatted, extracts color portions from ![c(color)] statements
|
|
163
|
+
def mirc_translated_color( i, s )
|
|
164
|
+
return [ i, nil ] if i >= s.length
|
|
165
|
+
|
|
166
|
+
if s[i].chr == '('
|
|
167
|
+
j = s.index( ')', i )
|
|
168
|
+
return [ j+1, "%02d" % COLORS[ s[i+1..j-1].downcase ] ]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
j = i
|
|
172
|
+
j += 1 while j < s.length && s[j].chr =~ /[0-9]/
|
|
173
|
+
j += 1 if j == s.length
|
|
174
|
+
return [ j, "%02d" % s[i..j-1].to_i ]
|
|
175
|
+
end
|
|
176
|
+
end #String
|
|
177
|
+
|
|
178
|
+
if __FILE__ == $0 then
|
|
179
|
+
require 'test/unit'
|
|
180
|
+
class TestIRCString < Test::Unit::TestCase
|
|
181
|
+
def test_user
|
|
182
|
+
assert("foobar".valid_nickname?)
|
|
183
|
+
assert(!"{foobar}".valid_nickname?)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|