// ==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: '输入框没有表情包'
//
},
{
selector: '.message-list-content .msg-item .emotion-items.emotion-items-big',
innerHTML: '下载对话框表情包',
alertMessage: '对话框没有表情包'
//
//
//
},
{
innerHTML: '下载评论区表情包',
alertMessage: '评论区没有表情包'
//
},
{
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'))
}
})
}
})()