bbrowning-ponder 0.0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,93 @@
1
+ require 'pathname'
2
+ require 'thread'
3
+ autoload :FileUtils, 'fileutils'
4
+
5
+ module Ponder
6
+ module Logger
7
+ class Twoflogger
8
+ attr_accessor :level, :levels, :time_format
9
+
10
+ def initialize(destination = Ponder.root.join('logs', 'log.log'), level = :debug, time_format = '%Y-%m-%d %H:%M:%S', levels = {:debug => 0, :info => 1, :warn => 2, :error => 3, :fatal => 4, :unknown => 5})
11
+ @level = level
12
+ @time_format = time_format
13
+ @levels = levels
14
+ @queue = Queue.new
15
+ @mutex = Mutex.new
16
+ @running = false
17
+
18
+ define_level_shorthand_methods
19
+ self.log_dev = destination
20
+ end
21
+
22
+ def start_logging
23
+ @running = true
24
+ @thread = Thread.new do
25
+ begin
26
+ while @running do
27
+ write(@queue.pop)
28
+ end
29
+ ensure
30
+ @log_dev.close if @log_dev.is_a?(File)
31
+ end
32
+ end
33
+ end
34
+
35
+ def stop_logging
36
+ @running = false
37
+ end
38
+
39
+ def log_dev=(destination)
40
+ stop_logging
41
+
42
+ if destination.is_a?(Pathname)
43
+ unless destination.exist?
44
+ unless destination.dirname.directory?
45
+ FileUtils.mkdir_p destination.dirname
46
+ end
47
+
48
+ File.new(destination, 'w+')
49
+ end
50
+ @log_dev = File.open(destination, 'a+')
51
+ @log_dev.sync = true
52
+ elsif destination.is_a?(IO)
53
+ @log_dev = destination
54
+ else
55
+ raise TypeError, 'need a Pathname or IO'
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def define_level_shorthand_methods
62
+ @levels.each_pair do |level_name, severity|
63
+ self.class.send(:define_method, level_name, Proc.new { |*messages| queue(severity, *messages) })
64
+ end
65
+ end
66
+
67
+ def queue(severity, *messages)
68
+ raise(ArgumentError, 'Need a message') if messages.empty?
69
+ raise(ArgumentError, 'Need messages that respond to #to_s') if messages.any? { |message| !message.respond_to?(:to_s) }
70
+
71
+ if severity >= @levels[@level]
72
+ message_hashes = messages.map { |message| {:severity => severity, :message => message} }
73
+
74
+ @mutex.synchronize do
75
+ message_hashes.each do |hash|
76
+ @queue << hash
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def write(*message_hashes)
83
+ begin
84
+ message_hashes.each do |hash|
85
+ @log_dev.puts "#{@levels.index(hash[:severity])} #{Time.now.strftime(@time_format)} #{hash[:message]}"
86
+ end
87
+ rescue => e
88
+ puts e.message, *e.backtrace
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,233 @@
1
+ require 'ponder/callback'
2
+ require 'ponder/connection'
3
+ require 'ponder/irc'
4
+ require 'ponder/async_irc'
5
+ require 'ponder/filter'
6
+ require 'ostruct'
7
+
8
+ module Ponder
9
+ class Thaum
10
+ include IRC
11
+ include AsyncIRC
12
+
13
+ if RUBY_VERSION >= '1.9'
14
+ require 'ponder/delegate'
15
+ include Delegate
16
+ end
17
+
18
+ attr_reader :config
19
+ attr_accessor :connected, :traffic_logger, :error_logger, :console_logger, :empty_logger
20
+
21
+ def initialize
22
+ @config = OpenStruct.new(:server => 'localhost',
23
+ :port => 6667,
24
+ :nick => 'Ponder',
25
+ :username => 'Ponder',
26
+ :real_name => 'Ponder',
27
+ :verbose => true,
28
+ :logging => false,
29
+ :reconnect => true,
30
+ :reconnect_interval => 30
31
+ )
32
+
33
+ @empty_logger = Logger::BlindIo.new
34
+ @traffic_logger = @empty_logger
35
+ @error_logger = @empty_logger
36
+ @console_logger = Logger::Twoflogger.new($stdout)
37
+
38
+ @observer_queues = {}
39
+
40
+ @connected = false
41
+ @reloading = false
42
+
43
+ # user callbacks
44
+ @callbacks = Hash.new { |hash, key| hash[key] = [] }
45
+
46
+ # standard callbacks for PING, VERSION, TIME and Nickname is already in use
47
+ on :query, /^\001PING \d+\001$/ do |env|
48
+ time = env[:message].scan(/\d+/)[0]
49
+ notice env[:nick], "\001PING #{time}\001"
50
+ end
51
+
52
+ on :query, /^\001VERSION\001$/ do |env|
53
+ notice env[:nick], "\001VERSION Ponder #{Ponder::VERSION} (http://github.com/tbuehlmann/ponder)\001"
54
+ end
55
+
56
+ on :query, /^\001TIME\001$/ do |env|
57
+ notice env[:nick], "\001TIME #{Time.now.strftime('%a %b %d %H:%M:%S %Y')}\001"
58
+ end
59
+
60
+ # before and after filter
61
+ @before_filters = Hash.new { |hash, key| hash[key] = [] }
62
+ @after_filters = Hash.new { |hash, key| hash[key] = [] }
63
+ end
64
+
65
+ def configure(&block)
66
+ unless @reloading
67
+ block.call(@config)
68
+
69
+ # logger changes (if differing from initialize)
70
+ if @config.logging
71
+ @traffic_logger = @config.traffic_logger ? @config.traffic_logger : Logger::Twoflogger.new(Ponder.root.join('logs', 'traffic.log'))
72
+ @error_logger = @config.error_logger ? @config.error_logger : Logger::Twoflogger.new(Ponder.root.join('logs', 'error.log'))
73
+ end
74
+
75
+ unless @config.verbose
76
+ @console_logger = @empty_logger
77
+ end
78
+ end
79
+ end
80
+
81
+ def on(event_types = [:channel], match = //, &block)
82
+ if event_types.is_a?(Array)
83
+ callbacks = event_types.map { |event_type| Callback.new(event_type, match, block) }
84
+ else
85
+ callbacks = [Callback.new(event_types, match, block)]
86
+ event_types = [event_types]
87
+ end
88
+
89
+ callbacks.each_with_index do |callback, index|
90
+ @callbacks[event_types[index]] << callback
91
+ end
92
+ end
93
+
94
+ def connect
95
+ unless @reloading
96
+ @traffic_logger.start_logging
97
+ @error_logger.start_logging
98
+ @console_logger.start_logging
99
+
100
+ @traffic_logger.info '-- Starting Ponder'
101
+ @console_logger.info '-- Starting Ponder'
102
+
103
+ EventMachine::run do
104
+ @connection = EventMachine::connect(@config.server, @config.port, Connection, self)
105
+ end
106
+ end
107
+ end
108
+
109
+ def reload!
110
+ @reloading = true
111
+ @callbacks.clear
112
+ load $0
113
+ @reloading = false
114
+ end
115
+
116
+ def reloading?
117
+ @reloading
118
+ end
119
+
120
+ # parsing incoming traffic
121
+ def parse(message)
122
+ message.chomp!
123
+ @traffic_logger.info "<< #{message}"
124
+ @console_logger.info "<< #{message}"
125
+
126
+ case message
127
+ when /^PING \S+$/
128
+ raw message.sub(/PING/, 'PONG')
129
+
130
+ when /^:\S+ (\d\d\d) /
131
+ number = $1.to_i
132
+ parse_event(number, :type => number, :params => $')
133
+
134
+ when /^:(\S+)!(\S+)@(\S+) PRIVMSG #(\S+) :/
135
+ parse_event(:channel, :type => :channel, :nick => $1, :user => $2, :host => $3, :channel => "##{$4}", :message => $')
136
+
137
+ when /^:(\S+)!(\S+)@(\S+) PRIVMSG \S+ :/
138
+ parse_event(:query, :type => :query, :nick => $1, :user => $2, :host => $3, :message => $')
139
+
140
+ when /^:(\S+)!(\S+)@(\S+) JOIN :*(\S+)$/
141
+ parse_event(:join, :type => :join, :nick => $1, :user => $2, :host => $3, :channel => $4)
142
+
143
+ when /^:(\S+)!(\S+)@(\S+) PART (\S+)/
144
+ parse_event(:part, :type => :part, :nick => $1, :user => $2, :host => $3, :channel => $4, :message => $'.sub(/ :/, ''))
145
+
146
+ when /^:(\S+)!(\S+)@(\S+) QUIT/
147
+ parse_event(:quit, :type => :quit, :nick => $1, :user => $2, :host => $3, :message => $'.sub(/ :/, ''))
148
+
149
+ when /^:(\S+)!(\S+)@(\S+) NICK :/
150
+ parse_event(:nickchange, :type => :nickchange, :nick => $1, :user => $2, :host => $3, :new_nick => $')
151
+
152
+ when /^:(\S+)!(\S+)@(\S+) KICK (\S+) (\S+) :/
153
+ parse_event(:kick, :type => :kick, :nick => $1, :user => $2, :host => $3, :channel => $4, :victim => $5, :reason => $')
154
+
155
+ when /^:(\S+)!(\S+)@(\S+) TOPIC (\S+) :/
156
+ parse_event(:topic, :type => :topic, :nick => $1, :user => $2, :host => $3, :channel => $4, :topic => $')
157
+ end
158
+
159
+ @observer_queues.each do |queue, regexps|
160
+ regexps.each do |regexp|
161
+ if message =~ regexp
162
+ queue << message
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ # process callbacks with its begin; rescue; end
169
+ def process_callbacks(event_type, event_data)
170
+ @callbacks[event_type].each do |callback|
171
+ EM.defer(
172
+ Proc.new do
173
+ begin
174
+ stop_running = false
175
+
176
+ # before filters (specific filters first, then :all)
177
+ (@before_filters[event_type] + @before_filters[:all]).each do |filter|
178
+ if filter.call(event_type, event_data) == false
179
+ stop_running = true
180
+ break
181
+ end
182
+ end
183
+
184
+ unless stop_running
185
+ # handling
186
+ callback.call(event_type, event_data)
187
+
188
+ # after filters (specific filters first, then :all)
189
+ (@after_filters[event_type] + @after_filters[:all]).each do |filter|
190
+ filter.call(event_type, event_data)
191
+ end
192
+ end
193
+ rescue => e
194
+ @error_logger.error(e.message, *e.backtrace)
195
+ @console_logger.error(e.message, *e.backtrace)
196
+ end
197
+ end
198
+ )
199
+ end
200
+ end
201
+
202
+ def before_filter(event_types = :all, match = //, &block)
203
+ filter(@before_filters, event_types, match, block)
204
+ end
205
+
206
+ def after_filter(event_types = :all, match = //, &block)
207
+ filter(@after_filters, event_types, match, block)
208
+ end
209
+
210
+ private
211
+
212
+ # parses incoming traffic (types)
213
+ def parse_event(event_type, event_data = {})
214
+ if ((event_type == 376) || (event_type == 422)) && !@connected
215
+ @connected = true
216
+ process_callbacks(:connect, event_data)
217
+ end
218
+
219
+ process_callbacks(event_type, event_data)
220
+ end
221
+
222
+ def filter(filter_type, event_types = :all, match = //, &block)
223
+ if event_types.is_a?(Array)
224
+ event_types.each do |event_type|
225
+ filter_type[event_type] << Filter.new(event_type, match, block)
226
+ end
227
+ else
228
+ filter_type[event_types] << Filter.new(event_types, match, block)
229
+ end
230
+ end
231
+ end
232
+ end
233
+
@@ -0,0 +1,3 @@
1
+ module Ponder
2
+ VERSION = '0.0.2'
3
+ end
@@ -0,0 +1,38 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'ponder'
3
+ s.version = '0.0.2'
4
+ s.summary = 'IRC bot framework'
5
+ s.description = 'Ponder (Stibbons) is a Domain Specific Language for writing IRC Bots using the EventMachine library.'
6
+
7
+ s.author = 'Tobias Bühlmann'
8
+ s.email = 'tobias.buehlmann@gmx.de'
9
+ s.homepage = 'http://github.com/tbuehlmann/ponder'
10
+
11
+ s.required_ruby_version = '>= 1.8.6'
12
+ s.add_dependency('eventmachine', '>= 0.12.10')
13
+ s.files = ['examples/echo.rb',
14
+ 'examples/github_blog.rb',
15
+ 'examples/redis_last_seen.rb',
16
+ 'lib/ponder/async_irc.rb',
17
+ 'lib/ponder/callback.rb',
18
+ 'lib/ponder/connection.rb',
19
+ 'lib/ponder/delegate.rb',
20
+ 'lib/ponder/filter.rb',
21
+ 'lib/ponder/formatting.rb',
22
+ 'lib/ponder/irc.rb',
23
+ 'lib/ponder/logger',
24
+ 'lib/ponder/thaum.rb',
25
+ 'lib/ponder/version.rb',
26
+ 'lib/ponder/logger/blind_io.rb',
27
+ 'lib/ponder/logger/twoflogger.rb',
28
+ 'lib/ponder/logger/twoflogger18.rb',
29
+ 'lib/ponder.rb',
30
+ 'test/test_async_irc.rb',
31
+ 'test/test_callback.rb',
32
+ 'test/test_helper.rb',
33
+ 'test/test_irc.rb',
34
+ 'LICENSE',
35
+ 'ponder.gemspec',
36
+ 'README.md']
37
+ end
38
+
@@ -0,0 +1,108 @@
1
+ require 'pathname'
2
+ $LOAD_PATH.unshift Pathname.new(__FILE__).dirname.expand_path
3
+ require 'test_helper'
4
+ require 'ponder/async_irc'
5
+
6
+ module Ponder
7
+ module AsyncIRC
8
+ def raw(*args)
9
+ end
10
+ end
11
+ end
12
+
13
+ include Ponder::AsyncIRC
14
+
15
+ class TestIRC < Test::Unit::TestCase
16
+ def setup
17
+ @observer_queues = {}
18
+ end
19
+
20
+ def test_get_topic_no_topic_set
21
+ topic = Thread.new do
22
+ assert_equal({:raw_numeric => 331, :message => 'No topic is set'}, get_topic('#mended_drum'))
23
+ end
24
+
25
+ event_loop = Thread.new do
26
+ loop do
27
+ message = ':server 331 Ponder #mended_drum :No topic is set.'
28
+ @observer_queues.each do |queue, regexps|
29
+ regexps.each do |regexp|
30
+ if message =~ regexp
31
+ queue << message
32
+ end
33
+ end
34
+ end
35
+
36
+ sleep 0.1
37
+ end
38
+ end
39
+ topic.join
40
+ end
41
+
42
+ def test_get_topic
43
+ topic = Thread.new do
44
+ assert_equal({:raw_numeric => 332, :message => 'No dwarfs in here!'}, get_topic('#mended_drum'))
45
+ end
46
+
47
+ event_loop = Thread.new do
48
+ loop do
49
+ message = ':server 332 Ponder #mended_drum :No dwarfs in here!'
50
+ @observer_queues.each do |queue, regexps|
51
+ regexps.each do |regexp|
52
+ if message =~ regexp
53
+ queue << message
54
+ end
55
+ end
56
+ end
57
+
58
+ sleep 0.1
59
+ end
60
+ end
61
+ topic.join
62
+ end
63
+
64
+ def test_get_topic_no_such_channel
65
+ topic = Thread.new do
66
+ assert_equal({:raw_numeric => 403, :message => 'No such channel'}, get_topic('#mended_drum'))
67
+ end
68
+
69
+ event_loop = Thread.new do
70
+ loop do
71
+ message = ':server 403 Ponder #mended_drum :No such channel'
72
+ @observer_queues.each do |queue, regexps|
73
+ regexps.each do |regexp|
74
+ if message =~ regexp
75
+ queue << message
76
+ end
77
+ end
78
+ end
79
+
80
+ sleep 0.1
81
+ end
82
+ end
83
+ topic.join
84
+ end
85
+
86
+ def test_get_topic_you_are_not_on_that_channel
87
+ topic = Thread.new do
88
+ assert_equal({:raw_numeric => 442, :message => "You're not on that channel"}, get_topic('#mended_drum'))
89
+ end
90
+
91
+ event_loop = Thread.new do
92
+ loop do
93
+ message = ":server 442 Ponder #mended_drum :You're not on that channel"
94
+ @observer_queues.each do |queue, regexps|
95
+ regexps.each do |regexp|
96
+ if message =~ regexp
97
+ queue << message
98
+ end
99
+ end
100
+ end
101
+
102
+ sleep 0.1
103
+ end
104
+ end
105
+ topic.join
106
+ end
107
+ end
108
+