grinch 1.0.0
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.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/LICENSE +22 -0
- data/README.md +180 -0
- data/docs/bot_options.md +454 -0
- data/docs/changes.md +541 -0
- data/docs/common_mistakes.md +60 -0
- data/docs/common_tasks.md +57 -0
- data/docs/encodings.md +69 -0
- data/docs/events.md +273 -0
- data/docs/getting_started.md +184 -0
- data/docs/logging.md +90 -0
- data/docs/migrating.md +267 -0
- data/docs/plugins.md +4 -0
- data/docs/readme.md +20 -0
- data/examples/basic/autovoice.rb +32 -0
- data/examples/basic/google.rb +35 -0
- data/examples/basic/hello.rb +15 -0
- data/examples/basic/join_part.rb +34 -0
- data/examples/basic/memo.rb +39 -0
- data/examples/basic/msg.rb +16 -0
- data/examples/basic/seen.rb +36 -0
- data/examples/basic/urban_dict.rb +35 -0
- data/examples/basic/url_shorten.rb +35 -0
- data/examples/plugins/autovoice.rb +37 -0
- data/examples/plugins/custom_prefix.rb +23 -0
- data/examples/plugins/dice_roll.rb +38 -0
- data/examples/plugins/google.rb +36 -0
- data/examples/plugins/hello.rb +22 -0
- data/examples/plugins/hooks.rb +36 -0
- data/examples/plugins/join_part.rb +42 -0
- data/examples/plugins/lambdas.rb +35 -0
- data/examples/plugins/last_nick.rb +24 -0
- data/examples/plugins/msg.rb +22 -0
- data/examples/plugins/multiple_matches.rb +32 -0
- data/examples/plugins/own_events.rb +37 -0
- data/examples/plugins/seen.rb +45 -0
- data/examples/plugins/timer.rb +22 -0
- data/examples/plugins/url_shorten.rb +33 -0
- data/lib/cinch.rb +5 -0
- data/lib/cinch/ban.rb +50 -0
- data/lib/cinch/bot.rb +479 -0
- data/lib/cinch/cached_list.rb +19 -0
- data/lib/cinch/callback.rb +20 -0
- data/lib/cinch/channel.rb +463 -0
- data/lib/cinch/channel_list.rb +29 -0
- data/lib/cinch/configuration.rb +73 -0
- data/lib/cinch/configuration/bot.rb +48 -0
- data/lib/cinch/configuration/dcc.rb +16 -0
- data/lib/cinch/configuration/plugins.rb +41 -0
- data/lib/cinch/configuration/sasl.rb +19 -0
- data/lib/cinch/configuration/ssl.rb +19 -0
- data/lib/cinch/configuration/timeouts.rb +14 -0
- data/lib/cinch/constants.rb +533 -0
- 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 +147 -0
- data/lib/cinch/dcc/outgoing.rb +1 -0
- data/lib/cinch/dcc/outgoing/send.rb +122 -0
- data/lib/cinch/exceptions.rb +46 -0
- data/lib/cinch/formatting.rb +125 -0
- data/lib/cinch/handler.rb +118 -0
- data/lib/cinch/handler_list.rb +90 -0
- data/lib/cinch/helpers.rb +231 -0
- data/lib/cinch/irc.rb +924 -0
- data/lib/cinch/isupport.rb +98 -0
- data/lib/cinch/log_filter.rb +21 -0
- data/lib/cinch/logger.rb +168 -0
- data/lib/cinch/logger/formatted_logger.rb +97 -0
- data/lib/cinch/logger/zcbot_logger.rb +22 -0
- data/lib/cinch/logger_list.rb +85 -0
- data/lib/cinch/mask.rb +69 -0
- data/lib/cinch/message.rb +392 -0
- data/lib/cinch/message_queue.rb +107 -0
- data/lib/cinch/mode_parser.rb +76 -0
- data/lib/cinch/network.rb +104 -0
- data/lib/cinch/open_ended_queue.rb +26 -0
- data/lib/cinch/pattern.rb +65 -0
- data/lib/cinch/plugin.rb +515 -0
- data/lib/cinch/plugin_list.rb +38 -0
- data/lib/cinch/rubyext/float.rb +3 -0
- data/lib/cinch/rubyext/module.rb +26 -0
- data/lib/cinch/rubyext/string.rb +33 -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/syncable.rb +83 -0
- data/lib/cinch/target.rb +199 -0
- data/lib/cinch/timer.rb +145 -0
- data/lib/cinch/user.rb +488 -0
- data/lib/cinch/user_list.rb +87 -0
- data/lib/cinch/utilities/deprecation.rb +16 -0
- data/lib/cinch/utilities/encoding.rb +37 -0
- data/lib/cinch/utilities/kernel.rb +13 -0
- data/lib/cinch/version.rb +4 -0
- metadata +140 -0
@@ -0,0 +1,38 @@
|
|
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
|
+
if plugins == self
|
28
|
+
plugins = self.dup
|
29
|
+
end
|
30
|
+
plugins.each { |plugin| unregister_plugin(plugin) }
|
31
|
+
end
|
32
|
+
|
33
|
+
# @since 2.0.0
|
34
|
+
def unregister_all
|
35
|
+
unregister_plugins(self)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Extensions to Ruby's Module class.
|
2
|
+
class Module
|
3
|
+
# Like `attr_reader`, but for defining a synchronized attribute
|
4
|
+
# reader.
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
def synced_attr_reader(attribute)
|
8
|
+
undef_method(attribute)
|
9
|
+
define_method(attribute) do
|
10
|
+
attr(attribute)
|
11
|
+
end
|
12
|
+
|
13
|
+
define_method("#{attribute}_unsynced") do
|
14
|
+
attr(attribute, false, true)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Like `attr_accessor`, but for defining a synchronized attribute
|
19
|
+
# accessor
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
def synced_attr_accessor(attr)
|
23
|
+
synced_attr_reader(attr)
|
24
|
+
attr_accessor(attr)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Extensions to Ruby's String class.
|
2
|
+
class String
|
3
|
+
# Like `String#downcase`, but respecting different IRC casemaps.
|
4
|
+
#
|
5
|
+
# @param [:rfc1459, :"strict-rfc1459", :ascii] mapping
|
6
|
+
# @return [String]
|
7
|
+
def irc_downcase(mapping)
|
8
|
+
case mapping
|
9
|
+
when :rfc1459
|
10
|
+
self.tr("A-Z[]\\\\^", "a-z{}|~")
|
11
|
+
when :"strict-rfc1459"
|
12
|
+
self.tr("A-Z[]\\\\", "a-z{}|")
|
13
|
+
else
|
14
|
+
# when :ascii or unknown/nil
|
15
|
+
self.tr("A-Z", "a-z")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Like `String#upcase`, but respecting different IRC casemaps.
|
20
|
+
#
|
21
|
+
# @param [:rfc1459, :"strict-rfc1459", :ascii] mapping
|
22
|
+
# @return [String]
|
23
|
+
def irc_upcase(mapping)
|
24
|
+
case mapping
|
25
|
+
when :ascii
|
26
|
+
self.tr("a-z", "A-Z")
|
27
|
+
when :rfc1459
|
28
|
+
self.tr("a-z{}|~", "A-Z[]\\\\^")
|
29
|
+
when :"strict-rfc1459"
|
30
|
+
self.tr("a-z{}|", "A-Z[]\\\\")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
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(Numeric, Numeric, Numeric)] 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
|
+
# duplicate the passed strings because we are modifying them
|
39
|
+
# later and they might come from the configuration store or
|
40
|
+
# similar
|
41
|
+
user = user.dup
|
42
|
+
password = password.dup
|
43
|
+
|
44
|
+
data = Base64.decode64(payload).force_encoding("ASCII-8BIT")
|
45
|
+
|
46
|
+
p, g, y = unpack_payload(data)
|
47
|
+
|
48
|
+
dh = DiffieHellman.new(p, g, 23)
|
49
|
+
pub_key = dh.generate
|
50
|
+
secret = OpenSSL::BN.new(dh.secret(y).to_s).to_s(2)
|
51
|
+
public = OpenSSL::BN.new(pub_key.to_s).to_s(2)
|
52
|
+
|
53
|
+
# Pad password so its length is a multiple of the cipher block size
|
54
|
+
password << "\0"
|
55
|
+
password << "." * (8 - (password.size % 8))
|
56
|
+
|
57
|
+
crypted = ""
|
58
|
+
cipher = OpenSSL::Cipher.new("BF-ECB")
|
59
|
+
cipher.key_len = 32 # OpenSSL's default of 16 doesn't work
|
60
|
+
cipher.encrypt
|
61
|
+
cipher.key = secret
|
62
|
+
|
63
|
+
crypted = cipher.update(password) # we do not want the content of cipher.final
|
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,83 @@
|
|
1
|
+
module Cinch
|
2
|
+
# Provide blocking access to user/channel information.
|
3
|
+
module Syncable
|
4
|
+
# Blocks until the object is synced.
|
5
|
+
#
|
6
|
+
# @return [void]
|
7
|
+
# @api private
|
8
|
+
def wait_until_synced(attr)
|
9
|
+
attr = attr.to_sym
|
10
|
+
waited = 0
|
11
|
+
while true
|
12
|
+
return if attribute_synced?(attr)
|
13
|
+
waited += 1
|
14
|
+
|
15
|
+
if waited % 100 == 0
|
16
|
+
bot.loggers.warn "A synced attribute ('%s' for %s) has not been available for %d seconds, still waiting" % [attr, self.inspect, waited / 10]
|
17
|
+
bot.loggers.warn caller.map {|s| " #{s}"}
|
18
|
+
|
19
|
+
if waited / 10 >= 30
|
20
|
+
bot.loggers.warn " Giving up..."
|
21
|
+
raise Exceptions::SyncedAttributeNotAvailable, "'%s' for %s" % [attr, self.inspect]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
sleep 0.1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @api private
|
29
|
+
# @return [void]
|
30
|
+
def sync(attribute, value, data = false)
|
31
|
+
if data
|
32
|
+
@data[attribute] = value
|
33
|
+
else
|
34
|
+
instance_variable_set("@#{attribute}", value)
|
35
|
+
end
|
36
|
+
@synced_attributes << attribute
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Boolean]
|
40
|
+
# @api private
|
41
|
+
def attribute_synced?(attribute)
|
42
|
+
@synced_attributes.include?(attribute)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [void]
|
46
|
+
# @api private
|
47
|
+
def unsync(attribute)
|
48
|
+
@synced_attributes.delete(attribute)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [void]
|
52
|
+
# @api private
|
53
|
+
# @since 1.0.1
|
54
|
+
def unsync_all
|
55
|
+
@synced_attributes.clear
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [Symbol] attribute
|
59
|
+
# @param [Boolean] data
|
60
|
+
# @param [Boolean] unsync
|
61
|
+
# @api private
|
62
|
+
def attr(attribute, data = false, unsync = false)
|
63
|
+
unless unsync
|
64
|
+
if @when_requesting_synced_attribute
|
65
|
+
@when_requesting_synced_attribute.call(attribute)
|
66
|
+
end
|
67
|
+
wait_until_synced(attribute)
|
68
|
+
end
|
69
|
+
|
70
|
+
if data
|
71
|
+
return @data[attribute]
|
72
|
+
else
|
73
|
+
return instance_variable_get("@#{attribute}")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @api private
|
78
|
+
# @return [void]
|
79
|
+
def mark_as_synced(attribute)
|
80
|
+
@synced_attributes << attribute
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/cinch/target.rb
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
module Cinch
|
2
|
+
# @since 2.0.0
|
3
|
+
class Target
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
# @return [String]
|
7
|
+
attr_reader :name
|
8
|
+
# @return [Bot]
|
9
|
+
attr_reader :bot
|
10
|
+
def initialize(name, bot)
|
11
|
+
@name = name
|
12
|
+
@bot = bot
|
13
|
+
end
|
14
|
+
|
15
|
+
# Sends a NOTICE to the target.
|
16
|
+
#
|
17
|
+
# @param [#to_s] text the message to send
|
18
|
+
# @return [void]
|
19
|
+
# @see #safe_notice
|
20
|
+
def notice(text)
|
21
|
+
send(text, true)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Sends a PRIVMSG to the target.
|
25
|
+
#
|
26
|
+
# @param [#to_s] text the message to send
|
27
|
+
# @param [Boolean] notice Use NOTICE instead of PRIVMSG?
|
28
|
+
# @return [void]
|
29
|
+
# @see #safe_msg
|
30
|
+
# @note The aliases `msg` and `privmsg` are deprecated and will be
|
31
|
+
# removed in a future version.
|
32
|
+
def send(text, notice = false)
|
33
|
+
# TODO deprecate `notice` argument, put splitting into own
|
34
|
+
# method
|
35
|
+
text = text.to_s
|
36
|
+
split_start = @bot.config.message_split_start || ""
|
37
|
+
split_end = @bot.config.message_split_end || ""
|
38
|
+
command = notice ? "NOTICE" : "PRIVMSG"
|
39
|
+
prefix = ":#{@bot.mask} #{command} #{@name} :"
|
40
|
+
|
41
|
+
text.lines.map(&:chomp).each do |line|
|
42
|
+
splitted = split_message(line, prefix, split_start, split_end)
|
43
|
+
|
44
|
+
splitted[0, (@bot.config.max_messages || splitted.size)].each do |string|
|
45
|
+
@bot.irc.send("#{command} #@name :#{string}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
alias_method :msg, :send # deprecated
|
50
|
+
alias_method :privmsg, :send # deprecated
|
51
|
+
undef_method(:msg) # yardoc hack
|
52
|
+
undef_method(:privmsg) # yardoc hack
|
53
|
+
|
54
|
+
# @deprecated
|
55
|
+
def msg(*args)
|
56
|
+
Cinch::Utilities::Deprecation.print_deprecation("2.2.0", "Target#msg", "Target#send")
|
57
|
+
send(*args)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @deprecated
|
61
|
+
def privmsg(*args)
|
62
|
+
Cinch::Utilities::Deprecation.print_deprecation("2.2.0", "Target#privmsg", "Target#send")
|
63
|
+
send(*args)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Like {#send}, but remove any non-printable characters from
|
67
|
+
# `text`. The purpose of this method is to send text of untrusted
|
68
|
+
# sources, like other users or feeds.
|
69
|
+
#
|
70
|
+
# Note: this will **break** any mIRC color codes embedded in the
|
71
|
+
# string. For more fine-grained control, use
|
72
|
+
# {Helpers#Sanitize} and
|
73
|
+
# {Formatting.unformat} directly.
|
74
|
+
#
|
75
|
+
# @return (see #send)
|
76
|
+
# @param (see #send)
|
77
|
+
# @see #send
|
78
|
+
def safe_send(text, notice = false)
|
79
|
+
send(Cinch::Helpers.sanitize(text), notice)
|
80
|
+
end
|
81
|
+
alias_method :safe_msg, :safe_send # deprecated
|
82
|
+
alias_method :safe_privmsg, :safe_msg # deprecated
|
83
|
+
undef_method(:safe_msg) # yardoc hack
|
84
|
+
undef_method(:safe_privmsg) # yardoc hack
|
85
|
+
|
86
|
+
# @deprecated
|
87
|
+
def safe_msg(*args)
|
88
|
+
Cinch::Utilities::Deprecation.print_deprecation("2.2.0", "Target#safe_msg", "Target#safe_send")
|
89
|
+
send(*args)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @deprecated
|
93
|
+
def safe_privmsg(*args)
|
94
|
+
Cinch::Utilities::Deprecation.print_deprecation("2.2.0", "Target#safe_privmsg", "Target#safe_send")
|
95
|
+
send(*args)
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# Like {#safe_msg} but for notices.
|
100
|
+
#
|
101
|
+
# @return (see #safe_msg)
|
102
|
+
# @param (see #safe_msg)
|
103
|
+
# @see #safe_notice
|
104
|
+
# @see #notice
|
105
|
+
def safe_notice(text)
|
106
|
+
safe_send(text, true)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Invoke an action (/me) in/to the target.
|
110
|
+
#
|
111
|
+
# @param [#to_s] text the message to send
|
112
|
+
# @return [void]
|
113
|
+
# @see #safe_action
|
114
|
+
def action(text)
|
115
|
+
line = text.to_s.each_line.first.chomp
|
116
|
+
@bot.irc.send("PRIVMSG #@name :\001ACTION #{line}\001")
|
117
|
+
end
|
118
|
+
|
119
|
+
# Like {#action}, but remove any non-printable characters from
|
120
|
+
# `text`. The purpose of this method is to send text from
|
121
|
+
# untrusted sources, like other users or feeds.
|
122
|
+
#
|
123
|
+
# Note: this will **break** any mIRC color codes embedded in the
|
124
|
+
# string. For more fine-grained control, use
|
125
|
+
# {Helpers#Sanitize} and
|
126
|
+
# {Formatting.unformat} directly.
|
127
|
+
#
|
128
|
+
# @param (see #action)
|
129
|
+
# @return (see #action)
|
130
|
+
# @see #action
|
131
|
+
def safe_action(text)
|
132
|
+
action(Cinch::Helpers.sanitize(text))
|
133
|
+
end
|
134
|
+
|
135
|
+
# Send a CTCP to the target.
|
136
|
+
#
|
137
|
+
# @param [#to_s] message the ctcp message
|
138
|
+
# @return [void]
|
139
|
+
def ctcp(message)
|
140
|
+
send "\001#{message}\001"
|
141
|
+
end
|
142
|
+
|
143
|
+
def concretize
|
144
|
+
if @bot.isupport["CHANTYPES"].include?(@name[0])
|
145
|
+
@bot.channel_list.find_ensured(@name)
|
146
|
+
else
|
147
|
+
@bot.user_list.find_ensured(@name)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# @return [Boolean]
|
152
|
+
def eql?(other)
|
153
|
+
self == other
|
154
|
+
end
|
155
|
+
|
156
|
+
# @param [Target, String] other
|
157
|
+
# @return [-1, 0, 1, nil]
|
158
|
+
def <=>(other)
|
159
|
+
casemapping = @bot.irc.isupport["CASEMAPPING"]
|
160
|
+
left = @name.irc_downcase(casemapping)
|
161
|
+
|
162
|
+
if other.is_a?(Target)
|
163
|
+
left <=> other.name.irc_downcase(casemapping)
|
164
|
+
elsif other.is_a?(String)
|
165
|
+
left <=> other.irc_downcase(casemapping)
|
166
|
+
else
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
def split_message(msg, prefix, split_start, split_end)
|
173
|
+
max_bytesize = 510 - prefix.bytesize
|
174
|
+
max_bytesize_without_end = max_bytesize - split_end.bytesize
|
175
|
+
|
176
|
+
if msg.bytesize <= max_bytesize
|
177
|
+
return [msg]
|
178
|
+
end
|
179
|
+
|
180
|
+
splitted = []
|
181
|
+
while msg.bytesize > max_bytesize_without_end
|
182
|
+
acc = 0
|
183
|
+
acc_rune_sizes = msg.each_char.map {|ch|
|
184
|
+
acc += ch.bytesize
|
185
|
+
}
|
186
|
+
|
187
|
+
max_rune = acc_rune_sizes.rindex {|bs| bs <= max_bytesize_without_end} || 0
|
188
|
+
r = [msg.rindex(/\s/, max_rune) || (max_rune + 1), 1].max
|
189
|
+
|
190
|
+
splitted << (msg[0...r] + split_end)
|
191
|
+
msg = split_start.tr(" ", "\cz") + msg[r..-1].lstrip
|
192
|
+
end
|
193
|
+
splitted << msg
|
194
|
+
|
195
|
+
# clean string from any substitute characters
|
196
|
+
splitted.map {|string| string.tr("\cz", " ")}
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|