ahoy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/ahoy.rb +18 -0
- data/lib/ahoy/broadcast.rb +26 -0
- data/lib/ahoy/chat.rb +58 -0
- data/lib/ahoy/contact.rb +59 -0
- data/lib/ahoy/contact_list.rb +76 -0
- data/lib/ahoy/errors.rb +3 -0
- data/lib/ahoy/user.rb +57 -0
- data/lib/ahoy/xmpp4r_hack.rb +41 -0
- metadata +82 -0
data/lib/ahoy.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
base = File.expand_path("#{File.dirname(__FILE__)}/ahoy")
|
2
|
+
libs = %W{chat contact contact_list errors user}
|
3
|
+
|
4
|
+
libs.each do |lib|
|
5
|
+
require "#{base}/#{lib}"
|
6
|
+
end
|
7
|
+
|
8
|
+
module Ahoy
|
9
|
+
SERVICE_TYPE = "_presence._tcp"
|
10
|
+
|
11
|
+
def self.more_coming?(reply)
|
12
|
+
reply.flags.to_i & DNSSD::Flags::MoreComing > 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.add?(reply)
|
16
|
+
reply.flags.to_i & DNSSD::Flags::Add > 0
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../ahoy")
|
2
|
+
|
3
|
+
module Ahoy
|
4
|
+
class Broadcast
|
5
|
+
def initialize(name, location="nowhere", domain="local")
|
6
|
+
user = Ahoy::User.new(name, location, domain)
|
7
|
+
user.sign_in
|
8
|
+
sleep 1
|
9
|
+
@chats = user.contacts.map {|contact| user.chat(contact)}
|
10
|
+
end
|
11
|
+
|
12
|
+
def send(message)
|
13
|
+
@chats.each do |chat|
|
14
|
+
begin
|
15
|
+
chat.send(message)
|
16
|
+
rescue
|
17
|
+
@chats.delete(chat)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def close
|
23
|
+
@chats.each {|chat| chat.close}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/ahoy/chat.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'xmpp4r'
|
3
|
+
require File.expand_path("#{File.dirname(__FILE__)}/xmpp4r_hack")
|
4
|
+
|
5
|
+
module Ahoy
|
6
|
+
class Chat
|
7
|
+
attr_reader :user, :contact, :client
|
8
|
+
private :client
|
9
|
+
|
10
|
+
def initialize(user, contact)
|
11
|
+
@user = user
|
12
|
+
@contact = contact
|
13
|
+
@client = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
# May raise Ahoy::ContactOfflineError
|
17
|
+
#
|
18
|
+
def start
|
19
|
+
user.contact.resolve.getaddrinfo
|
20
|
+
connect
|
21
|
+
end
|
22
|
+
|
23
|
+
# May raise Ahoy::ContactOfflineError
|
24
|
+
#
|
25
|
+
def send(message)
|
26
|
+
start unless client
|
27
|
+
|
28
|
+
message = Jabber::Message.new(contact.name, message)
|
29
|
+
message.type = :chat
|
30
|
+
begin
|
31
|
+
@client.send(message)
|
32
|
+
rescue IOError
|
33
|
+
connect
|
34
|
+
retry
|
35
|
+
end
|
36
|
+
message
|
37
|
+
end
|
38
|
+
|
39
|
+
def close
|
40
|
+
@client.close
|
41
|
+
@client = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def connect
|
46
|
+
contact.resolve
|
47
|
+
|
48
|
+
@client = Jabber::Client.new(Jabber::JID.new(user.name))
|
49
|
+
sleep 0.5
|
50
|
+
begin
|
51
|
+
@client.connect(contact.target, contact.port, user.contact.ip)
|
52
|
+
rescue Errno::ECONNREFUSED
|
53
|
+
raise Ahoy::ContactOfflineError.new("Contact Offline")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
data/lib/ahoy/contact.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'dnssd'
|
3
|
+
|
4
|
+
module Ahoy
|
5
|
+
class Contact
|
6
|
+
attr_reader :name, :domain, :target, :ip, :port, :interface
|
7
|
+
attr_accessor :online
|
8
|
+
|
9
|
+
def initialize(name, domain="local")
|
10
|
+
@name = name
|
11
|
+
@domain = domain
|
12
|
+
@target = nil
|
13
|
+
@ip = nil
|
14
|
+
@port = nil
|
15
|
+
@interface = nil
|
16
|
+
@online = true
|
17
|
+
end
|
18
|
+
|
19
|
+
def fullname
|
20
|
+
[name, Ahoy::SERVICE_TYPE, domain].join(".")
|
21
|
+
end
|
22
|
+
|
23
|
+
def ==(other)
|
24
|
+
other.is_a?(self.class) && other.fullname == fullname
|
25
|
+
end
|
26
|
+
|
27
|
+
def online?
|
28
|
+
online
|
29
|
+
end
|
30
|
+
|
31
|
+
def resolve
|
32
|
+
service = DNSSD::Service.new
|
33
|
+
main = Thread.current
|
34
|
+
service.resolve(name, Ahoy::SERVICE_TYPE, domain) do |resolved|
|
35
|
+
next if Ahoy::more_coming?(resolved)
|
36
|
+
service.stop unless service.stopped?
|
37
|
+
@target = resolved.target
|
38
|
+
@port = resolved.port
|
39
|
+
@interface = resolved.interface
|
40
|
+
main.run
|
41
|
+
end
|
42
|
+
Thread.stop unless service.stopped?
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def getaddrinfo
|
47
|
+
service = DNSSD::Service.new
|
48
|
+
main = Thread.current
|
49
|
+
service.getaddrinfo(target, DNSSD::Service::IPv4, 0, interface) do |addressed|
|
50
|
+
service.stop unless service.stopped?
|
51
|
+
@ip = addressed.address
|
52
|
+
main.run
|
53
|
+
end
|
54
|
+
Thread.stop unless service.stopped?
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'weakref'
|
3
|
+
|
4
|
+
module Ahoy
|
5
|
+
class ContactList
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
attr_reader :list, :weak_list, :lock, :user
|
9
|
+
private :list, :weak_list, :lock
|
10
|
+
|
11
|
+
def initialize(user)
|
12
|
+
@user = user
|
13
|
+
@list = []
|
14
|
+
@weak_list = []
|
15
|
+
@lock = Mutex.new
|
16
|
+
|
17
|
+
start_browse
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&block)
|
21
|
+
lock.synchronize do
|
22
|
+
list.each(&block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def start_browse
|
28
|
+
DNSSD.browse(Ahoy::SERVICE_TYPE) do |browsed|
|
29
|
+
# next if Ahoy::more_coming?(browsed)
|
30
|
+
if Ahoy::add?(browsed) && browsed.name != user.name
|
31
|
+
add(Ahoy::Contact.new(browsed.name, browsed.domain))
|
32
|
+
elsif Ahoy::add?(browsed)
|
33
|
+
user.contact = Ahoy::Contact.new(browsed.name, browsed.domain)
|
34
|
+
else
|
35
|
+
remove(browsed.fullname)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def add(contact)
|
41
|
+
lock.synchronize do
|
42
|
+
unless list.find {|in_list| contact == in_list}
|
43
|
+
contact ||= find_in_weak_list(contact)
|
44
|
+
contact.online = true
|
45
|
+
list.push(contact)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove(fullname)
|
51
|
+
fullname = fullname.fullname if fullname.respond_to?(:fullname)
|
52
|
+
lock.synchronize do
|
53
|
+
contact = list.find {|c| c.fullname == fullname}
|
54
|
+
if contact
|
55
|
+
list.delete(contact)
|
56
|
+
contact.online = false
|
57
|
+
weak_list.push(WeakRef.new(contact))
|
58
|
+
contact
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def find_in_weak_list(contact)
|
64
|
+
existing_contact = nil
|
65
|
+
Thread.exclusive do
|
66
|
+
GC.disable
|
67
|
+
weak_list.select! {|ref| ref.weakref_alive?}
|
68
|
+
contact_ref = weak_list.find {|ref| contact == ref}
|
69
|
+
existing_contact = contact_ref.__getobj__
|
70
|
+
GC.enable
|
71
|
+
end
|
72
|
+
existing_contact
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
data/lib/ahoy/errors.rb
ADDED
data/lib/ahoy/user.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'dnssd'
|
3
|
+
|
4
|
+
module Ahoy
|
5
|
+
class User
|
6
|
+
attr_reader :short_name, :location, :domain, :contacts
|
7
|
+
attr_accessor :port, :flags, :interface, :contact
|
8
|
+
|
9
|
+
def initialize(name, location="nowhere", domain="local")
|
10
|
+
@short_name = name
|
11
|
+
@location = location
|
12
|
+
@domain = domain
|
13
|
+
|
14
|
+
@contacts = Ahoy::ContactList.new(self)
|
15
|
+
@contact = nil
|
16
|
+
|
17
|
+
@port = 5562
|
18
|
+
@flags = 0
|
19
|
+
@interface = "en0"
|
20
|
+
end
|
21
|
+
|
22
|
+
def name
|
23
|
+
"#{short_name}@#{location}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def sign_in(status="avail", msg=nil)
|
27
|
+
@registrar = DNSSD.register(
|
28
|
+
name,
|
29
|
+
Ahoy::SERVICE_TYPE,
|
30
|
+
domain,
|
31
|
+
port,
|
32
|
+
txt_record(status, msg),
|
33
|
+
flags.to_i,
|
34
|
+
interface)
|
35
|
+
end
|
36
|
+
|
37
|
+
def contact
|
38
|
+
sleep 0.01 until @contact
|
39
|
+
@contact
|
40
|
+
end
|
41
|
+
|
42
|
+
def chat(contact)
|
43
|
+
Ahoy::Chat.new(self, contact)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def txt_record(status, msg)
|
48
|
+
DNSSD::TextRecord.new(
|
49
|
+
"txtvers" => 1,
|
50
|
+
"port.p2pj" => port,
|
51
|
+
"status" => status,
|
52
|
+
"msg" => msg,
|
53
|
+
"1st" => short_name)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'xmpp4r'
|
3
|
+
|
4
|
+
module Jabber
|
5
|
+
class Connection
|
6
|
+
def connect(host, port, local_host=nil, local_port=nil)
|
7
|
+
@host = host
|
8
|
+
@port = port
|
9
|
+
# Reset is_tls?, so that it works when reconnecting
|
10
|
+
@tls = false
|
11
|
+
|
12
|
+
Jabber::debuglog("CONNECTING:\n#{@host}:#{@port}, local #{local_host}:#{local_port}")
|
13
|
+
@socket = TCPSocket.new(@host, @port, local_host, local_port)
|
14
|
+
|
15
|
+
# We want to use the old and deprecated SSL protocol (usually on port 5223)
|
16
|
+
if @use_ssl
|
17
|
+
ssl = OpenSSL::SSL::SSLSocket.new(@socket)
|
18
|
+
ssl.connect # start SSL session
|
19
|
+
ssl.sync_close = true
|
20
|
+
Jabber::debuglog("SSL connection established.")
|
21
|
+
@socket = ssl
|
22
|
+
end
|
23
|
+
|
24
|
+
start
|
25
|
+
|
26
|
+
accept_features
|
27
|
+
|
28
|
+
@keepaliveThread = Thread.new do
|
29
|
+
Thread.current.abort_on_exception = true
|
30
|
+
keepalive_loop
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Client
|
36
|
+
def connect(host, port, local_host=nil, local_port=nil)
|
37
|
+
super
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ahoy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mat Sadler
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-27 00:00:00 +00:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: dnssd
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.3.1
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: xmpp4r
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0.5"
|
34
|
+
version:
|
35
|
+
description: Serverless Messaging using DNSDS/mDNS, XMPP, and Ruby
|
36
|
+
email: mat@sourcetagsandcodes.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- lib/ahoy/broadcast.rb
|
45
|
+
- lib/ahoy/chat.rb
|
46
|
+
- lib/ahoy/contact.rb
|
47
|
+
- lib/ahoy/contact_list.rb
|
48
|
+
- lib/ahoy/errors.rb
|
49
|
+
- lib/ahoy/user.rb
|
50
|
+
- lib/ahoy/xmpp4r_hack.rb
|
51
|
+
- lib/ahoy.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://sourcetagsandcodes.com
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --inline-source
|
59
|
+
- --charset=UTF-8
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.3.5
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Bonjour Chat for Ruby
|
81
|
+
test_files: []
|
82
|
+
|