cinch 0.3.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +192 -0
  3. data/Rakefile +53 -43
  4. data/examples/basic/autovoice.rb +32 -0
  5. data/examples/basic/google.rb +35 -0
  6. data/examples/basic/hello.rb +15 -0
  7. data/examples/basic/join_part.rb +38 -0
  8. data/examples/basic/memo.rb +39 -0
  9. data/examples/basic/msg.rb +16 -0
  10. data/examples/basic/seen.rb +36 -0
  11. data/examples/basic/urban_dict.rb +35 -0
  12. data/examples/basic/url_shorten.rb +35 -0
  13. data/examples/plugins/autovoice.rb +40 -0
  14. data/examples/plugins/custom_prefix.rb +23 -0
  15. data/examples/plugins/google.rb +37 -0
  16. data/examples/plugins/hello.rb +22 -0
  17. data/examples/plugins/join_part.rb +42 -0
  18. data/examples/plugins/memo.rb +50 -0
  19. data/examples/plugins/msg.rb +22 -0
  20. data/examples/plugins/multiple_matches.rb +41 -0
  21. data/examples/plugins/seen.rb +45 -0
  22. data/examples/plugins/urban_dict.rb +30 -0
  23. data/examples/plugins/url_shorten.rb +32 -0
  24. data/lib/cinch.rb +7 -20
  25. data/lib/cinch/ban.rb +41 -0
  26. data/lib/cinch/bot.rb +479 -0
  27. data/lib/cinch/callback.rb +11 -0
  28. data/lib/cinch/channel.rb +419 -0
  29. data/lib/cinch/constants.rb +369 -0
  30. data/lib/cinch/exceptions.rb +25 -0
  31. data/lib/cinch/helpers.rb +21 -0
  32. data/lib/cinch/irc.rb +344 -38
  33. data/lib/cinch/isupport.rb +96 -0
  34. data/lib/cinch/logger/formatted_logger.rb +80 -0
  35. data/lib/cinch/logger/logger.rb +44 -0
  36. data/lib/cinch/logger/null_logger.rb +18 -0
  37. data/lib/cinch/mask.rb +46 -0
  38. data/lib/cinch/message.rb +183 -0
  39. data/lib/cinch/message_queue.rb +62 -0
  40. data/lib/cinch/plugin.rb +205 -0
  41. data/lib/cinch/rubyext/infinity.rb +1 -0
  42. data/lib/cinch/rubyext/module.rb +18 -0
  43. data/lib/cinch/rubyext/queue.rb +19 -0
  44. data/lib/cinch/rubyext/string.rb +24 -0
  45. data/lib/cinch/syncable.rb +55 -0
  46. data/lib/cinch/user.rb +325 -0
  47. data/spec/bot_spec.rb +5 -0
  48. data/spec/channel_spec.rb +5 -0
  49. data/spec/cinch_spec.rb +5 -0
  50. data/spec/irc_spec.rb +5 -0
  51. data/spec/message_spec.rb +5 -0
  52. data/spec/plugin_spec.rb +5 -0
  53. data/spec/{helper.rb → spec_helper.rb} +0 -0
  54. data/spec/user_spec.rb +5 -0
  55. metadata +69 -51
  56. data/README.rdoc +0 -195
  57. data/examples/autovoice.rb +0 -32
  58. data/examples/custom_patterns.rb +0 -19
  59. data/examples/custom_prefix.rb +0 -25
  60. data/examples/google.rb +0 -31
  61. data/examples/hello.rb +0 -13
  62. data/examples/join_part.rb +0 -26
  63. data/examples/memo.rb +0 -40
  64. data/examples/msg.rb +0 -14
  65. data/examples/named-param-types.rb +0 -19
  66. data/examples/seen.rb +0 -41
  67. data/examples/urban_dict.rb +0 -31
  68. data/examples/url_shorten.rb +0 -34
  69. data/lib/cinch/base.rb +0 -368
  70. data/lib/cinch/irc/message.rb +0 -135
  71. data/lib/cinch/irc/parser.rb +0 -141
  72. data/lib/cinch/irc/socket.rb +0 -329
  73. data/lib/cinch/names.rb +0 -54
  74. data/lib/cinch/rules.rb +0 -171
  75. data/spec/base_spec.rb +0 -94
  76. data/spec/irc/helper.rb +0 -8
  77. data/spec/irc/message_spec.rb +0 -61
  78. data/spec/irc/parser_spec.rb +0 -103
  79. data/spec/irc/socket_spec.rb +0 -90
  80. data/spec/names_spec.rb +0 -393
  81. data/spec/options_spec.rb +0 -45
  82. data/spec/rules_spec.rb +0 -109
@@ -0,0 +1 @@
1
+ Infinity = 1.0/0.0
@@ -0,0 +1,18 @@
1
+ class Module
2
+ # @api private
3
+ def synced_attr_reader(attribute)
4
+ define_method(attribute) do
5
+ attr(attribute)
6
+ end
7
+
8
+ define_method("#{attribute}_unsynced") do
9
+ attr(attribute, false, true)
10
+ end
11
+ end
12
+
13
+ # @api private
14
+ def synced_attr_accessor(attr)
15
+ synced_attr_reader(attr)
16
+ attr_accessor(attr)
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ require "thread"
2
+ class Queue
3
+ def unshift(obj)
4
+ t = nil
5
+ @mutex.synchronize{
6
+ @que.unshift obj
7
+ begin
8
+ t = @waiting.shift
9
+ t.wakeup if t
10
+ rescue ThreadError
11
+ retry
12
+ end
13
+ }
14
+ begin
15
+ t.run if t
16
+ rescue ThreadError
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ class String
2
+ def irc_downcase(mapping)
3
+ case mapping
4
+ when :rfc1459
5
+ self.tr("A-Z[]\\^", "a-z{}|~")
6
+ when :"strict-rfc1459"
7
+ self.tr("A-Z[]\\", "a-z{}|")
8
+ else
9
+ # when :ascii or unknown/nil
10
+ self.tr("A-Z", "a-z")
11
+ end
12
+ end
13
+
14
+ def irc_upcase(mapping)
15
+ case mapping
16
+ when :ascii
17
+ self.tr("a-z", "A-Z")
18
+ when :rfc1459
19
+ self.tr("a-z{}|~", "A-Z[]\\^")
20
+ when :strict-rfc1459
21
+ self.tr("a-z{}|", "A-Z[]\\")
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,55 @@
1
+ module Cinch
2
+ module Syncable
3
+ # Blocks until the object is synced.
4
+ #
5
+ # @return [void]
6
+ def wait_until_synced(attr)
7
+ attr = attr.to_sym
8
+ while true
9
+ return if @synced_attributes.include?(attr)
10
+ sleep 0.1
11
+ end
12
+ end
13
+
14
+ # @api private
15
+ # @return [void]
16
+ def sync(attribute, value, data = false)
17
+ if data
18
+ @data[attribute] = value
19
+ else
20
+ instance_variable_set("@#{attribute}", value)
21
+ end
22
+ @synced_attributes << attribute
23
+ end
24
+
25
+ def synced?(attribute)
26
+ @synced_attributes.include?(attribute)
27
+ end
28
+
29
+ def unsync(attribute)
30
+ @synced_attributes.delete(attribute)
31
+ end
32
+
33
+ # @api private
34
+ def attr(attribute, data = false, unsync = false)
35
+ unless unsync
36
+ if @when_requesting_synced_attribute
37
+ @when_requesting_synced_attribute.call(attribute)
38
+ end
39
+ wait_until_synced(attribute)
40
+ end
41
+
42
+ if data
43
+ return @data[attribute]
44
+ else
45
+ return instance_variable_get("@#{attribute}")
46
+ end
47
+ end
48
+
49
+ # @api private
50
+ # @return [void]
51
+ def mark_as_synced(attribute)
52
+ @synced_attributes << attribute
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,325 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Cinch
3
+ class User
4
+ include Syncable
5
+
6
+ @users = {}
7
+ class << self
8
+
9
+ # @overload find_ensured(nick, bot)
10
+ # Finds or creates a user based on his nick.
11
+ #
12
+ # @param [String] nick The user's nickname
13
+ # @param [Bot] bot An instance of Bot
14
+ # @overload find_ensured(user, nick, host, bot)
15
+ # Finds or creates a user based on his nick but already
16
+ # setting user and host.
17
+ #
18
+ # @param [String] user The username
19
+ # @param [String] nick The nickname
20
+ # @param [String] host The user's hostname
21
+ # @param [Bot] bot An instance of bot
22
+ #
23
+ # @return [User]
24
+ def find_ensured(*args)
25
+ # FIXME CASEMAPPING
26
+ case args.size
27
+ when 2
28
+ nick = args.first
29
+ bargs = [args.first]
30
+ bot = args.last
31
+ when 4
32
+ nick = args[1]
33
+ bot = args.pop
34
+ bargs = args
35
+ else
36
+ raise ArgumentError
37
+ end
38
+ downcased_nick = nick.irc_downcase(bot.irc.isupport["CASEMAPPING"])
39
+ @users[downcased_nick] ||= new(*bargs, bot)
40
+ @users[downcased_nick]
41
+ end
42
+
43
+ # Finds a user.
44
+ #
45
+ # @param [String] nick nick of a user
46
+ # @return [User, nil]
47
+ def find(nick)
48
+ @users[nick]
49
+ end
50
+
51
+ # @return [Array<User>] Returns all users
52
+ def all
53
+ @users.values
54
+ end
55
+ end
56
+
57
+
58
+ # @return [String]
59
+ attr_reader :nick
60
+ # @return [Bot]
61
+ attr_reader :bot
62
+ # @return [Boolean]
63
+ attr_reader :synced
64
+ # @return [Boolean]
65
+ attr_reader :in_whois
66
+
67
+ # @return [String]
68
+ attr_reader :user
69
+ undef_method "user"
70
+
71
+ # @return [String]
72
+ attr_reader :host
73
+ undef_method "host"
74
+
75
+ # @return [String]
76
+ attr_reader :realname
77
+ undef_method "realname"
78
+
79
+ # @return [String]
80
+ attr_reader :authname
81
+ undef_method "authname"
82
+
83
+ # @return [Number] How long this user has been idle, in seconds.
84
+ # This is a snapshot of the last WHOIS.
85
+ attr_reader :idle
86
+ undef_method "idle"
87
+
88
+ # @return [Time]
89
+ attr_reader :signed_on_at
90
+ undef_method "signed_on_at"
91
+
92
+ # @return [Boolean] True if the instance references an user who
93
+ # cannot be found on the server.
94
+ attr_reader :unknown
95
+ alias_method :unknown?, :unknown
96
+ undef_method "unknown?"
97
+ undef_method "unknown"
98
+ def unknown
99
+ self.unknown?
100
+ end
101
+
102
+ # @return [Array<Channel>] All channels the user is in.
103
+ attr_reader :channels
104
+ undef_method "channels"
105
+
106
+ # @return [Boolean] True if the user is using a secure connection, i.e. SSL.
107
+ attr_reader :secure
108
+ alias_method :secure?, :secure
109
+ undef_method "secure?"
110
+ undef_method "secure"
111
+ def secure
112
+ self.secure?
113
+ end
114
+
115
+ # By default, you can use methods like User#user, User#host and
116
+ # alike – If you however fear that another thread might change
117
+ # data while you're using it and if this means a critical issue to
118
+ # your code, you can store the result of this method and work with
119
+ # that instead.
120
+ #
121
+ # @example
122
+ # on :channel do
123
+ # data = user.data
124
+ # do_something_with(data.user)
125
+ # do_something_with(data.host)
126
+ # end
127
+ # @return [Hash]
128
+ attr_reader :data
129
+ def initialize(*args)
130
+ @data = {
131
+ :user => nil,
132
+ :host => nil,
133
+ :realname => nil,
134
+ :authname => nil,
135
+ :idle => 0,
136
+ :signed_on_at => nil,
137
+ :unknown? => false,
138
+ :channels => [],
139
+ :secure? => false,
140
+ }
141
+ case args.size
142
+ when 2
143
+ @nick, @bot = args
144
+ when 4
145
+ @data[:user], @nick, @data[:host], @bot = args
146
+ else
147
+ raise ArgumentError
148
+ end
149
+
150
+ @synced_attributes = Set.new
151
+
152
+ @when_requesting_synced_attribute = lambda {|attr|
153
+ unless @synced
154
+ unsync attr
155
+ whois
156
+ end
157
+ }
158
+ end
159
+
160
+ # Checks if the user is identified. Currently officially supports
161
+ # Quakenet and Freenode.
162
+ #
163
+ # @return [Boolean] true if the user is identified
164
+ def authed?
165
+ @data[:authname]
166
+ end
167
+
168
+ # @see Syncable#attr
169
+ def attr(attribute, data = true, unsync = false)
170
+ super
171
+ end
172
+
173
+ # Queries the IRC server for information on the user. This will
174
+ # set the User's state to not synced. After all information are
175
+ # received, the object will be set back to synced.
176
+ #
177
+ # @return [void]
178
+ def whois
179
+ return if @in_whois
180
+ @synced = false
181
+ @data.keys.each do |attr|
182
+ unsync attr
183
+ end
184
+
185
+ @in_whois = true
186
+ @bot.raw "WHOIS #@nick #@nick"
187
+ end
188
+ alias_method :refresh, :whois
189
+
190
+ # Send a message to the user.
191
+ #
192
+ # @param [String] message the message
193
+ # @return [void]
194
+ def send(message)
195
+ @bot.msg(@nick, message)
196
+ end
197
+ alias_method :privmsg, :send
198
+ alias_method :msg, :send
199
+
200
+ # Send a message to the user, but remove any non-printable
201
+ # characters. The purpose of this method is to send text from
202
+ # untrusted sources, like other users or feeds.
203
+ #
204
+ # Note: this will **break** any mIRC color codes embedded in the
205
+ # string.
206
+ #
207
+ # @param (see #send)
208
+ # @return (see #send)
209
+ # @see #send
210
+ # @see Bot#safe_msg
211
+ # @todo Handle mIRC color codes more gracefully.
212
+ def safe_send(message)
213
+ @bot.safe_msg(@nick, message)
214
+ end
215
+ alias_method :safe_privmsg, :safe_send
216
+ alias_method :safe_msg, :safe_send
217
+
218
+ # Send a CTCP to the user.
219
+ #
220
+ # @param [String] message the ctcp message
221
+ # @return [void]
222
+ def ctcp(message)
223
+ send "\001#{message}\001"
224
+ end
225
+
226
+ # Send an action (/me) to the user.
227
+ #
228
+ # @param [String] message the message
229
+ # @return [void]
230
+ # @see #safe_action
231
+ def action(message)
232
+ @bot.action(@name, message)
233
+ end
234
+
235
+ # Send an action (/me) to the user but remove any non-printable
236
+ # characters. The purpose of this method is to send text from
237
+ # untrusted sources, like other users or feeds.
238
+ #
239
+ # Note: this will **break** any mIRC color codes embedded in the
240
+ # string.
241
+ #
242
+ # @param (see #action)
243
+ # @return (see #action)
244
+ # @see #action
245
+ # @see Bot#safe_action
246
+ # @todo Handle mIRC color codes more gracefully.
247
+ def safe_action(message)
248
+ @bot.safe_action(@name, message)
249
+ end
250
+
251
+ # @return [String]
252
+ def to_s
253
+ @nick
254
+ end
255
+
256
+ # @return [String]
257
+ def inspect
258
+ "#<User nick=#{@nick.inspect}>"
259
+ end
260
+
261
+ # Generates a mask for the user.
262
+ #
263
+ # @param [String] s a pattern for generating the mask.
264
+ #
265
+ # - %n = nickname
266
+ # - %u = username
267
+ # - %h = host
268
+ # - %r = realname
269
+ # - %a = authname
270
+ #
271
+ # @return [Mask]
272
+ def mask(s = "%n!%u@%h")
273
+ s = s.gsub(/%(.)/) {
274
+ case $1
275
+ when "n"
276
+ @nick
277
+ when "u"
278
+ self.user
279
+ when "h"
280
+ self.host
281
+ when "r"
282
+ self.realname
283
+ when "a"
284
+ self.authname
285
+ end
286
+ }
287
+
288
+ Mask.new(s)
289
+ end
290
+
291
+ # Provides synced access to user attributes.
292
+ def method_missing(m, *args)
293
+ if m.to_s =~ /^(.+)_unsynced$/
294
+ m = $1.to_sym
295
+ unsync = true
296
+ end
297
+
298
+ if @data.has_key?(m)
299
+ attr(m, true, unsync = false)
300
+ else
301
+ super
302
+ end
303
+ end
304
+
305
+ # @return [Boolean]
306
+ def ==(other)
307
+ return case other
308
+ when self.class
309
+ @nick == other.nick
310
+ when String
311
+ self.to_s == other
312
+ when Bot
313
+ self.nick == other.config.nick
314
+ else
315
+ false
316
+ end
317
+ end
318
+ alias_method :eql?, "=="
319
+
320
+ # @return [Fixnum]
321
+ def hash
322
+ @nick.hash
323
+ end
324
+ end
325
+ end