newton 0.0.1

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.
@@ -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