// ==UserScript==
// @name B站直播间机器人
// @namespace http://tampermonkey.net/
// @version 1.3.3
// @description 【bilibili,机器人,直播】定时发消息、自动回复、指令[#XXX]功能,注意自动回复对自己发的是不生效的~。对话和指令功能需要实现一个本地服务器,脚本向7564端口/_api/chat接口本地服务发送格式为{content: ""}数据,可以使用阿里云智能机器人或者自己实现一个机器人(例如python 的RASA)
// @author 皮燕子
// @match https://live.bilibili.com/*
// @exclude https://live.bilibili.com/p/html/live-web-mng/**
// @icon https://www.bilibili.com/favicon.ico
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_notification
// @grant GM_openInTab
// @grant GM_info
// @connect 127.0.0.1
// @license bonelf.com
// ==/UserScript==
(function () {
// http://libs.baidu.com/jquery/2.1.4/jquery.min.js
'use strict';
// 注意页面有个iframe导致多次执行脚本,所以添加了exclude https://live.bilibili.com/p/html/live-web-mng/index.html?...
// id,title,tips,defaultVal
var menu_ALL = [
['menu_notify', '所有配置项需要刷新页面生效!', '你懂了就好', false],
['menu_intervalMsg_switch', '定时发送消息开关', '定时发送消息开关', true],
['menu_intervalMsg', '定时发送消息', '定时发送消息', {60: ["新来的小伙伴关注点一点~"]}],
['menu_reply_switch', '自动回复开关', '自动回复开关', true],
['menu_reply', '自动回复', '自动回复', [{
regexp: "机器人在吗",
reply: "我随时在哦~",
rate: 1,
timeout: 0
}]], //旧版本 {"机器人在吗": ["我随时在哦~"]} + menu_rate
['menu_ruchang', '入场事件', '舰长入场、粉丝入场、普通用户入场', {
enterReplyNorm: '',
fansMedalLevel: 0,
enterReplyFans: '',
fansMedalContent: '',
enterReplyJianzhang: '',
enterReplyTidu: '',
}],
['menu_at', '显示@用户后缀', '@用户后缀', true],
['menu_short_name', '用户简称', '长B站用户昵称可使用此配置缩短对用户的称呼', {}],
['menu_clear_cache', '重置配置', '如果出现配置不生效或者页面混乱,尝试重置配置', {}],
['menu_enemy', 'AI对话用户[需要服务器]', '英文逗号“,”分割', '']
], menu_ID = [];
const mycss = `<style class="zhihuE_SettingStyle">
.zhihuE_SettingRoot {
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
-moz-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
-o-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
width: auto;
min-width: 400px;
max-width: 1000px;
height: auto;
min-height: 150px;
max-height: 400px;
color: #535353;
background-color: #fff;
border-radius: 3px;
}
.zhihuE_SettingBackdrop_1 {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9999;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
overflow-x: hidden;
overflow-y: auto;
-webkit-transition: opacity .3s ease-out;
transition: opacity .3s ease-out;
}
.zhihuE_SettingBackdrop_2 {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
background-color: rgba(18, 18, 18, .65);
-webkit-transition: background-color .3s ease-out;
transition: background-color .3s ease-out;
}
.zhihuE_SettingRoot .zhihuE_SettingHeader {
padding: 10px 20px;
color: #fff;
font-weight: bold;
background-color: #3994ff;
border-radius: 3px 3px 0 0;
}
.zhihuE_SettingRoot .zhihuE_SettingMain, .button-group {
padding: 10px 20px;
border-radius: 0 0 3px 3px;
}
.zhihuE_SettingHeader span {
float: right;
margin-top: 10px;
cursor: pointer;
}
.bonelf-close {
float: right;
margin-top: 10px;
cursor: pointer;
}
.zhihuE_SettingMain input {
margin: 10px 6px 10px 0;
cursor: pointer;
vertical-align: middle
}
.zhihuE_SettingMain label {
margin-right: 20px;
user-select: none;
cursor: pointer;
vertical-align: middle
}
.zhihuE_SettingMain hr {
border: 0.5px solid #f4f4f4;
}
[data-theme="dark"] .zhihuE_SettingRoot {
color: #adbac7;
background-color: #343A44;
}
[data-theme="dark"] .zhihuE_SettingHeader {
color: #d0d0d0;
background-color: #2D333B;
}
[data-theme="dark"] .zhihuE_SettingMain hr {
border: 0.5px solid #2d333b;
}
.bonelf-close {
display: inline-block;
width: 22px;
height: 4px;
background: white;
transform: rotate(45deg);
}
.bonelf-close::after {
content: '';
display: block;
width: 22px;
height: 4px;
background: white;
transform: rotate(-90deg);
}
.bonelf-finish {
background: white;
}
.bonelf-finish::after {
background: white;
}
.bonelf-delete {
background: black;
margin-top: 23px;
}
.bonelf-delete::after {
background: black;
}
.bonelf-key {
width: 100px;
}
.bonelf-val {
width: 200px;
}
input.bonelf_Setting {
padding: .375rem .75rem;
border-radius: .25rem;
border: 1px solid #ced4da;
}
input.bonelf_Setting:focus {
border-style: solid;
border-color: #03a9f4;
box-shadow: 0 0 5px #03a9f4;
}
input.bonelf_Setting:hover {
cursor: text;
}
.button-group > button {
float: right;
padding: .375rem .75rem;
border-radius: .25rem;
border: 1px solid #ced4da;
margin: 10px;
}
.button-group > button:hover {
border-color: #03a9f4;
}
.button-group > button:active {
background: #03a9f4;
}
</style>`
for (let i = 0; i < menu_ALL.length; i++) {
// 如果读取到的值为 null 就写入默认值
if (GM_getValue(menu_ALL[i][0]) == null) {
GM_setValue(menu_ALL[i][0], menu_ALL[i][3])
}
}
// 初始化注册
registerMenuCommand();
/**
* 注册脚本菜单
* 页面如果执行多次脚本将出现BUG
*/
function registerMenuCommand() {
if (menu_ID.length >= menu_ALL.length) { // 如果菜单ID数组多于菜单数组,说明不是首次添加菜单(有反馈),需要卸载所有脚本菜单
for (let i = 0; i < menu_ID.length; i++) {
GM_unregisterMenuCommand(menu_ID[i]);
}
}
for (let i = 0; i < menu_ALL.length; i++) { // 循环注册脚本菜单
menu_ALL[i][3] = GM_getValue(menu_ALL[i][0]);
if (menu_ALL[i][0] === 'menu_intervalMsg') {
if (menu_value(menu_ALL[i][0])) {
menu_ID[i] = GM_registerMenuCommand(`#️⃣ ${menu_ALL[i][1]}`, function () {
// 用键值对,因为每相同时间建立一个定时器减少开销
customKeyValPrompt(menu_ALL[i],
{type: 'number', placeholder: '定时时间/s(>10s)'},
{type: 'text', placeholder: '文本(<20字符)'}
);
});
}
} else if (menu_ALL[i][0] === 'menu_reply') {
if (menu_value(menu_ALL[i][0])) {
menu_ID[i] = GM_registerMenuCommand(`#️⃣ ${menu_ALL[i][1]}`, function () {
customReplyPrompt(menu_ALL[i]);
});
}
} else if (menu_ALL[i][0] === 'menu_short_name') {
if (menu_value(menu_ALL[i][0])) {
menu_ID[i] = GM_registerMenuCommand(`#️⃣ ${menu_ALL[i][1]}`, function () {
customKeyValPrompt(menu_ALL[i],
{type: 'text', placeholder: '用户昵称'},
{type: 'text', placeholder: '用户简称'}
);
});
}
} else if (menu_ALL[i][0] === 'menu_ruchang') {
menu_ID[i] = GM_registerMenuCommand(`#️⃣ ${menu_ALL[i][1]}`, function () {
customFormGroupPrompt(menu_ALL[i]);
});
} else if (menu_ALL[i][0] === 'menu_clear_cache') {
menu_ID[i] = GM_registerMenuCommand(`❗ ${menu_ALL[i][1]}`, function () {
if(confirm(menu_ALL[i][2])){
for(let each of menu_ALL){
GM_deleteValue(each[0])
}
alert("重置成功")
}
});
} else if (menu_ALL[i][0] === 'menu_enemy' || menu_ALL[i][0] === 'menu_rate') {
menu_ID[i] = GM_registerMenuCommand(`#️⃣ ${menu_ALL[i][1]}`, function () {
customMenuPrompt(menu_ALL[i][0], menu_ALL[i][2]);
});
} else {
menu_ID[i] = GM_registerMenuCommand(`${menu_ALL[i][3] ? '✅' : '❌'} ${menu_ALL[i][1]}`, function () {
menu_switch(`${menu_ALL[i][3]}`, `${menu_ALL[i][0]}`, `${menu_ALL[i][2]}`)
});
}
}
menu_ID[menu_ID.length] = GM_registerMenuCommand('💬 反馈 & 建议', function () {
window.GM_openInTab('https://greasyfork.org/zh-CN/scripts/444721-b%E7%AB%99%E7%9B%B4%E6%92%AD%E9%97%B4%E6%9C%BA%E5%99%A8%E4%BA%BA/feedback', {
active: true,
insert: true,
setParent: true
});
});
}
/**
* 输入框设置
* @param menu
* @param keyMethod
* @param valueMethod
*/
function customFormGroupPrompt(menu) {
let menuCode = menu[0];
let menuName = menu[1];
let pastVal = menu_value(menuCode) || {
enterReplyNorm: '',
fansMedalLevel: 0,
enterReplyFans: '',
fansMedalContent: '',
enterReplyJianzhang: '',
enterReplyTidu: '',
}
let _html = `
${mycss}
<div class="zhihuE_SettingBackdrop_1">
<div class="zhihuE_SettingBackdrop_2"></div>
<div class="zhihuE_SettingRoot">
<div class="zhihuE_SettingHeader">
${menuName}
<span class="bonelf-close bonelf-finish" title="点击关闭"></span>
</div>
<div class="zhihuE_SettingMain">
<div>
普通用户进场:
<input class="bonelf_Setting bonelf-val" name="enterReplyNorm" type="text" value="${pastVal.enterReplyNorm}" />
</div>
<div>
粉丝牌大于
<input class="bonelf_Setting bonelf-key" name="fansMedalLevel" type="number" value="${pastVal.fansMedalLevel}" />
级的用户进场:
<input class="bonelf_Setting bonelf-val" name="enterReplyFans" type="text" value="${pastVal.enterReplyFans}" />
<br/>
(我的粉丝牌:
<input class="bonelf_Setting bonelf-key" name="fansMedalContent" type="text" value="${pastVal.fansMedalContent}" />)
</div>
<div>
舰长进场:
<input class="bonelf_Setting bonelf-val" name="enterReplyJianzhang" type="text" value="${pastVal.enterReplyJianzhang}" />
</div>
<div>
提督进场:
<input class="bonelf_Setting bonelf-val" name="enterReplyTidu" type="text" value="${pastVal.enterReplyTidu}" />
</div>
<div>
tips:可用"{昵称}"表示用户昵称,为空则不会回复,配置将直接生效,发弹幕有冷却时间,请合理控制回复对象~
</div>
</div>
<div class="button-group">
<button class="bonelf-save">保存</button>
</div>
</div>
</div>
`
document.body.insertAdjacentHTML('beforeend', _html);
setTimeout(function () { // 延迟 100 毫秒,避免太快
// 关闭按钮 点击事件
let bonelfFinish = document.querySelector('.bonelf-finish');
if (bonelfFinish) {
bonelfFinish.onclick = function () {
this.parentElement.parentElement.parentElement.remove();
document.querySelector('.zhihuE_SettingStyle').remove();
}
}
// 添加点击事件
// 点击周围空白处 = 点击关闭按钮
let bonelfDrop = document.querySelector('.zhihuE_SettingBackdrop_2');
if (bonelfDrop) {
bonelfDrop.onclick = function (event) {
this.parentElement.remove();
document.querySelector('.zhihuE_SettingStyle').remove();
}
}
// 保存点击事件
let bonelfSave = document.querySelector('.bonelf-save');
if (bonelfSave) {
bonelfSave.onclick = function (event) {
let newVal = {}
let inputs = document.querySelectorAll('input.bonelf_Setting')
for (let i = 0; i < inputs.length; i++) {
if (newVal[inputs[i].getAttribute('name')]) {
newVal[inputs[i].getAttribute('name')].push(inputs[i].value)
} else {
newVal[inputs[i].getAttribute('name')] = inputs[i].value
}
}
console.log(newVal)
GM_setValue(menuCode, newVal);
registerMenuCommand(); // 重新注册脚本菜单
this.parentElement.parentElement.remove();
document.querySelector('.zhihuE_SettingStyle').remove();
}
}
}, 100)
}
/**
* 自动回复配置
* @param menu
*/
function customReplyPrompt(menu) {
let menuCode = menu[0];
let keyMethod = {type: 'text', placeholder: '关键词(支持正则)'},
valueMethod = {type: 'text', placeholder: '回复内容'};
return customInputPrompt(menu,
keyMethod,
valueMethod,
function getItemHtml(itemValue) {
return `<div>
<input class="bonelf_Setting bonelf-key" type="${keyMethod.type}" value="${itemValue.regexp||""}"
placeholder="${keyMethod.placeholder}">
每
<input class="bonelf_Setting bonelf-timeout" min="0" style="width:40px" type="number" value="${itemValue.timeout||0}">
秒且每
<input class="bonelf_Setting bonelf-rate" min="0" style="width:40px" type="number" value="${itemValue.rate||1}">
条回复
<input class="bonelf_Setting bonelf-val" type="${valueMethod.type}" value="${itemValue.reply||""}"
placeholder="${valueMethod.placeholder}">
一次
<span class="bonelf-close bonelf-delete" title="删除此行"></span>
</div>`
}, function (that) {
let keys = document.querySelectorAll('.bonelf-key')
let values = document.querySelectorAll('.bonelf-val')
let timeouts = document.querySelectorAll('.bonelf-timeout')
let rates = document.querySelectorAll('.bonelf-rate')
let newValList = []
for (let i = 0; i < keys.length; i++) {
let newVal = {}
newVal.regexp = keys[i].value;
newVal.reply = values[i].value;
newVal.timeout = timeouts[i].value;
newVal.rate = rates[i].value;
newValList.push(newVal)
}
GM_setValue(menuCode, newValList);
registerMenuCommand(); // 重新注册脚本菜单
that.currentTarget.parentElement.parentElement.remove();
document.querySelector('.zhihuE_SettingStyle').remove();
})
}
/**
* 输入框配置方法
* @param menu
* @param keyMethod
* @param valueMethod
* @param onSave
*/
function customInputPrompt(menu, keyMethod, valueMethod, itemHtml, onSave) {
function addDelEvt() {
let bonelfDel = document.querySelectorAll('.bonelf-delete')
if (bonelfDel.length > 0) {
bonelfDel.forEach(item => {
item.onclick = function (event) {
this.parentElement.remove();
}
})
}
}
let menuCode = menu[0];
let menuName = menu[1];
let pastVal = menu_value(menuCode)
let _html = `
${mycss}
<div class="zhihuE_SettingBackdrop_1">
<div class="zhihuE_SettingBackdrop_2"></div>
<div class="zhihuE_SettingRoot">
<div class="zhihuE_SettingHeader">
${menuName}
<span class="bonelf-close bonelf-finish" title="点击关闭"></span>
</div>
<div class="zhihuE_SettingMain">
`
if (Array.isArray(pastVal)) {
for (let each of pastVal) {
_html += itemHtml(each)
}
} else {
for (let pastValKey in pastVal) {
if (pastVal.hasOwnProperty(pastValKey)) {
if (Array.isArray(pastVal[pastValKey])) {
pastVal[pastValKey].forEach(item => {
_html += itemHtml({key: pastValKey, value: item})
})
} else {
_html += itemHtml({key: pastValKey, value: pastVal[pastValKey]})
}
}
}
}
_html += `
</div>
<div class="button-group">
<button class="bonelf-save">保存</button>
<button class="bonelf-add">新增</button>
</div>
</div>
</div>`
document.body.insertAdjacentHTML('beforeend', _html); // 插入网页末尾
setTimeout(function () { // 延迟 100 毫秒,避免太快
// 关闭按钮 点击事件
let bonelfFinish = document.querySelector('.bonelf-finish');
if (bonelfFinish) {
bonelfFinish.onclick = function () {
this.parentElement.parentElement.parentElement.remove();
document.querySelector('.zhihuE_SettingStyle').remove();
}
}
// 添加点击事件
// 点击周围空白处 = 点击关闭按钮
let bonelfDrop = document.querySelector('.zhihuE_SettingBackdrop_2');
if (bonelfDrop) {
bonelfDrop.onclick = function (event) {
this.parentElement.remove();
document.querySelector('.zhihuE_SettingStyle').remove();
}
}
// 点击删除按钮
addDelEvt()
// 添加点击事件
let bonelfAdd = document.querySelector('.bonelf-add');
if (bonelfAdd) {
bonelfAdd.onclick = function (event) {
document.querySelector('.zhihuE_SettingMain')
.insertAdjacentHTML('beforeend', itemHtml({key:"",value:""})); // 插入网页末尾
addDelEvt()
}
}
// 添加点击事件
let bonelfSave = document.querySelector('.bonelf-save');
if (bonelfSave) {
bonelfSave.onclick = onSave
}
}, 100)
}
/**
* 双输入框配置方法
* @param menu
* @param keyMethod
* @param valueMethod
* @param onSave
*/
function custom2InputPrompt(menu, keyMethod, valueMethod, onSave) {
return customInputPrompt(menu, keyMethod, valueMethod,
function getItemHtml(itemValue) {
return `<div>
<label>
<input class="bonelf_Setting bonelf-key" type="${keyMethod.type}" value="${itemValue.key}"
placeholder="${keyMethod.placeholder}">
</label>
<label>
<input class="bonelf_Setting bonelf-val" type="${valueMethod.type}" value="${itemValue.value}"
placeholder="${valueMethod.placeholder}">
</label>
<span class="bonelf-close bonelf-delete" title="删除此行"></span>
</div>`
}, onSave)
}
/**
* 双输入列表存储配置方法
* @param menu
* @param keyMethod
* @param valueMethod
* @param onSave
*/
function customListPrompt(menu, keyMethod, valueMethod) {
let menuCode = menu[0];
return custom2InputPrompt(menu, keyMethod, valueMethod, function (event) {
let keys = document.querySelectorAll('.bonelf-key')
let values = document.querySelectorAll('.bonelf-val')
let newValList = []
for (let i = 0; i < keys.length; i++) {
let newVal = {}
newVal[keyMethod.key] = keys[i].value;
newVal[valueMethod.key] = values[i].value;
newValList.push(newVal)
}
GM_setValue(menuCode, newValList);
registerMenuCommand(); // 重新注册脚本菜单
this.parentElement.parentElement.remove();
document.querySelector('.zhihuE_SettingStyle').remove();
})
}
/**
* 双输入列表键值对存储配置方法
* @param menu
* @param keyMethod
* @param valueMethod
*/
function customKeyValPrompt(menu, keyMethod, valueMethod) {
let menuCode = menu[0];
return custom2InputPrompt(menu, keyMethod, valueMethod, function (event) {
let keys = document.querySelectorAll('.bonelf-key')
let values = document.querySelectorAll('.bonelf-val')
let newVal = {}
for (let i = 0; i < keys.length; i++) {
if (newVal[keys[i].value]) {
newVal[keys[i].value].push(values[i].value)
} else {
newVal[keys[i].value] = [values[i].value]
}
}
if (newVal !== null) {
GM_setValue(menuCode, newVal);
registerMenuCommand(); // 重新注册脚本菜单
}
this.parentElement.parentElement.remove();
document.querySelector('.zhihuE_SettingStyle').remove();
})
}
/**
* 简单弹出框类型配置设置
* @param menuName
* @param tips
*/
function customMenuPrompt(menuName, tips) {
let nowBlockKeywords = menu_value(menuName) || ''
let newBlockKeywords = prompt(tips ? tips : '编辑', nowBlockKeywords);
if (newBlockKeywords != null) {
GM_setValue(menuName, newBlockKeywords);
registerMenuCommand(); // 重新注册脚本菜单
}
}
/**
* 开关类型配置设置
* @param menuStatus
* @param name
* @param tips
*/
function menu_switch(menuStatus, name, tips) {
if (menuStatus == 'true') {
GM_setValue(`${name}`, false);
GM_notification({
text: `已关闭 [${tips}] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function () {
location.reload();
}
});
} else {
GM_setValue(`${name}`, true);
GM_notification({
text: `已开启 [${tips}] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function () {
location.reload();
}
});
}
registerMenuCommand(); // 重新注册脚本菜单
}
/**
* 返回菜单值
*/
function menu_value(menuName) {
for (let menu of menu_ALL) {
if (menu[0] == menuName) {
return menu[3]
}
}
}
//---------------------------------------------------------------------
// 兼容性代码
//---------------------------------------------------------------------
// @since 1.2.4 menu_reply 存储格式变更
let menuReply = menu_value("menu_reply_switch") ? (menu_value("menu_reply") || []) : []
if (!Array.isArray(menuReply)) {
let menuRate = menu_value("menu_rate") || 1
let newValList = []
for (let menuReplyKey in menuReply) {
if (menuReply.hasOwnProperty(menuReplyKey)) {
for (let menuReplyElementKey of menuReply[menuReplyKey]) {
let newVal = {
timeout: 0
};
newVal.rate = menuRate;
newVal.regexp = menuReplyKey;
newVal.reply = menuReplyElementKey;
newValList.push(newVal);
}
}
}
GM_setValue("menu_reply", newValList);
alert("机器人脚本有数据更新,请刷新下页面");
}
//---------------------------------------------------------------------
// 兼容性代码 END
//---------------------------------------------------------------------
window.onload = function () {
// 消息队列
// var msgQueue = ["您可爱的机器人来啦~(●'◡'●)"]
var msgQueue = []
// 消息队列冷却 s
var msgQueueCooldown = 3;
// readArr.forEach(item=>{msgQueue.push(item)})
// 主播名称
// var streamerDom = document.getElementsByClassName("room-owner-username")[0].getAttribute('title') || "Unknown";
let streamerElem = document.querySelector('.header-info-ctnr');
let streamerAreaVue = streamerElem ? streamerElem.__vue__ : { };
var streamerName = streamerAreaVue.anchorUsername || "Unknown"
var liveAreaName = streamerAreaVue.liveAreaName || "Unknown"
// 登录用户名称
// var customerName = document.getElementsByClassName("username-info")[0].firstChild.getAttribute('title') || "Unknown"
var customerUid;
findVueElem('.user-panel-ctnr', (elem) => {
customerUid = Number(elem.__vue__.userData.uid) || 0
})
// 正则回复JSON
// var replyJson = {
// "主播你是做什么工作的": "?",
// "机器人?": "我是一个机器人,请多包容(●'◡'●)",
// }
var replyJson = []
var menuAt = false
var shortNameMap = {}
// 定时消息
var intervalMsg = []
/**
* 刷新配置
*/
function initConfig() {
// 定时消息
var intervalMsgData = menu_value("menu_intervalMsg_switch") ? (menu_value("menu_intervalMsg") || {}) : {}
for (let intervalMsgDataKey in intervalMsgData) {
if (intervalMsgData.hasOwnProperty(intervalMsgDataKey)) {
intervalMsg.push({
interval: Math.max(Number(intervalMsgDataKey), 10), msgArr: intervalMsgData[intervalMsgDataKey]
})
}
}
// 正则回复
// regexp: "机器人在吗",
// reply: "我随时在哦~",
// rate: 1,
// timeout: 0
replyJson = menu_value("menu_reply") || []
menuAt = menu_value("menu_at") || false
let shortNameMapData = menu_value("menu_short_name") || {}
for (let shortNameMapDataKey in shortNameMapData) {
if (shortNameMapData.hasOwnProperty(shortNameMapDataKey) &&
Array.isArray(shortNameMapData[shortNameMapDataKey]) &&
shortNameMapData[shortNameMapDataKey].length > 0) {
shortNameMap[shortNameMapDataKey] = shortNameMapData[shortNameMapDataKey][0]
}
}
}
initConfig()
console.log("当前生效自动回复", replyJson)
console.log("当前生效定时消息", intervalMsg)
console.log("当前生效用户简称", shortNameMap)
// var intervalMsg = [
// {interval: 60, msgArr: ["新来的小伙伴关注点一点~"]}
// ]
/**
* AI 对话
* @param param
*/
function aiReply(param) {
// AI 对话
if (param.onlineCount && param.onlineCount > 100) {
console.log("人数过多,AI对话禁用")
}
GM_xmlhttpRequest({
method: "post",
url: 'http://127.0.0.1:7564/_api/chat',
data: JSON.stringify({content: param.text}),
headers: {
"Content-Type": "application/json"
},
onload: function (res) {
let data
try {
data = JSON.parse(res.response);
} catch (e) {
}
if (data) {
let ctt = data.data + (param.uname && menuAt ? ("@" + param.uname) : "");
console.log("回复内容:" + ctt)
msgQueue.push(ctt)
}
},
onerror: function (err) {
console.error(err)
}
});
}
// 指令列表
var orderJson = {
"#测试": function (param = '') {
// sendStr("测试正常:" + param)
msgQueue.push("测试正常:" + param.text)
},
"#对话 ": function (param) {
aiReply(param);
}
// 其他指令过后台
// "#刷新配置": function (param = '') {
// initConfig()
// // sendStr("刷新成功")
// msgQueue.push("刷新成功")
// }
}
console.log("当前生效指令", orderJson)
// 弹幕输入框model所在元素
var danmakuElem;
/**
* 获取元素
* @param selector
* @param callback
*/
function findVueElem(selector, callback) {
// 弹幕输入框model所在元素获取任务定时器
var times = 0;
var findElemTimer = setInterval(() => {
var elem = document.querySelector(selector);
if (times > 20 || (elem && elem.__vue__)) {
callback(elem);
clearInterval(findElemTimer)
} else {
console.warn("获取弹幕元素失败")
}
times++
}, 1000)
}
findVueElem("#control-panel-ctnr-box", (res) => {
danmakuElem = res;
});
/**
* 发送数组消息
* @param arr
* @param finish 消息发送完即关闭定时器
*/
function sendArr(arr, finish = true) {
var times = 0;
var timer = setInterval(() => {
var elem = danmakuElem;
if (elem && elem.__vue__) {
let data = elem.__vue__.$data;
if (data && arr.length > 0) {
data.chatInput = arr.shift().substring(0, 20)
elem.__vue__.sendDanmaku()
}
if (arr.length === 0 && finish) {
clearInterval(timer)
}
} else if (times < 20) {
console.warn("danmakuElem 为空")
} else {
console.warn("消息队列因获取不到弹幕输入框model终止")
clearInterval(timer)
}
times++
}, msgQueueCooldown * 1000)
}
/**
* 对此方法调用可能会引起弹幕输入过快,
* 更推荐向消息队列+数据
* @param str
*/
function sendStr(str) {
var elem = danmakuElem;
if (elem && elem.__vue__) {
let data = elem.__vue__.$data;
if (!data) {
alert("发送失败")
}
data.chatInput = str.substring(0, 20)
elem.__vue__.sendDanmaku()
}
}
// 消息队列数据发送
sendArr(msgQueue, false)
// 定时发送消息
intervalMsg.forEach(item => {
item.timer = setInterval(() => {
item.msgArr.forEach(i => {
msgQueue.push(i)
})
}, item.interval * 1000)
})
// 处理到的最近的弹幕
var danmakuPointer
/**
* 初始化最近一条弹幕的DOM
*/
function initRecentDom() {
let allDanmakuElem = document.querySelectorAll("div.danmaku-item[data-uname]")
if (allDanmakuElem.length > 0) {
danmakuPointer = allDanmakuElem[allDanmakuElem.length - 1]
}
}
var countMap = {};
// 消息回复
var timer = setInterval(() => {
if (!danmakuPointer) {
initRecentDom();
}
// 以后出现弹幕过多时页面卡死则考虑加这个代码,但是要考虑高能预警情况 可能卫视 ###.##万 这种带单位的格式
let headerInfoCtnrElem = document.querySelector('.header-info-ctnr');
let onlineCount = headerInfoCtnrElem ? (headerInfoCtnrElem.__vue__.onlineCount || 0) : 0;
// if (onlineCount > 10000) {
// console.warn("本直播间人数超过10000,出于页面安全考虑禁用了自动回复功能")
// }
while (danmakuPointer && danmakuPointer.nextSibling != null) {
danmakuPointer = danmakuPointer.nextSibling
if (!danmakuPointer.classList.contains('danmaku-item')) {
continue;
}
let uid = Number(danmakuPointer.getAttribute('data-uid'))
let uname = danmakuPointer.getAttribute('data-uname')
// 跳过对自己的回复 uname === streamerName || 或者主播添加这个,其实能获取到用户id来判断
let text = danmakuPointer.getAttribute('data-danmaku')
// console.debug("uid:" + uid);
// console.debug("customerUid:" + customerUid);
if (uid !== customerUid) {
// 自动回复
for (let each of replyJson) {
let re = new RegExp(each.regexp);
if (text.match(re)) {
let now = new Date();
if (countMap[each.regexp] === undefined) {
countMap[each.regexp] = {count: 0, lastTime: undefined}
}
// sendStr(replyJson[each] + menuAt ? "@" + uname : "")
if (countMap[each.regexp].count % Number(each.rate) === 0 &&
// 时间未到
(countMap[each.regexp].lastTime === undefined ||
(now.getTime() - countMap[each.regexp].lastTime.getTime()) / 1000 > Number(each.timeout))) {
console.log("自动回复->" + each.regexp + ":" + each.reply)
msgQueue.push(each.reply + (menu_value("menu_at") ? ("@" + (shortNameMap[uname] || uname)) : ""))
countMap[each.regexp].lastTime = now;
}
countMap[each.regexp].count = countMap[each.regexp].count + 1;
break;
}
}
}
// 指令应答
if (text.startsWith("#")) {
let match = false;
for (let each in orderJson) {
let re = new RegExp(each);
let result = text.match(re);
if (result && result.length > 0) {
match = true;
console.log("指令应答:" + each)
let param = {
text: text.replace(result[0], ''),
onlineCount: onlineCount,
uname: uname
}
orderJson[each](param)
break;
}
}
if (!match) {
// 请求后台数据
GM_xmlhttpRequest({
method: "post",
url: 'http://127.0.0.1:7564/_api/order',
data: JSON.stringify({
content: text,
streamerName: streamerName,
customerName: customerUid,
channel: liveAreaName,
uname: uname,
}),
headers: {
"Content-Type": "application/json"
},
onload: function (res) {
let data
try {
data = JSON.parse(res.response);
} catch (e) {
}
if (data && data.data) {
let ctt = data.data.content + (uname && menuAt ? ("@" + uname) : "");
console.log("回复内容:" + ctt)
msgQueue.push(ctt)
}
},
onerror: function (err) {
console.error(err)
}
});
}
}
// 对话用户
let enemyStr = menu_value("menu_enemy");
if (enemyStr) {
for (let name of enemyStr.split(",")) {
if (uid === name && uid !== customerUid) {
aiReply({text: text})
}
}
}
}
}, 5000)
/**
* 普通用户进入直播间
*/
document.querySelector('#brush-prompt').addEventListener('DOMNodeInserted', function (e) {
// fans-medal-content 粉丝牌
let $fansMedalContent = document.querySelector('#brush-prompt .fans-medal-content')
let fansMedalContent = $fansMedalContent ? $fansMedalContent.innerHTML : null
// fans-medal-level 粉丝等级
let $fansMedalLevel = document.querySelector('#brush-prompt .fans-medal-level')
let fansMedalLevel = $fansMedalLevel ? Number($fansMedalLevel.innerHTML) : 0
// interact-name 用户昵称
let $interactName = document.querySelector('#brush-prompt .interact-name')
let interactName = $interactName ? $interactName.innerHTML : null
interactName = shortNameMap[interactName] || interactName;
// console.log('普通用户进入直播间', interactName);
let ruchangSetting = menu_value("menu_ruchang") || {};
let myFansMedalContent = ruchangSetting.fansMedalContent || null;
let myFansMedalLevelStr = ruchangSetting.fansMedalLevel;
// 普通用户
let normUserReply = ruchangSetting.enterReplyNorm;
// 粉丝
let fansUserReply = ruchangSetting.enterReplyFans;
// 达到要求的粉丝级别
let myFansMedalLevel = myFansMedalLevelStr ? Number(myFansMedalLevelStr) : 0;
if (fansUserReply && myFansMedalContent === fansMedalContent && myFansMedalLevel <= fansMedalLevel) {
// console.log(fansUserReply
// .replace("{昵称}", interactName))
msgQueue.push(fansUserReply
.replace("{昵称}", interactName))
}
// console.log("欢迎用户", normUserReply, interactName)
if (normUserReply) {
// console.log(normUserReply.replace("{昵称}", interactName))
msgQueue.push(normUserReply.replace("{昵称}", interactName))
}
});
/**
* 下面事件会触发两次,所以以此字段判断第二次不执行
*/
var lastWelcomeText;
/**
* 特殊用户进入直播间
*/
document.querySelector('#welcome-area-bottom-vm').addEventListener('DOMNodeInserted', function (e) {
let $samaNameBox = document.querySelector('#welcome-area-bottom-vm .sama-name-box')
let welcomeText = $samaNameBox ? $samaNameBox.textContent : ""
// console.log("特殊用户进入直播间事件触发", welcomeText)
if (lastWelcomeText === welcomeText) {
return;
}
function send(type, reply) {
let $interactName = document.querySelector('#welcome-area-bottom-vm .sama-name-box span')
let interactName = $interactName ? $interactName.innerHTML : null
interactName = shortNameMap[interactName] || interactName;
// console.log("欢迎" + type, jianzhangReply, interactName)
if (reply) {
//console.log(reply
// .replace("{昵称}", interactName))
msgQueue.push(reply
.replace("{昵称}", interactName))
}
}
let ruchangSetting = menu_value("menu_ruchang") || {};
if (welcomeText.startsWith("欢迎舰长 ")) {
send('舰长', ruchangSetting.enterReplyJianzhang);
} else if (welcomeText.startsWith("欢迎提督 ")) {
send('提督', ruchangSetting.enterReplyTidu);
} else {
// 还有一个没身份的特效,应该是高能用户?
send('~', ruchangSetting.enterReplyNorm);
}
lastWelcomeText = welcomeText;
});
/**
* 清除已定义的定时器
*/
function stopAllInterval() {
// 定时消息
intervalMsg.forEach(item => {
if (item.timer) {
clearInterval(item.timer)
}
delete item.timer;
})
clearInterval(timer)
}
function keyDown(e) {
if (e.which === 27) { //ESC
e.returnValue = false;
console.log("ESC")
stopAllInterval()
return false;
}
}
document.onkeydown = keyDown;
}
})();