使用XMLHttpRequest在用户脚本中下载图像

19

首先,在SO上有一个与这个标题相同的问题,但那不是我要找的,而且它也没有完整的答案。

所以这是我的问题。假设我有一个指向图像的URL。

https://fbcdn-photos-a.akamaihd.net/hphotos-ak-ash4/299595_10150290138650735_543370734_8021370_355110168_n.jpg

当我将参数?dl=1添加到URL末尾时,它就可以被下载了。

https://fbcdn-photos-a.akamaihd.net/hphotos-ak-ash4/299595_10150290138650735_543370734_8021370_355110168_n.jpg?dl=1

我正在尝试通过用户脚本完成此任务。所以我使用了XMLHttpRequest。

var url = "https://fbcdn-photos-a.akamaihd.net/hphotos-ak-ash4/299595_10150290138650735_543370734_8021370_355110168_n.jpg?dl=1";

var request = new XMLHttpRequest();  
request.open("GET", url, false);   
request.send(null);  

if (request.status === 200) 
{  
    alert(request.statusText);
}

这里有一个实例

但是它不能正常工作。


在您的情况下,“dl”参数是完全错误的。 - OnTheFly
@user539484 你是什么意思?它起作用了。只需将其复制并粘贴到地址栏即可。 - Isuru
我的意思是,看看它引起的响应头。这里不相关。 - OnTheFly
4个回答

25
< p >XMLHttpRequest 不能跨域工作,但由于这是一个用户脚本,Chrome现在只支持在用户脚本中使用 GM_xmlhttpRequest()

类似下面这样的代码应该可以工作,注意它是异步的:

GM_xmlhttpRequest ( {
    method:         'GET',
    url:            'https://fbcdn-photos-a.akamaihd.net/hphotos-ak-ash4/299595_10150290138650735_543370734_8021370_355110168_n.jpg?dl=1',
    onload:         function (responseDetails) {
                        alert(responseDetails.statusText);
                    }
} );




获取和使用实际图像数据是一个很大的痛点。

BlobBuilder             = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;

var url                 = "http://jsbin.com/images/gear.png";
var request             = new XMLHttpRequest();
request.open ("GET", url, false);
request.responseType    = "arraybuffer";
request.send (null);

if (request.status === 200) {
    var bb              = new BlobBuilder ();
    bb.append (request.response); // Note: not request.responseText

    var blob            = bb.getBlob ('image/png');
    var reader          = new FileReader ();
    reader.onload       = function (zFR_Event) {
        $("body").prepend ('<p>New image: <img src="' + zFR_Event.target.result + '"></p>')
    };

    reader.readAsDataURL (blob);
}


  • 很遗憾,GM_xmlhttpRequest()目前还不支持设置responseType


  • 因此,对于GM脚本或用户脚本应用程序,我们必须使用自定义的base64编码方案,就像“Javascript Hacks:Using XHR to load binary data”中所示的那样。

    脚本代码变成了这样:

    var imgUrl              = "http://jsbin.com/images/gear.png";
    
    GM_xmlhttpRequest ( {
        method:         'GET',
        url:            imgUrl,
        onload:         function (respDetails) {
                            var binResp     = customBase64Encode (respDetails.responseText);
    
                            /*-- Here, we just demo that we have a valid base64 encoding
                                by inserting the image into the page.
                                We could just as easily AJAX-off the data instead.
                            */
                            var zImgPara    = document.createElement ('p');
                            var zTargetNode = document.querySelector ("body *"); //1st child
    
                            zImgPara.innerHTML = 'Image: <img src="data:image/png;base64,'
                                               + binResp + '">';
                            zTargetNode.parentNode.insertBefore (zImgPara, zTargetNode);
                        },
        overrideMimeType: 'text/plain; charset=x-user-defined'
    } );
    
    
    function customBase64Encode (inputStr) {
        var
            bbLen               = 3,
            enCharLen           = 4,
            inpLen              = inputStr.length,
            inx                 = 0,
            jnx,
            keyStr              = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
                                + "0123456789+/=",
            output              = "",
            paddingBytes        = 0;
        var
            bytebuffer          = new Array (bbLen),
            encodedCharIndexes  = new Array (enCharLen);
    
        while (inx < inpLen) {
            for (jnx = 0;  jnx < bbLen;  ++jnx) {
                /*--- Throw away high-order byte, as documented at:
                  https://developer.mozilla.org/En/Using_XMLHttpRequest#Handling_binary_data
                */
                if (inx < inpLen)
                    bytebuffer[jnx] = inputStr.charCodeAt (inx++) & 0xff;
                else
                    bytebuffer[jnx] = 0;
            }
    
            /*--- Get each encoded character, 6 bits at a time.
                index 0: first  6 bits
                index 1: second 6 bits
                            (2 least significant bits from inputStr byte 1
                             + 4 most significant bits from byte 2)
                index 2: third  6 bits
                            (4 least significant bits from inputStr byte 2
                             + 2 most significant bits from byte 3)
                index 3: forth  6 bits (6 least significant bits from inputStr byte 3)
            */
            encodedCharIndexes[0] = bytebuffer[0] >> 2;
            encodedCharIndexes[1] = ( (bytebuffer[0] & 0x3) << 4)   |  (bytebuffer[1] >> 4);
            encodedCharIndexes[2] = ( (bytebuffer[1] & 0x0f) << 2)  |  (bytebuffer[2] >> 6);
            encodedCharIndexes[3] = bytebuffer[2] & 0x3f;
    
            //--- Determine whether padding happened, and adjust accordingly.
            paddingBytes          = inx - (inpLen - 1);
            switch (paddingBytes) {
                case 1:
                    // Set last character to padding char
                    encodedCharIndexes[3] = 64;
                    break;
                case 2:
                    // Set last 2 characters to padding char
                    encodedCharIndexes[3] = 64;
                    encodedCharIndexes[2] = 64;
                    break;
                default:
                    break; // No padding - proceed
            }
    
            /*--- Now grab each appropriate character out of our keystring,
                based on our index array and append it to the output string.
            */
            for (jnx = 0;  jnx < enCharLen;  ++jnx)
                output += keyStr.charAt ( encodedCharIndexes[jnx] );
        }
        return output;
    }
    

    1
    好的,更新了答案,展示了如何获取并对数据进行base64编码(这样可以在页面中使用或将其作为可用形式的AJAX发送到服务器)。 - Brock Adams
    1
    您可以将 GM_ 函数放置在其他函数内部,但仅限于在脚本范围内运行的函数。如果包装函数被注入或以其他方式在页面范围内运行,则 GM_ 函数将无法工作。这是出于安全考虑而设计的。 - Brock Adams
    1
    那行代码防止服务器响应被截断——因为尝试将二进制数据视为文本时,否则会发生这种情况——我们必须这样做是因为GM_xmlhttpRequest()尚不支持responseType - Brock Adams
    最好不要在评论中问后续问题。至于那个函数,你可能需要稍微修改一下,因为它期望 binResp 有一个 dataURI 前缀(例如:data:image/png;base64,)。你可能也可以跳过这一步,使用 append() - Brock Adams
    为什么要使用customBase64Encode,而不是直接使用atob和btoa? - BrianFreud
    显示剩余13条评论

    4

    您正在尝试使用XHR请求位于不同域上的资源,因此被阻止。使用CORS进行跨域消息传递。


    4

    现代浏览器具有Blob对象:

    GM_xmlhttpRequest({
      method: "GET",
      url: url,
      headers: { referer: url, origin: url },
      responseType: 'blob',
      onload: function(resp) {
        var img = document.createElement('img');
        img.src = window.URL.createObjectURL(resp.response);
        document.body.appendChild(img);
      }
    });
    

    “headers”参数将设置引荐者,以便您可以加载已锁定引荐者的图片。

    0
    Krof Drakula说得对,你不能从不同的域加载图像,但是你真的需要这样做吗?你可以创建并附加一个标签,并等待它加载(使用类似jQuery的load()函数)。
    var img = document.createElement( 'img' );
    img.setAttribute( 'src', url );
    document.getElementsByTagName('body')[0].appendChild( img );
    

    2
    不,我不想将图像嵌入页面中。我想使用XHR下载它。 - Isuru
    1
    我其实不太理解你想做什么,但是如果你需要在页面上使用图片,可以把它附加在一个隐藏的 div 中,例如,只是为了缓存它,在加载完成后再对它进行操作。如果你需要展示保存对话框(见上面的链接),那么可以直接使用 window.location = 'http://foo.com/bar?dl=1'。如果你确实需要使用 XHR,那么 CORS 可能是唯一的选择。 - Daniel J F

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