// ==UserScript==
// @name b站直播间房管辅助直播 bilibili 哔哩哔哩
// @namespace http://tampermonkey.net/
// @version 0.3
// @description try to take over the world!
// @author You
// @match *://live.bilibili.com/*
// @exclude *://live.bilibili.com/p/*
// @grant none
// ==/UserScript==
(function() {
const hour = '1'
function zfill(num, n) {
return (Array(n).join(0) + num).slice(-n);
}
class BasicAction{
static fontButtonOver(){this.style.color = '#23ade5'}
static fontButtonOut(){this.style.color = '#333'}
}
class FileUtils{
static saveFile(filename,data){
var urlObject = window.URL
var export_blob = new Blob([data],{type:'text/csv'});
var save_link = document.createElement('a')
save_link.href = urlObject.createObjectURL(export_blob);
save_link.download = filename;
save_link.click()
}
}
class Ajax{
static post(url,data={},head={},callback=()=>{},credentials=true){
const xhr = new XMLHttpRequest()
xhr.open('POST',url)
let urlData = new Array()
for(let d in data){urlData.push(d+'='+data[d])}
const urlParams = urlData.join('&')
for(let h in head){xhr.setRequestHeader(h,head[h])}
xhr.withCredentials = credentials
xhr.send(urlParams)
xhr.onreadystatechange = function(){
callback(xhr)
}
}
}
class Dialog{
static wholeScreen(child,height= '200px',width='200px',top='0px',left='0px'){
const background = document.createElement('div')
background.style = 'top:0px;left:0px;z-index:9999;position: fixed; background-color: rgba(0, 0, 0, 0.5); display: flex; flex-flow: column nowrap; justify-content: start; align-items: center; height: 100%; width: 100%;'
background.onclick = function(){
this.parentElement.removeChild(this)
}
let dialog = child
if(!child){
dialog = document.createElement('div')
dialog.style.backgroundColor = 'white'
dialog.style.borderRadius = '5px'
dialog.style.height = height
dialog.style.width = width
dialog.style.top = top
dialog.style.left = left
}
dialog.onclick = function (e){
e.stopPropagation()
}
background.appendChild(dialog)
document.body.appendChild(background)
return {background,dialog}
}
}
class AdminConsole{
constructor(){
this.injectStyle()
this.initConsole()
}
injectStyle(){
let style = ''
style += '.mana-fontbutton{cursor: pointer;margin-left: 4px;}'
style += '.mana-logbox { top: 200px; position: relative; display: flex; flex-flow: column nowrap; justify-content: start; align-items: center; padding: 20px 20px 20px 20px; background-color: white; border-radius: 5px; overflow: auto; width: 300px; height: 600px; }'
style += '.mana-logline{ border-bottom: 1px solid #e5e5e5; margin: 3px 0px 3px 0px; }'
const styleDOM = document.createElement('style')
styleDOM.innerHTML = style
styleDOM.setAttribute('mana-style','')
document.body.appendChild(styleDOM)
}
initConsole(){
const injectConsole = () =>{
const menu = document.getElementsByClassName('admin-drop-ctnr')[0]
if(menu){
const openLog = document.createElement('p')
openLog.className = 'drop-menu-item ts-dot-4'
openLog.innerText = '一键禁言记录'
openLog.style = 'white-space: nowrap;font-size: 12px;color: #333;line-height: 19px;margin: 0 0 8px;'
openLog.onmouseover = BasicAction.fontButtonOver
openLog.onmouseout = BasicAction.fontButtonOut
openLog.onclick = this.openLog
menu.appendChild(openLog)
return
}
requestAnimationFrame(injectConsole)
}
injectConsole()
}
openLog = () => {
let logFile = new Array()
for(let key in localStorage){
if(key.startsWith('blockLog-')){
logFile.push(key)
}
}
logFile.sort()
if(logFile.length > 0){
let box = document.createElement('div')
box.className = 'mana-logbox'
for(let file of logFile){
const line = document.createElement('div')
const title = document.createElement('span')
title.innerText = file
line.setAttribute('log',file)
line.className = 'mana-logline'
title.style.minWidth = '200px'
title.style.display = 'inline-block'
line.appendChild(title)
const del = document.createElement('span')
del.innerText = '删除'
del.onclick = this.deleteLog
del.onmouseover = BasicAction.fontButtonOver
del.onmouseout = BasicAction.fontButtonOut
del.className = 'mana-fontbutton'
const dowl = document.createElement('span')
dowl.innerText = '下载'
dowl.onclick = this.downloadLog
dowl.onmouseover = BasicAction.fontButtonOver
dowl.onmouseout = BasicAction.fontButtonOut
dowl.className = 'mana-fontbutton'
line.appendChild(del)
line.appendChild(dowl)
box.appendChild(line)
}
const d = Dialog.wholeScreen(box)
}
}
downloadLog(e){
try{
const logName = e.target.parentElement.getAttribute('log')
const log = localStorage.getItem(logName)
FileUtils.saveFile(logName+'.csv',log)
}catch(e){console.log('导出日志异常')}
}
deleteLog(e){
try{
const logName = e.target.parentElement.getAttribute('log')
if(confirm(`是否要删除日志: ${logName} ?`)){
localStorage.removeItem(logName)
const container = e.target.parentElement.parentElement
for(let line of container.children){
if(line.getAttribute('log') === logName){
container.removeChild(line)
}
}
}
}catch(e){console.log('删除日志异常')}
}
}
class Blocker{
constructor(){
this.bindMutation()
}
bindMutation(){
const observation = (mutations,observer) =>{
for(let mutation of mutations){
if(mutation.addedNodes.length > 0){
for(let node of mutation.addedNodes){
if(node.hasAttribute('data-uid')){
const deleteButton = document.createElement('span')
deleteButton.innerText = '禁言'
deleteButton.className = 'v-middle'
deleteButton.onclick = this.addBlockUser
deleteButton.style = 'color:#23ade5;cursor:pointer;line-height:20px'
node.insertBefore(deleteButton,node.children[0])
}
}
}
}
}
this.chatList = document.getElementById('chat-items')
const config = {childList:true}
const observer = new MutationObserver(observation)
observer.observe(this.chatList,config)
}
removeBlockUser (dom,data,logTitle,uid,uname) {
const head = {
'accept': 'application/json, text/plain, */*',
'content-type': 'application/x-www-form-urlencoded'
}
Ajax.post('//api.live.bilibili.com/banned_service/v1/Silent/del_room_block_user',data,head,(xhr)=>{
if(xhr.readyState == 4 && xhr.status == 200 ){
const res = JSON.parse(xhr.responseText)
if(res.code == 0){
let log = window.localStorage.getItem(logTitle) || ''
const date = new Date()
log += `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()},${uid},${uname},撤销禁言\n`
window.localStorage.setItem(logTitle,log)
dom.innerText = '禁言'
dom.onclick = this.addBlockUser
}
}
})
}
addBlockUser = (e) =>{
const container = e.target.parentElement
const uid = container.getAttribute('data-uid')
const uname = container.getAttribute('data-uname')
const danmaku = container.getAttribute('data-danmaku')
const date = new Date()
const block_uid = uid || '1'
const logTitle = `blockLog-${date.getFullYear()}-${zfill(date.getMonth()+1,2)}-${zfill(date.getDate(),2)}`
const cookie = new WebCookie()
const data = {
roomid:cookie.roomid || '1',
block_uid,
hour,
csrf_token: cookie.bili_jct || '1',
csrf: cookie.bili_jct || '1',
visit_id:""
}
const head = {
'accept': 'application/json, text/plain, */*',
'content-type': 'application/x-www-form-urlencoded'
}
Ajax.post('//api.live.bilibili.com/banned_service/v2/Silent/add_block_user',data,head,(xhr)=>{
if(xhr.readyState == 4 && xhr.status == 200 ){
const res = JSON.parse(xhr.responseText)
if(res.code == 0){
const id = res.data.id
let log = window.localStorage.getItem(logTitle) || ''
log += `${zfill(date.getHours(),2)}:${zfill(date.getMinutes(),2)}:${zfill(date.getSeconds(),2)},${uid},${uname},${danmaku}\n`
window.localStorage.setItem(logTitle,log)
const button = container.children[0]
button.innerText = '撤销'
const data = {
id,
roomid:cookie.roomid || '1',
csrf_token: cookie.bili_jct || '1',
csrf: cookie.bili_jct || '1',
visit_id:""
}
button.onclick = () =>{
this.removeBlockUser.call(this,button,data,logTitle,uid,uname)
}
}
}
})
}
}
class WebCookie{
constructor(){
const cookies = {}
for(let cookie of document.cookie.split(';')){
const [k,v] = cookie.trim().split('=')
cookies[k] = v
}
cookies ['roomid'] = document.location.pathname.substr(1).split('?')[0]
return cookies
}
}
const adminConsole = new AdminConsole()
const blocker = new Blocker()
})();