bbrowning-ponder 0.0.2.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.
@@ -0,0 +1,22 @@
1
+ require 'pathname'
2
+ require 'rubygems'
3
+
4
+ $LOAD_PATH.unshift Pathname.new(__FILE__).dirname.expand_path
5
+
6
+ module Ponder
7
+ def self.root
8
+ Pathname.new($0).dirname.expand_path
9
+ end
10
+
11
+ require 'ponder/version'
12
+ require 'ponder/thaum'
13
+ require 'ponder/formatting'
14
+ require 'ponder/logger/blind_io'
15
+
16
+ if RUBY_VERSION < '1.9'
17
+ require 'ponder/logger/twoflogger18'
18
+ else
19
+ require 'ponder/logger/twoflogger'
20
+ end
21
+ end
22
+
@@ -0,0 +1,117 @@
1
+ require 'thread'
2
+ require 'timeout'
3
+
4
+ module Ponder
5
+ module AsyncIRC
6
+ def get_topic(channel)
7
+ queue = Queue.new
8
+ @observer_queues[queue] = [/:\S+ (331|332|403|442) \S+ #{Regexp.escape(channel)} :/i]
9
+ raw "TOPIC #{channel}"
10
+
11
+ topic = begin
12
+ Timeout::timeout(30) do
13
+ response = queue.pop
14
+ raw_numeric = response.scan(/^:\S+ (\d{3})/)[0][0]
15
+
16
+ case raw_numeric
17
+ when '331'
18
+ {:raw_numeric => 331, :message => 'No topic is set'}
19
+ when '332'
20
+ {:raw_numeric => 332, :message => response.scan(/ :(.*)/)[0][0]}
21
+ when '403'
22
+ {:raw_numeric => 403, :message => 'No such channel'}
23
+ when '442'
24
+ {:raw_numeric => 442, :message => "You're not on that channel"}
25
+ end
26
+ end
27
+ rescue Timeout::Error
28
+ false
29
+ end
30
+
31
+ @observer_queues.delete queue
32
+ return topic
33
+ end
34
+
35
+ def channel_info(channel)
36
+ queue = Queue.new
37
+ @observer_queues[queue] = [/:\S+ (324|329|403) \S+ #{Regexp.escape(channel)}/i]
38
+ raw "MODE #{channel}"
39
+ information = {}
40
+ running = true
41
+
42
+ begin
43
+ Timeout::timeout(30) do
44
+ while running
45
+ response = queue.pop
46
+ raw_numeric = response.scan(/^:\S+ (\d{3})/)[0][0]
47
+
48
+ case raw_numeric
49
+ when '324'
50
+ information[:modes] = response.scan(/^:\S+ 324 \S+ \S+ \+(\w*)/)[0][0].split('')
51
+ limit = response.scan(/^:\S+ 324 \S+ \S+ \+\w* (\w*)/)[0]
52
+ information[:channel_limit] = limit[0].to_i if limit
53
+ when '329'
54
+ information[:created_at] = Time.at(response.scan(/^:\S+ 329 \S+ \S+ (\d+)/)[0][0].to_i)
55
+ running = false
56
+ when '403'
57
+ information = false
58
+ running = false
59
+ end
60
+ end
61
+ end
62
+ rescue Timeout::Error
63
+ information = false
64
+ end
65
+
66
+ @observer_queues.delete queue
67
+ return information
68
+ end
69
+
70
+ def whois(nick)
71
+ queue = Queue.new
72
+ @observer_queues[queue] = [/^:\S+ (307|311|312|318|319|401) \S+ #{Regexp.escape(nick)}/i]
73
+ raw "WHOIS #{nick}"
74
+ whois = {}
75
+ running = true
76
+
77
+ while running
78
+ begin
79
+ Timeout::timeout(30) do
80
+ response = queue.pop
81
+ raw_numeric = response.scan(/^:\S+ (\d{3})/)[0][0]
82
+
83
+ case raw_numeric
84
+ when '307'
85
+ whois[:registered] = true
86
+ when '311'
87
+ response = response.scan(/^:\S+ 311 \S+ (\S+) (\S+) (\S+) \* :(.*)$/)[0]
88
+ whois[:nick] = response[0]
89
+ whois[:username] = response[1]
90
+ whois[:host] = response[2]
91
+ whois[:real_name] = response[3]
92
+ when '312'
93
+ response = response.scan(/^:\S+ 312 \S+ \S+ (\S+) :(.*)/)[0]
94
+ whois[:server] = {:address => response[0], :name => response[1]}
95
+ when '318'
96
+ running = false
97
+ when '319'
98
+ channels_with_mode = response.scan(/^:\S+ 319 \S+ \S+ :(.*)/)[0][0].split(' ')
99
+ whois[:channels] = {}
100
+ channels_with_mode.each do |c|
101
+ whois[:channels][c.scan(/(.)?(#\S+)/)[0][1]] = c.scan(/(.)?(#\S+)/)[0][0]
102
+ end
103
+ when '401'
104
+ whois = false
105
+ running = false
106
+ end
107
+ end
108
+ rescue Timeout::Error
109
+ nil
110
+ end
111
+ end
112
+
113
+ @observer_queues.delete queue
114
+ return whois
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,42 @@
1
+ module Ponder
2
+ class Callback
3
+ LISTENED_TYPES = [:connect, :channel, :query, :join, :part, :quit, :nickchange, :kick, :topic, :disconnect] # + 3-digit numbers
4
+
5
+ def initialize(event_type = :channel, match = //, proc = Proc.new {})
6
+ unless self.class::LISTENED_TYPES.include?(event_type) || event_type.is_a?(Integer)
7
+ raise TypeError, "#{event_type} is an unsupported event-type"
8
+ end
9
+
10
+ self.match = match
11
+ self.proc = proc
12
+ end
13
+
14
+ def call(event_type, event_data = {})
15
+ if (event_type == :channel) || (event_type == :query)
16
+ @proc.call(event_data) if event_data[:message] =~ @match
17
+ elsif event_type == :topic
18
+ @proc.call(event_data) if event_data[:topic] =~ @match
19
+ else
20
+ @proc.call(event_data)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def match=(match)
27
+ if match.is_a?(Regexp)
28
+ @match = match
29
+ else
30
+ raise TypeError, "#{match} must be a Regexp"
31
+ end
32
+ end
33
+
34
+ def proc=(proc)
35
+ if proc.is_a?(Proc)
36
+ @proc = proc
37
+ else
38
+ raise TypeError, "#{proc} must be a Proc"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,37 @@
1
+ require 'eventmachine'
2
+
3
+ module Ponder
4
+ class Connection < EventMachine::Connection
5
+ include EventMachine::Protocols::LineText2
6
+
7
+ def initialize(thaum)
8
+ @thaum = thaum
9
+ end
10
+
11
+ def connection_completed
12
+ @thaum.register
13
+ end
14
+
15
+ def unbind
16
+ @thaum.connected = false
17
+ @thaum.process_callbacks :disconnect, {}
18
+ @thaum.traffic_logger.info '-- Ponder disconnected'
19
+ @thaum.console_logger.info '-- Ponder disconnected'
20
+
21
+ if @thaum.config.reconnect
22
+ @thaum.traffic_logger.info "-- Reconnecting in #{@thaum.config.reconnect_interval} seconds"
23
+ @thaum.console_logger.info "-- Reconnecting in #{@thaum.config.reconnect_interval} seconds"
24
+
25
+ EventMachine::add_timer(@thaum.config.reconnect_interval) do
26
+ reconnect @thaum.config.server, @thaum.config.port
27
+ end
28
+ else
29
+ EventMachine::stop_event_loop
30
+ end
31
+ end
32
+
33
+ def receive_line(line)
34
+ @thaum.parse line
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ module Ponder
2
+ module Delegate
3
+ def delegate
4
+ thaum = self
5
+
6
+ (IRC.instance_methods + [:configure, :on, :connect, :reload!, :reloading?]).each do |method|
7
+ Object.send(:define_method, method) { |*args, &block| thaum.send(method, *args, &block) }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ require 'ponder/callback'
2
+
3
+ module Ponder
4
+ class Filter < Callback
5
+ LISTENED_TYPES += [:all]
6
+ end
7
+ end
@@ -0,0 +1,30 @@
1
+ module Ponder
2
+ module Formatting
3
+ PLAIN = 15.chr
4
+ BOLD = 2.chr
5
+ ITALIC = 22.chr
6
+ UNDERLINE = 31.chr
7
+ COLOR_CODE = 3.chr
8
+ UNCOLOR_CODE = COLOR_CODE
9
+
10
+ #mIRC color codes from http://www.mirc.com/help/colors.html
11
+ COLORS = {:white => '00',
12
+ :black => '01',
13
+ :blue => '02',
14
+ :green => '03',
15
+ :red => '04',
16
+ :brown => '05',
17
+ :purple => '06',
18
+ :orange => '07',
19
+ :yellow => '08',
20
+ :lime => '09',
21
+ :teal => '10',
22
+ :cyan => '11',
23
+ :royal => '12',
24
+ :pink => '13',
25
+ :gray => '14',
26
+ :silver => '15'
27
+ }
28
+ end
29
+ end
30
+
@@ -0,0 +1,110 @@
1
+ module Ponder
2
+ module IRC
3
+ # raw IRC messages
4
+ def raw(message)
5
+ @connection.send_data "#{message}\r\n"
6
+ @traffic_logger.info ">> #{message}"
7
+ @console_logger.info ">> #{message}"
8
+ end
9
+
10
+ # send a message
11
+ def message(recipient, message)
12
+ raw "PRIVMSG #{recipient} :#{message}"
13
+ end
14
+
15
+ # register when connected
16
+ def register
17
+ raw "NICK #{@config.nick}"
18
+ raw "USER #{@config.username} * * :#{@config.real_name}"
19
+ raw "PASS #{@config.password}" if @config.password
20
+ end
21
+
22
+ # send a notice
23
+ def notice(recipient, message)
24
+ raw "NOTICE #{recipient} :#{message}"
25
+ end
26
+
27
+ # set a mode
28
+ def mode(recipient, option)
29
+ raw "MODE #{recipient} #{option}"
30
+ end
31
+
32
+ # kick a user
33
+ def kick(channel, user, reason = nil)
34
+ if reason
35
+ raw "KICK #{channel} #{user} :#{reason}"
36
+ else
37
+ raw "KICK #{channel} #{user}"
38
+ end
39
+ end
40
+
41
+ # perform an action
42
+ def action(recipient, message)
43
+ raw "PRIVMSG #{recipient} :\001ACTION #{message}\001"
44
+ end
45
+
46
+ # set a topic
47
+ def topic(channel, topic)
48
+ raw "TOPIC #{channel} :#{topic}"
49
+ end
50
+
51
+ # joining a channel
52
+ def join(channel, password = nil)
53
+ if password
54
+ raw "JOIN #{channel} #{password}"
55
+ else
56
+ raw "JOIN #{channel}"
57
+ end
58
+ end
59
+
60
+ # parting a channel
61
+ def part(channel, message = nil)
62
+ if message
63
+ raw "PART #{channel} :#{message}"
64
+ else
65
+ raw "PART #{channel}"
66
+ end
67
+ end
68
+
69
+ # quitting
70
+ def quit(message = nil)
71
+ if message
72
+ raw "QUIT :#{message}"
73
+ else
74
+ raw 'QUIT'
75
+ end
76
+
77
+ @config.reconnect = false # so Ponder does not reconnect after the socket has been closed
78
+ end
79
+
80
+ # rename
81
+ def rename(nick)
82
+ raw "NICK :#{nick}"
83
+ end
84
+
85
+ # set an away status
86
+ def away(message = nil)
87
+ if message
88
+ raw "AWAY :#{message}"
89
+ else
90
+ raw "AWAY"
91
+ end
92
+ end
93
+
94
+ # cancel an away status
95
+ def back
96
+ away
97
+ end
98
+
99
+ # invite an user to a channel
100
+ def invite(nick, channel)
101
+ raw "INVITE #{nick} #{channel}"
102
+ end
103
+
104
+ # ban an user
105
+ def ban(channel, address)
106
+ mode channel, "+b #{address}"
107
+ end
108
+ end
109
+ end
110
+
@@ -0,0 +1,11 @@
1
+ module Ponder
2
+ module Logger
3
+ class BlindIo
4
+ def initialize
5
+ [:debug, :info, :warn, :error, :fatal, :unknown, :start_logging, :stop_logging].each do |method_name|
6
+ self.class.send(:define_method, method_name, Proc.new { |*args| nil })
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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.key(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