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.
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
@@ -0,0 +1,98 @@
1
+ module Cinch
2
+ # This class exposes parsed ISUPPORT information of the IRC network.
3
+ class ISupport < Hash
4
+ @@mappings = {
5
+ %w[PREFIX] => lambda {|v|
6
+ modes, prefixes = v.match(/^\((.+)\)(.+)$/)[1..2]
7
+ h = {}
8
+ modes.split("").each_with_index do |c, i|
9
+ h[c] = prefixes[i]
10
+ end
11
+ h
12
+ },
13
+
14
+ %w[CHANTYPES] => lambda {|v| v.split("")},
15
+ %w[CHANMODES] => lambda {|v|
16
+ h = {}
17
+ h["A"], h["B"], h["C"], h["D"] = v.split(",").map {|l| l.split("")}
18
+ h
19
+ },
20
+
21
+ %w[MODES MAXCHANNELS NICKLEN MAXBANS TOPICLEN
22
+ KICKLEN CHANNELLEN CHIDLEN SILENCE AWAYLEN
23
+ MAXTARGETS WATCH MONITOR] => lambda {|v| v.to_i},
24
+
25
+ %w[CHANLIMIT MAXLIST IDCHAN] => lambda {|v|
26
+ h = {}
27
+ v.split(",").each do |pair|
28
+ args, num = pair.split(":")
29
+ args.split("").each do |arg|
30
+ h[arg] = num.to_i
31
+ end
32
+ end
33
+ h
34
+ },
35
+
36
+ %w[TARGMAX] => lambda {|v|
37
+ h = {}
38
+ v.split(",").each do |pair|
39
+ name, value = pair.split(":")
40
+ h[name] = value.to_i
41
+ end
42
+ h
43
+ },
44
+
45
+ %w[NETWORK] => lambda {|v| v},
46
+ %w[STATUSMSG] => lambda {|v| v.split("")},
47
+ %w[CASEMAPPING] => lambda {|v| v.to_sym},
48
+ %w[ELIST] => lambda {|v| v.split("")},
49
+ # TODO STD
50
+ }
51
+
52
+ def initialize(*args)
53
+ super
54
+ # by setting most numeric values to "Infinity", we let the
55
+ # server truncate messages and lists while at the same time
56
+ # allowing the use of strictness=:strict for servers that don't
57
+ # support ISUPPORT (hopefully none, anyway)
58
+
59
+ self["PREFIX"] = {"o" => "@", "v" => "+"}
60
+ self["CHANTYPES"] = ["#"]
61
+ self["CHANMODES"] = {
62
+ "A" => ["b"],
63
+ "B" => ["k"],
64
+ "C" => ["l"],
65
+ "D" => %w[i m n p s t r]
66
+ }
67
+ self["MODES"] = 1
68
+ self["NICKLEN"] = Float::INFINITY
69
+ self["MAXBANS"] = Float::INFINITY
70
+ self["TOPICLEN"] = Float::INFINITY
71
+ self["KICKLEN"] = Float::INFINITY
72
+ self["CHANNELLEN"] = Float::INFINITY
73
+ self["CHIDLEN"] = 5
74
+ self["AWAYLEN"] = Float::INFINITY
75
+ self["MAXTARGETS"] = 1
76
+ self["MAXCHANNELS"] = Float::INFINITY # deprecated
77
+ self["CHANLIMIT"] = {"#" => Float::INFINITY}
78
+ self["STATUSMSG"] = []
79
+ self["CASEMAPPING"] = :rfc1459
80
+ self["ELIST"] = []
81
+ self["MONITOR"] = 0
82
+ end
83
+
84
+ # @api private
85
+ # @return [void]
86
+ def parse(*options)
87
+ options.each do |option|
88
+ name, value = option.split("=")
89
+ if value
90
+ proc = @@mappings.find {|key, _| key.include?(name)}
91
+ self[name] = (proc && proc[1].call(value)) || value
92
+ else
93
+ self[name] = true
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,21 @@
1
+ module Cinch
2
+ # LogFilter describes an interface for filtering log messages before
3
+ # they're printed.
4
+ #
5
+ # @abstract
6
+ # @since 2.3.0
7
+ class LogFilter
8
+ # filter is called for each log message, except for exceptions. It
9
+ # returns a new string, which is the one that should be printed, or
10
+ # further filtered by other filters. Returning nil will drop the
11
+ # message.
12
+ #
13
+ # @param [String] message The message that is to be logged
14
+ # @param [:debug, :incoming, :outgoing, :info, :warn, :exception,
15
+ # :error, :fatal] event The kind of message
16
+ # @return [String, nil] The modified message, as it should be
17
+ # logged, or nil if the message shouldn't be logged at all
18
+ def filter(message, event)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,168 @@
1
+ module Cinch
2
+ # This is the base logger class from which all loggers have to
3
+ # inherit.
4
+ #
5
+ # @version 2.0.0
6
+ class Logger
7
+ # @private
8
+ LevelOrder = [:debug, :log, :info, :warn, :error, :fatal]
9
+
10
+ # @return [Array<:debug, :log, :info, :warn, :error, :fatal>]
11
+ # The minimum level of events to log
12
+ attr_accessor :level
13
+
14
+ # @return [Mutex]
15
+ # @api private
16
+ attr_reader :mutex
17
+
18
+ # @return [IO]
19
+ # @api private
20
+ attr_reader :output
21
+
22
+ # @param [IO] output The I/O object to write log data to
23
+ def initialize(output, level: :debug)
24
+ @output = output
25
+ @mutex = Mutex.new
26
+ @level = level
27
+ end
28
+
29
+ # Logs a debugging message.
30
+ #
31
+ # @param [String] message
32
+ # @return [void]
33
+ # @version 2.0.0
34
+ def debug(message)
35
+ log(message, :debug)
36
+ end
37
+
38
+ # Logs an error message.
39
+ #
40
+ # @param [String] message
41
+ # @return [void]
42
+ # @since 2.0.0
43
+ def error(message)
44
+ log(message, :error)
45
+ end
46
+
47
+ # Logs a fatal message.
48
+ #
49
+ # @param [String] message
50
+ # @return [void]
51
+ # @since 2.0.0
52
+ def fatal(message)
53
+ log(message, :fatal)
54
+ end
55
+
56
+ # Logs an info message.
57
+ #
58
+ # @param [String] message
59
+ # @return [void]
60
+ # @since 2.0.0
61
+ def info(message)
62
+ log(message, :info)
63
+ end
64
+
65
+ # Logs a warning message.
66
+ #
67
+ # @param [String] message
68
+ # @return [void]
69
+ # @since 2.0.0
70
+ def warn(message)
71
+ log(message, :warn)
72
+ end
73
+
74
+ # Logs an incoming IRC message.
75
+ #
76
+ # @param [String] message
77
+ # @return [void]
78
+ # @since 2.0.0
79
+ def incoming(message)
80
+ log(message, :incoming, :log)
81
+ end
82
+
83
+ # Logs an outgoing IRC message.
84
+ #
85
+ # @param [String] message
86
+ # @return [void]
87
+ # @since 2.0.0
88
+ def outgoing(message)
89
+ log(message, :outgoing, :log)
90
+ end
91
+
92
+ # Logs an exception.
93
+ #
94
+ # @param [Exception] e
95
+ # @return [void]
96
+ # @since 2.0.0
97
+ def exception(e)
98
+ log(e.message, :exception, :error)
99
+ end
100
+
101
+ # Logs a message.
102
+ #
103
+ # @param [String, Array] messages The message(s) to log
104
+ # @param [:debug, :incoming, :outgoing, :info, :warn,
105
+ # :exception, :error, :fatal] event The kind of event that
106
+ # triggered the message
107
+ # @param [:debug, :info, :warn, :error, :fatal] level The level of the message
108
+ # @return [void]
109
+ # @version 2.0.0
110
+ def log(messages, event = :debug, level = event)
111
+ return unless will_log?(level)
112
+ @mutex.synchronize do
113
+ Array(messages).each do |message|
114
+ message = format_general(message)
115
+ message = format_message(message, event)
116
+
117
+ next if message.nil?
118
+ @output.puts message.encode("locale", {:invalid => :replace, :undef => :replace})
119
+ end
120
+ end
121
+ end
122
+
123
+ # @param [:debug, :info, :warn, :error, :fatal] level
124
+ # @return [Boolean] Whether the currently set logging level will
125
+ # allow the passed in level to be logged
126
+ # @since 2.0.0
127
+ def will_log?(level)
128
+ LevelOrder.index(level) >= LevelOrder.index(@level)
129
+ end
130
+
131
+ private
132
+ def format_message(message, level)
133
+ __send__ "format_#{level}", message
134
+ end
135
+
136
+ def format_general(message)
137
+ message
138
+ end
139
+
140
+ def format_debug(message)
141
+ message
142
+ end
143
+
144
+ def format_error(message)
145
+ message
146
+ end
147
+
148
+ def format_info(message)
149
+ message
150
+ end
151
+
152
+ def format_warn(message)
153
+ message
154
+ end
155
+
156
+ def format_incoming(message)
157
+ message
158
+ end
159
+
160
+ def format_outgoing(message)
161
+ message
162
+ end
163
+
164
+ def format_exception(message)
165
+ message
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,97 @@
1
+ require "cinch/logger"
2
+
3
+ module Cinch
4
+ class Logger
5
+ # @version 2.0.0
6
+ class FormattedLogger < Logger
7
+ # @private
8
+ Colors = {
9
+ :reset => "\e[0m",
10
+ :bold => "\e[1m",
11
+ :red => "\e[31m",
12
+ :green => "\e[32m",
13
+ :yellow => "\e[33m",
14
+ :blue => "\e[34m",
15
+ :black => "\e[30m",
16
+ :bg_white => "\e[47m",
17
+ }
18
+
19
+ # (see Logger#exception)
20
+ def exception(e)
21
+ lines = ["#{e.backtrace.first}: #{e.message} (#{e.class})"]
22
+ lines.concat e.backtrace[1..-1].map {|s| "\t" + s}
23
+ log(lines, :exception, :error)
24
+ end
25
+
26
+ private
27
+ def timestamp
28
+ Time.now.strftime("[%Y/%m/%d %H:%M:%S.%L]")
29
+ end
30
+
31
+ # @api private
32
+ # @param [String] text text to colorize
33
+ # @param [Array<Symbol>] codes array of colors to apply
34
+ # @return [String] colorized string
35
+ def colorize(text, *codes)
36
+ return text unless @output.tty?
37
+ codes = Colors.values_at(*codes).join
38
+ text = text.gsub(/#{Regexp.escape(Colors[:reset])}/, Colors[:reset] + codes)
39
+ codes + text + Colors[:reset]
40
+ end
41
+
42
+ def format_general(message)
43
+ message.gsub(/[^[:print:]]/) do |m|
44
+ colorize(m.inspect[1..-2], :bg_white, :black)
45
+ end
46
+ end
47
+
48
+ def format_debug(message)
49
+ "%s %s %s" % [timestamp, colorize("!!", :yellow), message]
50
+ end
51
+
52
+ def format_warn(message)
53
+ format_debug(message)
54
+ end
55
+
56
+ def format_info(message)
57
+ "%s %s %s" % [timestamp, "II", message]
58
+ end
59
+
60
+ def format_incoming(message)
61
+ pre, msg = message.split(" :", 2)
62
+ pre_parts = pre.split(" ")
63
+
64
+ prefix = colorize(">>", :green)
65
+
66
+ if pre_parts.size == 1
67
+ pre_parts[0] = colorize(pre_parts[0], :bold)
68
+ else
69
+ pre_parts[0] = colorize(pre_parts[0], :blue)
70
+ pre_parts[1] = colorize(pre_parts[1], :bold)
71
+ end
72
+
73
+ "%s %s %s %s" % [timestamp,
74
+ prefix,
75
+ pre_parts.join(" "),
76
+ msg ? colorize(":#{msg}", :yellow) : ""]
77
+ end
78
+
79
+ def format_outgoing(message)
80
+ pre, msg = message.split(" :", 2)
81
+ pre_parts = pre.split(" ")
82
+
83
+ prefix = colorize("<<", :red)
84
+ pre_parts[0] = colorize(pre_parts[0], :bold)
85
+
86
+ "%s %s %s %s" % [timestamp,
87
+ prefix,
88
+ pre_parts.join(" "),
89
+ msg ? colorize(":#{msg}", :yellow) : ""]
90
+ end
91
+
92
+ def format_exception(message)
93
+ "%s %s %s" % [timestamp, colorize("!!", :red), message]
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,22 @@
1
+ require "cinch/logger"
2
+ module Cinch
3
+ class Logger
4
+ # This logger logs all incoming messages in the format of zcbot.
5
+ # All other debug output (outgoing messages, exceptions, ...) will
6
+ # silently be dropped. The sole purpose of this logger is to
7
+ # produce logs parseable by pisg (with the zcbot formatter) to
8
+ # create channel statistics..
9
+ class ZcbotLogger < Cinch::Logger
10
+ # (see Logger#log)
11
+ def log(messages, event, level = event)
12
+ return if event != :incoming
13
+ super
14
+ end
15
+
16
+ private
17
+ def format_incoming(message)
18
+ Time.now.strftime("%m/%d/%Y %H:%M:%S ") + message
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,85 @@
1
+ module Cinch
2
+ # This class allows Cinch to use multiple loggers at once. A common
3
+ # use-case would be to log formatted messages to STDERR and a
4
+ # pisg-compatible log to a file.
5
+ #
6
+ # It inherits directly from Array, so adding new loggers is as easy
7
+ # as calling LoggerList#push.
8
+ #
9
+ # @attr_writer level
10
+ # @since 2.0.0
11
+ class LoggerList < Array
12
+ # A list of log filters that will be applied before emitting a log
13
+ # message.
14
+ #
15
+ # @return [Array<LogFilter>]
16
+ # @since 2.3.0
17
+ attr_accessor :filters
18
+ def initialize(*args)
19
+ @filters = []
20
+ super
21
+ end
22
+
23
+ # (see Logger#level=)
24
+ def level=(level)
25
+ each {|l| l.level = level}
26
+ end
27
+
28
+ # (see Logger#log)
29
+ def log(messages, event = :debug, level = event)
30
+ messages = Array(messages).map {|m| filter(m, event)}.compact
31
+ each {|l| l.log(messages, event, level)}
32
+ end
33
+
34
+ # (see Logger#debug)
35
+ def debug(message)
36
+ (m = filter(message, :debug)) && each {|l| l.debug(m)}
37
+ end
38
+
39
+ # (see Logger#error)
40
+ def error(message)
41
+ (m = filter(message, :error)) && each {|l| l.error(m)}
42
+ end
43
+
44
+ # (see Logger#error)
45
+ def fatal(message)
46
+ (m = filter(message, :fatal)) && each {|l| l.fatal(m)}
47
+ end
48
+
49
+ # (see Logger#info)
50
+ def info(message)
51
+ (m = filter(message, :info)) && each {|l| l.info(m)}
52
+ end
53
+
54
+ # (see Logger#warn)
55
+ def warn(message)
56
+ (m = filter(message, :warn)) && each {|l| l.warn(m)}
57
+ end
58
+
59
+ # (see Logger#incoming)
60
+ def incoming(message)
61
+ (m = filter(message, :incoming)) && each {|l| l.incoming(m)}
62
+ end
63
+
64
+ # (see Logger#outgoing)
65
+ def outgoing(message)
66
+ (m = filter(message, :outgoing)) && each {|l| l.outgoing(m)}
67
+ end
68
+
69
+ # (see Logger#exception)
70
+ def exception(e)
71
+ each {|l| l.exception(e)}
72
+ end
73
+
74
+ private
75
+ def filter(m, ev)
76
+ @filters.each do |f|
77
+ m = f.filter(m, ev)
78
+ if m.nil?
79
+ break
80
+ end
81
+ end
82
+ m
83
+ end
84
+ end
85
+ end