newton 0.0.1
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.
- 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
|