風柳メモ

ソフトウェア・プログラミング関連の覚書が中心

【覚書】クロスドメインな画像を任意のファイル名でダウンロードするためのユーザースクリプトの記述方法

Twitter の原寸画像は、例えば「https://pbs.twimg.com/media/CYhLLnfU0AAbHun.jpg:orig」のようなURLとなっており、これを Windows のブラウザでダウンロードしようとすると、「CYhLLnfU0AAbHun.jpg-orig」のようなおかしな拡張子に変換されて保存されてしまうことがある*1

いちいち拡張子を修正するのが面倒だったため、
furyu.hatenablog.com
では、リンク(A)要素の download 属性で、ちゃんとした拡張子のファイル名(例えば、「CYhLLnfU0AAbHun-orig.jpg」)を指定してやり、リネームの手間を省こうとしたのだが……。
以前、
furyu.hatenablog.com
でも書いたように、クロスドメイン(クロスサイト)に於いては、download属性で指定したファイル名は無視されてしまう。

■例

<a href="https://pbs.twimg.com/media/CYhLLnfU0AAbHun.jpg:orig" download="<任意のファイル名>.jpg" id="download_link_sample_a">ダウンロードリンク-A</a>

これを何とかするための苦肉の策が以下のもの。
例によって、もっとスマートなやり方があればコメント願う。

ユーザースクリプトのサンプル

Cross-Domainなサイトの画像を任意のファイル名でダウンロードするユーザースクリプトのサンプル

このユーザースクリプトを Greasemonkey や Tampermonkey でインストールしてから、このページをリロードすると、↑の例のところに「ダウンロードリンク-B」というリンクが新たに表示されるはず。
これをクリックすると、「<任意のファイル名>.jpg」なるファイル名で画像がダウンロードできる……はず。
何をやっているかは、下記のソースコードを参照。また、このテスト以外には意味はないスクリプトなので、確認が終わったら削除しておくことを推奨。

// ==UserScript==
// @name            sample_download_link
// @namespace       http://furyu.hatenablog.com/
// @author          furyu
// @version         0.1.0.0
// @include         http://furyu.hatenablog.com/*
// @include         https://pbs.twimg.com/media/*
// @description     Cross-Domainなサイトの画像を任意のファイル名でダウンロードするユーザースクリプトのサンプル
// ==/UserScript==

( function () {

'use strict';

var SCRIPT_NAME = 'SAMPLE_DOWNLOAD_LINK',
    IFRAME_NAME = SCRIPT_NAME + '_download_frame',
    filename = '<任意のファイル名>.jpg',
    current_url = window.location.href;

if ( window !== window.parent ) {
    if ( window.name != IFRAME_NAME ) {
        return;
    }
    
    // 親 window から呼び出された画像用 IFRAME の処理
    var image_url = current_url,
        link = document.createElement( 'a' );
    
    link.href = image_url;
    link.download = filename;
    
    document.documentElement.appendChild( link );
    link.click(); // ダウンロード開始
    
    return;
}

// 親 window の処理

var source_image_link = document.querySelector( 'div#download_sample_image_container a#download_link_sample_a' );

if ( ! source_image_link ) {
    return;
}

var download_link = document.createElement( 'a' ),
    image_url = source_image_link.href;

download_link.href = image_url;
download_link.download = filename;
download_link.appendChild( document.createTextNode( 'ダウンロードリンク-B' ) );

download_link.addEventListener( 'click', function ( event ) {

    // リンクがクリックされたら、name を指定した隠し IFRAME を呼び出す
    
    event.stopPropagation();
    event.preventDefault();
    
    var iframe = document.createElement( 'iframe' ),
        iframe_style = iframe.style;
    
    iframe_style.width = '0';
    iframe_style.height = '0';
    iframe_style.visibility = 'hidden';
    
    iframe.src = image_url;
    iframe.name = IFRAME_NAME;
    
    document.documentElement.appendChild( iframe );
    
    return false;
}, false );

source_image_link.parentNode.appendChild( download_link );

} )()

注意

Google Chrome の拡張機能で上記の手法を使用するためには、manifest.json にて、

"content_scripts" : [
    {
        "matches" : [ "http://furyu.hatenablog.com/*", "https://pbs.twimg.com/media/*" ],
        "js" : [ "js/main.js" ],
        "run_at" : "document_end",
        "all_frames" : true
    }
]

のように、「 "all_frames" : true 」を指定して、IFRAME 内に対してもスクリプトが動作するように設定しておく必要がある。

*1:Firefoxだと、「CYhLLnfU0AAbHun.jpg orig.jpg」のようになるので、一応 JPEG としては解釈される