// ==UserScript==
// @name B站油管showroom简易打尻装置
// @namespace http://tampermonkey.net/
// @version 0.5.2.1
// @description 啊B、油管或showroom打尻,需要用户已登录。若有滥用等问题概不负责,诶嘿。顺便关注一下小东人鱼和noworld吧~
// @author 太陽闇の力
// @include /https?:\/\/live\.bilibili\.com\/(blanc\/)?\d+\??.*/
// @match https://www.youtube.com/live_chat*
// @exclude https://www.youtube.com/live_chat_replay*
// @match https://www.showroom-live.com/*
// @exclude https://www.showroom-live.com/room/*
// @require https://cdn.jsdelivr.net/gh/eric2788/bliveproxy@d66adfa34cbf41db3d313f49d0814e47cb3b6c4c/bliveproxy-unsafe.js
// @grant unsafeWindow
// @license MIT
// ==/UserScript==
//1.界面参考自小东人鱼午安社五更耗纸 https://github.com/gokoururi-git/gachihelper/
//2.interval.min、interval.max、interval.step分别是滑动条的最小值、最大值和滑动间隔,已根据平台区别设定了这三个值和interval的关系,尽量避免发送太猛吓到主播。
//3.B站直播五秒内同一句打call的话,会显示发送频率太快而无法发送成功。建议写多几句不一样的。
//4.showroom的50 count中,搜索找到下面这两行代码可以更改设置
//5.const kankaku = 2; //50 count的时间间隔 。网络不好的时候不宜进行50 count,会自动停止
//6.countDownButton.innerText = kankaku*(50-1)+1;//50 count所用总时间。这个值是我乱猜的,不过也有实验过姑且能用的样子
(function() {
let waitTime = 1;//等待1秒,如果加载比较慢的话,不等待可能获取不到元素。
let times = 10;//尝试查询的次数
let timescopy = times;
let maint;
let isOnLive;//showroom是否在直播
const main = () => {
try {
//-----------配置区-------------
//0默认收起,1默认展开
let isunfold = 0;
let unfold = ["展开","收起"];
let hachihachikinou = 1;//是否开启观众发送888的人数超过固定人则自动停止功能(仅限B站直播间)
const hachihachitext = ['不检888','检测888'];
let hachihachi = 3;//观众发送888的人数超过3人则自动停止
let hachihachitimes = 0;//观众发送888的人数计数器
//输入框选择符
let inputSelector = 'textarea';
//发送按钮选择符
let sendSelector = '.bl-button';
const urlCheck = (s)=>window.location.host == s;
const isShowroom = urlCheck("www.showroom-live.com");
const isBilibili = urlCheck("live.bilibili.com");
const isYoutube = urlCheck("www.youtube.com");
let countdown = "220"; //倒计时
let intervaltime = "6";//发送间隔
let biliTextArea;
let biliTextSender;
if(isYoutube){//如果在油管的话设置初始发送间隔时间为20秒
intervaltime="20";
}
if(isShowroom){//判断是不是showroom的直播间
intervaltime="50";//如果在showroom的话设置初始发送间隔时间为20秒
const showroomURL = "https://www.showroom-live.com";
let ogURL = window.document.querySelector("meta[property='og:url']");
ogURL = ogURL?.content;
if(ogURL==showroomURL) {
clearInterval(maint);
return;
}
const showroomID = /(?!room_id=)\d+$/.exec(window.document.querySelector("#this-room-profile")?.href)[0];
const liveURL = "https://www.showroom-live.com/api/live/live_info?room_id="+showroomID;
const req = new XMLHttpRequest();
let live_status;
req.onload = function(){
if(req.status == 200){
live_status = JSON.parse(req.responseText).live_status;
isOnLive = live_status == 2;
}
}
req.open("GET",liveURL,false);
req.send(null);
if(!isOnLive) {
clearInterval(maint);
return;
}
//免费礼物点一下就能十连发
const comboInterval=25;
const list = window.document.querySelector("#room-gift-item-list");
const gift = list.childNodes;
for(let i = 0;i<5;i++){
gift[i].addEventListener('mousedown',(e)=>{
if(e.which ==1){//左键点下
const num = e.target.parentNode.parentNode.querySelector("div").innerText.split(" ")[1]-1;
const comboNum = num > 9 ? 9 : num;
for(let i = 1;i<=comboNum;i++){
setTimeout(()=>{e.target.click();},i*comboInterval)
}
}
})
}
//评论栏和发送按钮
inputSelector ='.comment-input-text';
sendSelector = '.js-room-comment-btn';
}
//iframe,生日直播间之类的会成嵌套框架的结构,那个时候只看框架代码
if(isBilibili&&!(window.document.firstChild instanceof window.Comment)){//「あなたに逢えなくなって、錆びた時計と泣いたけど…」
clearInterval(maint);
return;
}
clearInterval(maint);
const callDealler = (call) => {
let tempcallResult = [];
call = call.trim();
call = call.replace(/ /g, '');
call = call.replace(/ /g, '');
call = call.replace(/\n{2,}/g, '\n');
tempcallResult = call.split('\n');
return tempcallResult;
}
let callResult = [];
let currIndex = 0;
let t = null;
let ct = null;
const inputEvent = document.createEvent("Event");
inputEvent.initEvent("input",true, true);
const send = function(){
if(isYoutube){
biliTextArea.innerText = callResult[currIndex++];
}else{
biliTextArea.value = callResult[currIndex++];
}
biliTextArea.dispatchEvent(inputEvent);
biliTextSender.click();
}
const next = function() {
if(isShowroom&&!biliTextSender.classList.contains('is-disabled')){
biliTextArea.value = "";
pause();
return;
}
currIndex %= callResult.length;
send();
}
let intervalChoose;
const init = function() {
biliTextArea = window.document.querySelector(inputSelector);
biliTextSender = window.document.querySelector(sendSelector);
if(isYoutube){
biliTextArea = window.document.querySelector("#input").querySelector("#input");
biliTextSender = window.document.querySelector("#send-button").querySelector("#button");
}
currIndex = 0;
send();
timeLabel.innerText = intervalChoose;
t = setInterval(next, intervalChoose * 1000);
}
// ------------------GUI设计开始---------------
// 总容器
const container = window.document.createElement('div');
container.style.cssText = 'width:260px;position:fixed;bottom:5px;left:5px;z-index:999;box-sizing:border-box;';
// 工具名称
const topTool = window.document.createElement('div');
topTool.innerText = 'call';
topTool.style.cssText = 'text-align:center;line-height:20px;height:20px;width:100%;color:rgb(210,143,166);font-size:14px;';
// 最小化按钮
const collapseButton = window.document.createElement('button');
collapseButton.innerText = unfold[isunfold];
collapseButton.style.cssText = 'float:right;width:40px;height:20px;border:none;cursor:pointer;background-color:#1890ff;border-radius:1px;color:#ffffff;';
// 主窗口
const mainWindow = window.document.createElement('div');
mainWindow.style.cssText = 'width:100%;background-color:rgba(220, 192, 221, .5);padding:10px;box-sizing:border-box;';
if(isunfold==0){
mainWindow.style.display = "none";
}
// call框
const textArea = window.document.createElement('textarea');
textArea.style.cssText = 'width:100%;height:50px;resize:none;outline:none;background-color:rgba(255,255,255,.5);border-radius:2px';
// 按钮区容器
const buttonArea = window.document.createElement('div');
buttonArea.style.cssText = 'width:100%;height:30px;box-sizing:border-box;display:flex; justify-content: center;';
// 按钮区容器2
const buttonArea2 = window.document.createElement('div');
buttonArea2.style.cssText = 'width:100%;height:30px;box-sizing:border-box;display:flex;justify-content: space-around;';
// 开始按钮
const goButton = window.document.createElement('button');
goButton.innerText = '开始';
goButton.style.cssText = 'width:max-content;height:28px;padding:0 5px;margin-left:5px;';
// 发送间隔提示文本
const intervalLabel = window.document.createElement('div');
intervalLabel.innerText = '发送间隔:'
intervalLabel.style.cssText = 'width:70px;height:28px;line-height:28px;';
// 888功能按钮
let hachihachiButton;
if(isBilibili){
hachihachiButton = window.document.createElement('button');
hachihachiButton.innerText = hachihachitext[hachihachikinou];
hachihachiButton.style.cssText = 'width:max-content;height:28px;padding:0 5px;margin-left:5px;';
}
// 选择延迟
const interval = window.document.createElement('input');
interval.type = "range";
interval.step = "0.1";
interval.min = (intervaltime-2)/2;
interval.value = intervaltime;
interval.max = (+intervaltime+14)*1.5;
interval.style.cssText = 'width:max-content;padding:0 5px;height:28px;margin-left:5px;';
const timeLabel = window.document.createElement('div');
timeLabel.innerText = intervaltime;
timeLabel.style.cssText = 'width:24px;height:28px;line-height:28px;';
const secondLabel = window.document.createElement('div');
secondLabel.innerText = '秒';
secondLabel.style.cssText = 'width:max-content;height:28px;line-height:28px;';
// 倒计时
const countDownButton = window.document.createElement('button');
countDownButton.setAttribute("contenteditable", "true");
countDownButton.innerText = countdown;
countDownButton.style.cssText = 'width:50px;height:28px;margin-left:5px;padding:0 5px;';
// 组装
topTool.appendChild(collapseButton);
container.appendChild(topTool);
mainWindow.appendChild(textArea);
buttonArea.appendChild(intervalLabel);
buttonArea.appendChild(interval);
buttonArea.appendChild(timeLabel);
buttonArea.appendChild(secondLabel);
buttonArea2.appendChild(goButton);
if(isBilibili){
buttonArea2.appendChild(hachihachiButton);
}
buttonArea2.appendChild(countDownButton);
mainWindow.appendChild(buttonArea);
mainWindow.appendChild(buttonArea2);
container.appendChild(mainWindow);
window.document.body.appendChild(container);
// 显示逻辑控制
collapseButton.addEventListener('click', () => {
if (collapseButton.innerText === '收起') {
mainWindow.style.display = 'none';
collapseButton.innerText = '展开';
return;
}
if (collapseButton.innerText === '展开') {
mainWindow.style.display = 'block';
collapseButton.innerText = '收起';
return;
}
}, false);
if(isBilibili){
hachihachiButton.addEventListener('click', () => {
hachihachikinou=(hachihachikinou+1)%2;
hachihachiButton.innerText = hachihachitext[hachihachikinou];
}, false);
}
//显示滑动条数字
interval.oninput = function() {
timeLabel.innerText = interval.value;
}
if(isShowroom){
container.style.width = "282px";
timeLabel.style.width = "32px";
}
let roomID;
if(isBilibili){
roomID = /\d+/.exec(location.pathname)[0];
//填充上次所写的打call语句
const tsc = localStorage.getItem('tampermonkey_script_call');
const setting = localStorage.getItem('tampermonkey_script_call_setting');
if(tsc){
const tscJson = JSON.parse(tsc);
textArea.value = tscJson[roomID]||``;
}else{
localStorage.setItem('tampermonkey_script_call',JSON.stringify({}));
}
//填充上次使用的设置
if(setting){
const settingJson = JSON.parse(setting);
if(settingJson[roomID]){
intervaltime = settingJson[roomID][0];
interval.min = 2;
interval.value = intervaltime;
interval.max = intervaltime>30?intervaltime:30;
timeLabel.innerText = intervaltime;
countDownButton.innerText = settingJson[roomID][1];
hachihachikinou = settingJson[roomID][2];
hachihachiButton.innerText = hachihachitext[hachihachikinou];
}
}else{
localStorage.setItem('tampermonkey_script_call_setting',JSON.stringify({}));
}
}
//-------------------gui设计结束------------------
let intervalValBox ;
function createInput(){
intervalLabel.innerText = "";
intervalValBox = document.createElement('input');
intervalValBox.style.width = "100%";
intervalValBox.placeholder = "输入数值";
intervalLabel.appendChild(intervalValBox);
intervalLabel.onclick = null;
}
intervalLabel.onclick =createInput;
function pause(){
clearInterval(t);
clearInterval(ct);
if(isBilibili){
bliveproxy.removeCommandHandler('DANMU_MSG', hdl);
}
goButton.innerText = '开始';
countDownButton.innerText = countdown;
countDownButton.setAttribute("contenteditable", "true");
if(isShowroom&&textArea.value.trim() === ''){
//showroom中如果输入为空,则进行50 count。
interval.min = (intervaltime-2)/2;
interval.value = intervaltime;
timeLabel.innerText = intervaltime;
}
}
function hdl(command) {
const info = command.info;
if(/^8+/.test(info[1])){
hachihachitimes+=1;
}
if(hachihachitimes>=hachihachi){
pause();
hachihachitimes = 0;
}
}
const countdownfunc = function() {
if (countDownButton.innerText > 0) {
countDownButton.innerText -= 1;
} else {
pause();
}
}
goButton.addEventListener('click', () => {
if (goButton.innerText == '暂停') {
pause()
return;
}
if(isBilibili&&hachihachikinou){
bliveproxy.addCommandHandler('DANMU_MSG', hdl);
}
if(intervalValBox){
intervalValBox.remove();
intervalLabel.innerText = "发送间隔:";
intervalLabel.onclick = createInput;
}
const value = textArea.value;
callResult = callDealler(value);
if (value.trim() === '') {
if(!isShowroom){
window.alert('打尻:您还没有输入call语句');
return;
}else{
//设定50 count
const kankaku = 2; //50 count的时间间隔
countDownButton.innerText = kankaku*(50-1)+1;//这个值是我乱猜的
interval.min = kankaku;
interval.value = kankaku;
timeLabel.innerText = kankaku;
callResult = Array.from({length:50}, (v,k) => k+1);
}
}
if(!(countDownButton.innerText>0)){
return
}
if(intervalValBox&&parseFloat(intervalValBox.value) > 0){
timeLabel.innerText = intervalValBox.value;
intervalChoose = intervalValBox.value;
}else{
intervalChoose = interval.value;
}
if(isBilibili){
const tsc_temp = localStorage.getItem('tampermonkey_script_call');
const tscJson_temp = JSON.parse(tsc_temp);
tscJson_temp[roomID] = textArea.value;
localStorage.setItem('tampermonkey_script_call',JSON.stringify(tscJson_temp));
const setting_temp = localStorage.getItem('tampermonkey_script_call_setting');
const settingJson_temp = JSON.parse(setting_temp);
settingJson_temp[roomID] = [intervalChoose,countDownButton.innerText,hachihachikinou];
localStorage.setItem('tampermonkey_script_call_setting',JSON.stringify(settingJson_temp));
}
ct = setInterval(countdownfunc, 1000);
goButton.innerText = '暂停';
countDownButton.setAttribute("contenteditable", "false");
if (!isNaN(parseFloat(countDownButton.innerText))&&!(isShowroom&&textArea.value.trim() === '')) {
countdown = countDownButton.innerText;
}
init();
}, false);
} catch (e) {
times-=1;
if(times==0){
times = timescopy;
clearInterval(maint);
if(window.confirm('打尻:发生未知错误\n可能是在加载中无法获取元素\n' + e+"\n是否重新尝试打尻?")){
maint= setInterval(main, 1000 * waitTime);
}
};
}
}
maint= setInterval(main, 1000 * waitTime);
})();