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,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