PerfectlyNormal-Flexo 0.3.9

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.
@@ -0,0 +1,130 @@
1
+ require 'timeout'
2
+ require 'socket'
3
+ require 'thread'
4
+
5
+ module Flexo
6
+ # The class that does all the socket-works.
7
+ # Using Sender, it sends commands to the server, and, using Dispatcher,
8
+ # gets the responses sent back into Flexo for further processing.
9
+ class Server
10
+ attr_reader :thread
11
+ attr_reader :server
12
+ attr_reader :nickname
13
+ attr_reader :monitor
14
+
15
+ def initialize
16
+ @manager = Flexo::Manager.instance
17
+ @socket = nil
18
+ @server = nil
19
+ @monitor = nil
20
+ end
21
+
22
+ def setup
23
+ connect
24
+
25
+ @thread = @manager.thread do
26
+ begin
27
+ while line = @socket.gets
28
+ @manager.dispatcher.receive(line)
29
+ end
30
+ disconnect
31
+ rescue Errno::ETIMEDOUT
32
+ @manager.logger.warn "Connection lost. Reconnecting"
33
+ connect
34
+ end
35
+ end
36
+
37
+ # Set up a thread to check the connection once
38
+ # in a while. See bug #15
39
+ @monitor = Thread.new do
40
+ while true
41
+ sleep 45
42
+ check_connection
43
+ end
44
+ end
45
+ end
46
+
47
+ def debug(line) # :nodoc:
48
+ @manager.logger.debug(line)
49
+ end
50
+
51
+ # Called by Sender.quote. This function just writes the
52
+ # prepared line to the socket.
53
+ def quote(data)
54
+ @socket.print("#{data.chomp}\r\n")
55
+ end
56
+
57
+ # Does the connecting, looping through the defined
58
+ # servers, trying the next one if it fails.
59
+ def connect
60
+ debug "Starting to connect!"
61
+ @manager.config['core.server'].each do |server|
62
+ debug "Connecting to #{server[:host]}:#{server[:port]}"
63
+ timeout(30) do
64
+ err = 0
65
+ begin
66
+ @socket = TCPSocket.new(server[:host], server[:port])
67
+ rescue Errno::ECONNREFUSED => h
68
+ msg = "Connection to #{server[:host]} at port #{server[:port]} was refused"
69
+ err = 1
70
+ rescue Errno::EHOSTUNREACH => h
71
+ msg = "#{server[:host]} is unreachable!"
72
+ err = 1
73
+ rescue Timeout::Error => h
74
+ msg = "Connection to #{server[:host]} timed out"
75
+ err = 1
76
+ rescue Errno::EADDRNOTAVAIL => h
77
+ msg = "Unable to assign requested address"
78
+ err = 2
79
+ end
80
+
81
+ if(err != 0)
82
+ @socket = nil
83
+ @manager.logger.warn(msg)
84
+ if err > 1 # Critical error!
85
+ @manager.logger.error("Critical errors encountered!")
86
+ exit 1
87
+ end
88
+ else
89
+ @manager.logger.info("Connected to #{server[:host]}")
90
+ login
91
+ return
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def check_connection
98
+ if(@socket == nil || @socket.closed?)
99
+ @manager.logger.error("Not connected to a server. Trying again.")
100
+ connect
101
+ end
102
+ end
103
+
104
+ # This function goes through the login-process
105
+ def login
106
+ config = @manager.config
107
+
108
+ # First, we add a handler to fix things
109
+ # if our nickname is taken
110
+ nicktaken = @manager.dispatcher.subscribe(Flexo::Events::ReplyEvent) do |event|
111
+ if event.numeric == 433
112
+ nick = config['core.nick'].shift
113
+ quote "NICK #{nick}"
114
+ @manager.logger.info("Nickname taken, trying #{nick}")
115
+ @manager.nickname = nick
116
+ end
117
+ end
118
+
119
+ nick = config['core.nick'].shift
120
+ @manager.sender.nick nick
121
+ @manager.sender.user config['core.username'], 0, config['core.realname']
122
+ @manager.nickname = nick
123
+ end
124
+
125
+ # Disconnects from the server
126
+ def disconnect
127
+ @socket.close unless @socket.closed?
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,44 @@
1
+ module Flexo
2
+ # A Trigger is a special type of handler, designed
3
+ # to trigger an action when a certain Flexo::Events::PrivmsgEvent
4
+ # is receieved. The PrivmsgEvent is matched against a regexp, and,
5
+ # if it matches, the action is triggered.
6
+ class Trigger
7
+ attr_reader :pattern
8
+ attr_reader :prefix
9
+
10
+ # Creates a trigger.
11
+ #
12
+ # _pattern_ is a string or a regular expression to match
13
+ # against the message. Any group matches will be passed
14
+ # as arguments to the block
15
+ def initialize(pattern, &block)
16
+ @manager = Flexo::Manager.instance
17
+ @pattern = Regexp.new(pattern)
18
+ @prefix = @manager.config['core.trigger_prefix']
19
+ @block = block
20
+ @handler = @manager.dispatcher.subscribe(Flexo::Events::PrivmsgEvent, &self)
21
+ end
22
+
23
+ # Invokes the trigger
24
+ def call(privmsg)
25
+ if(privmsg.text[0...@prefix.size] == @prefix)
26
+ text = privmsg.text[@prefix.size..-1]
27
+ elsif(privmsg.to_me? && privmsg.text[0...@prefix.length] == @prefix)
28
+ text = privmsg.text[@prefix.size..-1]
29
+ elsif(privmsg.to_me?)
30
+ text = privmsg.text
31
+ else
32
+ return
33
+ end
34
+
35
+ m = text.match(@pattern)
36
+ @block.call(privmsg, *m[1..-1]) if m
37
+ end
38
+
39
+ # Turns the Trigger into a Proc
40
+ def to_proc
41
+ proc { |event| call(event) }
42
+ end
43
+ end
44
+ end
data/plugins/auth.rb ADDED
@@ -0,0 +1,256 @@
1
+ module Flexo
2
+ module Plugins
3
+ # A plugin for managing a list of users and their privileges
4
+ # within Flexo. The usage of this plugin allows other plugins
5
+ # to add restricted commands that require a certain access level.
6
+ #
7
+ # There's no way to "log in", but each time a user tries to run
8
+ # a restricted command, their current nickname and hostmask
9
+ # is matched against the database, and if found, the command
10
+ # executes.
11
+ class AuthPlugin < Flexo::Plugin
12
+ NAME = 'auth'
13
+ VERSION = '0.1.3'
14
+ SUMMARY = 'Access control list for managing users and their permissions.'
15
+ DEPENDS = ['pstore', 'ruby-digest/md5']
16
+
17
+ attr_accessor :default_level
18
+
19
+ def initialize(*args)
20
+ super
21
+ @manager.logger.debug "Loaded AuthPlugin"
22
+ @default_level = 0
23
+
24
+ add_trigger(/^user register ([a-zA-Z][a-zA-Z0-9_-]*) (\S+)$/, &method(:cmd_register))
25
+ add_trigger(/^user addmask ([a-zA-Z][a-zA-Z0-9_-]*) (\S+)$/, &method(:cmd_addmask))
26
+
27
+ add_restricted_trigger(/^user change ([a-zA-Z][a-zA-Z0-9_-]*) (\d{1,3})$/, :level => 256, &method(:cmd_changelevel))
28
+ add_restricted_trigger(/^user remove ([a-zA-Z][a-zA-Z0-9_-]*)$/, :level => 256, &method(:cmd_remove))
29
+ add_trigger(/^user lookup ([a-zA-Z][a-zA-Z0-9_-]*)$/, &method(:cmd_lookup))
30
+
31
+ pstore_open("#{@manager.config.path}/user.db")
32
+ pstore_transaction do |pstore|
33
+ pstore[:users] ||= Hash.new
34
+ end
35
+ end
36
+
37
+ # Returns the number of users currently registered
38
+ # in the database.
39
+ def usercount
40
+ pstore_transaction do |pstore|
41
+ return pstore[:users].length
42
+ end
43
+ end
44
+
45
+ # Similar to Flexo::Dispatcher.add_trigger, except this
46
+ # also takes an access level argument to determine
47
+ # whether the user is allowed to run the command or not.
48
+ def add_restricted_trigger(pattern, opts = {}, &block)
49
+ opts = { :level => 0 }.merge(opts)
50
+ add_trigger(pattern) do |privmsg,*args|
51
+ @manager.logger.debug "Trying to run a restricted method"
52
+ level = opts[:level].to_i
53
+ user = get_user_from_hostmask(privmsg.from, privmsg.data.hostmask.hostname)
54
+
55
+ if user && user[:level] < level
56
+ @manager.logger.debug " Not authorized for that"
57
+ privmsg.reply("Not authorized.", false)
58
+ elsif !user && level > 0
59
+ @manager.logger.debug " But not registered"
60
+ privmsg.reply("Need to register.", false)
61
+ else
62
+ @manager.logger.debug " And running the command!"
63
+ block.call(privmsg,*args)
64
+ end
65
+ end
66
+ end
67
+
68
+ # Finds a user in the database
69
+ # Returns nil if not found
70
+ def get_user_from_hostmask(nick, host)
71
+ @manager.logger.debug "AuthPlugin: Trying to find #{nick}"
72
+ user = nil
73
+
74
+ pstore_transaction do |pstore|
75
+ user = pstore[:users].find do |name,user|
76
+ user[:hostmasks].any? do |dbnick,dbhost|
77
+ dbnick.downcase == nick.downcase &&\
78
+ dbhost.downcase == host.downcase
79
+ end
80
+ end
81
+ end
82
+
83
+ return user[1] if user
84
+ return nil
85
+ end
86
+
87
+ private
88
+
89
+ # Convenience function for sending a privmsg to a user
90
+ def msg(target, msg)
91
+ target.reply(msg)
92
+ end
93
+
94
+ # Checks if a given username is registered.
95
+ def username_exists?(username)
96
+ found = false
97
+ @manager.logger.debug "Going to see if #{username} exists"
98
+ pstore_transaction do |pstore|
99
+ pstore[:users].keys.any? { |dbname|
100
+ @manager.logger.debug " Checking #{dbname} against #{username}"
101
+ found = (dbname.downcase == username.downcase)
102
+ }
103
+ end
104
+
105
+ @manager.logger.debug "Found == #{found}"
106
+ return found
107
+ end
108
+
109
+ # Checks if a given hostmask is registered.
110
+ def hostmask_exists?(nick, mask)
111
+ @manager.logger.debug "Auth: Host: Checking if #{nick} and #{mask} exists"
112
+ found = false
113
+
114
+ pstore_transaction do |pstore|
115
+ pstore[:users].any? do |name,user|
116
+ user[:hostmasks].any? do |dbnick,dbmask|
117
+ found = true if (dbnick.downcase == nick.downcase && dbmask.downcase == mask.downcase)
118
+ end
119
+ end
120
+ end
121
+
122
+ @manager.logger.debug "Auth: Host: Returning #{found}"
123
+ return found
124
+ end
125
+
126
+ # Returns true if the username/password combination
127
+ # matches, and false if it does not.
128
+ def is_correct?(user, pass)
129
+ c = pstore_transaction do |pstore|
130
+ pstore[:users][user.downcase][:password] == Digest::MD5.hexdigest(pass).to_s
131
+ end
132
+
133
+ return c
134
+ end
135
+
136
+ # Creates a new user in the database
137
+ # Not meant to be called directly, but to be used
138
+ # from a running Flexo-instance, using the register-command.
139
+ def register(user, pass, nick, host, level)
140
+ user.downcase!
141
+ tmpuser = {
142
+ :username => user,
143
+ :password => Digest::MD5.hexdigest(pass).to_s,
144
+ :hostmasks => [[nick, host]],
145
+ :level => level.to_i
146
+ }
147
+
148
+ pstore_transaction do |pstore|
149
+ pstore[:users][user] = tmpuser
150
+ end
151
+ end
152
+
153
+ # Adds a nickname/hostmask combo to a given user
154
+ def addmask(user, nick, mask)
155
+ pstore_transaction do |pstore|
156
+ pstore[:users][user.downcase][:hostmasks] << [nick, mask]
157
+ end
158
+ end
159
+
160
+ # Triggered when a user tries to register via a privmsg
161
+ def cmd_register(privmsg, username, password)
162
+ @manager.logger.debug "AuthPlugin: Going to register #{username}"
163
+ p = privmsg
164
+ return notify_cannot_register_in_public(p) if privmsg.to_channel?
165
+ return notify_username_taken(p) if username_exists?(username)
166
+ return notify_hostmask_taken(p) if hostmask_exists?(privmsg.from, privmsg.data.mask.host)
167
+
168
+ master = (usercount == 0 ? true : false)
169
+ register(username, password, privmsg.sender, privmsg.data.hostmask.hostname, (master ? 256 : @default_level.to_i))
170
+
171
+ @manager.logger.debug "AuthPlugin: Successfully registered #{username}"
172
+ privmsg.reply("You have been successfully registered.", false)
173
+ privmsg.reply("You have been added as a master!", false) if master
174
+ end
175
+
176
+ # Triggered when a user wants to add a new hostmask to themselves
177
+ def cmd_addmask(privmsg, username, password)
178
+ @manager.logger.debug "AuthPlugin: Adding a mask to #{username}"
179
+ p = privmsg
180
+ return notify_cannot_register_in_public(p) if privmsg.to_channel?
181
+ return notify_incorrect_information(p) if (!username_exists?(username) || !is_correct?(username, password))
182
+ return notify_hostmask_taken(p) if hostmask_exists?(privmsg.sender, privmsg.data.hostmask.hostname)
183
+
184
+ addmask(username, privmsg.from, privmsg.data.hostmask.hostname)
185
+ msg(privmsg, "Successfully added the new mask to your account")
186
+ end
187
+
188
+ # Triggers when an admin want to change the level of a user
189
+ def cmd_changelevel(privmsg, username, level)
190
+ return notify_user_not_found(privmsg) if !username_exists?(username)
191
+
192
+ pstore_transaction do |pstore|
193
+ pstore[:users][username.downcase][:level] = level.to_i
194
+ end
195
+
196
+ msg(privmsg, "Successfully changed access level of user")
197
+ end
198
+
199
+ # Triggers when an admin deletes a user
200
+ def cmd_remove(privmsg, username)
201
+ @manager.logger.debug "AuthPlugin: Going to remove #{username}"
202
+ return notify_user_not_found(privmsg) if !username_exists?(username)
203
+
204
+ pstore_transaction do |pstore|
205
+ pstore[:users].delete(username.downcase)
206
+ end
207
+
208
+ privmsg.reply("Successfully deleted user")
209
+ @manager.logger.debug "AuthPlugin: Removed #{username}"
210
+ end
211
+
212
+ # Triggered when a user wants to lookup another user
213
+ def cmd_lookup(privmsg, username)
214
+ @manager.logger.debug "AuthPlugin: Going to lookup #{username}"
215
+ if !username_exists?(username)
216
+ @manager.logger.debug "AuthPlugin: #{username} not found!"
217
+ return notify_user_not_found(privmsg)
218
+ end
219
+
220
+ level = 0
221
+
222
+ pstore_transaction do |pstore|
223
+ level = pstore[:users][username.downcase][:level]
224
+ end
225
+
226
+ msg(privmsg, "#{username} has a level of #{level.to_s}")
227
+ end
228
+
229
+ # Convenience function for errors
230
+ def notify_cannot_register_in_public(privmsg)
231
+ @manager.logger.debug("Can't register in public!")
232
+ privmsg.reply "You cannot perform this in public! Try with a privmsg"
233
+ end
234
+
235
+ def notify_username_taken(privmsg)
236
+ @manager.logger.debug("Username taken!")
237
+ privmsg.reply "That username is already taken"
238
+ end
239
+
240
+ def notify_hostmask_taken(privmsg)
241
+ @manager.logger.debug("Hostnake taken!")
242
+ privmsg.reply "That hostmask is already registered to a different user."
243
+ end
244
+
245
+ def notify_incorrect_information(privmsg)
246
+ @manager.logger.debug("Incorrect information!")
247
+ privmsg.reply "Username or password is incorrect."
248
+ end
249
+
250
+ def notify_user_not_found(privmsg)
251
+ @manager.logger.debug("User not found")
252
+ privmsg.reply "User not found"
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,36 @@
1
+ module Flexo
2
+ module Plugins
3
+ # Allows a user to control Flexo through commands
4
+ # on IRC. Requires Flexo::Plugins::AuthPlugin in order
5
+ # to maintain the user database and authenticate users,
6
+ # as well as for adding the restricted commands
7
+ class ControlPlugin < Flexo::Plugin
8
+ NAME = 'Control'
9
+ VERSION = '0.1.0'
10
+ SUMMARY = 'Control a Flexo instance through IRC'
11
+ DEPENDS = ['auth']
12
+
13
+ def initialize(*args)
14
+ super
15
+
16
+ @manager.logger.debug("ControlPlugin started")
17
+ add_trigger(/^ctrl quit$/, &method(:cmd_quit))
18
+ add_trigger(/^ctrl realquit$/, &method(:cmd_realquit))
19
+ add_trigger(/^ctrl join (#\S+)$/, &method(:cmd_join))
20
+ end
21
+
22
+ def cmd_join(privmsg, channel)
23
+ @manager.sender.join(channel)
24
+ end
25
+
26
+ def cmd_quit(privmsg)
27
+ @manager.logger.debug("Trying to quit")
28
+ privmsg.reply("No!")
29
+ end
30
+
31
+ def cmd_realquit(privmsg)
32
+ @manager.sender.quit("Leaving!")
33
+ end
34
+ end
35
+ end
36
+ end
data/plugins/dbus.rb ADDED
@@ -0,0 +1,39 @@
1
+ module Flexo
2
+ module Plugins
3
+ class DBusPlugin < Flexo::Plugin
4
+ NAME = "DBus"
5
+ VERSION = "0.1.0"
6
+ SUMMARY = "Adds the possibility for remote-controlling Flexo via DBus"
7
+ DEPENDS = ['ruby-dbus']
8
+
9
+ def initialize(*args)
10
+ super
11
+
12
+ @bus = DBus::SessionBus.instance
13
+ @service = @bus.request_service("org.northblue.Flexo.service")
14
+ @dbusflexo = Flexo::Plugins::DBusPlugin::Flexo.new(@manager,
15
+ "/org/northblue/Flexo/instance")
16
+ @service.export(@dbusflexo)
17
+
18
+ Thread.new do
19
+ main = DBus::Main.new
20
+ main << @bus
21
+ main.run
22
+ end
23
+ end
24
+ end
25
+
26
+ class DBusPlugin::Flexo < ::DBus::Object
27
+ def initialize(path, manager)
28
+ super path
29
+ @manager = manager
30
+ end
31
+
32
+ dbus_interface "org.northblue.Flexo.interface" do
33
+ dbus_method :flexo_get_nick do
34
+ puts "My nick is #{@manager.nickname}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
data/plugins/pstore.rb ADDED
@@ -0,0 +1,34 @@
1
+ module Flexo
2
+ module Plugins
3
+ class PStorePlugin < Flexo::Plugin
4
+ NAME = 'pstore'
5
+ VERSION = '0.1.0'
6
+ SUMMARY = 'A plugin that provides permanent storage for other plugins'
7
+ DEPENDS = ['ruby-pstore']
8
+
9
+ def initialize(*args)
10
+ super
11
+ @pstore = nil
12
+ @manager = Flexo::Manager.instance
13
+ end
14
+
15
+ # Opens a database.
16
+ def pstore_open(file)
17
+ begin
18
+ @pstore = PStore.new(file)
19
+ rescue
20
+ raise PluginError("PStore: Unable to open database")
21
+ end
22
+ end
23
+
24
+ # Convenience function for utilizing the mutex, creating
25
+ # a transaction on our PStore-object, and running a code
26
+ # block against it.
27
+ def pstore_transaction(&block)
28
+ @manager.logger.debug "PStore: Starting transaction"
29
+ mutex { @pstore.transaction(&block) }
30
+ @manager.logger.debug "PStore: Transaction complete"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,63 @@
1
+ begin
2
+ require 'svn/info'
3
+ rescue LoadError
4
+ raise PluginDependencyError, "Ruby-bindings for Subversion not found"
5
+ end
6
+
7
+ module Flexo
8
+ module Plugins
9
+ # SVN Lookup -- Registers a command to view commit
10
+ # messages for a subversion repository.
11
+ # Requires Flexo to be running at the same machine
12
+ # as the repositories is hosted, since, for now,
13
+ # this plugin requires read-access to the filesystem.
14
+ class SvnLookupPlugin < Flexo::Plugin
15
+ NAME = 'SvnLookup'
16
+ VERSION = '0.1.0'
17
+ SUMMARY = 'Replies to !svn <project> [<revision>] with information about the commit'
18
+ DEPENDS = []
19
+
20
+ def initialize(*args)
21
+ super
22
+ @manager.config['plugins.svn.repositories'] ||= '/var/svn/repositories'
23
+ @path = @manager.config['plugins.svn.repositories']
24
+
25
+ add_trigger(/^svn (\S+) (\d+)?/, &method(:cmd_lookup))
26
+ end
27
+
28
+ private
29
+
30
+ def lookup(privmsg, project, revision = 'latest')
31
+ @manager.logger.debug "Requested information for #{project} (#{revision})"
32
+ begin
33
+ if revision == 'latest'
34
+ revision = Svn::Repos.open("#{@path}/#{project}").fs.youngest_rev
35
+ end
36
+
37
+ @info = Svn::Info.new("#{@path}/#{project}", revision)
38
+ rescue => e
39
+ raise PluginError, "#{e.message}\n#{e.backtrace}"
40
+ end
41
+
42
+ author = @info.author
43
+ files_added = @info.added_files.length
44
+ files_updated = @info.updated_files.length
45
+ files_deleted = @info.deleted_files.length
46
+ dirs_changed = @info.changed_dirs.length
47
+
48
+ str_changedirs = (dirs_changed < 5 ? "in #{@info.changed_dirs.join(', ')}" : 'in the repository')
49
+ str_changed = []
50
+ str_changed << "#{files_added.to_s} added" if files_added > 0
51
+ str_changed << "#{files_updated.to_s} updated" if files_updated > 0
52
+ str_changed << "#{files_deleted.to_s} deleted" if files_deleted > 0
53
+ str_changed = (str_changed.length > 0 ? changes.join(', ') : '')
54
+ str_changelog = @info.log
55
+
56
+ str_full = "[#{project}] #{str_changed} #{str_changedirs}."
57
+ str_full += " r#{revision} commited by #{author}: #{str_changelog}"
58
+
59
+ privmsg.reply str_full
60
+ end
61
+ end
62
+ end
63
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: PerfectlyNormal-Flexo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.9
5
+ platform: ruby
6
+ authors:
7
+ - Per Christian B. Viken
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-10-14 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: perchr@northblue.org
18
+ executables:
19
+ - flexo
20
+ - mkflexorc
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - README
27
+ - Rakefile
28
+ - LICENSE
29
+ - flexo.gemspec
30
+ - lib/daemonize.rb
31
+ - lib/caselesshash.rb
32
+ - lib/flexo/server.rb
33
+ - lib/flexo/data.rb
34
+ - lib/flexo/errors.rb
35
+ - lib/flexo/plugin.rb
36
+ - lib/flexo/numerics.rb
37
+ - lib/flexo/handler.rb
38
+ - lib/flexo/client.rb
39
+ - lib/flexo/sender.rb
40
+ - lib/flexo/pluginmanager.rb
41
+ - lib/flexo/logger.rb
42
+ - lib/flexo/constants.rb
43
+ - lib/flexo/events/kick.rb
44
+ - lib/flexo/events/join.rb
45
+ - lib/flexo/events/privmsg.rb
46
+ - lib/flexo/events/unknown.rb
47
+ - lib/flexo/events/reply.rb
48
+ - lib/flexo/events/ping.rb
49
+ - lib/flexo/events/pong.rb
50
+ - lib/flexo/events/topic.rb
51
+ - lib/flexo/events/mode.rb
52
+ - lib/flexo/events/quit.rb
53
+ - lib/flexo/events/notice.rb
54
+ - lib/flexo/events/part.rb
55
+ - lib/flexo/events/nick.rb
56
+ - lib/flexo/config.rb
57
+ - lib/flexo/trigger.rb
58
+ - lib/flexo/manager.rb
59
+ - lib/flexo/dispatcher.rb
60
+ - lib/flexo/event.rb
61
+ - plugins/pstore.rb
62
+ - plugins/auth.rb
63
+ - plugins/control.rb
64
+ - plugins/dbus.rb
65
+ - plugins/svnlookup.rb
66
+ has_rdoc: true
67
+ homepage: http://eastblue.org/dev/flexo/
68
+ post_install_message:
69
+ rdoc_options: []
70
+
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ requirements: []
86
+
87
+ rubyforge_project:
88
+ rubygems_version: 1.2.0
89
+ signing_key:
90
+ specification_version: 2
91
+ summary: A simple, extensible IRC bot based on Flux
92
+ test_files: []
93
+