B站表情包下载

批量下载B站表情包/收藏集图片

// ==UserScript==
// @name         B站表情包下载
// @namespace    http://tampermonkey.net/
// @version      1.7
// @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
// ==/UserScript==

;(function () {
  'use strict'
  const buttons = [
    {
      selector: '.emoji-list .emoji div',
      textContent: '下载选中系列表情包',
      alertMessage: '请先点击表情选项并选中需要下载的表情包'
      // <div title="[xxx]" style="background-image: url("https://xxx.png");"></div>
    },
    {
      selector: '.bili-im .right .dialog:not(.hide) .send-box .input-box .core-style img',
      textContent: '下载输入框表情包',
      alertMessage: '输入框没有表情包'
      // <img src="https://xxx.png" alt="[xxx]">
    },
    {
      selector: '.message-list-content .msg-item .emotion-items.emotion-items-big',
      textContent: '下载对话框表情包',
      alertMessage: '对话框没有表情包'
      // <a title="xxx">
      //  <div style="background-image:url(http://xxx.png);"></div>
      // </a>
    },
    {
      textContent: '下载评论区表情包',
      alertMessage: '评论区没有表情包'
      // <img src="//xxx.png@yyy" alt="[xxx]">
    },
    {
      textContent: '下载收藏集图片',
      alertMessage: '请参照教程打开收藏集卡池详情'
      // <div class="card-item">
      //   <div class="card-container">
      //     <div class="card">
      //       <div class="card-img">
      //         <img src="https://xxx.png@yyy" class="img" />
      //       </div>
      //     </div>
      //   </div>
      //   <div class="name">xxx</div>
      // </div>
    }
  ]
  const buttonStyle = {
    width: '130px',
    marginBottom: '25px',
    cursor: 'pointer',
    padding: '2px',
    border: '1px solid #767676',
    backgroundColor: '#efefef',
    fontSize: '12px'
  }
  // 是否私信
  const isMessage = location.href.startsWith('https://message.bilibili.com')
  // 是否评论区
  const isComment = /^https:\/\/www\.bilibili\.com\/(video|bangumi|opus)/.test(location.href)
  // 是否收藏集
  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 !== 0 && i !== 1 && i !== 2) {
        continue
      }
    } else if (isComment) {
      if (i !== 3) {
        continue
      }
    } else if (isBlackboard) {
      if (i !== 4) {
        continue
      }
    } else {
      return
    }
    const { selector, textContent } = buttons[i]
    const button = document.createElement('button')
    button.textContent = textContent
    Object.assign(button.style, buttonStyle)
    button.onclick = () => {
      try {
        let emojis = []
        if (isMessage) {
          emojis = getMessageEmojis(selector)
        } else if (isComment) {
          emojis = getCommentEmojis()
        } else if (isBlackboard) {
          emojis = getBlackboardEmojis()
        }
        downloadEmojis(emojis, i)
      } catch (e) {
        console.log(e)
      }
    }
    div.appendChild(button)
  }
  // 评论区添加单条评论表情下载按钮
  if (isComment) {
    const button = document.createElement('button')
    button.textContent = '单条评论表情下载'
    Object.assign(button.style, buttonStyle)
    button.onclick = () => {
      const emojis = getCommentEmojis()
      const emojiParent = new Set()
      emojis.forEach(item => {
        if (getImgName(item, 3).includes('_')) {
          // 排除普通表情
          emojiParent.add(item.parentNode)
        }
      })
      Array.from(emojiParent).forEach(item => {
        if (item.lastElementChild.tagName === 'BUTTON') {
          // 添加过的跳过
          return
        }
        // 写到外面会从原来的父节点移除
        const innerButton = document.createElement('button')
        innerButton.textContent = '下载'
        innerButton.onclick = () => {
          const emojis = Array.from(innerButton.parentNode.querySelectorAll('img'))
          downloadEmojis(emojis, 3)
        }
        item.appendChild(innerButton)
      })
    }
    div.appendChild(button)
  }
  document.body.appendChild(div)
  function getMessageEmojis(selector) {
    return Array.from(document.querySelectorAll(selector))
  }
  function getCommentEmojis() {
    return Array.from(
      document
        .querySelector('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')
            )
          )
        )
      )
    )
  }
  function getBlackboardEmojis() {
    return 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')))
  }
  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'))
      }
    })
  }
  function downloadEmojis(emojis, i) {
    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(buttons[i].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)
      }
      const url = getUrl(item, i)
      zip.file(
        getImgName(item, i) + '.' + url.split('.').pop(),
        await blobToBinary(await (await fetch(url)).blob())
      )
    })
    Promise.all(promises).then(() => {
      const url = URL.createObjectURL(zip.generate({ type: 'blob' }))
      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)
    })
  }
})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址