meimei 0.1.1

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/CHANGES ADDED
@@ -0,0 +1,7 @@
1
+ = meimei Changelog
2
+
3
+ === Version 0.1.1
4
+ * Fixed bug with plugins
5
+
6
+ === Version 0.1.0
7
+ * Initial public release
data/LICENSE ADDED
@@ -0,0 +1,31 @@
1
+ Copyright (c) 2008, Albert Yi
2
+
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+
11
+ * Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the
14
+ distribution.
15
+
16
+ * Neither the name of Albert Yi nor the
17
+ names of its contributors may be used to endorse or promote
18
+ products derived from this software without specific prior written
19
+ permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
25
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README ADDED
@@ -0,0 +1,28 @@
1
+ = meimei
2
+
3
+ == DESCRIPTION
4
+ Meimei is a simple IRC bot framework. It is designed to be easily extensible. In the interest of simplicity it only implements a subset RFC 1459, mainly those dealing with PRIVMSGs.
5
+
6
+ === Usage
7
+
8
+ require 'meimei'
9
+ client = Meimei::Client.new("meimei")
10
+ client.add_server("irc.synirc.net", 6667, "#meimei, #bonklers")
11
+ client.start()
12
+
13
+ === Plugins
14
+
15
+ # In plugins/reload.rb
16
+ define_plugin("!reload") do |msg|
17
+ load_plugins()
18
+ reply("Plugins reloaded.")
19
+ end
20
+
21
+ # In plugins/clock.rb
22
+ define_plugin("@1h") do
23
+ say("localhost", "#meimei", "One hour has passed.")
24
+ end
25
+
26
+ === Thread Safety
27
+
28
+ Meimei does not use any global or class variables, so a single client instance should be thread safe. Obviously Meimei does make use of instance variables, so using threads from within a single client may cause issues.
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # make sure our project's ./lib directory is added to the ruby search path
2
+ $: << File.join(File.dirname(__FILE__),"lib")
3
+
4
+ require 'rubygems'
5
+ require 'rake/gempackagetask'
6
+ require 'rake/clean'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/sshpublisher'
9
+
10
+ require 'meimei'
11
+
12
+ load 'tasks/setup.rb'
13
+
14
+
@@ -0,0 +1,7 @@
1
+ module ArrayExtensions
2
+ def pick_random
3
+ return self[rand(size)]
4
+ end
5
+ end
6
+
7
+ Array.__send__(:include, ArrayExtensions)
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'logger'
4
+ require 'socket'
5
+
6
+ module Meimei
7
+ class Client
8
+ # === Parameters
9
+ # - nick: The nickname your bot will use.
10
+ # - options[:username]: The username of your bot (will default to nick).
11
+ # - options[:realname]: The real name of your bot (will default to nick).
12
+ # - options[:password]: The password for your bot's account.
13
+ # - options[:plugin_dir]: An absolute path to the directory where Meimei plugins will be stored.
14
+ # - options[:log_dir]: An absolute path to where log files will be stored.
15
+ # - options[:log_level]: The log level. Can be: :error, :info, :debug.
16
+ def initialize(nick, options = {})
17
+ @servers = []
18
+ @running = false
19
+ @command_plugins = []
20
+ @timer_plugins = []
21
+ @nick = nick
22
+ @username = options[:username] || @nick
23
+ @realname = options[:realname] || @nick
24
+ @password = options[:password]
25
+ @plugin_dir = options[:plugin_dir] || "plugins"
26
+ @log_dir = options[:log_dir] || "."
27
+ @log_level = options[:log_level] || :info
28
+ @logger = Logger.new(open("#{@log_dir}/#{@nick}-#{Time.now.strftime('%Y%m%d-%H%M')}.log", "w"))
29
+ @logger.datetime_format = "%Y-%m-%d %H:%M:%S"
30
+ end
31
+
32
+ # Registers a server with the client. This method will not open a connection.
33
+ def add_server(hostname, port, autojoin)
34
+ @servers << Server.new(hostname, port, autojoin, :log_dir => @log_dir, :log_level => @log_level)
35
+ end
36
+
37
+ # Reloads all plugins.
38
+ def load_plugins
39
+ @command_plugins.clear()
40
+ @timer_plugins.clear()
41
+
42
+ # TODO: This is hackish
43
+ Dir["#{@plugin_dir}/*.rb"].sort.each do |file|
44
+ eval(File.open(file, "r").read, binding, file)
45
+ end
46
+ end
47
+
48
+ # Creates a new plugin.
49
+ #
50
+ # === Parameters
51
+ # - name: The name of the plugin. There are two special cases: (1) If the name begins with "!", then it is considered a command. For example, a plugin named "!wiki" would only be invoked if a message began with "!wiki". The message passed to the plugin will have the command stripped out. (2) If the plugin begins with "@", then it is considered a timer event. For example, a plugin named "@60" will run every 60 seconds. "@10m" will run every 10 minutes, and "@4h" will run every 4 hours.
52
+ # - options[:position]: Insert plugin at position n (lower number positions will be run first).
53
+ def define_plugin(name, options = {}, &block)
54
+ # TODO: Is there a memory leak here?
55
+
56
+ case name
57
+ when /^@(\d+)([mh])?/
58
+ interval = $1.to_i
59
+
60
+ if $2 == "m"
61
+ interval = interval * 60
62
+ elsif $2 == "h"
63
+ interval = interval * 60 * 60
64
+ end
65
+
66
+ plugins = @timer_plugins
67
+ plugin = [name, interval, Time.now, block, options]
68
+
69
+ else
70
+ plugins = @command_plugins
71
+ plugin = [name, block, options]
72
+ end
73
+
74
+ if options[:position]
75
+ plugins.insert(options[:position], plugin)
76
+ else
77
+ plugins << plugin
78
+ end
79
+
80
+ plugins.compact!
81
+ end
82
+
83
+ # Run all timer plugins.
84
+ def check_timer_plugins
85
+ @timer_plugins.each do |plugin|
86
+ begin
87
+ # Need to refer directly to the array since we'll be changing its values
88
+ # plugin[0]: name
89
+ # plugin[1]: interval
90
+ # plugin[2]: next_run_at
91
+ # plugin[3]: block
92
+ # plugin[4]: options
93
+
94
+ if plugin[2] < Time.now
95
+ plugin[3].call()
96
+ plugin[2] = Time.now + plugin[1]
97
+ end
98
+ rescue Exception => e
99
+ e.dump(@logger)
100
+ end
101
+ end
102
+ end
103
+
104
+ # Run all command plugins.
105
+ def check_command_plugins(msg)
106
+ @command_plugins.each do |name, block, options|
107
+ begin
108
+ if name.index("!") == 0
109
+ if msg =~ /^#{name}\b/
110
+ msg = msg.gsub(/!\S+\s*/, "")
111
+ block.call(msg)
112
+ end
113
+ else
114
+ block.call(msg)
115
+ end
116
+ rescue Exception => e
117
+ reply "Error: #{e.class}"
118
+ e.dump(@current_server.logger)
119
+ end
120
+ end
121
+ end
122
+
123
+ # Returns the first server that has data to be read.
124
+ def select
125
+ sockets = @servers.map {|x| x.socket}.compact
126
+ socket = Kernel.select(sockets, nil, nil, 1)
127
+
128
+ if socket == nil
129
+ return nil
130
+ else
131
+ socket = socket[0][0]
132
+ return @servers.find {|x| x.socket.__id__ == socket.__id__}
133
+ end
134
+ end
135
+
136
+ # Starts the main event loop.
137
+ def start
138
+ self.load_plugins()
139
+ @running = true
140
+
141
+ while @running
142
+ @servers.each do |server|
143
+ unless server.is_connected
144
+ if server.reconnect_delay_passed? && server.connect()
145
+ server.write("PASS #{@password}") if @password
146
+ server.write("NICK #{@nick}")
147
+ server.write("USER #{@username} hostname servername :#{@username}")
148
+ server.autojoin.each do |channel|
149
+ server.write("JOIN #{channel}")
150
+ end
151
+ end
152
+ end
153
+
154
+ self.check_timer_plugins()
155
+
156
+ @current_server = self.select()
157
+
158
+ if @current_server
159
+ msg = @current_server.read
160
+
161
+ if msg != nil
162
+ self.process_message(msg)
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ # Quits all clients and ends the event loop.
170
+ def quit(msg = nil)
171
+ @servers.each do |server|
172
+ if msg
173
+ server.write("QUIT :#{msg}")
174
+ else
175
+ server.write("QUIT")
176
+ end
177
+ end
178
+
179
+ @running = false
180
+ end
181
+
182
+ # Respond to the current server/channel/user. If {with_nick} is true, then the messager's sender's nick will be prepended to the response.
183
+ def reply(msg, with_nick = true)
184
+ if with_nick
185
+ @current_server.write("PRIVMSG #{@current_to} :#{@current_from}: #{msg}")
186
+ else
187
+ @current_server.write("PRIVMSG #{@current_to} :#{msg}")
188
+ end
189
+ end
190
+
191
+ # Send {msg} to {to} on {server}. {to} can either be a channel or someone's nick.
192
+ def say(server, to, msg)
193
+ if server.is_a?(String)
194
+ server = @servers.find {|x| x.hostname == server}
195
+ end
196
+
197
+ server.write("PRIVMSG #{to} :#{msg}")
198
+ end
199
+
200
+ # Parses a messages and dispatches as necessary.
201
+ def process_message(msg)
202
+ @current_from = nil
203
+ @current_to = nil
204
+ @current_msg = nil
205
+
206
+ case msg
207
+ when /^:(.+?)!(.+?)@(\S+) PRIVMSG (\S+) :(.+)$/
208
+ @current_from = $1
209
+ @current_to = $4
210
+ @current_msg = $5.strip
211
+
212
+ self.check_command_plugins(@current_msg)
213
+
214
+ when /^PING (.+)$/
215
+ @current_server.write("PONG #{$1}")
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,14 @@
1
+ module Meimei
2
+ module ExceptionDumpMethods
3
+ def dump(logger)
4
+ logger.info "* #{self.class} error thrown"
5
+ logger.info "* Message: #{self.message}"
6
+ logger.info "* Backtrace:"
7
+ self.backtrace.each do |line|
8
+ logger.info "** #{line}"
9
+ end
10
+ end
11
+ end
12
+
13
+ Exception.__send__(:include, ExceptionDumpMethods)
14
+ end
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'meimei/specification'
3
+ require 'meimei/version'
4
+ require 'rake'
5
+
6
+ # The Gem Specification plus some extras for meimei.
7
+ module Meimei
8
+ SPEC = Meimei::Specification.new do |spec|
9
+ spec.name = "meimei"
10
+ spec.version = Meimei::VERSION
11
+ spec.rubyforge_project = "meimei"
12
+ spec.author = "Albert Yi"
13
+ spec.email = "r888888888@gmail.com"
14
+ spec.homepage = "http://meimei.rubyforge.org/"
15
+
16
+ spec.summary = "A simple IRC bot framework."
17
+ spec.description = <<-DESC
18
+ Meimei is a simple IRC bot framework. It is designed to be easily extensible. In the interest of simplicity it only implements a subset RFC 1459, mainly those dealing with PRIVMSGs.
19
+ DESC
20
+
21
+ spec.extra_rdoc_files = FileList["[A-Z]*"]
22
+ spec.has_rdoc = true
23
+ spec.rdoc_main = "README"
24
+ spec.rdoc_options = [ "--line-numbers" , "--inline-source" ]
25
+
26
+ spec.test_files = FileList["spec/**/*.rb", "test/**/*.rb"]
27
+ spec.files = spec.test_files + spec.extra_rdoc_files +
28
+ FileList["lib/**/*.rb", "resources/**/*"]
29
+
30
+
31
+ # add dependencies
32
+ # spec.add_dependency("somegem", ">= 0.4.2")
33
+
34
+ spec.platform = Gem::Platform::RUBY
35
+
36
+ spec.local_rdoc_dir = "doc/rdoc"
37
+ spec.remote_rdoc_dir = "#{spec.name}/rdoc"
38
+ spec.local_coverage_dir = "doc/coverage"
39
+ spec.remote_coverage_dir= "#{spec.name}/coverage"
40
+
41
+ spec.remote_site_dir = "#{spec.name}/"
42
+ end
43
+ end
44
+
45
+
@@ -0,0 +1,43 @@
1
+ class PersistentHash
2
+ def initialize(file_path, commit_interval = 1)
3
+ @commit_interval = commit_interval
4
+ @commit_count = 0
5
+ @file_path = file_path
6
+ restore!
7
+ unless @hash.is_a?(Hash)
8
+ @hash = {}
9
+ commit!
10
+ end
11
+ end
12
+
13
+ def restore!
14
+ if File.exist?(@file_path)
15
+ mode = File::RDONLY
16
+ else
17
+ return
18
+ end
19
+
20
+ File.open(@file_path, mode) do |f|
21
+ @hash = Marshal.restore(f)
22
+ end
23
+ end
24
+
25
+ def commit!
26
+ File.open(@file_path, File::WRONLY | File::CREAT) do |f|
27
+ Marshal.dump(@hash, f)
28
+ end
29
+ end
30
+
31
+ def []=(key, value)
32
+ @hash[key] = value
33
+ @commit_count += 1
34
+ if @commit_count >= @commit_interval
35
+ @commit_count = 0
36
+ commit!
37
+ end
38
+ end
39
+
40
+ def method_missing(name, *params, &block)
41
+ @hash.send(name, *params, &block)
42
+ end
43
+ end
@@ -0,0 +1,107 @@
1
+ module Meimei
2
+ class Server
3
+ attr_accessor :socket, :hostname, :port, :is_connected, :logger, :autojoin
4
+
5
+ def initialize(hostname, port, autojoin, options = {})
6
+ @hostname, @port, @is_connected = hostname, port, false
7
+ @log_dir = options[:log_dir] || "."
8
+ @logger = Logger.new(open("#{@log_dir}/#{@hostname}-#{Time.now.strftime('%Y%m%d-%H%M')}.log", "w"))
9
+ @logger.datetime_format = "%Y-%m-%d %H:%M:%S"
10
+
11
+ case options[:log_level]
12
+ when :fatal
13
+ @logger.level = Logger::FATAL
14
+
15
+ when :error
16
+ @logger.level = Logger::ERROR
17
+
18
+ when :warn
19
+ @logger.level = Logger::WARN
20
+
21
+ when :info
22
+ @logger.level = Logger::INFO
23
+
24
+ when :debug
25
+ @logger.level = Logger::DEBUG
26
+
27
+ else
28
+ @logger.level = Logger::INFO
29
+ end
30
+
31
+ @autojoin = autojoin.split(/,\s*/)
32
+ end
33
+
34
+ def close
35
+ begin
36
+ @socket.close if @socket
37
+ rescue Exception => x
38
+ @is_connected = false
39
+ @logger.info "* Could not close socket"
40
+ x.dump(@logger)
41
+ end
42
+ end
43
+
44
+ def reconnect_delay_passed?
45
+ if @last_connected_at == nil
46
+ @last_connected_at = Time.now
47
+ return true
48
+ end
49
+
50
+ if Time.now - @last_connected_at > 10
51
+ @last_connected_at = Time.now
52
+ return true
53
+ else
54
+ return false
55
+ end
56
+ end
57
+
58
+ def connect
59
+ begin
60
+ self.close()
61
+ @socket = TCPSocket.open(@hostname, @port)
62
+ @is_connected = true
63
+ @logger.info "* Connected (resolved to #{@socket.peeraddr[3]})"
64
+ rescue Exception => x
65
+ @is_connected = false
66
+ @logger.info "* Connection failed"
67
+ x.dump(@logger)
68
+ end
69
+
70
+ return @is_connected
71
+ end
72
+
73
+ def read
74
+ begin
75
+ msg = @socket.gets
76
+ @logger.debug "> #{msg}"
77
+ return msg
78
+ rescue Errno::ECONNRESET => x
79
+ @logger.info "* Connection reset"
80
+ @is_connected = false
81
+ x.dump(@logger)
82
+ return nil
83
+ rescue Exception => x
84
+ @logger.info "* Read failed"
85
+ @is_connected = false
86
+ x.dump(@logger)
87
+ return nil
88
+ end
89
+ end
90
+
91
+ def write(msg)
92
+ @logger.debug "< #{msg}"
93
+
94
+ begin
95
+ @socket.write(msg + "\n")
96
+ rescue Errno::ECONNRESET => x
97
+ @logger.info "* Connection reset"
98
+ @is_connected = false
99
+ x.dump(@logger)
100
+ rescue Exception => x
101
+ @logger.info "* Write failed"
102
+ @is_connected = false
103
+ x.dump(@logger)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,128 @@
1
+ require 'rubygems'
2
+ require 'rubygems/specification'
3
+ require 'rake'
4
+
5
+ module Meimei
6
+ # Add some additional items to Gem::Specification
7
+ # A Meimei::Specification adds additional pieces of information the
8
+ # typical gem specification
9
+ class Specification
10
+
11
+ RUBYFORGE_ROOT = "/var/www/gforge-projects/"
12
+
13
+ # user that accesses remote site
14
+ attr_accessor :remote_user
15
+
16
+ # remote host, default 'rubyforge.org'
17
+ attr_accessor :remote_host
18
+
19
+ # name the rdoc main
20
+ attr_accessor :rdoc_main
21
+
22
+ # local directory in development holding the generated rdoc
23
+ # default 'doc'
24
+ attr_accessor :local_rdoc_dir
25
+
26
+ # remote directory for storing rdoc, default 'doc'
27
+ attr_accessor :remote_rdoc_dir
28
+
29
+ # local directory for coverage report
30
+ attr_accessor :local_coverage_dir
31
+
32
+ # remote directory for storing coverage reports
33
+ # This defaults to 'coverage'
34
+ attr_accessor :remote_coverage_dir
35
+
36
+ # local directory for generated website, default +site/public+
37
+ attr_accessor :local_site_dir
38
+
39
+ # remote directory relative to +remote_root+ for the website.
40
+ # website.
41
+ attr_accessor :remote_site_dir
42
+
43
+ # is a .tgz to be created?, default 'true'
44
+ attr_accessor :need_tar
45
+
46
+ # is a .zip to be created, default 'true'
47
+ attr_accessor :need_zip
48
+
49
+
50
+ def initialize
51
+ @remote_user = nil
52
+ @remote_host = "rubyforge.org"
53
+
54
+ @rdoc_main = "README"
55
+ @local_rdoc_dir = "doc"
56
+ @remote_rdoc_dir = "doc"
57
+ @local_coverage_dir = "coverage"
58
+ @remote_coverage_dir = "coverage"
59
+ @local_site_dir = "site/public"
60
+ @remote_site_dir = "."
61
+
62
+ @need_tar = true
63
+ @need_zip = true
64
+
65
+ @spec = Gem::Specification.new
66
+
67
+ yield self if block_given?
68
+
69
+ # update rdoc options to take care of the rdoc_main if it is
70
+ # there, and add a default title if one is not given
71
+ if not @spec.rdoc_options.include?("--main") then
72
+ @spec.rdoc_options.concat(["--main", rdoc_main])
73
+ end
74
+
75
+ if not @spec.rdoc_options.include?("--title") then
76
+ @spec.rdoc_options.concat(["--title","'#{name} -- #{summary}'"])
77
+ end
78
+ end
79
+
80
+ # if this gets set then it overwrites what would be the
81
+ # rubyforge default. If rubyforge project is not set then use
82
+ # name. If rubyforge project and name are set, but they are
83
+ # different then assume that name is a subproject of the
84
+ # rubyforge project
85
+ def remote_root
86
+ if rubyforge_project.nil? or
87
+ rubyforge_project == name then
88
+ return RUBYFORGE_ROOT + "#{name}/"
89
+ else
90
+ return RUBYFORGE_ROOT + "#{rubyforge_project}/#{name}/"
91
+ end
92
+ end
93
+
94
+ # rdoc files is the same as what would be generated during gem
95
+ # installation. That is, everything in the require paths plus
96
+ # the rdoc_extra_files
97
+ #
98
+ def rdoc_files
99
+ flist = extra_rdoc_files.dup
100
+ @spec.require_paths.each do |rp|
101
+ flist << FileList["#{rp}/**/*.rb"]
102
+ end
103
+ flist.flatten.uniq
104
+ end
105
+
106
+ # calculate the remote directories
107
+ def remote_root_location
108
+ "#{remote_user}@#{remote_host}:#{remote_root}"
109
+ end
110
+
111
+ def remote_rdoc_location
112
+ remote_root_location + @remote_rdoc_dir
113
+ end
114
+
115
+ def remote_coverage_location
116
+ remote_root_loation + @remote_coverage_dir
117
+ end
118
+
119
+ def remote_site_location
120
+ remote_root_location + @remote_site_dir
121
+ end
122
+
123
+ # we delegate any other calls to spec
124
+ def method_missing(method_id,*params,&block)
125
+ @spec.send method_id, *params, &block
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,18 @@
1
+ module Meimei
2
+ class Version
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ BUILD = 1
6
+
7
+ class << self
8
+ def to_a
9
+ [MAJOR, MINOR, BUILD]
10
+ end
11
+
12
+ def to_s
13
+ to_a.join(".")
14
+ end
15
+ end
16
+ end
17
+ VERSION = Version.to_s
18
+ end
data/lib/meimei.rb ADDED
@@ -0,0 +1,25 @@
1
+ module Meimei
2
+
3
+ ROOT_DIR = File.expand_path(File.join(File.dirname(__FILE__),".."))
4
+ LIB_DIR = File.join(ROOT_DIR,"lib").freeze
5
+ RESOURCE_DIR = File.join(ROOT_DIR,"resources").freeze
6
+
7
+ #
8
+ # Utility method to require all files ending in .rb in the directory
9
+ # with the same name as this file minus .rb
10
+ #
11
+ def require_all_libs_relative_to(fname)
12
+ prepend = File.basename(fname,".rb")
13
+ search_me = File.join(File.dirname(fname),prepend)
14
+
15
+ Dir.entries(search_me).each do |rb|
16
+ if File.extname(rb) == ".rb" then
17
+ require "#{prepend}/#{File.basename(rb,".rb")}"
18
+ end
19
+ end
20
+ end
21
+ module_function :require_all_libs_relative_to
22
+
23
+ end
24
+
25
+ Meimei.require_all_libs_relative_to(__FILE__)
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: meimei
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.1
7
+ date: 2008-02-06 00:00:00 -05:00
8
+ summary: A simple IRC bot framework.
9
+ require_paths:
10
+ - lib
11
+ email: r888888888@gmail.com
12
+ homepage: http://meimei.rubyforge.org/
13
+ rubyforge_project: meimei
14
+ description: Meimei is a simple IRC bot framework. It is designed to be easily extensible. In the interest of simplicity it only implements a subset RFC 1459, mainly those dealing with PRIVMSGs.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Albert Yi
31
+ files:
32
+ - CHANGES
33
+ - LICENSE
34
+ - Rakefile
35
+ - README
36
+ - lib/meimei/array_extensions.rb
37
+ - lib/meimei/client.rb
38
+ - lib/meimei/exception_dump.rb
39
+ - lib/meimei/gemspec.rb
40
+ - lib/meimei/persistent_hash.rb
41
+ - lib/meimei/server.rb
42
+ - lib/meimei/specification.rb
43
+ - lib/meimei/version.rb
44
+ - lib/meimei.rb
45
+ test_files: []
46
+
47
+ rdoc_options:
48
+ - --line-numbers
49
+ - --inline-source
50
+ - --main
51
+ - README
52
+ - --title
53
+ - "'meimei -- A simple IRC bot framework.'"
54
+ extra_rdoc_files:
55
+ - CHANGES
56
+ - LICENSE
57
+ - Rakefile
58
+ - README
59
+ executables: []
60
+
61
+ extensions: []
62
+
63
+ requirements: []
64
+
65
+ dependencies: []
66
+