我想通过 Facebook Chat API (XMPP) 连接,使用 Facebook PHP API 获取 access_token。我正在使用来自官方网页的代码(但包含在课堂上),但它对我不起作用 - 我被困在挑战握手中。
通讯流程:
[INFO] Opening connection... connnection open
<?xml version='1.0' ?><stream:stream from='chat.facebook.com' id='1' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' xml:lang='en'><stream:features><starttls xmlns='urn:ietf:param
s:xml:ns:xmpp-tls'/><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>X-FACEBOOK-PLATFORM</mechanism><mechanism>PLAIN</mechanism></mechanisms></stream:features>
Array
(
[0] => Array
(
[tag] => STREAM:STREAM
[type] => open
[level] => 1
[attributes] => Array
(
[FROM] => chat.facebook.com
[ID] => 1
[VERSION] => 1.0
[XMLNS:STREAM] => http://etherx.jabber.org/streams
[XMLNS] => jabber:client
[XML:LANG] => en
)
)
[1] => Array
(
[tag] => STREAM:FEATURES
[type] => open
[level] => 2
)
[2] => Array
(
[tag] => STARTTLS
[type] => complete
[level] => 3
[attributes] => Array
(
[XMLNS] => urn:ietf:params:xml:ns:xmpp-tls
)
)
[3] => Array
(
[tag] => MECHANISMS
[type] => open
[level] => 3
[attributes] => Array
(
[XMLNS] => urn:ietf:params:xml:ns:xmpp-sasl
)
)
[4] => Array
(
[tag] => MECHANISM
[type] => complete
[level] => 4
[value] => X-FACEBOOK-PLATFORM
)
[5] => Array
(
[tag] => MECHANISM
[type] => complete
[level] => 4
[value] => PLAIN
)
[6] => Array
(
[tag] => MECHANISMS
[type] => close
[level] => 3
)
[7] => Array
(
[tag] => STREAM:FEATURES
[type] => close
[level] => 2
)
)
Array
(
[STREAM:STREAM] => Array
(
[0] => 0
)
[STREAM:FEATURES] => Array
(
[0] => 1
[1] => 7
)
[STARTTLS] => Array
(
[0] => 2
)
[MECHANISMS] => Array
(
[0] => 3
[1] => 6
)
[MECHANISM] => Array
(
[0] => 4
[1] => 5
)
)
<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
Array
(
[0] => Array
(
[tag] => PROCEED
[type] => complete
[level] => 1
[attributes] => Array
(
[XMLNS] => urn:ietf:params:xml:ns:xmpp-tls
)
)
)
Array
(
[PROCEED] => Array
(
[0] => 0
)
)
<?xml version='1.0' ?><stream:stream from='chat.facebook.com' id='1' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' xml:lang='en'><stream:features><mechanisms xmlns='urn:ietf:par
ams:xml:ns:xmpp-sasl'><mechanism>X-FACEBOOK-PLATFORM</mechanism><mechanism>PLAIN</mechanism></mechanisms></stream:features>
Array
(
[0] => Array
(
[tag] => STREAM:STREAM
[type] => open
[level] => 1
[attributes] => Array
(
[FROM] => chat.facebook.com
[ID] => 1
[VERSION] => 1.0
[XMLNS:STREAM] => http://etherx.jabber.org/streams
[XMLNS] => jabber:client
[XML:LANG] => en
)
)
[1] => Array
(
[tag] => STREAM:FEATURES
[type] => open
[level] => 2
)
[2] => Array
(
[tag] => MECHANISMS
[type] => open
[level] => 3
[attributes] => Array
(
[XMLNS] => urn:ietf:params:xml:ns:xmpp-sasl
)
)
[3] => Array
(
[tag] => MECHANISM
[type] => complete
[level] => 4
[value] => X-FACEBOOK-PLATFORM
)
[4] => Array
(
[tag] => MECHANISM
[type] => complete
[level] => 4
[value] => PLAIN
)
[5] => Array
(
[tag] => MECHANISMS
[type] => close
[level] => 3
)
[6] => Array
(
[tag] => STREAM:FEATURES
[type] => close
[level] => 2
)
)
Array
(
[STREAM:STREAM] => Array
(
[0] => 0
)
[STREAM:FEATURES] => Array
(
[0] => 1
[1] => 6
)
[MECHANISMS] => Array
(
[0] => 2
[1] => 5
)
[MECHANISM] => Array
(
[0] => 3
[1] => 4
)
)
sent
<
Array
(
)
Array
(
)
<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dmVyc2lvbj0xJm1ldGhvZD1hdXRoLnhtcHBfbG9naW4mbm9uY2U9OTY4Qzc3MjU1NzE5RDQzQkM3QkNDNDIxQTRGRUNCMDc=</challenge>
Array
(
[0] => Array
(
[tag] => CHALLENGE
[type] => complete
[level] => 1
[attributes] => Array
(
[XMLNS] => urn:ietf:params:xml:ns:xmpp-sasl
)
[value] => dmVyc2lvbj0xJm1ldGhvZD1hdXRoLnhtcHBfbG9naW4mbm9uY2U9OTY4Qzc3MjU1NzE5RDQzQkM3QkNDNDIxQTRGRUNCMDc=
)
)
Array
(
[CHALLENGE] => Array
(
[0] => 0
)
)
C: dmVyc2lvbj0xJm1ldGhvZD1hdXRoLnhtcHBfbG9naW4mbm9uY2U9OTY4Qzc3MjU1NzE5RDQzQkM3QkNDNDIxQTRGRUNCMDc=
<
Array
(
)
Array
(
)
<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure></stream:stream>
Array
(
[0] => Array
(
[tag] => FAILURE
[type] => open
[level] => 1
[attributes] => Array
(
[XMLNS] => urn:ietf:params:xml:ns:xmpp-sasl
)
)
[1] => Array
(
[tag] => NOT-AUTHORIZED
[type] => complete
[level] => 2
)
[2] => Array
(
[tag] => FAILURE
[type] => close
[level] => 1
)
)
Array
(
[FAILURE] => Array
(
[0] => 0
[1] => 2
)
[NOT-AUTHORIZED] => Array
(
[0] => 1
)
)
An error ocurred
Authentication complete
和我的代码(FB_API 是包装标准 facebook api 的类的实例,它只使用一次来获取访问令牌,将 Utility_Facebook_Chat::$debug 设置为 true 以查看所有消息):
<?php
class Utility_Facebook_Chat {
private $STREAM_XML = '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" version="1.0" xmlns="jabber:client" to="chat.facebook.com" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace">';
private $AUTH_XML = '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="X-FACEBOOK-PLATFORM"></auth>';
private $CLOSE_XML = '</stream:stream>';
private $RESOURCE_XML = '<iq type="set" id="3"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>fb_xmpp_script</resource></bind></iq>';
private $SESSION_XML = '<iq type="set" id="4" to="chat.facebook.com"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>';
private $START_TLS = '<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>';
private $MESSAGE_XML = '<message from="-%d@chat.facebook.com" to="-%d@chat.facebook.com"><body>%s</body></message>';
public static $debug = false;
private $fsocket;
private $FB_API;
private $FB_USER_ID;
const CHAT_SERVER = 'chat.facebook.com';
public static $instances = array();
public static function getInstance($fbUserID, $facebookInstance){
if(!isset(self::$instances[$fbUserID])){
self::$instances[$fbUserID] = new self($fbUserID, $facebookInstance);
}
return self::$instances[$fbUserID];
}
public function __construct($userId, $FB_API){
$this->FB_USER_ID = $userId;
$this->FB_API = $FB_API;
$access_token = $this->FB_API->API->getAccessToken();
$options = array(
'uid' => $this->FB_USER_ID,
'app_id' => $this->FB_API->APP_ID,
'server' => self::CHAT_SERVER
);
if ($this->xmppConnect($options, $access_token)) {
if(self::$debug){
print "Connection successful\n";
}
} else {
if(self::$debug){
print "An error ocurred\n";
}
}
}
private function openConnection($server) {
if(self::$debug){
print "[INFO] Opening connection... ";
}
$this->fsocket = fsockopen($server, 5222, $errno, $errstr);
if(self::$debug){
if (!$this->fsocket) {
print "$errstr ($errno)\n";
} else {
print "connnection open\n";
}
}
return true;
}
private function sendXML($fp, $xml) {
//print_r($xml, true)."\n";
$a = fwrite($fp, $xml);
//print_r($a, true)." \n\n";
}
private function receiveXML($fp, $size=8096) {
$xml = fread($fp, $size);
if ($xml === "") {
return null;
}
if (!preg_match('/^</', $xml)) {
$xml = '<' . $xml;
}
if(self::$debug){
echo "\n".$xml."\n";
}
// parses xml
$xml_parser = xml_parser_create();
xml_parse_into_struct($xml_parser, $xml, $val, $index);
xml_parser_free($xml_parser);
return array($val, $index);
}
private function findXMPP($fp, $tag, $value=null, &$ret=null) {
static $val = null, $index = null;
do {
if ($val === null && $index === null) {
list($val, $index) = $this->receiveXML($fp);
if(self::$debug){
print_r($val);
print_r($index);
}
if ($val === null || $index === null) {
return false;
}
}
foreach ($index as $tag_key => $tag_array) {
if ($tag_key === $tag) {
if ($value === null) {
if (isset($val[$tag_array[0]]['value'])) {
$ret = $val[$tag_array[0]]['value'];
}
return true;
}
foreach ($tag_array as $i => $pos) {
if ($val[$pos]['tag'] === $tag && isset($val[$pos]['value']) &&
$val[$pos]['value'] === $value) {
$ret = $val[$pos]['value'];
return true;
}
}
}
}
$val = $index = null;
} while (!feof($fp));
return false;
}
private function xmppConnect($options, $access_token) {
$this->openConnection($options['server']);
if (!$this->fsocket) {
return false;
}
// initiates auth process (using X-FACEBOOK_PLATFORM)
$this->sendXML($this->fsocket, $this->STREAM_XML);
if (!$this->findXMPP($this->fsocket, 'STREAM:STREAM')) {
return false;
}
if (!$this->findXMPP($this->fsocket, 'MECHANISM', 'X-FACEBOOK-PLATFORM')) {
return false;
}
// starting tls - MANDATORY TO USE OAUTH TOKEN!!!!
$this->sendXML($this->fsocket, $this->START_TLS);
$proceed = '';
if (!$this->findXMPP($this->fsocket, 'PROCEED', null, $proceed)) {
return false;
}
stream_socket_enable_crypto($this->fsocket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
$this->sendXML($this->fsocket, $this->STREAM_XML);
if (!$this->findXMPP($this->fsocket, 'STREAM:STREAM')) {
return false;
}
if (!$this->findXMPP($this->fsocket, 'MECHANISM', 'X-FACEBOOK-PLATFORM')) {
return false;
}
// gets challenge from server and decode it
$this->sendXML($this->fsocket, $this->AUTH_XML);
echo ' sent ';
$challenge = null;
if (!$this->findXMPP($this->fsocket, 'CHALLENGE', null, $challenge)) {
return false;
}
echo 'C: '.$challenge."\n";
$challenge = base64_decode($challenge);
$challenge = urldecode($challenge);
parse_str($challenge, $challenge_array);
// creates the response array
$access_token = $this->FB_API->API->getAccessToken();
$resp_array = array(
'method' => $challenge_array['method'],
'nonce' => $challenge_array['nonce'],
'access_token' => $access_token,
'api_key' => $options['app_id'],
'call_id' => 0,
'v' => '1.0',
);
// creates signature
$response = http_build_query($resp_array);
// sends the response and waits for success
$xml = '<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">'.base64_encode($response).'</response>';
$this->sendXML($this->fsocket, $xml);
if (!$this->findXMPP($this->fsocket, 'SUCCESS')) {
return false;
}
// finishes auth process
$this->sendXML($this->fsocket, $this->STREAM_XML);
if (!$this->findXMPP($this->fsocket,'STREAM:STREAM')) {
return false;
}
if (!$this->findXMPP($this->fsocket, 'STREAM:FEATURES')) {
return false;
}
$this->sendXML($this->fsocket, $this->RESOURCE_XML);
if (!$this->findXMPP($this->fsocket, 'JID')) {
return false;
}
$this->sendXML($this->fsocket, $this->SESSION_XML);
if (!$this->findXMPP($this->fsocket, 'SESSION')) {
return false;
}
return true;
}
private function xmppDisconnect(){
$this->sendXML($this->fsocket, $this->CLOSE_XML);
if(self::$debug){
print ("Authentication complete\n");
}
return fclose($this->fsocket);
}
public function sendMessage($receiverId, $message){
$xml = sprintf($this->MESSAGE_XML, $this->FB_USER_ID, $receiverId, $message);
echo $xml."\n";
$this->sendXML($this->fsocket, $xml);
if ($this->findXMPP($this->fsocket, 'BODY')) {
return true;
} else {
return false;
}
}
public static function toggleDebug(){
self::$debug = !self::$debug;
}
public function __destruct(){
if($this->fsocket){
$this->xmppDisconnect();
}
}
}