// download_by_irvine.js  (2012-11-27; rentan)
// charset: UTF-8
// Irvineでリンクや画像をダウンロード


// 設定
var config = {
  // Ctrl+Shift キー押し下げ時の動作
  //	'imgonly': 画像をダウンロード
  //	'doc_url': 現在のページの URL をダウンロード
  //	'extract': ページ内の URL を抽出してダイアログ表示
  ctrl_shift_action: 'extract',

  // Ctrl キー押し下げ時の動作
  ctrl_action: 'doc_url',

  // Shift キー押し下げ時の動作
  shift_action: 'imgonly',

  // クリック位置がリンクや画像でない時の動作
  no_obj_action: 'extract',

  // 選択範囲にリンク（または URL テキスト）がない場合にダイアログ表示
  alert_if_no_link: true,

  // 'imgonly' モードで、画像が見つからなかった場合にダイアログ表示
  alert_if_no_img: true,

  // リファラを送信しないリンク元サイト
  no_referer: [
	'http://www.google.co.jp/reader/',
	'http://local.ptron/'
  ],

  // 登録先フォルダ
  // ここを '/Default/Folder' と変更すると登録先を変更できます
  dlFolder:'/Default'
};


// Irvine に登録するダウンロード情報
var list = [ ];
var list_check = { };
var referer = true;
var dialog = false;


// 対応しているスキームに適合する正規表現
var valid_scheme_re = (function () {
  // 対応スキームのリスト
  var scheme = [ 'http', 'https', 'ftp' ];

  var s = '^(' + scheme.join ('|') + '):';
  return new RegExp (s, 'i');  
}) ();

// ダウンロードする URL をリストに追加する
//	追加したら 1、そうでなければ 0 を返す
function add_url (url) {
  if (url == '' || !valid_scheme_re.test (url)) {
    // 非対応スキーム
    return 0;
  }
  if (url in list_check) {
    // 既に同じ URL が追加されている
    return 0;
  }

  // 問題ないのでリストに追加
  list_check [url] = true;
  list.push (url);
  return 1;
}

// リストの URL を Irvine にダウンロード登録する
function send_irvine () {
  //var ref = (referer ? 'Referer あり' : 'Referer なし');
  //var dia = (dialog ? 'ダイアログ使用' : 'ダイアログなし');
  //alert (ref + '\n' + dia + '\n\n' + list.join ('\n'));

  if (list.length == 0) {
    return;
  }

  var irvine = new ActiveXObject ('Irvine.Api');
  if (!irvine) {
    alert ('Irvine が見つかりません。');
    return;
  }

  // デフォルトフォルダでダウンロード、追加後もとのフォルダにもどす
  var saveCurrentQueueFolder;
  if (irvine.CurrentQueueFolder!=config.dlFolder) {
    saveCurrentQueueFolder=irvine.CurrentQueueFolder;
    irvine.CurrentQueueFolder = config.dlFolder;
  }

  var flag = dialog ? 1 : 0;

  if (referer) {
    // リファラあり
    var ref = external.menuArguments.document.URL.replace (/#.*$/, '');
    irvine.AddUrlAndReferer (list.join ('\n'), ref, flag);
	// api.txt の AddUrlAndReferer の解説には Flag…使用していません
	// とあるが、実際には Flag…[0:通常 1:選択]
  }
  else {
    // リファラなし
    irvine.AddUrl (list.join ('\n'), flag);
  }
  if(saveCurrentQueueFolder) {
     irvine.CurrentQueueFolder = saveCurrentQueueFolder;
  }
}


// リファラを送信しないリンク元サイトか調べる
function referer_check () {
  var url = external.menuArguments.document.URL;

  // http:// 以外ならリファラを送信しない
  //	about:  file://  https:// など
  //
  //	https:// → https:// のリンクはリファラを送信するのが普通だが、
  //	現状では送信しない仕様とする。
  if (!/^http:\/\//.test (url)) {
    return false;
  }

  var noref = config.no_referer;
  var noref_len = noref.length;

  for (var i = 0; i < noref_len; ++i) { //>
    var s = noref [i];
  
    if (typeof s == 'string') {
      // 文字列なら先頭一致
      if (s == url.slice (0, s.length)) {
	return false;
      }
    }
    else {
      alert ('no_referer の形式が違います。\n\n' + s.toString ());
    }
  }

  return true;
}


// タグの親を辿って <a> タグを探す
function get_parent_anchor (e) {
  while (e && e.tagName != 'A') {
    e = e.parentNode;
  }
  return e;
}

// href（または指定された属性）の値を絶対 URL で取得
function get_abs_href (e, atr) {
  if (typeof atr != 'string') {
    atr = 'href';
  }
  return e.getAttribute (atr, 4);
}


// 選択範囲から取得したリンクに相対 URL が含まれていたか調べる
function is_broken_relative_link (links) {
  var links_len = links.length;
  for (var i = 0; i < links_len; ++i) { //>
    if (links.item (i).href.match (/^about:/)) {
      return true;
    }
  }

  return false;
}

// 選択範囲のリンクを取得
function get_selected_links (doc, sel_range) {
  var div = doc.createElement ('div');
  div.innerHTML = sel_range.htmlText;
  return div.getElementsByTagName ('a');
}

// 選択範囲のリンクを取得（相対 URL の about: 化を回避）
function get_selected_links2 (doc, sel_range) {
  var div = doc.createElement ('div');

  // document に紐付けないと about:blank ゾーンとして扱われてしまうので、
  // タグを不可視にして一時的に document にぶら下げる
  div.style.display = 'none';
  doc.body.appendChild (div);

  div.innerHTML = sel_range.htmlText;
  var links = div.getElementsByTagName ('a');

  // 参照前に切り離すとコレクションが空になるので、別の配列にコピーする
  var links2 = [ ];
  links2.item = function (n) { return this [n]; };

  var links_len = links.length;
  for (var i = 0; i < links_len; ++i) { //>
    var a = links.item (i);
    a.href = get_abs_href (a);
    links2.push (a);
  }

  // ここまできたら切り離してよい
  doc.body.removeChild (div);

  return links2;
}


// テキスト中の URL を抽出する
function extract_url_from_text (text) {
  var url_re = /\b(http|https|ftp|ttp|tp):\/\/[\w\-]+(\.[\w\-]+)+(:\d+)?\/[\w\-!#$%&()*+,.\/:;=?@\[\]{}~]*/ig;

  var urls = text.match (url_re);

  if (urls) {
    // ttp、tp を http に修正
    var urls_len = urls.length;
    for (var i = 0; i < urls_len; ++i) { //>
      urls [i] = urls [i].replace (/^(ttp|tp):/i, 'http:');
    }
  }

  return urls;  
}


// 選択範囲からリスト追加
//	リスト追加数を返す
function download_selected_link_or_text (doc) {
  var founds = 0;
  var sel_range = doc.selection.createRange ();
  var links = get_selected_links (doc, sel_range);
  
  // about: から始まる URL があれば、相対 URL 対応のルーチンで取得し直す
  if (is_broken_relative_link (links)) {
    links = get_selected_links2 (doc, sel_range);
  }

  // 選択範囲にリンクあり → そのリンクをダウンロード
  var links_len = links.length;
  if (links_len) {
    for (var i = 0; i < links_len; ++i) { //>
      founds += add_url (get_abs_href (links.item (i)));
    }
    return founds;
  }

  // 選択範囲にリンクなし → テキスト形式の URL を抽出する
  links = extract_url_from_text (sel_range.text);
  if (links) {
    links_len = links.length;
    for (var i = 0; i < links_len; ++i) { //>
      founds += add_url (links [i]);
    }

    referer = false;		// この場合はリファラなし
    return founds;
  }

  // 選択範囲にはダウンロード出来そうなものが何もなかった
  return 0;
}


// 画像をリスト追加
//	リスト追加数を返す
function download_img (doc, e) {
  var founds = 0;

  // <img src="..."> なら画像をダウンロード
  if (e.tagName == 'IMG') {
    founds += add_url (get_abs_href (e, 'src'));
    return founds;
  }

  // 背景画像を探す
  for (; e && e.nodeName != 'HTML'; e = e.parentNode) {

    // スタイルシート
    var	bkImg=e.currentStyle?e.currentStyle.backgroundImage:undefined;
    if (bkImg&&bkImg.match (/^url\(["']([^'"]+)['"]\)$/)) {
      founds += add_url (RegExp.$1);
      return founds;
    }

    // <body background="..."> など
    if (e.background && e.background != '') {
      // 絶対 URL 化する細工 ※相対URLでの動作未確認、ダメかも
      var div = doc.createElement ('div');
      div.innerHTML = '<a href="' + e.background +'"></a>';
      var a = div.getElementsByTagName ('a') [0];

      foudns += add_url (get_abs_href (a));
      return founds;
    }
  }

  // 画像がなかった
  if (config.alert_if_no_img) {
    alert ('画像リソースが見つかりません。');
  }
  return 0;
}


// リンク先または画像をリスト追加（画像よりリンクを優先）
//	リスト追加数を返す
function download_link_or_img (e) {
  var founds = 0;

  // 親に <a href="..."> があればリンク先をダウンロード
  var a = get_parent_anchor (e);
  if (a) {
    founds += add_url (get_abs_href (a));
    return founds;
  }

  // <img src="..."> なら画像をダウンロード
  if (e.tagName == 'IMG') {
    founds += add_url (get_abs_href (e, 'src'));
    return founds;
  }

  // リンクも画像もなかった
  return 0;
}


// ページ内の全リンクをリスト追加
//	リスト追加数を返す
function download_all_links (links) {
  var founds = 0;

  var links_len = links.length;
  for (var i = 0; i < links_len; ++i) { //>
    founds += add_url (get_abs_href (links.item (i)));
  }

  return founds;
}


// ページの URL をリスト追加
//	リスト追加数を返す
function download_page_url () {
  var founds = add_url (external.menuArguments.document.URL);

  referer = false;		// リファラなし
  return founds;
}


// 設定に従って各種動作
function do_action (doc, event, action) {
  var founds = 0;

  var act = config [action + '_action'];

  if (act == 'imgonly') {
    // 画像をダウンロード
    founds = download_img (doc, event.srcElement);
  }
  else if (act == 'doc_url') {
    // 現在のページの URL をダウンロード
    founds = download_page_url ();
  }
  else if (act == 'extract') {
    // ページ内の URL を抽出してダイアログ表示
    founds = download_all_links (doc.links);
    dialog = true;		// ダイアログあり
  }
  else {
    alert (action + ' の形式が違います。\n\n' + act.toString ());
  }

  return founds;
}


function main () {
  var event = external.menuArguments.event;
  var doc = external.menuArguments.document;

  var founds = 0;

  if (doc.selection && (doc.selection.type == 'Text')) {
    // 選択範囲あり

    // 選択範囲のリンクまたは URL テキストをダウンロード
    founds = download_selected_link_or_text (doc);

    if (founds == 0) {
      if (config.alert_if_no_link) {
	alert ('選択範囲にリンクがありません。');
      }
      return;
    }
  }
  else {
    // 選択範囲なし

    var key = event.ctrlKey ? (event.shiftKey ? 'ctrl_shift' : 'ctrl')
			    : (event.shiftKey ? 'shift' : null);

    if (key) {
      // Ctrl/Shift キーが押されていたら、設定で指定された動作を実行
      founds = do_action (doc, event, key);
    }
    else {
      // 押されていなければ、右クリック位置のリンク先または画像をダウンロード

      founds = download_link_or_img (event.srcElement);

      if (founds == 0) {
	// リンクや画像でなければ、設定で指定された動作を実行
	founds = do_action (doc, event, 'no_obj');
      }
    }

  }


  // リファラを送らないリンク元サイトに該当するか調べる
  if (referer) {
    referer = referer_check ();
  }

  // Irvine へダウンロード登録
  send_irvine ();
}


//////////////////////////////// 以下、Ancia 用 ////////////////////////////////


// ダイアログ表示
function alert (message) {
  App.MsgBox (message, 'download_by_irvine');
}


// キー状態の取得
function getKeyStat () {
  var keys = { };
  keys.ctrlKey = App.getKeyState(0x11);
  keys.shiftKey = App.getKeyState(0x10);
  return keys;
}


// エレメント偽装
function createElement (tagname) {
  var e = {
    tagName: tagname,
    parentNode: null,
    getAttribute: function (atr, mode) { return this [atr]; }
  };

  return e;
}


// external オブジェクト偽装
var external = (function () {
  var keys = getKeyStat ();

  var ex = {
    menuArguments: {
      document: activeDocument,
      event: {
	ctrlKey: keys.ctrlKey,
	shiftKey: keys.shiftKey,
	srcElement: null
      }
    }
  };

  var a = null;
  var se = null;

  if (DataObject.url) {
    a = createElement ('A');
    a.href = DataObject.url;
    se = a;
  }

  if (DataObject.imageUrl) {
    var img = createElement ('IMG');
    img.src = DataObject.imageUrl;
    img.parentNode = a;
    se = img;
  }

  ex.menuArguments.event.srcElement = se;

  return ex;
}) ();

main ();


// EOF
