Facebook图形API - 使用JavaScript上传照片

42

使用JavaScript通过Facebook Graph API上传文件是否可能?我感觉我离成功很近。我正在使用以下JavaScript代码:

var params = {};
params['message'] = 'PicRolled';
params['source'] = '@'+path;
params['access_token'] = access_token;
params['upload file'] = true;

function saveImage() {
    FB.api('/me/photos', 'post', params, function(response) {
        if (!response || response.error) {
            alert(response);
        } else {
            alert('Published to stream - you might want to delete it now!');
        }
    }); 
}

运行此代码时,我收到以下错误信息...

"OAuthException" - "(#324) Requires upload file"

当我尝试研究这种方法时,我所能找到的只是一个解决此问题的 PHP 方法。

$facebook->setFileUploadSupport(true);

然而,我正在使用JavaScript,看起来这种方法可能与Facebook Graph的权限有关,但是我已经设置了user_photos和publish_stream权限,我认为这是我执行此操作所需的唯一权限。

我在stackoverflow上看到了几个未回答的问题,希望我已经足够解释清楚了。谢谢大家。


2
如果你在这里寻找答案,请超越第一个答案。 - Niall Paterson
13个回答

47

是的,这是可能的。我找到了两种解决方法,它们非常相似,只需要将url参数定义为外部图片的url即可。

第一种使用JavaScript SDK:

var imgURL="http://farm4.staticflickr.com/3332/3451193407_b7f047f4b4_o.jpg";//change with your external photo url
FB.api('/album_id/photos', 'post', {
    message:'photo description',
    url:imgURL        
}, function(response){

    if (!response || response.error) {
        alert('Error occured');
    } else {
        alert('Post ID: ' + response.id);
    }

});

第二个例子使用jQuery的Post请求和FormData:

 var postMSG="Your message";
 var url='https://graph.facebook.com/albumID/photos?access_token='+accessToken+"&message="+postMSG;
 var imgURL="http://farm4.staticflickr.com/3332/3451193407_b7f047f4b4_o.jpg";//change with your external photo url
 var formData = new FormData();
 formData.append("url",imgURL);

  $.ajax({
                    url: url,
                    data: formData,
                    cache: false,
                    contentType: false,
                    processData: false,
                    type: 'POST',

                    success: function(data){
                        alert("POST SUCCESSFUL");
                    }
                });

5
当然,你需要添加jQuery库 ;) - Volodymyr Dvornyk
1
第一个完美地工作,我不知道为什么文档甚至没有指定url参数。 - Michael de Menten
2
这个方法停止工作了吗?我已经使用第一种方法几个月了,然后突然失败了(发送无效图像消息),并尝试使用第二种方法,但仍然给我相同的错误消息。 - Oso
@Владимир Дворник,当涉及发布到“当前用户”墙上时,这似乎完全正常。但是你能告诉我如何发布到朋友的墙上吗?! - Surya
这并没有解决加载本地文件的问题,而我认为这才是原始问题所要求的。 - Shaun314
显示剩余6条评论

27

编辑:这个答案(现在)基本上已经不相关了。如果你的图片在网络上,只需根据API指示指定url参数即可(并请参见其他答案中的示例)。如果您想直接向Facebook发布图像内容,可以阅读此回答以获得理解。还请查看HTML5的Canvas.toDataUrl()

API 说明:“要发布照片,请发出带有照片文件附件的POST请求。”

FB期望上传的图像字节在HTTP请求的正文中,但它们并不存在于此。或者换句话说,在FB.api()调用中,您在哪里提供图像本身的实际内容?

FB.api() API文档很差,没有提供包括正文的HTTP POST示例。从缺少这样的示例可以推断,它不支持此操作。

那可能没关系——FB.api()在底层使用称为XmlHttpRequest的东西,它确实支持包含正文……在您喜欢的JavaScript参考资料中查找一下。

但是,您仍然需要解决两个子问题:

  1. 如何将图像字节(和请求的其余部分)准备为multipart/form-data;以及
  2. 获取图像本身的字节

(顺便说一句,需要编码消息正文可能就是PHP setFileUploadSupport(true)方法的作用——在发送之前告诉facebook对象将消息正文编码为multipart/form-data

但事情比较复杂

不幸的是,子问题“2”可能会困扰你——据我所知,没有办法从浏览器提供的Image对象中提取图像的字节。

如果要上传的图像可以通过URL访问,则可以使用XmlHttpRequest获取字节。这还算不错。

如果图像来自用户的桌面,则您可能的途径是向用户提供:

 <form enctype="multipart/form-data">
     <input type="filename" name="myfile.jpg" />
     <input type="hidden" name="source" value="@myfile.jpg"/>
     <input type="hidden" name="message" value="My Message"/>
     <input type="hidden" name="access_token" value="..."/> 
 </form> 

(请注意source这个名字是给文件上传部件指定的)

...并且希望FB能够预期以这种方式接收数据(在JS中动态编码之前,先通过静态HTML表单尝试一下)。事实上,由于他们没有提供其他方法,人们可能会推断它确实会这样做。


路径可以从URL访问,所以您认为提供图像数据的XML HTTP请求而不是路径是否可以?我的其余代码看起来没问题吗?我今晚晚些时候才能尝试,但再次感谢。 - Moz
如果你有一个服务器上传图片的空闲时间,那么我可能会建议你这样做。在各种浏览器中,XmlHttpRequest对象的二进制能力尚未成为标准,因此你将遇到更少的问题,而且不会受到同源策略的限制。服务器很可能比浏览器连接更好,可以更快地完成工作。 - David Bullock
@Crashalot - 可能是这样。请了解一下multipart/form-data,并考虑一个带有MIME头的部分,例如:“Content-Type: image/png”和“Content-Transfer-Encoding: base64”。(这将使您无需解码base64!但是,这取决于FaceBook的服务器是否会尊重它。API感觉像是为与HTML表单一起使用而设计的,因此如果您遇到问题,我建议您提供一个等同于浏览器提供的正文(例如,使用TamperData插件嗅探FireFox)。 - David Bullock
大家好,为什么我们在文件源代码的开头使用@符号呢? - alioygur
是否可以像共享 URL 一样共享本地图像:'image.png'? - srini
显示剩余3条评论

17

我使用了@Владимир Дворник的代码,并进行了一些修改,我遇到了相同的问题,但是使用这段代码后,问题得到了很好地解决:

        var imgURL = //your external photo url
        FB.api('/photos', 'post', {
            message: 'photo description',
            access_token: your accesstoken 
            url: imgURL
        }, function (response) {

            if (!response || response.error) {
                alert('Error occured:' + response);
            } else {
                alert('Post ID: ' + response.id);
            }

        });

1
这个可以工作,尽管我对 url 有些谨慎,因为它似乎没有被记录在文档中。但是很少有东西被记录在文档中。 - ObscureRobot
今天这个对我有用。不确定它能持续多久。可能和任何已记录的功能一样可靠。 :) - stephen
如何获取访问令牌? - Sam San
1
access_token来自于FB.login()的响应。 - posit labs
为什么它会自动创建相册。 - joshua pogi 28

11

以下是使用Ajax上传照片到Facebook个人资料的方法。

$.ajax({
            type: "POST",
            url: "https://graph.facebook.com/me/photos",
            data: {
                message: "Your Msg Goes Here",
                url: "http://www.knoje.com/images/photo.jpg[Replace with yours]",
                access_token: token,
                format: "json"
            },
            success: function(data){
               alert("POST SUCCESSFUL"); }
            });

这是使用GRAPH API发布照片到Facebook个人资料的最佳方法,也是最简单的方法。

我看过许多答案,其中图片URL以source、picture或image等形式显示,但这些方式都不起作用。

使用source、picture或image会导致(#324) 需要上传文件错误。

避免324错误的最佳方法。


如果您想上传本地图片怎么办?(即,图片在您的手机/电脑上而不是托管在其他地方) - ayjay
根据FB API的要求,您必须指定要发布的图片的URL。如果可行的话,您可以尝试提供本地图片路径。 - sumitkanoje

7
只有@Thiago的答案回答了通过javascript上传数据的问题。我发现Facebook JS API不涵盖这种情况。
我也试验了我的个人解决方案。
主要步骤:
1.获取图像的二进制数据(我使用了画布,但也可以使用输入框);
2.使用所有必要的数据为图形API调用创建一个多部分请求;
3.在请求中包含二进制数据;
4.将所有内容编码为一个二进制数组,并通过XHR发送。
代码
转换实用程序
var conversions = {
  stringToBinaryArray: function(string) {
    return Array.prototype.map.call(string, function(c) {
      return c.charCodeAt(0) & 0xff;
    });
  },
  base64ToString: function(b64String) {
    return atob(b64String);
  }
};

图片上传片段

var DEFAULT_CALL_OPTS = {
  url: 'https://graph.facebook.com/me/photos',
  type: 'POST',
  cache: false,
  success: function(response) {
    console.log(response);
  },
  error: function() {
    console.error(arguments);
  },
  // we compose the data manually, thus
  processData: false,
  /**
   *  Override the default send method to send the data in binary form
   */
  xhr: function() {
    var xhr = $.ajaxSettings.xhr();
    xhr.send = function(string) {
      var bytes = conversions.stringToBinaryArray(string);
      XMLHttpRequest.prototype.send.call(this, new Uint8Array(bytes).buffer);
    };
    return xhr;
  }
};
/**
 * It composes the multipart POST data, according to HTTP standards
 */
var composeMultipartData = function(fields, boundary) {
  var data = '';
  $.each(fields, function(key, value) {
    data += '--' + boundary + '\r\n';

    if (value.dataString) { // file upload
      data += 'Content-Disposition: form-data; name=\'' + key + '\'; ' +
        'filename=\'' + value.name + '\'\r\n';
      data += 'Content-Type: ' + value.type + '\r\n\r\n';
      data += value.dataString + '\r\n';
    } else {
      data += 'Content-Disposition: form-data; name=\'' + key + '\';' +
        '\r\n\r\n';
      data += value + '\r\n';
    }
  });
  data += '--' + boundary + '--';
  return data;
};

/**
 * It sets the multipart form data & contentType
 */
var setupData = function(callObj, opts) {
  // custom separator for the data
  var boundary = 'Awesome field separator ' + Math.random();

  // set the data
  callObj.data = composeMultipartData(opts.fb, boundary);

  // .. and content type
  callObj.contentType = 'multipart/form-data; boundary=' + boundary;
};

// the "public" method to be used
var postImage = function(opts) {

  // create the callObject by combining the defaults with the received ones
  var callObj = $.extend({}, DEFAULT_CALL_OPTS, opts.call);

  // append the access token to the url
  callObj.url += '?access_token=' + opts.fb.accessToken;

  // set the data to be sent in the post (callObj.data = *Magic*)
  setupData(callObj, opts);

  // POST the whole thing to the defined FB url
  $.ajax(callObj);
};

使用方法

postImage({
  fb: { // data to be sent to FB
    caption: caption,
    /* place any other API params you wish to send. Ex: place / tags etc.*/
    accessToken: 'ACCESS_TOKEN',
    file: {
      name: 'your-file-name.jpg',
      type: 'image/jpeg', // or png
      dataString: image // the string containing the binary data
    }
  },
  call: { // options of the $.ajax call
    url: 'https://graph.facebook.com/me/photos', // or replace *me* with albumid
    success: successCallbackFunction,
    error: errorCallbackFunction
  }
});

额外信息

提取画布图像的二进制字符串表示

var getImageToBeSentToFacebook = function() {
  // get the reference to the canvas
  var canvas = $('.some-canvas')[0];

  // extract its contents as a jpeg image
  var data = canvas.toDataURL('image/jpeg');

  // strip the base64 "header"
  data = data.replace(/^data:image\/(png|jpe?g);base64,/, '');

  // convert the base64 string to string containing the binary data
  return conversions.base64ToString(data);
}

如何从中加载binaryString的信息

HTML5文件API读取文本和二进制

注意事项:

  1. 当然,还存在其他替代方案
    • 使用iframe中的HTML表单 - 无法获取调用的响应
    • 使用FormData & File方法,但不幸的是,在这种情况下会出现许多不兼容性,使得该过程更难以使用,并且您最终将不得不围绕这些不一致性进行修补 - 因此我选择了手动数据组装,因为HTTP标准很少改变 :)
  2. 该解决方案不需要任何特殊的HTML5功能。
  3. 上述示例使用了jQuery.ajaxjQuery.extendjQuery.each

1
这是唯一对我有效的方法,可以从桌面上传文件并发布它,非常感谢你。 - Shaun314

5

是的,你可以像这里一样将数据发布到iframe中,或者你可以使用jQuery文件上传

问题在于你无法从iframe中获取响应,但使用插件可以使用页面处理程序。

例如:使用jQuery文件上传上传视频。

<form id="fileupload" action="https://graph-video.facebook.com/me/photos" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="acess_token" value="user_acess_token">
    <input type="text" name="title">
    <input type="text" name="description">
    <input type="file" name="file"> <!-- name must be file -->
</form>


<script type="text/javascript">

    $('#fileupload').fileupload({
        dataType: 'json',
        forceIframeTransport: true, //force use iframe or will no work
        autoUpload : true,
        //facebook book response will be send as param
        //you can use this page to save video (Graph Api) object on database
        redirect : 'http://pathToYourServer?%s' 
    });
</script>

2

如果想要仅使用Javascript从本地计算机上传文件,请尝试使用HelloJS

<form onsubmit="upload();">
     <input type="file" name="file"/>
     <button type="submit">Submit</button>
</form>

<script>
function upload(){
   hello.api("facebook:/me/photos", 'post', document.getElementById('form'), function(r){
      alert(r&&!r.error?'Success':'Failed'); 
   });
}
</script>

http://adodson.com/hello.js/demos/upload.html有一个上传演示。


2
这仍然有效。我正在按以下方式使用它:
var formdata= new FormData();
if (postAs === 'page'){
    postTo = pageId; //post to page using pageID
}

formdata.append("access_token", accessToken); //append page access token if to post as page, uAuth|paAuth
formdata.append("message", photoDescription);
formdata.append("url", 'http://images/image.png');

try {
    $.ajax({
        url: 'https://graph.facebook.com/'+ postTo +'/photos',
        type: "POST",
        data: formdata,
        processData: false,
        contentType: false,
        cache: false,
        error: function (shr, status, data) {
            console.log("error " + data + " Status " + shr.status);
        },
        complete: function () {
            console.log("Successfully uploaded photo to Facebook");
        }
    });
} catch (e) {
    console.log(e);
}

不过我想问一下,您是否知道与使用Facebook的PHP api相比,这种方法是否可取或存在较大的安全风险。


如果将来有人需要,这段代码也可以用于从input[type=file]上传文件。 - user6269864

2

这个是有效的:

   function x(authToken, filename, mimeType, imageData, message) {
    // this is the multipart/form-data boundary we'll use
    var boundary = '----ThisIsTheBoundary1234567890';

    // let's encode our image file, which is contained in the var
    var formData = '--' + boundary + '\r\n';
    formData += 'Content-Disposition: form-data; name="source"; filename="' + filename + '"\r\n';
    formData += 'Content-Type: ' + mimeType + '\r\n\r\n';
    for (var i = 0; i < imageData.length; ++i) {
        formData += String.fromCharCode(imageData[i] & 0xff);
    }
    formData += '\r\n';
    formData += '--' + boundary + '\r\n';
    formData += 'Content-Disposition: form-data; name="message"\r\n\r\n';
    formData += message + '\r\n';
    formData += '--' + boundary + '--\r\n';

    var xhr = new XMLHttpRequest();
    xhr.open('POST', 'https://graph.facebook.com/me/photos?access_token=' + authToken, true);
    xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary);

    // Solving problem with sendAsBinary for chrome
    try {
        if (typeof XMLHttpRequest.prototype.sendAsBinary == 'undefined') {
            XMLHttpRequest.prototype.sendAsBinary = function(text) {
                var data = new ArrayBuffer(text.length);
                var ui8a = new Uint8Array(data, 0);
                for (var i = 0; i < text.length; i++) ui8a[i] = (text.charCodeAt(i) & 0xff);
                this.send(ui8a);
            }
        }
    } catch (e) {}
    xhr.sendAsBinary(formData);
};

虽然这段代码可能回答了问题,但最好还是包括一些解释,说明代码的作用以及为什么这种方法比其他答案更好。 - MattDMo
答案很好,但它依赖于类型化数组,这在整体上并不支持。但实际上,这个片段启发了我的答案 - Matyas

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接