newton 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +189 -0
- data/Rakefile +6 -0
- data/examples/autovoice.rb +45 -0
- data/examples/echo_bot.rb +22 -0
- data/examples/excess_flood.rb +23 -0
- data/examples/memo.rb +50 -0
- data/examples/schema.rb +41 -0
- data/examples/secure_eval.rb +46 -0
- data/lib/newton/ban.rb +40 -0
- data/lib/newton/bot.rb +361 -0
- data/lib/newton/callback.rb +24 -0
- data/lib/newton/channel.rb +362 -0
- data/lib/newton/constants.rb +123 -0
- data/lib/newton/exceptions.rb +25 -0
- data/lib/newton/formatted_logger.rb +64 -0
- data/lib/newton/irc.rb +261 -0
- data/lib/newton/isupport.rb +96 -0
- data/lib/newton/mask.rb +46 -0
- data/lib/newton/message.rb +162 -0
- data/lib/newton/message_queue.rb +62 -0
- data/lib/newton/rubyext/infinity.rb +1 -0
- data/lib/newton/rubyext/module.rb +18 -0
- data/lib/newton/rubyext/queue.rb +19 -0
- data/lib/newton/rubyext/string.rb +24 -0
- data/lib/newton/syncable.rb +55 -0
- data/lib/newton/user.rb +226 -0
- data/lib/newton.rb +1 -0
- data/test/helper.rb +60 -0
- data/test/test_commands.rb +85 -0
- data/test/test_events.rb +89 -0
- data/test/test_helpers.rb +14 -0
- data/test/test_irc.rb +38 -0
- data/test/test_message.rb +117 -0
- data/test/test_parse.rb +153 -0
- data/test/test_queue.rb +49 -0
- data/test/tests.rb +9 -0
- metadata +100 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2010 Dominik Honnef <dominikh@fork-bomb.org>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
# Newton – Adding new forces to your bots
|
2
|
+
|
3
|
+
Newton started as a fork of
|
4
|
+
[Isaac](http://github.com/ichverstehe/isaac). Nowadays, however, they
|
5
|
+
only share 1% of code and basic DSL ideas and are not compatible to each other.
|
6
|
+
|
7
|
+
## Features/characteristics
|
8
|
+
### yard documentation
|
9
|
+
|
10
|
+
Newton uses yard to document all available methods, providing detailed
|
11
|
+
information on expected parameters, return values and raised
|
12
|
+
exceptions. Where appropriate, examples are provided.
|
13
|
+
|
14
|
+
### Extensive test suite using baretest
|
15
|
+
Current status: planned
|
16
|
+
|
17
|
+
Newton will use baretest to test all aspects of Newton. Right now we
|
18
|
+
are waiting for the 0.5 release of baretest before we start writing
|
19
|
+
any tests, as the 0.5 release will bring major changes.
|
20
|
+
|
21
|
+
### Object-oriented interface
|
22
|
+
|
23
|
+
One aspect of nearly all IRC bot frameworks written in Ruby is that
|
24
|
+
they don't make use of OO but instead rely on a rather functional
|
25
|
+
approach. This might make sense for **really** simple tasks, but as
|
26
|
+
soon as you want to deal with more information or implement anything
|
27
|
+
extensive, it gets hairy.
|
28
|
+
|
29
|
+
Instead of passing around strings and having methods like
|
30
|
+
`Bot#kick(channel, user, reason)`, Newton provides proper abstraction
|
31
|
+
and object-orientation like `Channel#kick(user, reason)`.
|
32
|
+
|
33
|
+
And whenever we do have to deal with strings (e.g. when matching
|
34
|
+
commands), Newton provides `Channel(channel)` and `User(user)` helper
|
35
|
+
methods.
|
36
|
+
|
37
|
+
### Key/value store
|
38
|
+
Current status: implemented
|
39
|
+
|
40
|
+
Newton provides a basic key/value store (a Hash) to store information
|
41
|
+
across different handler invocations:
|
42
|
+
|
43
|
+
configure do |c|
|
44
|
+
…
|
45
|
+
store[:messages] = []
|
46
|
+
end
|
47
|
+
|
48
|
+
on :channel, /^store this: (.+)$/ do |message|
|
49
|
+
store[:messages] << message
|
50
|
+
end
|
51
|
+
|
52
|
+
### Threading
|
53
|
+
|
54
|
+
Unlike most other IRC bot frameworks, Newton executes handlers each in
|
55
|
+
its own thread. This prevents bots from locking up on long operations,
|
56
|
+
but also means that bots have to be written with thread-safety in
|
57
|
+
mind.
|
58
|
+
|
59
|
+
#### synchronize(key) do .. end
|
60
|
+
|
61
|
+
In order to avoid race conditions, Newton provides an easy way of
|
62
|
+
synchronizing pieces of code using `synchronize`. Each call to
|
63
|
+
synchronize expects a name and a block.
|
64
|
+
|
65
|
+
configure do |c|
|
66
|
+
…
|
67
|
+
store[:i] = 0
|
68
|
+
end
|
69
|
+
|
70
|
+
on :channel, /^start counting!/ do
|
71
|
+
synchronize(:my_counter) do
|
72
|
+
10.times do
|
73
|
+
val = store[:i]
|
74
|
+
# at this point, another thread might've incremented :i already.
|
75
|
+
# this thread wouldn't know about it, though.
|
76
|
+
store[:i] = val + 1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
channel.send store[:i].to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
On any message that equals "start counting!", our bot will increment
|
84
|
+
the value in `store[:i]` by 10, one for one.
|
85
|
+
|
86
|
+
If we did not use `synchronize`, the value might not be higher by ten
|
87
|
+
at the end of the loop because of known possible race conditions, at
|
88
|
+
least in certain implementations of Ruby.
|
89
|
+
|
90
|
+
### Constants
|
91
|
+
|
92
|
+
Nobody is able to remember all IRC numeric replies (e.g. 401 for "no
|
93
|
+
such nick"). Unfortunately, some frameworks, like for example Isaac,
|
94
|
+
require you to uses those codes when hooking on errors (`on :error,
|
95
|
+
401 do`). Those codes, however, also all have a name, e.g.
|
96
|
+
ERR_NOSUCHNICK instead of 401 – and Newton provides all of those names
|
97
|
+
as constants.
|
98
|
+
|
99
|
+
### Known signals
|
100
|
+
:ctcp
|
101
|
+
: for CTCP requests
|
102
|
+
:ping
|
103
|
+
: Whenever the **server** pings us. Do not confuse this with CTCP PING
|
104
|
+
:message
|
105
|
+
: Applies to both channel and private messages
|
106
|
+
:channel
|
107
|
+
: On channel messages
|
108
|
+
:private
|
109
|
+
: On private messages (also refered to as "queries")
|
110
|
+
:error
|
111
|
+
: On errors returned by the server
|
112
|
+
:disconnect
|
113
|
+
: gets called when the connection to the server is being interrupted
|
114
|
+
|
115
|
+
### Colorized log output
|
116
|
+
|
117
|
+
Ever had to debug a bot and couldn't be arsed reading a wall of
|
118
|
+
unformatted log output of IRC commands? Help is on its way! Newton
|
119
|
+
will by default output colorized logs if printing to a terminal –
|
120
|
+
output redirected to a file or pipe will remain without color codes.
|
121
|
+
|
122
|
+
### Customg logging
|
123
|
+
|
124
|
+
If you want to print debug statements, you can use
|
125
|
+
`FormattedLogger.debug(message)`.
|
126
|
+
|
127
|
+
### Just a note on fork()
|
128
|
+
|
129
|
+
Because of how Newton internally works, reusing a bot in a forked Ruby
|
130
|
+
process will yield unexpected results.
|
131
|
+
|
132
|
+
### Flood control
|
133
|
+
|
134
|
+
Newton tries to optimize the use of the local send queue as well as
|
135
|
+
the receive queue of the IRC server. This means that Newton will send
|
136
|
+
as many messages as possible without any delay, while at the same time
|
137
|
+
making sure that we won't be kicked for excess flood. Newton does this
|
138
|
+
by calculating the amount of messages the server is likely to have
|
139
|
+
processed and comparing that value to the maximum allowed number of
|
140
|
+
messages in the receive queue.
|
141
|
+
|
142
|
+
#### Planned feature
|
143
|
+
Note: the following is just an idea.
|
144
|
+
|
145
|
+
Furthermore, Newton sorts the send queue to make sure that important
|
146
|
+
messages (PING replies, mostly) are sent before any other messages.
|
147
|
+
Additionally, if the queue contains messages targeted at different
|
148
|
+
receivers (say, different channels), they will be sorted in a fair
|
149
|
+
way.
|
150
|
+
|
151
|
+
Example:
|
152
|
+
- we got 4 channels, "A", "B", "C" and "D"
|
153
|
+
- we send 2 messages to each channel. first 2 to "A", then 2 to "B" and so on
|
154
|
+
|
155
|
+
A naive queue would send the messages in this order:
|
156
|
+
A, A, B, B, C, C, D, D
|
157
|
+
|
158
|
+
Newton's queue, however, will order them in this way:
|
159
|
+
A, B, C, D, A, B, C, D
|
160
|
+
|
161
|
+
This will indeed mean a higher perceived delay for each individual
|
162
|
+
channel. It will, however, make sure that all channels will be treated
|
163
|
+
fair.
|
164
|
+
|
165
|
+
### ISUPPORT
|
166
|
+
|
167
|
+
Newton parses and understands
|
168
|
+
[ISUPPORT](http://www.irc.org/tech_docs/005.html) and thus provides
|
169
|
+
adequate error handling, mode parsing and checks.
|
170
|
+
|
171
|
+
#### Strictness
|
172
|
+
|
173
|
+
Newton can operate in two different modes: _strict_ and _forgiving_.
|
174
|
+
Those can be set using the _strictness_ option.
|
175
|
+
|
176
|
+
##### strict
|
177
|
+
|
178
|
+
When invoking unsupported IRC commands (e.g. KNOCK) or disobeying
|
179
|
+
limits like the max length of topics, Newton will raise an exception.
|
180
|
+
|
181
|
+
##### forgiving
|
182
|
+
|
183
|
+
Unsupported IRC commands will still be sent to the server and length
|
184
|
+
restrictions will just be ignored (in most cases, the server will
|
185
|
+
truncate overlong information)
|
186
|
+
|
187
|
+
## Examples
|
188
|
+
|
189
|
+
See _examples/_
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'newton'
|
3
|
+
|
4
|
+
# Give this bot ops in a channel and it'll auto voice
|
5
|
+
# visitors
|
6
|
+
#
|
7
|
+
# Enable with !autovoice on
|
8
|
+
# Disable with !autovoice off
|
9
|
+
|
10
|
+
bot = Newton::Bot.new do
|
11
|
+
configure do |c|
|
12
|
+
c.nick = "newton_autovoice"
|
13
|
+
c.server = "irc.freenode.net"
|
14
|
+
c.port = 6667
|
15
|
+
c.verbose = true
|
16
|
+
|
17
|
+
store[:autovoice] = true
|
18
|
+
end
|
19
|
+
|
20
|
+
on :connect do
|
21
|
+
join "#dominikh-tests"
|
22
|
+
end
|
23
|
+
|
24
|
+
on :join do
|
25
|
+
unless user == bot # don't try to voice ourself
|
26
|
+
channel.voice(user) if store[:autovoice]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
on :message, "!autovoice on" do
|
31
|
+
if channel.opped?(user)
|
32
|
+
store[:autovoice] = true
|
33
|
+
reply "Autovoicing is now enabled"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
on :message, "!autovoice off" do
|
38
|
+
if channel.opped?(user)
|
39
|
+
store[:autovoice] = false
|
40
|
+
reply "Autovoicing is now disabled"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
bot.start
|
@@ -0,0 +1,22 @@
|
|
1
|
+
$LOAD_PATH.unshift '../lib'
|
2
|
+
|
3
|
+
require 'newton'
|
4
|
+
|
5
|
+
bot = Newton::Bot.new do
|
6
|
+
configure do |c|
|
7
|
+
c.nick = "echo_bot"
|
8
|
+
c.server = "irc.freenode.net"
|
9
|
+
c.port = 6667
|
10
|
+
c.verbose = true
|
11
|
+
end
|
12
|
+
|
13
|
+
on :connect do
|
14
|
+
join "#dominikh-tests"
|
15
|
+
end
|
16
|
+
|
17
|
+
on :message do
|
18
|
+
reply message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
bot.start
|
@@ -0,0 +1,23 @@
|
|
1
|
+
$LOAD_PATH.unshift '../lib'
|
2
|
+
require 'newton'
|
3
|
+
|
4
|
+
bot = Newton::Bot.new do
|
5
|
+
configure do |c|
|
6
|
+
c.nick = "The_Echo_Bot"
|
7
|
+
c.server = "irc.freenode.net"
|
8
|
+
c.port = 6667
|
9
|
+
c.verbose = true
|
10
|
+
end
|
11
|
+
|
12
|
+
on :connect do
|
13
|
+
join "#dominikh-tests"
|
14
|
+
end
|
15
|
+
|
16
|
+
on :channel, /flood/ do
|
17
|
+
20.times do |i|
|
18
|
+
reply "#{i}:: Let me take you down to the city for a while, just a little while, oh yes. This should never exceed, plz thx u"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
bot.start
|
data/examples/memo.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'newton'
|
3
|
+
|
4
|
+
class Memo < Struct.new(:user, :channel, :text, :time)
|
5
|
+
def to_s
|
6
|
+
"[%s] <%s/%s> %s" % [time.asctime, channel.name, user.nick, text]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
bot = Newton::Bot.new do
|
11
|
+
configure do |c|
|
12
|
+
c.nick = "newton_memo"
|
13
|
+
c.server = "irc.freenode.net"
|
14
|
+
c.port = 6667
|
15
|
+
c.verbose = true
|
16
|
+
|
17
|
+
store[:memos] = Hash.new {|h,k| h[k] = []}
|
18
|
+
end
|
19
|
+
|
20
|
+
on :connect do
|
21
|
+
join "#dominikh-tests"
|
22
|
+
end
|
23
|
+
|
24
|
+
on :message do
|
25
|
+
unless store[:memos][user].empty?
|
26
|
+
while memo = store[:memos][user].shift
|
27
|
+
user.send memo
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
on :message, /^test (.+) (.+)$/ do |a, b|
|
33
|
+
p [a, b]
|
34
|
+
end
|
35
|
+
|
36
|
+
on :message, /^!memo (\S+) (.+)$/ do |target, text|
|
37
|
+
p [target, text]
|
38
|
+
target = User(target)
|
39
|
+
if user == target
|
40
|
+
reply "You cannot leave memos for yourself.", true
|
41
|
+
elsif target == bot
|
42
|
+
reply "You cannot leave memos for me.", true
|
43
|
+
else
|
44
|
+
store[:memos][target] << Memo.new(user, channel, text, Time.now)
|
45
|
+
reply "Memo for #{target} recorded.", true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
bot.start
|
data/examples/schema.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
$LOAD_PATH.unshift '../lib'
|
2
|
+
require 'newton'
|
3
|
+
|
4
|
+
bot = Newton::Bot.new do
|
5
|
+
configure do |c|
|
6
|
+
c.nick = "SomeBot"
|
7
|
+
c.server = "irc.freenode.net"
|
8
|
+
c.port = 6667
|
9
|
+
c.realname = 'Isaac Hayes'
|
10
|
+
c.verbose = true
|
11
|
+
end
|
12
|
+
|
13
|
+
helpers do
|
14
|
+
def check(channel)
|
15
|
+
channel.send "this channel, #{channel}, is awesome!"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
on :connect do
|
20
|
+
join "#dominikh-tests"
|
21
|
+
User("asdfhaskfdhaskdfhaskdfasdf").send "foo"
|
22
|
+
end
|
23
|
+
|
24
|
+
on :private, /^t (.*)/ do |text|
|
25
|
+
reply "You said: " + text
|
26
|
+
end
|
27
|
+
|
28
|
+
on :channel, /quote/ do
|
29
|
+
reply "#{user} requested a quote: 'Smoking, a subtle form of suicide.' - Vonnegut"
|
30
|
+
end
|
31
|
+
|
32
|
+
on :channel, /status/ do
|
33
|
+
check(channel)
|
34
|
+
end
|
35
|
+
|
36
|
+
on :error, ERR_NOSUCHNICK do
|
37
|
+
debug "Ok, #{params[1]} doesn't exist."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
bot.start
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'newton'
|
3
|
+
|
4
|
+
bot = Newton::Bot.new do
|
5
|
+
configure do |c|
|
6
|
+
c.nick = "newton_eval"
|
7
|
+
c.server = "irc.freenode.net"
|
8
|
+
c.port = 6667
|
9
|
+
c.verbose = true
|
10
|
+
|
11
|
+
store[:allowed_users] = %w(DominikH)
|
12
|
+
end
|
13
|
+
|
14
|
+
helpers do
|
15
|
+
def allowed_user?(user)
|
16
|
+
# First we want to refresh the user's information (to prevent
|
17
|
+
# identity theft)…
|
18
|
+
user.whois
|
19
|
+
|
20
|
+
store[:allowed_users].include?(user.authname)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
on :connect do
|
25
|
+
join "#dominikh-tests"
|
26
|
+
end
|
27
|
+
|
28
|
+
on :message, /^!eval (.+)/ do |command|
|
29
|
+
if allowed_user?(user)
|
30
|
+
reply eval(command), true
|
31
|
+
else
|
32
|
+
reply "You are not allowed to evaluate ruby statements"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
on :message, /^allow (.+)/ do |target|
|
37
|
+
if allowed_user?(user)
|
38
|
+
store[:allowed_users] << target
|
39
|
+
reply "User added"
|
40
|
+
else
|
41
|
+
reply "You are not allowed to add new users", true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
bot.start
|
data/lib/newton/ban.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "newton/mask"
|
2
|
+
module Newton
|
3
|
+
class Ban
|
4
|
+
# @return [Mask, String]
|
5
|
+
attr_reader :mask
|
6
|
+
|
7
|
+
# @return [String]
|
8
|
+
attr_reader :by
|
9
|
+
|
10
|
+
# @return [Time]
|
11
|
+
attr_reader :created_at
|
12
|
+
|
13
|
+
# @return [Boolean]
|
14
|
+
attr_reader :extended
|
15
|
+
|
16
|
+
def initialize(mask, by, at)
|
17
|
+
@by, @created_at = by, at
|
18
|
+
if mask =~ /^\$/
|
19
|
+
@extended = true
|
20
|
+
@mask = mask
|
21
|
+
else
|
22
|
+
@extended = false
|
23
|
+
@mask = Mask.new(mask)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Boolean] true if the ban matches `user`
|
28
|
+
# @raise [Exceptions::UnsupportedFeature] Newton does not support Freenode's extended bans
|
29
|
+
def match(user)
|
30
|
+
raise UnsupportedFeature, "extended bans (freenode) are not supported yet" if @extended
|
31
|
+
@mask =~ user
|
32
|
+
end
|
33
|
+
alias_method :=~, :match
|
34
|
+
|
35
|
+
# @return [String]
|
36
|
+
def to_s
|
37
|
+
@mask.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|