弹幕词云

bilibili弹幕词云

目前为 2021-04-21 提交的版本。查看 最新版本

// ==UserScript==
// @name         弹幕词云
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  bilibili弹幕词云
// @author       You
// @match        https://www.bilibili.com/bangumi/play/*
// @match        https://www.bilibili.com/video/*
// @grant        none
// @require      https://cdn.bootcdn.net/ajax/libs/wordcloud2.js/1.1.2/wordcloud2.min.js
// ==/UserScript==

(function() {
    'use strict';
function geo(){
    console.log("开始")
  //  setInterval(modi,1000);
   init();
}
//======变量=======
var canv;
var btnTextE;
var baocun;//保存按钮
var baocun2;//保存按钮
var holder;//包裹词云
var canvP;//词云div
var title;
var hasCi=false;//词云是否显示
let s=[]//弹幕
let sai=[]//词频
let dmBar;
let fail=false;
let tez=[0,1]//[最大值,最小值,中位数]
let count=350//最大词数
let reqParm=[0,1]
let liFac=0.5;//
let weiFac=0.5;//字体权重
let colorTable=['#9794DB','#8240BF','#6344C1','#4840BF','#444EC1','#4461C1','#5E90C9','#4BABC3','#66C7CC','#66CCBD','#53C69F','#BDD071','#C9965E','#C15444','#BF4640','#D13D3D','#FF533E']
let peiIdx=0;
let sepei=[//背景色,高频色,低频色,遮罩
  ['#AC1F18',[28,13,26],[54,41,47],'//i0.hdslb.com/bfs/article/874074999995e6981d7602a7d5a770043a10b932.png@256w_256h.webp','对联红'], 
  ['#621d34',[226,225,228],[97,113,114],'//i0.hdslb.com/bfs/article/874074999995e6981d7602a7d5a770043a10b932.png@256w_256h.webp','鹞冠紫'],
  ['#ed9db2',[51,20,30],[181,152,162],'//i0.hdslb.com/bfs/article/874074999995e6981d7602a7d5a770043a10b932.png@256w_256h.webp','豇豆红'],
  ['#2775b6',[255,255,255],[73,92,105],'//i0.hdslb.com/bfs/article/874074999995e6981d7602a7d5a770043a10b932.png@256w_256h.webp','景泰蓝'],
  ['#1ba784',[173, 213, 162],[65, 174, 60],'//i0.hdslb.com/bfs/article/874074999995e6981d7602a7d5a770043a10b932.png@256w_256h.webp','竹绿'],
  ['#fed71a',[140, 194, 105],[140, 194, 105],'//i0.hdslb.com/bfs/article/874074999995e6981d7602a7d5a770043a10b932.png@256w_256h.webp','佛手黄'],
  ['#f26b1f',[249, 233, 205],[240, 156, 90],'//i0.hdslb.com/bfs/article/874074999995e6981d7602a7d5a770043a10b932.png@256w_256h.webp','金黄'],
  ['#500A16',[226,225,228],[97,113,114],'//i0.hdslb.com/bfs/article/d2bba9afdcd82302d8d387696e076da26050fb37.jpg@1320w_1760h.webp','浪'],
]//背景+文字颜色2
//======函数======

function drag(obj){
  obj.onmousedown = function(e){
      // 鼠标点击物体那一刻相对于物体左侧边框的距离=点击时的位置相对于浏览器
      // 最左边的距离-物体左边框相对于浏览器最左边的距离,纵向同理
      var divX = e.clientX - this.offsetLeft;
      var divY = e.clientY - this.offsetTop;
      
      document.onmousemove = function(e){
          var disX = e.clientX - divX;
          var disY = e.clientY - divY;
        
          // 移动时重新得到物体的距离,解决拖动时出现晃动现象  
          obj.style.top = disY + "px";
          obj.style.left = disX + "px";
          document.onmouseup = function(){    // 鼠标抬起时不再移动  
              // 预防鼠标弹起来后还会循环(即预防鼠标放上去的时候还会移动)
              document.onmousedown = document.onmousemove = null;
          }
      }
  }
}

var init=()=>{
  var sty=document.createElement("style");
  sty.innerHTML=` .blura{
    animation: 1s animatea 1 linear;
  }
  .blura:hover{
    animation: 2s animateb 1 linear;
  }
  @keyframes animatea{
  0%{ text-shadow: 0 0 0 white; }
  50%{
    color: rgba(238, 235, 235, 0.8);
    text-shadow: 0 0 30px rgb(216, 227, 243);
  }
  100%{ text-shadow: 0 0 40px white; }
  }
  @keyframes animateb{
    0%{ text-shadow: 0 0 0px rgba(255,255,255,0); }
    50%{
      color: rgba(255, 255, 255, 0.925);
      text-shadow: 0 0 10px rgb(184, 245, 242);
    }
    100%{ text-shadow: 0 0 0 white; }
  }
  @keyframes ac{
    0%{ color: rgba(33, 33, 33, 0.9); }
    50%{color: rgba(238, 235, 235, 0.9);}
    100%{ color: rgba(33, 33, 33, 0.9); }
  }
  @keyframes ad{
    0%{ color: rgba(33, 33, 33, 0.9); }
    50%{color: rgba(238, 235, 235, 0.9);}
    100%{ color: rgba(33, 33, 33, 0.9); }
  }
  .btnBg{
    position:relative;top:-17px;background: linear-gradient(45deg , #e5c0ff, skyblue);border-radius: 4px;filter: blur(15px);width: 100%;height: 100%;
  }
  .blr-btn{
    user-select:none;overflow:hidden;position:fixed;top:56px;z-index:999999;left:10px;width: 54px;height: 26px;border-radius: 3px;font-size: 18px;padding: 5px;cursor: pointer;
    width:fit-content;
    width:-webkit-fit-content;
    width:-moz-fit-content;
    animation: 0.5s ad 1 linear;
  }
  .blr-btn:hover{
    animation: 0.5s ac 1 linear;
  }
  .fEl{
    width: 100%;
    height: 25px;
    padding: 5px 0px;
    color: cornsilk;
  }
  .bar-indicator{
    height: 5px;
    background-color: rgb(76 173 104);
    width: 80%;
  }
  .fEl:hover{
    background-color: rgba(96, 160, 243, 0.363);
    transition:1s;
    cursor: pointer;
  }
  .frequency{
    width: 10%;
    float: right;
    font-size: 14px;
    padding: 9px 5px 0 0;
    text-align: right;
    text-shadow: -2px -1px 8px #bdfa9d;
  }
  .danmu-word{
    padding: 5px;
    font-size: 12px;
    line-height: 15px;
  }
  `
  document.head.append(sty)
  //按钮
  let cibtn=newBtn(document.body,ciw,10,54,'词云');
  btnTextE=cibtn.tx;
  let saveBtn=newBtn(document.body,()=>{
    saveDiv(holder)
  },10,100,'保存')
  let saveBtn2=newBtn(document.body,()=>{
    saveDiv(canvP)
  },10,140,'仅文字保存')
  
  let dmBarBtn=newBtn(document.body,()=>{
    if(dmBar.hidden) {
      dmBar.hidden=false
      $(dmBar).fadeIn( 300 , 'linear')
    }
    else{
      dmBar.hidden=true;
      dmBar.style.display='none'
    }
  },60,54,'列表')
  
  baocun=saveBtn.bt
  baocun.style.display='none'
  baocun2=saveBtn2.bt
  baocun2.style.display='none'
  //词云画布
  canv=document.createElement("div");
  canvP=document.createElement("div");//背景
  canvP.append(canv);
  canv.style="height: 100%;width: 100%;"
  
  //渐变
  // canvP.style="opacity:0.96;border: 5px solid rgb(237 237 237 / 74%);height: 700px;width: 1200px;position:fixed;top:50px;left:60px;z-index:999999;background:linear-gradient(311deg, rgb(234 229 229), rgb(233 241 227), rgb(218 231 230));";
  //单色background-image: url(https://i0.hdslb.com/bfs/album/d16f34b89e59174bd066a27101bf6b9c92d16615.jpg);
  canvP.style="background-image: url(//i0.hdslb.com/bfs/article/874074999995e6981d7602a7d5a770043a10b932.png@256w_256h.webp);opacity:0.96;border: 5px solid rgb(237 237 237 / 74%);height: 700px;width: 1200px;background-color: rgb(236, 43, 36);";
  holder=document.createElement('div')
  holder.style="position:fixed;top:40px;left:80px;z-index:999999;"
  holder.setAttribute("hidden",true);
  title=document.createElement("span")
  title.style="font-size:10px;color:rgb(225,235,215);position:absolute;z-index:10;background-color:#888"
  holder.append(canvP)
  holder.append(title)
  document.body.append(holder)
  drag(holder)

  //弹幕列表
  dmBar=document.createElement('div')
  dmBar.style='background-color:rgb(128 204 150 / 96%);position: absolute;z-index: 999990;width: 320px;border: 4px solid #c1f8db38;'
  dmBar.style.display='none'
  dmBar.hidden=true
  drag(dmBar)
}

var modi=()=>{
  var dms=$(".b-danmaku");
  for(var i=0;i<dms.length;i++){
      if(dms[i].innerHTML.indexOf("div")==-1){
          var s=dms[i].style.fontSize.replace("px","")
          var l=dms[i].innerHTML.length*s+"px"
          dms[i].innerHTML=` <div style="overflow: hidden;height: `+dms[i].style.fontSize+`;width: `+l+`">
          <div style="position: relative;top: -32px;">`+dms[i].innerHTML+`</div>
          <div style="width: 100%;
          height:  100%;
          background: linear-gradient(45deg , #e5c0ff, skyblue);
          border-radius: 4px;
          filter: blur(20px);
          position: relative;
          top: -90px;"></div>
      </div>`
      }
  }
}
//产生词云
function ciw(){
  if(hasCi) {
    holder.setAttribute("hidden",true);
    $(baocun,baocun2).fadeOut( 1000 , 'linear' , ()=>{
    })
    $(baocun2).fadeOut( 1000 , 'linear' , ()=>{
    })
    btnTextE.innerText='词云'
    hasCi=false;
    return;
  };
  console.log('生成词云');
  btnTextE.innerText='→_→';
  if(sai.length==0||window.cid!=reqParm[0]) analysi();

  peiIdx=peiIdx==sepei.length-1?0:peiIdx+1
  holder.removeAttribute("hidden");
  btnTextE.innerText='关闭'
  backgroundImag()
  WordCloud([canv], 
  { 
   list: sai,
   color: colorw, //'random-light'
   backgroundColor: '',
   gridSize: 18,
   weightFactor: weiFac,//200/tez[0],//被除数为最大字号
  fontFamily: '楷体',
  rotateRatio: 0.5,
  classes: "blura",
  rotationSteps: 2
} );
hasCi=true;
$(baocun).fadeIn( 1000 , 'linear')
$(baocun2).fadeIn( 1000 , 'linear')
}
//上色
let colorw=(wd,weight)=>{
  //tez[0]红色 #ff0000
  //1=>蓝色 #0000ff
  // console.log(wd,weight,tez[0])
  let i=sai.findIndex(e=>e[0]==wd);
  let v=i/sai.length;
  
  // let v=parseInt(colorTable.length*i/sai.length)
  // return colorTable[v];
  let s=sepei[peiIdx][1]
  let e=sepei[peiIdx][2]
  let colo='rgb('
  for(let x in s){
    colo+=(parseInt(s[x]+v*(e[x]-s[x])))+','
  }
  return colo.substr(0,colo.lastIndexOf(','))+')'
}

let analysi=()=>{
  //清空
  s=[];
  sai=[];
  loadParam()
  reCalcFac()
  console.log("等待弹幕获取完成")
  btnTextE.innerText='^_^'
  getOneSeg(1)
  //计算weiFac
  if(sai.length>count) sai.splice(count)
  let siz=0;
  sai.forEach(e=>siz+=e[0].length*e[1]*e[1])
  let bgSize=canvP.style.height.replace("px","")*canvP.style.width.replace("px","")
  weiFac=Math.sqrt(bgSize/siz)

  tez[0]=sai[0][1]
  tez[1]=sai.slice(-1)[0][1]
  tez[2]=sai[parseInt(sai.length/2)][1]

  console.log("大小中",tez)
  console.log("筛选结果:",sai)
  dmLiebiao()//
}

let getOneSeg=(seg)=>{
  let regex=/:.(.*?)[�@]/
  $.ajax({
       url:'https://api.bilibili.com/x/v2/dm/web/seg.so?type=1&oid='+reqParm[0]+'&pid='+reqParm[1]+'&segment_index='+seg,
       data:{},
            async:false,
            cache:false,       
            ifModified :true,
      type:'GET',
      success:function(re, textStatus, xhr){ //成功回调函数
              if(xhr.status!=200) {
                console.log("失败点:",seg)
                fail=true
                return;
              }
              let ss=re.split('\n');
              ss.forEach(e=>{
                let dm=regex.exec(e);
                if(dm&&dm[1])
                s.push(dm[1])
              })
              console.log(s);
              //分析进行
              cia(1)
              //清空s
              s=[]
              sleep(500)
              getOneSeg(seg+1)
       },
      error:function (err){ //失败回调函数
            fail=true;
            console.log("失败点:",seg)
       console.log(err);
       }
      });
}

function cia(l){
  console.log('分析弹幕...')
  btnTextE.innerText='>_<'
  let wds=[]
  s.forEach(e => {
    e=e.replace(/[\ |\~|\`|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\||\\|\[|\]|\{|\}|\;|\:|\"|\'|\,|\<|\.|\>|\/|\?]/g,"");
    ctin(wds,e)
  });
  
  wds.forEach(e=>{
  if(e[1]>=l) {
    sai.push(e)
  }
  })
  sai.sort((a,b)=>b[1]-a[1]);//降序
}
function ctin(a,b){
  var h=true;
  for(let i in a){
    if(a[i][0].indexOf(b)>-1||xiangsi(a[i][0],b)){
      a[i][1]+=1;
      h=false;
    } 
}
if(h){
  a.push([b,0])
}
}
let xiangsi=(a,b)=>{
  if(xiangsi0(a,b)||xiangsi0(b,a)) return true;
  return false;
}
let xiangsi0=(a,b)=>{
  let m=a.split('')
  let c=0;
  m.forEach(e=>{
    if(b.indexOf(e)>-1) c++;
  })
  if(c/m.length>=liFac) return true;
  return false;
}

let loadParam=()=>{
  reqParm= [window.cid,window.aid]
  console.log('找到参数:',reqParm)
}
//设置词云宽高和筛选因子
let reCalcFac=()=>{
  let m= parseInt(document.querySelector('.bilibili-player-video-info-danmaku-number').innerText);
  let titleEl=document.querySelector('#media_module > div > a')
  if(!titleEl) titleEl=document.querySelector('#viewbox_report > h1 > span')
  title.innerText="视频标题:"+titleEl.innerText+"   "+new Date()
  if(m<1000) {
    liFac=0.5
  }else{
    liFac=0.5+(m-1000)/10000;
    if(liFac>1) liFac=1;
  }
  let w=window.innerWidth-160;
  let h=window.innerHeight-70;
  canvP.style.width=w+'px'
  canvP.style.height=h+'px'
  holder.style.width=(10+w)+'px'
  holder.style.height=(10+h+17.6)+'px'
}
let dmLiebiao=()=>{
  dmBar.innerHTML=''//
  if(null==dmBar.parent){
    let p=document.querySelector('#app > div.v-wrap > div.r-con');
    if(null==p) p=document.querySelector('#app > div.plp-r');
    p.prepend(dmBar)
  }
  sai.forEach(e=>{
    let fe=document.createElement('div');
    fe.className='fEl';
    let dw=document.createElement('span');
    dw.className='danmu-word';
    dw.innerHTML=e[0]
    if(e[0].length>21){
      dw.innerText=e[0].substr(0,21)+"..."
    }
    let f=document.createElement('div');
    f.className="frequency";
    f.innerText=e[1]
    let bi=document.createElement('div');
    bi.className='bar-indicator'
    bi.style='width:'+(Math.round(80*e[1]/tez[0]))+'%'
    fe.append(dw)
    fe.append(f)
    fe.append(bi)
    dmBar.append(fe)
  })
}

let backgroundImag=()=>{
  console.log("主题更改:",sepei[peiIdx][4])
  canvP.style.backgroundImage='url("'+sepei[peiIdx][3]+'")'
  canvP.style.backgroundColor=sepei[peiIdx][0]
  // $.ajax({
  //   url:'http://zhongguose.com/img/texture.png',
  //   type:'get',
  //   success:(data)=>{
  //     console.log(daata)
  //     var img=new Blob([data],{type:"png"})
  //     var url=URL.createObjectURL(img);
  //     canvP.style.backgroundImage='url("'+url+'")'
  //   }
  // })
}
//保存div内容为png
function saveDiv(div){
  //1.将div转成svg
  var divContent = div.innerHTML;
  let w=div.offsetWidth;
  let h=div.offsetHeight
  if(w==0||h==0){
    w=div.firstElementChild.offsetWidth
    h=div.firstElementChild.offsetHeight
  }
  var data = "data:image/svg+xml," +
  "<svg xmlns='http://www.w3.org/2000/svg' width='"+w+"' height='"+h+"'>" +
  "<foreignObject width='100%' height='100%'>" +
  "<div xmlns='http://www.w3.org/1999/xhtml' style='font-size:16px;font-family:Helvetica'>" +
  divContent +
  "</div>" +
  "</foreignObject>" +
  "</svg>";
  var img = new Image();
  img.src = data;
  // document.body.appendChild(img);
  //2.svg转成canvas
  var canvas = document.createElement('canvas');  //准备空画布
  
  img.onload=()=>{
    var dpr =window.devicePixelRatio;
    canvas.width = img.width;
    canvas.height =img.height;
    canvas.style.width =dpr*w + "px"
    canvas.style.height =dpr*h + "px"
    var context = canvas.getContext('2d');  //取得画布的2d绘图上下文
    context.drawImage(img, 0, 0);
    var a = document.createElement('a');
    a.href = canvas.toDataURL('image/png');  //将画布内的信息导出为png图片数据
    let titleEl=document.querySelector('#media_module > div > a')
    if(!titleEl) titleEl=document.querySelector('#viewbox_report > h1 > span')
    a.download =titleEl.innerText+(new Date()).getTime()+".png";  //设定下载名称
    a.click()
  }
}
//生产按钮
function newBtn(p,f,left,top,word){
  var cibtn= document.createElement("div");
  var cibtnBg= document.createElement("div");
  cibtnBg.className='btnBg'
  let TextE=document.createElement('span')
  TextE.innerText=word
  cibtn.append(TextE)
  cibtn.className='blr-btn'
  cibtn.append(cibtnBg);
  cibtn.style.left=left+'px';
  cibtn.style.top=top+'px';
  // moveAble(cibtn,f)//setTimeOut还是卡
  cibtn.onclick=f;
  p.append(cibtn)
  return {bt:cibtn,bg:cibtnBg,tx:TextE}
}
function sleep(numberMillis) {
	var now = new Date();
	var exitTime = now.getTime() + numberMillis;
	while (true) {
		now = new Date();
		if (now.getTime() > exitTime)
		return;
	    }
}
geo();
})();

QingJ © 2025

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