dragoon 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ tags
6
+ gem_link
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ..gemspec
4
+ gemspec
@@ -0,0 +1,27 @@
1
+ Dragoon
2
+ ============
3
+
4
+ An IRC bot that notifies you of keyword occurences in your favorite chatrooms. You can listen to multiple channels but right now, i've only tested it on freenode.net. Support for multiple networks will be done soon.
5
+
6
+
7
+ Dependencies
8
+ -----------
9
+
10
+ - growl
11
+ - growlnotify
12
+
13
+ P.S. You can still run the program even without growl, you'll only see the keywords being highlighted and there won't be any growl notificaiton, that's all.
14
+
15
+ Usage
16
+ -----
17
+
18
+ 1. `git clone git://github.com/redgetan/dragoon.git`
19
+ 2. Create a configuration file under config/ directory and name it "config.yml". See "config.yml.example" for reference.
20
+ 3. If you have growl support, make sure it's turned on
21
+ 4. To start the program, type `./bin/dragoon`
22
+
23
+ P.S. will gemify this soon so you only have to do 'gem install dragoon', then 'dragoon'
24
+
25
+ Screenshot
26
+ ---------
27
+ http://farm8.staticflickr.com/7027/6733543233_6f354d61d0_b.jpg
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/TODO ADDED
@@ -0,0 +1,48 @@
1
+ TODO
2
+ ----------
3
+ - [x] config.yml.example
4
+ - [x] network: irc.freenode.net
5
+ - [x] channels: #ubuntu, #ruby, #ffmpeg
6
+ - [x] keywords: and, how, why, help
7
+ - [x] connect to network
8
+ - [ ] - multiple networks
9
+ - [x] login
10
+ - [x] join channels
11
+ - [x] Event Parsing
12
+ - [x] during msg events (PRIVMSG)
13
+ - [x] - parse message
14
+ - [x] - highligh keywords
15
+ - [x] - feature
16
+ - [ ] - add ignore case
17
+ - [x] - tests
18
+ - [x] - colorize channel
19
+ - [x] - feature
20
+ - [x] - tests
21
+ - [ ] channel view
22
+ - [ ] - enter a command (i.e view #ubuntu)
23
+ - [ ] - shows only messages belonging to a specific channel
24
+ - [ ] command line options
25
+ - [ ] - help
26
+ - [ ] - verbose mode (list all msgs sent to me msgs that i send to server)
27
+
28
+
29
+ Problems encountered
30
+ --------------
31
+ Cannot join certain channels
32
+ - :calvino.freenode.net 477 pacman23 ##java :Cannot join channel (+r) - you need to be identified with services
33
+
34
+ Helpful links
35
+ -------------
36
+ - http://www.irchelp.org/irchelp/rfc/
37
+ - http://www.irchelp.org/irchelp/misc/ccosmos.html (Advanced IRC Guide)
38
+ - http://www.irchelp.org/irchelp/text/rfc1459.txt
39
+ - http://www.devarticles.com/c/a/Delphi-Kylix/Building-an-IRC-Client/
40
+ - http://www.jibble.org/pircbot.php
41
+ - http://www.scribd.com/doc/28253878/EventMachine-scalable-non-blocking-i-o-in-ruby
42
+ - http://www.paperplanes.de/2011/4/25/eventmachine-how-does-it-work.html (EM vs lower level socket prog, ie select, poll)
43
+ - http://cia.vc/doc/
44
+
45
+ Existing IRC clients/bots to get ideas from
46
+ -------------------------------------------
47
+ - https://github.com/cinchrb/cinch
48
+ - https://github.com/RISCFuture/autumn
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift("#{File.expand_path(File.dirname(__FILE__))}/../lib")
4
+
5
+ require 'dragoon'
6
+
7
+ client = Dragoon::Bot.new
8
+ client.run
9
+
10
+
@@ -0,0 +1,4 @@
1
+ nickname: fenix45
2
+ networks: [ irc.freenode.net ]
3
+ channels: [ '#ubuntu', '#ruby', '#rubyonrails', '#linux', '#javascript', '#programming' ]
4
+ keywords: [ why, how, who, and ]
@@ -0,0 +1,4 @@
1
+ nickname: fenix45
2
+ networks: [ irc.freenode.net ]
3
+ channels: [ '#ubuntu', '#ruby', '#rubyonrails', '#linux', '#javascript', '#programming' ]
4
+ keywords: [ why, how, who, and ]
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "dragoon/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dragoon"
7
+ s.version = Dragoon::VERSION
8
+ s.authors = ["Reginald Tan"]
9
+ s.email = ["redge.tan@gmail.com"]
10
+ s.homepage = "https://github.com/redgetan/dragoon"
11
+ s.summary = %q{IRC bot for keyword notification }
12
+ s.description = %q{An IRC bot that alerts you whenever certain keywords appear in your favorite channels }
13
+
14
+ s.rubyforge_project = "dragoon"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec"
22
+ s.add_runtime_dependency "term/ansicolor"
23
+ end
@@ -0,0 +1,106 @@
1
+ require 'socket'
2
+ require 'ostruct'
3
+ require 'forwardable'
4
+ require 'yaml'
5
+
6
+ require 'term/ansicolor'
7
+
8
+ require 'dragoon/commands'
9
+ require 'dragoon/event'
10
+ require 'dragoon/event_manager'
11
+ require 'dragoon/color'
12
+ require 'dragoon/core_ext'
13
+
14
+
15
+ module Dragoon
16
+ class Bot
17
+ extend Forwardable
18
+
19
+ include Commands
20
+ include Color
21
+
22
+ attr_accessor :nickname, :networks, :channels, :keywords
23
+
24
+ def_delegators :@event_manager, :on, :dispatch
25
+
26
+ DEFAULT_PORT = 6667
27
+
28
+ def initialize(&blk)
29
+ @event_manager = EventManager.new
30
+ load_config
31
+ init_callbacks
32
+ end
33
+
34
+ def load_config
35
+ begin
36
+ config = YAML.load_file(File.expand_path(File.join(File.dirname(__FILE__), "./../config/config.yml")))
37
+ rescue => e
38
+ $stderr.puts e.message
39
+ exit 1
40
+ end
41
+ @nickname = config["nickname"]
42
+ @networks = config["networks"]
43
+ @channels = config["channels"]
44
+ @keywords = config["keywords"]
45
+ end
46
+
47
+ def init_callbacks
48
+ on :startup do |msg|
49
+ puts msg.text
50
+ if msg.text =~ /.*Found your hostname.*/
51
+ # login
52
+ user(self.nickname)
53
+ nick(self.nickname)
54
+ end
55
+ end
56
+
57
+ on :ping do |msg|
58
+ pong(msg.server) # keep alive
59
+ end
60
+
61
+ on :mode do |msg|
62
+ puts "*** Logged in as #{@nickname}"
63
+ join_channels
64
+ end
65
+
66
+ on :join do |msg|
67
+ puts "*** Listening on channel #{colorize(msg.channel)}" if self.nickname == msg.nickname
68
+ end
69
+
70
+ on :err do |msg|
71
+ puts "err: #{msg.text}" unless msg.error_code == '470' # channel forwarding
72
+ end
73
+
74
+ on :privmsg do |msg|
75
+ if system("which growlnotify > /dev/null")
76
+ if keyword = self.keywords.detect { |keyword| msg.text.include?(keyword) }
77
+ system("growlnotify " +
78
+ "-t \"#{keyword}\" " +
79
+ "-m \"#{escape_double_quotes(msg.privmsg)}\"")
80
+ end
81
+ end
82
+ puts "#{colorize(msg.channel)} <#{msg.nickname}> #{highlight_keywords(msg.privmsg,self.keywords)}"
83
+ end
84
+ end
85
+
86
+ def run
87
+ @socket = connect
88
+ while line = @socket.gets
89
+ @event = Event.parse(line)
90
+ dispatch(@event)
91
+ end
92
+ end
93
+
94
+ def connect
95
+ puts "*** Connecting to #{@networks.first} on port #{DEFAULT_PORT}"
96
+ TCPSocket.new(@networks.first, DEFAULT_PORT)
97
+ end
98
+
99
+ def escape_double_quotes(text)
100
+ text.gsub("\"", "\\\"")
101
+ end
102
+
103
+ end
104
+ end
105
+
106
+ #http://www.nightmare.com/medusa/async_sockets.html
@@ -0,0 +1,38 @@
1
+ module Dragoon
2
+ module Color
3
+
4
+ # borrowed from Term::ANSIColors
5
+ COLORED_REGEXP = /\e\[(?:(?:[349]|10)[0-7]|[0-9])?m/
6
+ # :yellow is reserved for highlight keywords
7
+ @@color_list = [:red, :green, :blue, :magenta, :cyan, :white]
8
+
9
+ def colorize(text)
10
+ @c_table ||= {}
11
+ color = @c_table.fetch(text) rescue @c_table[text] = next_color
12
+ text.send color
13
+ end
14
+
15
+ def next_color
16
+ @@color_list.push(@@color_list.shift).first
17
+ end
18
+
19
+ def highlight_keywords(text,keywords)
20
+ keywords.sort_by(&:length).reverse.inject(text) do |result, keyword|
21
+ result.gsub(keyword) { |word|
22
+ to_left_of_word, to_right_of_word = $`, $'
23
+
24
+ # check if inside exisitng highlighted word via ANSI ESCAPE
25
+ if to_left_of_word =~ /#{COLORED_REGEXP}\S+$/ && to_right_of_word =~ /^\S+#{COLORED_REGEXP}/
26
+ word
27
+ else
28
+ highlight(word)
29
+ end
30
+ }
31
+ end
32
+ end
33
+
34
+ def highlight(word)
35
+ word.black.on_yellow
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,31 @@
1
+ module Dragoon
2
+ module Commands
3
+
4
+ def nick(text)
5
+ write "NICK #{text}"
6
+ end
7
+
8
+ def user(text)
9
+ write "USER #{text} #{text} #{text} #{text}"
10
+ end
11
+
12
+ def pong(text)
13
+ write "PONG #{text}"
14
+ end
15
+
16
+ def join(text)
17
+ write "JOIN #{text}"
18
+ end
19
+
20
+ def join_channels
21
+ @channels.each { |channel| join(channel) }
22
+ end
23
+
24
+ private
25
+
26
+ def write(text)
27
+ @socket.write "#{text}\r\n"
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ class String
2
+ include Term::ANSIColor
3
+ end
@@ -0,0 +1,43 @@
1
+ module Dragoon
2
+ class Event
3
+
4
+ class Message < OpenStruct
5
+ end
6
+
7
+ attr_reader :name, :message
8
+
9
+ def initialize(name, message)
10
+ @name = name
11
+ @message = message
12
+ end
13
+
14
+ def self.parse(line)
15
+ case line
16
+ when /^:\S+ NOTICE \* :\*{3} .*/
17
+ self.new(:startup, Message.new(:text => line))
18
+ when /^PING :(\S+)/
19
+ self.new(:ping, Message.new(:text => line, :server => $1))
20
+ when /^:\S+ MODE \S+ :.*/
21
+ self.new(:mode, Message.new(:text => line))
22
+ when /^:(\S+)!(\S+)@(\S+) JOIN (\S+)/
23
+ self.new(:join, Message.new(:text => line,
24
+ :nickname => $1,
25
+ :user => $2,
26
+ :hostname => $3,
27
+ :channel => $4))
28
+ when /^:(\S+)!(\S+)@(\S+) PRIVMSG (\S+) :(.+)/
29
+ self.new(:privmsg, Message.new(:text => line,
30
+ :nickname => $1,
31
+ :user => $2,
32
+ :hostname => $3,
33
+ :channel => $4,
34
+ :privmsg => $5))
35
+ when /^:\S+ (4\d{2}|5\d{2}) .*/
36
+ self.new(:err, Message.new(:text => line, :error_code => $1))
37
+ else
38
+ self.new(:unknown, Message.new(:text => line))
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ module Dragoon
2
+ class EventManager
3
+
4
+ attr_reader :handlers
5
+
6
+ def dispatch(event)
7
+ if @handlers.include?(event.name) && event.name != :unknown
8
+ @handlers[event.name].call(event.message)
9
+ end
10
+ end
11
+
12
+ # register callbacks for certain events
13
+ # valid events are found on constant VALID_EVENTS
14
+ def on(event_name, &callback)
15
+ @handlers = {} if @handlers.nil?
16
+ @handlers[event_name] = callback
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Dragoon
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ # borrowed from Term::ANSIColors
4
+ COLORED_REGEXP = /\e\[(?:(?:[349]|10)[0-7]|[0-9])?m/
5
+
6
+ describe Dragoon::Color do
7
+ before(:each) do
8
+ class Client
9
+ include Dragoon::Color
10
+ end
11
+ @client = Client.new
12
+ end
13
+
14
+ describe "#colorize" do
15
+ context "single word" do
16
+ before(:each) do
17
+ @text = "#scala"
18
+ end
19
+
20
+ it "should assign text a permanent color" do
21
+ colored = @client.colorize(@text)
22
+ colored.should match COLORED_REGEXP
23
+ 3.times { colored.should == @client.colorize(@text) }
24
+ end
25
+ end
26
+
27
+ context "multiple words" do
28
+ before(:each) do
29
+ @words = %w[ #ruby #java #scala #c #c++ #music #movies #video #opencv #webgl #webkit #processingjs #coffee]
30
+ end
31
+
32
+ it "should assign text a permanent color" do
33
+ @words.each do |text|
34
+ colored = @client.colorize(text)
35
+ colored.should match COLORED_REGEXP
36
+ 3.times { colored.should == @client.colorize(text) }
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ describe "#highlight" do
43
+ context "a keyword overlaps with another" do
44
+ context "on the TAIL" do
45
+ before(:each) do
46
+ @text = "Should I use textmate or vim?"
47
+ @keywords = %w[ textmate ate ]
48
+ end
49
+
50
+ it "should highlight only longest keyword" do
51
+ result = @client.highlight_keywords(@text, @keywords)
52
+ result.should ==
53
+ "Should I use #{@client.highlight('textmate')} or vim?"
54
+ end
55
+ end
56
+
57
+ context "on the HEAD" do
58
+ before(:each) do
59
+ @text = "I'll be surprised if he believes in it."
60
+ @keywords = %w[ be believe ]
61
+ end
62
+
63
+ it "should highlight only longest keyword" do
64
+ result = @client.highlight_keywords(@text, @keywords)
65
+ result.should ==
66
+ "I'll #{@client.highlight("be")} surprised if " +
67
+ "he #{@client.highlight("believe")}s in it."
68
+
69
+ end
70
+ end
71
+
72
+ context "on the MIDDLE" do
73
+ before(:each) do
74
+ @keywords = %w[ computer put ]
75
+ @text = "Please put the computer down"
76
+ end
77
+
78
+ it "should highlight only longest keyword" do
79
+ result = @client.highlight_keywords(@text, @keywords)
80
+ result.should ==
81
+ "Please #{@client.highlight("put")} the " +
82
+ "#{@client.highlight("computer")} down"
83
+ end
84
+ end
85
+ end
86
+
87
+ context "no keyword overlaps" do
88
+ before(:each) do
89
+ @keywords = %w[ game linux need ]
90
+ @text = "I basically need to simulate the game port."
91
+ end
92
+
93
+ it "should highlight keywords" do
94
+ @client.highlight_keywords(@text, @keywords).should ==
95
+ "I basically \e[43m\e[30mneed\e[0m\e[0m to simulate the \e[43m\e[30mgame\e[0m\e[0m port."
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dragoon::EventManager do
4
+
5
+ before(:each) do
6
+ @event_manager = Dragoon::EventManager.new
7
+ end
8
+
9
+ describe "#dispatch" do
10
+ context "event is unknown" do
11
+ it "should not do anything" do
12
+ pending "help from other people"
13
+ #@event_manager.dispatch(:unknown, ":adams.freenode.net 372 felix21 :- portrayed in his uniquely British irony. He is sorely missed")
14
+ end
15
+ end
16
+
17
+ context "no handler for an event" do
18
+ it "should not do anything" do
19
+ pending "help from other people"
20
+ #@event_manager.dispatch(:mode, ":felix21 MODE felix21 :+i")
21
+ end
22
+ end
23
+
24
+ context "callback exists for an event" do
25
+ before(:each) do
26
+ @callback = lambda { puts "hello world" }
27
+ @event = Dragoon::Event.parse(":felix21 MODE felix21 :+i")
28
+ @event_manager.on @event.name, &@callback
29
+ end
30
+
31
+ it "should run the corresponding callback" do
32
+ @callback.should_receive(:call)
33
+ @event_manager.dispatch(@event)
34
+ end
35
+ end
36
+ end
37
+
38
+ describe "#on" do
39
+ it "should register that event" do
40
+ callback = lambda { puts "this is a callback" }
41
+ @event_manager.on(:mode, &callback)
42
+ @event_manager.handlers[:mode].should == callback
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dragoon::Event do
4
+
5
+ describe "#get_event" do
6
+ it "should process startup event" do
7
+ msg = ":zelazny.freenode.net NOTICE * :*** Found your hostname"
8
+ event = Dragoon::Event.parse(msg)
9
+ event.name.should == :startup
10
+ event.message.text.should == msg
11
+ end
12
+
13
+ it "should process ping event" do
14
+ msg = "PING :zelazny.freenode.net"
15
+ event = Dragoon::Event.parse(msg)
16
+ event.name.should == :ping
17
+ event.message.text.should == "PING :zelazny.freenode.net"
18
+ event.message.server.should == "zelazny.freenode.net"
19
+ end
20
+
21
+ it "should process mode event" do
22
+ msg = ":favor212 MODE favor212 :+i"
23
+ event = Dragoon::Event.parse(msg)
24
+ event.name.should == :mode
25
+ event.message.text.should == ":favor212 MODE favor212 :+i"
26
+ end
27
+
28
+ it "should process join event" do
29
+ msg = ":zigcat_Fox!~zigcat_fox@hostname JOIN #time_to_show_off"
30
+ event = Dragoon::Event.parse(msg)
31
+ event.name.should == :join
32
+ event.message.text.should == ":zigcat_Fox!~zigcat_fox@hostname JOIN #time_to_show_off"
33
+ event.message.nickname.should == "zigcat_Fox"
34
+ event.message.user.should == "~zigcat_fox"
35
+ event.message.hostname.should == "hostname"
36
+ event.message.channel.should == "#time_to_show_off"
37
+ end
38
+
39
+ it "should process privmsg event" do
40
+ msg = ":nick!~user@hostname PRIVMSG #ruby :array << 0 if array.size.odd?"
41
+ event = Dragoon::Event.parse(msg)
42
+ event.name.should == :privmsg
43
+ event.message.text.should == ":nick!~user@hostname PRIVMSG #ruby :array << 0 if array.size.odd?"
44
+ event.message.nickname.should == "nick"
45
+ event.message.user.should == "~user"
46
+ event.message.hostname.should == "hostname"
47
+ event.message.channel.should == "#ruby"
48
+ event.message.privmsg.should == "array << 0 if array.size.odd?"
49
+ end
50
+
51
+ it "should process error reply event" do
52
+ msg1 = ":asimov.freenode.net 477 zigcat_Fox ##java :Cannot join channel (+r) - you need to be identified with services"
53
+ msg2 = ":kornbluth.freenode.net 461 fenix45 USER :Not enough parameters"
54
+ event = Dragoon::Event.parse(msg1)
55
+ event.name.should == :err
56
+ event.message.error_code.should == '477'
57
+ event = Dragoon::Event.parse(msg2)
58
+ event.name.should == :err
59
+ event.message.error_code.should == '461'
60
+ end
61
+ end
62
+ end
63
+
64
+
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift("#{File.expand_path(File.dirname(__FILE__))}/../lib")
2
+
3
+ require 'dragoon'
4
+
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dragoon
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Reginald Tan
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-01-22 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: term/ansicolor
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: "An IRC bot that alerts you whenever certain keywords appear in your favorite channels "
49
+ email:
50
+ - redge.tan@gmail.com
51
+ executables:
52
+ - dragoon
53
+ extensions: []
54
+
55
+ extra_rdoc_files: []
56
+
57
+ files:
58
+ - .gitignore
59
+ - Gemfile
60
+ - Gemfile.lock
61
+ - README.md
62
+ - Rakefile
63
+ - TODO
64
+ - bin/dragoon
65
+ - config/config.yml
66
+ - config/config.yml.example
67
+ - dragoon.gemspec
68
+ - lib/dragoon.rb
69
+ - lib/dragoon/color.rb
70
+ - lib/dragoon/commands.rb
71
+ - lib/dragoon/core_ext.rb
72
+ - lib/dragoon/event.rb
73
+ - lib/dragoon/event_manager.rb
74
+ - lib/dragoon/version.rb
75
+ - spec/colors_spec.rb
76
+ - spec/event_manager_spec.rb
77
+ - spec/event_spec.rb
78
+ - spec/spec_helper.rb
79
+ homepage: https://github.com/redgetan/dragoon
80
+ licenses: []
81
+
82
+ post_install_message:
83
+ rdoc_options: []
84
+
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ hash: 3
102
+ segments:
103
+ - 0
104
+ version: "0"
105
+ requirements: []
106
+
107
+ rubyforge_project: dragoon
108
+ rubygems_version: 1.8.10
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: IRC bot for keyword notification
112
+ test_files:
113
+ - spec/colors_spec.rb
114
+ - spec/event_manager_spec.rb
115
+ - spec/event_spec.rb
116
+ - spec/spec_helper.rb
117
+ has_rdoc: