手动开始集成 Xcode Bot?
Start Integration of Xcode Bot manually?
我正在观看 WWDC 2014 "Continuous Integration with Xcode" 视频,它看起来很棒如何使用机器人进行 运行 测试。
但我的问题是任何看过视频的人,当他向 Jeeves 发送消息说“集成 CoffeeBoard”时,Bot 开始集成。我想知道他是怎么做到的。
我想在 github 上添加 post-receive 挂钩,它在收到任何提交时应该在我的 OS X 服务器上启动 Xcode bot。我的大多数团队成员使用 SourceTree 或 GitHub 来管理他们的 git,他们不想使用 Xcode 源代码管理。我认为创建一个机器人并将其选项设置为手动启动就可以了。我需要知道,"Does OS X Server gives you option like some sort of url which will start a bot? "
对不起,如果我不够清楚。但这对我来说太混乱了,因为他们关于触发器的文档非常少。尽管他提到它是很酷的新功能,但他们没有包含实现该功能的任何信息
I want to add post-receive hook on github which on receiving any commit should start Xcode bot on my OS X Server.
如果您想 'build on commit' 那么在创建机器人时只需 select 该选项即可。您可以选择 运行 机器人 手动 、 定期 或 提交时 。后者会按照您的描述进行操作。一旦您的团队成员之一提交对您的 github 存储库的更改,Xcode 服务器将进行构建。
是的,正如我 ,您首先需要找出机器人 _id
,然后向机器人的端点发送 POST
请求。有关详细信息,请参阅 link。
前两个答案并未完全回答 "how they did that" 从“信息”应用中启动机器人的原始问题。
我重新创建了模拟 Jeeves 虚拟助手与机器人交互(以及获取天气)所需的确切工作流程和脚本。
有关完整的详细信息,请参阅 linked PDF 文档:
https://s3.amazonaws.com/icefield/IntegratingXcodeBotsWithMessages.pdf
编辑:原答案已被删除,我相信是因为我通过 link 引用了完整答案。此编辑添加了完整的实施细节作为此答案的一部分。我希望 SO 回答不会太长。
将 Xcode 机器人与消息集成
在 WWDC 2014 期间 Session 415,与 Xcode 6 的持续集成,Apple 演示了通过自定义集成触发器将 Xcode 机器人与消息应用程序集成。更具体地说,从 session 的视频 (https://developer.apple.com/videos/play/wwdc2014-415/) 的第 23 分钟开始,Apple 演示了如何将集成触发器与 Messages 结合使用,以接收构建服务器上的集成状态。此外,通过使用虚拟聊天室成员 Jeeves,他们展示了直接从消息应用程序内部开始集成的能力。以下文章提供了 step-by-step 重现该功能的说明。
客户端和服务器配置
首先,这里是我用来模拟 Jeeves 功能的客户端和服务器的配置:
客户
OS X 版本 10.11 (El Capitan),Xcode 7.0.1
服务器
OS X 版本 10.11 (El Capitan),OS X 服务器 5.0.4,Xcode 7.0.1,Ruby 2.0.0p645
网络
对于我的开发和持续集成,我使用内部网络。我的 OS X 服务器位于 domain.local,我的开发机器是同一内部网络上的另一个节点。无论您使用的是内部服务器还是外部服务器,下面的说明都应该有效。
Jabber – 消息的基础
Jabber 是用于实例消息传递的开源协议的原始名称。 Jabber 已重命名为可扩展消息传递和在线状态协议 (XMPP)。 OS X Messages 应用的核心是 Jabber。
我们将在这项工作中广泛使用 Jabber(消息),所以让我们确保它处于开启状态。在 OS X 服务器应用程序中,select 服务 > 消息视图,然后打开右上角的消息。对于 Jeeves,我使用的 Messages 服务设置如下:
从您服务器上的终端 window,如果您想检查 Jabber 的具体设置,请使用
$ sudo serveradmin settings jabber
请特别注意 jabberClientPortTLS (5222) 和 jabberClientPortSSL (5223) 值。这些是您的服务器上将用于与 Jabber 服务通信的端口。
我们将使用 Ruby 为 Jeeves 编写大部分脚本,并且我们需要 XMPP/Jabber 库来完成此任务。从服务器上的终端 window,使用
安装 XMPP4R(Ruby 的 XMPP/Jabber 库)
$ gem install xmpp4r
为 Jabber 服务创建用户
因为我的服务器是本地服务器,上面没有任何开发人员帐户,所以我需要为各种开发人员创建帐户以登录 Jabber。您可能需要也可能不需要此步骤,具体取决于您的服务器是否已经定义了用户帐户。
从您服务器上的 OS X 服务器应用程序,转到帐户 > 用户列表,并为每个将使用虚拟 Jeeves 助手的客户端添加新用户。请务必为 Jeeves 创建一个新用户。对于用户“Tom”,这里是使用的设置。请务必为每个用户创建一个电子邮件地址,但邮件服务不需要 运行ning。这些电子邮件地址将用于从您客户端上的消息应用程序登录 Jabber 服务。
从客户端开发机器登录 Jabber
在您的服务器上定义了用户帐户后,现在可以从您的客户端计算机登录 Jabber 帐户了。在客户端的消息应用程序中,转到消息 > 首选项 > 帐户。 Select左下方的+号,select“其他消息帐户...”,然后按继续。在“添加消息帐户”对话框中,select Jabber 作为帐户类型,并填写您的用户的凭据信息。这是我使用的设置:
(请注意,启用 SSL 后,端口 (5223) 与您之前在检查服务器上的 Jabber 服务设置时列出的 jabberClientPortSSL 值匹配。)
成功登录Jabber服务后,您可以在Jabber账户的“聊天设置”页面下选择更改您的账户昵称。所有其他默认设置都可以保留原样。
[=85=创建聊天室
我们希望所有机器人集成状态以及与我们的虚拟助手 Jeeves 的通信都通过消息聊天室进行。聊天室允许群组交流,但您不需要邀请即可加入。要创建聊天室,请执行以下操作。
从“消息”中选择“文件”>“转到聊天室”。您应该会看到您登录到列出的 Jabber 服务的帐户。输入 integration@rooms..local 作为房间名称,然后 select Go。 (请注意,我发现聊天室需要是“rooms..local”.com”>。使用“rooms”以外的词不会创建聊天室。)
配置服务器网站服务
当在您的客户端计算机上从 Xcode 运行 开始集成时,预集成脚本和 post- 集成脚本通过对OS X 服务器网站服务上的一个文件。您必须配置 OS X 服务器网站服务来处理这些调用。
您需要修改 non-SSL http(端口 80)站点的设置。这是我使用的设置。
Select 端口 80 网站,select 下面的铅笔图标使您的设置与这些相匹配。
Select“编辑高级设置...”并使您的设置与这些设置相匹配。 (启用“允许 CGI 执行...”启用 Ruby 脚本执行。)
最后,您需要启用一个特定文件(message_room – 我们将在稍后讨论)配置为 运行 作为 Ruby 脚本。为此,请将以下 .htaccess 文件放入 Web 服务器的默认主文件夹(通常为 /Library/Server/Web/Data/Sites/Default)。
Options +ExecCGI
<FilesMatch message_room$>
SetHandler cgi-script
</FilesMatch>
注意:在以下所有 ruby 脚本中,您需要修改每个脚本中“凭据”注释下方的变量以匹配您的域和登录凭据。
前和 Post-Integration 脚本
当我们在客户端计算机上从 Xcode 开始集成时,我们希望向 Jabber 集成聊天室发送一条消息,以便聊天室的所有成员都能收到集成已开始(和完成)的通知。在 Xcode.
的 bot Triggers 页面上将以下 pre- 和 post-integration 脚本添加到项目的 bot
这是pre-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
message = "#{ENV['XCS_BOT_NAME']} integration #{ENV['XCS_INTEGRATION_NUMBER']} is now starting."
# -------------------------------------------------------------------------------------
# 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)
这是post-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)
前两个 Ruby 脚本调用位于 OS X 服务器网站主文件夹(通常为 /Library/Server/Web/Data/Sites/Default)中的 message_room 文件。将以下 message_room 文件放入该文件夹。
#!/usr/bin/env ruby
require 'cgi'
require 'json'
require 'xmpp4r'
require 'xmpp4r/muc'
# -------------------------------------------------------------------------------------
# credentials and such
domain = "<domain>.local"
userId = "jeeves@#{domain}"
userPw = "<jeevesAccountPassword>"
roomName = "integration@rooms.#{domain}"
# -------------------------------------------------------------------------------------
# header sent back
cgi = CGI.new
puts cgi.header( "type" => "text/html", "status" => "OK")
# -------------------------------------------------------------------------------------
# get the message out of the json formatted text
keyValue = JSON.parse(cgi.params.keys.first)
key = "message"
value = keyValue[key] puts value
# -------------------------------------------------------------------------------------
# create the message to the iChat (jabber) room
fromJID = Jabber::JID.new(userId)
jabberClient = Jabber::Client.new(fromJID)
jabberClient.connect
jabberClient.auth(userPw)
jabberClient.send(Jabber::Presence.new.set_type(:available))
# -------------------------------------------------------------------------------------
# send the message to a chat room
roomID = roomName + "/" + jabberClient.jid.node
roomJID = Jabber::JID::new(roomID)
room = Jabber::MUC::MUCClient.new(jabberClient) room.join(roomJID)
roomMessage = Jabber::Message.new(roomJID, value) room.send(roomMessage)
从消息应用启动集成
我们希望能够从消息应用程序中向我们的虚拟助手 Jeeves 发出指令。我们将支持三种指令:
Jeeves, weather # 获取当前天气 (w/o
zip 默认为 Cupertino)
Jeeves, integration (Bot Name) # 开始给定的集成
机器人
Jeeves,退出 # 关闭你的 OS X Server
上的 Jeeves
以下文件将放置在您的 OS X 服务器网站的默认文件夹中(通常为 /Library/Server/Web/Data/Sites/Default)。
处理虚拟助手 Jeeves 的主要文件是 jeevesManager.rb。通过输入
启动此文件以唤醒 Jeeves
$ ruby ./jeevesManager.rb
从您服务器上网站的默认文件夹。
#!/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 (m.x.nil?) # 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
上面的 Jeeves 管理器文件使用了另外两个补充文件。下面的第一个处理获取天气预报并对其进行格式化,第二个处理开始集成。
######### 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
最后,这是从消息应用启动集成的脚本
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
我正在观看 WWDC 2014 "Continuous Integration with Xcode" 视频,它看起来很棒如何使用机器人进行 运行 测试。 但我的问题是任何看过视频的人,当他向 Jeeves 发送消息说“集成 CoffeeBoard”时,Bot 开始集成。我想知道他是怎么做到的。
我想在 github 上添加 post-receive 挂钩,它在收到任何提交时应该在我的 OS X 服务器上启动 Xcode bot。我的大多数团队成员使用 SourceTree 或 GitHub 来管理他们的 git,他们不想使用 Xcode 源代码管理。我认为创建一个机器人并将其选项设置为手动启动就可以了。我需要知道,"Does OS X Server gives you option like some sort of url which will start a bot? "
对不起,如果我不够清楚。但这对我来说太混乱了,因为他们关于触发器的文档非常少。尽管他提到它是很酷的新功能,但他们没有包含实现该功能的任何信息
I want to add post-receive hook on github which on receiving any commit should start Xcode bot on my OS X Server.
如果您想 'build on commit' 那么在创建机器人时只需 select 该选项即可。您可以选择 运行 机器人 手动 、 定期 或 提交时 。后者会按照您的描述进行操作。一旦您的团队成员之一提交对您的 github 存储库的更改,Xcode 服务器将进行构建。
是的,正如我 _id
,然后向机器人的端点发送 POST
请求。有关详细信息,请参阅 link。
前两个答案并未完全回答 "how they did that" 从“信息”应用中启动机器人的原始问题。
我重新创建了模拟 Jeeves 虚拟助手与机器人交互(以及获取天气)所需的确切工作流程和脚本。
有关完整的详细信息,请参阅 linked PDF 文档:
https://s3.amazonaws.com/icefield/IntegratingXcodeBotsWithMessages.pdf
编辑:原答案已被删除,我相信是因为我通过 link 引用了完整答案。此编辑添加了完整的实施细节作为此答案的一部分。我希望 SO 回答不会太长。
将 Xcode 机器人与消息集成
在 WWDC 2014 期间 Session 415,与 Xcode 6 的持续集成,Apple 演示了通过自定义集成触发器将 Xcode 机器人与消息应用程序集成。更具体地说,从 session 的视频 (https://developer.apple.com/videos/play/wwdc2014-415/) 的第 23 分钟开始,Apple 演示了如何将集成触发器与 Messages 结合使用,以接收构建服务器上的集成状态。此外,通过使用虚拟聊天室成员 Jeeves,他们展示了直接从消息应用程序内部开始集成的能力。以下文章提供了 step-by-step 重现该功能的说明。
客户端和服务器配置
首先,这里是我用来模拟 Jeeves 功能的客户端和服务器的配置:
客户 OS X 版本 10.11 (El Capitan),Xcode 7.0.1
服务器 OS X 版本 10.11 (El Capitan),OS X 服务器 5.0.4,Xcode 7.0.1,Ruby 2.0.0p645
网络 对于我的开发和持续集成,我使用内部网络。我的 OS X 服务器位于 domain.local,我的开发机器是同一内部网络上的另一个节点。无论您使用的是内部服务器还是外部服务器,下面的说明都应该有效。
Jabber – 消息的基础
Jabber 是用于实例消息传递的开源协议的原始名称。 Jabber 已重命名为可扩展消息传递和在线状态协议 (XMPP)。 OS X Messages 应用的核心是 Jabber。
我们将在这项工作中广泛使用 Jabber(消息),所以让我们确保它处于开启状态。在 OS X 服务器应用程序中,select 服务 > 消息视图,然后打开右上角的消息。对于 Jeeves,我使用的 Messages 服务设置如下:
从您服务器上的终端 window,如果您想检查 Jabber 的具体设置,请使用
$ sudo serveradmin settings jabber
请特别注意 jabberClientPortTLS (5222) 和 jabberClientPortSSL (5223) 值。这些是您的服务器上将用于与 Jabber 服务通信的端口。
我们将使用 Ruby 为 Jeeves 编写大部分脚本,并且我们需要 XMPP/Jabber 库来完成此任务。从服务器上的终端 window,使用
安装 XMPP4R(Ruby 的 XMPP/Jabber 库)$ gem install xmpp4r
为 Jabber 服务创建用户
因为我的服务器是本地服务器,上面没有任何开发人员帐户,所以我需要为各种开发人员创建帐户以登录 Jabber。您可能需要也可能不需要此步骤,具体取决于您的服务器是否已经定义了用户帐户。
从您服务器上的 OS X 服务器应用程序,转到帐户 > 用户列表,并为每个将使用虚拟 Jeeves 助手的客户端添加新用户。请务必为 Jeeves 创建一个新用户。对于用户“Tom”,这里是使用的设置。请务必为每个用户创建一个电子邮件地址,但邮件服务不需要 运行ning。这些电子邮件地址将用于从您客户端上的消息应用程序登录 Jabber 服务。
从客户端开发机器登录 Jabber
在您的服务器上定义了用户帐户后,现在可以从您的客户端计算机登录 Jabber 帐户了。在客户端的消息应用程序中,转到消息 > 首选项 > 帐户。 Select左下方的+号,select“其他消息帐户...”,然后按继续。在“添加消息帐户”对话框中,select Jabber 作为帐户类型,并填写您的用户的凭据信息。这是我使用的设置:
(请注意,启用 SSL 后,端口 (5223) 与您之前在检查服务器上的 Jabber 服务设置时列出的 jabberClientPortSSL 值匹配。)
成功登录Jabber服务后,您可以在Jabber账户的“聊天设置”页面下选择更改您的账户昵称。所有其他默认设置都可以保留原样。
[=85=创建聊天室我们希望所有机器人集成状态以及与我们的虚拟助手 Jeeves 的通信都通过消息聊天室进行。聊天室允许群组交流,但您不需要邀请即可加入。要创建聊天室,请执行以下操作。
从“消息”中选择“文件”>“转到聊天室”。您应该会看到您登录到列出的 Jabber 服务的帐户。输入 integration@rooms..local 作为房间名称,然后 select Go。 (请注意,我发现聊天室需要是“rooms..local”.com”>。使用“rooms”以外的词不会创建聊天室。)
配置服务器网站服务
当在您的客户端计算机上从 Xcode 运行 开始集成时,预集成脚本和 post- 集成脚本通过对OS X 服务器网站服务上的一个文件。您必须配置 OS X 服务器网站服务来处理这些调用。
您需要修改 non-SSL http(端口 80)站点的设置。这是我使用的设置。
Select 端口 80 网站,select 下面的铅笔图标使您的设置与这些相匹配。
Select“编辑高级设置...”并使您的设置与这些设置相匹配。 (启用“允许 CGI 执行...”启用 Ruby 脚本执行。)
最后,您需要启用一个特定文件(message_room – 我们将在稍后讨论)配置为 运行 作为 Ruby 脚本。为此,请将以下 .htaccess 文件放入 Web 服务器的默认主文件夹(通常为 /Library/Server/Web/Data/Sites/Default)。
Options +ExecCGI
<FilesMatch message_room$>
SetHandler cgi-script
</FilesMatch>
注意:在以下所有 ruby 脚本中,您需要修改每个脚本中“凭据”注释下方的变量以匹配您的域和登录凭据。
前和 Post-Integration 脚本 当我们在客户端计算机上从 Xcode 开始集成时,我们希望向 Jabber 集成聊天室发送一条消息,以便聊天室的所有成员都能收到集成已开始(和完成)的通知。在 Xcode.
的 bot Triggers 页面上将以下 pre- 和 post-integration 脚本添加到项目的 bot这是pre-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
message = "#{ENV['XCS_BOT_NAME']} integration #{ENV['XCS_INTEGRATION_NUMBER']} is now starting."
# -------------------------------------------------------------------------------------
# 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)
这是post-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)
前两个 Ruby 脚本调用位于 OS X 服务器网站主文件夹(通常为 /Library/Server/Web/Data/Sites/Default)中的 message_room 文件。将以下 message_room 文件放入该文件夹。
#!/usr/bin/env ruby
require 'cgi'
require 'json'
require 'xmpp4r'
require 'xmpp4r/muc'
# -------------------------------------------------------------------------------------
# credentials and such
domain = "<domain>.local"
userId = "jeeves@#{domain}"
userPw = "<jeevesAccountPassword>"
roomName = "integration@rooms.#{domain}"
# -------------------------------------------------------------------------------------
# header sent back
cgi = CGI.new
puts cgi.header( "type" => "text/html", "status" => "OK")
# -------------------------------------------------------------------------------------
# get the message out of the json formatted text
keyValue = JSON.parse(cgi.params.keys.first)
key = "message"
value = keyValue[key] puts value
# -------------------------------------------------------------------------------------
# create the message to the iChat (jabber) room
fromJID = Jabber::JID.new(userId)
jabberClient = Jabber::Client.new(fromJID)
jabberClient.connect
jabberClient.auth(userPw)
jabberClient.send(Jabber::Presence.new.set_type(:available))
# -------------------------------------------------------------------------------------
# send the message to a chat room
roomID = roomName + "/" + jabberClient.jid.node
roomJID = Jabber::JID::new(roomID)
room = Jabber::MUC::MUCClient.new(jabberClient) room.join(roomJID)
roomMessage = Jabber::Message.new(roomJID, value) room.send(roomMessage)
从消息应用启动集成
我们希望能够从消息应用程序中向我们的虚拟助手 Jeeves 发出指令。我们将支持三种指令:
Jeeves, weather # 获取当前天气 (w/o zip 默认为 Cupertino)
Jeeves, integration (Bot Name) # 开始给定的集成 机器人
Jeeves,退出 # 关闭你的 OS X Server
上的 Jeeves
以下文件将放置在您的 OS X 服务器网站的默认文件夹中(通常为 /Library/Server/Web/Data/Sites/Default)。
处理虚拟助手 Jeeves 的主要文件是 jeevesManager.rb。通过输入
启动此文件以唤醒 Jeeves$ ruby ./jeevesManager.rb
从您服务器上网站的默认文件夹。
#!/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 (m.x.nil?) # 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
上面的 Jeeves 管理器文件使用了另外两个补充文件。下面的第一个处理获取天气预报并对其进行格式化,第二个处理开始集成。
######### 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
最后,这是从消息应用启动集成的脚本
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