您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add download button to Twitter image, and click to download the original image named by format.
// ==UserScript== // @name TwitterImg Downloader // @namespace TypeNANA // @version 0.15 // @description Add download button to Twitter image, and click to download the original image named by format. // @author HY // @include *://twitter.com/* // @include *://*.twitter.com/* // @include *://x.com/* // @include *://*.x.com/* // @require http://code.jquery.com/jquery-3.3.1.min.js // @grant none // @license MIT // ==/UserScript== (function () { /** Edit defaultFileName to change the file name format * * <%Userid> Twitter user ID. eg: shiratamacaron * <%Tid> Tweet ID. eg: 1095705491429158912 * <%Time> Current timestamp. eg: 1550557810891 * <%PicName> Original pic name. eg: DzS6RkJUUAA_0LX * <%PicNo> Ordinal number of pic. eg: 0 * * default: "<%Userid> <%Tid>_p<%PicNo>" * result: "shiratamacaron 1095705491429158912_p0.jpg" * * example1: "<%Userid> <%Tid> <%PicName>” * result: "shiratamacaron 1095705491429158912 DzS6RkJUUAA_0LX.jpg" * * example2: "<%Tid>_p<%PicNo>” * result: "1095705491429158912_p0.jpg" */ let defaultFileName = "<%Userid> <%Tid>_p<%PicNo>"; /** Edit following value to change download shortcut key in gallery mode * KeyCode value can be found at https://keycode.info/ * * default: shift + s (s->83) */ let shortCut_Shift = true; //true - Yes , false - No let shortCut_Ctrl = false; let shortCut_Alt = false; let shortCut_KeyCode = 83 //KeyCode value 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属性来下载指定文件名的文件 let anchor = view; let src = URL.createObjectURL(blob); anchor.download = fileName; anchor.href = src; view.click(); } const addDownloadButton = function (v) { if (newVersionFlag) { newVer(v); } else { oldVer(v) } return false; } function addDlBtn(){ let btnDownloadImg; if(document.getElementsByClassName("img-link").length==0){ btnDownloadImg = document.createElement('A'); btnDownloadImg.className = 'img-link'; document.getElementById("react-root").appendChild(btnDownloadImg); }else{ btnDownloadImg = document.getElementsByClassName("img-link")[0]; } return btnDownloadImg; } function newVer(v) { if (v == null || v.length == 0) return; let target = v[0]; if (target == null || target.src == null) return; if (target.alt == null || target.alt == "") return; if (target.parentElement.getAttribute("aria-label") == null || target.parentElement.getAttribute("aria-label") == "") return; let dlbtn = document.createElement('DIV'); target.parentElement.parentElement.appendChild(dlbtn); dlbtn.outerHTML = '<div class="dl_btn_div" style="cursor: pointer;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 = addDlBtn(); let urlregex = /https\:\/\/(twitter|x).com\//; if (!document.location.href.includes("photo")) { //信息流模式 let firstA = findFirstA(target); //获取文件名 // 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]; let dl_time = new Date().getTime(); //获取图片编号 // ameto_y/status/1151067160078274561/photo/1 let array = firstA.href.replace(urlregex, "").split("/"); let dl_userid = array[0]; let dl_tid = array[2]; let dl_picno = array[4]; //替换内容,拼接文件名 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("format=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; }); } else { //大图画廊模式 //获取文件名 // 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]; //获取拓展名,推特只存在.jpg和.png格式的图片,故偷个懒不做正则判断 let dl_ext = "jpg"; if (target.src.includes("format=png")) { dl_ext = "png"; } dlbtn.addEventListener('click', function (e) { //调用下载方法 cancelBubble(e); //获取图片编号 // ameto_y/status/1151067160078274561/photo/1 let array = document.location.href.replace(urlregex, "").split("/"); let dl_userid = array[0]; let dl_tid = array[2]; let dl_picno = array[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); download("https://pbs.twimg.com/media/" + dl_picname + "?format=" + dl_ext + "&name=orig", dl_filename + "." + dl_ext, btnDownloadImg); return false; }); } } //画廊模式下的快捷键功能 function onShortCut() { let locationArr = document.location.href.split("/"); let targetArr = $('ul[role="list"]'); let imgNo = null; let imgArr = null; let target = null; //判断是否找到了画廊的ul标签 if (targetArr.length == 0 ) { //如果找不到ul标签,并且不是画廊模式(那么网址内没有“photo”), 则不进行进一步的处理 if(locationArr.length < 2 || locationArr[locationArr.length - 2] != "photo") return; //否则进行搜索单张图画廊的情况 var arr = $('img[src^="https://pbs.twimg.com/media/'); for(var i=0;i< arr.length;i++){ var imgUrl = arr[i].src.split("?")[i] //判断是否是推特附带的图片img,并且判断是不是信息流的图片(信息流图片的所有母层中必定带有一个a标签) if(arr[i].parentElement.firstElementChild!="" && arr[i].parentElement.firstElementChild.style.backgroundImage.includes(imgUrl) && findFirstA(arr[i])==null){ //单图模式的赋值 let imgNo = 0; target = arr[i]; break; } } //如果找不到任何目标,则不进行进一步的处理 if(target == null) return; } else { //多图模式的赋值 imgNo = locationArr[locationArr.length - 1] - 1; imgArr = targetArr[0].getElementsByTagName("img"); target = imgArr[imgNo]; } //获取文件名 // 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]; //获取拓展名,推特只存在.jpg和.png格式的图片,故偷个懒不做正则判断 let dl_ext = "jpg"; if (target.src.includes("format=png")) { dl_ext = "png"; } //获取图片编号 // ameto_y/status/1151067160078274561/photo/1 let urlregex = /https\:\/\/(twitter|x).com\//; let array = document.location.href.replace(urlregex, "").split("/"); let dl_userid = array[0]; let dl_tid = array[2]; let dl_picno = array[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); let btnDownloadImg = addDlBtn(); download("https://pbs.twimg.com/media/" + dl_picname + "?format=" + dl_ext + "&name=orig", dl_filename + "." + dl_ext, btnDownloadImg); } 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( 'img[src^="https://pbs.twimg.com/media/"]', addDownloadButton ); } else { waitForKeyElements( '.AdaptiveMedia-container img', addDownloadButton ); } $(document).keyup(function (e) { let shiftFlag = true let ctrlFlag = true let altFlag = true if (shortCut_Shift) { shiftFlag = e.shiftKey } if (shortCut_Ctrl) { ctrlFlag = e.ctrlKey } if (shortCut_Alt) { altFlag = e.altKey } if (e.keyCode == shortCut_KeyCode && shiftFlag && ctrlFlag && altFlag) { onShortCut() } }) 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 == null) return null; if (tmp.nodeName == "a" || tmp.nodeName == "A") { return tmp } } } function findFirstLi(node) { var tmp = node; for (var i = 0; i < 20; i++) { tmp = tmp.parentElement if (tmp == null) return null; if (tmp.nodeName == "li" || tmp.nodeName == "LI") { return tmp } } } function cancelBubble(e) { var evt = e ? e : window.event; if (evt.stopPropagation) { evt.stopPropagation(); } else { evt.cancelBubble = true; } } })();