// ==UserScript== // @name QQ 群相册批量下载 // @namespace http://lvshuncai.com // @homepage https://github.com/ShunCai/QQ-Group-Albums-Downloader // @version 1.0 // @description 自动点击 QQ 群相册的下载功能,实现所有的群相册的批量下载 // @author 芷炫 // @match https://h5.qzone.qq.com/groupphoto/index?inqq=*&groupId=* // @match https://h5.qzone.qq.com/groupphoto/album?inqq=*&groupId=* // @icon https://qzonestyle.gtimg.cn/aoi/img/logo/favicon.ico // @grant none // @run-at document-end // @downloadURL none // ==/UserScript== (function () { 'use strict'; // 获取查询参数 var getQueryString = function (name) { let reg = new RegExp ("(^|&)" + name + "=([^&]*)(&|$)", "i"); let r = window.location.search.substring (1).match (reg); if (r != null) { return decodeURI (r [2]); }; return null; } // 延迟函数 var delay = milliseconds => new Promise (resolve => { setTimeout (resolve, milliseconds); }); // 上传相片按钮 let $uploadBtn = document.querySelector ('#js_pic_upload_btn,#js-header-upload') if (!$uploadBtn) { return; } // 替换文件名称的特殊字符 const replaceFileName = name => { return name.replace (/'|#|~|&| |!|\\|\/|:|\?|"|<|>|\*|\|/g,"_"); } // 下载文件 const download = async albums => { for (const ablum of albums) { // 创建 A 标签 const downLink = document.createElement ('a'); downLink.download = replaceFileName (ablum.title) + '.zip'; downLink.href = ablum.downloadUrl.replace ('http:', 'https:'); downLink.style.display = 'none'; document.body.append (downLink); // 模拟点击 downLink.click (); // Chrome requires the timeout await delay (1000); // 移除 A 标签 downLink.remove (); } } // 迅雷下载 const invokeThunder = albums => { // 迅雷下载任务 const thunderTask = []; for (const album of albums) { thunderTask.push ({ name: replaceFileName (album.title) + '.zip', url: album.downloadUrl }) } // 迅雷下载信息 const thunderInfo = { taskGroupName: 'QQ 群相册_' + getQueryString ('groupId'), hideYunPan: '0', referer: 'https://h5.qzone.qq.com/', tasks: thunderTask } copyToClipboard ('thunderx://' + JSON.stringify (thunderInfo)); } // 复制文本到剪切板 const copyToClipboard = async text => { navigator.clipboard.writeText (text).catch ((error) => { console.error (' 异步复制失败 ', error); // 创建 text area let textArea = document.createElement ("textarea"); textArea.value = text; // 使 text area 不在 viewport,同时设置不可见 textArea.style.position = "absolute"; textArea.style.opacity = 0; textArea.style.left = "-999999px"; textArea.style.top = "-999999px"; document.body.appendChild (textArea); textArea.focus (); textArea.select (); // 执行复制命令并移除文本框 document.execCommand ('copy') ? res () : rej (); textArea.remove (); }); } // 复制相册下载地址 const copyAlbumUrls = async albums => { const urls = []; for (const album of albums) { urls.push (album.downloadUrl); } await copyToClipboard (urls.join ('\n')); } // 获取下载 URL var getDownloadUrl = async album => { // 请求地址参数 const urlParmas = new URLSearchParams (); urlParmas.append ('g_tk', PSY.user.token ()); urlParmas.append ('qzonetoken', window.g_qzonetoken); // 请求实体参数 const bodyParmas = new URLSearchParams (); bodyParmas.append ('uin', PSY.user.getLoginUin ()); bodyParmas.append ('hostUin', -1); bodyParmas.append ('inCharset', 'utf-8'); bodyParmas.append ('outCharset', 'utf-8'); bodyParmas.append ('refer', 'refer'); bodyParmas.append ('source', 'qzone'); bodyParmas.append ('platform', 'qzone'); bodyParmas.append ('format', 'json'); bodyParmas.append ('appid', 422); bodyParmas.append ('selectMode', 1); bodyParmas.append ('albumid', album.id); bodyParmas.append ('hostid', getQueryString ('groupId')); bodyParmas.append ('albumName', album.title); const response = await fetch ('https://h5.qzone.qq.com/proxy/domain/app.photo.qq.com/cgi-bin/app/cgi_arch_photo_v2?' + urlParmas.toString (), { method: 'POST', body: bodyParmas }); return await response.json (); } // 相册每页条目数 const ALBUMNS_PAGE_SIZE = 2; // 获取相册信息 const getAlbumInfo = async (page) => { const parmas = new URLSearchParams (); parmas.append ('g_tk', PSY.user.token ()); parmas.append ('qzonetoken', window.g_qzonetoken); parmas.append ('qunId', getQueryString ('groupId')); parmas.append ('uin', PSY.user.getLoginUin ()); parmas.append ('start', page * ALBUMNS_PAGE_SIZE); parmas.append ('num', ALBUMNS_PAGE_SIZE); parmas.append ('format', 'json'); parmas.append ('inCharset', 'utf-8'); parmas.append ('outCharset', 'utf-8'); parmas.append ('platform', 'qzone'); parmas.append ('source', 'qzone'); parmas.append ('cmd', 'qunGetAlbumList'); const response = await fetch ('https://h5.qzone.qq.com/proxy/domain/u.photo.qzone.qq.com/cgi-bin/upp/qun_list_album_v2?' + parmas.toString ()); return await response.json (); } // 获取相册信息 const getAlbumList = async () => { window.albums = []; // 获取第一页相册 const albumInfo = await getAlbumInfo (0); window.albums.push (...albumInfo.data.album || []); // 相册个数 const total = albumInfo.data.total; if (total > ALBUMNS_PAGE_SIZE) { for (let page = 1; page * ALBUMNS_PAGE_SIZE < total; page++) { const pageAlbumInfo = await getAlbumInfo (page); window.albums.push (...pageAlbumInfo.data.album || []); await delay (1500); } } return window.albums; } // 获取相册下载链接 const getDownloadLinks = async albums => { for (const album of albums) { // 获取相册下载地址 const downloadInfo = await getDownloadUrl (album); album.downloadUrl = downloadInfo.data.downloadUrl; // 延迟 await delay (1000); } } // 批量下载 const $downloadBtn = document.createElement ('a'); $downloadBtn.innerText = ' 批量下载 '; $downloadBtn.setAttribute ('class', 'mod-btn-upload'); $downloadBtn.setAttribute ('title', ' 直接通过浏览器自身下载全部相册,相册过多时,下载容易出错,建议自行复制链接到第三方工具下载 '); $downloadBtn.style.cssText = "background-color: #dd905b;border-color: #dd905b;margin-left: 10px;"; $downloadBtn.addEventListener ("click", async function () { // 获取相册列表 if (!window.albums) { this.innerText = ' 获取下载链接...'; window.albums = await getAlbumList (); await getDownloadLinks (window.albums); } this.innerText = ' 正在下载 '; // 浏览器下载 await download (albums); this.innerText = ' 下载完成 '; setTimeout (() => { this.innerText = ' 批量下载 '; }, 1500); }) $uploadBtn.parentElement.appendChild ($downloadBtn); // 迅雷下载 const $thunderBtn = document.createElement ('a'); $thunderBtn.innerText = ' 迅雷下载 '; $thunderBtn.setAttribute ('title', ' 需先安装迅雷,并打开迅雷,以及打开剪切板监听,或直接复制链接到迅雷下载 '); $thunderBtn.setAttribute ('class', 'mod-btn-upload'); $thunderBtn.style.cssText = "background-color: #5b63dd;border-color: #5b63dd;margin-left: 10px;"; $thunderBtn.addEventListener ("click", async function () { // 获取相册列表 if (!window.albums) { this.innerText = ' 获取下载链接...'; window.albums = await getAlbumList (); await getDownloadLinks (window.albums); } this.innerText = ' 正在唤醒迅雷 '; // 迅雷下载 invokeThunder (albums); this.innerText = ' 已唤醒迅雷 '; setTimeout (() => { this.innerText = ' 迅雷下载 '; }, 1500); }) $uploadBtn.parentElement.appendChild ($thunderBtn); // 复制链接 const $copyLinks = document.createElement ('a'); $copyLinks.innerText = ' 复制链接 '; $copyLinks.setAttribute ('title', ' 复制下载链接到剪切板,可以自行到迅雷、IDM 等第三方工具下载,建议尽快下载,避免存在有效期 '); $copyLinks.setAttribute ('class', 'mod-btn-upload'); $copyLinks.style.cssText = "background-color: #5bdd6b;border-color: #5bdd6b;margin-left: 10px;"; $copyLinks.addEventListener ("click", async function () { // 获取相册列表 if (!window.albums) { this.innerText = ' 获取下载链接...'; window.albums = await getAlbumList (); await getDownloadLinks (window.albums); } this.innerText = ' 正在复制 '; // 复制到剪切板 await copyAlbumUrls (albums); this.innerText = ' 复制完成 '; setTimeout (() => { this.innerText = ' 复制链接 '; }, 1500); }) $uploadBtn.parentElement.appendChild ($copyLinks); })();