grinch 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/LICENSE +22 -0
  4. data/README.md +180 -0
  5. data/docs/bot_options.md +454 -0
  6. data/docs/changes.md +541 -0
  7. data/docs/common_mistakes.md +60 -0
  8. data/docs/common_tasks.md +57 -0
  9. data/docs/encodings.md +69 -0
  10. data/docs/events.md +273 -0
  11. data/docs/getting_started.md +184 -0
  12. data/docs/logging.md +90 -0
  13. data/docs/migrating.md +267 -0
  14. data/docs/plugins.md +4 -0
  15. data/docs/readme.md +20 -0
  16. data/examples/basic/autovoice.rb +32 -0
  17. data/examples/basic/google.rb +35 -0
  18. data/examples/basic/hello.rb +15 -0
  19. data/examples/basic/join_part.rb +34 -0
  20. data/examples/basic/memo.rb +39 -0
  21. data/examples/basic/msg.rb +16 -0
  22. data/examples/basic/seen.rb +36 -0
  23. data/examples/basic/urban_dict.rb +35 -0
  24. data/examples/basic/url_shorten.rb +35 -0
  25. data/examples/plugins/autovoice.rb +37 -0
  26. data/examples/plugins/custom_prefix.rb +23 -0
  27. data/examples/plugins/dice_roll.rb +38 -0
  28. data/examples/plugins/google.rb +36 -0
  29. data/examples/plugins/hello.rb +22 -0
  30. data/examples/plugins/hooks.rb +36 -0
  31. data/examples/plugins/join_part.rb +42 -0
  32. data/examples/plugins/lambdas.rb +35 -0
  33. data/examples/plugins/last_nick.rb +24 -0
  34. data/examples/plugins/msg.rb +22 -0
  35. data/examples/plugins/multiple_matches.rb +32 -0
  36. data/examples/plugins/own_events.rb +37 -0
  37. data/examples/plugins/seen.rb +45 -0
  38. data/examples/plugins/timer.rb +22 -0
  39. data/examples/plugins/url_shorten.rb +33 -0
  40. data/lib/cinch.rb +5 -0
  41. data/lib/cinch/ban.rb +50 -0
  42. data/lib/cinch/bot.rb +479 -0
  43. data/lib/cinch/cached_list.rb +19 -0
  44. data/lib/cinch/callback.rb +20 -0
  45. data/lib/cinch/channel.rb +463 -0
  46. data/lib/cinch/channel_list.rb +29 -0
  47. data/lib/cinch/configuration.rb +73 -0
  48. data/lib/cinch/configuration/bot.rb +48 -0
  49. data/lib/cinch/configuration/dcc.rb +16 -0
  50. data/lib/cinch/configuration/plugins.rb +41 -0
  51. data/lib/cinch/configuration/sasl.rb +19 -0
  52. data/lib/cinch/configuration/ssl.rb +19 -0
  53. data/lib/cinch/configuration/timeouts.rb +14 -0
  54. data/lib/cinch/constants.rb +533 -0
  55. data/lib/cinch/dcc.rb +12 -0
  56. data/lib/cinch/dcc/dccable_object.rb +37 -0
  57. data/lib/cinch/dcc/incoming.rb +1 -0
  58. data/lib/cinch/dcc/incoming/send.rb +147 -0
  59. data/lib/cinch/dcc/outgoing.rb +1 -0
  60. data/lib/cinch/dcc/outgoing/send.rb +122 -0
  61. data/lib/cinch/exceptions.rb +46 -0
  62. data/lib/cinch/formatting.rb +125 -0
  63. data/lib/cinch/handler.rb +118 -0
  64. data/lib/cinch/handler_list.rb +90 -0
  65. data/lib/cinch/helpers.rb +231 -0
  66. data/lib/cinch/irc.rb +924 -0
  67. data/lib/cinch/isupport.rb +98 -0
  68. data/lib/cinch/log_filter.rb +21 -0
  69. data/lib/cinch/logger.rb +168 -0
  70. data/lib/cinch/logger/formatted_logger.rb +97 -0
  71. data/lib/cinch/logger/zcbot_logger.rb +22 -0
  72. data/lib/cinch/logger_list.rb +85 -0
  73. data/lib/cinch/mask.rb +69 -0
  74. data/lib/cinch/message.rb +392 -0
  75. data/lib/cinch/message_queue.rb +107 -0
  76. data/lib/cinch/mode_parser.rb +76 -0
  77. data/lib/cinch/network.rb +104 -0
  78. data/lib/cinch/open_ended_queue.rb +26 -0
  79. data/lib/cinch/pattern.rb +65 -0
  80. data/lib/cinch/plugin.rb +515 -0
  81. data/lib/cinch/plugin_list.rb +38 -0
  82. data/lib/cinch/rubyext/float.rb +3 -0
  83. data/lib/cinch/rubyext/module.rb +26 -0
  84. data/lib/cinch/rubyext/string.rb +33 -0
  85. data/lib/cinch/sasl.rb +34 -0
  86. data/lib/cinch/sasl/dh_blowfish.rb +71 -0
  87. data/lib/cinch/sasl/diffie_hellman.rb +47 -0
  88. data/lib/cinch/sasl/mechanism.rb +6 -0
  89. data/lib/cinch/sasl/plain.rb +26 -0
  90. data/lib/cinch/syncable.rb +83 -0
  91. data/lib/cinch/target.rb +199 -0
  92. data/lib/cinch/timer.rb +145 -0
  93. data/lib/cinch/user.rb +488 -0
  94. data/lib/cinch/user_list.rb +87 -0
  95. data/lib/cinch/utilities/deprecation.rb +16 -0
  96. data/lib/cinch/utilities/encoding.rb +37 -0
  97. data/lib/cinch/utilities/kernel.rb +13 -0
  98. data/lib/cinch/version.rb +4 -0
  99. metadata +140 -0
data/lib/cinch/dcc.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "cinch/dcc/outgoing"
2
+ require "cinch/dcc/incoming"
3
+
4
+ module Cinch
5
+ # Cinch supports the following DCC commands:
6
+ #
7
+ # - SEND (both {DCC::Incoming::Send incoming} and
8
+ # {DCC::Outgoing::Send outgoing})
9
+ # @since 2.0.0
10
+ module DCC
11
+ end
12
+ end
@@ -0,0 +1,37 @@
1
+ module Cinch
2
+ module DCC
3
+ # This module describes the required interface for objects that should
4
+ # be sendable via DCC.
5
+ #
6
+ # @note `File` conforms to this interface.
7
+ # @since 2.0.0
8
+ # @abstract
9
+ module DCCableObject
10
+ # Return the next `number` bytes of the object.
11
+ #
12
+ # @param [Integer] number Read `number` bytes at most
13
+ # @return [String] The read data
14
+ # @return [nil] If no more data can be read
15
+ def read(number)
16
+ end
17
+
18
+ # Seek to a specific position.
19
+ #
20
+ # @param [Integer] position The position in bytes to seek to
21
+ # @return [void]
22
+ def seek(position)
23
+ end
24
+
25
+ # @return [String] A string representing the object's path or name.
26
+ #
27
+ # @note This is only required if calling {User#dcc_send} with only
28
+ # one argument
29
+ def path
30
+ end
31
+
32
+ # @return [Integer] The total size of the data, in bytes.
33
+ def size
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1 @@
1
+ require "cinch/dcc/incoming/send"
@@ -0,0 +1,147 @@
1
+ require "socket"
2
+ require "ipaddr"
3
+
4
+ module Cinch
5
+ module DCC
6
+ module Incoming
7
+ # DCC SEND is a protocol for transferring files, usually found
8
+ # in IRC. While the handshake, i.e. the details of the file
9
+ # transfer, are transferred over IRC, the actual file transfer
10
+ # happens directly between two clients. As such it doesn't put
11
+ # stress on the IRC server.
12
+ #
13
+ # When someone tries to send a file to the bot, the `:dcc_send`
14
+ # event will be triggered, in which the DCC request can be
15
+ # inspected and optionally accepted.
16
+ #
17
+ # The event handler receives the plain message object as well as
18
+ # an instance of this class. That instance contains information
19
+ # about {#filename the suggested file name} (in a sanitized way)
20
+ # and allows for checking the origin.
21
+ #
22
+ # It is advised to reject transfers that seem to originate from
23
+ # a {#from_private_ip? private IP} or {#from_localhost? the
24
+ # local IP itself} unless that is expected. Otherwise, specially
25
+ # crafted requests could cause the bot to connect to internal
26
+ # services.
27
+ #
28
+ # Finally, the file transfer can be {#accept accepted} and
29
+ # written to any object that implements a `#<<` method, which
30
+ # includes File objects as well as plain strings.
31
+ #
32
+ # @example Saving a transfer to a temporary file
33
+ # require "tempfile"
34
+ #
35
+ # listen_to :dcc_send, method: :incoming_dcc
36
+ # def incoming_dcc(m, dcc)
37
+ # if dcc.from_private_ip? || dcc.from_localhost?
38
+ # @bot.loggers.debug "Not accepting potentially dangerous file transfer"
39
+ # return
40
+ # end
41
+ #
42
+ # t = Tempfile.new(dcc.filename)
43
+ # dcc.accept(t)
44
+ # t.close
45
+ # end
46
+ #
47
+ # @attr_reader filename
48
+ class Send
49
+ # @private
50
+ PRIVATE_NETS = [IPAddr.new("fc00::/7"),
51
+ IPAddr.new("10.0.0.0/8"),
52
+ IPAddr.new("172.16.0.0/12"),
53
+ IPAddr.new("192.168.0.0/16")]
54
+
55
+ # @private
56
+ LOCAL_NETS = [IPAddr.new("127.0.0.0/8"),
57
+ IPAddr.new("::1/128")]
58
+
59
+ # @return [User]
60
+ attr_reader :user
61
+
62
+ # @return [Integer]
63
+ attr_reader :size
64
+
65
+ # @return [String]
66
+ attr_reader :ip
67
+
68
+ # @return [Fixnum]
69
+ attr_reader :port
70
+
71
+ # @param [Hash] opts
72
+ # @option opts [User] user
73
+ # @option opts [String] filename
74
+ # @option opts [Integer] size
75
+ # @option opts [String] ip
76
+ # @option opts [Fixnum] port
77
+ # @api private
78
+ def initialize(opts)
79
+ @user, @filename, @size, @ip, @port = opts.values_at(:user, :filename, :size, :ip, :port)
80
+ end
81
+
82
+ # @return [String] The basename of the file name, with
83
+ # (back)slashes removed.
84
+ def filename
85
+ File.basename(File.expand_path(@filename)).delete("/\\")
86
+ end
87
+
88
+ # This method is used for accepting a DCC SEND offer. It
89
+ # expects an object to save the result to (usually an instance
90
+ # of IO or String).
91
+ #
92
+ # @param [#<<] io The object to write the data to.
93
+ # @return [Boolean] True if the transfer finished
94
+ # successfully, false otherwise.
95
+ # @note This method blocks.
96
+ # @example Saving to a file
97
+ # f = File.open("/tmp/foo", "w")
98
+ # dcc.accept(f)
99
+ # f.close
100
+ #
101
+ # @example Saving to a string
102
+ # s = ""
103
+ # dcc.accept(s)
104
+ def accept(io)
105
+ socket = TCPSocket.new(@ip, @port)
106
+ total = 0
107
+
108
+ while buf = socket.readpartial(8192)
109
+ total += buf.bytesize
110
+
111
+ begin
112
+ socket.write_nonblock [total].pack("N")
113
+ rescue Errno::EWOULDBLOCK, Errno::AGAIN
114
+ # Nobody cares about ACKs, really. And if the sender
115
+ # couldn't receive it at this point, they probably don't
116
+ # care, either.
117
+ end
118
+ io << buf
119
+
120
+ # Break here in case the sender doesn't close the
121
+ # connection on the final ACK.
122
+ break if total == @size
123
+ end
124
+
125
+ socket.close
126
+ return true
127
+ rescue EOFError
128
+ return false
129
+ end
130
+
131
+ # @return [Boolean] True if the DCC originates from a private ip
132
+ # @see #from_localhost?
133
+ def from_private_ip?
134
+ ip = IPAddr.new(@ip)
135
+ PRIVATE_NETS.any? {|n| n.include?(ip)}
136
+ end
137
+
138
+ # @return [Boolean] True if the DCC originates from localhost
139
+ # @see #from_private_ip?
140
+ def from_localhost?
141
+ ip = IPAddr.new(@ip)
142
+ LOCAL_NETS.any? {|n| n.include?(ip)}
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1 @@
1
+ require "cinch/dcc/outgoing/send"
@@ -0,0 +1,122 @@
1
+ require "socket"
2
+ require "ipaddr"
3
+ require "timeout"
4
+
5
+ module Cinch
6
+ module DCC
7
+ module Outgoing
8
+ # DCC SEND is a protocol for transferring files, usually found
9
+ # in IRC. While the handshake, i.e. the details of the file
10
+ # transfer, are transferred over IRC, the actual file transfer
11
+ # happens directly between two clients. As such it doesn't put
12
+ # stress on the IRC server.
13
+ #
14
+ # Cinch allows sending files by either using
15
+ # {Cinch::User#dcc_send}, which takes care of all parameters as
16
+ # well as setting up resume support, or by creating instances of
17
+ # this class directly. The latter will only be useful to people
18
+ # working on the Cinch code itself.
19
+ #
20
+ # {Cinch::User#dcc_send} expects an object to send as well as
21
+ # optionaly a file name, which is sent to the receiver as a
22
+ # suggestion where to save the file. If no file name is
23
+ # provided, the method will use the object's `#path` method to
24
+ # determine it.
25
+ #
26
+ # Any object that implements {DCC::DCCableObject} can be sent,
27
+ # but sending files will probably be the most common case.
28
+ #
29
+ # If you're behind a NAT it is necessary to explicitly set the
30
+ # external IP using the {file:docs/bot_options.md#dccownip dcc.own_ip
31
+ # option}.
32
+ #
33
+ # @example Sending a file to a user
34
+ # match "send me something"
35
+ # def execute(m)
36
+ # m.user.dcc_send(open("/tmp/cookies"))
37
+ # end
38
+ class Send
39
+ # @param [Hash] opts
40
+ # @option opts [User] receiver
41
+ # @option opts [String] filename
42
+ # @option opts [File] io
43
+ # @option opts [String] own_ip
44
+ def initialize(opts = {})
45
+ @receiver, @filename, @io, @own_ip = opts.values_at(:receiver, :filename, :io, :own_ip)
46
+ end
47
+
48
+ # Start the server
49
+ #
50
+ # @return [void]
51
+ def start_server
52
+ @socket = TCPServer.new(0)
53
+ @socket.listen(1)
54
+ end
55
+
56
+ # Send the handshake to the user.
57
+ #
58
+ # @return [void]
59
+ def send_handshake
60
+ handshake = "\001DCC SEND %s %d %d %d\001" % [@filename, IPAddr.new(@own_ip).to_i, port, @io.size]
61
+ @receiver.send(handshake)
62
+ end
63
+
64
+ # Listen for an incoming connection.
65
+ #
66
+ # This starts listening for an incoming connection to the server
67
+ # started by {#start_server}. After a client successfully
68
+ # connected, the server socket will be closed and the file
69
+ # transferred to the client.
70
+ #
71
+ # @raise [Timeout::Error] Raised if the receiver did not connect
72
+ # within 30 seconds
73
+ # @return [void]
74
+ # @note This method blocks.
75
+ def listen
76
+ begin
77
+ fd = nil
78
+ Timeout.timeout(30) do
79
+ fd, _ = @socket.accept
80
+ end
81
+ send_data(fd)
82
+ fd.close
83
+ ensure
84
+ @socket.close
85
+ end
86
+ end
87
+
88
+ # Seek to `pos` in the data.
89
+ #
90
+ # @param [Integer] pos
91
+ # @return [void]
92
+ # @api private
93
+ def seek(pos)
94
+ @io.seek(pos)
95
+ end
96
+
97
+ # @return [Fixnum] The port used for the socket
98
+ def port
99
+ @port ||= @socket.addr[1]
100
+ end
101
+
102
+ private
103
+ def send_data(fd)
104
+ @io.advise(:sequential)
105
+
106
+ while chunk = @io.read(8096)
107
+ while true
108
+ rs, ws = IO.select([fd], [fd])
109
+ if !rs.empty?
110
+ rs.first.recv(8096)
111
+ end
112
+ if !ws.empty?
113
+ ws.first.write(chunk)
114
+ break
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,46 @@
1
+ module Cinch
2
+ # A collection of exceptions.
3
+ module Exceptions
4
+ # Generic error. Superclass for all Cinch-specific errors.
5
+ class Generic < ::StandardError
6
+ end
7
+
8
+ # Generic error when an argument is too long.
9
+ class ArgumentTooLong < Generic
10
+ end
11
+
12
+ # Error that is raised when a topic is too long to be set.
13
+ class TopicTooLong < ArgumentTooLong
14
+ end
15
+
16
+ # Error that is raised when a nick is too long to be used.
17
+ class NickTooLong < ArgumentTooLong
18
+ end
19
+
20
+ # Error that is raised when a kick reason is too long.
21
+ class KickReasonTooLong < ArgumentTooLong
22
+ end
23
+
24
+ # Raised whenever Cinch discovers a feature it doesn't support
25
+ # yet.
26
+ class UnsupportedFeature < Generic
27
+ end
28
+
29
+ # Raised when Cinch discovers a user or channel mode, which it
30
+ # doesn't support yet.
31
+ class UnsupportedMode < Generic
32
+ def initialize(mode)
33
+ super "Cinch does not support the mode '#{mode}' yet."
34
+ end
35
+ end
36
+
37
+ # Error stating that an invalid mode string was encountered.
38
+ class InvalidModeString < Generic
39
+ end
40
+
41
+ # Raised when a synced attribute hasn't been available for too
42
+ # long.
43
+ class SyncedAttributeNotAvailable < Generic
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,125 @@
1
+ module Cinch
2
+ # This module can be used for adding and removing colors and
3
+ # formatting to/from messages.
4
+ #
5
+ # The format codes used are those defined by mIRC, which are also
6
+ # the ones supported by most clients.
7
+ #
8
+ # For usage instructions and examples, see {.format}.
9
+ #
10
+ # List of valid colors
11
+ # =========================
12
+ # - aqua
13
+ # - black
14
+ # - blue
15
+ # - brown
16
+ # - green
17
+ # - grey
18
+ # - lime
19
+ # - orange
20
+ # - pink
21
+ # - purple
22
+ # - red
23
+ # - royal
24
+ # - silver
25
+ # - teal
26
+ # - white
27
+ # - yellow
28
+ #
29
+ # List of valid attributes
30
+ # ========================
31
+ # - bold
32
+ # - italic
33
+ # - reverse/reversed
34
+ # - underline/underlined
35
+ #
36
+ # Other
37
+ # =====
38
+ # - reset (Resets all formatting to the client's defaults)
39
+ #
40
+ # @since 2.0.0
41
+ module Formatting
42
+ # @private
43
+ Colors = {
44
+ :white => "00",
45
+ :black => "01",
46
+ :blue => "02",
47
+ :green => "03",
48
+ :red => "04",
49
+ :brown => "05",
50
+ :purple => "06",
51
+ :orange => "07",
52
+ :yellow => "08",
53
+ :lime => "09",
54
+ :teal => "10",
55
+ :aqua => "11",
56
+ :royal => "12",
57
+ :pink => "13",
58
+ :grey => "14",
59
+ :silver => "15",
60
+ }
61
+
62
+ # @private
63
+ Attributes = {
64
+ :bold => 2.chr,
65
+ :underlined => 31.chr,
66
+ :underline => 31.chr,
67
+ :reversed => 22.chr,
68
+ :reverse => 22.chr,
69
+ :italic => 29.chr,
70
+ :reset => 15.chr,
71
+ }
72
+
73
+ # @param [Array<Symbol>] settings The colors and attributes to apply.
74
+ # When supplying two colors, the first will be used for the
75
+ # foreground and the second for the background.
76
+ # @param [String] string The string to format.
77
+ # @return [String] the formatted string
78
+ # @since 2.0.0
79
+ # @raise [ArgumentError] When passing more than two colors as arguments.
80
+ # @see Helpers#Format Helpers#Format for easier access to this method.
81
+ #
82
+ # @example Nested formatting, combining text styles and colors
83
+ # reply = Format(:underline, "Hello %s! Is your favourite color %s?" % [Format(:bold, "stranger"), Format(:red, "red")])
84
+ def self.format(*settings, string)
85
+ string = string.dup
86
+
87
+ attributes = settings.select {|k| Attributes.has_key?(k)}.map {|k| Attributes[k]}
88
+ colors = settings.select {|k| Colors.has_key?(k)}.map {|k| Colors[k]}
89
+ if colors.size > 2
90
+ raise ArgumentError, "At most two colors (foreground and background) might be specified"
91
+ end
92
+
93
+ attribute_string = attributes.join
94
+ color_string = if colors.empty?
95
+ ""
96
+ else
97
+ "\x03#{colors.join(",")}"
98
+ end
99
+
100
+ prepend = attribute_string + color_string
101
+ append = Attributes[:reset]
102
+
103
+ # attributes act as toggles, so e.g. underline+underline = no
104
+ # underline. We thus have to delete all duplicate attributes
105
+ # from nested strings.
106
+ string.delete!(attribute_string)
107
+
108
+ # Replace the reset code of nested strings to continue the
109
+ # formattings of the outer string.
110
+ string.gsub!(/#{Attributes[:reset]}/, Attributes[:reset] + prepend)
111
+ return prepend + string + append
112
+ end
113
+
114
+ # Deletes all mIRC formatting codes from the string. This strips
115
+ # formatting for bold, underline and so on, as well as color
116
+ # codes. This does include removing the numeric arguments.
117
+ #
118
+ # @param [String] string The string to filter
119
+ # @return [String] The filtered string
120
+ # @since 2.2.0
121
+ def self.unformat(string)
122
+ string.gsub(/[\x02\x0f\x16\x1f\x12]|\x03(\d{1,2}(,\d{1,2})?)?/, '')
123
+ end
124
+ end
125
+ end