Pulse 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ module Pulse
4
+ class Channel
5
+ attr_accessor :name, :users
6
+
7
+ def initialize name, client, users = []
8
+ @name, @client, @users = name, client, users
9
+
10
+ users.each { |user| user.channel = self }
11
+ end
12
+
13
+ def say message
14
+ @client.say self, message
15
+ end
16
+
17
+ def user name; @users.find { |user| user.name == name } end
18
+ def user? name; not user(name).nil? end
19
+
20
+ def to_s; @name end
21
+ def to_yaml opts = {}; @name.to_yaml opts end
22
+
23
+ def inspect
24
+ %{#<#{self.class.name} @name=#{@name.inspect} @users=#{@users.inspect}}
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,97 @@
1
+ # encoding: utf-8
2
+
3
+ require 'pulse/handling'
4
+
5
+ module Pulse
6
+ class Client
7
+ include Handling
8
+
9
+ attr_accessor :scripts
10
+
11
+ def initialize options
12
+ @scripts = []
13
+ @channels = {}
14
+ @settings = Settings.new options
15
+ @callbacks = {}
16
+ @connection = Connection.new self, @settings
17
+ @conversations = {}
18
+
19
+ load_scripts
20
+ end
21
+
22
+ def connect
23
+ trap 2 do
24
+ unload_scripts
25
+ transmit :QUIT, "I was interrupted"
26
+ end
27
+ @connection.establish
28
+ end
29
+
30
+ def got_command command
31
+ name = :"got_#{command.name.downcase}"
32
+ puts "<< #{command}"
33
+
34
+ if respond_to? name
35
+ __send__ name, command
36
+ end
37
+ end
38
+
39
+ def connection_established connection
40
+ puts "Connection has been established."
41
+
42
+ transmit :NICK, @settings.nickname
43
+ transmit :USER, @settings.username, ?*, ?*, @settings.realname
44
+ end
45
+
46
+ def connection_terminated connection
47
+ puts "The connection was terminated."
48
+ end
49
+
50
+ def say recipient, line
51
+ transmit :PRIVMSG, recipient.to_s, line
52
+ end
53
+
54
+ def load_scripts
55
+ @settings.scripts.each do |path|
56
+ @scripts.<< Script.new path, self
57
+ end
58
+ end
59
+
60
+ def unload_scripts
61
+ @scripts.each &:unload!
62
+ end
63
+
64
+ def each_user name
65
+ @channels.values.select { |c| c.user? name }.each do |channel|
66
+ yield channel.user name
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def emit name, *args
73
+ @callbacks[name].each do |callback|
74
+ callback.call *args
75
+ end if @callbacks[name]
76
+
77
+
78
+ begin
79
+ @scripts.each do |script|
80
+ if script.respond_to? name
81
+ script.__send__ name, *args
82
+ end
83
+ end
84
+ rescue
85
+ puts "Script error #$!"
86
+ end
87
+ end
88
+
89
+ def catch name, &block
90
+ (@callbacks[name] ||= []) << block
91
+ end
92
+
93
+ def transmit name, *args
94
+ @connection.transmit name, *args
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require 'ostruct'
4
+
5
+ module Pulse
6
+ class Command
7
+ attr_accessor :name, :params, :prefix
8
+
9
+ Pattern = /^(?:[:@]([^\s]+) )?([^\s]+)(?: ((?:[^:\s][^\s]* ?)*))?(?: ?:(.*))?$/
10
+
11
+ def self.parse data
12
+ match = data.strip.match Pattern
13
+ prefix, name, args, extra = match.captures
14
+ params = extra ? args.split << extra : args.split
15
+
16
+ new(name, params).tap do |this|
17
+ this.prefix = prefix
18
+ end
19
+ end
20
+
21
+ def initialize name, params = []
22
+ @name, @params = name, params
23
+ end
24
+
25
+ def [] index; @params[index] end
26
+
27
+ def sender
28
+ return @sender if @sender
29
+
30
+ if prefix =~ /^(\S+)!(\S+)@(\S+)$/
31
+ @sender = OpenStruct.new nickname: $1, username: $2, hostname: $3
32
+ else
33
+ @sender = prefix
34
+ end
35
+ end
36
+
37
+ def to_s
38
+ String.new.tap do |line|
39
+ line << "#{prefix} " if prefix
40
+ line << name.to_s
41
+
42
+ params.each_with_index do |param, index|
43
+ line << ' '
44
+ line << ?: if index == params.length - 1 and param =~ /[ :]/
45
+ line << param.to_s
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ module Pulse
4
+ class Connection
5
+ def initialize delegate, settings
6
+ @delegate = delegate
7
+ @settings = settings
8
+ @queue = Queue.new
9
+ end
10
+
11
+ def establish
12
+ TCPSocket.open @settings.hostname, @settings.port do |socket|
13
+ Thread.start socket, &@queue.method(:process)
14
+ @delegate.connection_established self
15
+
16
+ until socket.eof?
17
+ @delegate.got_command Command.parse socket.gets
18
+ end
19
+ end
20
+
21
+ @delegate.connection_terminated self
22
+ end
23
+
24
+ def transmit name, *arguments
25
+ Command.new(name, arguments).tap { |this| @queue << this }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ module Pulse
4
+ class Conversation
5
+ attr_accessor :id, :recipient
6
+
7
+ def initialize recipient, client = nil
8
+ @id, @recipient, @client = random_id, recipient, client
9
+ end
10
+
11
+ def say message
12
+ @client.say @recipient.name, message
13
+ end
14
+
15
+ def name; @recipient.name end
16
+
17
+ def inspect
18
+ %{#<#{self.class.name} @recipient=#{@recipient}>}
19
+ end
20
+
21
+ private
22
+ def random_id
23
+ chars = 'abcdefghijklmnopqrstu0123456789'
24
+
25
+ String.new.tap do |result|
26
+ 6.times { result << chars[rand chars.length] }
27
+ end.to_sym
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ class String
4
+ alias_method :starts_with?, :start_with?
5
+ end
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+
3
+ module Pulse
4
+ class Client
5
+ module Handling
6
+
7
+ protected
8
+ # End of MOTD
9
+ def got_end_of_motd command
10
+ emit :connection_ready, @connection
11
+ end
12
+
13
+ # The /NAMES list
14
+ def got_353 command
15
+ users = command[3].split.map &User.method(:new)
16
+
17
+ if channel = @channels[command[2]]
18
+ users.each &channel.users.method(:<<)
19
+ users.each { |user| user.channel = channel }
20
+ else
21
+ @channels[command[2]] ||= Channel.new(command[2], self, users)
22
+ end
23
+ p users, command[3]
24
+ end
25
+
26
+ # The IRCd is checking whether or not we're still alive
27
+ # PING :1285409133
28
+ # PONG :1285409133
29
+ def got_ping command
30
+ transmit :PONG, command[0]
31
+ end
32
+
33
+ # Someone has changed their nickname
34
+ # mk!mk@maero.dk NICK mk_
35
+ def got_nick command
36
+ each_user command.sender.nickname do |user|
37
+ emit :rename, user, command[0]
38
+ user.name = command[0]
39
+ end
40
+ end
41
+
42
+ # Someone has sent a message, it can be both a private message and
43
+ # a channel message
44
+ # mk!mk@maero.dk PRIVMSG #maero :tp: kod
45
+ def got_privmsg command
46
+ name, message = command.params
47
+
48
+ if channel = @channels[name]
49
+ user = channel.user command.sender.nickname
50
+ user.synchronize command.sender
51
+ emit :message, user, channel, message
52
+ else
53
+ user = User.new command.sender.nickname
54
+
55
+ (@conversations[command.sender.nickname] ||= Conversation.new user, self).tap do |conversation|
56
+ user.channel = conversation
57
+ emit :conversation, user, conversation, message
58
+ end
59
+ end
60
+ end
61
+
62
+
63
+ # Someone has entered a channel.
64
+ def got_join command
65
+ if channel = @channels[command[0]]
66
+ user = User.new command.sender.nickname
67
+ user.channel = channel
68
+ user.synchronize command.sender
69
+ channel.users << user
70
+ emit :user_joined, user, channel
71
+ end
72
+ end
73
+
74
+ # Someone has left a channel.
75
+ def got_part command
76
+ if channel = @channels[command[0]]
77
+ user = channel.user command.sender.nickname
78
+ channel.users.delete user
79
+ emit :user_left, user, channel
80
+ end
81
+ end
82
+
83
+ alias_method :got_422, :got_end_of_motd
84
+ alias_method :got_376, :got_end_of_motd
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ module Pulse
4
+ class Queue
5
+ def initialize
6
+ @queue = []
7
+ end
8
+
9
+ def process socket
10
+ @socket ||= socket
11
+ @thread = Thread.current
12
+
13
+ while @thread.alive?
14
+ if command = @queue.shift
15
+ puts ">> #{command}"
16
+ @socket.write "#{command}\n"
17
+ else
18
+ Thread.stop
19
+ end
20
+ end
21
+ end
22
+
23
+ def << command
24
+ @queue << command
25
+ @thread.run if @thread
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ module Pulse
4
+ class Script
5
+ class Cache
6
+ def initialize script
7
+ @script = script
8
+ @containers = {}
9
+ end
10
+
11
+ def save
12
+ create_directory
13
+
14
+ File.open path, ?w do |file|
15
+ YAML.dump @containers, file
16
+ end
17
+
18
+ puts "Dumping cache for script #{@script.inspect} to #{path} …"
19
+ end
20
+
21
+ def load
22
+ if yaml = YAML.load_file(path)
23
+ @containers = yaml
24
+
25
+ puts "Imported cache from #{path} …"
26
+ end
27
+ rescue
28
+
29
+ puts "The cache is corrupt. Removing."
30
+ File.unlink path
31
+ end
32
+
33
+ def containers; @containers.keys end
34
+ def clear sure = false; @containers.clear if sure end
35
+
36
+ def [] key; @containers[key] ||= {} end
37
+ def []= key, value; @containers[key] ||= value end
38
+
39
+ private
40
+
41
+ def create_directory
42
+ Dir.mkdir File.dirname path unless File.directory? File.dirname path
43
+ end
44
+
45
+ def path
46
+ "#{File.dirname File.expand_path $0}/cache/#{@script.name}.yml"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ module Pulse
4
+ class Script < Module
5
+
6
+ attr_accessor :name, :author, :version
7
+
8
+ def initialize path, client
9
+ @path, @client = path, client
10
+ @loaded = false
11
+
12
+ evaluate_script
13
+ cache.load if has_cache?
14
+ __send__ :loaded if respond_to? :loaded
15
+ end
16
+
17
+ def Script name, version = [1, 0, 0], author = nil, &block
18
+ @name, @version, @author = name, version, author
19
+ instance_eval &block
20
+ end
21
+
22
+ def loaded?; @loaded == true end
23
+
24
+ def unload!
25
+ cache.save if @cache
26
+ @client.scripts.delete self
27
+ __send__ :unloaded if respond_to? :unloaded
28
+ end
29
+
30
+ def to_yaml opts = {}; @name.to_yaml opts end
31
+
32
+ def inspect
33
+ %{#<#{self.class.name} @name=#{@name.inspect} @version=#{@version.inspect} @author=#{@author.inspect}}
34
+ end
35
+
36
+ def has_cache?
37
+ File.exists? "#{File.expand_path File.dirname $0}/cache/#@name.yml"
38
+ end
39
+
40
+ private
41
+
42
+ def evaluate_script
43
+ @loaded = true if module_eval File.read(@path), File.basename(@path), 0
44
+ rescue
45
+ puts "Parse error: #{$!.message}"
46
+ end
47
+
48
+ def cache; @cache ||= Cache.new self end
49
+ end
50
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ module Pulse
4
+ class Settings < Hash
5
+ class Error < StandardError; end
6
+
7
+ attr_accessor :nickname, :username, :realname, :hostname, :password
8
+
9
+ def initialize options = {}
10
+ @options = options
11
+
12
+ @nickname = options[:nickname] or raise Error, 'No nickname given'
13
+ @username = options[:username] || @nickname
14
+ @realname = options[:realname] || @username
15
+ @hostname = options[:hostname] or raise Error, 'No hostname given'
16
+
17
+ @script_path = options[:path] || File.expand_path(File.dirname $0) + '/scripts'
18
+ end
19
+
20
+ def port; @options[:port] or secure ? 6697 : 6667 end
21
+ def secure?; @options[:secure] == true end
22
+ def password?; not @options[:password].nil? end
23
+
24
+ def scripts
25
+ Dir.glob "#@script_path/*.rb"
26
+ end
27
+
28
+ def method_missing name, *args; @options[name] end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ module Pulse
4
+ class User
5
+ attr_accessor :name, :user, :host, :channel
6
+
7
+ def initialize name
8
+ @name = name.sub /^\W/, ''
9
+ end
10
+
11
+ def synchronize sender
12
+ @name, @user, @host = sender.nickname, sender.username, sender.hostname
13
+ end
14
+
15
+ def to_s; @name end
16
+ def to_yaml opts = {}; @name.to_yaml opts end
17
+
18
+ def inspect
19
+ %{#<#{self.class.name} @name=#{@name.inspect} @channel=#{@channel.name.inspect}>}
20
+ end
21
+ end
22
+ end
data/library/pulse.rb ADDED
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+ require 'socket'
5
+ require 'pulse/user'
6
+ require 'pulse/queue'
7
+ require 'pulse/client'
8
+ require 'pulse/script'
9
+ require 'pulse/channel'
10
+ require 'pulse/command'
11
+ require 'pulse/settings'
12
+ require 'pulse/connection'
13
+ require 'pulse/enhancements'
14
+ require 'pulse/conversation'
15
+ require 'pulse/script/cache'
16
+
17
+ Thread.abort_on_exception = true
18
+
19
+ module Pulse
20
+ class << Version = [1,1]
21
+ def to_s; join ?. end
22
+ end
23
+
24
+ class << self
25
+ def connect options, &block
26
+ puts "=> Pulse #{Pulse::Version}"
27
+
28
+ Client.new(options).tap do |client|
29
+ client.instance_eval &block
30
+ end.connect
31
+ end
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Pulse
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 1
8
+ - 0
9
+ version: 1.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Mikkel Kroman
13
+ autorequire:
14
+ bindir: executables
15
+ cert_chain: []
16
+
17
+ date: 2010-11-25 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email: mk@maero.dk
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - library/pulse/client.rb
31
+ - library/pulse/handling.rb
32
+ - library/pulse/connection.rb
33
+ - library/pulse/script.rb
34
+ - library/pulse/queue.rb
35
+ - library/pulse/command.rb
36
+ - library/pulse/settings.rb
37
+ - library/pulse/script/cache.rb
38
+ - library/pulse/user.rb
39
+ - library/pulse/conversation.rb
40
+ - library/pulse/channel.rb
41
+ - library/pulse/enhancements.rb
42
+ - library/pulse.rb
43
+ has_rdoc: true
44
+ homepage:
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options: []
49
+
50
+ require_paths:
51
+ - library
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 1
59
+ - 9
60
+ - 1
61
+ version: 1.9.1
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.3.7
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: Extensible IRC library
77
+ test_files: []
78
+