ahoy 0.0.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.
- 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
|
+
|