percy 0.0.6 → 1.0.0

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.
Files changed (4) hide show
  1. data/README.md +34 -32
  2. data/VERSION +1 -1
  3. data/lib/percy.rb +187 -178
  4. metadata +13 -4
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Percy 0.0.6
1
+ # Percy 1.0.0
2
2
 
3
3
  ## Configuring and starting the bot
4
4
 
@@ -9,29 +9,31 @@
9
9
  require 'rubygems'
10
10
  require 'percy'
11
11
 
12
- bot = Percy.new
13
-
14
- bot.configure do |c|
15
- c.server = 'chat.eu.freenode.net'
16
- c.port = 6667
17
- c.nick = 'Percyguy'
18
- c.verbose = true
19
- c.logging = true
12
+ Percy.configure do |c|
13
+ c.server = 'chat.eu.freenode.net'
14
+ c.port = 6667
15
+ # c.password = 'password'
16
+ c.nick = 'Percyguy'
17
+ c.username = 'Percyguy'
18
+ c.verbose = true
19
+ c.logging = true
20
+ c.reconnect = true
21
+ c.reconnect_interval = 30
20
22
  end
21
23
 
22
- bot.connect
24
+ Percy.connect
23
25
 
24
26
  Start it with `ruby mybot.rb`.
25
27
 
26
28
  ## Handling Events
27
29
  ### Connect
28
- bot.on :connect do
30
+ Percy.on :connect do
29
31
  # ...
30
32
  end
31
33
  No variables.
32
34
 
33
35
  ### Channel message
34
- bot.on :channel, /^foo!/ do |env|
36
+ Percy.on :channel, /^foo!/ do |env|
35
37
  # ...
36
38
  end
37
39
  Variables:
@@ -43,7 +45,7 @@ env[:channel]<br />
43
45
  env[:message]</tt>
44
46
 
45
47
  ### Query message
46
- bot.on :query, /^bar!/ do |env|
48
+ Percy.on :query, /^bar!/ do |env|
47
49
  # ...
48
50
  end
49
51
  Variables:
@@ -54,7 +56,7 @@ env[:host]<br />
54
56
  env[:message]</tt>
55
57
 
56
58
  ### Join
57
- bot.on :join do |env|
59
+ Percy.on :join do |env|
58
60
  # ...
59
61
  end
60
62
  Variables:
@@ -65,7 +67,7 @@ env[:host]<br />
65
67
  env[:channel]</tt>
66
68
 
67
69
  ### Part
68
- bot.on :part do |env|
70
+ Percy.on :part do |env|
69
71
  # ...
70
72
  end
71
73
  Variables:
@@ -77,7 +79,7 @@ env[:channel]<br />
77
79
  env[:message]</tt>
78
80
 
79
81
  ### Quit
80
- bot.on :quit do |env|
82
+ Percy.on :quit do |env|
81
83
  # ...
82
84
  end
83
85
  Variables:
@@ -88,7 +90,7 @@ env[:host]<br />
88
90
  env[:message]</tt>
89
91
 
90
92
  ### Nickchange
91
- bot.on :nickchange do |env|
93
+ Percy.on :nickchange do |env|
92
94
  # ...
93
95
  end
94
96
  Variables:
@@ -99,7 +101,7 @@ env[:host]<br />
99
101
  env[:new_nick]</tt>
100
102
 
101
103
  ### Kick
102
- bot.on :kick do |env|
104
+ Percy.on :kick do |env|
103
105
  # ...
104
106
  end
105
107
  Variables:
@@ -111,58 +113,58 @@ env[:channel]<br />
111
113
  env[:victim]<br />
112
114
  env[:reason]</tt>
113
115
 
114
- ## Availabe Methods
116
+ ## Availabe Class Methods
115
117
 
116
- `raw(msg)`
118
+ `Percy.raw(msg)`
117
119
 
118
120
  Sends a raw message to the server.
119
121
 
120
- `message(recipient, msg)`
122
+ `Percy.message(recipient, msg)`
121
123
 
122
124
  Sends a message to a channel or an user.
123
125
 
124
- `notice(recipient, msg)`
126
+ `Percy.notice(recipient, msg)`
125
127
 
126
128
  Sends a notice to an user.
127
129
 
128
- `action(recipient, msg)`
130
+ `Percy.action(recipient, msg)`
129
131
 
130
132
  Performs an action (/me ...).
131
133
 
132
- `mode(recipient, option)`
134
+ `Percy.mode(recipient, option)`
133
135
 
134
136
  Sets a mode for a channel or an user.
135
137
 
136
- `channellimit(channel)`
138
+ `Percy.channellimit(channel)`
137
139
 
138
140
  Returns the channel limit of a channel (as integer if set, else (not set/timeout) false).
139
141
 
140
- `kick(channel, user, reason)`
142
+ `Percy.kick(channel, user, reason)`
141
143
 
142
144
  Kicks an user from a channel with a specific reason.
143
145
 
144
- `topic(channel, topic)`
146
+ `Percy.topic(channel, topic)`
145
147
 
146
148
  Sets the topic for a channel.
147
149
 
148
- `join(channel, password = nil)`
150
+ `Percy.join(channel, password = nil)`
149
151
 
150
152
  Joins a channel.
151
153
 
152
- `part(channel, msg)`
154
+ `Percy.part(channel, msg)`
153
155
 
154
156
  Parts a channel with a message.
155
157
 
156
- `quit(msg = nil)`
158
+ `Percy.quit(msg = nil)`
157
159
 
158
160
  Quits from the server with a message.
159
161
 
160
- `users_on(channel)`
162
+ `Percy.users_on(channel)`
161
163
 
162
164
  Returns an array of users from a channel (mode in front like: ['@percy', 'Peter_Parker', '+The_Librarian']) or false if timeout.
163
165
 
164
166
 
165
- `is_online(nick)`
167
+ `Percy.is_online(nick)`
166
168
 
167
169
  Returns a nickname as string if online, else false (not online/timeout)
168
170
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 1.0.0
data/lib/percy.rb CHANGED
@@ -1,43 +1,73 @@
1
1
  $:.unshift File.expand_path(File.dirname(__FILE__))
2
2
 
3
+ require 'rubygems'
4
+ require 'eventmachine'
3
5
  require 'percylogger'
4
- require 'socket'
5
6
  require 'timeout'
6
7
  require 'thread'
7
8
 
8
9
  Thread.abort_on_exception = true
9
10
 
10
- class Percy
11
- VERSION = 'Percy 0.0.6 (http://github.com/tbuehlmann/percy)'
11
+ class Connection < EventMachine::Connection
12
+ include EventMachine::Protocols::LineText2
12
13
 
13
- Config = Struct.new(:server, :port, :password, :nick, :username, :verbose, :logging)
14
+ def connection_completed
15
+ Percy.raw "NICK #{Percy.config.nick}"
16
+ Percy.raw "USER #{Percy.config.nick} 0 * :#{Percy.config.username}"
17
+ Percy.raw "PASS #{Percy.config.password}" if Percy.config.password
18
+ end
14
19
 
15
- def initialize
16
- @config = Config.new("localhost", 6667, nil, 'Percy', 'Percy', true, false)
17
-
18
- # helper variables for getting server return values
19
- @observers = 0
20
- @temp_socket = []
21
-
22
- # user methods
23
- @on_channel = []
24
- @on_query = []
25
- @on_connect = []
26
- @on_join = []
27
- @on_part = []
28
- @on_quit = []
29
- @on_nickchange = []
30
- @on_kick = []
31
-
32
- # observer synchronizer
33
- @mutex = Mutex.new
34
-
35
- # running variable (provisional solution for rejoining at netsplit)
36
- @running = true
20
+ def unbind
21
+ Percy.connected = false
22
+ Percy.traffic_logger.info('-- Percy disconnected') if Percy.traffic_logger
23
+ puts "#{Time.now.strftime('%d.%m.%Y %H:%M:%S')} -- Percy disconnected"
24
+ if Percy.config.reconnect
25
+ Percy.traffic_logger.info("-- Reconnecting in #{Percy.config.reconnect_interval} seconds") if Percy.traffic_logger
26
+ puts "#{Time.now.strftime('%d.%m.%Y %H:%M:%S')} -- Reconnecting in #{Percy.config.reconnect_interval} seconds"
27
+
28
+ EventMachine::add_timer(Percy.config.reconnect_interval) do
29
+ reconnect Percy.config.server, Percy.config.port
30
+ end
31
+ end
32
+ end
33
+
34
+ def receive_line(line)
35
+ Percy.parse line
36
+ end
37
+ end
38
+
39
+ class Percy
40
+ class << self
41
+ attr_reader :config
42
+ attr_accessor :traffic_logger, :connected
37
43
  end
38
44
 
39
- # configure block
40
- def configure(&block)
45
+ VERSION = 'Percy 1.0.0 (http://github.com/tbuehlmann/percy)'
46
+
47
+ Config = Struct.new(:server, :port, :password, :nick, :username, :verbose, :logging, :reconnect, :reconnect_interval)
48
+
49
+ @config = Config.new("localhost", 6667, nil, 'Percy', 'Percy', true, false, false, 30)
50
+
51
+ # helper variables for getting server return values
52
+ @observers = 0
53
+ @temp_socket = []
54
+
55
+ @connected = false
56
+
57
+ # user methods
58
+ @on_channel = []
59
+ @on_query = []
60
+ @on_connect = []
61
+ @on_join = []
62
+ @on_part = []
63
+ @on_quit = []
64
+ @on_nickchange = []
65
+ @on_kick = []
66
+
67
+ # observer synchronizer
68
+ @mutex_observer = Mutex.new
69
+
70
+ def self.configure(&block)
41
71
  block.call(@config)
42
72
 
43
73
  # logger
@@ -45,32 +75,84 @@ class Percy
45
75
  @error_logger = PercyLogger.new("#{PERCY_ROOT}/logs/error.log") if @config.logging
46
76
  end
47
77
 
48
- # raw irc messages
49
- def raw(msg)
50
- @socket.puts "#{msg}\r\n"
51
- @traffic_logger.info(">> #{msg}") if @traffic_logger
52
- puts "#{Time.now.strftime('%d.%m.%Y %H:%M:%S')} >> #{msg}" if @config.verbose
78
+ # raw IRC messages
79
+ def self.raw(message)
80
+ @connection.send_data "#{message}\r\n"
81
+ @traffic_logger.info(">> #{message}") if @traffic_logg
82
+ puts "#{Time.now.strftime('%d.%m.%Y %H:%M:%S')} >> #{message}" if @config.verbose
53
83
  end
54
84
 
55
85
  # send a message
56
- def message(recipient, msg)
57
- raw "PRIVMSG #{recipient} :#{msg}"
86
+ def self.message(recipient, message)
87
+ self.raw "PRIVMSG #{recipient} :#{message}"
58
88
  end
59
89
 
60
90
  # send a notice
61
- def notice(recipient, msg)
62
- raw "NOTICE #{recipient} :#{msg}"
91
+ def self.notice(recipient, message)
92
+ self.raw "NOTICE #{recipient} :#{message}"
63
93
  end
64
94
 
65
95
  # set a mode
66
- def mode(recipient, option)
67
- raw "MODE #{recipient} #{option}"
96
+ def self.mode(recipient, option)
97
+ self.raw "MODE #{recipient} #{option}"
98
+ end
99
+
100
+ # kick a user
101
+ def self.kick(channel, user, reason)
102
+ if reason
103
+ self.raw "KICK #{channel} #{user} :#{reason}"
104
+ else
105
+ self.raw "KICK #{channel} #{user}"
106
+ end
107
+ end
108
+
109
+ # perform an action
110
+ def self.action(recipient, message)
111
+ self.raw "PRIVMSG #{recipient} :\001ACTION #{message}\001"
112
+ end
113
+
114
+ # set a topic
115
+ def self.topic(channel, topic)
116
+ self.raw "TOPIC #{channel} :#{topic}"
117
+ end
118
+
119
+ # joining a channel
120
+ def self.join(channel, password = nil)
121
+ if password
122
+ self.raw "JOIN #{channel} #{password}"
123
+ else
124
+ self.raw "JOIN #{channel}"
125
+ end
126
+ end
127
+
128
+ # parting a channel
129
+ def self.part(channel, message)
130
+ if msg
131
+ self.raw "PART #{channel} :#{message}"
132
+ else
133
+ self.raw 'PART'
134
+ end
135
+ end
136
+
137
+ # quitting
138
+ def self.quit(message = nil)
139
+ if message
140
+ self.raw "QUIT :#{message}"
141
+ else
142
+ self.raw 'QUIT'
143
+ end
144
+
145
+ @config.reconnect = false # so Percy does not reconnect after the socket has been closed
146
+ end
147
+
148
+ def self.nick
149
+ @config.nick
68
150
  end
69
151
 
70
152
  # returns all users on a specific channel
71
- def users_on(channel)
72
- add_observer
73
- raw "NAMES #{channel}"
153
+ def self.users_on(channel)
154
+ self.add_observer
155
+ self.raw "NAMES #{channel}"
74
156
 
75
157
  begin
76
158
  Timeout::timeout(10) do # try 10 seconds to retrieve the users of <channel>
@@ -92,14 +174,14 @@ class Percy
92
174
  rescue Timeout::Error
93
175
  return false
94
176
  ensure
95
- remove_observer
177
+ self.remove_observer
96
178
  end
97
179
  end
98
180
 
99
181
  # get the channel limit of a channel
100
- def channel_limit(channel)
101
- add_observer
102
- raw "MODE #{channel}"
182
+ def self.channel_limit(channel)
183
+ self.add_observer
184
+ self.raw "MODE #{channel}"
103
185
 
104
186
  begin
105
187
  Timeout::timeout(10) do # try 10 seconds to retrieve l mode of <channel>
@@ -121,14 +203,14 @@ class Percy
121
203
  rescue Timeout::Error
122
204
  return false
123
205
  ensure
124
- remove_observer
206
+ self.remove_observer
125
207
  end
126
208
  end
127
209
 
128
210
  # check whether an user is online
129
- def is_online(nick)
130
- add_observer
131
- raw "WHOIS #{nick}"
211
+ def self.is_online(nick)
212
+ self.add_observer
213
+ self.raw "WHOIS #{nick}"
132
214
 
133
215
  begin
134
216
  Timeout::timeout(10) do
@@ -152,60 +234,12 @@ class Percy
152
234
  rescue Timeout::Error
153
235
  return false
154
236
  ensure
155
- remove_observer
156
- end
157
- end
158
-
159
- # kick a user
160
- def kick(channel, user, reason)
161
- if reason
162
- raw "KICK #{channel} #{user} :#{reason}"
163
- else
164
- raw "KICK #{channel} #{user}"
165
- end
166
- end
167
-
168
- # perform an action
169
- def action(recipient, msg)
170
- raw "PRIVMSG #{recipient} :\001ACTION #{msg}\001"
171
- end
172
-
173
- # set a topic
174
- def topic(channel, topic)
175
- raw "TOPIC #{channel} :#{topic}"
176
- end
177
-
178
- # joining a channel
179
- def join(channel, password = nil)
180
- if password
181
- raw "JOIN #{channel} #{password}"
182
- else
183
- raw "JOIN #{channel}"
237
+ self.remove_observer
184
238
  end
185
239
  end
186
240
 
187
- # parting a channel
188
- def part(channel, msg)
189
- if msg
190
- raw "PART #{channel} :#{msg}"
191
- else
192
- raw 'PART'
193
- end
194
- end
195
-
196
- # quitting
197
- def quit(msg = nil)
198
- if msg
199
- raw "QUIT :#{msg}"
200
- else
201
- raw 'QUIT'
202
- end
203
-
204
- @running = false # so Percy does not reconnect after the socket has been closed
205
- end
206
-
207
241
  # on method
208
- def on(type = :channel, match = //, &block)
242
+ def self.on(type = :channel, match = //, &block)
209
243
  case type
210
244
  when :channel
211
245
  @on_channel << {:match => match, :proc => block}
@@ -227,22 +261,22 @@ class Percy
227
261
  end
228
262
 
229
263
  # add observer
230
- def add_observer
231
- @mutex.synchronize do
264
+ def self.add_observer
265
+ @mutex_observer.synchronize do
232
266
  @observers += 1
233
267
  end
234
268
  end
235
269
 
236
270
  # remove observer
237
- def remove_observer
238
- @mutex.synchronize do
271
+ def self.remove_observer
272
+ @mutex_observer.synchronize do
239
273
  @observers -= 1 # remove observer
240
274
  @temp_socket = [] if @observers == 0 # clear @temp_socket if no observers are active
241
275
  end
242
276
  end
243
277
 
244
- # parses incoming traffic
245
- def parse(type, env = nil)
278
+ # parses incoming traffic (types)
279
+ def self.parse_type(type, env = nil)
246
280
  case type
247
281
  when :connect
248
282
  @on_connect.each do |block|
@@ -284,17 +318,17 @@ class Percy
284
318
  when :query
285
319
  # version respones
286
320
  if env[:message] == "\001VERSION\001"
287
- notice env[:nick], "\001VERSION #{VERSION}\001"
321
+ self.notice env[:nick], "\001VERSION #{VERSION}\001"
288
322
  end
289
323
 
290
324
  # time response
291
325
  if env[:message] == "\001TIME\001"
292
- notice env[:nick], "\001TIME #{Time.now.strftime('%a %b %d %H:%M:%S %Y')}\001"
326
+ self.notice env[:nick], "\001TIME #{Time.now.strftime('%a %b %d %H:%M:%S %Y')}\001"
293
327
  end
294
328
 
295
329
  # ping response
296
330
  if env[:message] =~ /\001PING (\d+)\001/
297
- notice env[:nick], "\001PING #{$1}\001"
331
+ self.notice env[:nick], "\001PING #{$1}\001"
298
332
  end
299
333
 
300
334
  @on_query.each do |method|
@@ -396,77 +430,52 @@ class Percy
396
430
  end
397
431
  end
398
432
 
399
- def nick
400
- @config.nick
433
+ # connect!
434
+ def self.connect
435
+ @traffic_logger.info('-- Starting Percy') if @traffic_logger
436
+ puts "#{Time.now.strftime('%d.%m.%Y %H:%M:%S')} -- Starting Percy"
437
+
438
+ EventMachine::run do
439
+ @connection = EventMachine::connect(@config.server, @config.port, Connection)
440
+ end
401
441
  end
402
442
 
403
- # connect!
404
- def connect
405
- begin
406
- while @running
407
- @traffic_logger.info('-- Starting Percy') if @traffic_logger
408
- puts "#{Time.now.strftime('%d.%m.%Y %H:%M:%S')} -- Starting Percy"
409
-
410
-
411
- @socket = TCPSocket.open(@config.server, @config.port)
412
- raw "PASS #{@config.password}" if @config.password
413
- raw "NICK #{@config.nick}"
414
- raw "USER #{@config.nick} 0 * :#{@config.username}"
415
-
416
- while line = @socket.gets
417
- @traffic_logger.info("<< #{line.chomp}") if @traffic_logger
418
- puts "#{Time.now.strftime('%d.%m.%Y %H:%M:%S')} << #{line.chomp}" if @config.verbose
419
-
420
- case line.chomp
421
- when /^PING \S+$/
422
- raw line.chomp.gsub('PING', 'PONG')
423
-
424
- when /^:\S+ 376|422/
425
- parse(:connect)
426
-
427
- when /^:(\S+)!(\S+)@(\S+) PRIVMSG #(\S+) :/
428
- parse(:channel, :nick => $1, :user => $2, :host => $3, :channel => "##{$4}", :message => $')
429
-
430
- when /^:(\S+)!(\S+)@(\S+) PRIVMSG \S+ :/
431
- parse(:query, :nick => $1, :user => $2, :host => $3, :message => $')
432
-
433
- when /^:(\S+)!(\S+)@(\S+) JOIN :*(\S+)$/
434
- parse(:join, :nick => $1, :user => $2, :host => $3, :channel => $4)
435
-
436
- when /^:(\S+)!(\S+)@(\S+) PART (\S+)/
437
- parse(:part, :nick => $1, :user => $2, :host => $3, :channel => $4, :message => $'.sub(' :', ''))
438
-
439
- when /^:(\S+)!(\S+)@(\S+) QUIT/
440
- parse(:quit, :nick => $1, :user => $2, :host => $3, :message => $'.sub(' :', ''))
441
-
442
- when /^:(\S+)!(\S+)@(\S+) NICK :/
443
- parse(:nickchange, :nick => $1, :user => $2, :host => $3, :new_nick => $')
444
-
445
- when /^:(\S+)!(\S+)@(\S+) KICK (\S+) (\S+) :/
446
- parse(:kick, :nick => $1, :user => $2, :host => $3, :channel => $4, :victim => $5, :reason => $')
447
- end
448
-
449
- if @observers > 0
450
- @temp_socket << line.chomp
451
- end
452
- end
453
-
454
- @traffic_logger.info('-- Percy disconnected') if @traffic_logger
455
- puts "#{Time.now.strftime('%d.%m.%Y %H:%M:%S')} -- Percy disconnected"
456
- @connected = false
457
- end
458
- rescue => e
459
- @error_logger.error(e.message)
460
- e.backtrace.each do |line|
461
- @error_logger.error(line)
462
- end
463
-
464
- @traffic_logger.info('-- Percy disconnected') if @traffic_logger
465
- puts "#{Time.now.strftime('%d.%m.%Y %H:%M:%S')} -- Percy disconnected"
466
- @connected = false
467
- ensure
468
- @traffic_logger.file.close if @traffic_logger
469
- @error_logger.file.close if @error_logger
443
+ # parsing incoming traffic
444
+ def self.parse(message)
445
+ @traffic_logger.info("<< #{message.chomp}") if @traffic_logger
446
+ puts "#{Time.now.strftime('%d.%m.%Y %H:%M:%S')} << #{message.chomp}" if @config.verbose
447
+
448
+ case message.chomp
449
+ when /^PING \S+$/
450
+ self.raw message.chomp.gsub('PING', 'PONG')
451
+
452
+ when /^:\S+ 376|422/
453
+ self.parse_type(:connect)
454
+
455
+ when /^:(\S+)!(\S+)@(\S+) PRIVMSG #(\S+) :/
456
+ self.parse_type(:channel, :nick => $1, :user => $2, :host => $3, :channel => "##{$4}", :message => $')
457
+
458
+ when /^:(\S+)!(\S+)@(\S+) PRIVMSG \S+ :/
459
+ self.parse_type(:query, :nick => $1, :user => $2, :host => $3, :message => $')
460
+
461
+ when /^:(\S+)!(\S+)@(\S+) JOIN :*(\S+)$/
462
+ self.parse_type(:join, :nick => $1, :user => $2, :host => $3, :channel => $4)
463
+
464
+ when /^:(\S+)!(\S+)@(\S+) PART (\S+)/
465
+ self.parse_type(:part, :nick => $1, :user => $2, :host => $3, :channel => $4, :message => $'.sub(' :', ''))
466
+
467
+ when /^:(\S+)!(\S+)@(\S+) QUIT/
468
+ self.parse_type(:quit, :nick => $1, :user => $2, :host => $3, :message => $'.sub(' :', ''))
469
+
470
+ when /^:(\S+)!(\S+)@(\S+) NICK :/
471
+ self.parse_type(:nickchange, :nick => $1, :user => $2, :host => $3, :new_nick => $')
472
+
473
+ when /^:(\S+)!(\S+)@(\S+) KICK (\S+) (\S+) :/
474
+ self.parse_type(:kick, :nick => $1, :user => $2, :host => $3, :channel => $4, :victim => $5, :reason => $')
475
+ end
476
+
477
+ if @observers > 0
478
+ @temp_socket << message.chomp
470
479
  end
471
480
  end
472
481
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: percy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - "Tobias B\xC3\xBChlmann"
@@ -9,10 +9,19 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-13 00:00:00 +01:00
12
+ date: 2009-12-21 00:00:00 +01:00
13
13
  default_executable:
14
- dependencies: []
15
-
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: eventmachine
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.12.10
24
+ version:
16
25
  description: Percy is an IRC bot framework inspired by isaac with various changes.
17
26
  email: tobias.buehlmann@gmx.de
18
27
  executables: []