// ==UserScript== // @name 微博媒体下载 // @namespace http://your.namespace.com // @version 0.2 // @description 读取微博媒体的链接,发送到下载下载,并保存 // @author Your name // @run-at document-start // @match https://*.weibo.com/* // @icon https://weibo.com/favicon.ico // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @downloadURL none // ==/UserScript== //页面元素监测,判断小红书笔记列表是否出现 (function () { // 创建一个 MutationObserver 实例 var observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { // 检查每个变化的类型 if (mutation.type === "childList" && mutation.addedNodes.length > 0) { // 循环遍历添加的节点 mutation.addedNodes.forEach(function (addedNode) { // 检查添加的节点是否为目标元素 if (addedNode.classList) { let like = addedNode.querySelector('[class="woo-like-main toolbar_btn_Cg9tz"]') if (like) { //console.log(addedNode) let parentElement = like.parentElement?.parentElement?.parentElement?.parentElement?.parentElement if (parentElement?.classList?.contains('woo-box-flex')) { if (!parentElement.querySelector('.go')) { 添加按钮(parentElement, addedNode) let share = addedNode.querySelector('[class="toolbar_share_39C6P toolbar_cursor_34j5V"]') if (share) { share.querySelector('span').textContent = ''; share.style.paddingRight = "30px"; share.style.paddingLeft = "30px"; share.style.marginRight = "15px"; } } } } else { let copy = addedNode.querySelector('[class="copy-slide-fade-enter copy-slide-fade-enter-active"]') if (copy) { copy.textContent = '' } else { let share = addedNode.querySelector('[class="share-slide-fade-enter share-slide-fade-enter-active"]') if (share) { share.textContent = '' } else { //console.log(1, addedNode) } } } } }); } }); }); // 开始观察父节点下的变化 observer.observe(document.body, { childList: true, subtree: true }); })(); function 添加按钮(parentElement, addedNode) { //return //广告元素 if(addedNode.querySelector('[class="wbpro-tag head-info_tag_3iMJw"]')){ addedNode.style.filter=' blur(4px)'; return; } let svgparentElement = document.createElement('div'); if (parentElement.parentElement.parentElement.classList.contains('Feed_retweetBar_3IHPj')) { svgparentElement.className = "personallike2"; addedNode = addedNode.querySelector('.retweet') console.log(addedNode) //转发微博 } else { svgparentElement.className = "personallike"; } parentElement.appendChild(svgparentElement); svgparentElement.addEventListener('mouseover', function (event) { event.stopPropagation(); svgparentElement.classList.add('mouseover') }); svgparentElement.addEventListener('mouseout', function (event) { event.stopPropagation(); svgparentElement.classList.remove('mouseover') }); svgparentElement.addEventListener("click", function (event) { event.stopPropagation(); 获取信息发送(svgparentElement, addedNode); }); svgparentElement.addEventListener('contextmenu', function (event) { event.preventDefault(); // 阻止默认的右键菜单弹出 // 在这里编写处理右键点击事件的代码 保存目录设置(); }); svgparentElement.innerHTML += ` ` } if (!document.querySelector('.svgstyle')) { let css = ` .toolbar_share_39C6P.toolbar_cursor_34j5V { display: none; } .personallike { width: 30px; height: 30px; cursor: pointer; margin: 8px 30px 0 0; user-select: none } .personallike2{ width:30px; height:30px; cursor: pointer; user-select: none; margin: 0px 30px 0 30px; } .mouseover .go{ stroke: #ff6f00; } [state="eorro"] .go { stroke: #464646 !important; } [state="wait"] .go { stroke: #e5b800 !important; } [state="fail"] .go { stroke: #2C3227 !important; } [state="complete"] .go { stroke: #5CE500 !important; } .savedDirectoryPath{ background: #bcbdc2cf; backdrop-filter: blur(5px); z-index: 99; position: fixed; border-radius: 10px; top: calc(30vw - 45px); left: calc(50vw - 150px); width: 350px; height: 90px; height: 120px; user-select: none; } .pathinput { outline: aquamarine; border: none; width: 310px; height: 40px; margin: 10px 15px 5px; border-radius: 10px; background: #e4e4e4; } .pathinput1 { outline: aquamarine; border: none; width: 310px; height: 20px; margin: 10px 15px 0px; border-radius: 10px; background: #e4e4e4; } .pathbutton { position: relative; right: -250px; cursor: pointer; border-radius: 5px; border: none; font-size: 14px; } .pathbutton1 { position: relative; right: -15px; cursor: pointer; border-radius: 5px; border: none; font-size: 14px; } .pathbutton:hover { background: #56c1c5; } .pathbutton:active { background: #ff9500; } ` let style = document.createElement('style') style.className = "svgstyle" style.textContent = css; document.body.appendChild(style) } function 获取信息发送(svgparentElement, msgElement) { try { let href = msgElement.querySelector('[class="head-info_time_6sFQg"]')?.href if (href) { console.dir(href) let 匹配, js2, js3, js4, 发帖时间, 用户名, id, ip, 微博文案, filename, url, savedDirectoryPath, 混合媒体, 单视频, 图片, 视频id,清晰度,码率; id = 取文件名(href); 匹配 = false; js3 = []; js4 = {}; for (let index = 0; index < js.length; index++) { js2 = js[index]; if (js2.mblogid === id) { 匹配 = true; 发帖时间 = 转换时间(js2.created_at); 用户名 = js2.user?.screen_name?.trim(); id = js2.idstr.trim(); ip = js2.region_name?.trim().split(' ')[1]; 微博文案 = js2.text_raw?.trim(); savedDirectoryPath = GM_getValue('directoryPath'); 混合媒体 = js2.mix_media_info?.items; 单视频 = js2.page_info?.media_info.playback_list; 图片 = js2.pic_infos; if (混合媒体?.length > 0) { for (let i2 = 0; i2 < 混合媒体?.length; i2++) { let 图片id = 混合媒体[i2].data?.pic_id; if(混合媒体[i2].type==='video'){ 视频id = 混合媒体[i2].data?. media_info?.media_id; url = 混合媒体[i2].data?. media_info?.h265_mp4_hd; filename = `${用户名}----${微博文案}----${id}----${视频id}----${ip}${发帖时间}----(${i2+1}).mp4`; }else{ url = 混合媒体[i2].data?.largest?.url; filename = `${用户名}----${微博文案}----${id}----${图片id}----${ip}${发帖时间}----(${i2+1}).JPG`; } filename = filename.replace(/[<>:"/\\|?*\x00-\x1F\ud800-\udfff]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27FF]/g, ''); js3.push({ url: url, 文件名: filename }); } } else { if (单视频?.length > 0) { url = 单视频[0].play_info?.url; 清晰度 = 单视频[0].play_info?.quality_desc; 码率 = 单视频[0].play_info?.quality_label; 视频id = 单视频[0].media_id; filename = `${用户名}----${微博文案}----${id}----${视频id} ${清晰度}${码率}----${ip}${发帖时间}.mp4`; filename = filename.replace(/[<>:"/\\|?*\x00-\x1F\ud800-\udfff]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27FF]/g, ''); js3.push({ url: url, 文件名: filename }); } else { if (图片 && js2.hasOwnProperty('pic_infos')) { let 图片ID集 = Object.keys(js2.pic_infos); for (let i2 = 0; i2 < 图片ID集.length; i2++) { let 图片id = 图片ID集[i2]; url = js2.pic_infos[图片ID集[i2]].largest?.url; filename = `${用户名}----${微博文案}----${id}----${图片id}----${ip}${发帖时间}----(${i2+1}).JPG`; filename = filename.replace(/[<>:"/\\|?*\x00-\x1F\ud800-\udfff]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27FF]/g, ''); js3.push({ url: url, 文件名: filename }); } } } } js4.media = js3; js4.微博文 = `《${用户名}》 \n ${微博文案}`; js4.目录 = savedDirectoryPath + 用户名.replace(/[<>:"/\\|?*\x00-\x1F\ud800-\udfff]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27FF]/g, ''); savedDirectoryPath = GM_getValue("directoryPath"); if (savedDirectoryPath) { svgparentElement.setAttribute("state", "wait"); 发送信息(JSON.stringify(js4), svgparentElement); } else { svgparentElement.setAttribute("state", "error"); 保存目录设置(); } console.log(js4, JSON.stringify(js4)) // 去除特殊符 (发帖用户 + “----” + 类型 + “----” + 微博文案 + “----”, ) // 去除特殊符 (文件名 + 微博ID + “----” + 发帖时间 + “(” + 到文本 (计次3) + “).JPG”, ) // let filename = `${用户名}----${微博文案}----${id}----${发帖时间}----${js.ip}${js.postingtime}----${urlname}` // filename = filename.replace(/[<>:"/\\|?*\x00-\x1F\ud800-\udfff]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27FF]/g, ''); return; } } // if (匹配) { // svgparentElement.setAttribute("state", "wait"); // 发送信息(extractedText, "推文ID", svgparentElement); // } else { // showToast("未找到匹配的内容。", true); // svgparentElement.setAttribute("state", "error"); // } } } catch (error) { console.log(error) } }; function 判断服务器(服务器地址) { if (/^(https?:\/\/)/.test(服务器地址) === false) { showToast('如需要使用外部网页文件下载器,请在源码里填写服务器地址。') return false; } return true; } function 保存目录设置() { let div = document.createElement('div') div.className = 'savedDirectoryPath'; document.body.appendChild(div); var ipinput = document.createElement('input'); ipinput.className = 'pathinput1'; ipinput.type = 'text'; ipinput.placeholder = '服务器地址:http://150.111.42.12:5001/weibodown'; div.appendChild(ipinput); ipinput.value = GM_getValue('severip', ''); // 创建编辑框 var input = document.createElement('input'); input.className = 'pathinput'; input.type = 'text'; input.placeholder = '输入目录路径:I:\\微博影像\\'; div.appendChild(input); input.value = GM_getValue('directoryPath', ''); var closebutton = document.createElement('button'); closebutton.className = 'pathbutton1'; closebutton.innerHTML = '关闭'; div.appendChild(closebutton); // 创建按钮 var button = document.createElement('button'); button.className = 'pathbutton'; button.innerHTML = '保存'; div.appendChild(button); closebutton.addEventListener('click', function (event) { event.stopPropagation(); div.remove(); }) // 监听按钮点击事件 button.addEventListener('click', function (event) { event.stopPropagation(); var directoryPath = input.value; if (directoryPath) { // 检查输入框不为空 let path = isValidDirectoryPath(directoryPath) if (path) { // 判断路径是否合法 severip = ipinput.value; GM_setValue("directoryPath", path); input.value = GM_getValue('directoryPath'); if (判断服务器(severip)) { GM_setValue("severip", severip); showToast('服务器地址、目录路径已保存,当前目录' + path + '\n,右键下载点击按钮重新设置路径', true); div.remove(); } else { alert('请填写服务器地址'); } } else { alert(`输入的路径不合法,请重新输入,\\需要转换成\\\\`); } } else { alert('请输入目录路径'); } }); } function isValidDirectoryPath(path) { let path2 = path.replace(path.split("\\").pop(), "") if (path2 === '') { path2 = path.replace(path.split("/").pop(), "") } path2 = path2.replace(/\\/g, '/').replace(/\/\//g, '/'); // 去掉前后的引号和双引号 path2 = path2.replace(/^['"]|['"]$/g, ''); return path2; } function 取文件名(name) { const parts = name.split('/'); return parts[parts.length - 1]; } function 发送信息(信息, 按钮) { // 发送跨域 POST 请求 console.log(GM_getValue("severip")) if (GM_getValue("severip") === '' || !GM_getValue("severip")) { showToast('请填写并保存你服务器地址。', false); 保存目录设置() return; } GM_xmlhttpRequest({ method: 'POST', url: GM_getValue("severip"), headers: { 'Content-Type': 'application/json', }, data: 信息, // 请求体数据 onload: function (response) { console.log("请求完成", response.responseText); 按钮.setAttribute("state", "complete"); if (!response.responseText) { response.responseText = 信息.微博文 + '\n请求失败'; } showToast(JSON.parse(response.responseText).msg['下载']['下载状态'], true); }, onerror: function (error) { console.error("请求失败", error); showToast(信息 + '网络请求失败' + error, false); 按钮.setAttribute("state", "fail"); } }); } function showToast(message, isError) { if (!message) { message = '传入信息为空。' } // 创建新的提示框 const toastContainer = document.createElement('div'); // 设置样式属性 toastContainer.style.position = 'fixed'; toastContainer.style.justifyContent = 'center'; toastContainer.style.top = '30%'; toastContainer.style.left = '50%'; toastContainer.style.width = '65vw'; toastContainer.style.transform = 'translate(-50%, -50%)'; toastContainer.style.display = 'flex'; toastContainer.style.padding = '5px'; toastContainer.style.fontSize = '20px'; toastContainer.style.background = '#e7f4ff'; toastContainer.style.zIndex = '999'; toastContainer.style.borderRadius = '15px'; toastContainer.classList.add('PopupMessage'); // 设置 class 名称为 PopupMessage // 根据是否为错误提示框添加不同的样式 if (isError) { toastContainer.classList.add('success'); toastContainer.style.color = '#3fc91d'; } else { toastContainer.classList.add('error'); toastContainer.style.color = '#CC5500'; } // 将提示框添加到页面中 document.body.appendChild(toastContainer); // 获取页面高度的 20vh const windowHeight = window.innerHeight; //设置最低的高度。 const height = windowHeight * 0.2; // 设置当前提示框的位置 toastContainer.style.top = `${height}px`; // 在页面中插入新的信息 const toast = document.createElement('div'); // 使用
实现换行 toast.innerHTML = message.replace(/\n/g, '
'); toastContainer.appendChild(toast); // 获取所有的弹出信息元素,包括新添加的元素 const popupMessages = document.querySelectorAll('.PopupMessage'); // 调整所有提示框的位置 let offset = 0; popupMessages.forEach(popup => { if (popup !== toastContainer) { popup.style.top = `${parseInt(popup.style.top) - toast.offsetHeight - 5}px`; } offset += popup.offsetHeight; }); // 在 3 秒后隐藏提示框 setTimeout(() => { toastContainer.classList.add('hide'); // 过渡动画结束后移除提示框 setTimeout(() => { toastContainer.parentNode.removeChild(toastContainer); }, 300); }, 3000); }; 监测页面请求() function 监测页面请求() { // 保存原始的 XMLHttpRequest 对象 var originalXhrOpen = XMLHttpRequest.prototype.open; var originalXhrSend = XMLHttpRequest.prototype.send; // 重写 XMLHttpRequest 的 open 方法 XMLHttpRequest.prototype.open = function (method, url) { //console.log('发起网络请求:', method, url); // 保存请求URL this.__url = url; // 调用原始的 open 方法 originalXhrOpen.apply(this, arguments); }; // 重写 XMLHttpRequest 的 send 方法 XMLHttpRequest.prototype.send = function (data) { var xhr = this; // 监听请求完成事件 xhr.addEventListener('load', function () { // console.log('请求URL:', xhr.__url); // console.log('请求头:', xhr.getAllResponseHeaders()); // console.log('响应内容:', xhr.responseText); 数据判断(xhr.__url, xhr.responseText) }); // 调用原始的 send 方法 originalXhrSend.apply(this, arguments); }; // 监听 fetch 请求 if (window.fetch) { var originalFetch = window.fetch; window.fetch = function (url, options) { console.log('发起网络请求:', url, options); // 调用原始的 fetch 方法 return originalFetch.apply(this, arguments) .then(function (response) { //console.log('响应URL:', response.url); //console.log('响应头:', response.headers); return response.text().then(function (text) { //console.log('响应内容:', text); 数据判断(response.url, text); return new Response(text, response); }); }); }; } } var js = []; function 数据判断(url, data) { try { // // 判断是否包含有 "comment/page" 和 "cursor=" 的 URL 请求 // console.log('响应链接', currentUrl); // // 判断是否包含有 "comment/page" 和 "cursor=" 的 URL 请求 // console.log('请求链接:', currentUrl); // console.log('请求协议头:', xhr.getAllResponseHeaders()); // console.log('Cookie:', document.cookie); // console.log('提交数据:', data); // console.log('响应数据:', xhr.responseText); console.dir(url) /// if (url.includes('/ajax/feed/groupstimeline?list_id=') || url.includes('ajax/feed/unreadfriendstimeline?list_id=') ) { let statuses = JSON.parse(data).statuses if (statuses.length > 0) { for (let index = 0; index < statuses.length; index++) { if (statuses[index].retweeted_status) { js.push(statuses[index].retweeted_status) } else { js.push(statuses[index]) } } console.log(js) } else { console.log(2, js) } } if (url.includes('ajax/statuses/mymblog?uid=')) { let statuses = JSON.parse(data).data?.list if (statuses.length > 0) { for (let index = 0; index < statuses.length; index++) { if (statuses[index].retweeted_status) { js.push(statuses[index].retweeted_status) } else { js.push(statuses[index]) } } console.log(js) } else { console.log(2, js) } } //收藏视频 if (url.includes('ajax/favorites/all_fav?uid=')) { let statuses = JSON.parse(data).data?.status if (statuses.length > 0) { for (let index = 0; index < statuses.length; index++) { if (statuses[index].retweeted_status) { js.push(statuses[index].retweeted_status) } else { js.push(statuses[index]) } } console.log(js) } else { console.log(2, js) } } //喜欢的视频 if (url.includes('ajax/favorites/all_fav?uid=')) { let statuses = JSON.parse(data).data?.list if (statuses.length > 0) { for (let index = 0; index < statuses.length; index++) { if (statuses[index].retweeted_status) { js.push(statuses[index].retweeted_status) } else { js.push(statuses[index]) } } console.log(js) } else { console.log(2, js) } } if (url.includes('ajax/statuses/show?id=')) { let statuses = JSON.parse(data).mblogid if (statuses) { js.push(JSON.parse(data)) console.log(js) } else { console.log(2, js) } } } catch (error) { console.log("错误信息", error) } } function 转换时间(inputDateString) { // 解析日期字符串 const date = new Date(inputDateString); // 获取年、月、日、时、分、秒 const year = date.getFullYear(); const month = date.getMonth() + 1; // 月份是从0开始的,需要加1 const day = date.getDate(); const hours = date.getHours(); const minutes = date.getMinutes(); const seconds = date.getSeconds(); // 格式化输出 const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')} ${hours.toString().padStart(2, '0')}${minutes.toString().padStart(2, '0')}${seconds.toString().padStart(2, '0')}`; return formattedDate; } (function () { 'use strict'; })();