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,45 @@
1
+ require 'ostruct'
2
+
3
+ module Flexo
4
+ module Events
5
+ # A numeric reply of some kind.
6
+ class ReplyEvent < Event
7
+ attr_reader :mynickname
8
+ attr_reader :numeric
9
+ attr_reader :text
10
+ attr_reader :reply
11
+
12
+ alias mynick mynickname
13
+ alias struct reply
14
+
15
+ def initialize(*args)
16
+ super
17
+ @mynickname = @data.params[0]
18
+ @numeric = @data.message.to_i
19
+ @text = @data.string
20
+ @is_error = @numeric >= 400
21
+ @reply = Struct.new
22
+ end
23
+
24
+ def self.[](reply)
25
+ n = (reply =~ /^\d+$/ || reply.kind_of?(Integer)) ? reply.to_i : Numerics.const_get(reply)
26
+ Numeric.new(n)
27
+ end
28
+ end
29
+
30
+ # This open structure is used to construct detailed objects about a specific
31
+ # reply, thus contains information specific to the received response.
32
+ class ReplyEvent::Struct < OpenStruct
33
+ end
34
+
35
+ # Returned by ReplyEvent.[] and used by Dispatcher to be able to subscribe to
36
+ # specific reply events.
37
+ class ReplyEvent::Numeric
38
+ attr_reader :numeric # :nodoc:
39
+
40
+ def initialize(numeric) # :nodoc:
41
+ @numeric = numeric
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ module Flexo
2
+ module Events
3
+ class TopicEvent < Event
4
+ attr_reader :channel
5
+ attr_reader :topic
6
+
7
+ def initialize(*args)
8
+ super
9
+ @channel = @data.params[0]
10
+ @topic = @data.string
11
+ end
12
+
13
+ def cleared?
14
+ @topic.empty?
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ module Flexo
2
+ module Events
3
+ # Events not implemented.
4
+ # Most likely because of lazyness.
5
+ # Maybe it's a bug in Flexo.
6
+ # Or it could be a special IRC server.
7
+ class UnknownEvent < Event
8
+ # dunno what to do with an unknown event!
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ module Flexo
2
+ # As seen in Flexo::Dispatcher.
3
+ # This is the class that's spawned to handle
4
+ # a subscription to an event.
5
+ #
6
+ # Each call to Flexo::Dispatcher.subscribe
7
+ # creates an instance of Flexo::Handler, which
8
+ # is called when the event fires.
9
+ #
10
+ # Also, to unsubscribe, the original handle-object
11
+ # is required. So don't throw it away.
12
+ class Handler
13
+ # This just stores some instance variables
14
+ def initialize(numeric, &block)
15
+ @manager = Flexo::Manager.instance
16
+ @numeric = numeric
17
+ @block = block
18
+ end
19
+
20
+ # While this function does the actuall
21
+ # calling of the block. So when this function,
22
+ # aptly named 'call', is called, the block passed
23
+ # as block when creating this instance, is executed.
24
+ def call(event)
25
+ if !@numeric || event.class == ReplyEvent && @numeric == event.numeric
26
+ @manager.thread { @block.call(event) }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,86 @@
1
+ require 'thread'
2
+
3
+ module Flexo
4
+ # Having a little log of what's going on might not
5
+ # be such a bad idea. This provides methods
6
+ # for outputting information to a logfile
7
+ class Logger
8
+ attr_reader :logfile
9
+ attr_accessor :loglevel
10
+
11
+ # Open a logfile for writing.
12
+ # We want to open the logger first, so
13
+ # instead of reading values from the config when
14
+ # initializing, we set them to defaults, and then,
15
+ # after the config has been loaded, override them.
16
+ def initialize
17
+ @logfile = File.expand_path('~/flexo.log')
18
+ @loglevel = 3
19
+ @handle = STDERR
20
+ end
21
+
22
+ # If we change the logfile, we should also reopen the handle
23
+ def logfile=(file)
24
+ file = File.expand_path(file)
25
+ if File.writable?(file)
26
+ @handle = File.open(file, 'a+')
27
+ info "Switched logfile to #{file}"
28
+ else
29
+ # Little fallback to make sure we have somewhere to log
30
+ @handle = STDERR
31
+ end
32
+ end
33
+
34
+ # Quick and dirty hack to find the textual representation
35
+ # of a log level based on the numeric value
36
+ def const_to_text(i)
37
+ hit = ""
38
+
39
+ Flexo::Constants.constants.each do |const|
40
+ if Flexo::Constants.const_get(const) == i
41
+ hit = const
42
+ end
43
+ end
44
+
45
+ return hit.split('_')[-1]
46
+ end
47
+
48
+ # Writes a line into the log
49
+ def log(level, line)
50
+ i = level
51
+ level = const_to_text(level)
52
+
53
+ if(i <= @loglevel)
54
+ Thread.new do
55
+ @handle.print "#{level.to_s.upcase} #{Time.now} #{line}\n"
56
+ @handle.flush
57
+ end
58
+ end
59
+ end
60
+
61
+ # Convenience function for debug-messages
62
+ def debug(line)
63
+ log(Flexo::Constants::LOG_DEBUG, line)
64
+ end
65
+
66
+ # Convenience for more-verbose-than-debug-messages
67
+ def debug2(line)
68
+ log(Flexo::Constants::LOG_DEBUG2, line)
69
+ end
70
+
71
+ # Convenience for information
72
+ def info(line)
73
+ log(Flexo::Constants::LOG_INFO, line)
74
+ end
75
+
76
+ # Convenience for warnings
77
+ def warn(line)
78
+ log(Flexo::Constants::LOG_WARN, line)
79
+ end
80
+
81
+ # Convenience for errors
82
+ def error(line)
83
+ log(Flexo::Constants::LOG_ERROR, line)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,152 @@
1
+ require 'thread'
2
+ require 'singleton'
3
+ require 'flexo/errors'
4
+ require 'flexo/config'
5
+ require 'flexo/constants'
6
+ require 'flexo/data'
7
+ require 'flexo/dispatcher'
8
+ require 'flexo/event'
9
+ require 'flexo/handler'
10
+ require 'flexo/logger'
11
+ require 'flexo/numerics'
12
+ require 'flexo/pluginmanager'
13
+ require 'flexo/plugin'
14
+ require 'flexo/sender'
15
+ require 'flexo/server'
16
+ require 'flexo/trigger'
17
+
18
+ module Flexo
19
+ # One class to rule them all.
20
+ # And in the darkness bind them.
21
+ #
22
+ # This singleton-class creates an
23
+ # instance of each other class that's needed
24
+ # and manages threads, mutexes and other things
25
+ # that need sharing between classes. Each
26
+ # class can just save a reference to Flexo::Manager
27
+ # and get access to everything they need.
28
+ class Manager
29
+ include Singleton
30
+
31
+ def initialize
32
+ end
33
+
34
+ # Create everything
35
+ def setup
36
+ return if @setup == true
37
+ @setup = true
38
+
39
+ @config = Flexo::Config.new
40
+ raise InvalidConfigurationException unless @config.valid?
41
+
42
+ @nickname = nil
43
+ @threadqueue = Queue.new
44
+ @threadgroup = ThreadGroup.new
45
+ @threadmax = 100
46
+ @mutex = Mutex.new
47
+ @pluginmutex = Mutex.new
48
+
49
+ @logger = Flexo::Logger.new
50
+ @logger.loglevel = @config['log.level']
51
+ @logger.logfile = @config['log.file']
52
+
53
+ @logger.debug "Creating Dispatcher"
54
+ @dispatcher = Flexo::Dispatcher.new
55
+
56
+ @logger.debug "Creating PluginManager"
57
+ @pluginmanager = Flexo::PluginManager.new(@pluginmutex)
58
+ @pluginmanager.load_plugin('control')
59
+ @pluginmanager.load_plugin('auth')
60
+
61
+ @logger.debug "Creating Sender"
62
+ @sender = Flexo::Sender.new
63
+
64
+ @logger.debug "Creating Server"
65
+ @server = Flexo::Server.new
66
+ @server.setup
67
+
68
+ @logger.debug "Created all Flexo-classes. Going to create threads"
69
+ manager = Thread.new do
70
+ loop do
71
+ if @threadqueue.size < @threadmax
72
+ @threadgroup.add(@threadqueue.shift.wakeup)
73
+ else
74
+ sleep 0.1
75
+ end
76
+ end
77
+ @threadgroup.add manager
78
+ end
79
+
80
+ @logger.info "Setting up default handlers"
81
+ dispatcher.subscribe(:PING) { |ping| ping.pong }
82
+ dispatcher.subscribe(Flexo::Events::PrivmsgEvent) { |msg|
83
+ return unless msg.ctcp?
84
+
85
+ case msg.text.split(' ')[0].upcase
86
+ when 'VERSION'
87
+ l = "\001VERSION Flexo #{Flexo::Constants::FLEXO_VERSION} "
88
+ l += "(#{Flexo::Constants::FLEXO_RELEASE})\001"
89
+ @sender.notice(msg.data.hostmask.nickname, l)
90
+ when 'PING'
91
+ arg = msg.text.split(' ')[1]
92
+ cmd = "\001PING #{arg}\001"
93
+ @sender.notice(msg.data.hostmask.nickname, cmd)
94
+ when 'QUIT'
95
+ if msg.text.split(' ')[1] == 'lololz'
96
+ @server.disconnect
97
+ exit
98
+ end
99
+ end
100
+ }
101
+
102
+ @logger.debug "Sleeping a while to allow logging into the server"
103
+ sleep 15
104
+
105
+ @logger.info "Joining channels"
106
+ @config['core.channels'].each do |channel|
107
+ @sender.join(channel[:channel], channel[:key])
108
+ end
109
+
110
+ @logger.debug "Joining server-thread"
111
+ @server.thread.join
112
+ end
113
+
114
+ # Spawns a thread to handle the block
115
+ def thread(&block)
116
+ thread = Thread.new { sleep; block.call }
117
+ @threadqueue << thread
118
+ return thread
119
+ end
120
+
121
+ def mutex
122
+ @mutex.synchronize { yield }
123
+ end
124
+
125
+ def pluginmutex
126
+ @pluginmutex.synchronize { yield }
127
+ end
128
+
129
+ def dispatcher
130
+ return @dispatcher
131
+ end
132
+
133
+ def logger
134
+ return @logger
135
+ end
136
+
137
+ def config
138
+ return @config
139
+ end
140
+
141
+ def sender
142
+ return @sender
143
+ end
144
+
145
+ def server
146
+ return @server
147
+ end
148
+
149
+ def nickname; return @nickname; end
150
+ def nickname=(nick); @nickname = nick; nick; end
151
+ end
152
+ end
@@ -0,0 +1,207 @@
1
+ module Flexo
2
+ # This module contains a long list of constants.
3
+ # It maps numeric IRC codes to their text equivalents
4
+ #
5
+ # (Everything is stolen from http://tools.ietf.org/html/rfc2812#section-5)
6
+ module Numerics
7
+ RPL_WELCOME = 001 # "Welcome to the Internet Relay Network
8
+ # <nick>!<user>@<host>"
9
+ RPL_YOURHOST = 002 # "Your host is <servername>, running version <ver>"
10
+ RPL_CREATED = 003 # "This server was created <date>"
11
+ RPL_MYINFO = 004 # "<servername> <version> <available user modes>
12
+ # <available channel modes>"
13
+ RPL_BOUNCE = 005 # "Try server <server>, port <port>"
14
+ # Sent to suggest an alternative server
15
+ # Often when the current server is full
16
+
17
+ RPL_TRACELINK = 200 # "Link <version & debug level> <destination>
18
+ # <next server> V<protocol version>
19
+ # <link uptime in seconds> <backstream sendq>
20
+ # <upstream sendq>"
21
+ RPL_TRACECONNECTING = 201 # "Try. <class> <server>"
22
+ RPL_TRACEHANDSHAKE = 202 # "H.S. <class> <server>"
23
+ RPL_TRACEUNKNOWN = 203 # "???? <class> [<client IP address in dot form>]"
24
+ RPL_TRACEOPERATOR = 204 # "Oper <class> <nick>"
25
+ RPL_TRACEUSER = 205 # "User <class> <nick>"
26
+ RPL_TRACESERVER = 206 # "Serv <class> <int>S <int>C <server>
27
+ # <nick!user|*!*>@<host|server> V<protocol version>"
28
+ RPL_TRACESERVICE = 207 # "Service <class> <name> <type> <active type>"
29
+ RPL_TRACENEWTYPE = 208 # "<newtype> 0 <client name>"
30
+ RPL_TRACECLASS = 209 # "Class <class> <count>"
31
+ RPL_TRACERECONNECT = 210 # Unused.
32
+ RPL_TRACELOG = 261 # "File <logfile> <debuglevel>"
33
+ RPL_TRACEEND = 262 # "<server name> <version & debug level> :End of TRACE"
34
+
35
+ RPL_STATSLINKINFO = 211 # "<linkname> <sendq> <sent messages>
36
+ # <sent Kbytes> <received messages>
37
+ # <received Kbytes> <time open>"
38
+ RPL_STATSCOMMANDS = 212 # "<command> <count> <byte count> <remote count>"
39
+ RPL_ENDOFSTATS = 219 # "<stats letter> :End of STATS report"
40
+ RPL_STATSUPTIME = 242 # ":server up %d days %d:%02d:%02d"
41
+ RPL_STATSOLINE = 243 # "O <hostmask> * <name>"
42
+ RPL_UMODEIS = 221 # "<user mode string>"
43
+ RPL_SERVLIST = 234 # "<name> <server> <mask> <type> <hopcount> <info>"
44
+ RPL_SERVLISTEND = 235 # "<mask> <type> :End of service listing"
45
+ RPL_LUSERCLIENT = 251 # ":There are <integer> users and <integer>
46
+ # services on <integer> servers"
47
+ RPL_LUSEROP = 252 # "<integer> :operator(s) online"
48
+ RPL_LUSERUNKNOWN = 253 # "<integer> :unknown connection(s)"
49
+ RPL_LUSERCHANNELS = 254 # "<integer> :channels formed"
50
+ RPL_LUSERME = 255 # ":I have <integer> clients and <integer> servers"
51
+ RPL_ADMINME = 256 # "<server> :Administrative info"
52
+ RPL_ADMINLOC1 = 257 # ":<admin info>"
53
+ RPL_ADMINLOC2 = 258 # ":<admin info>"
54
+ RPL_ADMINEMAIL = 259 # ":<admin info>"
55
+ RPL_TRYAGAIN = 263 # "<command> :Please wait a while and try again."
56
+
57
+ RPL_USERHOST = 302 # ":*1<reply> *( " " <reply> )"
58
+ RPL_ISON = 303 # ":*1<nick> *( " " <nick> )"
59
+ RPL_AWAY = 301 # "<nick> :<away message>"
60
+ RPL_UNAWAY = 305 # ":You are no longer marked as being away"
61
+ RPL_NOWAWAY = 306 # ":You have been marked as being away"
62
+ RPL_WHOISUSER = 311 # "<nick> <user> <host> * :<real name>"
63
+ RPL_WHOISSERVER = 312 # "<nick> <server> :<server info>"
64
+ RPL_WHOISOPERATOR = 313 # "<nick> :is an IRC operator"
65
+ RPL_WHOISIDLE = 317 # "<nick> <integer> :seconds idle"
66
+ RPL_ENDWHOIS = 318 # "<nick> :End of WHOIS list"
67
+ RPL_WHOISCHANNELS = 319 # "<nick> :*( ( "@" / "+" ) <channel> " " )"
68
+ RPL_WHOWASUSER = 314 # "<nick> <user> <host> * :<real name>"
69
+ RPL_ENDWHOWAS = 369 # "<nick> :End of WHOWAS"
70
+ RPL_LISTSTART = 321 # Obsolete. Not used.
71
+ RPL_LIST = 322 # "<channel> <# visible> :<topic>"
72
+ RPL_LISTEND = 323 # ":End of LIST"
73
+ RPL_UNIQOPIS = 325 # <channel> <nickname>"
74
+ RPL_CHANNELMODEIS = 324 # "<channel> <mode> <mode params>"
75
+ RPL_NOTOPIC = 331 # "<channel> :No topic is set"
76
+ RPL_TOPIC = 332 # "<channel> :<topic>"
77
+ RPL_INVITING = 341 # "<channel> <nick>"
78
+ RPL_SUMMONING = 342 # "<user> :Summoning user to IRC"
79
+ RPL_INVITELIST = 346 # "<channel> <invitemask>"
80
+ RPL_ENDOFINVITELIST = 347 # "<channel> :End of channel invite list"
81
+ RPL_EXCEPTLIST = 348 # "<channel> <exceptionmask>"
82
+ RPL_ENDOFEXCEPTLIST = 349 # "<channel> :End of channel exception list"
83
+ RPL_VERSION = 351 # "<version>.<debuglevel> <server> :<comments>"
84
+ RPL_WHOREPLY = 352 # "<channel> <user> <host> <server> <nick>
85
+ # ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
86
+ # :<hopcount> <real name>"
87
+ RPL_ENDOFWHO = 315 # "<name> :End of WHO list"
88
+ RPL_NAMREPLY = 353 # ( "=" / "*" / "@" ) <channel>
89
+ # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
90
+ # - "@" is used for secret channels, "*" for private
91
+ # channels, and "=" for others (public channels).
92
+ RPL_ENDOFNAMES = 366 # "<channel> :End of NAMES list"
93
+ RPL_LINKS = 364 # "<mask> <server> :<hopcount> <server info>"
94
+ RPL_ENDOFLINKS = 365 # "<mask> :End of LINKS list"
95
+ RPL_BANLIST = 367 # "<channel> <banmask>"
96
+ RPL_ENDOFBANLIST = 368 # "<channel> :End of channel ban list"
97
+ RPL_INFO = 371 # ":<string>"
98
+ RPL_ENFOFINFO = 374 # ":End of INFO list"
99
+ RPL_MOTDSTART = 375 # ":- <server> Message of the day - "
100
+ RPL_MOTD = 372 # ":- <text>"
101
+ RPL_ENDOFMOTD = 376 # ":End of MOTD command"
102
+ RPL_YOUREOPER = 381 # ":You are now an IRC operator"
103
+ RPL_REHASHING = 382 # "<config file> :Rehashing"
104
+ RPL_YOURESERVICE = 383 # "You are service <servicename>"
105
+ RPL_TIME = 391 # "<server> :<server's local time>"
106
+ RPL_USERSSTART = 392 # ":UserID Terminal Host"
107
+ RPL_USERS = 393 # ":<username> <ttyline> <hostname>"
108
+ RPL_ENDOFUSERS = 394 # ":End of users"
109
+ RPL_NOUSERS = 395 # ":Nobody logged in"
110
+
111
+ ERR_NOSUCHNICK = 401 # "<nickname> :No such nick/channel"
112
+ ERR_NOSUCHSERVER = 402 # "<server name> :No such server"
113
+ ERR_NOSUCHCHANNEL = 403 # "<channel name> :No such channel"
114
+ ERR_CANNOTSENDTOCHAN = 404 # "<channel name> :Cannot send to channel"
115
+ ERR_TOOMANYCHANNELS = 405 # "<channel name> :You have joined too many channels"
116
+ ERR_WASNOSUCHNICK = 406 # "<nickname> :There was no such nickname"
117
+ ERR_TOOMANYTARGETS = 407 # <target> :<error code> recipients. <abort message>"
118
+ # - Returned to a client which is attempting to send a
119
+ # PRIVMSG/NOTICE using the user@host destination format
120
+ # and for a user@host which has several occurrences.
121
+ # - Returned to a client which trying to send a
122
+ # PRIVMSG/NOTICE to too many recipients.
123
+ # - Returned to a client which is attempting to JOIN a safe
124
+ # channel using the shortname when there are more than one
125
+ # such channel.
126
+ ERR_NOSUCHSERVICE = 408 # "<service name> :No such service"
127
+ ERR_NOORIGIN = 409 # ":No origin specified"
128
+ # - PING or PONG message missing the
129
+ # originator parameter.
130
+ ERR_NORECIPIENT = 411 # ":No recipient given (<command>)"
131
+ ERR_NOTEXTTOSEND = 412 # ":No text to send"
132
+ ERR_NOTOPLEVEL = 413 # "<mask> :No toplevel domain specified"
133
+ ERR_WILDTOPLEVEL = 414 # "<mask> :Wildcard in toplevel domain"
134
+ ERR_BADMASK = 415 # "<mask> :Bad server/host mask"
135
+ ERR_UNKNOWNCOMMAND = 421 # "<command> :Unknown command"
136
+ ERR_NOMOTD = 422 # ":MOTD File is missing"
137
+ ERR_NOADMININFO = 423 # "<server> :No administrative info available"
138
+ ERR_FILEERROR = 424 # ":File error doing <file op> on <file>"
139
+ ERR_NONICKNAMEGIVEN = 431 # ":No nickname given"
140
+ ERR_ERRONEUSNICKNAME = 432 # "<nick> :Erroneous nickname"
141
+ ERR_NICKNAMEINUSE = 433 # "<nick> :Nickname is already in use"
142
+ ERR_NICKCOLLISION = 436 # "<nick> :Nickname collision KILL from <user>@<host>"
143
+ ERR_UNAVAILRESOURCE = 437 # "<nick/channel> :Nick/channel is temporarily unavailable"
144
+ ERR_USERNOTINCHANNEL = 441 # "<nick> <channel> :They aren't on that channel"
145
+ ERR_NOTONCHANNEL = 442 # "<channel> :You're not on that channel"
146
+ ERR_USERONCHANNEL = 443 # "<user> <channel> :is already on channel"
147
+ ERR_NOLOGIN = 444 # "<user> :User not logged in"
148
+ ERR_SUMMONDISABLED = 445 # ":SUMMON has been disabled"
149
+ ERR_USERSDISABLED = 446 # ":USERS has been disabled"
150
+ ERR_NOTREGISTERED = 451 # ":You have not registered"
151
+ ERR_NEEDMOREPARAMS = 461 # "<command> :Not enough parameters"
152
+ ERR_ALREADYREGISTRED = 462 # ":Unauthorized command (already registered)"
153
+ ERR_NOPERMFORHOST = 463 # ":Your host isn't among the privileged"
154
+ ERR_PASSWDMISMATCH = 464 # ":Password incorrect"
155
+ ERR_YOUREBANNEDCREEP = 465 # ":You are banned from this server"
156
+ ERR_YOUWILLBEBANNED = 466 # Sent by a server to inform that access will soon be denied
157
+ ERR_KEYSET = 467 # "<channel> :Channel key already set"
158
+ ERR_CHANNELISFULL = 471 # "<channel> :Cannot join channel (+l)"
159
+ ERR_UNKNOWNMODE = 472 # "<char> :is unknown mode char to me for <channel>"
160
+ ERR_INVITEONLYCHAN = 473 # "<channel> :Cannot join channel (+i)"
161
+ ERR_BANNEDFROMCHAN = 474 # "<channel> :Cannot join channel (+b)"
162
+ ERR_BADCHANNELKEY = 475 # "<channel> :Cannot join channel (+k)"
163
+ ERR_BADCHANMASK = 476 # "<channel> :Bad Channel Mask"
164
+ ERR_NOCHANMODES = 477 # "<channel> :Channel doesn't support modes"
165
+ ERR_BANLISTFULL = 478 # "<channel> <char> :Channel list is full"
166
+ ERR_NOPRIVILEGES = 481 # ":Permission Denied - You're not an IRC operator"
167
+ ERR_CHANOPPRIVSNEEDED = 482 # "<channel> :You're not channel operator"
168
+ ERR_CANTKILLSERVER = 483 # ":You can't kill a server!"
169
+ ERR_RESTRICTED = 484 # ":Your connection is restricted!"
170
+ ERR_UNIQOPPRIVSNEEDED = 485 # ":You're not the original channel operator"
171
+ ERR_NOOPERHOST = 491 # ":No O-lines for your host"
172
+ ERR_UMODEUNKNOWNFLAG = 501 # ":Unknown MODE flag"
173
+ ERR_USERSDONTMATCH = 502 # ":Cannot change mode for other users
174
+
175
+ # The following numerics are either:
176
+ # 1. no longer in use
177
+ # 2. reserved for future planned use
178
+ # 3. in current use but part of a non-generic 'feature'
179
+ # of the current IRC server
180
+ RPL_SERVICEINFO = 231
181
+ RPL_ENDOFSERVICES = 232
182
+ RPL_SERVICE = 233
183
+ RPL_NONE = 300
184
+ RPL_WHOISCHANOP = 316
185
+ RPL_KILLDONE = 361
186
+ RPL_CLOSING = 362
187
+ RPL_CLOSEEND = 363
188
+ RPL_INFOSTART = 373
189
+ RPL_MYPORTIS = 384
190
+
191
+ RPL_STATSCLINE = 213
192
+ RPL_STATSNLINE = 214
193
+ RPL_STATSILINE = 215
194
+ RPL_STATSKLINE = 216
195
+ RPL_STATSQLINE = 217
196
+ RPL_STATSYLINE = 218
197
+ RPL_STATSVLINE = 240
198
+ RPL_STATSLLINE = 241
199
+ RPL_STATSHLINE = 244
200
+ RPL_STATSSLINE = 244
201
+ RPL_STATSPING = 246
202
+ RPL_STATSBLINE = 247
203
+ RPL_STATSDLINE = 250
204
+
205
+ ERR_NOSERVICEHOST = 492
206
+ end
207
+ end
@@ -0,0 +1,69 @@
1
+ module Flexo
2
+ # Base class of all plugins.
3
+ # All plugins wanting to be loaded by
4
+ # Flexo must subclass this class or face the
5
+ # wrath of Flexo::PluginManager.
6
+ #
7
+ # If the plugin requires persistent storage,
8
+ # keeping it as a YAML-file like ~/.flexo/<plugin>.yaml
9
+ # would make Flexo::Config load it when starting, and
10
+ # making them available to the plugin through a simple
11
+ # call to @manager.config[key]. But that requires plugins
12
+ # to make their own namespace, preferably something like
13
+ # plugins.<pluginname>.key to prevent collisions.
14
+ class Plugin
15
+ def self.const_missing(const)
16
+ super unless [:NAME, :VERSION, :SUMMARY, :DEPENDS].include?(const)
17
+ end
18
+
19
+ # Variable, mutex, check.
20
+ def initialize(plugins, *args)
21
+ @manager = Flexo::Manager.instance
22
+ @plugins = plugins
23
+ @manager.logger.debug "Created #{self.class}, with #{@plugins.to_a}"
24
+ end
25
+
26
+ # Overriding method_missing to let plugins use commands
27
+ # from other plugins. If the plugin tries to use a function
28
+ # that doesn't exist, check the list of loaded plugins
29
+ # if any of those respond to the function.
30
+ def method_missing(method_name, *args, &block)
31
+ @manager.logger.debug "Method #{method_name} not found in #{self.class}"
32
+ @plugins.each do |plugin_name, plugin|
33
+ @manager.logger.debug " Checking in #{plugin_name}"
34
+ if plugin.respond_to?(method_name)
35
+ @manager.logger.debug " Found #{method_name} in #{plugin_name}"
36
+ return plugin.__send__(method_name, *args, &block)
37
+ end
38
+ end
39
+
40
+ @manager.logger.debug " Method #{method_name} not found in any loaded plugins"
41
+ super
42
+ end
43
+
44
+ # Synchronize mutex
45
+ def mutex
46
+ @manager.pluginmutex { yield }
47
+ end
48
+
49
+ # Convenience. See Flexo::Dispatcher.subscribe
50
+ def subscribe(event, &block)
51
+ @manager.dispatcher.subscribe(event, &block)
52
+ end
53
+
54
+ # Convenience. See Flexo::Dispatcher.unsubscribe
55
+ def unsubscribe(handler)
56
+ @manager.dispatcher.unsubscribe(handler)
57
+ end
58
+
59
+ # Convenience. See Flexo::Dispatcher.add_trigger
60
+ def add_trigger(pattern, &block)
61
+ @manager.dispatcher.add_trigger(pattern, &block)
62
+ end
63
+
64
+ # Convenience. See Flexo::Dispatcher.remove_trigger
65
+ def remove_trigger(trigger)
66
+ @manager.dispatcher.remove_trigger(trigger)
67
+ end
68
+ end
69
+ end