The previous two answers do not exactly answer the original question of โhow did they do itโ in order to launch bots from the Messages application.
I recreated the exact workflow and scripts needed to simulate the Jeeves virtual assistant to interact with bots (and to get the weather).
See the linked PDF for more details:
https://s3.amazonaws.com/icefield/IntegratingXcodeBotsWithMessages.pdf
Edit: The original answer was deleted due to, I believe, the fact that I was linking to the full answer. This change adds full implementation details as part of this answer. Hope this is not too long for an SO answer.
Xcode bot integration with messaging
During the 415 WWDC 2014 "Continuous Integration with Xcode 6" session, Apple demonstrated the integration of Xcode bots with the Messages application using custom integration triggers. More specifically, starting from the 23-minute mark of this video session ( https://developer.apple.com/videos/play/wwdc2014-415/ ), Apple demonstrates the use of integration triggers in conjunction with messages to receive integration status on the build server. In addition, through the use of Jeeves' virtual chat member, they demonstrate the ability to start integration directly from the Messages application. The following article provides step-by-step instructions for reproducing this functionality.
Client and Server Configurations
To get started, here are the client and server configurations that I used to simulate Jeeves functionality:
Client OS X Version 10.11 (El Capitan), Xcode 7.0.1
Server OS X Version 10.11 (El Capitan), OS X Server 5.0.4, Xcode 7.0.1, Ruby 2.0.0p645
Network For my development and continuous integration I use an internal network. My OS X Server is in a .local domain, and my development machine is another node on the same internal network. The instructions below should work whether you are using an internal or external server.
Jabber - the basis of messages
Jabber is the source name of an open source protocol, for example, for messaging. Jabber has been renamed Extensible Messaging and Presence Protocol (XMPP). The OS X Messages application is built using Jabber in its core.
Make good use of Jabber (Messages) in this effort, so let's turn it on. In OS X Server, select Services> Messages and go to Messages in the upper right corner. For Jeeves, the message service settings I used are as follows:

In the terminal window on your server, if you want to check specific Jabber settings, use
$ sudo serveradmin settings jabber
Note in particular the values โโof jabberClientPortTLS (5222) and jabberClientPortSSL (5223). These are the ports on your server that you will use to communicate with the Jabber service.
Having written most of the scripts for Jeeves well using Ruby, and for this you need the XMPP / Jabber library. In the terminal window on your server, install XMPP4R (XMPP / Jabber library for Ruby) using
$ gem install xmpp4r
Create Users for the Jabber Service
Since my Server is a local server without any developer accounts, I had to create accounts for different developers to log in to Jabber. This step may or may not be necessary if your server already has user accounts.
In the OS X Server application on your server, go to the Accounts> Users list and add a new user for each client that will use Jeeves' virtual assistant. Remember to create a new user for Jeeves. For user Tom, here are the settings that were used. Remember to create an email address for each user, but the Mail service should not start. These email addresses will be used to log in to the Jabber service from the Messages application on your client.

Logging in to Jabber from a customer development machine
With user accounts defined on your server, now is the time to log in to your Jabber account from your client computer. In the Messages application on your client, go to Messages> Preferences> Accounts. Select the + sign in the lower left, select "Other messages ......" and click "Continue." In the Add Messages dialog box, select Jabber for the type of account, and fill in the credentials for your users. Here are the settings I used:

(Note that with SSL enabled, port (5223) matches the jabberClientPortSSL value you specified earlier when checking the Jabber service settings on your server.)
After successfully logging in to Jabber, you can change your account alias on the Chat Settings page of your Jabber account. All other default settings can be left as is.
Create chat
We want all bot integration statuses and communication with our virtual assistant Jeeves to go through the message room. Chats allow you to communicate in groups, but you do not need an invitation to join. To create a chat room, follow these steps:
From the messages, choose File> Go To Chat Room. You should see the account that you are logged into the specified Jabber service. Enter @ rooms..local for the room name and select Go. (Note that I found that the chat should be "rooms..local.com>. Using a word other than" rooms "would not create a chat room.)

Configure server website service
When integration starts from Xcode running on your client machine, pre-integration and post-integration scripts interact with the Jabber service, creating an http call to the file in the OS X Server website service. You must configure the OS X Server Web site service to handle these calls.
You need to change the settings for the site without SSL http (port 80). Here are the settings I used.

Select the port 80 website and select the pencil icon at the bottom so that your settings match these settings.

Select "Change advanced settings ..." and make the appropriate settings. (Enabling "Allow CGI Execution ..." allows you to execute the Ruby script.)

Finally, you will need to include a specific file (message_room - well discussed later) so that it can be run as a Ruby script. To do this, put the following .htaccess file in the default home folder for web servers (usually / Library / Server / Web / Data / Sites / Default).
Options +ExecCGI <FilesMatch message_room$> SetHandler cgi-script </FilesMatch>
NOTE. In all of the following ruby โโscenarios, you will only need to change the variables under the "credentials" comment in each script to match your domain and login credentials.
Preliminary and post-integration scripts When we start integration with Xcode on our client machine, we want to send a message to the Jabber integration chat room so that all chat participants can be notified that the integration has started (and is finished). Add the following scripts before and after integration into your projects on the trigger bot page in Xcode.
This is a script pre-integration trigger:
#!/usr/bin/env ruby require 'json' require 'net/http' require 'uri'
This is the trigger after the script integration:
#!/usr/bin/env ruby require 'json' require 'net/http' require 'uri' # ------------------------------------------------------------------------------------- # credentials and such domain = "<yourDomain>.local" # ------------------------------------------------------------------------------------- # our messaging endpoint uri = URI.parse("http://#{domain}:80/message_room") # ------------------------------------------------------------------------------------- # what we want to say integrationResult = case ENV['XCS_INTEGRATION_RESULT'] when "succeeded" "has completed successfully." when "test-failures" tc = ENV['XCS_TEST_FAILURE_COUNT'].to_i "completed with #{tc} failing #{(tc ==1 ) ? 'test' : 'tests'}." when "build-errors" ec = ENV['XCS_ERROR_COUNT'].to_i "failed with #{ec} build #{(ec == 1) ? 'error' : 'errors'}." when "warnings" wc = ENV['XCS_WARNING_COUNT'].to_i "completed with #{wc} #{(wc == 1) ? 'warning' : 'warnings'}." when "analyzer-warnings" ic = ENV['XCS_ANALYZER_WARNING_COUNT'].to_i "completed with #{ic} static analysis #{(ic == 1) ? 'issue' : 'issues'}." when "trigger-error" "failed running trigger script." when "checkout-error" "failed to checkout from source control." else "failed with unexpected errors." end message = "#{ENV['XCS_BOT_NAME']} integration #{ENV['XCS_INTEGRATION_NUMBER']} #{integrationResult}" # ------------------------------------------------------------------------------------- # build up the request body reqBody = {:message => message} body = JSON.generate(reqBody) # ------------------------------------------------------------------------------------- # the connect type http = Net::HTTP.new(uri.host, uri.port) # ------------------------------------------------------------------------------------- # build up the request request = Net::HTTP::Post.new(uri.request_uri) request.add_field('Content-type', 'application/json') request.body = body # ------------------------------------------------------------------------------------- # send the request and get the response response = http.request(request)
The previous two Ruby scripts call the message_room file, which is located in the home folder of the OS X Server website (usually / Library / Server / Web / Data / Sites / Default). Place the following message_room file in this folder.
#!/usr/bin/env ruby require 'cgi' require 'json' require 'xmpp4r' require 'xmpp4r/muc'
Starting integration from the Messages application
We want to be able to give instructions to our virtual assistant Jeeves from the Messages application. We will support three instructions:
Jeeves, weather # gets the current weather (excluding zip by default for Cupertino)
Jeeves, integration (bot name) # launches integration for this Bot
Jeeves, exit # shutdown Jeeves on your OS X server
The following files will be placed in the default folder for OS X Server websites (usually / Library / Server / Web / Data / Sites / Default).
The main file that the virtual assistant, Jeeves, processes is jeevesManager.rb. Run this file to wake Jeeves by typing
$ ruby ./jeevesManager.rb
from the default folder of websites on your server.
#!/usr/bin/env ruby require 'xmpp4r' require 'xmpp4r/muc' require 'xmpp4r/delay' require './jeevesWeather.rb' require './jeevesIntegration.rb' # ------------------------------------------------------------------------------------- # credentials and such domain = "<domain>.local" userId = " jeeves@ #{domain}" userPw = "<jeevesAccountPassword>" roomName = " integration@rooms. #{domain}" defaultWeatherZipCode = "95015" # ------------------------------------------------------------------------------------- # create the client we'll use fromJID = Jabber::JID.new(userId) jabberClient = Jabber::Client.new(fromJID) jabberClient.connect jabberClient.auth(userPw) jabberClient.send(Jabber::Presence.new.set_type(:available)) # ------------------------------------------------------------------------------------- # connect to the chatroom roomID = roomName + "/" + jabberClient.jid.node roomJID = Jabber::JID::new(roomID) room = Jabber::MUC::MUCClient.new(jabberClient) room.join(roomJID) # ------------------------------------------------------------------------------------- # weather def getWeather(m) begin words = m.body.downcase.split("weather") where = defaultWeatherZipCode if (words.length == 2) where = words[1].strip end weather = get_weather_for_city(where,'f') rescue weather = "Couldn't get weather for that location - try zip code" end return weather end # ------------------------------------------------------------------------------------- # integration def startIntegration(m) begin words = m.body.split("integrate") botName = "Invalid BOT Name" if (words.length == 2) botName = words[1].strip end integrationMessage = jeevesIntegration(botName) rescue integrationMessage = "Failed integrating #{botName}" end return integrationMessage end # ------------------------------------------------------------------------------------- # listen for messages in chatroom (this callback will run in a separate thread) room.add_message_callback do |m| if (mxnil?) # the msg is current if m.type != :error body = m.body; if (body.downcase.include? "jeeves") # assume Jeeves does not understand command understood = 0 # exit Jeeves if (body.downcase.include? "exit") understood = 1 message = "Good-bye" mainthread.wakeup end # Weather if (body.downcase.include? "weather") understood = 1 message = getWeather(m) end # Integrate BOT if (body.downcase.include? "integrate") understood = 1 message = startIntegration(m) end # Jeeves doesn't understand command if (understood == 0) message = "I don't understand that command!" end # let user know what has happened roomMessage = Jabber::Message.new(roomJID, message) room.send(roomMessage) end end end end # ------------------------------------------------------------------------------------- # add the callback to respond to server ping (to keep the connect alive) jabberClient.add_iq_callback do |iq_received| if iq_received.type == :get if iq_received.queryns.to_s != 'http://jabber.org/protocol/disco#info' iq = Jabber::Iq.new(:result, jabberClient.jid.node) iq.id = iq_received.id iq.from = iq_received.to iq.to = iq_received.from jabberClient.send(iq) end end end # ------------------------------------------------------------------------------------- # stop the main thread (the call back will still be alive this way) print "Connected to chat room...\n" Thread.stop print "Disconnected from chat room...\n" # leave chat room and log out of Jabber room.exit jabberClient.close
Two other additional files are used by the Jeeves manager file above. The first of them processes the weather forecast and formats it, and the second processes the start of integration.
######### Weather ######### require 'rexml/document' require 'open-uri' require 'net/smtp' # ------------------------------------------------------------------------------------- # yahoo weather url info # http://developer.yahoo.net/weather/#examples # ------------------------------------------------------------------------------------- #Returns a hash containing the location and temperature information #Accepts US zip codes or Yahoo location id's def yahoo_weather_query(loc_id, units) h = {} open("http://xml.weather.yahoo.com/forecastrss?p=#{loc_id}&u=#{units}") do |http| response = http.read doc = REXML::Document.new(response) root = doc.root channel = root.elements['channel'] location = channel.elements['yweather:location'] h[:city] = location.attributes["city"] h[:region] = location.attributes["region"] h[:country] = location.attributes["country"] h[:temp] = channel.elements["item"].elements["yweather:condition"].attributes["temp"] h[:text] = channel.elements["item"].elements["yweather:condition"].attributes["text"] h[:wind_speed] = channel.elements['yweather:wind'].attributes['speed'] h[:humidity] = channel.elements['yweather:atmosphere'].attributes['humidity'] h[:sunrise] = channel.elements['yweather:astronomy'].attributes['sunrise'] h[:sunset] = channel.elements['yweather:astronomy'].attributes['sunset'] h[:forecast_low] = channel.elements["item"].elements['yweather:forecast'].attributes['low'] h[:forecast_high] = channel.elements["item"].elements['yweather:forecast'].attributes['high'] end return h end # ------------------------------------------------------------------------------------- def get_weather_for_city(city_code,units) weather_info = yahoo_weather_query(city_code, units) city = weather_info[:city] region = weather_info[:region] country = weather_info[:country] temp = weather_info[:temp] wind_speed = weather_info[:wind_speed] humidity = weather_info[:humidity] text = weather_info[:text] sunrise = weather_info[:sunrise] sunset = weather_info[:sunset] forecast_low = weather_info[:forecast_low] forecast_high = weather_info[:forecast_high] return "#{city}, #{region}:\n" + " Currently #{temp} degrees, #{humidity}% humidity, #{wind_speed} mph winds, #{text}.\n" + " Forecast: #{forecast_low} low, #{forecast_high} high.\n" + " Sunrise: #{sunrise}, sunset: #{sunset}.\n" end
Finally, this is a script that launches integration from the Messages application
require 'json' require 'open-uri' require 'openssl' # ------------------------------------------------------------------------------------- def jeevesIntegration(botToIntegrate) # credentials domain = "<domain>.local" endpoint = "https://#{domain}:20343" user = "your-integration-username (not Jeeves)" password = "password" # return message message = "Bot '#{botToIntegrate}' does not exist on server #{domain}" # request JSON construct with all the BOTS botsRequestURI = URI.parse("#{endpoint}/api/bots") output = open(botsRequestURI, {ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE}) bots = JSON.parse(output.readlines.join("")) # loop through full list of BOTS for the one we're interested in bots['results'].each do |bot| botName = bot['name'] if (botName.downcase == botToIntegrate.downcase) botID = bot['_id'] # curl -k -X POST -u "#{user}:#{password}" "#{endpoint}/api/bots/#{botid}/integrations" -i # ------------------------------------------------------------------- # kickoff integration uri = URI.parse(endpoint) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Post.new("/api/bots/#{botID}/integrations") request.basic_auth(user, password) response = http.request(request) message = "Integrating #{botName} on server #{domain}" end end return message end