Sutto-marvin 0.1.0.20081016 → 0.1.20081115
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/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:
|