Spacebat / Twitter Full-Resolution Image Downloader

// ==UserScript==
// @name            Twitter Full-Resolution Image Downloader
// @namespace       https://openuserjs.org/users/Spacebat
// @description     Save full-res images from Twitter
// @version         1.0
// @author          Spacebat
// @license         MIT; http://opensource.org/licenses/MIT
// @homepageURL     https://twitter.com/MC_Spacebat/
// @supportURL      https://twitter.com/MC_Spacebat/
// @updateURL       https://openuserjs.org/meta/Spacebat/Twitter_Full-Resolution_Image_Downloader.meta.js
// @include         /^(?:http(?:s)?:\/\/)?(?:[^\.]+\.)?twitter\.com(?:\/.*)?$/
// @include         /^(?:http(?:s)?:\/\/)?(?:[^\.]+\.)?twimg\.com(?:\/.*)?$/
// @require         https://raw.githubusercontent.com/eligrey/FileSaver.js/master/FileSaver.js
// ==/UserScript==

if (!("contextMenu" in document.documentElement &&
      "HTMLMenuItemElement" in window)) return;

var body = document.body;
body.addEventListener("contextmenu", initMenu, false);

var menu = body.appendChild(document.createElement("menu"));
menu.outerHTML = '<menu id="DTF-save-full-image" type="context">\
                    <menuitem label="Save full image"\
                              ></menuitem>\
                  </menu>';

document.querySelector("#DTF-save-full-image menuitem")
        .addEventListener("click", saveFullImage, false);

function initMenu(aEvent) {
  var node = aEvent.target;
  var item = document.querySelector("#DTF-save-full-image menuitem");
  if (node.localName == "img") {
    body.setAttribute("contextmenu", "DTF-save-full-image");
    item.setAttribute("imageURL", node.src);
  } else {
    body.removeAttribute("contextmenu");
    item.removeAttribute("imageURL");
  }
}

function loadXHR(url) {

    return new Promise(function(resolve, reject) {
        try {
            var xhr = new XMLHttpRequest();
            xhr.open("GET", url);
            xhr.responseType = "blob";
            xhr.onerror = function() {reject("Network error.")};
            xhr.onload = function() {
                if (xhr.status === 200) {resolve(xhr.response)}
                else {reject("Loading error:" + xhr.statusText)}
            };
            xhr.send();
        }
        catch(err) {reject(err.message)}
    });
}


function saveFullImage(aEvent) {
  var imageURL = aEvent.target.getAttribute("imageURL");
  var filename = imageURL.split("/").pop();
  var fullImageURL = imageURL+':orig';
  loadXHR(fullImageURL).then(blob => {
      saveAs(blob, filename);
  });
}