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