// ==UserScript== // @name B站表情包下载 // @namespace http://tampermonkey.net/ // @version 1.5 // @description 批量下载B站表情包/收藏集图片 // @author jzh // @match https://*.bilibili.com/* // @icon https://www.bilibili.com/favicon.ico // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/2.6.1/jszip.min.js // @license MIT // @downloadURL none // ==/UserScript== ;(function () { 'use strict' const buttons = [ { selector: '.emoji-list .emoji div', innerHTML: '下载选中系列表情包', alertMessage: '请先点击表情选项并选中需要下载的表情包' //
}, { selector: '.bili-im .right .dialog:not(.hide) .send-box .input-box .core-style img', innerHTML: '下载输入框表情包', alertMessage: '输入框没有表情包' // [xxx] }, { selector: '.message-list-content .msg-item .emotion-items.emotion-items-big', innerHTML: '下载对话框表情包', alertMessage: '对话框没有表情包' // //
//
}, { innerHTML: '下载评论区表情包', alertMessage: '评论区没有表情包' // [xxx] }, { innerHTML: '下载收藏集图片', alertMessage: '请参照教程打开收藏集卡池详情' //
//
//
//
// //
//
//
//
xxx
//
} ] // 是否私信 const isMessage = location.href.startsWith('https://message.bilibili.com') // 是否通用格式的评论区 const isCommonComment = /^https:\/\/www\.bilibili\.com\/(list|bangumi|read|opus)/.test( location.href ) // 是否特殊格式的评论区 const isSpecialComment = location.href.startsWith('https://www.bilibili.com/video') // 是否收藏集 const isBlackboard = location.href.startsWith('https://www.bilibili.com/blackboard') const div = document.createElement('div') Object.assign(div.style, { position: 'fixed', top: '250px', right: 0, display: 'flex', flexDirection: 'column', justifyContent: 'center' }) for (let i = 0; i < 5; i++) { if (isMessage) { if (i === 3 || i === 4) { continue } } else if (isCommonComment || isSpecialComment) { if (i !== 3) { continue } } else if (isBlackboard) { if (i !== 4) { continue } } else { return } const { selector, innerHTML, alertMessage } = buttons[i] const button = document.createElement('button') button.innerHTML = innerHTML Object.assign(button.style, { width: '125px', marginBottom: '25px', cursor: 'pointer', padding: '2px', border: '1px solid #767676', backgroundColor: '#efefef', fontSize: '12px' }) button.onclick = () => { let emojis = [] try { if (isMessage) { emojis = Array.from(document.querySelectorAll(selector)) } else if (isCommonComment) { emojis = Array.from( // list // #app #mirror-vdcon .playlist-container--left .playlist-comment .comment // bangumi // #__next .home-container .main-container .plp-l .player-left-components #comment-module #comment-body // read // #app .article-detail #comment-wrapper.comment-wrapper .comment-m .article-comment // opus // #app .opus-detail .bili-tabs.opus-tabs .bili-tabs__content .bili-tab-pane .comment-wrap .bili-comment-container document.querySelectorAll( '.bili-comment .comment-container .reply-warp .reply-list .reply-item' ) ).flatMap(item => Array.from( item.querySelectorAll( '.root-reply-container .content-warp .root-reply .reply-content-container .reply-content .emoji-large' ) ).concat( Array.from(item.querySelectorAll('.sub-reply-container .sub-reply-list')).flatMap( item2 => Array.from( item2.querySelectorAll( '.sub-reply-item .reply-content-container.sub-reply-content .reply-content .emoji-large' ) ) ) ) ) } else if (isSpecialComment) { emojis = Array.from( document .querySelector( '#app #mirror-vdcon .left-container #commentapp bili-comments' ) .shadowRoot.querySelectorAll('#contents #feed bili-comment-thread-renderer') ).flatMap(item => Array.from( Array.from( item.shadowRoot .querySelector('#comment') .shadowRoot.querySelector('#body #main #content bili-rich-text') .shadowRoot.querySelectorAll('#contents>img') ).concat( Array.from( item.shadowRoot .querySelector('#replies bili-comment-replies-renderer') .shadowRoot.querySelectorAll( '#expander #expander-contents bili-comment-reply-renderer' ) ).flatMap(item2 => Array.from( item2.shadowRoot .querySelector('#body #main bili-rich-text') .shadowRoot.querySelectorAll('#contents>img') ) ) ) ) ) } else if (isBlackboard) { emojis = Array.from( document .querySelector('#mall-iframe') .contentDocument.querySelectorAll( '#app .digital-card .digital-card-content .drawer .content .v-switcher .v-switcher__content .v-switcher__content__wrap .v-switcher__content__item .dlc-detail .dlc-cards .scarcity-block' ) ).flatMap(item => Array.from(item.querySelectorAll('.card-block .card-item'))) } } catch (error) { console.log(error) } const seen = new Set() const filteredEmojis = emojis.filter(item => { const imgName = getImgName(item, i) if (i !== 0 && i !== 4 && !imgName.includes('_')) { // 排除普通表情 return false } if (!seen.has(imgName)) { seen.add(imgName) return true } return false }) if (filteredEmojis.length === 0) { alert(alertMessage) return } let zipName = '' // eslint-disable-next-line no-undef const zip = new JSZip() const promises = filteredEmojis.map(async (item, imgIndex) => { if (imgIndex === 0) { zipName = getZipName(item, i) } zip.file( getImgName(item, i) + '.png', await blobToBinary(await (await fetch(getUrl(item, i))).blob()) ) }) Promise.all(promises).then(() => { const url = URL.createObjectURL(zip.generate({ type: 'blob' })) if (isCommonComment) { // a.click()会重定向 window.open(url) URL.revokeObjectURL(url) return } const a = document.createElement('a') a.href = url a.download = zipName + '.zip' document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) }) } div.appendChild(button) } document.body.appendChild(div) function getUrl(item, type) { let url, index switch (type) { case 0: // url("https://xxx.png") // url("//xxx.png" url = item.style.backgroundImage // return item.style.backgroundImage.match(/url\((")(.*?)\1\)/)?.[2] return url.replace('url("', '').replace('")', '') || url case 1: return item.src case 2: // 'url("https://xxx.png")' // 'url("http://xxx.png")' url = item.querySelector('.img-emoji-big').style.backgroundImage // return backgroundImage.match(/url\((")(.*?)\1\)/)?.[2].replace('http:', 'https:') return url.replace('url("', '').replace('")', '').replace('http:', 'https:') || url case 3: // //xxx.png@yyy url = item.src index = url.indexOf('@') return index > 0 ? url.slice(0, index) : url case 4: url = item.querySelector('.card-container .card .card-img img').src index = url.indexOf('@') return index > 0 ? url.slice(0, index) : url } } function getImgName(item, type) { switch (type) { case 0: return item.title.slice(1, -1) || item.title case 1: return item.alt.slice(1, -1) || item.alt case 2: return item.title case 3: return item.alt.slice(1, -1) || item.alt case 4: return item.querySelector('.name').innerText } } function getZipName(item, type) { switch (type) { case 0: return item.title.slice(1, -1).match(/^(.*?)_/)?.[1] || item.title default: return getImgName(item, type) + '等' } } function blobToBinary(blob) { return new Promise((resolve, reject) => { let fileReader = new FileReader() fileReader.readAsArrayBuffer(blob) fileReader.onloadend = e => { resolve(new Uint8Array(e.target.result)) } fileReader.onerror = () => { reject(new Error('blobToBinary failure')) } }) } })()