// ==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();
})();