cinch 1.1.3 → 2.0.0.pre.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.
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