ahoy 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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