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.
- data/LICENSE +1 -0
- data/README.md +3 -3
- data/docs/bot_options.md +435 -0
- data/docs/changes.md +440 -0
- data/docs/common_mistakes.md +35 -0
- data/docs/common_tasks.md +47 -0
- data/docs/encodings.md +67 -0
- data/docs/events.md +272 -0
- data/docs/logging.md +5 -0
- data/docs/migrating.md +267 -0
- data/docs/readme.md +18 -0
- data/examples/plugins/custom_prefix.rb +1 -1
- data/examples/plugins/dice_roll.rb +38 -0
- data/examples/plugins/lambdas.rb +1 -1
- data/examples/plugins/memo.rb +16 -10
- data/examples/plugins/url_shorten.rb +1 -0
- data/lib/cinch.rb +5 -60
- data/lib/cinch/ban.rb +13 -7
- data/lib/cinch/bot.rb +228 -403
- data/lib/cinch/{cache_manager.rb → cached_list.rb} +5 -1
- data/lib/cinch/callback.rb +3 -0
- data/lib/cinch/channel.rb +119 -195
- data/lib/cinch/{channel_manager.rb → channel_list.rb} +6 -3
- data/lib/cinch/configuration.rb +73 -0
- data/lib/cinch/configuration/bot.rb +47 -0
- data/lib/cinch/configuration/dcc.rb +16 -0
- data/lib/cinch/configuration/plugins.rb +41 -0
- data/lib/cinch/configuration/sasl.rb +17 -0
- data/lib/cinch/configuration/ssl.rb +19 -0
- data/lib/cinch/configuration/storage.rb +37 -0
- data/lib/cinch/configuration/timeouts.rb +14 -0
- data/lib/cinch/constants.rb +531 -369
- data/lib/cinch/dcc.rb +12 -0
- data/lib/cinch/dcc/dccable_object.rb +37 -0
- data/lib/cinch/dcc/incoming.rb +1 -0
- data/lib/cinch/dcc/incoming/send.rb +131 -0
- data/lib/cinch/dcc/outgoing.rb +1 -0
- data/lib/cinch/dcc/outgoing/send.rb +115 -0
- data/lib/cinch/exceptions.rb +8 -1
- data/lib/cinch/formatting.rb +106 -0
- data/lib/cinch/handler.rb +104 -0
- data/lib/cinch/handler_list.rb +86 -0
- data/lib/cinch/helpers.rb +167 -10
- data/lib/cinch/irc.rb +525 -110
- data/lib/cinch/isupport.rb +11 -9
- data/lib/cinch/logger.rb +168 -0
- data/lib/cinch/logger/formatted_logger.rb +72 -55
- data/lib/cinch/logger/zcbot_logger.rb +9 -24
- data/lib/cinch/logger_list.rb +62 -0
- data/lib/cinch/mask.rb +19 -10
- data/lib/cinch/message.rb +94 -28
- data/lib/cinch/message_queue.rb +70 -28
- data/lib/cinch/mode_parser.rb +6 -1
- data/lib/cinch/network.rb +104 -0
- data/lib/cinch/{rubyext/queue.rb → open_ended_queue.rb} +8 -1
- data/lib/cinch/pattern.rb +24 -4
- data/lib/cinch/plugin.rb +352 -177
- data/lib/cinch/plugin_list.rb +35 -0
- data/lib/cinch/rubyext/float.rb +3 -0
- data/lib/cinch/rubyext/module.rb +7 -0
- data/lib/cinch/rubyext/string.rb +9 -0
- data/lib/cinch/sasl.rb +34 -0
- data/lib/cinch/sasl/dh_blowfish.rb +71 -0
- data/lib/cinch/sasl/diffie_hellman.rb +47 -0
- data/lib/cinch/sasl/mechanism.rb +6 -0
- data/lib/cinch/sasl/plain.rb +26 -0
- data/lib/cinch/storage.rb +62 -0
- data/lib/cinch/storage/null.rb +12 -0
- data/lib/cinch/storage/yaml.rb +96 -0
- data/lib/cinch/syncable.rb +13 -1
- data/lib/cinch/target.rb +144 -0
- data/lib/cinch/timer.rb +145 -0
- data/lib/cinch/user.rb +169 -225
- data/lib/cinch/{user_manager.rb → user_list.rb} +7 -2
- data/lib/cinch/utilities/deprecation.rb +12 -0
- data/lib/cinch/utilities/encoding.rb +54 -0
- data/lib/cinch/utilities/kernel.rb +13 -0
- data/lib/cinch/utilities/string.rb +13 -0
- data/lib/cinch/version.rb +4 -0
- metadata +88 -47
- data/lib/cinch/logger/logger.rb +0 -44
- data/lib/cinch/logger/null_logger.rb +0 -18
- 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
|
data/lib/cinch/rubyext/module.rb
CHANGED
@@ -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)
|
data/lib/cinch/rubyext/string.rb
CHANGED
@@ -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
|
data/lib/cinch/sasl.rb
ADDED
@@ -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,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
|
data/lib/cinch/syncable.rb
CHANGED
@@ -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
|
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
|