Pulse 1.1.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.
- data/library/pulse/channel.rb +27 -0
- data/library/pulse/client.rb +97 -0
- data/library/pulse/command.rb +50 -0
- data/library/pulse/connection.rb +28 -0
- data/library/pulse/conversation.rb +30 -0
- data/library/pulse/enhancements.rb +5 -0
- data/library/pulse/handling.rb +87 -0
- data/library/pulse/queue.rb +28 -0
- data/library/pulse/script/cache.rb +50 -0
- data/library/pulse/script.rb +50 -0
- data/library/pulse/settings.rb +30 -0
- data/library/pulse/user.rb +22 -0
- data/library/pulse.rb +33 -0
- metadata +78 -0
@@ -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,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
|
+
|