cinch 1.1.3 → 2.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/LICENSE +1 -0
  2. data/README.md +3 -3
  3. data/docs/bot_options.md +435 -0
  4. data/docs/changes.md +440 -0
  5. data/docs/common_mistakes.md +35 -0
  6. data/docs/common_tasks.md +47 -0
  7. data/docs/encodings.md +67 -0
  8. data/docs/events.md +272 -0
  9. data/docs/logging.md +5 -0
  10. data/docs/migrating.md +267 -0
  11. data/docs/readme.md +18 -0
  12. data/examples/plugins/custom_prefix.rb +1 -1
  13. data/examples/plugins/dice_roll.rb +38 -0
  14. data/examples/plugins/lambdas.rb +1 -1
  15. data/examples/plugins/memo.rb +16 -10
  16. data/examples/plugins/url_shorten.rb +1 -0
  17. data/lib/cinch.rb +5 -60
  18. data/lib/cinch/ban.rb +13 -7
  19. data/lib/cinch/bot.rb +228 -403
  20. data/lib/cinch/{cache_manager.rb → cached_list.rb} +5 -1
  21. data/lib/cinch/callback.rb +3 -0
  22. data/lib/cinch/channel.rb +119 -195
  23. data/lib/cinch/{channel_manager.rb → channel_list.rb} +6 -3
  24. data/lib/cinch/configuration.rb +73 -0
  25. data/lib/cinch/configuration/bot.rb +47 -0
  26. data/lib/cinch/configuration/dcc.rb +16 -0
  27. data/lib/cinch/configuration/plugins.rb +41 -0
  28. data/lib/cinch/configuration/sasl.rb +17 -0
  29. data/lib/cinch/configuration/ssl.rb +19 -0
  30. data/lib/cinch/configuration/storage.rb +37 -0
  31. data/lib/cinch/configuration/timeouts.rb +14 -0
  32. data/lib/cinch/constants.rb +531 -369
  33. data/lib/cinch/dcc.rb +12 -0
  34. data/lib/cinch/dcc/dccable_object.rb +37 -0
  35. data/lib/cinch/dcc/incoming.rb +1 -0
  36. data/lib/cinch/dcc/incoming/send.rb +131 -0
  37. data/lib/cinch/dcc/outgoing.rb +1 -0
  38. data/lib/cinch/dcc/outgoing/send.rb +115 -0
  39. data/lib/cinch/exceptions.rb +8 -1
  40. data/lib/cinch/formatting.rb +106 -0
  41. data/lib/cinch/handler.rb +104 -0
  42. data/lib/cinch/handler_list.rb +86 -0
  43. data/lib/cinch/helpers.rb +167 -10
  44. data/lib/cinch/irc.rb +525 -110
  45. data/lib/cinch/isupport.rb +11 -9
  46. data/lib/cinch/logger.rb +168 -0
  47. data/lib/cinch/logger/formatted_logger.rb +72 -55
  48. data/lib/cinch/logger/zcbot_logger.rb +9 -24
  49. data/lib/cinch/logger_list.rb +62 -0
  50. data/lib/cinch/mask.rb +19 -10
  51. data/lib/cinch/message.rb +94 -28
  52. data/lib/cinch/message_queue.rb +70 -28
  53. data/lib/cinch/mode_parser.rb +6 -1
  54. data/lib/cinch/network.rb +104 -0
  55. data/lib/cinch/{rubyext/queue.rb → open_ended_queue.rb} +8 -1
  56. data/lib/cinch/pattern.rb +24 -4
  57. data/lib/cinch/plugin.rb +352 -177
  58. data/lib/cinch/plugin_list.rb +35 -0
  59. data/lib/cinch/rubyext/float.rb +3 -0
  60. data/lib/cinch/rubyext/module.rb +7 -0
  61. data/lib/cinch/rubyext/string.rb +9 -0
  62. data/lib/cinch/sasl.rb +34 -0
  63. data/lib/cinch/sasl/dh_blowfish.rb +71 -0
  64. data/lib/cinch/sasl/diffie_hellman.rb +47 -0
  65. data/lib/cinch/sasl/mechanism.rb +6 -0
  66. data/lib/cinch/sasl/plain.rb +26 -0
  67. data/lib/cinch/storage.rb +62 -0
  68. data/lib/cinch/storage/null.rb +12 -0
  69. data/lib/cinch/storage/yaml.rb +96 -0
  70. data/lib/cinch/syncable.rb +13 -1
  71. data/lib/cinch/target.rb +144 -0
  72. data/lib/cinch/timer.rb +145 -0
  73. data/lib/cinch/user.rb +169 -225
  74. data/lib/cinch/{user_manager.rb → user_list.rb} +7 -2
  75. data/lib/cinch/utilities/deprecation.rb +12 -0
  76. data/lib/cinch/utilities/encoding.rb +54 -0
  77. data/lib/cinch/utilities/kernel.rb +13 -0
  78. data/lib/cinch/utilities/string.rb +13 -0
  79. data/lib/cinch/version.rb +4 -0
  80. metadata +88 -47
  81. data/lib/cinch/logger/logger.rb +0 -44
  82. data/lib/cinch/logger/null_logger.rb +0 -18
  83. data/lib/cinch/rubyext/infinity.rb +0 -1
@@ -0,0 +1,35 @@
1
+ module Cinch
2
+ # @since 2.0.0
3
+ class PluginList < Array
4
+ def initialize(bot)
5
+ @bot = bot
6
+ super()
7
+ end
8
+
9
+ # @param [Class<Plugin>] plugin
10
+ def register_plugin(plugin)
11
+ self << plugin.new(@bot)
12
+ end
13
+
14
+ # @param [Array<Class<Plugin>>] plugins
15
+ def register_plugins(plugins)
16
+ plugins.each { |plugin| register_plugin(plugin) }
17
+ end
18
+
19
+ # @since 2.0.0
20
+ def unregister_plugin(plugin)
21
+ plugin.unregister
22
+ delete(plugin)
23
+ end
24
+
25
+ # @since 2.0.0
26
+ def unregister_plugins(plugins)
27
+ plugins.each { |plugin| unregister_plugin(plugin) }
28
+ end
29
+
30
+ # @since 2.0.0
31
+ def unregister_all
32
+ unregister_plugins(self)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ unless Float.const_defined?(:INFINITY)
2
+ Float::INFINITY = 1.0/0.0
3
+ end
@@ -1,4 +1,8 @@
1
+ # Extensions to Ruby's Module class.
1
2
  class Module
3
+ # Like `attr_reader`, but for defining a synchronized attribute
4
+ # reader.
5
+ #
2
6
  # @api private
3
7
  def synced_attr_reader(attribute)
4
8
  define_method(attribute) do
@@ -10,6 +14,9 @@ class Module
10
14
  end
11
15
  end
12
16
 
17
+ # Like `attr_accessor`, but for defining a synchronized attribute
18
+ # accessor
19
+ #
13
20
  # @api private
14
21
  def synced_attr_accessor(attr)
15
22
  synced_attr_reader(attr)
@@ -1,4 +1,9 @@
1
+ # Extensions to Ruby's String class.
1
2
  class String
3
+ # Like `String#downcase`, but respecting different IRC casemaps.
4
+ #
5
+ # @param [Symbol<:rfc1459, :strict-rfc1459, :ascii>] mapping
6
+ # @return [String]
2
7
  def irc_downcase(mapping)
3
8
  case mapping
4
9
  when :rfc1459
@@ -11,6 +16,10 @@ class String
11
16
  end
12
17
  end
13
18
 
19
+ # Like `String#upcase`, but respecting different IRC casemaps.
20
+ #
21
+ # @param [Symbol<:rfc1459, :strict-rfc1459, :ascii>] mapping
22
+ # @return [String]
14
23
  def irc_upcase(mapping)
15
24
  case mapping
16
25
  when :ascii
@@ -0,0 +1,34 @@
1
+ require "cinch/sasl/diffie_hellman"
2
+ require "cinch/sasl/plain"
3
+ require "cinch/sasl/dh_blowfish"
4
+
5
+ module Cinch
6
+ # SASL is a modern way of authentication in IRC, solving problems
7
+ # such as transmitting passwords as plain text (see the DH-BLOWFISH
8
+ # mechanism) and fully identifying before joining any channels.
9
+ #
10
+ # Cinch automatically detects which mechanisms are supported by the
11
+ # IRC network and uses the best available one.
12
+ #
13
+ # # Supported Mechanisms
14
+ #
15
+ # - {SASL::DH_Blowfish DH-BLOWFISH}
16
+ # - {SASL::Plain PLAIN}
17
+ #
18
+ # # Configuration
19
+ # In order to use SASL one has to set the username and password
20
+ # options as follows:
21
+ #
22
+ # configure do |c|
23
+ # c.sasl.username = "foo"
24
+ # c.sasl.password = "bar"
25
+ # end
26
+ #
27
+ # @note All classes and modules in this module are for internal use by
28
+ # Cinch only.
29
+ #
30
+ # @api private
31
+ # @since 2.0.0
32
+ module SASL
33
+ end
34
+ end
@@ -0,0 +1,71 @@
1
+ require "openssl"
2
+ require "base64"
3
+ require "cinch/sasl/mechanism"
4
+
5
+ module Cinch
6
+ module SASL
7
+ # DH-BLOWFISH is a combination of Diffie-Hellman key exchange and
8
+ # the Blowfish encryption algorithm. Due to its nature it is more
9
+ # secure than transmitting the password unencrypted and can be
10
+ # used on potentially insecure networks.
11
+ class DH_Blowfish < Mechanism
12
+ class << self
13
+ # @return [String]
14
+ def mechanism_name
15
+ "DH-BLOWFISH"
16
+ end
17
+
18
+ # @return [Array<Number, Number, Number>] p, g and y for DH
19
+ def unpack_payload(payload)
20
+ pgy = []
21
+ payload = payload.dup
22
+
23
+ 3.times do
24
+ size = payload.unpack("n").first
25
+ payload.slice!(0, 2)
26
+ pgy << payload.unpack("a#{size}").first
27
+ payload.slice!(0, size)
28
+ end
29
+
30
+ pgy.map {|i| OpenSSL::BN.new(i, 2).to_i}
31
+ end
32
+
33
+ # @param [String] user
34
+ # @param [String] password
35
+ # @param [String] payload
36
+ # @return [String]
37
+ def generate(user, password, payload)
38
+ data = Base64.decode64(payload).force_encoding("ASCII-8BIT")
39
+
40
+ p, g, y = unpack_payload(data)
41
+
42
+ dh = DiffieHellman.new(p, g, 23)
43
+ pub_key = dh.generate
44
+ secret = OpenSSL::BN.new(dh.secret(y).to_s).to_s(2)
45
+ public = OpenSSL::BN.new(pub_key.to_s).to_s(2)
46
+
47
+ # Pad password so its length is a multiple of the cipher block size
48
+ password << "\0"
49
+ password << "." * (8 - (password.size % 8))
50
+
51
+ crypted = ""
52
+ cipher = OpenSSL::Cipher.new("BF")
53
+
54
+ while password.size > 0 do
55
+ # We have to reinitialize this every time because for OpenSSL, "BF" is synonynmous with "BF-CBC", and we do not want CBC
56
+ cipher.reset
57
+ cipher.key_len = 32 # OpenSSL's default of 16 doesn't work
58
+ cipher.encrypt
59
+ cipher.key = secret
60
+
61
+ clear = password.slice!(0, 8)
62
+ crypted << cipher.update(clear) # we do not want the content of cipher.final
63
+ end
64
+
65
+ answer = [public.bytesize, public, user, crypted].pack("na*Z*a*")
66
+ Base64.strict_encode64(answer)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,47 @@
1
+ module Cinch
2
+ module SASL
3
+ class DiffieHellman
4
+ attr_reader :p, :g, :q, :x, :e
5
+
6
+ def initialize(p, g, q)
7
+ @p = p
8
+ @g = g
9
+ @q = q
10
+ end
11
+
12
+ def generate(tries = 16)
13
+ tries.times do
14
+ @x = rand(@q)
15
+ @e = mod_exp(@g, @x, @p)
16
+ return @e if valid?
17
+ end
18
+ raise ArgumentError, "can't generate valid e"
19
+ end
20
+
21
+ # compute the shared secret, given the public key
22
+ def secret(f)
23
+ mod_exp(f, @x, @p)
24
+ end
25
+
26
+ private
27
+ # validate a public key
28
+ def valid?
29
+ @e && @e.between?(2, @p - 2) && bits_set(@e) > 1
30
+ end
31
+
32
+ def bits_set(e)
33
+ ("%b" % e).count('1')
34
+ end
35
+
36
+ def mod_exp(b, e, m)
37
+ result = 1
38
+ while e > 0
39
+ result = (result * b) % m if e[0] == 1
40
+ e = e >> 1
41
+ b = (b * b) % m
42
+ end
43
+ return result
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+ module Cinch
2
+ module SASL
3
+ class Mechanism
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,26 @@
1
+ require "base64"
2
+ require "cinch/sasl/mechanism"
3
+
4
+ module Cinch
5
+ module SASL
6
+ # The simplest mechanisms simply transmits the username and
7
+ # password without adding any encryption or hashing. As such it's more
8
+ # insecure than DH-BLOWFISH and should only be used in combination with
9
+ # SSL.
10
+ class Plain < Mechanism
11
+ class << self
12
+ # @return [String]
13
+ def mechanism_name
14
+ "PLAIN"
15
+ end
16
+
17
+ # @param [String] user
18
+ # @param [String] password
19
+ # @return [String]
20
+ def generate(user, password, _ = nil)
21
+ Base64.strict_encode64([user, user, password].join("\0"))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,62 @@
1
+ module Cinch
2
+ # @since 2.0.0
3
+ class Storage
4
+ include Enumerable
5
+
6
+ # @param [Hash] options
7
+ # @param [Plugin] plugin
8
+ def initialize(options, plugin)
9
+ end
10
+
11
+ # @param [Object] key
12
+ # @return [Object, nil]
13
+ def [](key)
14
+ end
15
+
16
+ # @param [Object] key
17
+ # @param [Object] value
18
+ # @return [value]
19
+ def []=(key, value)
20
+ end
21
+
22
+ # @return [self]
23
+ def each
24
+ self
25
+ end
26
+
27
+ # @return [self]
28
+ def each_key
29
+ self
30
+ end
31
+
32
+ # @return [self]
33
+ def each_value
34
+ self
35
+ end
36
+
37
+ # @param [Object] key
38
+ # @return [Boolean]
39
+ def has_key?(key)
40
+ false
41
+ end
42
+ alias_method :include?, :has_key?
43
+ alias_method :key?, :has_key?
44
+ alias_method :member?, :has_key?
45
+
46
+ # @param [Object] key
47
+ # @return [Object, nil] The deleted object
48
+ def delete(key)
49
+ end
50
+
51
+ # @return [self]
52
+ def delete_if
53
+ self
54
+ end
55
+
56
+ def save
57
+ end
58
+
59
+ def unload
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,12 @@
1
+ require "cinch/storage"
2
+
3
+ module Cinch
4
+ class Storage
5
+ # The Null storage is the default storage (used if the user didn't
6
+ # supply a different storage) and will simply ignore all requests.
7
+ # This way, plugins will continue to work if programmed properly,
8
+ # but no data will be preserved.
9
+ class Null < Storage
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,96 @@
1
+ require "cinch/storage"
2
+ require "yaml"
3
+
4
+ module Cinch
5
+ class Storage
6
+ # A basic storage backed by YAML, using one file per plugin.
7
+ class YAML < Storage
8
+ # (see Storage#initialize)
9
+ def initialize(options, plugin)
10
+ # We are a basic example, so we load everything into memory. yey.
11
+ @file = options.basedir + plugin.class.plugin_name + ".yaml"
12
+ if File.exist?(@file)
13
+ @yaml = ::YAML.load_file(@file) || {}
14
+ else
15
+ @yaml = {}
16
+ end
17
+ @options = options
18
+
19
+ @mutex = Mutex.new
20
+ end
21
+
22
+ # (see Storage#[])
23
+ def [](key)
24
+ @yaml[key]
25
+ end
26
+
27
+ # (see Strage#[]=)
28
+ def []=(key, value)
29
+ @yaml[key] = value
30
+ end
31
+
32
+ # (see Storage#has_key?)
33
+ def has_key?(key)
34
+ @yaml.has_key?(key)
35
+ end
36
+ alias_method :include?, :has_key?
37
+ alias_method :key?, :has_key?
38
+ alias_method :member?, :has_key?
39
+
40
+ # (see Storage#each)
41
+ def each
42
+ @yaml.each {|e| yield(e)}
43
+
44
+ self
45
+ end
46
+
47
+ # (see Storage#each_key)
48
+ def each_key
49
+ @yaml.each_key {|e| yield(e)}
50
+
51
+ self
52
+ end
53
+
54
+ # (see Storage#each_value)
55
+ def each_value
56
+ @yaml.each_value {|e| yield(e)}
57
+
58
+ self
59
+ end
60
+
61
+ # (see Storage#delete)
62
+ def delete(key)
63
+ obj = @yaml.delete(key)
64
+
65
+ obj
66
+ end
67
+
68
+ # (see Storage#delete_if)
69
+ def delete_if
70
+ delete_keys = []
71
+ each do |key, value|
72
+ delete_keys << key if yield(key, value)
73
+ end
74
+
75
+ delete_keys.each do |key|
76
+ delete(key)
77
+ end
78
+
79
+ self
80
+ end
81
+
82
+ # (see Storage#save)
83
+ def save
84
+ @mutex.synchronize do
85
+ File.open(@file, "w") do |f|
86
+ f.write @yaml.to_yaml
87
+ end
88
+ end
89
+ end
90
+
91
+ # (see Storage#unload)
92
+ def unload
93
+ end
94
+ end
95
+ end
96
+ end
@@ -1,4 +1,5 @@
1
1
  module Cinch
2
+ # Provide blocking access to user/channel information.
2
3
  module Syncable
3
4
  # Blocks until the object is synced.
4
5
  #
@@ -6,8 +7,16 @@ module Cinch
6
7
  # @api private
7
8
  def wait_until_synced(attr)
8
9
  attr = attr.to_sym
10
+ waited = 0
9
11
  while true
10
- return if @synced_attributes.include?(attr)
12
+ return if synced?(attr)
13
+ waited += 1
14
+
15
+ if waited % 100 == 0
16
+ # TODO improve this message
17
+ bot.loggers.warn "A synced attribute ('%s') has not been available for %d seconds, still waiting" % [attr, waited / 10]
18
+ bot.loggers.warn caller.map {|s| " #{s}"}
19
+ end
11
20
  sleep 0.1
12
21
  end
13
22
  end
@@ -42,6 +51,9 @@ module Cinch
42
51
  @synced_attributes.clear
43
52
  end
44
53
 
54
+ # @param [Symbol] attribute
55
+ # @param [Boolean] data
56
+ # @param [Boolean] unsync
45
57
  # @api private
46
58
  def attr(attribute, data = false, unsync = false)
47
59
  unless unsync