// ==UserScript==
// @name TwitterImg Downloader
// @namespace TypeNANA
// @version 0.7
// @description Add a picture download button to Twitter, and click to download the original image named by format.
// @author HY
// @match https://twitter.com/
// @include https://twitter.com/*
// @require http://code.jquery.com/jquery-3.3.1.min.js
// @grant none
// ==/UserScript==
(function () {
/** 修改 defaultFileName 可改变默认文件名格式
*
* <%Userid> 画师的推特ID,如 “shiratamacaron”
* <%Tid> 图片源的推文id,如 “1095705491429158912”
* <%Time> 保存时间,13位时间戳,如 “1550557810891”
* <%PicName> 图片的默认文件名,如 “DzS6RkJUUAA_0LX”
* <%PicNo> 图片在推中的序数,即推中的第几张图片,从0开始计数,如 “0”
*
* 建议使用<%Time>、<%PicName>和<%PicNo>中的至少使用一个来作为图片区分
* 默认格式为 “<%Userid> <%Tid> <%PicName>” ,效果为 “shiratamacaron 1095705491429158912 DzS6RkJUUAA_0LX”
* 推荐格式1 “<%Userid> <%Tid>_p<%PicNo>” ,效果为 “shiratamacaron 1095705491429158912_p0”
* 推荐格式2 “<%Tid>_p<%PicNo>” ,效果为 “1095705491429158912_p0”
*/
let defaultFileName = "<%Userid> <%Tid>_p<%PicNo>";
function download(url, name, view) {
//通过fetch获取blob
fetch(url).then(response => {
if (response.status == 200)
return response.blob();
throw new Error(`status: ${response.status}.`)
}).then(blob => {
downloadFile(name, blob, view)
}).catch(error => {
console.log("failed. cause:", error)
})
}
function downloadFile(fileName, blob, view) {
//通过a标签的download属性来下载指定文件名的文件
const anchor = view;
const src = URL.createObjectURL(blob);
anchor.download = fileName;
anchor.href = src;
view.click();
}
const addDownloadButton = (v) => {
if (newVersionFlag) {
newVer(v);
} else {
oldVer(v)
}
return false;
}
function newVer(v) {
if(v == null || v.length ==0) return;
var target = v[0];
if (target==null || target.src == null) return;
if (!target.src.startsWith("https://pbs.twimg.com/media")) return;
if (target.parentElement.getAttribute("aria-label") == null || target.parentElement.getAttribute("aria-label") == "") return;
console.log(1)
let dlbtn = document.createElement('DIV');
target.parentElement.parentElement.appendChild(dlbtn);
dlbtn.outerHTML = '<div class="dl_btn_div" style="z-index: 999;display: table;font-size: 15px;color: white;position: absolute;right: 5px;bottom: 5px;background: #0000007f;height: 30px;width: 30px;border-radius: 15px;text-align: center;"><svg class="icon" style="width: 15px;height: 15px;vertical-align: top;display: inline-block;margin-top: 7px;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3658"><path d="M925.248 356.928l-258.176-258.176a64 64 0 0 0-45.248-18.752H144a64 64 0 0 0-64 64v736a64 64 0 0 0 64 64h736a64 64 0 0 0 64-64V402.176a64 64 0 0 0-18.752-45.248zM288 144h192V256H288V144z m448 736H288V736h448v144z m144 0H800V704a32 32 0 0 0-32-32H256a32 32 0 0 0-32 32v176H144v-736H224V288a32 32 0 0 0 32 32h256a32 32 0 0 0 32-32V144h77.824l258.176 258.176V880z" p-id="3659"></path></svg></div>';
dlbtn = target.parentElement.parentElement.getElementsByClassName("dl_btn_div")[0];
let btnDownloadImg = document.createElement('A');
btnDownloadImg.className = 'img-link';
document.getElementById("react-root").appendChild(btnDownloadImg);
//获取文件名
// https://pbs.twimg.com/media/D_mR-WEUYAAZJVH?format=jpg&name=360x360
let fooName = target.src.split("?")[0];
let barName = fooName.split("/");
let dl_picname = barName[barName.length - 1];
//获取图片编号
// ameto_y/status/1151067160078274561/photo/1
let fooNode = findFirstA(target);
let fooNo = fooNode.href.replace("https://twitter.com/", "").split("/");
let dl_userid = fooNo[0];
let dl_tid = fooNo[2];
let dl_picno = fooNo[4];
let dl_time = new Date().getTime();
//替换内容,拼接文件名
let dl_filename = defaultFileName
.replace("<%Userid>", dl_userid)
.replace("<%Tid>", dl_tid)
.replace("<%Time>", dl_time)
.replace("<%PicName>", dl_picname)
.replace("<%PicNo>", dl_picno - 1);
//获取拓展名,推特只存在.jpg和.png格式的图片,故偷个懒不做正则判断
let dl_ext = "jpg";
if (target.src.includes("png")) {
dl_ext = "png";
}
dlbtn.addEventListener('touchstart', function (e) {
dlbtn.onclick = function (e) {
return false;
}
return false;
});
dlbtn.addEventListener('mousedown', function (e) {
dlbtn.onclick = function (e) {
return false;
}
return false;
});
dlbtn.addEventListener('click', function (e) {
//调用下载方法
cancelBubble(e);
download("https://pbs.twimg.com/media/" + dl_picname + "?format=" + dl_ext + "&name=orig", dl_filename + "." + dl_ext, btnDownloadImg);
return false;
});
}
function oldVer(v) {
let tweets = document.querySelectorAll('.tweet');
tweets.forEach((t) => {
//忽略视频信息
if (t.getElementsByClassName("PlayableMedia").length > 0) return;
//文件名信息
let dl_userid = t.getAttribute("data-screen-name");
let dl_name = t.getAttribute("data-name");
let dl_tid = t.getAttribute("data-tweet-id");
//尝试获取发推时间,但是部分情况无法获取,故采用保存文件时间
//let dl_time = t.getElementsByClassName("_timestamp")[0].getAttribute("data-time");
let dl_time = new Date().getTime();
/* 画廊 */
if (t.parentElement.className.includes("GalleryTweet")) {
//获取画廊容器
let imgContent = t.parentElement.parentElement.getElementsByClassName("Gallery-media")[0];
//防止按钮重复叠加
if (imgContent.parentElement.parentElement.getElementsByClassName("dl_btn_div").length != 0) return;
//创建下载按钮
let dlbtn = document.createElement('div');
imgContent.parentElement.appendChild(dlbtn);
dlbtn.outerHTML = '<div class="dl_btn_div" style="z-index: 999;display: table;font-size: 15px;color: white;position: absolute;right: 5px;top: 5px;background: #0000007f;height: 30px;width: 30px;border-radius: 15px;text-align: center;"><a style="display: table-cell;height: 30px;width: 30px;vertical-align: middle;color:white;font-family:edgeicons;text-decoration: none;user-select: none;" id="a_dl"></a></div>';
dlbtn = imgContent.parentElement.getElementsByClassName("dl_btn_div")[0];
//创建不可见的下载用标签
let btnDownloadImg = document.createElement('A');
btnDownloadImg.className = 'img-link';
imgContent.parentElement.parentElement.appendChild(btnDownloadImg);
//添加点击事件
dlbtn.addEventListener('click', function () {
//去掉图片链接尾部的 ":large"
let ImgUrl = imgContent.getElementsByClassName("media-image")[0].src.replace(":large", "");
//获取文件名
let dl_picname = ImgUrl.replace('https://pbs.twimg.com/media/', '').replace('.png', '').replace('.jpg', '');
//设置默认图片编号0
let dl_picno = 0;
//个人页面class
let Images = imgContent.parentElement.querySelectorAll('.AdaptiveMedia-container img');
if (Images.length <= 0) {
//信息流class
Images = imgContent.parentElement.querySelectorAll('.AdaptiveMedia-photoContainer img');
}
//通过循环比较获取图片序号
for (var imgNo = 0; imgNo < Images.length; imgNo++) {
if (ImgUrl == Images[imgNo].src) {
dl_picno = imgNo;
break;
}
}
//获取拓展名,推特只存在.jpg和.png格式的图片,故偷个懒不做正则判断
let dl_ext = ".jpg";
if (ImgUrl.includes(".png")) {
dl_ext = ".png";
}
//替换内容,拼接文件名
let dl_filename = defaultFileName
.replace("<%Userid>", dl_userid)
.replace("<%Name>", dl_name)
.replace("<%Tid>", dl_tid)
.replace("<%Time>", dl_time)
.replace("<%PicName>", dl_picname)
.replace("<%PicNo>", dl_picno);
//调用下载方法
download(ImgUrl + ":orig", dl_filename + dl_ext, btnDownloadImg);
});
return;
}
/* 信息流 */
//防止按钮重复叠加
if (t.getElementsByClassName("dl_btn_div").length != 0) return;
//获取全部图片标签
let Images = t.querySelectorAll('.AdaptiveMedia-container img');
for (var i = 0; i < Images.length; i++) {
let Img = Images[i];
if (Img) {
//获取图片链接
let ImgUrl = Img.src;
//如果为blob对象则跳过
if (Img.src.includes('blob')) break;
//创建下载按钮
let dlbtn = document.createElement('div');
Img.parentElement.parentElement.appendChild(dlbtn);
dlbtn.outerHTML = '<div class="dl_btn_div" style="display: table;font-size: 15px;color: white;position: absolute;right: 5px;bottom: 5px;background: #0000007f;height: 30px;width: 30px;border-radius: 15px;text-align: center;"><a style="display: table-cell;height: 30px;width: 30px;vertical-align: middle;color:white;font-family:edgeicons;text-decoration: none;user-select: none;" id="a_dl"></a></div>';
dlbtn = Img.parentElement.parentElement.getElementsByClassName("dl_btn_div")[0];
//创建不可见的下载用标签
let btnDownloadImg = document.createElement('A');
btnDownloadImg.className = 'img-link';
t.appendChild(btnDownloadImg);
//获取文件名
let dl_picname = Img.src.replace('https://pbs.twimg.com/media/', '').replace('.png', '').replace('.jpg', '');
//获取图片编号
let dl_picno = i;
//替换内容,拼接文件名
let dl_filename = defaultFileName
.replace("<%Userid>", dl_userid)
.replace("<%Name>", dl_name)
.replace("<%Tid>", dl_tid)
.replace("<%Time>", dl_time)
.replace("<%PicName>", dl_picname)
.replace("<%PicNo>", dl_picno);
//获取拓展名,推特只存在.jpg和.png格式的图片,故偷个懒不做正则判断
let dl_ext = ".jpg";
if (ImgUrl.includes(".png")) {
dl_ext = ".png";
}
//添加点击事件
dlbtn.addEventListener('click', function () {
//调用下载方法
download(ImgUrl + ":orig", dl_filename + dl_ext, btnDownloadImg);
});
}
};
});
}
let newVersionFlag = (document.getElementById("react-root") != null);
if (newVersionFlag) {
waitForKeyElements(
'article img',
addDownloadButton
);
} else {
waitForKeyElements(
'.AdaptiveMedia-container img',
addDownloadButton
);
}
function waitForKeyElements(
selectorTxt,
actionFunction,
bWaitOnce,
iframeSelector
) {
var targetNodes, btargetsFound;
if (typeof iframeSelector == "undefined") {
targetNodes = $(selectorTxt);
} else {
targetNodes = $(iframeSelector).contents().find(selectorTxt);
}
if (targetNodes && targetNodes.length > 0) {
btargetsFound = true;
targetNodes.each(function () {
var jThis = $(this);
var alreadyFound = jThis.data('alreadyFound') || false;
if (!alreadyFound) {
var cancelFound = actionFunction(jThis);
if (cancelFound) {
btargetsFound = false;
} else {
jThis.data('alreadyFound', true);
}
}
});
} else {
btargetsFound = false;
}
var controlObj = waitForKeyElements.controlObj || {};
var controlKey = selectorTxt.replace(/[^\w]/g, "_");
var timeControl = controlObj[controlKey];
if (btargetsFound && bWaitOnce && timeControl) {
clearInterval(timeControl);
delete controlObj[controlKey]
} else {
if (!timeControl) {
timeControl = setInterval(function () {
waitForKeyElements(selectorTxt,
actionFunction,
bWaitOnce,
iframeSelector
);
}, 300);
controlObj[controlKey] = timeControl;
}
}
waitForKeyElements.controlObj = controlObj;
}
function findFirstA(node) {
var tmp = node;
for (var i = 0; i < 20; i++) {
tmp = tmp.parentElement
if (tmp.nodeName == "a" || tmp.nodeName == "A") {
return tmp
}
}
}
function cancelBubble(e) {
var evt = e ? e : window.event;
if (evt.stopPropagation) {
evt.stopPropagation();
} else {
evt.cancelBubble = true;
}
}
})();