Sutto-marvin 0.1.0.20081016 → 0.1.20081115
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +155 -0
- data/VERSION.yml +4 -0
- data/handlers/hello_world.rb +1 -6
- data/handlers/tweet_tweet.rb +2 -0
- data/lib/marvin/abstract_client.rb +21 -60
- data/lib/marvin/base.rb +2 -2
- data/lib/marvin/core_ext.rb +6 -0
- data/lib/marvin/dispatchable.rb +94 -0
- data/lib/marvin/handler.rb +12 -0
- data/lib/marvin/irc/base_server.rb +11 -0
- data/lib/marvin/irc/client.rb +1 -3
- data/lib/marvin/irc/socket_client.rb +7 -1
- data/lib/marvin/irc.rb +4 -3
- data/lib/marvin/middle_man.rb +1 -3
- data/lib/marvin/parsers/regexp_parser.rb +2 -0
- data/lib/marvin/test_client.rb +2 -2
- data/lib/marvin/util.rb +3 -3
- data/lib/marvin.rb +2 -1
- data/script/run +9 -3
- data/spec/marvin/abstract_client_test.rb +38 -0
- data/spec/spec_helper.rb +14 -0
- metadata +20 -12
data/README.textile
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
h1. Marvin
|
2
|
+
|
3
|
+
Marvin is a simple IRC Framework for Rails suitable for building things
|
4
|
+
such as simple IRC bots. Extracted from real use - we'd originally used
|
5
|
+
a heavily modified version of MatzBot - it's been built to service a
|
6
|
+
particular need.
|
7
|
+
|
8
|
+
h2. Background
|
9
|
+
|
10
|
+
Marvin is an event driven framework in two ways - for one, it uses
|
11
|
+
EventMachine for all networking purposes - as a result, it's both
|
12
|
+
relatively stable / reliable and also powerful.
|
13
|
+
|
14
|
+
Following on from this, the irc library is event driven. At the base
|
15
|
+
level, you choose a client (By Default, Marvin::IRC::Client.) and then you register
|
16
|
+
any number of handlers. Whenever an event happens e.g. an incoming message,
|
17
|
+
a connection unbinding or event just post_init, each handler is notified
|
18
|
+
and given a small set of details about the event.
|
19
|
+
|
20
|
+
Handlers are very simple - in fact, you could get away with registering
|
21
|
+
Object.new as a handler.
|
22
|
+
|
23
|
+
To function, handlers only require one method: handle - which takes
|
24
|
+
two options. an event name (e.g. :incoming_message) and a hash
|
25
|
+
of the aforementioned attributes / details. This data can then be processed.
|
26
|
+
Alternatively, if a handler has a "handle_[event_name]" method (e.g.
|
27
|
+
handle_incoming_message), it will instead be called. Also, if client=
|
28
|
+
is implemented this will be called when the client is setup containing
|
29
|
+
a reference to said client. This is used to that the handler can
|
30
|
+
respond to actions.
|
31
|
+
|
32
|
+
Like Rack for HTTP, Marvin provides a fair amount of example
|
33
|
+
handlers for simple stuff inside IRC.
|
34
|
+
|
35
|
+
h2. Getting Started
|
36
|
+
|
37
|
+
The easiest way to get started with Marvin is by installing the Marvin gem. To
|
38
|
+
do this, make sure Github is added to your gem sources (and you are using
|
39
|
+
rubygems >= 1.2.0):
|
40
|
+
|
41
|
+
$ gem sources -a http://gems.github.com
|
42
|
+
$ sudo gem install username-marvin
|
43
|
+
|
44
|
+
Once you have installed the gem, you should have access to the "marvin" command:
|
45
|
+
|
46
|
+
$ marvin --help
|
47
|
+
|
48
|
+
You can create a new marvin folder:
|
49
|
+
|
50
|
+
$ marvin create my_marvin_project
|
51
|
+
|
52
|
+
Then simply edit your settings in the +config/settings.yml+
|
53
|
+
|
54
|
+
default:
|
55
|
+
name: "My Marvin Bot"
|
56
|
+
server: irc.freenode.net
|
57
|
+
port: 6667
|
58
|
+
channel: "#marvin-testing"
|
59
|
+
use_logging: false
|
60
|
+
datastore_location: tmp/datastore.json
|
61
|
+
development:
|
62
|
+
user: MarvinBot
|
63
|
+
name: MarvinBot
|
64
|
+
nick: MarvinBot3000
|
65
|
+
production:
|
66
|
+
deployed: false
|
67
|
+
|
68
|
+
You can use the defaults or configure it. By default your marvin bot will not
|
69
|
+
do any logging. You will need to set that up. From this point you can begin
|
70
|
+
running the bot as a daemon or as a console application:
|
71
|
+
|
72
|
+
$ ./script/run
|
73
|
+
|
74
|
+
The bot should join the specified channel and will resond to some simple
|
75
|
+
commands by default:
|
76
|
+
|
77
|
+
|<YourName> MarvinBot3000: hello
|
78
|
+
|<MarvinBot3000> YourName: Hola!
|
79
|
+
|
80
|
+
h2. Marvin::Base - A handler starting point
|
81
|
+
|
82
|
+
The first, Marvin::Base provides a base set of methods (e.g. say,
|
83
|
+
reply etc etc.) which make writing a client easier. You can simply
|
84
|
+
inherit from Marvin::Base, write some logic and then use the class
|
85
|
+
method on_event to define responses to events. The passed in meta
|
86
|
+
data for each event is then usable via options.attribute_name - an
|
87
|
+
openstruct version of the details. e.g.
|
88
|
+
|
89
|
+
class NinjaStuff < Marvin::Base
|
90
|
+
on_event :incoming_message do
|
91
|
+
do_something
|
92
|
+
end
|
93
|
+
def do_something
|
94
|
+
reply options.message # Will echo back the message
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
Or the like. Also, the halt! method can be called in any subclass to
|
99
|
+
halt the handler callback chain.
|
100
|
+
|
101
|
+
h2. Marvin::CommandHandler - Ridiculously easy Bots
|
102
|
+
|
103
|
+
With Marvin::CommandHandler, you get to define seriously
|
104
|
+
simple classes which can act as a simple bot. It takes
|
105
|
+
great inspiration from "MatzBot":http://github.com/defunkt/matzbot/tree/master
|
106
|
+
which was actually one of the main inspirations for
|
107
|
+
creating marvin.
|
108
|
+
|
109
|
+
To write a CommandHandler, you simply create a subclass
|
110
|
+
(ala ActiveRecord::Base), define a few methods and then
|
111
|
+
just use the "exposes" class method. e.g.
|
112
|
+
|
113
|
+
class MySecondExample < Marvin::CommandHandler
|
114
|
+
exposes :hello
|
115
|
+
def hello(data)
|
116
|
+
reply "Hello!"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
Where data is an array of parameters. exposed methods will be called
|
121
|
+
when they match the following pattern:
|
122
|
+
|
123
|
+
Botname: <exposed-method> <space-seperated-list-meaning-data>
|
124
|
+
|
125
|
+
h2. Marvin::MiddleMan - Introducing middleware
|
126
|
+
|
127
|
+
Marvin::MiddleMan lets you insert middleware between handlers
|
128
|
+
and you're client - letting you do things such as translating
|
129
|
+
all messages on the fly. It's build to be extensible and is
|
130
|
+
relatively simple to use. On any Marvin::Base subclass (baring
|
131
|
+
the MiddleMan itself), you can simple use the normal methods
|
132
|
+
of registering a handler with one exception - you now pass
|
133
|
+
one argument, the class reference to your middleman class.
|
134
|
+
|
135
|
+
h2. Marvin::DataStore - A dead simple persistent hash store
|
136
|
+
|
137
|
+
Want to save data between when you stop and start your IRC
|
138
|
+
Client? With Marvin, it's really, really simple - Marvin::DataStore
|
139
|
+
offers a simple syntax for persistent data stores.
|
140
|
+
|
141
|
+
New datastores can be created with Marvin::DataStore.new("global-store-key").
|
142
|
+
From there, you have a hash to do whatever the hell you want. Just
|
143
|
+
make sure the data you store is JSON-serializable.
|
144
|
+
|
145
|
+
When you start - stop a server (via Marvin::Loader.run! and Marvin::Loader.stop!)
|
146
|
+
you're data will be loaded from and written to disk accordingly.
|
147
|
+
|
148
|
+
If you're inside a Marvin::Base subclass it's even easier. You can get a cattr_access
|
149
|
+
style accessor for free - just use the "uses_datastore" method. e.g:
|
150
|
+
|
151
|
+
class X < Marvin::Base
|
152
|
+
uses_datastore "datastore-global-key", :cattr_name
|
153
|
+
end
|
154
|
+
|
155
|
+
Then, self.cattr_name will point to the data store instance.
|
data/VERSION.yml
ADDED
data/handlers/hello_world.rb
CHANGED
@@ -2,13 +2,8 @@ class HelloWorld < Marvin::CommandHandler
|
|
2
2
|
|
3
3
|
exposes :hello
|
4
4
|
|
5
|
-
uses_datastore "hello-count", :counts
|
6
|
-
|
7
5
|
def hello(data)
|
8
|
-
|
9
|
-
self.counts[options.nick] ||= 0
|
10
|
-
self.counts[options.nick] += 1
|
11
|
-
reply "Oh hai there - This is hello ##{self.counts[options.nick]} from you!"
|
6
|
+
reply "Hola!" unless target == "#all"
|
12
7
|
end
|
13
8
|
|
14
9
|
end
|
data/handlers/tweet_tweet.rb
CHANGED
@@ -5,11 +5,12 @@ require "marvin/irc/event"
|
|
5
5
|
module Marvin
|
6
6
|
class AbstractClient
|
7
7
|
|
8
|
-
|
8
|
+
include Marvin::Dispatchable
|
9
|
+
|
10
|
+
cattr_accessor :events, :configuration, :logger, :is_setup, :connections
|
9
11
|
attr_accessor :channels, :nickname
|
10
12
|
|
11
13
|
# Set the default values for the variables
|
12
|
-
self.handlers = []
|
13
14
|
self.events = []
|
14
15
|
self.configuration = OpenStruct.new
|
15
16
|
self.configuration.channels = []
|
@@ -27,12 +28,13 @@ module Marvin
|
|
27
28
|
logger.debug "Setting the client for each handler"
|
28
29
|
self.handlers.each { |h| h.client = self if h.respond_to?(:client=) }
|
29
30
|
logger.debug "Dispatching the default :client_connected event"
|
30
|
-
|
31
|
+
dispatch :client_connected
|
31
32
|
end
|
32
33
|
|
33
34
|
def process_disconnect
|
34
35
|
self.connections.delete(self) if self.connections.include?(self)
|
35
|
-
|
36
|
+
dispatch :client_disconnected
|
37
|
+
Marvin::Loader.stop! if self.connections.blank?
|
36
38
|
end
|
37
39
|
|
38
40
|
# Sets the current class-wide settings of this IRC Client
|
@@ -64,53 +66,10 @@ module Marvin
|
|
64
66
|
|
65
67
|
## Handling all of the the actual client stuff.
|
66
68
|
|
67
|
-
# Appends a handler to the end of the handler callback
|
68
|
-
# chain. Note that they will be called in the order they
|
69
|
-
# are appended.
|
70
|
-
def self.register_handler(handler)
|
71
|
-
return if handler.blank?
|
72
|
-
self.handlers << handler
|
73
|
-
end
|
74
|
-
|
75
69
|
def receive_line(line)
|
76
|
-
|
70
|
+
dispatch :incoming_line, :line => line
|
77
71
|
event = Marvin::Settings.default_parser.parse(line)
|
78
|
-
|
79
|
-
end
|
80
|
-
|
81
|
-
# Handles the dispatch of an event and it's associated options
|
82
|
-
# / properties (defaulting to an empty hash) to both the client
|
83
|
-
# (used for things such as responding to PING) and each of the
|
84
|
-
# registered handlers.
|
85
|
-
def dispatch_event(name, opts = {})
|
86
|
-
# The full handler name is simply what is used to handle
|
87
|
-
# a single event (e.g. handle_incoming_message)
|
88
|
-
full_handler_name = "handle_#{name}"
|
89
|
-
|
90
|
-
# If the current handle_name method is defined on this
|
91
|
-
# class, we dispatch to that first. We use this to provide
|
92
|
-
# functionality such as responding to PING's and handling
|
93
|
-
# required stuff on connections.
|
94
|
-
self.send(full_handler_name, opts) if respond_to?(full_handler_name)
|
95
|
-
|
96
|
-
begin
|
97
|
-
# For each of the handlers, check first if they respond to
|
98
|
-
# the full handler name (e.g. handle_incoming_message) - calling
|
99
|
-
# that if it exists - otherwise falling back to the handle method.
|
100
|
-
# if that doesn't exist, nothing is done.
|
101
|
-
self.handlers.each do |handler|
|
102
|
-
if handler.respond_to?(full_handler_name)
|
103
|
-
handler.send(full_handler_name, opts)
|
104
|
-
elsif handler.respond_to?(:handle)
|
105
|
-
handler.handle name, opts
|
106
|
-
end
|
107
|
-
end
|
108
|
-
# Raise an exception in order to stop the flow
|
109
|
-
# of the control. Ths enables handlers to prevent
|
110
|
-
# responses from happening multiple times.
|
111
|
-
rescue HaltHandlerProcessing
|
112
|
-
logger.debug "Handler Progress halted; Continuing on."
|
113
|
-
end
|
72
|
+
dispatch(event.to_incoming_event_name, event.to_hash) unless event.nil?
|
114
73
|
end
|
115
74
|
|
116
75
|
# Default handlers
|
@@ -123,16 +82,18 @@ module Marvin
|
|
123
82
|
def handle_client_connected(opts = {})
|
124
83
|
logger.debug "About to handle post init"
|
125
84
|
# IRC Connection is establish so we send all the required commands to the server.
|
126
|
-
logger.debug "sending user command"
|
127
|
-
command :user, self.configuration.user, "0", "*", Marvin::Util.last_param(self.configuration.name)
|
128
|
-
default_nickname = self.configuration.nick || self.configuration.nicknames.shift
|
129
85
|
logger.debug "Setting default nickname"
|
86
|
+
default_nickname = self.configuration.nick || self.configuration.nicknames.shift
|
130
87
|
nick default_nickname
|
88
|
+
logger.debug "sending user command"
|
89
|
+
command :user, self.configuration.user, "0", "*", Marvin::Util.last_param(self.configuration.name)
|
131
90
|
# If a password is specified, we will attempt to message
|
132
91
|
# NickServ to identify ourselves.
|
133
92
|
say ":IDENTIFY #{self.configuration.password}", "NickServ" unless self.configuration.password.blank?
|
134
93
|
# Join the default channels
|
135
94
|
self.configuration.channels.each { |c| self.join c }
|
95
|
+
rescue Exception => e
|
96
|
+
Marvin::ExceptionTracker.log(e)
|
136
97
|
end
|
137
98
|
|
138
99
|
# The default handler for when a users nickname is taken on
|
@@ -167,7 +128,7 @@ module Marvin
|
|
167
128
|
def handle_incoming_numeric(opts = {})
|
168
129
|
code = opts[:code].to_i
|
169
130
|
args = Marvin::Util.arguments(opts[:data])
|
170
|
-
|
131
|
+
dispatch :incoming_numeric_processed, {:code => code, :data => args}
|
171
132
|
end
|
172
133
|
|
173
134
|
## General IRC Functions
|
@@ -190,14 +151,14 @@ module Marvin
|
|
190
151
|
self.channels << channel
|
191
152
|
command :JOIN, channel
|
192
153
|
logger.info "Joined channel #{channel}"
|
193
|
-
|
154
|
+
dispatch :outgoing_join, :target => channel
|
194
155
|
end
|
195
156
|
|
196
157
|
def part(channel, reason = nil)
|
197
158
|
channel = Marvin::Util.channel_name(channel)
|
198
159
|
if self.channels.include?(channel)
|
199
160
|
command :part, channel, Marvin::Util.last_param(reason)
|
200
|
-
|
161
|
+
dispatch :outgoing_part, :target => channel, :reason => reason
|
201
162
|
logger.info "Parted from room #{channel}#{reason ? " - #{reason}" : ""}"
|
202
163
|
else
|
203
164
|
logger.warn "Tried to disconnect from #{channel} - which you aren't a part of"
|
@@ -212,7 +173,7 @@ module Marvin
|
|
212
173
|
end
|
213
174
|
logger.debug "Parted from all channels, quitting"
|
214
175
|
command :quit
|
215
|
-
|
176
|
+
dispatch :quit
|
216
177
|
# Remove the connections from the pool
|
217
178
|
self.connections.delete(self)
|
218
179
|
logger.info "Quit from server"
|
@@ -221,19 +182,19 @@ module Marvin
|
|
221
182
|
def msg(target, message)
|
222
183
|
command :privmsg, target, Marvin::Util.last_param(message)
|
223
184
|
logger.info "Message sent to #{target} - #{message}"
|
224
|
-
|
185
|
+
dispatch :outgoing_message, :target => target, :message => message
|
225
186
|
end
|
226
187
|
|
227
188
|
def action(target, message)
|
228
189
|
action_text = Marvin::Util.last_param "\01ACTION #{message.strip}\01"
|
229
190
|
command :privmsg, target, action_text
|
230
|
-
|
191
|
+
dispatch :outgoing_action, :target => target, :message => message
|
231
192
|
logger.info "Action sent to #{target} - #{message}"
|
232
193
|
end
|
233
194
|
|
234
195
|
def pong(data)
|
235
196
|
command :pong, data
|
236
|
-
|
197
|
+
dispatch :outgoing_pong
|
237
198
|
logger.info "PONG sent to #{data}"
|
238
199
|
end
|
239
200
|
|
@@ -241,7 +202,7 @@ module Marvin
|
|
241
202
|
logger.info "Changing nickname to #{new_nick}"
|
242
203
|
command :nick, new_nick
|
243
204
|
self.nickname = new_nick
|
244
|
-
|
205
|
+
dispatch :outgoing_nick, :new_nick => new_nick
|
245
206
|
logger.info "Nickname changed to #{new_nick}"
|
246
207
|
end
|
247
208
|
|
data/lib/marvin/base.rb
CHANGED
@@ -97,11 +97,11 @@ module Marvin
|
|
97
97
|
|
98
98
|
# Determines whether the previous message was inside a channel.
|
99
99
|
def from_channel?
|
100
|
-
self.target && self.target[0
|
100
|
+
self.target && self.target[0] == ?#
|
101
101
|
end
|
102
102
|
|
103
103
|
def addressed?
|
104
|
-
self.from_user? || options.message.split(" ").first == "#{self.client.nickname}:"
|
104
|
+
self.from_user? || options.message.split(" ").first.downcase == "#{self.client.nickname.downcase}:"
|
105
105
|
end
|
106
106
|
|
107
107
|
def setup_defaults(options)
|
data/lib/marvin/core_ext.rb
CHANGED
@@ -0,0 +1,94 @@
|
|
1
|
+
module Marvin
|
2
|
+
# = Marvin::Dispatchable
|
3
|
+
# A Generic mixin which lets you define an object
|
4
|
+
# Which accepts handlers which can have arbitrary
|
5
|
+
# events dispatched.
|
6
|
+
# == Usage
|
7
|
+
#
|
8
|
+
# class X
|
9
|
+
# include Marvin::Dispatchable
|
10
|
+
# self.handlers << SomeHandler.new
|
11
|
+
# end
|
12
|
+
# X.new.dispatch(:name, {:args => "Values"})
|
13
|
+
#
|
14
|
+
# Will first check if SomeHandler#handle_name exists,
|
15
|
+
# calling handle_name({:args => "Values"}) if it does,
|
16
|
+
# otherwise calling SomeHandler#handle(:name, {:args => "Values"})
|
17
|
+
module Dispatchable
|
18
|
+
|
19
|
+
def self.included(parent)
|
20
|
+
parent.class_eval do
|
21
|
+
include InstanceMethods
|
22
|
+
extend ClassMethods
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module InstanceMethods
|
27
|
+
|
28
|
+
# Returns the handlers registered on this class,
|
29
|
+
# used inside +dispatch+.
|
30
|
+
def handlers
|
31
|
+
self.class.handlers
|
32
|
+
end
|
33
|
+
|
34
|
+
# Dispatch an 'event' with a given name to the handlers
|
35
|
+
# registered on the current class. Used as a nicer way of defining
|
36
|
+
# behaviours that should occur under a given set of circumstances.
|
37
|
+
# == Params
|
38
|
+
# +name+: The name of the current event
|
39
|
+
# +opts+: an optional hash of options to pass
|
40
|
+
def dispatch(name, opts = {})
|
41
|
+
# The full handler name is the method we call given it exists.
|
42
|
+
full_handler_name = :"handle_#{name.to_s.underscore}"
|
43
|
+
# First, dispatch locally if the method is defined.
|
44
|
+
if self.respond_to?(full_handler_name)
|
45
|
+
self.send(full_handler_name, opts)
|
46
|
+
end
|
47
|
+
# Iterate through all of the registered handlers,
|
48
|
+
# If there is a method named handle_<event_name>
|
49
|
+
# defined we sent that otherwise we call the handle
|
50
|
+
# method on the handler. Note that the handle method
|
51
|
+
# is the only required aspect of a handler. An improved
|
52
|
+
# version of this would likely cache the respond_to?
|
53
|
+
# call.
|
54
|
+
self.handlers.each do |handler|
|
55
|
+
if handler.respond_to?(full_handler_name)
|
56
|
+
handler.sent(full_handler_name, opts)
|
57
|
+
else
|
58
|
+
handler.handle name, opts
|
59
|
+
end
|
60
|
+
end
|
61
|
+
# If we get the HaltHandlerProcessing exception, we
|
62
|
+
# catch it and continue on our way. In essence, we
|
63
|
+
# stop the dispatch of events to the next set of the
|
64
|
+
# handlers.
|
65
|
+
rescue HaltHandlerProcessing
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
module ClassMethods
|
71
|
+
|
72
|
+
# Return an array of all registered handlers, stored in the
|
73
|
+
# class variable @@handlers. Used inside the #handlers instance
|
74
|
+
# method as well as inside things such as register_handler.
|
75
|
+
def handlers
|
76
|
+
@@handlers ||= []
|
77
|
+
end
|
78
|
+
|
79
|
+
# Assigns a new array of handlers and assigns each.
|
80
|
+
def handlers=(new_value)
|
81
|
+
@@handlers = []
|
82
|
+
new_value.to_a.each { |h| register_handler h }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Appends a handler to the list of handlers for this object.
|
86
|
+
# Handlers are called in the order they are registered.
|
87
|
+
def register_handler(handler)
|
88
|
+
self.handlers << handler unless handler.nil? || !handler.respond_to?(:handle)
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Marvin
|
2
|
+
module Handler
|
3
|
+
|
4
|
+
# Received a given +message+ with a set of default
|
5
|
+
# +opts+ (defaulting back to an empty hash), which
|
6
|
+
# will be used to perform some sort of action.
|
7
|
+
def handle(message, opts = {})
|
8
|
+
Marvin::Logger.debug "NOP handle - got message #{message.inspect}"
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
data/lib/marvin/irc/client.rb
CHANGED
@@ -57,12 +57,10 @@ module Marvin::IRC
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def post_init
|
60
|
-
super
|
61
60
|
client.process_connect
|
62
61
|
end
|
63
62
|
|
64
63
|
def unbind
|
65
|
-
super
|
66
64
|
client.process_disconnect
|
67
65
|
end
|
68
66
|
|
@@ -98,7 +96,7 @@ module Marvin::IRC
|
|
98
96
|
|
99
97
|
# Registers a callback handle that will be periodically run.
|
100
98
|
def periodically(timing, event_callback)
|
101
|
-
callback = proc { self.
|
99
|
+
callback = proc { self.dispatch event_callback.to_sym }
|
102
100
|
EventMachine::add_periodic_timer(timing, &callback)
|
103
101
|
end
|
104
102
|
|
@@ -56,7 +56,13 @@ module Marvin::IRC
|
|
56
56
|
|
57
57
|
# Registers a callback handle that will be periodically run.
|
58
58
|
def periodically(timing, event_callback)
|
59
|
-
callback = proc { self.
|
59
|
+
callback = proc { self.dispatch event_callback.to_sym }
|
60
|
+
Thread.new do
|
61
|
+
while true
|
62
|
+
callback.call
|
63
|
+
sleep timing
|
64
|
+
end
|
65
|
+
end
|
60
66
|
end
|
61
67
|
|
62
68
|
end
|
data/lib/marvin/irc.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
module Marvin
|
2
2
|
module IRC
|
3
|
-
autoload :Client,
|
4
|
-
autoload :Event,
|
5
|
-
autoload :SocketClient,
|
3
|
+
autoload :Client, 'marvin/irc/client'
|
4
|
+
autoload :Event, 'marvin/irc/event'
|
5
|
+
autoload :SocketClient, 'marvin/irc/socket_client'
|
6
6
|
autoload :AbstractServer, 'marvin/irc/abstract_server'
|
7
|
+
autoload :BaseServer, 'marvin/irc/base_server'
|
7
8
|
end
|
8
9
|
end
|
data/lib/marvin/middle_man.rb
CHANGED
@@ -85,9 +85,7 @@ module Marvin
|
|
85
85
|
private
|
86
86
|
|
87
87
|
def setup_subhandler_clients
|
88
|
-
self.subhandlers.each
|
89
|
-
sh.client = self.client if sh.respond_to?(:client=)
|
90
|
-
end
|
88
|
+
self.subhandlers.each { |sh| sh.client = self.client if sh.respond_to?(:client=) }
|
91
89
|
end
|
92
90
|
|
93
91
|
# This should probably be extracted into some sort of Util's library as
|
data/lib/marvin/test_client.rb
CHANGED
data/lib/marvin/util.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module Marvin
|
2
|
-
|
3
|
-
class << self
|
2
|
+
module Util
|
4
3
|
|
5
4
|
# Return the channel-name version of a string by
|
6
5
|
# appending "#" to the front if it doesn't already
|
@@ -25,6 +24,7 @@ module Marvin
|
|
25
24
|
end
|
26
25
|
alias lp last_param
|
27
26
|
|
28
|
-
|
27
|
+
extend self
|
28
|
+
|
29
29
|
end
|
30
30
|
end
|
data/lib/marvin.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__) # Append the current working dir to the front of the line.
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
require '
|
4
|
+
require 'active_support'
|
5
5
|
require 'marvin/core_ext'
|
6
6
|
|
7
7
|
# Make all exceptions available
|
@@ -9,6 +9,7 @@ require 'marvin/exceptions'
|
|
9
9
|
|
10
10
|
module Marvin
|
11
11
|
autoload :Util, 'marvin/util'
|
12
|
+
autoload :Dispatchable, 'marvin/dispatchable'
|
12
13
|
autoload :AbstractClient, 'marvin/abstract_client'
|
13
14
|
autoload :Base, 'marvin/base'
|
14
15
|
autoload :ClientMixin, 'marvin/client_mixin'
|
data/script/run
CHANGED
@@ -11,9 +11,15 @@ IS_DAEMON = ARGV.include?("--is-daemon")
|
|
11
11
|
# And Require Marvin.
|
12
12
|
require 'marvin'
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
["INT", "TERM"].each do |sig|
|
15
|
+
|
16
|
+
# Trap a given signal and run all
|
17
|
+
# of our callbacks etc,
|
18
|
+
trap sig do
|
19
|
+
Marvin::Loader.stop!
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
|
17
23
|
end
|
18
24
|
|
19
25
|
Marvin::Loader.run!
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
# Tests the behaviour of the AbstractClient functionality
|
4
|
+
# via a thin wrapper of the class via Marvin::TestClient.
|
5
|
+
|
6
|
+
describe "the base Marvin::TestClient functionality" do
|
7
|
+
|
8
|
+
it "should dispatch :client_connected as the first event on process_connect" do
|
9
|
+
client(true).dispatched_events.should == []
|
10
|
+
client.process_connect
|
11
|
+
client.dispatched_events.first.should == [:client_connected, {}]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should dispatch :client_connected as the first event on process_connect" do
|
15
|
+
client(true).dispatched_events.should == []
|
16
|
+
client.process_connect
|
17
|
+
Marvin::Logger.info client.outgoing_commands.inspect
|
18
|
+
client.dispatched_events[-2].first.should == :outgoing_nick
|
19
|
+
client.dispatched_events[-1].first.should == :outgoing_join
|
20
|
+
client.outgoing_commands.length.should == 3
|
21
|
+
client.outgoing_commands[0].should =~ /^USER \w+ 0 \* :\w+ \r\n$/
|
22
|
+
client.outgoing_commands[1].should =~ /^NICK \w+ \r\n$/
|
23
|
+
client.outgoing_commands[2].should =~ /^JOIN \#[A-Za-z0-9\-\_]+ \r\n$/
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should dispatch :client_disconnect on process_disconnect" do
|
27
|
+
client(true).dispatched_events.should == []
|
28
|
+
client.process_disconnect
|
29
|
+
client.dispatched_events.last.should == [:client_disconnected, {}]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should add an :incoming_line event for each incoming line" do
|
33
|
+
client(true).dispatched_events.should == []
|
34
|
+
client.receive_line "SOME RANDOM LINE THAT HAS ZERO ACTUAL USE"
|
35
|
+
client.dispatched_events.first.should == [:incoming_line, {:line => "SOME RANDOM LINE THAT HAS ZERO ACTUAL USE"}]
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bacon"
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + "/../lib/marvin"
|
5
|
+
# Now, Set everything up.
|
6
|
+
Marvin::Logger.logger = Logger.new(File.dirname(__FILE__) + "/../log/test.log")
|
7
|
+
Marvin::Settings.default_client = Marvin::TestClient
|
8
|
+
Marvin::Loader.run!
|
9
|
+
|
10
|
+
def client(force_new = false)
|
11
|
+
$test_client = Marvin::TestClient.new if force_new || $test_client.nil?
|
12
|
+
return $test_client
|
13
|
+
end
|
14
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: Sutto-marvin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.20081115
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darcy Laycock
|
@@ -9,8 +9,8 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
13
|
-
default_executable:
|
12
|
+
date: 2008-11-15 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
16
|
description: Marvin is a Ruby IRC library / framework for ultimate awesomeness and with an evented design.
|
@@ -22,6 +22,9 @@ extensions: []
|
|
22
22
|
extra_rdoc_files: []
|
23
23
|
|
24
24
|
files:
|
25
|
+
- README.textile
|
26
|
+
- VERSION.yml
|
27
|
+
- bin/marvin
|
25
28
|
- lib/marvin
|
26
29
|
- lib/marvin/abstract_client.rb
|
27
30
|
- lib/marvin/abstract_parser.rb
|
@@ -29,11 +32,14 @@ files:
|
|
29
32
|
- lib/marvin/command_handler.rb
|
30
33
|
- lib/marvin/core_ext.rb
|
31
34
|
- lib/marvin/data_store.rb
|
35
|
+
- lib/marvin/dispatchable.rb
|
32
36
|
- lib/marvin/drb_handler.rb
|
33
37
|
- lib/marvin/exception_tracker.rb
|
34
38
|
- lib/marvin/exceptions.rb
|
39
|
+
- lib/marvin/handler.rb
|
35
40
|
- lib/marvin/irc
|
36
41
|
- lib/marvin/irc/abstract_server.rb
|
42
|
+
- lib/marvin/irc/base_server.rb
|
37
43
|
- lib/marvin/irc/client.rb
|
38
44
|
- lib/marvin/irc/event.rb
|
39
45
|
- lib/marvin/irc/socket_client.rb
|
@@ -53,16 +59,18 @@ files:
|
|
53
59
|
- lib/marvin/test_client.rb
|
54
60
|
- lib/marvin/util.rb
|
55
61
|
- lib/marvin.rb
|
56
|
-
-
|
57
|
-
-
|
58
|
-
-
|
62
|
+
- spec/marvin
|
63
|
+
- spec/marvin/abstract_client_test.rb
|
64
|
+
- spec/spec_helper.rb
|
65
|
+
- script/daemon-runner
|
66
|
+
- script/run
|
59
67
|
- handlers/hello_world.rb
|
60
68
|
- handlers/logging_handler.rb
|
61
69
|
- handlers/tweet_tweet.rb
|
62
|
-
-
|
63
|
-
-
|
64
|
-
has_rdoc:
|
65
|
-
homepage: http://
|
70
|
+
- config/setup.rb
|
71
|
+
- config/settings.yml.sample
|
72
|
+
has_rdoc: false
|
73
|
+
homepage: http://blog.ninjahideout.com/
|
66
74
|
post_install_message:
|
67
75
|
rdoc_options: []
|
68
76
|
|
@@ -80,8 +88,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
88
|
- !ruby/object:Gem::Version
|
81
89
|
version: "0"
|
82
90
|
version:
|
83
|
-
requirements:
|
84
|
-
|
91
|
+
requirements: []
|
92
|
+
|
85
93
|
rubyforge_project:
|
86
94
|
rubygems_version: 1.2.0
|
87
95
|
signing_key:
|