newton 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,362 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "set"
3
+
4
+ module Newton
5
+ class Channel
6
+ include Syncable
7
+ @channels = {}
8
+
9
+ class << self
10
+ # Finds or creates a channel.
11
+ #
12
+ # @param [String] name name of a channel
13
+ # @param [Bot] bot a bot
14
+ # @return [Channel]
15
+ # @see Bot#Channel
16
+ def find_ensured(name, bot)
17
+ downcased_name = name.irc_downcase(bot.irc.isupport["CASEMAPPING"])
18
+ @channels[downcased_name] ||= new(name, bot)
19
+ @channels[downcased_name]
20
+ end
21
+
22
+ # Finds a channel.
23
+ #
24
+ # @param [String] name name of a channel
25
+ # @return [Channel, nil]
26
+ def find(name)
27
+ @channels[name]
28
+ end
29
+
30
+ # @return [Array<Channel>] Returns all channels
31
+ def all
32
+ @channels.values
33
+ end
34
+ end
35
+
36
+ # @return [Bot]
37
+ attr_reader :bot
38
+
39
+ # @return [String]
40
+ attr_reader :name
41
+
42
+ # @return [Array<User>]
43
+ attr_reader :users
44
+ synced_attr_reader :users
45
+
46
+ # @return [String]
47
+ attr_accessor :topic
48
+ synced_attr_reader :topic
49
+
50
+ # @return [Array<Ban>]
51
+ attr_reader :bans
52
+ synced_attr_reader :bans
53
+
54
+ # @return [Array<String>]
55
+ attr_reader :modes
56
+ synced_attr_reader :modes
57
+ def initialize(name, bot)
58
+ @bot = bot
59
+ @name = name
60
+ @users = {}
61
+ @bans = []
62
+
63
+ @modes = {}
64
+ # TODO raise if not a channel
65
+
66
+ @topic = nil
67
+
68
+ @in_channel = false
69
+
70
+ @synced_attributes = Set.new
71
+ @when_requesting_synced_attribute = lambda {|attr|
72
+ unless @in_channel
73
+ unsync(attr)
74
+ case attr
75
+ when :users
76
+ @bot.raw "NAMES #@name"
77
+ when :topic
78
+ @bot.raw "TOPIC #@name"
79
+ when :bans
80
+ @bot.raw "MODE #@name +b"
81
+ when :modes
82
+ @bot.raw "MODE #@name"
83
+ end
84
+ end
85
+ }
86
+ end
87
+
88
+ attr_accessor :limit
89
+ # @return [Number]
90
+ def limit
91
+ @modes["l"].to_i
92
+ end
93
+
94
+ def limit=(val)
95
+ if val == -1 or val.nil?
96
+ mode "-l"
97
+ else
98
+ mode "+l #{val}"
99
+ end
100
+ end
101
+
102
+ attr_accessor :secret
103
+ # @return [Boolean] true if the channel is secret (+s)
104
+ def secret
105
+ @modes["s"]
106
+ end
107
+ alias_method :secret?, :secret
108
+
109
+ def secret=(bool)
110
+ if bool
111
+ mode "+s"
112
+ else
113
+ mode "-s"
114
+ end
115
+ end
116
+
117
+ attr_accessor :moderated # documentation only
118
+ # @return [Boolean] true if the channel is moderated (only users
119
+ # with +o and +v are able to send messages)
120
+ def moderated
121
+ @modes["m"]
122
+ end
123
+ alias_method :moderated?, :moderated
124
+
125
+ def moderated=(val)
126
+ if bool
127
+ mode "+m"
128
+ else
129
+ mode "-m"
130
+ end
131
+ end
132
+
133
+ attr_accessor :invite_only
134
+ # @return [Boolean] true if the channel is invite only (+i)
135
+ def invite_only
136
+ @modes["i"]
137
+ end
138
+ alias_method :invite_only?, :invite_only
139
+
140
+ def invite_only=(bool)
141
+ if bool
142
+ mode "+i"
143
+ else
144
+ mode "-i"
145
+ end
146
+ end
147
+
148
+ attr_accessor :key
149
+ # @return [String, nil]
150
+ def key
151
+ @modes["k"]
152
+ end
153
+
154
+ def key=(new_key)
155
+ if new_key.nil?
156
+ mode "-k #{key}"
157
+ else
158
+ mode "+k #{new_key}"
159
+ end
160
+ end
161
+
162
+ # Sets or unsets modes. Most of the time you won't need this but
163
+ # use setter methods like {Channel#invite_only=}.
164
+ # @param [String] s a mode string
165
+ # @return [void]
166
+ # @example
167
+ # channel.mode "+n"
168
+ def mode(s)
169
+ @bot.raw "MODE #@name #{s}"
170
+ end
171
+
172
+ # @api private
173
+ # @return [void]
174
+ def sync_modes(all = true)
175
+ unsync :users
176
+ unsync :bans
177
+ unsync :modes
178
+ @bot.raw "NAMES #@name" if all
179
+ @bot.raw "MODE #@name +b" # bans
180
+ @bot.raw "MODE #@name"
181
+ end
182
+
183
+ # @return [Boolean] true if `user` is opped in the channel
184
+ def opped?(user)
185
+ user = User.find_ensured(user, @bot) unless user.is_a?(User)
186
+ @users[user] == "@"
187
+ end
188
+
189
+ # @return [Boolean] true if `user` is voiced in the channel
190
+ def voiced?(user)
191
+ user = User.find_ensured(user, @bot) unless user.is_a?(User)
192
+ @users[user] == "+"
193
+ end
194
+
195
+ # Bans someone from the channel.
196
+ #
197
+ # @param [Ban, Mask, User, String] target the mask to ban
198
+ # @return [Mask] the mask used for banning
199
+ def ban(target)
200
+ mask = Mask.from(target)
201
+
202
+ @bot.raw "MODE #@name +b #{mask}"
203
+ mask
204
+ end
205
+
206
+ # Unbans someone from the channel.
207
+ #
208
+ # @param [Ban, Mask, User, String] target the mask to unban
209
+ # @return [Mask] the mask used for unbanning
210
+ def unban(target)
211
+ mask = Mask.from(target)
212
+
213
+ @bot.raw "MODE #@name -b #{mask}"
214
+ mask
215
+ end
216
+
217
+ # @param [String, User] user the user to op
218
+ # @return [void]
219
+ def op(user)
220
+ @bot.raw "MODE #@name +o #{user}"
221
+ end
222
+
223
+ # @param [String, User] user the user to deop
224
+ # @return [void]
225
+ def deop(user)
226
+ @bot.raw "MODE #@name -o #{user}"
227
+ end
228
+
229
+ # @param [String, User] user the user to voice
230
+ # @return [void]
231
+ def voice(user)
232
+ @bot.raw "MODE #@name +v #{user}"
233
+ end
234
+
235
+ # @param [String, User] user the user to devoice
236
+ # @return [void]
237
+ def devoice(user)
238
+ @bot.raw "MODE #@name -v #{user}"
239
+ end
240
+
241
+ # @api private
242
+ # @return [void]
243
+ def add_user(user, mode = nil)
244
+ @in_channel = true if user == @bot
245
+ @users[user] = mode # TODO can a user have more than one mode?
246
+ end
247
+
248
+ # @api private
249
+ # @return [void]
250
+ def remove_user(user)
251
+ @in_channel = false if user == @bot
252
+ @users.delete(user)
253
+ end
254
+
255
+ # Removes all users
256
+ #
257
+ # @api private
258
+ # @return [void]
259
+ def clear_users
260
+ @users.clear
261
+ end
262
+
263
+ # Send a message to the channel.
264
+ #
265
+ # @param [String] message the message
266
+ # @return [void]
267
+ def send(message)
268
+ @bot.msg(@name, message)
269
+ end
270
+ alias_method :privmsg, :send
271
+
272
+
273
+ # Send a CTCP to the channel.
274
+ #
275
+ # @param [String] message the ctcp message
276
+ # @return [void]
277
+ def ctcp(message)
278
+ send "\001#{message}\001"
279
+ end
280
+
281
+ # Invoke an action (/me) in the channel.
282
+ #
283
+ # @param [String] message the message
284
+ # @return [void]
285
+ def action(message)
286
+ @bot.action(@name, message)
287
+ end
288
+
289
+ # Invites a user to the channel.
290
+ #
291
+ # @param [String, User] user the user to invite
292
+ # @return [void]
293
+ def invite(user)
294
+ @bot.raw("INVITE #{user} #@name")
295
+ end
296
+
297
+ # Sets the topic.
298
+ #
299
+ # @param [String] new_topic the new topic
300
+ # @raise [Exceptions::TopicTooLong]
301
+ def topic=(new_topic)
302
+ if new_topic.size > @bot.irc.isupport["TOPICLEN"] && @bot.strict?
303
+ raise Exceptions::TopicTooLong, new_topic
304
+ end
305
+
306
+ @bot.raw "TOPIC #@name :#{new_topic}"
307
+ end
308
+
309
+ # Kicks a user from the channel.
310
+ #
311
+ # @param [String, User] user the user to kick
312
+ # @param [String] a reason for the kick
313
+ # @raise [Exceptions::KickReasonTooLong]
314
+ # @return [void]
315
+ def kick(user, reason = nil)
316
+ if reason.to_s.size > @bot.irc.isupport["KICKLEN"] && @bot.strict?
317
+ raise Exceptions::KickReasonTooLong, reason
318
+ end
319
+
320
+ @bot.raw("KICK #@name #{user} :#{reason}")
321
+ end
322
+
323
+ # Invites a user to the channel.
324
+ #
325
+ # @param [String, User] user the user to invite
326
+ # @return [void]
327
+ def invite(user)
328
+ @bot.raw "INVITE #{user} #@name"
329
+ end
330
+
331
+ # Causes the bot to part from the channel.
332
+ #
333
+ # @param [String] message the part message.
334
+ # @return [void]
335
+ def part(message = nil)
336
+ @bot.raw "PART #@name :#{message}"
337
+ end
338
+
339
+ # Joins the channel
340
+ #
341
+ # @param [String] key the channel key, if any. If none is
342
+ # specified but @key is set, @key will be used
343
+ # @return [void]
344
+ def join(key = nil)
345
+ if key.nil? and modes["k"] != true
346
+ key = modes["k"]
347
+ end
348
+ @bot.raw "JOIN #{[@name, key].compact.join(" ")}"
349
+ end
350
+
351
+ # @return [String]
352
+ def to_s
353
+ @name
354
+ end
355
+ alias_method :to_str, :to_s
356
+
357
+ # @return [String]
358
+ def inspect
359
+ "#<Channel name=#{@name.inspect}>"
360
+ end
361
+ end
362
+ end
@@ -0,0 +1,123 @@
1
+ module Newton
2
+ ERR_NOSUCHNICK = 401
3
+ ERR_NOSUCHSERVER = 402
4
+ ERR_NOSUCHCHANNEL = 403
5
+ ERR_CANNOTSENDTOCHAN = 404
6
+ ERR_TOOMANYCHANNELS = 405
7
+ ERR_WASNOSUCHNICK = 406
8
+ ERR_TOOMANYTARGETS = 407
9
+ ERR_NOORIGIN = 409
10
+ ERR_NORECIPIENT = 411
11
+ ERR_NOTEXTTOSEND = 412
12
+ ERR_NOTOPLEVEL = 413
13
+ ERR_WILDTOPLEVEL = 414
14
+ ERR_UNKNOWNCOMMAND = 421
15
+ ERR_NOMOTD = 422
16
+ ERR_NOADMININFO = 423
17
+ ERR_FILEERROR = 424
18
+ ERR_NONICKNAMEGIVEN = 431
19
+ ERR_ERRONEUSNICKNAME = 432
20
+ ERR_NICKNAMEINUSE = 433
21
+ ERR_NICKCOLLISION = 436
22
+ ERR_USERNOTINCHANNEL = 441
23
+ ERR_NOTONCHANNEL = 442
24
+ ERR_USERONCHANNEL = 443
25
+ ERR_NOLOGIN = 444
26
+ ERR_SUMMONDISABLED = 445
27
+ ERR_USERSDISABLED = 446
28
+ ERR_NOTREGISTERED = 451
29
+ ERR_NEEDMOREPARAMS = 461
30
+ ERR_ALREADYREGISTRED = 462
31
+ ERR_NOPERMFORHOST = 463
32
+ ERR_PASSWDMISMATCH = 464
33
+ ERR_YOUREBANNEDCREEP = 465
34
+ ERR_KEYSET = 467
35
+ ERR_CHANNELISFULL = 471
36
+ ERR_UNKNOWNMODE = 472
37
+ ERR_INVITEONLYCHAN = 473
38
+ ERR_BANNEDFROMCHAN = 474
39
+ ERR_BADCHANNELKEY = 475
40
+ ERR_NOPRIVILEGES = 481
41
+ ERR_CHANOPRIVSNEEDED = 482
42
+ ERR_CANTKILLSERVER = 483
43
+ ERR_NOOPERHOST = 491
44
+ ERR_UMODEUNKNOWNFLAG = 501
45
+ ERR_USERSDONTMATCH = 502
46
+
47
+ RPL_NONE = 300
48
+ RPL_USERHOST = 302
49
+ RPL_ISON = 303
50
+ RPL_AWAY = 301
51
+ RPL_UNAWAY = 305
52
+ RPL_NOWAWAY = 306
53
+ RPL_WHOISUSER = 311
54
+ RPL_WHOISSERVER = 312
55
+ RPL_WHOISOPERATOR = 313
56
+ RPL_WHOISIDLE = 317
57
+ RPL_ENDOFWHOIS = 318
58
+ RPL_WHOISCHANNELS = 319
59
+ RPL_WHOWASUSER = 314
60
+ RPL_ENDOFWHOWAS = 369
61
+ RPL_LISTSTART = 321
62
+ RPL_LIST = 322
63
+ RPL_LISTEND = 323
64
+ RPL_CHANNELMODEIS = 324
65
+ RPL_WHOISACCOUNT = 330
66
+ RPL_NOTOPIC = 331
67
+ RPL_TOPIC = 332
68
+ RPL_INVITING = 341
69
+ RPL_SUMMONING = 342
70
+ RPL_VERSION = 351
71
+ RPL_WHOREPLY = 352
72
+ RPL_ENDOFWHO = 315
73
+ RPL_NAMEREPLY = 353
74
+ RPL_NAMREPLY = 353
75
+ RPL_ENDOFNAMES = 366
76
+ RPL_LINKS = 364
77
+ RPL_ENDOFLINKS = 365
78
+ RPL_BANLIST = 367
79
+ RPL_ENDOFBANLIST = 368
80
+ RPL_INFO = 371
81
+ RPL_ENDOFINFO = 374
82
+ RPL_MOTDSTART = 375
83
+ RPL_MOTD = 372
84
+ RPL_ENDOFMOTD = 376
85
+ RPL_YOUREOPER = 381
86
+ RPL_REHASHING = 382
87
+ RPL_TIME = 391
88
+ RPL_USERSSTART = 392
89
+ RPL_USERS = 393
90
+ RPL_ENDOFUSERS = 394
91
+ RPL_NOUSERS = 395
92
+ RPL_TRACELINK = 200
93
+ RPL_TRACECONNECTING = 201
94
+ RPL_TRACEHANDSHAKE = 202
95
+ RPL_TRACEUNKNOWN = 203
96
+ RPL_TRACEOPERATOR = 204
97
+ RPL_TRACEUSER = 205
98
+ RPL_TRACESERVER = 206
99
+ RPL_TRACENEWTYPE = 208
100
+ RPL_TRACELOG = 261
101
+ RPL_STATSLINKINFO = 211
102
+ RPL_STATSCOMMANDS = 212
103
+ RPL_STATSCLINE = 213
104
+ RPL_STATSNLINE = 214
105
+ RPL_STATSILINE = 215
106
+ RPL_STATSKLINE = 216
107
+ RPL_STATSYLINE = 218
108
+ RPL_ENDOFSTATS = 219
109
+ RPL_STATSLLINE = 241
110
+ RPL_STATSUPTIME = 242
111
+ RPL_STATSOLINE = 243
112
+ RPL_STATSHLINE = 244
113
+ RPL_UMODEIS = 221
114
+ RPL_LUSERCLIENT = 251
115
+ RPL_LUSEROP = 252
116
+ RPL_LUSERUNKNOWN = 253
117
+ RPL_LUSERCHANNELS = 254
118
+ RPL_LUSERME = 255
119
+ RPL_ADMINME = 256
120
+ RPL_ADMINLOC1 = 257
121
+ RPL_ADMINLOC2 = 258
122
+ RPL_ADMINEMAIL = 259
123
+ end
@@ -0,0 +1,25 @@
1
+ module Newton
2
+ module Exceptions
3
+ # Generic error. Superclass for all Newton-specific errors.
4
+ class Generic < ::StandardError
5
+ end
6
+
7
+ class ArgumentTooLong < Generic
8
+ end
9
+
10
+ # Error that is raised when a topic is too long to be set.
11
+ class TopicTooLong < ArgumentTooLong
12
+ end
13
+
14
+ # Error that is raised when a nick is too long to be used.
15
+ class NickTooLong < ArgumentTooLong
16
+ end
17
+
18
+ # Error that is raised when a kick reason is too long.
19
+ class KickReasonTooLong < ArgumentTooLong
20
+ end
21
+
22
+ class UnsupportedFeature < Generic
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,64 @@
1
+ module Newton
2
+ class FormattedLogger
3
+ COLORS = {
4
+ :reset => "\e[0m",
5
+ :bold => "\e[1m",
6
+ :red => "\e[31m",
7
+ :green => "\e[32m",
8
+ :yellow => "\e[33m",
9
+ :blue => "\e[34m",
10
+ }
11
+
12
+ class << self
13
+ # @return [void]
14
+ def debug(message)
15
+ log(message, :debug)
16
+ end
17
+
18
+ # @api private
19
+ # @return [void]
20
+ def log(message, kind = :generic)
21
+ message = message.to_s.chomp # don't want to tinker with the original string
22
+ unless $stdout.tty?
23
+ $stderr.puts message
24
+ return
25
+ end
26
+
27
+ if kind == :debug
28
+ prefix = colorize("!! ", :yellow)
29
+ message = prefix + message
30
+ else
31
+ pre, msg = message.split(" :", 2)
32
+ pre_parts = pre.split(" ")
33
+
34
+ if kind == :incoming
35
+ prefix = colorize(">> ", :green)
36
+
37
+ if pre_parts.size == 1
38
+ pre_parts[0] = colorize(pre_parts[0], :bold)
39
+ else
40
+ pre_parts[0] = colorize(pre_parts[0], :blue)
41
+ pre_parts[1] = colorize(pre_parts[1], :bold)
42
+ end
43
+
44
+ elsif kind == :outgoing
45
+ prefix = colorize("<< ", :red)
46
+ pre_parts[0] = colorize(pre_parts[0], :bold)
47
+ end
48
+
49
+ message = prefix + pre_parts.join(" ")
50
+ message << colorize(" :#{msg}", :yellow) if msg
51
+ end
52
+ $stderr.puts message
53
+ end
54
+
55
+ # @api private
56
+ # @param [String] text text to colorize
57
+ # @param [Array<Symbol>] codes array of colors to apply
58
+ # @return [String] colorized string
59
+ def colorize(text, *codes)
60
+ COLORS.values_at(*codes).join + text + COLORS[:reset]
61
+ end
62
+ end
63
+ end
64
+ end