ahoy 0.0.2 → 0.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.
@@ -8,11 +8,7 @@ end
8
8
  module Ahoy
9
9
  SERVICE_TYPE = "_presence._tcp"
10
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
11
+ class << self
12
+ attr_accessor :use_markdown # default for Chat#use_markdown
17
13
  end
18
14
  end
@@ -1,13 +1,31 @@
1
1
  require File.expand_path("#{File.dirname(__FILE__)}/../ahoy")
2
2
 
3
3
  module Ahoy
4
+
5
+ # Ahoy::Broadcast provides a simple interface to send a message to all online
6
+ # users. Example:
7
+ # user = Ahoy::User.new("Dr. Nick")
8
+ # cast = Ahoy::Broadcast.new(user)
9
+ #
10
+ # cast.send("Hi, everybody!")
11
+ # cast.close
12
+ #
4
13
  class Broadcast
14
+
15
+ # :call-seq: Broadcast.new(user) -> broadcast
16
+ #
17
+ # Create a new Ahoy::Broadcast.
18
+ #
5
19
  def initialize(user)
6
20
  user.sign_in
7
21
  sleep 1
8
22
  @chats = user.contacts.map {|contact| user.chat(contact)}
9
23
  end
10
24
 
25
+ # :call-seq: broadcast.send(string) -> array
26
+ #
27
+ # Send string to all online contacts.
28
+ #
11
29
  def send(message)
12
30
  @chats.each do |chat|
13
31
  begin
@@ -18,6 +36,10 @@ module Ahoy
18
36
  end
19
37
  end
20
38
 
39
+ # :call-seq: broadcast.close -> close
40
+ #
41
+ # End all chats.
42
+ #
21
43
  def close
22
44
  @chats.each {|chat| chat.close}
23
45
  end
@@ -1,44 +1,68 @@
1
1
  require 'rubygems'
2
2
  require 'xmpp4r'
3
- require File.expand_path("#{File.dirname(__FILE__)}/xmpp4r_hack")
4
3
 
5
4
  module Ahoy
5
+
6
+ # Ahoy::Chat models a conversation between the user and one of their contacts.
7
+ # It can be thought of as representing an iChat chat window, or treated more
8
+ # like a Ruby socket.
9
+ #
6
10
  class Chat
7
- attr_reader :user, :contact
8
- attr_accessor :client
9
- protected :client, :client=
11
+ attr_reader :contact_name
10
12
 
11
- def initialize(user, contact)
12
- @user = user
13
- @contact = contact
13
+ # :call-seq: Chat.new(contact_name) -> chat
14
+ #
15
+ # Create a new Ahoy::Chat.
16
+ #
17
+ def initialize(contact_name)
18
+ @contact_name = contact_name
14
19
  @client = nil
20
+ self.use_markdown = Ahoy.use_markdown
21
+ end
22
+
23
+ # :call-seq: chat.connect(target, port) -> chat
24
+ # chat.connect(socket) -> chat
25
+ #
26
+ # Connect to target on port, or use the connection provided by socket.
27
+ #
28
+ def connect(host, port=nil)
29
+ if host.respond_to?(:read) && host.respond_to?(:write)
30
+ connect_with(host)
31
+ else
32
+ begin
33
+ client.connect(host, port)
34
+ rescue Errno::ECONNREFUSED
35
+ raise Ahoy::ContactOfflineError.new("Contact Offline")
36
+ end
37
+ end
38
+ self
15
39
  end
16
40
 
17
- # May raise Ahoy::ContactOfflineError
41
+ # :call-seq: chat.connected? -> bool
18
42
  #
19
- def start
20
- user.contact.resolve.getaddrinfo
21
- connect
43
+ def connected?
44
+ client.is_connected?
22
45
  end
23
46
 
24
- # May raise Ahoy::ContactOfflineError
47
+ # :call-seq: chat.send(string) -> message
48
+ #
49
+ # Send string to contact. May raise Ahoy::ContactOfflineError.
25
50
  #
26
- def send(message)
27
- start unless client
51
+ def send(text)
52
+ raise Ahoy::NotConnectedError.new("Not Connected") unless connected?
28
53
 
29
- message = Jabber::Message.new(contact.name, message)
54
+ message = Jabber::Message.new(contact_name, text)
30
55
  message.type = :chat
31
- begin
32
- client.send(message)
33
- rescue IOError
34
- connect
35
- retry
36
- end
56
+ markdown(message) if markdown?
57
+ client.send(message)
37
58
  message
38
59
  end
39
60
 
61
+ # :call-seq: chat.on_reply {|string| block }
62
+ #
63
+ # Set up block as a callback for when a message is received.
64
+ #
40
65
  def on_reply(&block)
41
- start unless client
42
66
  client.delete_message_callback("on_reply")
43
67
 
44
68
  client.add_message_callback(0, "on_reply") do |message|
@@ -46,8 +70,12 @@ module Ahoy
46
70
  end
47
71
  end
48
72
 
49
- def receive
50
- start unless client
73
+ # :call-seq: chat.receive -> string
74
+ #
75
+ # Block until a message is received, then return the message body as a
76
+ # string.
77
+ #
78
+ def receive(*ignore_args)
51
79
  thread = Thread.current
52
80
  reply = nil
53
81
 
@@ -62,23 +90,108 @@ module Ahoy
62
90
  client.delete_message_callback("receive")
63
91
  reply
64
92
  end
93
+ alias read receive
94
+ alias sysread receive
95
+ alias gets receive
96
+ alias readline receive
65
97
 
98
+ # :call-seq: chat.close -> nil
99
+ #
100
+ # End the chat.
101
+ #
66
102
  def close
67
103
  client.close
68
- self.client = nil
104
+ @client = nil
69
105
  end
70
106
 
71
- private
72
- def connect
73
- contact.resolve
74
-
75
- self.client = Jabber::Client.new(Jabber::JID.new(user.name))
76
- sleep 0.5
77
- begin
78
- client.connect(contact.target, contact.port, user.contact.ip)
79
- rescue Errno::ECONNREFUSED
80
- raise Ahoy::ContactOfflineError.new("Contact Offline")
107
+ # :call-seq: chat.use_markdown = bool -> bool
108
+ #
109
+ # Set true to send a html copy of messages, by interpreting the message text
110
+ # as markdown.
111
+ #
112
+ def use_markdown=(value)
113
+ @use_markdown = value
114
+ if value && !markdown_processor
115
+ %W{rdiscount kramdown maruku bluecloth}.each do |lib|
116
+ begin
117
+ require lib
118
+ break
119
+ rescue LoadError
120
+ end
121
+ end
81
122
  end
123
+ value
124
+ end
125
+
126
+ # :call-seq: chat.markdown? -> bool
127
+ #
128
+ # Are messages sent to this chat being interpreted as markdown?
129
+ #
130
+ def markdown?
131
+ @use_markdown && markdown_processor
132
+ end
133
+ alias use_markdown markdown?
134
+
135
+ # :call-seq: chat << string -> chat
136
+ #
137
+ # See #send
138
+ #
139
+ def <<(string)
140
+ send(string)
141
+ self
142
+ end
143
+
144
+ # :call-seq: chat.puts(string) -> nil
145
+ #
146
+ # See #send
147
+ #
148
+ def puts(string)
149
+ send(string)
150
+ nil
151
+ end
152
+
153
+ # :call-seq: chat.write -> integer
154
+ #
155
+ # See #send
156
+ #
157
+ def write(string)
158
+ send(string)
159
+ string.length
160
+ end
161
+ alias syswrite write
162
+
163
+ private
164
+ def connect_with(socket)
165
+ client.instance_variable_set(:@socket, socket)
166
+ client.start
167
+ client.accept_features
168
+ client.instance_variable_set(:@keepaliveThread, Thread.new do
169
+ Thread.current.abort_on_exception = true
170
+ client.__send__(:keepalive_loop)
171
+ end)
172
+ end
173
+
174
+ def client
175
+ return @client if @client
176
+ @client = Jabber::Client.new(nil)
177
+ @client.features_timeout = 0.001
178
+ @client
179
+ end
180
+
181
+ def markdown(message)
182
+ html = REXML::Element.new("html")
183
+ html.add_attribute("xmlns", "http://www.w3.org/1999/xhtml")
184
+ body = html.add_element("body")
185
+ markdown = markdown_processor.new(message.body)
186
+ body.add_element(REXML::Document.new(markdown.to_html))
187
+ message.add_element(html)
188
+ end
189
+
190
+ def markdown_processor
191
+ return RDiscount if defined?(RDiscount)
192
+ return Kramdown::Document if defined?(Kramdown::Document)
193
+ return Maruku if defined?(Maruku)
194
+ return BlueCloth if defined?(BlueCloth)
82
195
  end
83
196
 
84
197
  end
@@ -2,56 +2,134 @@ require 'rubygems'
2
2
  require 'dnssd'
3
3
 
4
4
  module Ahoy
5
+
6
+ # Ahoy::Contact represents another user or system, available to recieve
7
+ # messages, or who may send them to our user.
8
+ #
5
9
  class Contact
6
- attr_reader :name, :domain, :target, :ip, :port, :interface
10
+ attr_reader :name, :domain
7
11
  attr_accessor :online
12
+ alias online? online
8
13
 
9
- def initialize(name, domain="local")
14
+ # :call-seq: Contact.new(name, domain="local.") -> contact
15
+ #
16
+ # Create a new Ahoy::Contact. name should be in name@location format.
17
+ #
18
+ def initialize(name, domain="local.")
10
19
  @name = name
11
20
  @domain = domain
12
21
  @target = nil
13
- @ip = nil
14
22
  @port = nil
15
- @interface = nil
23
+ @interface_addresses = {}
16
24
  @online = true
17
25
  end
18
26
 
27
+ # :call-seq: contact.fullname -> string
28
+ #
29
+ # Returns the contact's full name in name@location.service.domain format
30
+ #
19
31
  def fullname
20
32
  [name, Ahoy::SERVICE_TYPE, domain].join(".")
21
33
  end
22
34
 
23
- def ==(other)
24
- other.is_a?(self.class) && other.fullname == fullname
35
+ # :call-seq: contact.target -> string
36
+ #
37
+ # Return the contact's target attribute. Pass true as the argument to use
38
+ # the cached value rather than looking it up.
39
+ #
40
+ def target(use_cache=nil)
41
+ resolve(use_cache)
42
+ @target
25
43
  end
26
44
 
27
- def online?
28
- online
45
+ # :call-seq: contact.port -> string
46
+ #
47
+ # Return the contact's port attribute. Pass true as the argument to use
48
+ # the cached value rather than looking it up.
49
+ #
50
+ def port(use_cache=nil)
51
+ resolve(use_cache)
52
+ @port
29
53
  end
30
54
 
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?
55
+ # :call-seq: contact.interfaces -> array
56
+ #
57
+ # Return the contact's interfaces. Pass true as the argument to use the
58
+ # cached value rather than looking it up.
59
+ #
60
+ def interfaces(use_cache=nil)
61
+ resolve(use_cache)
62
+ @interface_addresses.keys
63
+ end
64
+
65
+ # Internal use only.
66
+ #
67
+ def add_interface(name) # :nodoc:
68
+ @interface_addresses[name] = [] unless @interface_addresses.key?(name)
69
+ end
70
+
71
+ # :call-seq: contact.ip_addresses(interface=nil)
72
+ #
73
+ # Returns all of contact's IP addresses, or if an interface is supplied as
74
+ # an argument, just the IP addresses for that interface.
75
+ #
76
+ # Pass true as the second argument to prevent a lookup of interfaces, pass
77
+ # true as the third argument to prevent a lookup of IP addresses, and
78
+ # instead use the cached value.
79
+ #
80
+ def ip_addresses(interface=nil, resolve_cache=nil, use_cache=nil)
81
+ getaddrinfo(resolve_cache) unless use_cache
82
+ if interface
83
+ @interface_addresses[interface]
84
+ else
85
+ @interface_addresses.values.flatten
86
+ end
87
+ end
88
+
89
+ # :call-seq: contact == other_contact -> bool
90
+ #
91
+ # Equality. Two contacts are equal if they have the same fullname (and
92
+ # therefore name, location, service, and domain).
93
+ #
94
+ def ==(other)
95
+ other.is_a?(self.class) && other.fullname == fullname
96
+ end
97
+
98
+ # :call-seq: contact.resolve -> contact
99
+ #
100
+ # Determine and set the contact's target, port, and interfaces.
101
+ #
102
+ def resolve(use_cache=nil)
103
+ if use_cache && @target && @port && @interface_addresses.keys.any?
104
+ return self
105
+ end
106
+ @interface_addresses.clear
107
+ DNSSD::Service.new.resolve(name, Ahoy::SERVICE_TYPE, domain) do |resolved|
37
108
  @target = resolved.target
38
109
  @port = resolved.port
39
- @interface = resolved.interface
40
- main.run
110
+ @interface_addresses[resolved.interface] = []
111
+ break unless resolved.flags.more_coming?
41
112
  end
42
- Thread.stop unless service.stopped?
43
113
  self
44
114
  end
45
115
 
46
- def getaddrinfo
116
+ # :call-seq: contact.getaddrinfo(interface=nil) -> self
117
+ #
118
+ # Determine and set the contact's IP addresses. If an interface is passed,
119
+ # only lookup the IP addresses for that interface.
120
+ #
121
+ # Pass true as the second argument to prevent a resolve.
122
+ #
123
+ def getaddrinfo(interface=nil, resolve_cache=nil)
124
+ unless interface
125
+ interfaces(resolve_cache).each {|inter| getaddrinfo(inter, true)}
126
+ return self
127
+ end
47
128
  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
129
+ service.getaddrinfo(target(resolve_cache), 0, 0, interface) do |addressed|
130
+ @interface_addresses[addressed.interface].push(addressed.address)
131
+ break unless addressed.flags.more_coming?
53
132
  end
54
- Thread.stop unless service.stopped?
55
133
  self
56
134
  end
57
135
 
@@ -2,14 +2,23 @@ require 'thread'
2
2
  require 'weakref'
3
3
 
4
4
  module Ahoy
5
+
6
+ # Ahoy::ContactList is a self-populating collection of Contacts, and provides
7
+ # methods to retrieve and iterate over its contents.
8
+ #
5
9
  class ContactList
6
10
  include Enumerable
7
11
 
8
- attr_reader :list, :weak_list, :lock, :user
12
+ attr_reader :list, :weak_list, :lock, :user_name
9
13
  private :list, :weak_list, :lock
10
14
 
11
- def initialize(user)
12
- @user = user
15
+ # :call-seq: ContactList.new(user_name=nil) -> contact_list
16
+ #
17
+ # Create a new Ahoy::ContactList. Provide a username as the argument to
18
+ # avoid adding our user to the list.
19
+ #
20
+ def initialize(user_name=nil)
21
+ @user_name = user_name
13
22
  @list = []
14
23
  @weak_list = []
15
24
  @lock = Mutex.new
@@ -17,38 +26,52 @@ module Ahoy
17
26
  start_browse
18
27
  end
19
28
 
29
+ # :call-seq: contact_list.each {|contact| block } -> contact_list
30
+ #
31
+ # Calls block once for each contact in the contact list.
32
+ #
20
33
  def each(&block)
21
- lock.synchronize do
22
- list.each(&block)
23
- end
34
+ lock.synchronize {list.each(&block)}
35
+ self
36
+ end
37
+
38
+ # :call-seq: contact_list[name] -> contact or nil
39
+ #
40
+ # Returns the first contact who's fullname or name matches name.
41
+ #
42
+ # The case equality operator (===) is used in the comparison, so strings or
43
+ # regexps can be used as the argument.
44
+ #
45
+ def [](name)
46
+ find {|c| name === c.fullname || name === c.name}
47
+ end
48
+ alias find_by_name []
49
+
50
+ # :call-seq: contact_list.find_by_ip(string) -> contact or nil
51
+ #
52
+ # Returns the first contact with the ip address matching string.
53
+ #
54
+ def find_by_ip(ip)
55
+ find {|contact| contact.ip_addresses.include?(ip)}
24
56
  end
25
57
 
26
58
  private
27
59
  def start_browse
28
60
  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)
61
+ if browsed.flags.add? && browsed.name != user_name
62
+ existing = self[browsed.fullname]
63
+ contact = existing || find_in_weak_list(browsed.fullname) ||
64
+ Ahoy::Contact.new(browsed.name, browsed.domain)
65
+ contact.online = true
66
+ contact.add_interface(browsed.interface)
67
+ lock.synchronize {list.push(contact)} unless existing
34
68
  else
35
69
  remove(browsed.fullname)
36
70
  end
37
71
  end
38
72
  end
39
73
 
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
74
  def remove(fullname)
51
- fullname = fullname.fullname if fullname.respond_to?(:fullname)
52
75
  lock.synchronize do
53
76
  contact = list.find {|c| c.fullname == fullname}
54
77
  if contact
@@ -60,16 +83,17 @@ module Ahoy
60
83
  end
61
84
  end
62
85
 
63
- def find_in_weak_list(contact)
64
- existing_contact = nil
86
+ def find_in_weak_list(fullname)
65
87
  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
88
+ begin
89
+ GC.disable
90
+ weak_list.reject! {|ref| !ref.weakref_alive?}
91
+ refrence = weak_list.find {|ref| fullname == ref.fullname}
92
+ refrence.__getobj__ if refrence
93
+ ensure
94
+ GC.enable
95
+ end
71
96
  end
72
- existing_contact
73
97
  end
74
98
 
75
99
  end
@@ -1,3 +1,4 @@
1
1
  module Ahoy
2
2
  ContactOfflineError = Class.new(StandardError)
3
+ NotConnectedError = Class.new(StandardError)
3
4
  end
@@ -1,28 +1,65 @@
1
+ require 'socket'
1
2
  require 'rubygems'
2
3
  require 'dnssd'
3
4
 
4
5
  module Ahoy
6
+
7
+ # Ahoy::User represents us, or the current system, and is the entry point for
8
+ # using the Ahoy library.
9
+ #
10
+ # Send a message to a specific example:
11
+ # user = Ahoy::User.new("Ford")
12
+ # user.sign_in
13
+ #
14
+ # chat = user.chat(user.contacts[/Arthur/])
15
+ # chat.send("Don't panic")
16
+ #
17
+ # Simple echo server:
18
+ # user = Ahoy::User.new("echo")
19
+ # user.sign_in
20
+ #
21
+ # user.on_chat do |chat|
22
+ # chat.on_reply do |reply|
23
+ # chat.send(reply)
24
+ # end
25
+ # end.join
26
+ #
27
+ #
5
28
  class User
6
- attr_reader :short_name, :location, :domain, :contacts
7
- attr_accessor :port, :flags, :interface, :contact
29
+ attr_reader :display_name, :short_name, :location, :domain, :contacts
30
+ attr_accessor :port, :flags, :interface
8
31
 
9
- def initialize(name, location="nowhere", domain="local")
10
- @short_name = name
11
- @location = location
32
+ # :call-seq: User.new(name, location="nowhere", domain="local.") -> user
33
+ #
34
+ # Create a new Ahoy::User.
35
+ #
36
+ # Location should be set to the bonjour/zeroconf hostname.
37
+ #
38
+ def initialize(display_name, location="nowhere", domain="local.")
39
+ @display_name = display_name
40
+ @short_name = display_name.downcase.gsub(/ /, "-").gsub(/[^a-z0-9-]/, "")
41
+ @location = location.downcase.gsub(/ /, "-").gsub(/[^a-z0-9-]/, "")
12
42
  @domain = domain
13
43
 
14
- @contacts = Ahoy::ContactList.new(self)
15
- @contact = nil
44
+ @contacts = Ahoy::ContactList.new(name)
16
45
 
17
46
  @port = 5562
18
47
  @flags = 0
19
48
  @interface = DNSSD::InterfaceAny
20
49
  end
21
50
 
51
+ # :call-seq: user.name -> string
52
+ #
53
+ # The user's name, in name@location format.
54
+ #
22
55
  def name
23
56
  "#{short_name}@#{location}"
24
57
  end
25
58
 
59
+ # :call-seq: user.sign_in(status="avail", msg=nil) -> user
60
+ #
61
+ # Register user as 'on-line' and available to send/receive messages.
62
+ #
26
63
  def sign_in(status="avail", msg=nil)
27
64
  @registrar = DNSSD.register(
28
65
  name,
@@ -32,15 +69,42 @@ module Ahoy
32
69
  txt_record(status, msg),
33
70
  flags.to_i,
34
71
  interface)
72
+ self
35
73
  end
36
74
 
37
- def contact
38
- sleep 0.01 until @contact
39
- @contact
75
+ # :call-seq: user.chat(contact) -> chat
76
+ #
77
+ # Initiate a new chat session with contact.
78
+ #
79
+ def chat(contact)
80
+ chat = Ahoy::Chat.new(contact.name)
81
+ chat.connect(contact.target, contact.port(true))
40
82
  end
41
83
 
42
- def chat(contact)
43
- Ahoy::Chat.new(self, contact)
84
+ # :call-seq: user.listen -> chat
85
+ #
86
+ # Listen for an incoming chat. This method will block until a chat is
87
+ # recieved.
88
+ #
89
+ def listen
90
+ socket = server.accept
91
+ domain, port, hostname, ip = socket.peeraddr
92
+ Ahoy::Chat.new(name, contacts.find_by_ip(ip).name).connect(socket)
93
+ end
94
+
95
+ # :call-seq: user.on_chat {|chat| block } -> thread
96
+ #
97
+ # Set up block as a callback for when a chat is initiated by a contact.
98
+ #
99
+ # This method does not block, but does return a thread, which can be joined
100
+ # if you wish to block.
101
+ #
102
+ def on_chat(&block)
103
+ Thread.new do
104
+ while chat = listen
105
+ block.call(chat)
106
+ end
107
+ end
44
108
  end
45
109
 
46
110
  private
@@ -50,7 +114,11 @@ module Ahoy
50
114
  "port.p2pj" => port,
51
115
  "status" => status,
52
116
  "msg" => msg,
53
- "1st" => short_name)
117
+ "1st" => display_name)
118
+ end
119
+
120
+ def server
121
+ @server ||= TCPServer.new("0.0.0.0", port)
54
122
  end
55
123
 
56
124
  end
@@ -9,8 +9,6 @@ The Bonjour chat protocol is pretty much XMPP with the presence and server parts
9
9
 
10
10
  Ahoy isn't much more than a wrapper with a nice API around the dnssd and xmpp4r gems.
11
11
 
12
- The API is based around the idea that this is an instant messaging protocol, there is a user, that user has a list of contacts, you can start a chat with a contact and send messages the contact though that chat session. This may be refined, but is unlikely to change in it's outlook.
13
-
14
12
  Example:
15
13
 
16
14
  require 'rubygems'
@@ -52,13 +50,7 @@ Along with the blocking Chat#receive method to receive replies, there is an on_r
52
50
  sleep 1
53
51
  end
54
52
 
55
- If you are sending messages to a contact on the same machine (likely while testing, sending messages to yourself in iChat), you may need to set the interface on the user to your primary network interface:
56
-
57
- user = Ahoy::User.new("mat")
58
- user.interface = "en1" # on Mac OS X en1 is usually Wi-Fi, en0 is ethernet
59
- user.sign_in
60
-
61
- ...
53
+ Messages can be formatted using markdown, simply set Chat#use_markdown (or Ahoy.use_markdown for the global default). Ahoy will use any of the following markdown processors, in order of preference: rdiscount, kramdown, maruku, bluecloth.
62
54
 
63
55
  The current use case is to send a message to a team of developers when a deploy script is run, there is a simplified interface for this:
64
56
 
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ahoy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
5
11
  platform: ruby
6
12
  authors:
7
13
  - Mat Sadler
@@ -9,29 +15,39 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2010-03-04 00:00:00 +00:00
18
+ date: 2011-05-07 00:00:00 +01:00
13
19
  default_executable:
14
20
  dependencies:
15
21
  - !ruby/object:Gem::Dependency
16
22
  name: dnssd
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
20
26
  requirements:
21
- - - ">="
27
+ - - ~>
22
28
  - !ruby/object:Gem::Version
23
- version: 1.3.1
24
- version:
29
+ hash: 3
30
+ segments:
31
+ - 2
32
+ - 0
33
+ version: "2.0"
34
+ type: :runtime
35
+ version_requirements: *id001
25
36
  - !ruby/object:Gem::Dependency
26
37
  name: xmpp4r
27
- type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
30
41
  requirements:
31
42
  - - "="
32
43
  - !ruby/object:Gem::Version
44
+ hash: 1
45
+ segments:
46
+ - 0
47
+ - 5
33
48
  version: "0.5"
34
- version:
49
+ type: :runtime
50
+ version_requirements: *id002
35
51
  description: Serverless Messaging using DNSDS/mDNS, XMPP, and Ruby
36
52
  email: mat@sourcetagsandcodes.com
37
53
  executables: []
@@ -47,11 +63,10 @@ files:
47
63
  - lib/ahoy/contact_list.rb
48
64
  - lib/ahoy/errors.rb
49
65
  - lib/ahoy/user.rb
50
- - lib/ahoy/xmpp4r_hack.rb
51
66
  - lib/ahoy.rb
52
67
  - readme.rdoc
53
68
  has_rdoc: true
54
- homepage: http://sourcetagsandcodes.com
69
+ homepage: http://github.com/matsadler/ahoy
55
70
  licenses: []
56
71
 
57
72
  post_install_message:
@@ -61,21 +76,27 @@ rdoc_options:
61
76
  require_paths:
62
77
  - lib
63
78
  required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
64
80
  requirements:
65
81
  - - ">="
66
82
  - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
67
86
  version: "0"
68
- version:
69
87
  required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
70
89
  requirements:
71
90
  - - ">="
72
91
  - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
73
95
  version: "0"
74
- version:
75
96
  requirements: []
76
97
 
77
98
  rubyforge_project:
78
- rubygems_version: 1.3.5
99
+ rubygems_version: 1.3.7
79
100
  signing_key:
80
101
  specification_version: 3
81
102
  summary: Bonjour Chat for Ruby
@@ -1,41 +0,0 @@
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