PerfectlyNormal-Flexo 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+