cinch 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +25 -44
- data/examples/basic/autovoice.rb +1 -1
- data/examples/basic/join_part.rb +0 -4
- data/examples/plugins/autovoice.rb +2 -5
- data/examples/plugins/google.rb +1 -2
- data/examples/plugins/hooks.rb +36 -0
- data/examples/plugins/lambdas.rb +35 -0
- data/examples/plugins/last_nick.rb +24 -0
- data/examples/plugins/multiple_matches.rb +1 -10
- data/examples/plugins/own_events.rb +37 -0
- data/examples/plugins/timer.rb +22 -0
- data/examples/plugins/url_shorten.rb +1 -1
- data/lib/cinch.rb +50 -1
- data/lib/cinch/ban.rb +5 -2
- data/lib/cinch/bot.rb +360 -193
- data/lib/cinch/cache_manager.rb +15 -0
- data/lib/cinch/callback.rb +6 -0
- data/lib/cinch/channel.rb +150 -96
- data/lib/cinch/channel_manager.rb +26 -0
- data/lib/cinch/constants.rb +6 -4
- data/lib/cinch/exceptions.rb +9 -0
- data/lib/cinch/irc.rb +197 -82
- data/lib/cinch/logger/formatted_logger.rb +8 -8
- data/lib/cinch/logger/zcbot_logger.rb +37 -0
- data/lib/cinch/mask.rb +17 -3
- data/lib/cinch/message.rb +14 -7
- data/lib/cinch/message_queue.rb +8 -4
- data/lib/cinch/mode_parser.rb +56 -0
- data/lib/cinch/pattern.rb +45 -0
- data/lib/cinch/plugin.rb +129 -34
- data/lib/cinch/rubyext/string.rb +4 -4
- data/lib/cinch/syncable.rb +8 -0
- data/lib/cinch/user.rb +68 -13
- data/lib/cinch/user_manager.rb +60 -0
- metadata +17 -35
- data/Rakefile +0 -66
- data/lib/cinch/PLANNED +0 -4
- data/spec/bot_spec.rb +0 -5
- data/spec/channel_spec.rb +0 -5
- data/spec/cinch_spec.rb +0 -5
- data/spec/irc_spec.rb +0 -5
- data/spec/message_spec.rb +0 -5
- data/spec/plugin_spec.rb +0 -5
- data/spec/spec.opts +0 -2
- data/spec/spec_helper.rb +0 -8
- data/spec/user_spec.rb +0 -5
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Cinch -
|
1
|
+
Cinch - The IRC Bot Building Framework
|
2
2
|
=====================================
|
3
3
|
|
4
4
|
Description
|
@@ -17,8 +17,6 @@ If you'd like to test your own Cinch experiments you can do so in the
|
|
17
17
|
[irc.freenode.org](irc://irc.freenode.org/cinch-bots). For general
|
18
18
|
support, join [#cinch](irc://irc.freenode.org/cinch).
|
19
19
|
|
20
|
-
This original document can be found [here](http://doc.injekt.net/cinch).
|
21
|
-
|
22
20
|
Installation
|
23
21
|
------------
|
24
22
|
|
@@ -32,7 +30,7 @@ You can install the latest Cinch gem using RubyGems
|
|
32
30
|
|
33
31
|
Alternatively you can check out the latest code directly from Github
|
34
32
|
|
35
|
-
git clone http://github.com/
|
33
|
+
git clone http://github.com/cinchrb/cinch.git
|
36
34
|
|
37
35
|
Example
|
38
36
|
-------
|
@@ -61,54 +59,37 @@ Features
|
|
61
59
|
|
62
60
|
### Documentation
|
63
61
|
|
64
|
-
Cinch provides a documented API, which is online for your viewing pleasure
|
62
|
+
Cinch provides a documented API, which is online for your viewing pleasure
|
63
|
+
[here](http://rubydoc.info/github/cinchrb/cinch/master/).
|
65
64
|
|
66
65
|
### Object Oriented
|
67
66
|
|
68
|
-
Many IRC bots (and there are, so
|
69
|
-
advantage of the awesome Object Oriented Interface which most Ruby
|
70
|
-
become accustomed to and grown to love.
|
67
|
+
Many IRC bots (and there are, **so** many) are great, but we see so little of
|
68
|
+
them take advantage of the awesome Object Oriented Interface which most Ruby
|
69
|
+
programmers will have become accustomed to and grown to love.
|
71
70
|
|
72
|
-
Well, Cinch uses this functionality to
|
73
|
-
a reference to a channel or a user, to another method, which then
|
74
|
-
another method (by which time you're confused about what's
|
75
|
-
an OOP interface for even the simpliest of tasks,
|
76
|
-
to comprehend.
|
71
|
+
Well, Cinch uses this functionality to its advantage. Rather than having to
|
72
|
+
pass around a reference to a channel or a user, to another method, which then
|
73
|
+
passes it to another method (by which time you're confused about what's
|
74
|
+
going on) -- Cinch provides an OOP interface for even the simpliest of tasks,
|
75
|
+
making your code simple and easy to comprehend.
|
77
76
|
|
78
77
|
### Threaded
|
79
78
|
|
80
|
-
Unlike a lot of popular IRC frameworks, Cinch is threaded. But wait, don't let
|
81
|
-
scare you. It's totally easy to grasp.
|
82
|
-
|
83
|
-
Each of Cinch's plugins and handlers are executed in their own personal thread. This
|
84
|
-
means the main thread can stay focused on what it does best, providing non-blocking
|
85
|
-
reading and writing to an IRC server. This will prevent your bot from locking up
|
86
|
-
when one of your plugins starts doing some intense operations. Damn that's handy.
|
87
|
-
|
88
|
-
### Key/Value Store
|
79
|
+
Unlike a lot of popular IRC frameworks, Cinch is threaded. But wait, don't let
|
80
|
+
that scare you. It's totally easy to grasp.
|
89
81
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
96
|
-
|
97
|
-
on :message, /^add friend (.+)$/ do |m, friend|
|
98
|
-
store[:friends] << friend
|
99
|
-
end
|
100
|
-
|
101
|
-
on :message /^get friends$/ do |m|
|
102
|
-
m.reply "Your friends are: #{store[:friends].join(', ')}"
|
103
|
-
end
|
104
|
-
|
105
|
-
Neat, right?
|
82
|
+
Each of Cinch's plugins and handlers are executed in their own personal thread.
|
83
|
+
This means the main thread can stay focused on what it does best, providing
|
84
|
+
non-blocking reading and writing to an IRC server. This will prevent your bot
|
85
|
+
from locking up when one of your plugins starts doing some intense operations.
|
86
|
+
Damn that's handy.
|
106
87
|
|
107
88
|
### Plugins
|
108
89
|
|
109
|
-
That's right folks, Cinch provides a modular based plugin system. This is a
|
110
|
-
many people have bugged us about for a long time. It's finally here,
|
111
|
-
as awesome as you had hoped!
|
90
|
+
That's right folks, Cinch provides a modular based plugin system. This is a
|
91
|
+
feature many people have bugged us about for a long time. It's finally here,
|
92
|
+
and it's as awesome as you had hoped!
|
112
93
|
|
113
94
|
This system allows you to create feature packed plugins without interfering with
|
114
95
|
any of the Cinch internals. Everything in your plugin is self contained, meaning
|
@@ -180,13 +161,13 @@ Please note that although we very much appreciate all of your efforts, Cinch
|
|
180
161
|
will not accept patches in aid of Ruby 1.8 compatibility. We have no intention
|
181
162
|
of supporting Ruby versions below 1.9.1.
|
182
163
|
|
183
|
-
Fork the project, implement your awesome feature in
|
164
|
+
Fork the project, implement your awesome feature in its own branch, and send
|
184
165
|
a pull request to one of the Cinch collaborators. We'll be more than happy
|
185
166
|
to check it out.
|
186
167
|
|
187
|
-
Just remember, no specs, no cookies!
|
188
|
-
|
189
168
|
### Contributors
|
190
169
|
- darix <darix [at] nordisch.org> (wrote the message splitting algorithm)
|
191
170
|
- robgleeson (thanks for testing, contributing a lot of ideas,
|
192
171
|
discussing design decisions etc)
|
172
|
+
- Emil Loer (http://github.com/thedjinn) for improving the handling of
|
173
|
+
unexpected disconnects and reconnects.
|
data/examples/basic/autovoice.rb
CHANGED
data/examples/basic/join_part.rb
CHANGED
@@ -9,7 +9,7 @@ require 'cinch'
|
|
9
9
|
class Autovoice
|
10
10
|
include Cinch::Plugin
|
11
11
|
listen_to :join
|
12
|
-
match /autovoice (on|off)
|
12
|
+
match /autovoice (on|off)$/
|
13
13
|
|
14
14
|
def listen(m)
|
15
15
|
unless m.user.nick == bot.nick
|
@@ -28,13 +28,10 @@ bot = Cinch::Bot.new do
|
|
28
28
|
configure do |c|
|
29
29
|
c.nick = "cinch_autovoice"
|
30
30
|
c.server = "irc.freenode.org"
|
31
|
+
c.channels = ["#cinch-bots"]
|
31
32
|
c.verbose = true
|
32
33
|
c.plugins.plugins = [Autovoice]
|
33
34
|
end
|
34
|
-
|
35
|
-
on :connect do
|
36
|
-
bot.join "#cinch"
|
37
|
-
end
|
38
35
|
end
|
39
36
|
|
40
37
|
bot.start
|
data/examples/plugins/google.rb
CHANGED
@@ -14,10 +14,9 @@ class Google
|
|
14
14
|
title = res.text
|
15
15
|
link = res.at('a')[:href]
|
16
16
|
desc = res.at("./following::div").children.first.text
|
17
|
+
CGI.unescape_html "#{title} - #{desc} (#{link})"
|
17
18
|
rescue
|
18
19
|
"No results found"
|
19
|
-
else
|
20
|
-
CGI.unescape_html "#{title} - #{desc} (#{link})"
|
21
20
|
end
|
22
21
|
|
23
22
|
def execute(m, query)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'cinch'
|
3
|
+
|
4
|
+
class HooksDemo
|
5
|
+
include Cinch::Plugin
|
6
|
+
|
7
|
+
hook :pre, method: :generate_random_number
|
8
|
+
def generate_random_number(m)
|
9
|
+
# Hooks are called in the same thread as the handler and thus
|
10
|
+
# using thread local variables is possible.
|
11
|
+
Thread.current[:rand] = Kernel.rand
|
12
|
+
end
|
13
|
+
|
14
|
+
hook :post, method: :cheer
|
15
|
+
def cheer(m)
|
16
|
+
m.reply "Yay, I successfully ran a command…"
|
17
|
+
end
|
18
|
+
|
19
|
+
match "rand"
|
20
|
+
def execute(m)
|
21
|
+
m.reply "Random number: " + Thread.current[:rand].to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
bot = Cinch::Bot.new do
|
26
|
+
configure do |c|
|
27
|
+
c.nick = "cinch_hooks"
|
28
|
+
c.server = "irc.freenode.org"
|
29
|
+
c.channels = ["#cinch-bots"]
|
30
|
+
c.verbose = true
|
31
|
+
c.plugins.plugins = [HooksDemo]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
bot.start
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'cinch'
|
2
|
+
|
3
|
+
class DirectAddressing
|
4
|
+
include Cinch::Plugin
|
5
|
+
|
6
|
+
# Note: the lambda will be executed in the context it has been
|
7
|
+
# defined in, in this case the class DirectAddressing (and not an
|
8
|
+
# instance of said class).
|
9
|
+
#
|
10
|
+
# The reason we are using a lambda is that the bot's nick can change
|
11
|
+
# and the prefix has to be up to date.
|
12
|
+
prefix lambda{ |m| m.bot.nick + ": " }
|
13
|
+
|
14
|
+
match "hello", method: :greet
|
15
|
+
def greet(m)
|
16
|
+
m.reply "Hello to you, too."
|
17
|
+
end
|
18
|
+
|
19
|
+
match "rename", method: :rename
|
20
|
+
def rename(m)
|
21
|
+
@bot.nick += "_"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
bot = Cinch::Bot.new do
|
26
|
+
configure do |c|
|
27
|
+
c.nick = "cinch_lambda"
|
28
|
+
c.server = "irc.freenode.org"
|
29
|
+
c.channels = ["#cinch-bots"]
|
30
|
+
c.verbose = true
|
31
|
+
c.plugins.plugins = [DirectAddressing]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
bot.start
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'cinch'
|
2
|
+
|
3
|
+
class Nickchange
|
4
|
+
include Cinch::Plugin
|
5
|
+
listen_to :nick
|
6
|
+
|
7
|
+
def listen(m)
|
8
|
+
# This will send a PM to the user who changed his nick and inform
|
9
|
+
# him of his old nick.
|
10
|
+
m.reply "Your old nick was: #{m.user.last_nick}" ,true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
bot = Cinch::Bot.new do
|
15
|
+
configure do |c|
|
16
|
+
c.nick = "cinch_nickchange"
|
17
|
+
c.server = "irc.freenode.org"
|
18
|
+
c.channels = ["#cinch-bots"]
|
19
|
+
c.verbose = true
|
20
|
+
c.plugins.plugins = [Nickchange]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
bot.start
|
@@ -1,11 +1,5 @@
|
|
1
1
|
require 'cinch'
|
2
2
|
|
3
|
-
# Give this bot ops in a channel and it'll auto voice
|
4
|
-
# visitors
|
5
|
-
#
|
6
|
-
# Enable with !autovoice on
|
7
|
-
# Disable with !autovoice off
|
8
|
-
|
9
3
|
class MultiCommands
|
10
4
|
include Cinch::Plugin
|
11
5
|
match /command1 (.+)/, method: :command1
|
@@ -29,13 +23,10 @@ bot = Cinch::Bot.new do
|
|
29
23
|
configure do |c|
|
30
24
|
c.nick = "cinch_multi"
|
31
25
|
c.server = "irc.freenode.org"
|
26
|
+
c.channels = ["#cinch-bots"]
|
32
27
|
c.verbose = true
|
33
28
|
c.plugins.plugins = [MultiCommands]
|
34
29
|
end
|
35
|
-
|
36
|
-
on :connect do
|
37
|
-
bot.join "#dominikh"
|
38
|
-
end
|
39
30
|
end
|
40
31
|
|
41
32
|
bot.start
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'cinch'
|
2
|
+
|
3
|
+
class RandomNumberGenerator
|
4
|
+
def initialize(bot)
|
5
|
+
@bot = bot
|
6
|
+
end
|
7
|
+
|
8
|
+
def start
|
9
|
+
while true
|
10
|
+
sleep 5 # pretend that we are waiting for some kind of entropy
|
11
|
+
@bot.dispatch(:random_number, nil, Kernel.rand)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class DoSomethingRandom
|
17
|
+
include Cinch::Plugin
|
18
|
+
|
19
|
+
listen_to :random_number
|
20
|
+
def listen(m, number)
|
21
|
+
Channel("#cinch-bots").send "I got a random number: #{number}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
bot = Cinch::Bot.new do
|
26
|
+
configure do |c|
|
27
|
+
c.nick = "cinch_events"
|
28
|
+
c.server = "irc.freenode.org"
|
29
|
+
c.channels = ["#cinch-bots"]
|
30
|
+
c.verbose = true
|
31
|
+
c.plugins.plugins = [DoSomethingRandom]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
Thread.new { RandomNumberGenerator.new(bot).start }
|
37
|
+
bot.start
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'cinch'
|
2
|
+
|
3
|
+
class TimedPlugin
|
4
|
+
include Cinch::Plugin
|
5
|
+
|
6
|
+
timer 5, method: :timed
|
7
|
+
def timed
|
8
|
+
Channel("#cinch-bots").send "5 seconds have passed"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
bot = Cinch::Bot.new do
|
13
|
+
configure do |c|
|
14
|
+
c.nick = "cinch_timer"
|
15
|
+
c.server = "irc.freenode.org"
|
16
|
+
c.channels = ["#cinch-bots"]
|
17
|
+
c.verbose = true
|
18
|
+
c.plugins.plugins = [TimedPlugin]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
bot.start
|
data/lib/cinch.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'cinch/bot'
|
2
2
|
|
3
3
|
module Cinch
|
4
|
-
VERSION = '1.0
|
4
|
+
VERSION = '1.1.0'
|
5
5
|
|
6
6
|
# @return [String]
|
7
7
|
# @todo Handle mIRC color codes more gracefully.
|
@@ -9,4 +9,53 @@ module Cinch
|
|
9
9
|
def self.filter_string(string)
|
10
10
|
string.gsub(/[\x00-\x1f]/, '')
|
11
11
|
end
|
12
|
+
|
13
|
+
# @api private
|
14
|
+
def self.encode_incoming(string, encoding)
|
15
|
+
string = string.dup
|
16
|
+
if encoding == :irc
|
17
|
+
# If incoming text is valid UTF-8, it will be interpreted as
|
18
|
+
# such. If it fails validation, a CP1252 -> UTF-8 conversion
|
19
|
+
# is performed. This allows you to see non-ASCII from mIRC
|
20
|
+
# users (non-UTF-8) and other users sending you UTF-8.
|
21
|
+
#
|
22
|
+
# (from http://xchat.org/encoding/#hybrid)
|
23
|
+
string.force_encoding("UTF-8")
|
24
|
+
if !string.valid_encoding?
|
25
|
+
string.force_encoding("CP1252").encode!("UTF-8", {:invalid => :replace, :undef => :replace})
|
26
|
+
end
|
27
|
+
else
|
28
|
+
string.force_encoding(encoding).encode!({:invalid => :replace, :undef => :replace})
|
29
|
+
string = string.chars.select { |c| c.valid_encoding? }.join
|
30
|
+
end
|
31
|
+
|
32
|
+
return string
|
33
|
+
end
|
34
|
+
|
35
|
+
# @api private
|
36
|
+
def self.encode_outgoing(string, encoding)
|
37
|
+
string = string.dup
|
38
|
+
if encoding == :irc
|
39
|
+
# If your text contains only characters that fit inside the CP1252
|
40
|
+
# code page (aka Windows Latin-1), the entire line will be sent
|
41
|
+
# that way. mIRC users should see it correctly. XChat users who
|
42
|
+
# are using UTF-8 will also see it correctly, because it will fail
|
43
|
+
# UTF-8 validation and will be assumed to be CP1252, even by older
|
44
|
+
# XChat versions.
|
45
|
+
#
|
46
|
+
# If the text doesn't fit inside the CP1252 code page, (for eaxmple if you
|
47
|
+
# type Eastern European characters, or Russian) it will be sent as UTF-8. Only
|
48
|
+
# UTF-8 capable clients will be able to see these characters correctly
|
49
|
+
#
|
50
|
+
# (from http://xchat.org/encoding/#hybrid)
|
51
|
+
begin
|
52
|
+
string.encode!("CP1252")
|
53
|
+
rescue Encoding::UndefinedConversionError
|
54
|
+
end
|
55
|
+
else
|
56
|
+
string.encode!(encoding, {:invalid => :replace, :undef => :replace})
|
57
|
+
end
|
58
|
+
|
59
|
+
return string
|
60
|
+
end
|
12
61
|
end
|
data/lib/cinch/ban.rb
CHANGED
@@ -4,15 +4,18 @@ module Cinch
|
|
4
4
|
# @return [Mask, String]
|
5
5
|
attr_reader :mask
|
6
6
|
|
7
|
-
# @return [
|
7
|
+
# @return [User]
|
8
8
|
attr_reader :by
|
9
9
|
|
10
10
|
# @return [Time]
|
11
11
|
attr_reader :created_at
|
12
12
|
|
13
|
-
# @return [Boolean]
|
13
|
+
# @return [Boolean] whether this is an extended ban (as used by for example Freenode)
|
14
14
|
attr_reader :extended
|
15
15
|
|
16
|
+
# @param [String] mask The mask
|
17
|
+
# @param [User] by The user who created the ban
|
18
|
+
# @param [Time] at The time at which the ban was created
|
16
19
|
def initialize(mask, by, at)
|
17
20
|
@by, @created_at = by, at
|
18
21
|
if mask =~ /^\$/
|