// ==UserScript==
// @name B站直播间挂机助手
// @name:zh B站直播间挂机助手
// @name:en Bilibili Live Helper
// @namespace https://github.com/andywang425
// @author andywang425
// @description 优化直播观看体验
// @description:en Improve live viewing experience
// @homepageURL https://github.com/andywang425/BLTH
// @supportURL https://github.com/andywang425/BLTH/issues
// @icon https://z4a.net/images/2022/09/15/script-icon.png
// @copyright 2021, andywang425 (https://github.com/andywang425)
// @license MIT
// @compatible chrome 80 or later
// @compatible firefox 77 or later
// @compatible opera 69 or later
// @compatible safari 13.1 or later
// @version 6.1.5
// @match *://live.bilibili.com/*
// @exclude *://live.bilibili.com/?*
// @run-at document-start
// @connect passport.bilibili.com
// @connect api.live.bilibili.com
// @connect api.bilibili.com
// @connect api.vc.bilibili.com
// @connect live-trace.bilibili.com
// @require https://gcore.jsdelivr.net/gh/andywang425/BLTH@bca9261faa84ffd8f804c85c1a5153d3aa27a9a3/assets/js/library/Ajax-hook.min.js
// @require https://gcore.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require https://gcore.jsdelivr.net/gh/andywang425/BLTH@4dbe95160c430bc64757580f07489bb11e766fcb/assets/js/library/bliveproxy.min.js
// @require https://gcore.jsdelivr.net/gh/andywang425/BLTH@8ac823ac669ad87fb39a63b6bfe02c01c0c30f89/assets/js/library/libWbiSign.min.js
// @require https://gcore.jsdelivr.net/gh/andywang425/BLTH@8ac823ac669ad87fb39a63b6bfe02c01c0c30f89/assets/js/library/BilibiliAPI_Mod.min.js
// @require https://gcore.jsdelivr.net/gh/andywang425/BLTH@4368883c643af57c07117e43785cd28adcb0cb3e/assets/js/library/layer.min.js
// @require https://gcore.jsdelivr.net/gh/andywang425/BLTH@f9fc6466ae78ead12ddcd2909e53fcdcc7528f78/assets/js/library/Emitter.min.js
// @require https://gcore.jsdelivr.net/npm/[email protected]/dist/hotkeys.min.js
// @require https://gcore.jsdelivr.net/npm/[email protected]/crypto-js.min.js
// @require https://gcore.jsdelivr.net/gh/andywang425/BLTH@c117d15784f92f478196de0129c8e5653a9cb32e/assets/js/library/BiliveHeart.min.js
// @require https://gcore.jsdelivr.net/gh/andywang425/BLTH@4c2e8bc541656a8ea6d62d6055e8fd149caa4210/assets/js/library/libBilibiliToken.min.js
// @resource layerCss https://gcore.jsdelivr.net/gh/andywang425/BLTH@d25aa353c8c5b2d73d2217b1b43433a80100c61e/assets/css/layer.css
// @resource myCss https://gcore.jsdelivr.net/gh/andywang425/BLTH@5bcc31da7fb98eeae8443ff7aec06e882b9391a8/assets/css/myCss.min.css
// @resource main https://gcore.jsdelivr.net/gh/andywang425/BLTH@16ad988dce34491d8479416911a2ac4691df45c3/assets/html/main.min.html
// @resource eula https://gcore.jsdelivr.net/gh/andywang425/BLTH@da3d8ce68cde57f3752fbf6cf071763c34341640/assets/html/eula.min.html
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @grant GM_getResourceText
// @grant GM_notification
// @grant GM_openInTab
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_addStyle
// ==/UserScript==
(function () {
const NAME = 'BLTH',
W = typeof unsafeWindow === 'undefined' ? window : unsafeWindow,
eventListener = W.addEventListener,
wfetch = W.fetch,
_setTimeout = W.setTimeout,
_setInterval = W.setInterval,
ts_ms = () => Date.now(), // 当前毫秒
ts_s = () => Math.round(ts_ms() / 1000), // 当前秒
tz_offset = new Date().getTimezoneOffset() + 480, // 本地时间与东八区差的分钟数
appToken = new BilibiliToken(),
setToken = async (refresh = false) => {
if (!refresh && tokenData.hasOwnProperty(Live_info.uid) && tokenData[Live_info.uid]['expires_at'] > ts_s()) {
userToken = tokenData[Live_info.uid];
} else {
tokenData[Live_info.uid] = await appToken.getToken();
if (tokenData[Live_info.uid] === undefined) return MYERROR('appToken', 'tokenData获取失败');
tokenData[Live_info.uid].expires_at = ts_s() + tokenData[Live_info.uid].expires_in;
GM_setValue(`appToken`, tokenData);
userToken = tokenData[Live_info.uid];
}
MYDEBUG(`[setToken] appToken`, tokenData);
return 'OK';
},
getCHSdate = () => {
// 返回东八区 Date
return new Date(ts_ms() + tz_offset * 60000);
},
delayCall = (callback, delay = 120e3) => {
const p = $.Deferred();
setTimeout(() => {
const t = callback();
if (t && t.then) t.then((arg1, arg2, arg3, arg4, arg5, arg6) => p.resolve(arg1, arg2, arg3, arg4, arg5, arg6));
else p.resolve();
}, delay);
return p;
},
MYDEBUG = (sign, ...data) => {
if (!SP_CONFIG.debugSwitch) return;
if (typeof data[0] === 'object' && data[0].netError) return MYERROR(sign, ...data);
let d = new Date();
d = `%c[${NAME}]%c[${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}:${d.getMilliseconds()}]%c`;
if (data.length === 1) return console.log(d, 'font-weight: bold;', 'color: #0920e6;', '', `${sign}:`, data[0]);
console.log(d, 'font-weight: bold;', 'color: #0920e6;', '', `${sign}:`, data);
},
MYERROR = (sign, ...data) => {
let d = new Date();
d = `[${NAME}][${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}:${d.getMilliseconds()}]`;
if (data.length === 1) return console.error(d, `${sign}:`, data[0]);
console.error(d, `${sign}:`, data);
},
runMidnight = (callback, msg) => {
// 明天凌晨0点1分再次运行
const t = new Date();
let name = msg || ' ';
t.setMinutes(t.getMinutes() + tz_offset);
t.setDate(t.getDate() + 1);
t.setHours(0, 1, 0, 0);
t.setMinutes(t.getMinutes() - tz_offset);
setTimeout(callback, t - ts_ms());
MYDEBUG('runMidnight', name + ' ' + t.toString());
},
runExactMidnight = (callback, msg) => {
// 明天凌晨0点再次运行
const t = new Date();
let name = msg || ' ';
t.setMinutes(t.getMinutes() + tz_offset);
t.setDate(t.getDate() + 1);
t.setHours(0, 0, 0, 0);
t.setMinutes(t.getMinutes() - tz_offset);
setTimeout(callback, t - ts_ms());
MYDEBUG('runExactMidnight', name + ' ' + t.toString());
},
runTomorrow = (callback, hour, minute, msg) => {
// 明天运行,可自定义时间
const t = new Date();
let name = msg || ' ';
t.setMinutes(t.getMinutes() + tz_offset);
t.setDate(t.getDate() + 1);
t.setHours(hour, minute, 0, 0);
t.setMinutes(t.getMinutes() - tz_offset);
setTimeout(callback, t - ts_ms());
MYDEBUG('runTomorrow', name + ' ' + t.toString());
},
runToday = (callback, hour, minute, msg) => {
// 今天运行,可自定义时间
const t = new Date();
let name = msg || ' ';
t.setMinutes(t.getMinutes() + tz_offset);
t.setHours(hour, minute, 0, 0);
t.setMinutes(t.getMinutes() - tz_offset);
setTimeout(callback, t - ts_ms());
MYDEBUG('runToday', name + ' ' + t.toString());
},
newWindow = {
init: () => {
return newWindow.Toast.init();
},
Toast: {
// 设置右上角浮动提示框 Need Init
init: () => {
try {
const list = [];
window.toast = (msg, type = 'info', timeout = 5e3) => {
switch (type) {
case 'success':
case 'info':
case 'caution':
case 'error':
break;
default:
type = 'info';
}
const a = $(`<div class="link-toast ${type} fixed" style="z-index:2001"><span class="toast-text">${msg}</span></div>`)[0];
document.body.appendChild(a);
MYDEBUG('toast-' + type, msg);
a.style.top = document.body.scrollTop + list.length * 40 + 10 + 'px';
a.style.left = document.body.offsetWidth + document.body.scrollLeft - a.offsetWidth - 5 + 'px';
if (!SP_CONFIG.windowToast) $('.link-toast').hide();
list.push(a);
setTimeout(() => {
a.className += ' out';
setTimeout(() => {
list.shift();
list.forEach((v) => {
v.style.top = parseInt(v.style.top, 10) - 40 + 'px';
});
$(a).remove();
}, 200);
}, timeout);
};
window.singleToast = (msg, type = 'info', timeout = 5e3, top, left) => {
switch (type) {
case 'success':
case 'info':
case 'caution':
case 'error':
break;
default:
type = 'info';
}
const a = $(`<div class="link-toast ${type} fixed" style="z-index:2001"><span class="toast-text">${msg}</span></div>`)[0];
document.body.appendChild(a);
MYDEBUG('singleToast-' + type, msg);
a.style.top = top;
a.style.left = left;
setTimeout(() => {
a.className += ' out';
setTimeout(() => {
$(a).remove();
}, 200);
}, timeout);
};
return $.Deferred().resolve();
} catch (err) {
MYERROR(`初始化浮动提示时出现异常`, err);
return $.Deferred().reject();
}
},
},
},
addStyle = () => {
const layerCss = GM_getResourceText('layerCss');
const myCss = GM_getResourceText('myCss');
const allCss = layerCss + myCss;
return GM_addStyle(allCss);
},
getScrollPosition = (el = window) => ({
x: el.scrollX !== undefined ? el.scrollX : el.scrollLeft,
y: el.scrollY !== undefined ? el.scrollY : el.scrollTop,
}),
linkMsg = (link, msg = link) => '<a href="' + link + '"target="_blank" style="color:">' + msg + '</a>',
autoMaxQuality = () => {
let autoMaxQualityTimer = setInterval(() => {
try {
let videoDom = document.querySelector("#live-player");
videoDom.dispatchEvent(new Event("mousemove"));
let quality = document.querySelector(".quality-wrap");
quality.dispatchEvent(new Event("mouseenter"));
let qualityItems = document.querySelectorAll(".quality-it");
let originalQualityItem = Array.from(qualityItems).find((item) => item.innerText === "原画");
if (originalQualityItem) {
originalQualityItem.click();
quality.dispatchEvent(new Event("mouseleave"));
clearInterval(autoMaxQualityTimer);
}
} catch { }
}, 500);
};
SP_CONFIG_DEFAULT = {
showEula: true, // 显示EULA
storageLastFixVersion: '0', // 上次修复设置的版本
mainDisplay: 'show', // UI隐藏开关
darkMode: false, // 深色模式
debugSwitch: false, // 控制台日志开关
windowToast: true, // 右上提示信息
nosleep: true, // 屏蔽挂机检测
invisibleEnter: false, // 隐身入场
banP2p: false, // 禁止p2p上传
lastShowUpdateMsgVersion: '0', // 上次显示更新信息的版本
DANMU_MODIFY: false, // 修改弹幕
AUTO_CHECK_DANMU: false, // 检查弹幕是否发送成功
blockLiveStream: false, // 拦截直播流
blockliveDataUpdate: false, // 拦截直播观看数据上报
wear_medal_before_danmu: false, // 手动发弹幕前自动佩戴当前房间勋章
wear_medal_type: 'ONLY_FIRST', // 自动佩戴勋章方式
add_like_button: true, // 添加一个点赞按钮
auto_max_quality: false, // 自动最高清晰度
};
let otherScriptsRunningCheck = $.Deferred(),
otherScriptsRunning = false,
SP_CONFIG = GM_getValue('SP_CONFIG') || {},
SEND_GIFT_NOW = false, // 立刻送出礼物
SEND_DANMU_NOW = false, // 立刻发弹幕
hideBtnClickable = false, // 隐藏/显示控制面板,面板加载后设为 true
hasWornMedal = false,
danmuEmitter = new Emitter(),
Live_info = {
room_id: undefined,
short_room_id: undefined,
uid: undefined,
ruid: undefined,
gift_list: [
{ id: 6, price: 1e3 },
{ id: 1, price: 100 },
],
rnd: undefined,
visit_id: undefined,
bili_jct: undefined,
tid: undefined,
uname: undefined,
user_level: undefined, // 直播等级
// level: undefined, // 主站等级
danmu_length: undefined, // 直播弹幕长度限制
medal: undefined, // 当前直播间勋章的 target_id
vipStatus: undefined, // 大会员状态 (0:无, 1:有)
},
tokenData = GM_getValue('appToken', {}), // 所有用户的token
userToken = {}, // 当前用户的token
medal_info = { status: $.Deferred(), medal_list: [] },
mainIndex = undefined,
logIndex = undefined,
layerUiMain = undefined, // 控制面板
layerLogWindow = undefined, // 日志窗口
logDiv = undefined,
tabContent = undefined,
JQmenuWindow = undefined,
layerLogWindow_Height = undefined,
layerLogWindow_ScrollHeight = undefined,
layerLogWindow_ScrollTop = undefined,
layerLogWindow_ScrollY = undefined,
readConfigArray = [undefined],
noticeJson = GM_getValue('noticeJson') || {}; // 检查更新时获取的json
/**
* 替换字符串中所有的匹配项(可处理特殊字符如括号)
* @param oldSubStr 搜索的字符串
* @param newSubStr 替换内容
*/
String.prototype.replaceall = function (oldSubStr, newSubStr) {
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& 代表所有被匹配的字符串
}
return this.replace(new RegExp(escapeRegExp(oldSubStr), 'g'), () => newSubStr);
};
// 初始化特殊设置
mergeObject(SP_CONFIG, SP_CONFIG_DEFAULT);
// 拦截直播流/数据上报,需要尽早
if (SP_CONFIG.blockLiveStream || SP_CONFIG.blockliveDataUpdate || SP_CONFIG.wear_medal_before_danmu || SP_CONFIG.AUTO_CHECK_DANMU) {
W.fetch = (...arg) => {
if (SP_CONFIG.blockLiveStream && arg[0].includes('bilivideo')) {
return new Promise((resolve, reject) => { });
} else if (SP_CONFIG.blockliveDataUpdate && arg[0].includes('data.bilibili.com/gol/postweb')) {
return {};
} else if (arg[0].includes('//api.live.bilibili.com/msg/send')) {
if (SP_CONFIG.AUTO_CHECK_DANMU) {
danmuEmitter.emit('danmu', arg[1].data.msg);
}
if (SP_CONFIG.wear_medal_before_danmu) {
if (medal_info.status.state() !== 'resolved' || Live_info.medal === null || (SP_CONFIG.wear_medal_type === 'ONLY_FIRST' && hasWornMedal)) return wfetch(...arg);
if (typeof Live_info.medal === 'undefined') {
for (const m of medal_info.medal_list) {
if (m.roomid === Live_info.short_room_id) {
Live_info.medal = m;
break;
}
}
}
if (typeof Live_info.medal === 'undefined') {
Live_info.medal = null; // 没有该勋章,之后无需再检查
return wfetch(...arg);
}
return BAPI.xlive.wearMedal(Live_info.medal.medal_id).then((response) => {
MYDEBUG('API.xlive.wearMedal', response);
if (response.code === 0) {
hasWornMedal = true;
try {
let medalJqItem = $('.dp-i-block.medal-item-margin');
if (medalJqItem === null) return;
let border = medalJqItem.find('.v-middle.fans-medal-item');
const medalColor = '#' + Live_info.medal.medal_color_start.toString(16);
const medalLevel = Live_info.medal.medal_level;
const medalText = Live_info.medal.medal_name;
if (border.length !== 0) {
// 之前戴着勋章
let background = border.find('.fans-medal-label');
let level = border.find('.fans-medal-level');
let text = background.find('.fans-medal-content');
border.css('border-color', medalColor);
background.css('background-image', `linear-gradient(45deg, ${medalColor}, ${medalColor})`);
level.text(medalLevel);
text.text(medalText);
} else {
// 如果没戴勋章则需插入缺失的 html
$('.action-item.medal.wear-medal').remove(); // 移除提示水印
medalJqItem.html(`<div data-v-2c4630d2="" data-v-34b5b0e1="" class="v-middle fans-medal-item" style="border-color: ${medalColor}">
<div data-v-2c4630d2="" class="fans-medal-label" style="background-image: linear-gradient(45deg, ${medalColor}, ${medalColor});">
<span data-v-2c4630d2="" class="fans-medal-content">${medalText}</span>
</div>
<div data-v-2c4630d2="" class="fans-medal-level" style="color: ${medalColor};">${medalLevel}</div>
</div>`);
}
} catch (e) {
MYERROR('修改弹幕框左侧粉丝牌样式出错', e);
}
} else {
window.toast(`自动佩戴粉丝勋章出错 ${response.message}`, 'error');
}
return wfetch(...arg);
});
}
return wfetch(...arg);
} else {
return wfetch(...arg);
}
};
}
// 隐身入场,拦截观看数据上报,需要尽早
if (SP_CONFIG.invisibleEnter || SP_CONFIG.blockliveDataUpdate) {
try {
ah.proxy({
onRequest: (XHRconfig, handler) => {
if (SP_CONFIG.invisibleEnter && XHRconfig.url.includes('//api.live.bilibili.com/xlive/web-room/v1/index/getInfoByUser')) {
MYDEBUG('getInfoByUser request', XHRconfig);
XHRconfig.url = '//api.live.bilibili.com/xlive/web-room/v1/index/getInfoByUser?room_id=22474988&from=0';
handler.next(XHRconfig);
} else if (SP_CONFIG.blockliveDataUpdate && XHRconfig.url.includes('//data.bilibili.com/log')) {
handler.resolve('ok');
} else {
handler.next(XHRconfig);
}
},
onResponse: async (response, handler) => {
if (response.config.url.includes('//api.live.bilibili.com/xlive/web-room/v1/index/getInfoByUser')) {
MYDEBUG('getInfoByUser response', response);
if (!response.response.includes('"code":0')) {
MYDEBUG('隐身入场出错,取消隐身入场并以当前房间号再次获取用户数据');
response.response = await BAPI.xlive.getInfoByUser(W.BilibiliLive.ROOMID).then((re) => {
MYDEBUG('API.xlive.getInfoByUser(W.BilibiliLive.ROOMID)', re);
if (re.code === 0) return JSON.stringify(re);
else return window.toast(`获取房间基础信息失败 ${re.message}`, 'error');
});
}
response.response = response.response.replace('"is_room_admin":false', '"is_room_admin":true');
const json_response = JSON.parse(response.response);
Live_info.danmu_length = json_response.data.property.danmu.length;
}
handler.next(response);
},
});
} catch (e) {
MYDEBUG('ah.proxy Ajax-hook代理运行出错', e);
}
}
// DOM加载完成后运行
$(function ready() {
// 若 window 下无 BilibiliLive,则说明页面有 iframe,此时脚本在在 top 中运行 或 发生错误
if (W.BilibiliLive === undefined) return;
// 等待BilibiliLive中数据加载完成
if (!W.BilibiliLive.UID) return setTimeout(ready, 100);
// 初始化右上角提示信息弹窗
newWindow.init();
// 检查浏览器版本并显示提示信息
const checkBrowserArr = checkBrowserVersion();
if (checkBrowserArr[0] !== 'ok') {
window.toast(...checkBrowserArr);
if (checkBrowserArr[1] === 'error') return;
}
// 唯一运行检测
onlyScriptCheck();
if (SP_CONFIG.DANMU_MODIFY || SP_CONFIG.AUTO_CHECK_DANMU) {
W.bliveproxy.hook();
MYDEBUG('bliveproxy hook complete', bliveproxy);
}
if (SP_CONFIG.nosleep) {
setInterval(() => mouseMove(), 200e3);
W.addEventListener = (...arg) => {
if (arg[0].indexOf('visibilitychange') > -1) return;
else return eventListener(...arg);
};
W.setTimeout = function (func, ...args) {
if (String(func).indexOf('triggerSleepCallback') !== -1) return _setTimeout.call(this, function () { }, ...args);
else return _setTimeout.call(this, func, ...args);
};
W.setInterval = function (func, ...args) {
if (String(func).indexOf('triggerSleepCallback') !== -1) return _setTimeout.call(this, function () { }, ...args);
else return _setInterval.call(this, func, ...args);
};
}
if (SP_CONFIG.banP2p) {
const RTClist = ['RTCPeerConnection', 'RTCDataChannel', 'mozRTCPeerConnection', 'webkitRTCPeerConnection', 'DataChannel'];
for (const i of RTClist) {
delete W[i];
}
}
if (SP_CONFIG.add_like_button) {
const right_ctnr = $('.right-ctnr');
const share = right_ctnr.find('.v-middle.icon-font.icon-share').parent();
const like_button = $(
'<div data-v-6d89404b="" data-v-42ea937d="" title="" class="icon-ctnr live-skin-normal-a-text pointer" id = "blth_like_button" style="line-height: 16px;margin-left: 15px;"><i data-v-6d89404b="" class="v-middle icon-font icon-good" style="font-size: 16px;"></i><span data-v-6d89404b="" class="action-text v-middle" style="font-size: 12px;margin-left: 5px;">点赞</span></div>'
);
like_button.click(() => {
BAPI.xlive.likeReportV3(Live_info.room_id, Live_info.uid).then((response) => {
MYDEBUG(`点击点赞按钮 likeReportV3(${Live_info.room_id}) response`, response);
const offest = like_button.offset(),
width = like_button.width(),
height = like_button.height();
const top = parseInt(offest.top + height * 1.2) + 'px',
left = parseInt(offest.left + width * 1.2) + 'px';
if (response.code === 0) window.singleToast('点赞成功', 'success', 2e3, top, left);
else window.singleToast(`点赞失败`, 'caution', 2e3, top, left);
});
});
if ($('.right-ctnr').length == 0) return MYERROR('[添加点赞按钮] 无法找到元素 .right-ctnr');
right_ctnr[0].insertBefore(like_button[0], share[0]);
}
if (SP_CONFIG.auto_max_quality) {
autoMaxQuality();
}
const loadInfo = (delay = 0) => {
return setTimeout(async () => {
if (parseInt(W.BilibiliLive.UID) === 0 || isNaN(parseInt(W.BilibiliLive.UID))) {
//MYDEBUG(`${GM_info.script.name}`,'无配置信息');
return loadInfo(500);
} else {
window.toast('正在获取礼物 / 用户 / 账号 / 粉丝勋章数据...', 'info');
Live_info.room_id = W.BilibiliLive.ROOMID;
Live_info.short_room_id = W.BilibiliLive.SHORT_ROOMID;
Live_info.uid = W.BilibiliLive.UID;
Live_info.tid = W.BilibiliLive.ANCHOR_UID;
await BAPI.gift.gift_config().then((response) => {
MYDEBUG('InitData: API.gift.gift_config', response);
if (response.data && Array.isArray(response.data)) {
return (Live_info.gift_list = response.data);
} else if (response.data.list && Array.isArray(response.data.list)) {
return (Live_info.gift_list = response.data.list);
} else {
return window.toast(`直播间礼物数据获取失败 ${response.message}\n使用默认数据`, 'warning');
}
});
let reqFailed = false;
await BAPI.getuserinfo().then((re) => {
MYDEBUG('InitData: API.getuserinfo', re);
if (re.code === 'REPONSE_OK') {
Live_info.uname = re.data.uname;
Live_info.user_level = re.data.user_level;
} else {
window.toast(`API.getuserinfo 获取用户信息失败 ${re.message}`, 'error');
reqFailed = true;
}
});
await BAPI.x.myinfo().then((re) => {
MYDEBUG('InitData: API.x.myinfo', re);
if (re.code === 0) {
// Live_info.level = re.data.level;
Live_info.vipStatus = re.data.profile.vip.status;
} else {
window.toast(`API.x.myinfo 获取账号信息失败 ${re.message}`, 'error');
reqFailed = true;
}
});
if (reqFailed)
return window.toast(`缺少必要的数据,挂机助手停止运行`, 'error')
Live_info.bili_jct = BAPI.getCookie('bili_jct');
Live_info.ruid = W.BilibiliLive.ANCHOR_UID;
Live_info.rnd = W.BilibiliLive.RND;
Live_info.visit_id = W.__statisObserver ? W.__statisObserver.__visitId : '';
MYDEBUG('Live_info', Live_info);
await getMedalList();
MYDEBUG('medla_info', medal_info);
init();
}
}, delay);
};
return loadInfo();
});
function init() {
// 初始化各项功能
const MY_API = {
CONFIG_DEFAULT: {
APP_TASK: false, // APP用户任务(发5条弹幕领1电池)
AUTO_DANMU: false, // 发送弹幕
AUTO_CHECK_DANMU_TIMEOUT: 3000, // 检测弹幕是否发送成功 超时时间
AUTO_GIFT: false, // 自动送礼
AUTO_GIFT_ROOMID: ['0'], // 送礼优先房间
AUTO_GROUP_SIGN: true, // 应援团签到开关
COIN: false, // 投币
COIN_NUMBER: 0, // 投币数量
COIN_TYPE: 'COIN_DYN', // 投币方法 动态/UID
COIN_UID: ['0'], // 投币up主
COIN2SILVER: false, // 银币换银瓜子
COIN2SILVER_NUM: 1, // 银币换银瓜子,硬币数量
DANMU_CONTENT: ['这是一条弹幕'], // 弹幕内容
DANMU_ROOMID: ['22474988'], // 发弹幕房间号
DANMU_INTERVAL_TIME: ['10m'], // 弹幕发送时间
DANMU_MODIFY_REGEX: ['/【/'], // 匹配弹幕 正则字符串
DANMU_MODIFY_UID: [0], // 匹配弹幕 UID
DANMU_MODIFY_POOL: [4], // 修改弹幕 弹幕池
DANMU_MODIFY_COLOR: ['#f7335d'], // 修改弹幕 颜色
DANMU_MODIFY_SIZE: [1.2], // 修改弹幕 大小
GIFT_LIMIT: 1, // 礼物到期时间(天)
GIFT_SEND_HOUR: 23, // 送礼小时
GIFT_SEND_MINUTE: 59, // 送礼分钟
GIFT_INTERVAL: 5, // 送礼间隔
GIFT_METHOD: 'GIFT_SEND_TIME', // 送礼时间策略
GIFT_SORT: 'GIFT_SORT_HIGH', // 送礼优先高等级
GIFT_ALLOW_TYPE: ['1', '6'], // 允许送出的礼物类型,默认:辣条,亿圆
GIFT_SEND_METHOD: 'GIFT_SEND_BLACK', // 送礼黑白名单策略
GIFT_SEND_ROOM: ['0'], // 送礼黑白名单策略 - 房间列表
GET_PRIVILEGE: false, // 自动领取大会员权益
LIVE_SIGN: true, // 直播区签到
LOGIN: true, // 主站登陆
LIKE_LIVEROOM: false, // 点赞直播间
LIKE_LIVEROOM_INTERVAL: 400, // 点赞间隔(毫秒)
LIVE_TASKS_ROOM: ['0'], // 直播区任务房间列表
LIVE_TASKS_METHOD: 'LIVE_TASKS_BLACK', // 直播区任务执行方式
MEDAL_DANMU_INTERVAL: 2, // 打卡弹幕发送间隔(秒)
MEDAL_DANMU: false, // 粉丝勋章打卡弹幕
MEDAL_DANMU_CONTENT: [
'(⌒▽⌒)',
'( ̄▽ ̄)',
'(=・ω・=)',
'(`・ω・´)',
'(〜 ̄△ ̄)〜',
'(・∀・)',
'(°∀°)ノ',
'╮( ̄▽ ̄)╭',
'_(:3」∠)_',
'(^・ω・^ )',
'(● ̄(エ) ̄●)',
'ε=ε=(ノ≧∇≦)ノ',
'⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄',
'←◡←',
], // 粉丝勋章打卡弹幕内容
REMOVE_ELEMENT_2233: false, // 移除2233
REMOVE_ELEMENT_pkBanner: true, // 移除大乱斗入口
REMOVE_ELEMENT_rank: true, // 移除排行榜入口
REMOVE_ELEMENT_rightSideBar: false, // 移除右侧边栏
REMOVE_ELEMENT_flipView: true, // 移除移除礼物栏下方广告
REMOVE_ELEMENT_anchor: false, // 移除天选时刻弹窗及图标
REMOVE_ELEMENT_pk: false, // 移除PK弹窗及进度条
REMOVE_ELEMENT_playerIcon: true, // 移除直播水印
REMOVE_ELEMENT_ecommerce: false, // 移除小橙车相关内容
RND_DELAY_END: 5, // 延迟最大值
RND_DELAY_START: 2, // 延迟最小值
SEND_ALL_GIFT: false, // 送满全部勋章
SHARE: true, // 分享
SILVER2COIN: false, // 银瓜子换硬币
SPARE_GIFT_ROOM: '0', // 剩余礼物送礼房间
TIME_RELOAD: false, // 定时刷新直播间
TIME_RELOAD_MINUTE: 120, // 直播间重载时间
UPDATE_TIP: true, //更新提示
WATCH: true, // 观看视频
WatchLive: false, // 观看直播
WatchLiveInterval: 400, // 观看直播每两次心跳的间隔
WatchLiveTime: 65, // 观看直播时间
},
CACHE_DEFAULT: {
AUTO_SEND_DANMU_TS: [], // 弹幕发送
AUTO_GROUP_SIGH_TS: 0, // 应援团签到
MainSite_login_TS: 0, // 登录(不可用)
MainSite_watch_TS: 0, // 观看视频
MainSite_coin_TS: 0, // 投币
MainSite_share_TS: 0, // 分享视频
Live_sign_TS: 0, // 直播签到
Live_like_TS: 0, // 点赞
Live_share_TS: 0, // 分享
Live_watch_TS: 0, // 观看直播
Live_medalDanmu_TS: 0, // 粉丝勋章打卡弹幕
Silver2Coin_TS: 0, // 银瓜子换硬币
Coin2Sliver_TS: 0, // 硬币换银瓜子
Gift_TS: 0, // 自动送礼(定时)
GiftInterval_TS: 0, // 自动送礼(间隔)
NextVipPrivilege_TS: 0, // 领取大会员权益
AppTaskRewards: 0, // 领取APP用户任务奖励(发5条弹幕领1电池)
},
CONFIG: {},
CACHE: {},
init: () => {
addStyle();
SP_CONFIG.darkMode = $('html').attr('lab-style') === 'dark' ? true : false;
const tabList = $('.tab-list.dp-flex'),
ct = $('.chat-history-panel'),
ctWidth = ct.width(),
aside_area_vmHeight = $('#aside-area-vm').height(),
chat_control_panel_vmHeight = $('#chat-control-panel-vm').height(),
eleList = ['.chat-history-list', '.attention-btn-ctnr', '.live-player-mounter'];
tabContent = $('.tab-content');
logDiv = $(
`<li data-v-2fdbecb2="" data-v-d2be050a="" class="item dp-i-block live-skin-separate-border border-box t-center pointer live-skin-normal-text" style = 'font-weight:bold;color: #999;' id = "logDiv"><span id="logDivText">日志</span><div class="blth_num" style="display: none;" id = 'logRedPoint'>0</div></mli>`
);
let tabOffSet = 0,
top = 0,
left = 0;
if (eleList.some((i) => i.length === 0) || tabList.length === 0 || tabContent.length === 0) {
window.toast('必要页面元素缺失,强制运行(可能会看不到控制面板,提示信息)', 'error');
}
tabList.append(logDiv);
JQlogRedPoint = $('#logRedPoint');
let tabListItems = [];
for (let i = 0; i < tabList.children('li').length; i++) {
tabListItems.push(tabList.children('li')[i]);
}
logIndex = myopen({
type: 1,
title: false,
offset: [String(top) + 'px', String(left) + 'px'],
closeBtn: 0,
shade: 0,
zIndex: 2000,
fixed: false,
area: [String(ctWidth) + 'px', String(aside_area_vmHeight - chat_control_panel_vmHeight) + 'px'], //宽高
anim: -1,
isOutAnim: false,
resize: false,
content: '<div id = "menuWindow"></div>',
success: () => {
layerLogWindow = $('#layui-layer1 .layui-layer-content');
JQmenuWindow = $('#menuWindow');
let logDivText = $('#logDivText');
layerLogWindow.on('DOMNodeInserted', function () {
layerLogWindow_Height = $(this).height();
layerLogWindow_ScrollHeight = $(this)[0].scrollHeight;
if (layerLogWindow_ScrollHeight > layerLogWindow_Height) {
layerLogWindow.scrollTop(layerLogWindow.prop('scrollHeight'));
$(this).off('DOMNodeInserted');
}
});
layerLogWindow.scroll(function () {
layerLogWindow_Height = $(this).height();
layerLogWindow_ScrollHeight = $(this)[0].scrollHeight;
layerLogWindow_ScrollTop = $(this)[0].scrollTop;
layerLogWindow_ScrollY = layerLogWindow_ScrollTop + layerLogWindow_Height + 1;
if (layerLogWindow_ScrollY < layerLogWindow_ScrollHeight) logDivText.text('日志🚀');
else logDivText.text('日志');
});
},
});
layer.style(logIndex, {
'box-shadow': 'none',
display: 'none',
'background-color': SP_CONFIG.darkMode ? '#1c1c1c' : '#f2f3f5',
});
for (const i of tabListItems) {
let JQi = $(i);
JQi.click(() => {
for (const item of tabListItems) {
let JQitem = $(item);
if (item != i) {
if (JQitem.css('color') !== 'rgb(153, 153, 153)') JQitem.css('color', '#999');
if (JQitem.hasClass('live-skin-main-text')) JQitem.removeClass('live-skin-main-text');
if (JQitem.hasClass('active')) JQitem.removeClass('active');
if (!JQitem.hasClass('live-skin-normal-text')) JQitem.addClass('live-skin-normal-text');
} else {
if (JQitem.css('color') !== 'rgb(51, 51, 51)') JQi.css('color', '#333');
if (!JQitem.hasClass('live-skin-main-text')) JQi.addClass('live-skin-main-text');
if (!JQitem.hasClass('active')) JQi.addClass('active');
if (JQitem.hasClass('live-skin-normal-text')) JQi.removeClass('live-skin-normal-text');
}
}
if (JQi.attr('id') === 'logDiv') {
if (!tabOffSet) {
tabOffSet = $('.tab-content').offset();
top = tabOffSet.top;
left = tabOffSet.left;
layer.style(logIndex, {
top: String(top) + 'px',
left: String(left) + 'px',
});
}
layer.style(logIndex, {
display: 'block',
});
} else {
layer.style(logIndex, {
display: 'none',
});
}
});
}
let p1 = $.Deferred(),
p2 = $.Deferred(),
p3 = $.Deferred();
try {
// 设置token
BAPI.setCommonArgs(Live_info.bili_jct);
p1.resolve();
} catch (err) {
MYERROR(`设置token错误`, err);
p1.reject();
}
try {
MY_API.loadConfig().then(() => {
MY_API.chatLog('脚本载入配置成功', 'success');
p2.resolve();
});
} catch (e) {
MYERROR('API初始化出错', e);
MY_API.chatLog('API初始化出错', 'error');
p2.reject();
}
try {
MY_API.loadCache().then(() => {
window.toast('CACHE载入成功', 'success');
p3.resolve();
});
} catch (e) {
MYERROR('CACHE初始化出错', e);
window.toast('CACHE初始化出错', 'error');
p3.reject();
}
return $.when(p1, p2, p3);
},
loadConfig: () => {
// 加载配置函数
let p = $.Deferred();
try {
MY_API.CONFIG = GM_getValue('CONFIG') || {};
mergeObject(MY_API.CONFIG, MY_API.CONFIG_DEFAULT);
p.resolve();
} catch (e) {
MYDEBUG('API载入配置失败,加载默认配置', e);
MY_API.setDefaults();
p.reject();
}
return p;
},
loadCache: () => {
// 加载CACHE
let p = $.Deferred();
try {
MY_API.CACHE = GM_getValue('CACHE') || {};
mergeObject(MY_API.CACHE, MY_API.CACHE_DEFAULT);
p.resolve();
} catch (e) {
MYDEBUG('CACHE载入配置失败,加载默认配置', e);
MY_API.setDefaults();
p.reject();
}
return p;
},
newMessage: (version) => {
try {
const cache = SP_CONFIG.lastShowUpdateMsgVersion || '0';
if (versionStringCompare(cache, version) === -1) {
// cache < version
const clientMliList = [
`修复屏蔽大乱斗弹窗和进度条功能失效的Bug。`,
];
function createHtml(mliList) {
if (mliList.length === 0) return '无';
let mliHtml = '';
for (const mli of mliList) {
mliHtml = mliHtml + '<mli>' + mli + '</mli>';
}
return mliHtml;
}
myopen({
title: `${version}更新提示`,
area: [String($(window).width() * 0.382) + 'px', String($(window).height() * 0.618) + 'px'],
content: `
<h2>更新内容</h2>
<mol>${createHtml(clientMliList)}</mol>
<hr><em style="color:grey;">
如果在使用过程中遇到问题,请到 ${linkMsg('https://github.com/andywang425/BLTH/issues', 'github')}反馈。
也欢迎加入${linkMsg(
'https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=1W7eVLs&businessType=9&from=181074&biz=ka&shareSource=5',
'官方QQ频道'
)}(聊天、反馈问题、提出建议)和${linkMsg('https://t.me/LaTiao01', '非官方电报群')}(纯聊天)。
</em>
`,
});
SP_CONFIG.lastShowUpdateMsgVersion = version;
saveSpConfig();
}
} catch (e) {
MYDEBUG('提示信息CACHE载入失败', e);
}
},
saveConfig: (show = true) => {
// 保存配置函数
try {
GM_setValue('CONFIG', MY_API.CONFIG);
if (show) window.toast('配置已保存,部分设置需刷新后才能生效', 'info');
MYDEBUG('MY_API.CONFIG', MY_API.CONFIG);
return true;
} catch (e) {
MYDEBUG('API保存出错', e);
return false;
}
},
saveCache: (logswitch = true) => {
// 保存缓存函数
try {
GM_setValue('CACHE', MY_API.CACHE);
if (logswitch) MYDEBUG('CACHE已保存', MY_API.CACHE);
return true;
} catch (e) {
MYDEBUG('CACHE保存出错', e);
return false;
}
},
setDefaults: () => {
// 重置配置函数
MY_API.CONFIG = MY_API.CONFIG_DEFAULT;
MY_API.CACHE = MY_API.CACHE_DEFAULT;
MY_API.saveConfig();
MY_API.saveCache();
mymsg('配置和缓存已重置为默认,请刷新页面使配置生效。', { icon: 1 });
},
resetCache: () => {
MY_API.CACHE = MY_API.CACHE_DEFAULT;
MY_API.saveCache();
mymsg('缓存已重置为默认,请刷新页面使配置生效。', { icon: 1 });
},
resetMainSiteTasksCache: () => {
MY_API.CACHE.MainSite_login_TS = 0;
MY_API.CACHE.MainSite_watch_TS = 0;
MY_API.CACHE.MainSite_coin_TS = 0;
MY_API.CACHE.MainSite_share_TS = 0;
MY_API.saveCache();
mymsg('主站每日任务缓存已重置为默认,请刷新页面使配置生效。', { icon: 1 });
},
resetLiveTasksCache: () => {
MY_API.CACHE.Live_sign_TS = 0;
MY_API.CACHE.Live_like_TS = 0;
MY_API.CACHE.Live_share_TS = 0;
MY_API.CACHE.Live_watch_TS = 0;
MY_API.CACHE.Live_medalDanmu_TS = 0;
MY_API.saveCache();
mymsg('直播任务缓存已重置为默认,请刷新页面使配置生效。', { icon: 1 });
},
resetOtherTasksCache: () => {
MY_API.CACHE.Silver2Coin_TS = 0;
MY_API.CACHE.Coin2Sliver_TS = 0;
MY_API.CACHE.AUTO_GROUP_SIGH_TS = 0;
MY_API.saveCache();
mymsg('其它任务缓存已重置为默认,请刷新页面使配置生效。', { icon: 1 });
},
removeUnnecessary: () => {
// 移除不必要的页面元素
const unnecessaryObj = [
{
// 2233
settingName: 'REMOVE_ELEMENT_2233',
rmJQpath: ['#my-dear-haruna-vm'],
},
{
// 大乱斗入口
settingName: 'REMOVE_ELEMENT_pkBanner',
rmJQpath: ['.awesome-pk-box'],
},
{
// 排行榜(活动?)
settingName: 'REMOVE_ELEMENT_rank',
rmJQpath: ['.activity-gather-entry', '.activity-rank', '.rank-item'],
},
{
// 右侧边栏
settingName: 'REMOVE_ELEMENT_rightSideBar',
rmJQpath: ['.side-bar-cntr'],
},
{
// 礼物栏下方广告
settingName: 'REMOVE_ELEMENT_flipView',
rmJQpath: ['.flip-view'],
},
{
// 天选时刻弹窗及图标
settingName: 'REMOVE_ELEMENT_anchor',
addCss: '.anchor-lottery-entry * {display: none;} #anchor-guest-box-id * {display: none;}',
eval: `setInterval(() => {$("iframe").contents().find("#app .close-btn").click()}, 200)`,
},
{
// PK弹窗及进度条
settingName: 'REMOVE_ELEMENT_pk',
addCss: '.process-box * {display: none;} #chaos-pk-vm * {display:none;}',
eval: `setInterval(() => {$("iframe").contents().find("#app .closeBtn").click()}, 200)`,
},
{
// 直播水印
settingName: 'REMOVE_ELEMENT_playerIcon',
rmJQpath: ['.web-player-icon-roomStatus'],
},
{
// 小橙车
settingName: 'REMOVE_ELEMENT_ecommerce',
rmJQpath: ['#shop-popover-vm', '.ecommerce-entry'],
},
];
const removeElement = (obj) => {
if (MY_API.CONFIG[obj.settingName]) {
if (obj.hasOwnProperty('rmJQpath')) {
for (const path of obj.rmJQpath) {
let timer = setInterval(() => {
const unnecessaryItem = $(path);
if (unnecessaryItem.length > 0) {
unnecessaryItem.remove();
clearInterval(timer);
}
}, 200);
}
}
if (obj.hasOwnProperty('addCss')) GM_addStyle(obj.addCss);
if (obj.hasOwnProperty('eval')) eval(obj.eval);
}
};
for (const i of unnecessaryObj) {
removeElement(i);
}
},
buyFanMedal: (room_id) => {
return BAPI.live_user.get_anchor_in_room(room_id).then(function (response) {
MYDEBUG('API.live_user.get_anchor_in_room response', response);
if (response.code === 0 && response.data.info) {
const uid = String(response.data.info.uid),
uname = response.data.info.uname;
return BAPI.xlive.getInfoByUser(room_id).then(function (res) {
if (res.code === 0) {
if (!res.data.medal.up_medal) {
return mymsg(`<div style = "text-align:center">UP主<br>${linkMsg('https://space.bilibili.com/' + uid, uname)}<br>没有粉丝勋章,无法购买</div>`, {
time: 2500,
icon: 2,
});
}
myconfirm(
`<div style = "text-align:center">是否消耗2B币购买UP主<br>${linkMsg('https://space.bilibili.com/' + uid, uname)}<br>的粉丝勋章?</div>`,
{
title: `购买勋章 房间号:${room_id}`,
btn: ['是', '否'],
},
function () {
BAPI.x.elec_pay_quick(response.data.info.uid).then((re) => {
MYDEBUG('API.x.elec_pay_quick re', re);
if (re.code === 0 && re.data.status === 4) {
mymsg('购买成功', {
time: 2000,
icon: 1,
});
} else {
mymsg(`购买失败 ${re.data.msg}`, {
time: 2500,
icon: 2,
});
}
});
},
function () {
mymsg('已取消购买', {
time: 2000,
});
}
);
} else {
return mymsg(`检查房间出错 ${response.message}`, {
time: 2500,
});
}
});
} else if (response.code === 0 && response.data.info === undefined) {
mymsg(`房间不存在`, {
time: 2500,
});
} else {
mymsg(`检查房间出错 ${response.message}`, {
time: 2500,
});
}
});
},
creatSetBox: async () => {
//添加按钮
const btnmsg = SP_CONFIG.mainDisplay === 'hide' ? '显示控制面板' : '隐藏控制面板';
const btn = $(`<button class="blth_btn" style="display: inline-block; float: left; margin-right: 7px;cursor: pointer;box-shadow: 1px 1px 2px #00000075;" id="hiderbtn">${btnmsg}<br></button>`);
const body = $('body');
const webHtml = $('html');
const html = GM_getResourceText('main');
function layerOpenAbout() {
return myopen({
title: `版本${GM_info.script.version}`,
content: `<h3 style="text-align:center">B站直播间挂机助手</h3>作者:${linkMsg('https://github.com/andywang425/', 'andywang425')}<br>许可证:${linkMsg(
'https://raw.githubusercontent.com/andywang425/BLTH/master/LICENSE',
'MIT'
)}<br>github项目地址:${linkMsg('https://github.com/andywang425/BLTH', 'BLTH')}<br>反馈:${linkMsg('https://github.com/andywang425/BLTH/issues', 'BLTH/issues')}<br>交流qq群:${linkMsg(
'https://jq.qq.com/?_wv=1027&k=9refOc8c',
'657763329'
)}<br>`,
});
}
const saveAction = (div) => {
let val = undefined;
let valArray = undefined;
let val1, val2, val3;
// TIME_RELOAD save
val = parseInt(div.find('div[data-toggle="TIME_RELOAD"] .delay-seconds').val());
if (val <= 0 || val > 10000) return window.toast('[直播间重载时间]数据小于等于0或大于10000', 'caution');
MY_API.CONFIG.TIME_RELOAD_MINUTE = val;
// COIN
val = parseInt(div.find('div[data-toggle="COIN"] .coin_number').val());
if (val < 0 || val > 5) return window.toast('[自动投币]数据小于0或大于5', 'caution');
MY_API.CONFIG.COIN_NUMBER = val;
// AUTO_GIFT_ROOMID
val = div.find('div[data-toggle="AUTO_GIFT_ROOMID"] .num').val();
valArray = val.split(',');
for (let i = 0; i < valArray.length; i++) {
if (valArray[i] === '') {
valArray[i] = 22474988;
}
}
MY_API.CONFIG.AUTO_GIFT_ROOMID = valArray;
// GIFT_LIMIT
val = parseInt(div.find('div[data-toggle="GIFT_LIMIT"] .num').val());
MY_API.CONFIG.GIFT_LIMIT = val;
// GIFT_INTERVAL
val = parseInt(div.find('div[data-toggle="GIFT_INTERVAL"] .num').val());
MY_API.CONFIG.GIFT_INTERVAL = val;
// GIFT_SEND_TIME
val1 = parseInt(div.find('div[data-toggle="GIFT_SEND_TIME"] .Hour').val());
val2 = parseInt(div.find('div[data-toggle="GIFT_SEND_TIME"] .Minute').val());
if (val1 < 0 || val2 < 0 || val1 >= 24 || val2 >= 60) return window.toast('[送礼时间]时间错误', 'caution');
MY_API.CONFIG.GIFT_SEND_HOUR = val1;
MY_API.CONFIG.GIFT_SEND_MINUTE = val2;
// SPARE_GIFT_ROOM
val = div.find('div[data-toggle="SPARE_GIFT_ROOM"] .num').val();
MY_API.CONFIG.SPARE_GIFT_ROOM = val;
// COIN_UID
val = div.find('div[data-toggle="COIN_UID"] .num').val();
valArray = val.split(',');
for (let i = 0; i < valArray.length; i++) {
if (valArray[i] === '') {
valArray[i] = 0;
}
}
MY_API.CONFIG.COIN_UID = valArray;
// AUTO_DANMU
val1 = div.find('div[data-toggle="AUTO_DANMU_SETTINGS"] .Danmu').val();
valArray = val1.split(',');
for (let i = 0; i < valArray.length; i++) {
if (valArray[i] === '') {
valArray[i] = '这是一条弹幕';
}
}
val1 = valArray;
val2 = div.find('div[data-toggle="AUTO_DANMU_SETTINGS"] .Roomid').val();
valArray = val2.split(',');
for (let i = 0; i < valArray.length; i++) {
if (valArray[i] === '') {
valArray[i] = '22474988';
}
}
val2 = valArray;
val3 = div.find('div[data-toggle="AUTO_DANMU_SETTINGS"] .Time').val();
valArray = val3.split(',');
for (let i = 0; i < valArray.length; i++) {
if (valArray[i] === '') {
valArray[i] = '10m';
}
}
val3 = valArray;
MY_API.CONFIG.DANMU_CONTENT = val1;
MY_API.CONFIG.DANMU_ROOMID = val2;
MY_API.CONFIG.DANMU_INTERVAL_TIME = val3;
// MEDAL_DANMU_INTERVAL
val = parseFloat(div.find('[data-toggle="MEDAL_DANMU_INTERVAL"] .num').val());
if (isNaN(val) || val < 0) return window.toast('[打卡弹幕发送间隔] 错误输入', 'caution');
MY_API.CONFIG.MEDAL_DANMU_INTERVAL = val;
// COIN2SILVER_NUM
val = parseInt(div.find('[data-toggle="COIN2SILVER"] .coin_number').val());
if (isNaN(val) || val < 0) return window.toast('[硬币换银瓜子] 错误输入', 'caution');
MY_API.CONFIG.COIN2SILVER_NUM = val;
// GIFT_ALLOW_TYPE
val = div.find('[data-toggle="GIFT_ALLOW_TYPE"] .str').val();
valArray = val.split(',');
for (let i = 0; i < valArray.length; i++) {
if (valArray[i] === '') {
valArray[i] = '0';
}
}
MY_API.CONFIG.GIFT_ALLOW_TYPE = valArray;
// DANMU_MODIFY_REGEX
val = div.find('div[data-toggle="DANMU_MODIFY_REGEX"] .str').val();
valArray = val.split(',');
for (let i = 0; i < valArray.length; i++) {
if (valArray[i] === '') valArray[i] = 1;
else valArray[i] = valArray[i];
}
MY_API.CONFIG.DANMU_MODIFY_REGEX = valArray;
// DANMU_MODIFY_UID
val = div.find('div[data-toggle="DANMU_MODIFY_UID"] .str').val();
valArray = val.split(',');
for (let i = 0; i < valArray.length; i++) {
if (valArray[i] === '') valArray[i] = 0;
else valArray[i] = parseInt(valArray[i]);
}
MY_API.CONFIG.DANMU_MODIFY_UID = valArray;
// DANMU_MODIFY_POOL
val = div.find('div[data-toggle="DANMU_MODIFY_POOL"] .str').val();
valArray = val.split(',');
for (let i = 0; i < valArray.length; i++) {
if (valArray[i] === '') valArray[i] = 1;
else valArray[i] = parseInt(valArray[i]);
}
MY_API.CONFIG.DANMU_MODIFY_POOL = valArray;
// DANMU_MODIFY_COLOR
val = div.find('div[data-toggle="DANMU_MODIFY_COLOR"] .str').val();
valArray = val.split(',');
for (let i = 0; i < valArray.length; i++) {
if (valArray[i] === '') valArray[i] = '#FF000';
}
MY_API.CONFIG.DANMU_MODIFY_COLOR = valArray;
// DANMU_MODIFY_SIZE
val = div.find('div[data-toggle="DANMU_MODIFY_SIZE"] .str').val();
valArray = val.split(',');
for (let i = 0; i < valArray.length; i++) {
if (valArray[i] === '') valArray[i] = 1;
else valArray[i] = parseFloat(valArray[i]);
}
MY_API.CONFIG.DANMU_MODIFY_SIZE = valArray;
// AUTO_CHECK_DANMU_TIMEOUT
val = parseInt(div.find('[data-toggle="AUTO_CHECK_DANMU_TIMEOUT"] .num').val());
if (isNaN(val) || val <= 0) return window.toast('[检查弹幕是否发送成功超时时间] 错误输入', 'caution');
MY_API.CONFIG.AUTO_CHECK_DANMU_TIMEOUT = val;
// LIKE_LIVEROOM_INTERVAL
val = parseInt(div.find('[data-toggle="LIKE_LIVEROOM_INTERVAL"] .num').val());
if (isNaN(val) || val < 0) return window.toast('[直播点赞请求间隔] 错误输入', 'caution');
MY_API.CONFIG.LIKE_LIVEROOM_INTERVAL = val;
// WatchLiveInterval
val = parseInt(div.find('[data-toggle="WatchLiveInterval"] .num').val());
if (isNaN(val) || val < 0) return window.toast('[观看直播心跳请求间隔] 错误输入', 'caution');
MY_API.CONFIG.WatchLiveInterval = val;
// WatchLiveTime
val = parseInt(div.find('[data-toggle="WatchLiveTime"] .num').val());
if (isNaN(val) || val < 0) return window.toast('[观看直播心跳请求间隔] 错误输入', 'caution');
MY_API.CONFIG.WatchLiveTime = val;
return MY_API.saveConfig();
};
const checkList = [
'APP_TASK',
'AUTO_DANMU',
'AUTO_GIFT',
'AUTO_GROUP_SIGN',
'COIN',
'COIN2SILVER',
'GET_PRIVILEGE',
'WatchLive',
'LIVE_SIGN',
'LOGIN',
'MEDAL_DANMU',
'REMOVE_ELEMENT_2233',
'REMOVE_ELEMENT_anchor',
'REMOVE_ELEMENT_flipView',
'REMOVE_ELEMENT_rightSideBar',
'REMOVE_ELEMENT_pk',
'REMOVE_ELEMENT_pkBanner',
'REMOVE_ELEMENT_playerIcon',
'REMOVE_ELEMENT_rank',
'REMOVE_ELEMENT_ecommerce',
'SEND_ALL_GIFT',
'SHARE',
'SILVER2COIN',
'TIME_RELOAD',
'UPDATE_TIP',
'WATCH',
'LIKE_LIVEROOM',
];
const radioList = [
/**
* {
* name: 包含所有多选框的div的data-toggle,多选框的name,MY_API.CONFIG中的对象名
* toggle<num>: 每个多选框的div的data-toggle,MY_API.CONFIG中对应设置的值
* }
*/
{
name: 'COIN_TYPE',
toggle1: 'COIN_DYN',
toggle2: 'COIN_UID',
},
{
name: 'GIFT_METHOD',
toggle1: 'GIFT_INTERVAL',
toggle2: 'GIFT_SEND_TIME',
},
{
name: 'GIFT_SORT',
toggle1: 'GIFT_SORT_HIGH',
toggle2: 'GIFT_SORT_LOW',
},
{
name: 'LIVE_TASKS_METHOD',
toggle1: 'LIVE_TASKS_WHITE',
toggle2: 'LIVE_TASKS_BLACK',
},
{
name: 'GIFT_SEND_METHOD',
toggle1: 'GIFT_SEND_WHITE',
toggle2: 'GIFT_SEND_BLACK',
},
];
const helpText = {
// 帮助信息
GIFT_SEND_METHOD:
'自动送礼策略,有白名单和黑名单两种。后文中的<code>直播间</code>指拥有粉丝勋章的直播间。<mul><mli>白名单:仅给房间列表内的直播间送礼。</mli><mli>黑名单:给房间列表以外的直播间送礼。</mli><mli>如果要填写多个房间,每两个房间号之间需用半角逗号<code>,</code>隔开。</mli></mul>',
MEDAL_DANMU:
'在拥有粉丝勋章的直播间内,每天发送的首条弹幕将点亮对应勋章并给该勋章+100亲密度。<mh3>注意:</mh3><mul><mli>如果要填写多条弹幕,每条弹幕间请用半角逗号<code>,</code>隔开,发弹幕时将依次选取弹幕进行发送(若弹幕数量不足则循环选取)。</mli><mli>本功能运行时【自动发弹幕】,【自动送礼】和【APP用户任务】将延后运行。</mli></mul>',
AUTO_DANMU:
'发送直播间弹幕。<mul><mli><mp>弹幕内容,房间号,发送时间可填多个,数据之间用半角逗号<code>,</code>隔开(数组格式)。脚本会按顺序将这三个值一一对应,发送弹幕。</mp></mli><mli><mp>由于B站服务器限制,每秒最多只能发1条弹幕。若在某一时刻有多条弹幕需要发送,脚本会在每条弹幕间加上1.5秒间隔时间(对在特定时间点发送的弹幕无效)。</mp></mli><mli><mp>如果数据没对齐,缺失的数据会自动向前对齐。如填写<code>弹幕内容 lalala</code>,<code>房间号 3,4</code>,<code>发送时间 5m,10:30</code>,少填一个弹幕内容。那么在发送第二条弹幕时,第二条弹幕的弹幕内容会自动向前对齐(即第二条弹幕的弹幕内容是lalala)。 </mp></mli><mli><mp>可以用默认值所填的房间号来测试本功能,但是请不要一直发。</mp></mli><mli><mp>发送时间有两种填写方法</mp><mp>1.【小时】h【分钟】m【秒】s</mp><mul><mli>每隔一段时间发送一条弹幕</mli><mli>例子:<code>1h2m3s</code>, <code>300m</code>, <code>30s</code>, <code>1h50s</code>, <code>2m6s</code>, <code>0.5h</code></mli><mli>可以填小数</mli><mli>可以只填写其中一项或两项</mli></mul><mp>脚本会根据输入数据计算出间隔时间,每隔一个间隔时间就会发送一条弹幕。如果不加单位,如填写<code>10</code>则默认单位是分钟(等同于<code>10m</code>)。</mp><mp><em>注意:必须按顺序填小时,分钟,秒,否则会出错(如<code>3s5h</code>就是错误的写法)</em></mp><mp>2.【小时】:【分钟】:【秒】</mp><mul><mli>在特定时间点(本地时间)发一条弹幕</mli><mli>例子: <code>10:30:10</code>, <code>0:40</code></mli><mli>只能填整数</mli><mli>小时分钟必须填写,秒数可以不填</mli></mul><mp>脚本会在该时间点发一条弹幕(如<code>13:30:10</code>就是在下午1点30分10秒的时候发弹幕)。</mp></mli></mul>',
NOSLEEP:
'屏蔽B站的挂机检测。不开启本功能时,标签页后台或长时间无操作就会触发B站的挂机检测。<mh3>原理:</mh3><mul><mli>劫持页面上的<code>addEventListener</code>绕过页面可见性检测,每5分钟触发一次鼠标移动事件规避鼠标移动检测。同时劫持页面上的setTimeout和setInterval避免暂停直播的函数被调用。</mli><mul>',
INVISIBLE_ENTER:
'开启后进任意直播间其他人都不会看到你进直播间的提示【xxx 进入直播间】(只有你自己能看到)。<mh3>缺点:</mh3><mul><mli>开启后无法获取自己是否是当前直播间房管的数据,关注按钮状态均为未关注。所以开启本功能后进任意直播间都会有【禁言】按钮(如果不是房管操作后会显示你没有权限),发弹幕时弹幕旁边会有房管标识(如果不是房管则只有你能看到此标识)。</mli><mli>无法打开页面下拉后出现的动态的评论区。</mli></mul>',
BUY_MEDAL: '通过给UP充电,消耗2B币购买某位UP的粉丝勋章。<mul><mli>默认值为当前房间号。点击购买按钮后有确认界面,无需担心误触。</mli></mul>',
btnArea:
'缓存中存放的是各个任务上次运行的时间,脚本通过缓存来判断某些周期性执行的任务需不需要执行(比如每天一次的分享视频任务)。<mul><mli>重置所有为默认:指将设置和缓存重置为默认。</mli><mli>导出配置:导出一个包含当前脚本设置的json到浏览器的默认下载路径,文件名为<code>BLTH_CONFIG.json</code>。</mli><mli>导入配置:从一个json文件导入脚本配置,导入成功后脚本会自动刷新页面使配置生效。</mli></mul>',
WatchLive:
'通过模拟心跳完成连续观看直播的任务(无论目标房间是否开播都能完成任务)。<mul><mli>本任务运行时不会自动刷新页面。</mli><mli>如果你使用了带有广告拦截功能的浏览器拓展,可能会导致该功能无法使用。请自行将以下两个URL(或者合适的拦截规则)添加到拓展程序的白名单中:<br><code>https://live-trace.bilibili.com/xlive/data-interface/v1/x25Kn/E</code><br><code>https://live-trace.bilibili.com/xlive/data-interface/v1/x25Kn/X</code></mli><mli>如果主播没有设置直播分区,该任务无法完成。</mli></mul>',
SEND_ALL_GIFT: '若不勾选该项,自动送礼只会送出在【允许被送出的礼物类型】中的礼物。',
AUTO_GIFT_ROOMID:
'送礼时优先给这些房间送礼,送到对应粉丝牌亲密度上限后再送其它的。<mul><mli>如果要填写多个房间,每两个房间号之间需用半角逗号<code>,</code>隔开。如<code>666,777,888</code>。</mli></mul>',
GIFT_LIMIT: '将要在这个时间段里过期的礼物会被送出。<mh3>注意:</mh3><mul><mli>勾选【无视礼物类型和到期时间限制】时无论礼物是否将要过期都会被送出。</mli></mul>',
AUTO_GIFT:
'<mh3>说明:</mh3><mul><mli>送礼设置优先级:<br>不送礼房间 >优先送礼房间 >优先高/低等级粉丝牌。</mli><mli>送礼设置逻辑规则:<br>无论【优先高/低等级粉丝牌】如何设置,会根据【无视礼物类型和到期时间限制】(勾选则无视是否到期补满亲密度,否则只送到期的)条件去按优先送礼房间先后顺序送礼。之后根据【优先高/低等级粉丝牌】决定先送高级还是低级。 </mli><mli>送礼顺序:<br>高亲密度的礼物会被优先送出,在满足此条件的情况下先送快要过期的礼物。 </mli><mli>不会送出永久礼物。 </mli></mul>',
SPARE_GIFT_ROOM: '【剩余礼物】指送满了所有粉丝牌,但仍有剩余的将在1天内过期的礼物。<mul><mli>该项填<code>0</code>则不送剩余礼物。</mli></mul>',
COIN: '自动给视频投币,每天最多投5个。<mul><mli>脚本会根据今日你已获得的投币经验值判断你已经投了多少个币,然后自动投剩余没投的币。<blockquote>如今日已获得投币经验20,脚本投币数量设置为4,则会投2个币。</blockquote></mli></mul>',
COIN_UID:
'该项若填<code>0</code>则给动态中的视频依次投币(不存在UID为0的用户)。<mul><mli>可以填写多个uid,每两个uid间用半角逗号<code>,</code>隔开。</mli><mli>如果填了多个uid,则会依次检查这些UP是否有可投币的视频。</mli></mul>',
LIVE_TASKS_METHOD:
'执行以下三个任务(点赞直播间,连续观看直播,粉丝勋章打卡弹幕)的任务模式,有白名单和黑名单两种。后文中的<code>直播间</code>指拥有粉丝勋章的直播间。<mul><mli>白名单:仅在房间列表内的直播间执行任务。</mli><mli>黑名单:在房间列表以外的直播间执行任务。</mli><mli>若要填写多个直播间,每两个直播间号之间用半角逗号<code>,</code>隔开。</mli></mul>',
debugSwitch:
'开启或关闭控制台日志(Chrome可通过<code>ctrl + shift + i</code>,再点击<code>Console</code>打开控制台)。<mul><mli>平时建议关闭,减少资源占用。</mli><mli>该设置只会影响日志(<code>console.log</code>),不会影响报错(<code>console.error</code>)。</mli></mul>',
UPDATE_TIP: '每次更新后第一次运行脚本时显示关于更新内容的弹窗。',
MEDAL_DANMU_INTERVAL: '每两条弹幕间所等待的时间。<mh3>注意:</mh3><mul><mli>由于B站服务器限制,间隔时间必须大于等于1秒,否则弹幕发送会出错。</mli></mul>',
SHARE: '并不会真的分享视频,通过调用特定api直接完成任务。',
COIN2SILVER:
'普通用户每天兑换上限<code>25</code>硬币,老爷或大会员每天兑换上限<code>50</code>硬币。<mul><mli><code>1</code>硬币 = <code>450</code>银瓜子(老爷或大会员<code>500</code>银瓜子)。</mli></mul>',
SILVER2COIN: '每日直播用户都可以将部分银瓜子转化为硬币,每天仅一次机会。<mul><mli><code>700</code>银瓜子 = <code>1</code>硬币。</mul></mli>',
windowToast: `右上角的提示信息。相对来说不是那么重要,所以不放在日志窗口里。<mul style = "line-height:1em;"><div class="link-toast info fixed"><span class="toast-text">普通消息</span></div><br><br><br><div class="link-toast success fixed"><span class="toast-text">成功</span></div><br><br><br><div class="link-toast error fixed"><span class="toast-text">发生错误</span></div></mul>`,
GIFT_ALLOW_TYPE:
'可以填写礼物的id或者礼物名称。<mul><mli>如果要填写多个,每两项之间请用半角逗号<code>,</code>隔开。</mli><mli>如果填写礼物名称,请确保所填写的名称与官方名称完全一致,否则将无法识别。</mli><mli>在脚本中打开控制台日志后,在控制台(Chrome可通过<code>ctrl + shift + i</code>,再点击<code>Console</code>打开控制台)中搜索<code>InitData: API.gift.gift_config</code>可以找到一个包含礼物名称和 id 的json。将data下的几项全部展开,再搜索礼物名即可找到 id 。</mli><mli>常用 id :1: <code>辣条</code> 6: <code>亿圆</code></mli></mul>',
REMOVE_ELEMENT_anchor:
'屏蔽天选时刻弹窗和礼物栏左侧的图标。<mh3>注意:</mh3><mul><mli>开启这一功能后会消耗相对较多的资源。</mli><mli>弹窗出现后(不可见)0-200ms的时间内浏览器窗口会无法滚动。</mli></mul><mh3>原理:</mh3><mul>通过修改css样式使弹窗不显示。但弹窗出现时浏览器窗口会被限制滚动,脚本检测到之后会将其关闭来解除滚动限制。</mul>',
REMOVE_ELEMENT_anchor:
'屏蔽天选时刻弹窗和礼物栏左侧的图标。<mh3>注意:</mh3><mul><mli>开启这一功能后会消耗相对较多的资源。</mli><mli>弹窗出现后(不可见)0-200ms的时间内浏览器窗口会无法滚动。</mli></mul><mh3>原理:</mh3><mul>通过修改css样式使弹窗不显示。但弹窗出现时浏览器窗口会被限制滚动,脚本检测到之后会将其关闭来解除滚动限制。</mul>',
REMOVE_ELEMENT_pk:
'屏蔽大乱斗弹窗和进度条。<mh3>注意:</mh3><mul><mli>开启这一功能后会消耗相对较多的资源。</mli><mli>弹窗出现后(不可见)0-200ms的时间内浏览器窗口会无法滚动。</mli></mul><mh3>原理:</mh3><mul>通过修改css样式使弹窗不显示。但弹窗出现时浏览器窗口会被限制滚动,脚本检测到之后会将其关闭来解除滚动限制。</mul>',
banP2p:
"禁止p2p上传(下载),减少上行带宽的占用。<mh3>原理:</mh3><mul>删除window下部分WebRTC方法,如<code>RTCPeerConnection</code>,<code>RTCDataChannel</code>。</mul><h3>说明:</h3><mul><mli>B站的<a href = 'https://baike.baidu.com/item/%E5%AF%B9%E7%AD%89%E7%BD%91%E7%BB%9C/5482934' target = '_blank'>P2P</a>上传速率大概在600KB/s左右,目的是为了让其他用户能更加流畅地观看直播。如果你的上行带宽较小建议禁用。</mli><mli>开启后控制台可能会出现大量报错如<code style='color:red;'>unsupported bilibili p2p</code>,<code style='color:red;'>Error: launch bili_p2p failed</code>,此类报错均为b站js的报错,无视即可。</mli></mul>",
DANMU_MODIFY:
"修改匹配到的当前直播间弹幕,改变弹幕的显示方式。<mh3>注意:</mh3><mul><mli>匹配弹幕和修改弹幕中的所有设置项都支持填写多个数据。若要填写多个,请用半角逗号<code>,</code>隔开。例:正则表达式 <code>/团【/,/P【/</code>。 </mli><mli>若填写了多个数据,脚本会把这些数据一一匹配,创建不同的规则。缺失的数据会自动向前对齐。<br>例:脚本设置为 匹配弹幕:<code>/团【/,/P【/</code> 发送者UID:<code>0</code> 弹幕池:<code>4,5</code> 颜色:<code>#FF0000,#9932CC</code> 大小:<code>1.2</code><br>此时有这么一条弹幕:<code>P【这个塔的伤害好高啊</code>,满足了第二条匹配规则<code>/P【/</code>。但由于该规则中缺少【大小】数据,则自动向前对齐,即大小被设为<code>1.2</code>。</mli></mli></mul><mh3>匹配弹幕</mh3>有【正则表达式】和【发送者UID】两种匹配方式,任意一项匹配成功则对弹幕进行修改。<mul><mli>正则表达式:即<a href='https://www.runoob.com/js/js-regexp.html' target='_blank'>JavaScript正则表达式</a>。格式为<code>/【正则】/【修饰符】(可选)</code>,如<code>/cards/i</code>。<br>如果填写的正则表达式能匹配弹幕内容则对弹幕进行修改。 </mli><mli>发送者UID:如果填写的UID中包含弹幕发送者的UID则对弹幕进行修改。</mli></mul><mh3>修改弹幕</mh3><mul><mli>弹幕池:修改弹幕所在的弹幕池,可以改变弹幕的显示位置。<br>弹幕池编号:<code>1</code>滚动,<code>4</code>底部,<code>5</code>顶部。如果填写其他数字则不会显示。</mli><mli>颜色:修改弹幕的颜色。<br>需填写所要修改颜色的<a href='http://tools.jb51.net/color/rgb_hex_color' target='_blank'>十六进制颜色码</a>,如<code style='color:#FF0000;'>#FF0000</code>。</mli><mli>大小:缩放弹幕到指定大小。<br>填<code>1.5</code>就是放大到原来的1.5倍,填<code>0.5</code>则是缩小到一半。</mli></mul>",
blockLiveStream: `拦截直播流。开启本功能后将无法观看直播。<mh3>原理:</mh3><mul>劫持页面上的fetch,通过判断url是否含有<code>bilivideo</code>拦截所有直播流请求。</mul><mh3>注意:</mh3><mul><mli>开启本功能后控制台中会出现大量报错,如<code style='color:red;'>id 38: player core NetworkError, {"code":11001,"errInfo":{"url":"https://d1--cn-gotcha204.bilivideo.com/live-bvc/284219/live_50333369_2753084_4000/index.m3u8?expires=1618677399&len=0&oi=1700331273&pt=web&qn=0&trid=9cc4c8772c0543999b03360f513dd1fa&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha04&sign=bd05d848ebf2c7a815e0242ac1477187&p2p_type=1&src=9&sl=4&sk=59b4112a8c653bb","info":"TypeError: Cannot read property 'then' of undefined"}}</code>,此类报错均为b站js的报错,无视即可。</mli></mul>`,
blockliveDataUpdate:
"拦截直播观看数据上报。<mh3>原理:</mh3><mul>劫持页面上的fetch和XMLHttpRequest,拦截所有url中含有<code>data.bilibili.com/gol/postweb</code>的fetch请求和url中含有<code>data.bilibili.com/log</code>的xhr请求。</mul><mh3>注意:</mh3><mul><mli>开启本功能后控制台中会出现大量警告,如<code style='color:rgb(255 131 0);'>jQuexry.Deferred exception: Cannot read property 'status' of undefined TypeError: Cannot read property 'status' of undefined</code>,此类报错均为b站js的报错,无视即可。 </mli></mul><mh3>说明:</mh3><mul><mli>根据观察,目前上报的数据有:p2p种类,直播画质,直播流编码方式,直播流地址,直播流名称,直播流协议,窗口大小,观看时长,请求花费时长, 请求成功/失败数量,通过p2p下载的有效直播流大小,通过p2p上传的直播流大小,当前直播间地址,当前时间戳等等。 </mli></mul>",
WEAR_MEDAL_BEFORE_DANMU:
'手动发送弹幕前自动佩戴当前房间的粉丝勋章再发弹幕。<mul><mli>如果没有当前直播间的粉丝勋章则不进行任何操作。</mli><mli>【一直自动佩戴】比较适合需要同时在多个直播间发弹幕的情况。如果只想在某一个直播间发弹幕勾选【仅在首次发弹幕时自动佩戴】即可。</mli><mli>佩戴成功后会把弹幕框左侧的粉丝牌替换为当前直播间的粉丝牌。</mli></mul>',
REMOVE_ELEMENT_pkBanner: '移除位于直播画面上方的大乱斗入口。',
REMOVE_ELEMENT_rank:
'移除位于直播画面上方的排行榜(?)入口。<mul><mli>这个位置有时候会变成某个活动的入口。如果你不是主播也不是喜欢给主播送礼物的观众,那么这些活动通常和你没关系。</mli></mul>',
GET_PRIVILEGE: '每个月领取一次大会员权益。<mul><mli>目前仅支持领取B币券和会员购优惠券。</mli></mul>',
AUTO_CHECK_DANMU:
'检查你在当前直播间发送的弹幕是否发送成功。脚本向其它直播间发送的弹幕不在检测范围内。<mul><mli>这里的发送成功指的是你发送的弹幕对其他人可见。有时候表面上弹幕发送成功了,但实际上只有你自己能看见那条弹幕。</mli><mli>若弹幕疑似发送失败,则在右上角显示一条提示信息。</mli></mul>',
AUTO_CHECK_DANMU_TIMEOUT: '弹幕被发送出去后开始计时,如果没有在超时时间内从当前直播间的webSocket中接收到之前所发送的弹幕则认为发送失败。',
LIKE_LIVEROOM: '完成点赞直播间1次的任务',
LIKE_LIVEROOM_INTERVAL: '每两次点赞之间的间隔时间。<mul><mli>若间隔时间过短,部分点赞可能会无效,即不加亲密度。请自行调整到一个合适的间隔时间。</mli></mul>',
WatchLiveInterval: '每两次心跳的间隔时间。<mul><mli>脚本会依次给每个粉丝勋章对应的直播间发心跳包,然后重复数次直到观看时间达标为止。</mli><mli>若间隔时间过短可能会出错。</mli></mul>',
DailyTasksBtnArea:
'缓存中存放的是各个任务上次运行的时间,脚本通过缓存来判断某些周期性执行的任务需不需要执行(比如每天一次的分享视频任务)。<mul><mli>重置缓存并刷新页面可以让脚本再次执行今天已经执行过的任务。</mli></mul>',
add_like_button:
'在直播画面上方,分享按钮左侧添加一个点赞按钮。<mul><mli>该按钮被按下后只会触发一次点赞事件(可用来完成点赞任务),不会发送点赞弹幕。如果想发送点赞弹幕请使用B站的原生功能。</mli></mul>',
WatchLiveTime: `观看直播时长。单位分钟,必须填写整数。<mul><mli>每观看五分钟可获得100亲密度。如果完成了点赞和发弹幕任务,观看65分钟即可挂满亲密度。请根据自身情况调整观看时间。</mli><mli>具体规则请查阅B站官方公告${linkMsg(
'https://link.bilibili.com/p/eden/news#/newsdetail?id=2886'
)}。</mli></mul>`,
APP_TASK:
'自动完成APP用户任务并领取奖励。<h3>注意:</h3><mul><mli>本功能运行时【自动发弹幕】将延后运行,并且会等待【粉丝勋章打卡弹幕】任务完成后再运行。</mli><mli>初次运行本功能时可能会导致B站变为未登录(不可用)状态,但如果每次运行时都会导致登出,请停用本功能。</mli><mli>本功能的日志显示在日志窗口。</mli></mul>目前脚本支持的任务有:<mul><mli><strong>发5条弹幕领取1电池奖励</strong><br>如果本功能运行时任务还未完成,会自动在直播间22474988发弹幕来完成任务并领取奖励。弹幕内容会从【粉丝勋章打卡弹幕】配置的弹幕列表里抽取,若数量不够则使用“打卡+数字”作为弹幕内容。</mli></mul>',
};
const openMainWindow = () => {
let settingTableoffset = $('.live-player-mounter').offset(),
settingTableHeight = $('.live-player-mounter').height();
mainIndex = myopen({
type: 1,
title: false,
offset: [String(settingTableoffset.top - getScrollPosition().y) + 'px', String(settingTableoffset.left - getScrollPosition().x) + 'px'],
closeBtn: 0,
shade: 0,
zIndex: 1000,
fixed: false,
area: [, String(settingTableHeight) + 'px'], // 宽高
resize: false,
content: html,
success: () => {
// layer窗口中的总div
let myDiv = $('#allsettings');
// 整个layer窗口
layerUiMain = myDiv.parent().parent();
// 显示输入框的值
myDiv.find('div[data-toggle="WatchLiveTime"] .num').val(MY_API.CONFIG.WatchLiveTime);
myDiv.find('div[data-toggle="WatchLiveInterval"] .num').val(MY_API.CONFIG.WatchLiveInterval);
myDiv.find('div[data-toggle="LIKE_LIVEROOM_INTERVAL"] .num').val(MY_API.CONFIG.LIKE_LIVEROOM_INTERVAL);
myDiv.find('div[data-toggle="AUTO_CHECK_DANMU_TIMEOUT"] .num').val(MY_API.CONFIG.AUTO_CHECK_DANMU_TIMEOUT);
myDiv.find('div[data-toggle="DANMU_MODIFY_SIZE"] .str').val(MY_API.CONFIG.DANMU_MODIFY_SIZE.toString());
myDiv.find('div[data-toggle="DANMU_MODIFY_COLOR"] .str').val(MY_API.CONFIG.DANMU_MODIFY_COLOR.toString());
myDiv.find('div[data-toggle="DANMU_MODIFY_POOL"] .str').val(MY_API.CONFIG.DANMU_MODIFY_POOL.toString());
myDiv.find('div[data-toggle="DANMU_MODIFY_REGEX"] .str').val(MY_API.CONFIG.DANMU_MODIFY_REGEX.toString());
myDiv.find('div[data-toggle="DANMU_MODIFY_UID"] .str').val(MY_API.CONFIG.DANMU_MODIFY_UID.toString());
myDiv.find('div[data-toggle="GIFT_ALLOW_TYPE"] .str').val(MY_API.CONFIG.GIFT_ALLOW_TYPE.toString());
myDiv.find('div[data-toggle="COIN2SILVER"] .coin_number').val(parseInt(MY_API.CONFIG.COIN2SILVER_NUM).toString());
myDiv.find('div[data-toggle="MEDAL_DANMU_INTERVAL"] .num').val(parseFloat(MY_API.CONFIG.MEDAL_DANMU_INTERVAL).toString());
myDiv.find('div[data-toggle="AUTO_DANMU_SETTINGS"] .Time').val(MY_API.CONFIG.DANMU_INTERVAL_TIME.toString());
myDiv.find('div[data-toggle="AUTO_DANMU_SETTINGS"] .Roomid').val(MY_API.CONFIG.DANMU_ROOMID.toString());
myDiv.find('div[data-toggle="AUTO_DANMU_SETTINGS"] .Danmu').val(MY_API.CONFIG.DANMU_CONTENT.toString());
myDiv.find('div[data-toggle="GIFT_INTERVAL"] .num').val(parseInt(MY_API.CONFIG.GIFT_INTERVAL).toString());
myDiv.find('div[data-toggle="SPARE_GIFT_ROOM"] .num').val(MY_API.CONFIG.SPARE_GIFT_ROOM.toString());
myDiv.find('div[data-toggle="TIME_RELOAD"] .delay-seconds').val(parseInt(MY_API.CONFIG.TIME_RELOAD_MINUTE).toString());
myDiv.find('div[data-toggle="COIN"] .coin_number').val(parseInt(MY_API.CONFIG.COIN_NUMBER).toString());
myDiv.find('div[data-toggle="COIN_UID"] .num').val(MY_API.CONFIG.COIN_UID.toString());
myDiv.find('div[data-toggle="AUTO_GIFT_ROOMID"] .num').val(MY_API.CONFIG.AUTO_GIFT_ROOMID.toString());
myDiv.find('div[data-toggle="GIFT_SEND_TIME"] .Hour').val(MY_API.CONFIG.GIFT_SEND_HOUR.toString());
myDiv.find('div[data-toggle="GIFT_SEND_TIME"] .Minute').val(MY_API.CONFIG.GIFT_SEND_MINUTE.toString());
myDiv.find('div[data-toggle="GIFT_LIMIT"] .num').val(parseInt(MY_API.CONFIG.GIFT_LIMIT).toString());
myDiv.find('div[data-toggle="BUY_MEDAL"] .num').val(Live_info.room_id);
// 监听导入文件按钮
const inputConfig = $('#BLTH_config_file');
inputConfig.on('change', importConfig);
// 禁止选中
myDiv[0].onselectstart = function () {
return false;
};
// 输入后自动保存
myDiv.find('.blth_input').bind(
'input',
debounce(() => saveAction(myDiv), 1000)
);
myDiv.find('button[data-action="exportConfig"]').click(() => {
// 导出配置按钮
exportConfig(MY_API.CONFIG, SP_CONFIG);
mymsg('配置已导出', {
time: 2500,
});
});
myDiv.find('button[data-action="importConfig"]').click(() => {
// 导入配置按钮
readConfigArray[1] = $.Deferred();
inputConfig.click();
readConfigArray[1].then(() => {
let json = readConfigArray[0];
MYDEBUG('readConfigArray 文件读取结果:', readConfigArray[0]);
MY_API.CONFIG = json.MY_API_CONFIG;
MY_API.saveConfig(false);
SP_CONFIG = json.SP_CONFIG;
saveSpConfig();
mymsg('配置导入成功,3秒后将自动刷新页面', {
time: 3000,
icon: 1,
});
setTimeout(() => {
W.location.reload();
}, 3000);
});
});
myDiv.find('div[data-toggle="BUY_MEDAL"] [data-action="buy_medal"]').click(function () {
// 购买勋章
const room_id = parseInt(myDiv.find('div[data-toggle="BUY_MEDAL"] .num').val());
MY_API.buyFanMedal(room_id);
});
myDiv.find('button[data-action="reset"]').click(() => {
// 重置按钮
const index = myconfirm(
`<div style = "text-align:center">是否重置所有设置及缓存为默认?</div>`,
{
title: '重置所有为默认',
btn: ['是', '否'],
},
function () {
layer.close(index);
MY_API.setDefaults();
},
function () {
mymsg('已取消', { time: 2000 });
}
);
});
myDiv.find('button[data-action="resetCache"]').click(() => {
// 重置缓存 CACHE
const index = myconfirm(
`<div style = "text-align:center">是否重置缓存为默认?</div>`,
{
title: '重置缓存',
btn: ['是', '否'],
},
function () {
layer.close(index);
MY_API.resetCache();
},
function () {
mymsg('已取消', { time: 2000 });
}
);
});
myDiv.find('button[data-action="resetMainSiteTasksCache"]').click(() => {
// 重置主站任务缓存
const index = myconfirm(
`<div style = "text-align:center">是否重置主站每日任务缓存为默认?</div>`,
{
title: '重置缓存',
btn: ['是', '否'],
},
function () {
layer.close(index);
MY_API.resetMainSiteTasksCache();
},
function () {
mymsg('已取消', { time: 2000 });
}
);
});
myDiv.find('button[data-action="resetLiveTasksCache"]').click(() => {
// 重置直播区任务缓存
const index = myconfirm(
`<div style = "text-align:center">是否重置直播区任务缓存为默认?</div>`,
{
title: '重置缓存',
btn: ['是', '否'],
},
function () {
layer.close(index);
MY_API.resetLiveTasksCache();
},
function () {
mymsg('已取消', { time: 2000 });
}
);
});
myDiv.find('button[data-action="resetOtherTasksCache"]').click(() => {
// 重置主站任务缓存
const index = myconfirm(
`<div style = "text-align:center">是否重置其它任务缓存为默认?</div>`,
{
title: '重置缓存',
btn: ['是', '否'],
},
function () {
layer.close(index);
MY_API.resetOtherTasksCache();
},
function () {
mymsg('已取消', { time: 2000 });
}
);
});
myDiv.find('button[data-action="about"]').click(() => {
// 关于
layerOpenAbout();
});
myDiv.find('button[data-action="edit_liveTasksRoomList"]').click(() => {
// 编辑直播区任务房间列表
myprompt(
{
formType: 2,
value: String(MY_API.CONFIG.LIVE_TASKS_ROOM),
maxlength: Number.MAX_SAFE_INTEGER,
title: '请输入直播区任务房间列表',
btn: ['保存', '取消'],
},
function (value, index) {
let valArray = value.split(',');
valArray = [...new Set(valArray)];
for (let i = 0; i < valArray.length; i++) {
if (!valArray[i]) valArray.splice(i, 1);
}
MY_API.CONFIG.LIVE_TASKS_ROOM = [...valArray];
MY_API.saveConfig(false);
mymsg('直播区任务房间列表保存成功', {
time: 2500,
icon: 1,
});
layer.close(index);
}
);
});
myDiv.find('button[data-action="edit_GIFT_SEND_ROOM"]').click(() => {
// 编辑自动送礼黑白名单策略
myprompt(
{
formType: 2,
value: String(MY_API.CONFIG.GIFT_SEND_ROOM),
maxlength: Number.MAX_SAFE_INTEGER,
title: '请输入自动送礼房间列表',
btn: ['保存', '取消'],
},
function (value, index) {
let valArray = value.split(',');
valArray = [...new Set(valArray)];
for (let i = 0; i < valArray.length; i++) {
if (!valArray[i]) valArray.splice(i, 1);
}
MY_API.CONFIG.GIFT_SEND_ROOM = [...valArray];
MY_API.saveConfig(false);
mymsg('自动送礼房间列表保存成功', {
time: 2500,
icon: 1,
});
layer.close(index);
}
);
});
myDiv.find('button[data-action="edit_medalDanmu"]').click(() => {
// 编辑打卡弹幕内容
myprompt(
{
formType: 2,
value: String(MY_API.CONFIG.MEDAL_DANMU_CONTENT),
maxlength: Number.MAX_SAFE_INTEGER,
title: '请输入粉丝勋章打卡弹幕',
btn: ['保存', '取消'],
},
function (value, index) {
let valArray = value.split(',');
for (let i = 0; i < valArray.length; i++) {
if (!valArray[i]) valArray.splice(i, 1);
}
MY_API.CONFIG.MEDAL_DANMU_CONTENT = [...valArray];
MY_API.saveConfig(false);
mymsg('粉丝勋章打卡弹幕保存成功', {
time: 2500,
icon: 1,
});
layer.close(index);
}
);
});
myDiv.find('button[data-action="sendGiftNow"]').click(() => {
// 立刻开始送礼
if (!MY_API.CONFIG.AUTO_GIFT) {
window.toast('[ 立刻开始送礼 ] 请先勾选【自动送礼】再点击此按钮', 'info');
return;
}
SEND_GIFT_NOW = true;
MY_API.Gift.run();
});
myDiv.find('button[data-action="sendDanmuNow"]').click(() => {
// 立刻发送弹幕
if (!MY_API.CONFIG.AUTO_DANMU) {
window.toast('[ 立刻发送弹幕 ] 请先勾选【自动发弹幕】再点击此按钮', 'info');
return;
}
SEND_DANMU_NOW = true;
MY_API.AUTO_DANMU.run();
});
myDiv.find('button[data-action="clearDanmuCache"]').click(() => {
// 清除弹幕缓存
MY_API.CACHE.AUTO_SEND_DANMU_TS = [];
if (MY_API.saveCache()) window.toast('清除弹幕缓存成功', 'success');
});
// 绑定所有checkbox事件
for (const i of checkList) {
const input = myDiv.find(`div[data-toggle="${i}"] input:checkbox`);
if (MY_API.CONFIG[i]) input.prop('checked', true);
input.change(function () {
MY_API.CONFIG[i] = $(this).prop('checked');
input.each(function () {
this.checked = MY_API.CONFIG[i];
});
MY_API.saveConfig();
});
}
// 绑定特殊设置(不在MY_API.CONFIG中)
const specialSetting = [
{
jqPath1: `div[data-toggle="INVISIBLE_ENTER"] input:checkbox`,
gmItem: `invisibleEnter`,
toastMsg: ['[隐身入场] 配置已保存', 'info'],
},
{
jqPath1: `div[data-toggle="NOSLEEP"] input:checkbox`,
gmItem: `nosleep`,
toastMsg: ['[屏蔽挂机检测] 配置已保存', 'info'],
},
{
jqPath1: `div[data-toggle="banP2p"] input:checkbox`,
gmItem: `banP2p`,
toastMsg: ['[禁止p2p上传] 配置已保存', 'info'],
},
{
jqPath1: `div[data-toggle="debugSwitch"] input:checkbox`,
gmItem: `debugSwitch`,
toastMsg: ['[控制台日志] 配置已保存', 'info'],
changeFn: function (self) {
SP_CONFIG.debugSwitch = $(self).prop('checked');
},
},
{
jqPath1: `div[data-toggle="windowToast"] input:checkbox`,
gmItem: `windowToast`,
// toastMsg: ["[提示信息] 配置已保存", "info"],
changeFn: function (self) {
SP_CONFIG.windowToast = $(self).prop('checked');
if (SP_CONFIG.windowToast) $('.link-toast').show();
else $('.link-toast').hide();
},
},
{
jqPath1: `div[data-toggle="DANMU_MODIFY"] input:checkbox`,
gmItem: `DANMU_MODIFY`,
toastMsg: ['[弹幕修改] 配置已保存', 'info'],
},
{
jqPath1: `div[data-toggle="blockLiveStream"] input:checkbox`,
gmItem: `blockLiveStream`,
toastMsg: ['[拦截直播流] 配置已保存', 'info'],
},
{
jqPath1: `div[data-toggle="blockliveDataUpdate"] input:checkbox`,
gmItem: `blockliveDataUpdate`,
toastMsg: ['[拦截直播观看数据上报] 配置已保存', 'info'],
},
{
jqPath1: `div[data-toggle="WEAR_MEDAL_BEFORE_DANMU"] input:checkbox`,
gmItem: `wear_medal_before_danmu`,
toastMsg: ['[自动佩戴勋章] 配置已保存', 'info'],
},
{
jqPath1: `div[data-toggle="ONLY_FIRST"] input:radio`,
jqPath2: `div[data-toggle="ALWAYS"] input:radio`,
changeFn: function (self, gmItem) {
if ($(self).is(':checked')) SP_CONFIG[gmItem] = $(self).parent().attr('data-toggle');
},
name: 'WEAR_MEDAL_BEFORE_DANMU',
gmItem: `wear_medal_type`,
toastMsg: ['[自动佩戴勋章] 配置已保存', 'info'],
},
{
jqPath1: `div[data-toggle="AUTO_CHECK_DANMU"] input:checkbox`,
gmItem: `AUTO_CHECK_DANMU`,
toastMsg: ['[检测弹幕是否发送成功] 配置已保存', 'info'],
},
{
jqPath1: `div[data-toggle="add_like_button"] input:checkbox`,
gmItem: `add_like_button`,
toastMsg: ['[添加点赞按钮] 配置已保存', 'info'],
},
{
jqPath1: `div[data-toggle="AUTO_MAX_QUALITY"] input:checkbox`,
gmItem: `auto_max_quality`,
toastMsg: ['[自动切换最高清晰度] 配置已保存', 'info'],
},
];
for (const i of specialSetting) {
let input,
isradio = i.hasOwnProperty('name') ? true : false;
for (let count = 1; true; count++) {
const jqPathNum = 'jqPath' + String(count);
if (!i.hasOwnProperty(jqPathNum)) break;
input = myDiv.find(i[jqPathNum]);
const setting = SP_CONFIG[i.gmItem];
if (!isradio) {
if (setting) input.attr('checked', '');
} else {
if (setting === i[jqPathNum].match(/data\-toggle="(.*)"/)[1]) {
$(i[jqPathNum]).attr('checked', '');
break;
}
}
}
if (isradio) input = $(`input:radio[name= ${i.name} ]`);
input.change(function () {
let self = this;
if (i.hasOwnProperty('changeFn')) isradio ? i.changeFn(self, i.gmItem) : i.changeFn(self);
if (!isradio) SP_CONFIG[i.gmItem] = $(self).prop('checked');
saveSpConfig();
if (i.hasOwnProperty('toastMsg')) window.toast(i.toastMsg[0], i.toastMsg[1]);
});
}
// 绑定多选框事件
for (const i of radioList) {
for (let count = 1; true; count++) {
const toggleName = 'toggle' + String(count);
if (!i.hasOwnProperty(toggleName)) break;
if (MY_API.CONFIG[i.name] === i[toggleName]) {
$(`div[data-toggle= ${i[toggleName]}] input:radio`).attr('checked', '');
break;
}
}
$(`input:radio[name= ${i.name} ]`).change(function () {
for (let count = 1; true; count++) {
const toggleName = 'toggle' + String(count);
if (!i.hasOwnProperty(toggleName)) break;
if ($(`div[data-toggle= ${i[toggleName]} ] input:radio`).is(':checked')) {
MY_API.CONFIG[i.name] = i[toggleName];
MY_API.saveConfig();
break;
}
}
});
}
// 绑定帮助文字 (?)
$('.helpText').click(function () {
const id = $(this).attr('helpdata');
if (id !== undefined) {
if (helpText.hasOwnProperty(id)) {
myopen({
title: `帮助信息 ${id}`,
anim: 5,
area: [String($(window).width() * 0.382) + 'px', String($(window).height() * 0.618) + 'px'],
content: helpText[id],
});
}
}
});
// 允许按钮点击
hideBtnClickable = true;
},
end: () => {
// 理论上此处代码不会运行,因为窗口保持常开不关闭
SP_CONFIG.mainDisplay = 'hide';
saveSpConfig();
document.getElementById('hiderbtn').innerHTML = '显示控制面板';
},
});
};
// 打开窗口
openMainWindow();
let JQshow = false;
if (SP_CONFIG.mainDisplay === 'hide') {
layerUiMain.hide();
JQshow = true;
}
if (SP_CONFIG.darkMode) {
layer.style(mainIndex, {
'background-color': '#1c1c1c',
color: '#a2a7ae',
});
} else {
layer.style(mainIndex, {
'background-color': 'white',
color: 'black',
});
}
// 添加隐藏/显示窗口按钮
let followCntr = $('.follow-ctnr');
followCntr[0].insertBefore(btn[0], followCntr.children()[0]);
// 监听隐藏/显示窗口按钮
function btnClickFunc() {
if (hideBtnClickable) {
hideBtnClickable = false;
setTimeout(function () {
hideBtnClickable = true;
}, 310);
if (SP_CONFIG.mainDisplay === 'show') {
// 显示 -> 隐藏
SP_CONFIG.mainDisplay = 'hide';
saveSpConfig(false);
animChange(layerUiMain, true);
document.getElementById('hiderbtn').innerHTML = '显示控制面板';
setTimeout(() => layer.style(mainIndex, { zIndex: 0 }), 300);
} else {
// 隐藏 -> 显示
SP_CONFIG.mainDisplay = 'show';
layer.style(mainIndex, { zIndex: 1000 });
saveSpConfig(false);
if (JQshow) {
layerUiMain.show();
JQshow = false;
} else animChange(layerUiMain, false);
document.getElementById('hiderbtn').innerHTML = '隐藏控制面板';
}
}
}
btn.click(btnClickFunc);
// 绑定快捷键
hotkeys('alt+b', btnClickFunc);
// 监听播放器全屏变化
function bodyPropertyChange() {
let attr = body.attr('class'),
tabOffSet = tabContent.offset(),
top = tabOffSet.top,
left = tabOffSet.left;
if (/(player\-full\-win)|(fullscreen\-fix)/.test(attr)) {
if (SP_CONFIG.mainDisplay === 'show') {
// 显示 -> 隐藏
SP_CONFIG.mainDisplay = 'hide';
saveSpConfig(false);
animChange(layerUiMain, true);
document.getElementById('hiderbtn').innerHTML = '显示控制面板';
setTimeout(() => layer.style(mainIndex, { zIndex: 0 }), 300);
}
}
layer.style(logIndex, {
top: String(top) + 'px',
left: String(left) + 'px',
});
}
let bodyMutationObserver = new MutationObserver(bodyPropertyChange);
bodyMutationObserver.observe(body[0], { attributes: true });
// 监听页面html节点属性变化
function webHtmlPropertyChange() {
let attr = webHtml.attr('lab-style');
if (attr === 'dark') {
SP_CONFIG.darkMode = true;
layer.style(logIndex, {
'background-color': '#1c1c1c',
});
layer.style(mainIndex, {
'background-color': '#1c1c1c',
color: '#a2a7ae',
});
} else {
SP_CONFIG.darkMode = false;
layer.style(logIndex, {
'background-color': '#f2f3f5',
});
layer.style(mainIndex, {
'background-color': 'white',
color: 'black',
});
}
}
let webHtmlMutationObserver = new MutationObserver(webHtmlPropertyChange);
webHtmlMutationObserver.observe(webHtml[0], { attributes: true });
// 初次运行时tips
if (!MY_API.CACHE.MainSite_login_TS) {
mytips('点我隐藏/显示控制面板', '#hiderbtn', {
tips: 1,
});
setTimeout(() => mytips('点我查看日志', '#logDivText'), 5e3);
}
},
chatLog: function (text, _type = 'info') {
// 自定义提示
let div = $("<div class='chatLogDiv'>"),
msg = $("<div class='chatLogMsg'>"),
myDate = new Date();
msg.html(text);
div.text(myDate.toLocaleString());
div.append(msg);
switch (_type) {
case 'warning':
div.addClass('chatLogWarning');
break;
case 'success':
div.addClass('chatLogSuccess');
break;
case 'error':
div.addClass('chatLogError');
break;
case 'prize':
div.addClass('chatLogWinPrize');
break;
default:
div.addClass('chatLogDefault');
}
JQmenuWindow.append(div);
if (layerLogWindow_ScrollY >= layerLogWindow_ScrollHeight) layerLogWindow.scrollTop(layerLogWindow.prop('scrollHeight'));
MYDEBUG('chatLog', text);
},
GroupSign: {
fullLevalMedalUidList: [],
getGroups: () => {
//获取应援团列表
return BAPI.Group.my_groups().then((response) => {
MYDEBUG('GroupSign.getGroups: API.Group.my_groups', response);
if (response.code === 0) return $.Deferred().resolve(response.data.list);
else {
window.toast(`[自动应援团签到]获取应援团列表失败 ${response.msg}`, 'error');
return delayCall(() => MY_API.GroupSign.getGroups());
}
});
},
signInList: (list, i = 0) => {
// 应援团签到
if (i >= list.length) return $.Deferred().resolve();
const obj = list[i];
// 自己不能给自己的应援团应援,不给20或40级粉丝牌的应援团签到
if (obj.owner_uid == Live_info.uid || MY_API.GroupSign.fullLevalMedalUidList == Live_info.uid) return MY_API.GroupSign.signInList(list, i + 1);
return BAPI.Group.sign_in(obj.group_id, obj.owner_uid).then((response) => {
MYDEBUG('GroupSign.signInList: API.Group.sign_in', response);
let p = $.Deferred();
if (response.code === 0) {
if (response.data.add_num > 0) {
// || response.data.status === 1
MYDEBUG(`[自动应援团签到] 应援团(group_id=${obj.group_id},owner_uid=${obj.owner_uid})签到成功,当前勋章亲密度+${response.data.add_num}`);
p.resolve();
} else if (response.data.add_num == 0) {
window.toast(`[自动应援团签到]应援团(group_id=${obj.group_id},owner_uid=${obj.owner_uid})已签到`, 'caution');
p.resolve();
} else {
p.reject();
}
} else {
window.toast(`[自动应援团签到] 应援团(group_id=${obj.group_id},owner_uid=${obj.owner_uid})签到失败 ${response.msg}`, 'caution');
p.reject();
return delayCall(() => MY_API.GroupSign.signInList(list, i));
}
return $.when(MY_API.GroupSign.signInList(list, i + 1), p);
});
},
run: () => {
// 执行应援团任务
try {
if (!MY_API.CONFIG.AUTO_GROUP_SIGN || otherScriptsRunning) return $.Deferred().resolve();
if (!checkNewDay(MY_API.CACHE.AUTO_GROUP_SIGH_TS)) {
runTomorrow(() => MY_API.GroupSign.run(), 8, 1, '应援团签到');
return $.Deferred().resolve();
} else if (getCHSdate().getHours() < 8 && MY_API.CACHE.AUTO_GROUP_SIGH_TS !== 0) {
runToday(() => MY_API.GroupSign.run(), 8, 1, '应援团签到');
return $.Deferred().resolve();
}
window.toast(`[应援团签到] 开始签到`, 'info');
return MY_API.GroupSign.getGroups().then((list) => {
for (const i of medal_info.medal_list) {
if (i.medal_level === 20 || i.medal_level === 40) MY_API.GroupSign.fullLevalMedalUidList.push(i.target_id);
}
return MY_API.GroupSign.signInList(list).then(() => {
window.toast(`[应援团签到] 今日签到已完成`, 'success');
MY_API.CACHE.AUTO_GROUP_SIGH_TS = ts_ms();
MY_API.saveCache();
runTomorrow(() => MY_API.GroupSign.run(), 8, 1, '应援团签到');
return $.Deferred().resolve();
});
});
} catch (err) {
window.toast('[自动应援团签到]运行时出现异常,已停止', 'error');
MYERROR(`自动应援团签到出错`, err);
return $.Deferred().reject();
}
},
},
DailyReward: {
// 每日任务
coin_exp: 0,
login: () => {
if (!MY_API.CONFIG.LOGIN) return $.Deferred().resolve();
if (!checkNewDay(MY_API.CACHE.MainSite_login_TS)) {
runMidnight(() => MY_API.DailyReward.login(), '主站任务 - 登录(不可用)');
return $.Deferred().resolve();
}
return BAPI.DailyReward.login().then((response) => {
MYDEBUG('DailyReward.login: API.DailyReward.login');
if (response.code === 0) {
window.toast('[自动每日奖励][每日登录(不可用)]完成', 'success');
MY_API.CACHE.MainSite_login_TS = ts_ms();
MY_API.saveCache();
runMidnight(() => MY_API.DailyReward.login(), '主站任务 - 登录(不可用)');
} else {
window.toast(`[自动每日奖励][每日登录(不可用)]失败 ${response.message}`, 'error');
return delayCall(() => MY_API.DailyReward.login());
}
});
},
watch: (aid, cid) => {
if (!MY_API.CONFIG.WATCH) return $.Deferred().resolve();
if (!checkNewDay(MY_API.CACHE.MainSite_watch_TS)) {
return $.Deferred().resolve();
}
return BAPI.DailyReward.watch(aid, cid, Live_info.uid, ts_s()).then((response) => {
MYDEBUG('DailyReward.watch: API.DailyReward.watch', response);
if (response.code === 0) {
window.toast(`[自动每日奖励][每日观看]完成(av=${aid})`, 'success');
MY_API.CACHE.MainSite_watch_TS = ts_ms();
MY_API.saveCache();
} else {
window.toast(`[自动每日奖励][每日观看]失败 aid=${aid}, cid=${cid} ${response.msg}`, 'error');
return delayCall(() => MY_API.DailyReward.watch(aid, cid));
}
});
},
coin: (cards, n, i = 0, one = false) => {
if (!MY_API.CONFIG.COIN) return $.Deferred().resolve();
if (!checkNewDay(MY_API.CACHE.MainSite_coin_TS)) {
return $.Deferred().resolve();
}
if (MY_API.DailyReward.coin_exp >= MY_API.CONFIG.COIN_NUMBER * 10) {
window.toast('[自动每日奖励][每日投币]今日投币已完成', 'info');
MY_API.CACHE.MainSite_coin_TS = ts_ms();
MY_API.saveCache();
return $.Deferred().resolve();
}
if (i >= cards.length) {
window.toast('[自动每日奖励][每日投币]动态里可投币的视频不足', 'caution');
return $.Deferred().resolve();
}
const obj = JSON.parse(cards[i].card);
let num = Math.min(2, n);
if (one) num = 1;
return BAPI.x.getCoinInfo('', 'jsonp', obj.aid, ts_ms()).then((re) => {
MYDEBUG(`API.x.getCoinInfo aid = ${obj.aid}`, re);
if (re.code === 0) {
let p = $.Deferred();
setTimeout(() => p.resolve(), 500);
return p.then(() => {
if (re.data.multiply === 2) {
MYDEBUG('API.x.getCoinInfo', `已投币两个 aid = ${obj.aid}`);
return MY_API.DailyReward.coin(vlist, n, i + 1);
} else {
if (re.data.multiply === 1) num = 1;
return BAPI.DailyReward.coin(obj.aid, num).then((response) => {
MYDEBUG('DailyReward.coin: API.DailyReward.coin', response);
if (response.code === 0) {
MY_API.DailyReward.coin_exp += num * 10;
window.toast(`[自动每日奖励][每日投币]投币成功(av=${obj.aid},num=${num})`, 'success');
return MY_API.DailyReward.coin(cards, n - num, i + 1);
} else if (response.code === -110) {
window.toast('[自动每日奖励][每日投币]未绑定手机,已停止', 'error');
return $.Deferred().reject();
} else if (response.code === 34003) {
// 非法的投币数量
if (one) return MY_API.DailyReward.coin(cards, n, i + 1);
return MY_API.DailyReward.coin(cards, n, i, true);
} else if (response.code === 34005) {
// 塞满啦!先看看库存吧~
return MY_API.DailyReward.coin(cards, n, i + 1);
} else if (response.code === -104) {
//硬币余额不足
window.toast('[自动每日奖励][每日投币]剩余硬币不足,已停止', 'warning');
return $.Deferred().reject();
}
window.toast(`[自动每日奖励][每日投币]出错 ${response.msg}`, 'error');
return delayCall(() => MY_API.DailyReward.coin(cards, n, i));
});
}
});
} else {
window.toast(`[自动每日奖励][每日投币]获取视频(aid=${obj.aid})投币状态出错 ${response.msg}`, 'error');
return delayCall(() => MY_API.DailyReward.coin(cards, n, i));
}
});
},
coin_uid: (vlist, n, pagenum, uidIndex, i = 0, one = false) => {
if (!MY_API.CONFIG.COIN) return $.Deferred().resolve();
if (MY_API.DailyReward.coin_exp >= MY_API.CONFIG.COIN_NUMBER * 10) {
window.toast('[自动每日奖励][每日投币]今日投币已完成', 'info');
MY_API.CACHE.MainSite_coin_TS = ts_ms();
MY_API.saveCache();
return $.Deferred().resolve();
}
if (i >= vlist.length) {
return MY_API.DailyReward.UserSpace(uidIndex, 30, 0, pagenum + 1, '', 'pubdate', 'jsonp');
}
const obj = vlist[i],
uid = MY_API.CONFIG.COIN_UID[uidIndex];
if (obj.hasOwnProperty('is_union_video') && obj.is_union_video === 1 && obj.mid != uid) {
MYDEBUG('DailyReward.coin_uid', `联合投稿且UP不是指定UID用户 aid = ${obj.aid}`);
return MY_API.DailyReward.coin_uid(vlist, n, pagenum, uidIndex, i + 1);
}
let num = Math.min(2, n);
if (one) num = 1;
return BAPI.x.getCoinInfo('', 'jsonp', obj.aid, ts_ms()).then((re) => {
if (re.code === 0) {
let p = $.Deferred();
setTimeout(() => p.resolve(), 500);
return p.then(() => {
if (re.data.multiply === 2) {
MYDEBUG('API.x.getCoinInfo', `已投币两个 aid = ${obj.aid}`);
return MY_API.DailyReward.coin_uid(vlist, n, pagenum, uidIndex, i + 1);
} else {
if (re.data.multiply === 1) num = 1;
return BAPI.DailyReward.coin(obj.aid, num).then((response) => {
MYDEBUG('DailyReward.coin_uid: API.DailyReward.coin_uid', response);
if (response.code === 0) {
MY_API.DailyReward.coin_exp += num * 10;
window.toast(`[自动每日奖励][每日投币]投币成功(av=${obj.aid},num=${num})`, 'success');
return MY_API.DailyReward.coin_uid(vlist, n - num, pagenum, uidIndex, i + 1);
} else if (response.code === -110) {
window.toast('[自动每日奖励][每日投币]未绑定手机,已停止', 'error');
return $.Deferred().reject();
} else if (response.code === 34003) {
// 非法的投币数量
if (one) return MY_API.DailyReward.coin_uid(vlist, n, pagenum, uidIndex, i + 1);
return MY_API.DailyReward.coin_uid(vlist, n, i, pagenum, uidIndex, true);
} else if (response.code === 34005) {
// 塞满啦!先看看库存吧~
return MY_API.DailyReward.coin_uid(vlist, n, pagenum, uidIndex, i + 1);
} else if (response.code === -104) {
// 硬币余额不足
window.toast('[自动每日奖励][每日投币]剩余硬币不足,已停止', 'warning');
return $.Deferred().reject();
}
window.toast(`[自动每日奖励][每日投币] 出错 ${response.msg}`, 'caution');
return delayCall(() => MY_API.DailyReward.coin_uid(vlist, n, pagenum, uidIndex, i));
});
}
});
} else {
window.toast(`[自动每日奖励][每日投币]获取视频(aid=${obj.aid})投币状态出错 ${response.msg}`, 'error');
return delayCall(() => MY_API.DailyReward.coin(cards, n, i));
}
});
},
share: (aid) => {
if (!MY_API.CONFIG.SHARE) return $.Deferred().resolve();
if (!checkNewDay(MY_API.CACHE.MainSite_share_TS)) {
return $.Deferred().resolve();
}
return BAPI.DailyReward.share(aid).then((response) => {
MYDEBUG('DailyReward.share: API.DailyReward.share', response);
if (response.code === 0) {
window.toast(`[自动每日奖励][每日分享]分享成功(av=${aid})`, 'success');
} else if (response.code === 71000) {
// 重复分享
window.toast('[自动每日奖励][每日分享]今日分享已完成', 'info');
} else {
window.toast(`[自动每日奖励][每日分享] 出错 ${response.msg}`, 'caution');
return delayCall(() => MY_API.DailyReward.share(aid));
}
MY_API.CACHE.MainSite_share_TS = ts_ms();
MY_API.saveCache();
});
},
dynamic: async () => {
if (!MY_API.CONFIG.COIN && !MY_API.CONFIG.WATCH && !MY_API.CONFIG.SHARE) return $.Deferred().resolve();
if (
(!MY_API.CONFIG.WATCH || (MY_API.CONFIG.WATCH && !checkNewDay(MY_API.CACHE.MainSite_watch_TS))) &&
(!MY_API.CONFIG.SHARE || (MY_API.CONFIG.SHARE && !checkNewDay(MY_API.CACHE.MainSite_share_TS))) &&
(!MY_API.CONFIG.COIN || (MY_API.CONFIG.COIN && !checkNewDay(MY_API.CACHE.MainSite_coin_TS)))
)
return runMidnight(() => MY_API.DailyReward.dynamic(), `主站任务 - ${MY_API.CONFIG.WATCH ? '观看视频' : ''} ${MY_API.CONFIG.SHARE ? '分享视频' : ''} ${MY_API.CONFIG.COIN ? '投币' : ''}`);
MY_API.DailyReward.coin_exp = await BAPI.DailyReward.exp().then((response) => {
MYDEBUG('DailyReward.run: API.DailyReward.exp', response);
if (response.code === 0) {
return response.number;
} else {
window.toast(`[自动每日奖励] 获取今日已获得的投币经验出错 ${response.message}`, 'caution');
return delayCall(() => MY_API.DailyReward.run());
}
});
const coinNum = MY_API.CONFIG.COIN_NUMBER - MY_API.DailyReward.coin_exp / 10;
const throwCoinNum = await BAPI.getuserinfo().then((re) => {
MYDEBUG('DailyReward.dynamic: API.getuserinfo', re);
if (re.code === 'REPONSE_OK') {
if (re.data.biliCoin < coinNum) return re.data.biliCoin;
else return coinNum;
} else {
window.toast(`[自动每日奖励][每日投币] 获取用户信息失败 ${response.message}`, 'error');
}
});
if (throwCoinNum < coinNum) window.toast(`[自动每日奖励][每日投币]剩余硬币不足,仅能投${throwCoinNum}个币`, 'warning');
return BAPI.dynamic_svr.dynamic_new(Live_info.uid, 8).then((response) => {
MYDEBUG('DailyReward.dynamic: API.dynamic_svr.dynamic_new', response);
if (response.code === 0) {
if (response.data.cards) {
const obj = JSON.parse(response.data.cards[0].card);
const p1 = MY_API.DailyReward.watch(obj.aid, obj.cid);
let p2;
if (MY_API.CONFIG.COIN_UID == 0 || MY_API.CONFIG.COIN_TYPE == 'COIN_DYN') {
p2 = MY_API.DailyReward.coin(response.data.cards, Math.max(throwCoinNum, 0));
} else {
p2 = MY_API.DailyReward.UserSpace(0, 30, 0, 1, '', 'pubdate', 'jsonp');
}
const p3 = MY_API.DailyReward.share(obj.aid);
return $.when(p1, p2, p3).then(() =>
runMidnight(() => MY_API.DailyReward.dynamic(), `主站任务 - ${MY_API.CONFIG.WATCH ? '观看视频' : ''} ${MY_API.CONFIG.SHARE ? '分享视频' : ''} ${MY_API.CONFIG.COIN ? '投币' : ''}`)
);
} else {
window.toast('[自动每日奖励]"动态-投稿视频"中暂无动态', 'info');
}
} else {
window.toast(`[自动每日奖励]获取"动态-投稿视频"失败 ${response.msg}`, 'caution');
return delayCall(() => MY_API.DailyReward.dynamic());
}
});
},
UserSpace: (uidIndex, ps, tid, pn, keyword, order, jsonp) => {
if (!checkNewDay(MY_API.CACHE.MainSite_coin_TS)) {
return $.Deferred().resolve();
}
return BAPI.x.getUserSpace(MY_API.CONFIG.COIN_UID[uidIndex], ps, tid, pn, keyword, order, jsonp).then((response) => {
MYDEBUG('DailyReward.UserSpace: API.dynamic_svr.UserSpace', response);
if (response.code === 0) {
if (response.data.list.vlist) {
const throwCoinNum = MY_API.CONFIG.COIN_NUMBER - MY_API.DailyReward.coin_exp / 10;
return MY_API.DailyReward.coin_uid(response.data.list.vlist, Math.max(throwCoinNum, 0), pn, uidIndex);
} else if (uidIndex < MY_API.CONFIG.COIN_UID.length - 1) {
const throwCoinNum = MY_API.CONFIG.COIN_NUMBER - MY_API.DailyReward.coin_exp / 10;
return MY_API.DailyReward.coin_uid(response.data.list.vlist, Math.max(throwCoinNum, 0), pn, uidIndex + 1);
} else {
window.toast(`[自动每日奖励]"UID = ${String(MY_API.CONFIG.COIN_UID)}的空间-投稿视频"中暂无视频`, 'info');
}
} else {
window.toast(`[自动每日奖励]获取UID = ${MY_API.CONFIG.COIN_UID[uidIndex]}的"空间-投稿视频"失败 ${response.msg}`, 'caution');
return delayCall(() => MY_API.DailyReward.UserSpace(uid, ps, tid, pn, keyword, order, jsonp));
}
});
},
run: () => {
if ((!MY_API.CONFIG.LOGIN && !MY_API.CONFIG.COIN && !MY_API.CONFIG.WATCH && !MY_API.CONFIG.SHARE) || otherScriptsRunning) return $.Deferred().resolve();
MY_API.DailyReward.login();
MY_API.DailyReward.dynamic();
},
},
LiveReward: {
dailySignIn: () => {
if (!MY_API.CONFIG.LIVE_SIGN) return $.Deferred().resolve();
if (!checkNewDay(MY_API.CACHE.Live_sign_TS)) return runMidnight(() => MY_API.LiveReward.dailySignIn(), '直播区 - 直播签到');
return BAPI.xlive.dosign().then((response) => {
MYDEBUG('LiveReward.dailySignIn: API.xlive.dosign', response);
if (response.code === 0) {
window.toast('[自动直播签到]完成', 'success');
$('.hinter').remove(); // 移除签到按钮和小红点
$('.checkin-btn').remove();
} else if (response.code === 1011040) {
window.toast('[自动直播签到]今日直播签到已完成', 'info');
} else {
window.toast(`[自动直播签到]失败 ${response.message},尝试点击签到按钮`, 'caution');
$('.checkin-btn').click();
return delayCall(() => MY_API.LiveReward.dailySignIn());
}
MY_API.CACHE.Live_sign_TS = ts_ms();
MY_API.saveCache();
runMidnight(() => MY_API.LiveReward.dailySignIn(), '直播区 - 直播签到');
});
},
likeLiveRoom: async (medal_list) => {
if (!MY_API.CONFIG.LIKE_LIVEROOM) return $.Deferred().resolve();
if (!checkNewDay(MY_API.CACHE.Live_like_TS)) return runMidnight(() => MY_API.LiveReward.likeLiveRoom(), '直播区 - 点赞');
const likeTimes = 1;
window.toast('[点赞直播间] 开始点赞直播间', 'info');
for (let i = 0; i < likeTimes; i++) {
for (const medal of medal_list) {
await BAPI.xlive.likeReportV3(medal.real_roomid, medal.target_id).then((response) => {
MYDEBUG(`API.xlive.likeReportV3(${medal.real_roomid}) response`, response);
if (response.code !== 0) window.toast(`[点赞直播间] 直播间${medal.real_roomid}点赞失败 ${response.message}`, 'caution');
});
await sleep(MY_API.CONFIG.LIKE_LIVEROOM_INTERVAL);
}
}
window.toast('[点赞直播间] 今日点赞完成', 'success');
MY_API.CACHE.Live_like_TS = ts_ms();
MY_API.saveCache();
runMidnight(() => MY_API.LiveReward.likeLiveRoom(), '直播区 - 点赞');
},
WatchLive: async (medal_list) => {
if (!MY_API.CONFIG.WatchLive) return $.Deferred().resolve();
if (!checkNewDay(MY_API.CACHE.Live_watch_TS)) return runMidnight(() => MY_API.LiveReward.WatchLive(), '直播区 - 观看直播');
window.toast('[观看直播] 开始模拟观看直播', 'info');
let pReturn = $.Deferred();
for (let f = 0; f < medal_list.length; f++) {
const funsMedalData = medal_list[f];
let roomHeart = new RoomHeart(funsMedalData.real_roomid, MY_API.CONFIG.WatchLiveTime);
await roomHeart.start();
if (f === medal_list.length - 1)
roomHeart.doneFunc = () => {
window.toast('[观看直播] 今日观看任务已完成', 'success');
pReturn.resolve();
};
await sleep(MY_API.CONFIG.WatchLiveInterval);
}
await pReturn;
MY_API.CACHE.Live_watch_TS = ts_ms();
MY_API.saveCache();
runMidnight(() => MY_API.LiveReward.WatchLive(), '直播区 - 观看直播');
},
run: () => {
if ((!MY_API.CONFIG.LIVE_SIGN && !MY_API.CONFIG.WatchLive && !MY_API.CONFIG.LIKE_LIVEROOM) || otherScriptsRunning) return $.Deferred().resolve();
let runTasksMedalList;
if (medal_info.status.state() === 'resolved') {
if (MY_API.CONFIG.LIVE_TASKS_METHOD === 'LIVE_TASKS_WHITE')
runTasksMedalList = medal_info.medal_list.filter((r) => MY_API.CONFIG.LIVE_TASKS_ROOM.findIndex((m) => m == r.roomid) > -1 && r.roomid && r.level < 20);
else {
runTasksMedalList = medal_info.medal_list.filter((r) => MY_API.CONFIG.LIVE_TASKS_ROOM.findIndex((m) => m == r.roomid) === -1 && r.roomid && r.level < 20);
}
} else {
window.toast('[观看直播] [点赞直播间] 粉丝勋章列表未被完全获取,暂停运行', 'error');
return medal_info.status.then(() => {
MY_API.LiveReward.WatchLive();
MY_API.LiveReward.likeLiveRoom();
});
}
MY_API.LiveReward.dailySignIn();
MY_API.LiveReward.WatchLive(runTasksMedalList);
MY_API.LiveReward.likeLiveRoom(runTasksMedalList);
},
},
Exchange: {
coin2silver: (num) => {
return BAPI.xlive.revenue.coin2silver(num).then((response) => {
MYDEBUG('Exchange.coin2silver: API.Exchange.coin2silver', response);
if (response.code === 0) {
window.toast(`[硬币换银瓜子] ${response.message},获得${response.data.silver}银瓜子`, 'success');
} else {
// 其它状态码待补充
window.toast(`[银瓜子换硬币] 失败 ${response.message}`, 'caution');
return delayCall(() => MY_API.Exchange.coin2silver(num));
}
});
},
silver2coin: () => {
return BAPI.xlive.revenue.silver2coin().then((response) => {
MYDEBUG('Exchange.silver2coin: API.Exchange.silver2coin', response);
if (response.code === 0) {
window.toast(`[银瓜子换硬币] ${response.message}`, 'success'); // 兑换成功
} else if (response.code === 403) {
window.toast(`[银瓜子换硬币] ${response.message}`, 'info'); // 每天最多能兑换 1 个 or 银瓜子余额不足
} else {
window.toast(`[银瓜子换硬币] 失败 ${response.message}`, 'caution');
return delayCall(() => MY_API.Exchange.silver2coin());
}
});
},
runC2S: () => {
if (!MY_API.CONFIG.COIN2SILVER || otherScriptsRunning) return $.Deferred().resolve();
if (!checkNewDay(MY_API.CACHE.Coin2Sliver_TS)) {
// 同一天,不再兑换瓜子
runMidnight(() => MY_API.Exchange.runC2S(), '硬币换瓜子');
return $.Deferred().resolve();
}
return MY_API.Exchange.coin2silver(MY_API.CONFIG.COIN2SILVER_NUM).then(
() => {
MY_API.CACHE.Coin2Sliver_TS = ts_ms();
MY_API.saveCache();
runMidnight(() => MY_API.Exchange.runC2S(), '硬币换瓜子');
},
() => delayCall(() => MY_API.Exchange.runC2S())
);
},
runS2C: () => {
try {
if (!MY_API.CONFIG.SILVER2COIN || otherScriptsRunning) return $.Deferred().resolve();
if (!checkNewDay(MY_API.CACHE.Silver2Coin_TS)) {
// 同一天,不再兑换硬币
runMidnight(() => MY_API.Exchange.runS2C(), '瓜子换硬币');
return $.Deferred().resolve();
}
return MY_API.Exchange.silver2coin().then(
() => {
MY_API.CACHE.Silver2Coin_TS = ts_ms();
MY_API.saveCache();
runMidnight(() => MY_API.Exchange.runS2C(), '瓜子换硬币');
},
() => delayCall(() => MY_API.Exchange.runS2C())
);
} catch (err) {
window.toast('[银瓜子换硬币]运行时出现异常,已停止', 'error');
MYERROR(`银瓜子换硬币出错`, err);
return $.Deferred().reject();
}
},
},
Gift: {
run_timer: undefined, // 可用来取消下次运行的计划 clearTimeout(MY_API.Gift.run_timer)
ruid: undefined, // 包裹内礼物的ruid
room_id: undefined, // 送礼目标房间号
medal_list: undefined, // 勋章列表
bag_list: undefined, // 包裹
giftFeed_list: {}, // 每种礼物所对应的亲密度
remain_feed: undefined, // 该勋章今日剩余亲密度
over: undefined, // 是否结束送礼
allowGiftList: undefined, // 允许被送出礼物的id
/**
* 获取礼物包裹
*/
getBagList: async () => {
return BAPI.gift.bag_list().then((response) => {
MYDEBUG('Gift.getBagList: API.gift.bag_list', response);
if (response.code === 0) {
MY_API.Gift.bag_list = response.data.list;
} else {
window.toast(`[自动送礼]获取包裹列表失败,${response.message}`, 'error');
return delayCall(() => MY_API.Gift.getBagList());
}
});
},
/**
* 通过礼物id获取礼物的亲密度
* @param {Number} gift_id 礼物id
*/
getFeedByGiftID: (gift_id) => {
for (let i = Live_info.gift_list.length - 1; i >= 0; --i) {
if (Live_info.gift_list[i].id === gift_id) {
return Math.ceil(Live_info.gift_list[i].price / 100);
}
}
return 0;
},
/**
* 排序粉丝勋章
* @param {Object} medals
*/
sort_medals: (medals) => {
if (MY_API.CONFIG.GIFT_SORT == 'GIFT_SORT_HIGH') {
medals.sort((a, b) => {
if (b.level - a.level == 0) {
return b.intimacy - a.intimacy;
}
return b.level - a.level;
});
} else {
medals.sort((a, b) => {
if (a.level - b.level == 0) {
return a.intimacy - b.intimacy;
}
return a.level - b.level;
});
}
if (MY_API.CONFIG.AUTO_GIFT_ROOMID) {
let sortRooms = [...MY_API.CONFIG.AUTO_GIFT_ROOMID];
sortRooms.reverse();
for (let froom of sortRooms) {
let rindex = medals.findIndex((r) => r.roomid == froom);
if (rindex != -1) {
let tmp = medals[rindex];
medals.splice(rindex, 1);
medals.unshift(tmp);
}
}
}
return medals;
},
run: async (noTimeCheck = false) => {
/**
* 一轮送礼结束后运行的函数
*/
const waitForNextRun = () => {
window.toast('[自动送礼] 本次送礼结束', 'info');
SEND_GIFT_NOW = false;
if (MY_API.CONFIG.GIFT_METHOD == 'GIFT_SEND_TIME') {
let alternateTime = getIntervalTime(MY_API.CONFIG.GIFT_SEND_HOUR, MY_API.CONFIG.GIFT_SEND_MINUTE);
MY_API.Gift.run_timer = setTimeout(() => MY_API.Gift.run(true), alternateTime);
let runTime = new Date(ts_ms() + alternateTime).toLocaleString();
MYDEBUG('[自动送礼]', `将在${runTime}进行自动送礼`);
MY_API.CACHE.Gift_TS = ts_ms();
MY_API.saveCache();
} else {
let alternateTime = MY_API.CONFIG.GIFT_INTERVAL * 60 * 1000;
MY_API.Gift.run_timer = setTimeout(() => MY_API.Gift.run(true), alternateTime);
MYDEBUG('[自动送礼]', `将在${MY_API.CONFIG.GIFT_INTERVAL}分钟后进行自动送礼`);
MY_API.CACHE.GiftInterval_TS = ts_ms();
MY_API.saveCache();
}
};
/**
* 处理用户输入的【允许被送出的礼物类型】,将礼物名转换为id
*/
const handleGiftList = () => {
MY_API.Gift.allowGiftList = [...MY_API.CONFIG.GIFT_ALLOW_TYPE];
MYDEBUG('[自动送礼]', `处理前的礼物列表 ${MY_API.Gift.allowGiftList}`);
for (let i = 0; i < MY_API.Gift.allowGiftList.length; i++) {
const listItem = MY_API.Gift.allowGiftList[i];
let matchItem;
if (isNaN(listItem)) {
// 如果填了礼物名,转换为id
matchItem = Live_info.gift_list.find((item) => item.name === listItem);
if (matchItem) MY_API.Gift.allowGiftList[i] = String(matchItem.id);
}
}
MYDEBUG('[自动送礼]', `处理后得到的礼物id列表 ${MY_API.Gift.allowGiftList}`);
};
/**
* 获取礼物列表中的每种礼物所对应的亲密度,把结果保存至 giftFeed_list。
* 格式:{ id1: feed1, id2: feed2, ... }
*/
const getGiftFeed = () => {
for (const i of MY_API.Gift.bag_list) {
if (!MY_API.Gift.giftFeed_list.hasOwnProperty(i.gift_id)) {
MY_API.Gift.giftFeed_list[i.gift_id] = MY_API.Gift.getFeedByGiftID(i.gift_id);
}
}
};
/**
* 处理包裹。
* 1. 根据礼物到期时间过滤包裹
* 2. 按礼物亲密度由高到低排序
* 3. 按过期时间由早到晚排序
* @param filter 是否按设置过滤礼物
*/
const handleBagList = (filter = true) => {
let bag_list;
if (!MY_API.CONFIG.SEND_ALL_GIFT && filter) {
// 送之前查一次有没有可送的
bag_list = MY_API.Gift.bag_list.filter(
(r) => MY_API.Gift.allowGiftList.includes(String(r.gift_id)) && r.gift_num > 0 && (r.expire_at * 1000 - ts_ms()) / 86400000 <= MY_API.CONFIG.GIFT_LIMIT
);
MYDEBUG('[自动送礼] if分支 过滤后的礼物', bag_list);
if (bag_list.length === 0) {
MY_API.Gift.over = true;
return;
}
} else {
bag_list = MY_API.Gift.bag_list.filter((r) => r.gift_num > 0 && r.expire_at != 0);
MYDEBUG('[自动送礼] else分支 过滤后的礼物', bag_list);
if (bag_list.length === 0) {
MY_API.Gift.over = true;
return;
}
}
// 按礼物亲密度由高到低排序
for (const i of bag_list) {
i.gift_feed = MY_API.Gift.giftFeed_list[i.gift_id];
}
bag_list.sort(function (a, b) {
return b.gift_feed - a.gift_feed;
});
// 按过期时间由早到晚
bag_list.sort(function (a, b) {
if (b.gift_feed === a.gift_feed) {
return a.expire_at - b.expire_at;
}
});
MY_API.Gift.bag_list = [...bag_list];
MYDEBUG('Gift.bag_list (sorted)', MY_API.Gift.bag_list);
};
/**
* 处理粉丝勋章
* @param {Object} MY_API.Gift.medal_list
*/
const handleMedalList = () => {
MY_API.Gift.medal_list = MY_API.Gift.medal_list.filter((it) => it.day_limit - it.today_feed > 0 && it.level < 20 && it.roomid);
MY_API.Gift.medal_list = MY_API.Gift.sort_medals(MY_API.Gift.medal_list);
// 排除直播间
if (MY_API.CONFIG.GIFT_SEND_METHOD === 'GIFT_SEND_BLACK') {
// 黑名单
MY_API.Gift.medal_list = MY_API.Gift.medal_list.filter((Er) => MY_API.CONFIG.GIFT_SEND_ROOM.findIndex((exp) => exp == Er.roomid) == -1);
} else {
// 白名单
MY_API.Gift.medal_list = MY_API.Gift.medal_list.filter((Er) => MY_API.CONFIG.GIFT_SEND_ROOM.findIndex((exp) => exp == Er.roomid) > -1);
}
};
/**
* 判断包裹内是否还有礼物
* @returns {Boolean} 有礼物 true, 无礼物 false
*/
const checkRemainGift = () => {
return MY_API.Gift.bag_list.some((g) => g.gift_num > 0) ? true : false;
};
try {
if (!MY_API.CONFIG.AUTO_GIFT || otherScriptsRunning) return $.Deferred().resolve();
if (MY_API.MEDAL_DANMU.isRunning) {
// 【粉丝牌打卡】任务运行中
return setTimeout(() => MY_API.Gift.run(), 3e3);
}
if (MY_API.Gift.run_timer) clearTimeout(MY_API.Gift.run_timer);
if (MY_API.CONFIG.GIFT_METHOD == 'GIFT_SEND_TIME' && !isTime(MY_API.CONFIG.GIFT_SEND_HOUR, MY_API.CONFIG.GIFT_SEND_MINUTE) && !SEND_GIFT_NOW && !noTimeCheck) {
// 定时送礼
let alternateTime = getIntervalTime(MY_API.CONFIG.GIFT_SEND_HOUR, MY_API.CONFIG.GIFT_SEND_MINUTE);
MY_API.Gift.run_timer = setTimeout(() => MY_API.Gift.run(true), alternateTime);
let runTime = new Date(ts_ms() + alternateTime).toLocaleString();
MYDEBUG('[自动送礼]', `将在${runTime}进行自动送礼`);
return $.Deferred().resolve();
} else if (MY_API.CONFIG.GIFT_METHOD == 'GIFT_INTERVAL' && !SEND_GIFT_NOW && !noTimeCheck) {
// 间隔__分钟送礼
let GiftInterval = MY_API.CONFIG.GIFT_INTERVAL * 60e3;
if (MY_API.CACHE.GiftInterval_TS) {
const interval = ts_ms() - MY_API.CACHE.GiftInterval_TS;
if (interval < GiftInterval) {
let intervalTime = GiftInterval - interval;
MY_API.Gift.run_timer = setTimeout(() => MY_API.Gift.run(true), intervalTime);
MYDEBUG('[自动送礼]', `将在${intervalTime}毫秒后进行自动送礼`);
return $.Deferred().resolve();
}
} else {
MY_API.CACHE.GiftInterval_TS = ts_ms();
MY_API.saveCache();
}
}
if (medal_info.status.state() === 'resolved') MY_API.Gift.medal_list = [...medal_info.medal_list];
else {
window.toast('[自动送礼] 粉丝勋章列表未被完全获取,暂停运行', 'error');
return medal_info.status.then(() => MY_API.Gift.run());
}
MYDEBUG('Gift.run: Gift.getMedalList().then: Gift.medal_list', MY_API.Gift.medal_list);
MY_API.Gift.over = false; // 开始运行前先把停止运行设为 false
handleGiftList();
await MY_API.Gift.getBagList();
await getGiftFeed();
handleBagList(false);
if (MY_API.Gift.over) return waitForNextRun();
if (MY_API.Gift.medal_list.length > 0) {
handleMedalList();
handleBagList();
if (MY_API.Gift.over) return waitForNextRun();
for (const v of MY_API.Gift.medal_list) {
if (!checkRemainGift()) {
MY_API.Gift.over = true;
break;
}
const response = await BAPI.room.room_init(parseInt(v.roomid, 10)).then((re) => {
MYDEBUG(`[自动送礼] API.room.room_init(${v.roomid})`, re);
if (re.code !== 0) throw re.msg;
return re;
});
MY_API.Gift.room_id = parseInt(response.data.room_id, 10);
MY_API.Gift.ruid = v.target_id;
MY_API.Gift.remain_feed = v.day_limit - v.today_feed;
if (MY_API.Gift.remain_feed > 0) {
window.toast(`[自动送礼]勋章[${v.medal_name}] 今日亲密度未满[${v.today_feed}/${v.day_limit}],预计需要[${MY_API.Gift.remain_feed}]送礼开始`, 'info');
await MY_API.Gift.sendGift(v);
} else {
window.toast(`[自动送礼]勋章[${v.medal_name}] 今日亲密度已满`, 'info');
}
}
}
if (!MY_API.Gift.over) await MY_API.Gift.sendRemainGift(MY_API.CONFIG.SPARE_GIFT_ROOM);
return waitForNextRun();
} catch (err) {
window.toast('[自动送礼] 运行时出现异常,已停止', 'error');
MYERROR(`自动送礼出错`, err);
return delayCall(() => MY_API.Gift.run());
}
},
sendGift: async (medal) => {
for (const v of MY_API.Gift.bag_list) {
if (MY_API.Gift.remain_feed <= 0) {
return window.toast(`[自动送礼]勋章[${medal.medal_name}]送礼结束,今日亲密度已满[${medal.today_feed}/${medal.day_limit}]`, 'info');
}
if (v.gift_num === 0) continue; // 如果这一礼物送完了则跳到下一个礼物
const feed = MY_API.Gift.giftFeed_list[v.gift_id];
if (feed > 0) {
let feed_num = Math.floor(MY_API.Gift.remain_feed / feed);
if (feed_num === 0) continue; // 当前礼物亲密度大于勋章今日剩余亲密度
if (feed_num > v.gift_num) feed_num = v.gift_num;
MYDEBUG(`[自动送礼]送出礼物类型${v.gift_name},该项礼物数量${v.gift_num},送出礼物数量${feed_num}`);
await BAPI.gift.bag_send(Live_info.uid, v.gift_id, MY_API.Gift.ruid, feed_num, v.bag_id, MY_API.Gift.room_id, Live_info.rnd).then((response) => {
MYDEBUG('Gift.sendGift: API.gift.bag_send', response);
if (response.code === 0) {
v.gift_num -= feed_num;
medal.today_feed += feed_num * feed;
MY_API.Gift.remain_feed -= feed_num * feed;
window.toast(
`[自动送礼]勋章[${medal.medal_name}] 送礼成功,送出${feed_num}个${v.gift_name},[${medal.today_feed}/${medal.day_limit}]距离今日亲密度上限还需[${MY_API.Gift.remain_feed}]`,
'success'
);
} else if (response.code === 200028) {
// 当前直播间无法赠送礼物哦~
window.toast(`[自动送礼]勋章[${medal.medal_name}] 送礼失败:${response.msg}`, 'caution');
} else {
window.toast(`[自动送礼]勋章[${medal.medal_name}] 送礼异常:${response.msg}`, 'caution');
return delayCall(() => MY_API.Gift.sendGift(medal));
}
});
}
}
},
sendRemainGift: async (ROOM_ID) => {
if (ROOM_ID == 0) return $.Deferred().resolve();
let UID = undefined;
await BAPI.room.room_init(ROOM_ID).then((response) => {
MYDEBUG('API.room.room_init', response);
if (response.code === 0) UID = response.data.uid;
else {
window.toast(`[自动送礼]【剩余礼物】检查房间出错 ${response.message}`);
return delayCall(() => BAPI.room.room_init(ROOM_ID));
}
});
let bag_list = MY_API.Gift.bag_list.filter((r) => MY_API.Gift.allowGiftList.includes(String(r.gift_id)) && r.gift_num > 0 && (r.expire_at * 1000 - ts_ms()) / 86400000 <= 1);
if (bag_list.length === 0) return;
MYDEBUG('[自动送礼]【剩余礼物】bag_list', bag_list);
for (const v of bag_list) {
if (v.gift_num <= 0) continue;
const feed = MY_API.Gift.giftFeed_list[v.gift_id];
if (feed > 0) {
let feed_num = v.gift_num;
await BAPI.gift.bag_send(Live_info.uid, v.gift_id, UID, feed_num, v.bag_id, ROOM_ID, Live_info.rnd).then((response) => {
MYDEBUG('Gift.sendGift: API.gift.bag_send', response);
if (response.code === 0) {
v.gift_num -= feed_num;
window.toast(`[自动送礼]【剩余礼物】房间[${ROOM_ID}] 送礼成功,送出${feed_num}个${v.gift_name}`, 'success');
} else if (response.code === 200028) {
// 当前直播间无法赠送礼物哦~
window.toast(`[自动送礼]勋章[${medal.medal_name}] 送礼失败:${response.msg}`, 'caution');
} else {
window.toast(`[自动送礼]【剩余礼物】房间[${ROOM_ID}] 送礼异常:${response.msg}`, 'caution');
return delayCall(() => MY_API.Gift.sendGift(medal));
}
});
}
}
},
},
AUTO_DANMU: {
setValue: (array, index) => {
if (MY_API.CONFIG[array][index] === undefined && index > 0) return MY_API.AUTO_DANMU.setValue(array, index - 1);
else return MY_API.CONFIG[array][index];
},
sendDanmu: async (danmuContent, roomId) => {
let realRoomId = roomId;
if (Number(roomId) <= 10000) {
realRoomId = await BAPI.room.get_info(roomId).then((res) => {
MYDEBUG(`API.room.get_info roomId=${roomId} res`, res); // 可能是短号,要用长号发弹幕
if (res.code === 0) {
return res.data.room_id;
} else {
window.toast(`[自动发弹幕]房间号【${roomId}】信息获取失败 ${res.message}`, 'error');
return roomId;
}
});
}
return BAPI.sendLiveDanmu(danmuContent, realRoomId).then((response) => {
MYDEBUG(`[自动发弹幕]弹幕发送内容【${danmuContent}】,房间号【${roomId}】`, response);
if (response.code === 0) {
window.toast(`[自动发弹幕]弹幕【${danmuContent}】(房间号【${roomId}】)发送成功`, 'success');
} else {
window.toast(`[自动发弹幕]弹幕【${danmuContent}】(房间号【${roomId}】)出错 ${response.msg}`, 'caution');
}
});
},
getMaxLength: () => {
let maxLength = undefined;
const contentLength = MY_API.CONFIG.DANMU_CONTENT.length,
roomidLength = MY_API.CONFIG.DANMU_ROOMID.length,
intervalTimeLength = MY_API.CONFIG.DANMU_INTERVAL_TIME.length;
if (contentLength >= roomidLength) maxLength = contentLength;
else maxLength = roomidLength;
if (maxLength < intervalTimeLength) maxLength = intervalTimeLength;
return maxLength;
},
run: async () => {
if (!MY_API.CONFIG.AUTO_DANMU || otherScriptsRunning) return $.Deferred().resolve();
if (MY_API.MEDAL_DANMU.isRunning || MY_API.AppUserTask.isRunning) {
// 【粉丝牌打卡】【APP用户任务】任务运行中
return setTimeout(() => MY_API.AUTO_DANMU.run(), 3e3);
}
danmuTaskRunning = true;
if (SEND_DANMU_NOW) {
for (let i = 0; i < MY_API.CONFIG.DANMU_CONTENT.length; i++) {
let danmu_content = MY_API.AUTO_DANMU.setValue('DANMU_CONTENT', i),
danmu_roomid = parseInt(MY_API.AUTO_DANMU.setValue('DANMU_ROOMID', i));
await MY_API.AUTO_DANMU.sendDanmu(danmu_content, danmu_roomid);
await sleep(1000);
}
SEND_DANMU_NOW = false;
} else {
let maxLength = MY_API.AUTO_DANMU.getMaxLength();
for (let i = 0; i < maxLength; i++) {
let danmu_content = MY_API.AUTO_DANMU.setValue('DANMU_CONTENT', i),
danmu_roomid = parseInt(MY_API.AUTO_DANMU.setValue('DANMU_ROOMID', i)),
danmu_intervalTime = MY_API.AUTO_DANMU.setValue('DANMU_INTERVAL_TIME', i), // 设置-发送时间
lastSendTime = undefined, // 上次发弹幕的时间戳(毫秒)
jsonCache = MY_API.CACHE.AUTO_SEND_DANMU_TS,
objIndex = undefined, // 弹幕缓存下标
isTimeData = undefined, // 是否是时间点数据(eg 9:01)
intervalTime = undefined, // 据上次发弹幕的时间(毫秒)
danmu_intervalTime_Ts = undefined, // 间隔时间
sleepTime = 0;
function getDanmuCache() {
for (let i = 0; i < jsonCache.length; i++) {
const obj = jsonCache[i];
if (obj.roomid == danmu_roomid && obj.content == danmu_content) {
lastSendTime = obj.sendTs;
objIndex = i;
break;
}
}
}
if (danmu_intervalTime.indexOf(':') > -1) {
// 时间
isTimeData = true;
const danmu_time = danmu_intervalTime.split(':'); // 小时,分钟,秒
const hour = parseInt(danmu_time[0]),
minute = parseInt(danmu_time[1]),
second = parseInt(danmu_time[2]);
if (!isTime(hour, minute, second)) sleepTime = getIntervalTime(hour, minute, second);
else sleepTime = 86400000;
} else {
isTimeData = false;
danmu_intervalTime = danmu_intervalTime.toLowerCase();
if (danmu_intervalTime.indexOf('h') > -1 || danmu_intervalTime.indexOf('m') > -1 || danmu_intervalTime.indexOf('s') > -1) {
const hourArray = danmu_intervalTime.split('h'); // 1h5m3s
const minuteArray = hourArray[1] === undefined ? hourArray[0].split('m') : hourArray[1].split('m');
const secondArray = minuteArray[1] === undefined ? minuteArray[0].split('s') : minuteArray[1].split('s');
const hour = hourArray[0],
minute = minuteArray[0],
second = secondArray[0];
const finalHour = isNaN(hour) ? 0 : hour || 0,
finalMinute = isNaN(minute) ? 0 : minute || 0,
finalSecond = isNaN(second) ? 0 : second || 0;
danmu_intervalTime_Ts = finalHour * 3600000 + finalMinute * 60000 + finalSecond * 1000;
} else {
// 没有h或m或s则默认是分钟
danmu_intervalTime_Ts = danmu_intervalTime * 60000;
}
}
MYDEBUG('[自动发弹幕]MY_API.CACHE.AUTO_SEND_DANMU_TS => jsoncache', jsonCache);
getDanmuCache();
if (!isTimeData) {
if (lastSendTime) intervalTime = ts_ms() - lastSendTime;
else intervalTime = ts_ms();
}
const setCache = () => {
const newJson = {
roomid: danmu_roomid,
content: danmu_content,
sendTs: ts_ms(),
};
getDanmuCache();
if (objIndex === undefined) {
jsonCache.push(newJson);
} else {
jsonCache[objIndex].sendTs = ts_ms();
}
MY_API.CACHE.AUTO_SEND_DANMU_TS = jsonCache;
return MY_API.saveCache(false);
};
const sendNextDanmu = (intervalTS, isTime) => {
if (!isTime) setCache();
return setTimeout(async () => {
await MY_API.AUTO_DANMU.sendDanmu(danmu_content, danmu_roomid);
if (!isTime) setCache();
return sendNextDanmu(intervalTS, isTime);
}, intervalTS);
};
if (!isTimeData && intervalTime >= danmu_intervalTime_Ts) {
// 非时间点数据,距上次发送的时间大于间隔时间
await MY_API.AUTO_DANMU.sendDanmu(danmu_content, danmu_roomid);
MYDEBUG(`[自动发弹幕]弹幕发送内容【${danmu_content}】,房间号【${danmu_roomid}】,距下次发送还有`, danmu_intervalTime);
sendNextDanmu(danmu_intervalTime_Ts, isTimeData);
} else if (isTimeData && !sleepTime) {
// 时间点数据,立刻发送
await MY_API.AUTO_DANMU.sendDanmu(danmu_content, danmu_roomid);
MYDEBUG(`[自动发弹幕]弹幕发送内容【${danmu_content}】,房间号【${danmu_roomid}】,距下次发送还有`, '24小时');
sendNextDanmu(sleepTime, isTimeData);
} else {
// 时间点数据,需等待一段时间再发送
MYDEBUG(
`[自动发弹幕]弹幕发送内容【${danmu_content}】,房间号【${danmu_roomid}】,距下次发送还有`,
`${!isTimeData ? (danmu_intervalTime_Ts - intervalTime) / 60000 : sleepTime / 60000}分钟`
);
setTimeout(
async () => {
await MY_API.AUTO_DANMU.sendDanmu(danmu_content, danmu_roomid);
sendNextDanmu(isTimeData ? 86400000 : danmu_intervalTime_Ts, isTimeData);
},
isTimeData ? sleepTime : danmu_intervalTime_Ts - intervalTime
);
}
await sleep(1500);
}
}
danmuTaskRunning = false;
},
},
AUTO_CHECK_DANMU: {
sendDanmu: {},
initEmitter: () => {
danmuEmitter.on('danmu', (msg) => {
let timer = setTimeout(() => {
window.toast(`弹幕【${msg}】疑似发送失败`, 'caution');
delete MY_API.AUTO_CHECK_DANMU.sendDanmu[timer];
}, MY_API.CONFIG.AUTO_CHECK_DANMU_TIMEOUT);
MY_API.AUTO_CHECK_DANMU.sendDanmu[timer] = msg;
});
},
run: () => {
if (!SP_CONFIG.AUTO_CHECK_DANMU) return;
MY_API.AUTO_CHECK_DANMU.initEmitter();
W.bliveproxy.addCommandHandler('DANMU_MSG', (command) => {
if (MY_API.AUTO_CHECK_DANMU.sendDanmu === {}) return;
const info = command.info;
if (info[2][0] === Live_info.uid) {
for (const d in MY_API.AUTO_CHECK_DANMU.sendDanmu) {
if (MY_API.AUTO_CHECK_DANMU.sendDanmu[d] === info[1]) {
MYDEBUG(`[检查弹幕是否发送成功] 已找到弹幕(timer = ${d})`, MY_API.AUTO_CHECK_DANMU.sendDanmu[d]);
clearTimeout(d);
delete MY_API.AUTO_CHECK_DANMU.sendDanmu[d];
}
}
}
});
},
},
MEDAL_DANMU: {
isRunning: false,
medal_list: [],
sendDanmu: async (danmuContent, roomId, medal_name) => {
return BAPI.sendLiveDanmu(danmuContent, roomId).then((response) => {
MYDEBUG(`API.sendLiveDanmu(弹幕 = ${danmuContent}, roomid = ${roomId})`, response);
if (response.code === 0) {
return MYDEBUG(`[粉丝牌打卡弹幕] 弹幕发送内容【${danmuContent}】,房间号【${roomId}】,真实房间号【${roomId}】,粉丝勋章【${medal_name}】`, response);
} else {
return window.toast(`[粉丝牌打卡弹幕] 弹幕【${danmuContent}】(房间号【${roomId}】,粉丝勋章【${medal_name}】)出错 ${response.message}`, 'caution');
}
});
},
run: async () => {
if (!MY_API.CONFIG.MEDAL_DANMU || otherScriptsRunning) return $.Deferred().resolve();
if (!checkNewDay(MY_API.CACHE.Live_medalDanmu_TS)) {
runMidnight(() => MY_API.MEDAL_DANMU.run(), '粉丝勋章打卡弹幕');
return $.Deferred().resolve();
}
if (medal_info.status.state() === 'resolved') MY_API.MEDAL_DANMU.medal_list = [...medal_info.medal_list];
else {
window.toast('[粉丝牌打卡] 粉丝勋章列表未被完全获取,暂停运行', 'error');
return medal_info.status.then(() => MY_API.MEDAL_DANMU.run());
}
MY_API.MEDAL_DANMU.isRunning = true;
let lightMedalList;
if (MY_API.CONFIG.LIVE_TASKS_METHOD === 'LIVE_TASKS_WHITE')
lightMedalList = MY_API.MEDAL_DANMU.medal_list.filter((r) => MY_API.CONFIG.LIVE_TASKS_ROOM.findIndex((m) => m == r.roomid) > -1 && r.roomid);
else {
lightMedalList = MY_API.MEDAL_DANMU.medal_list.filter((r) => MY_API.CONFIG.LIVE_TASKS_ROOM.findIndex((m) => m == r.roomid) === -1 && r.roomid);
}
MYDEBUG('[粉丝牌打卡] 过滤后的粉丝勋章房间列表', lightMedalList);
let danmuContentIndex = 0;
const configDanmuLength = MY_API.CONFIG.MEDAL_DANMU_CONTENT.length;
// 第一轮
for (const up of lightMedalList) {
if (danmuContentIndex >= configDanmuLength) danmuContentIndex = 0;
const medal_name = up.medal_name,
roomid = up.real_roomid,
danmuContent = MY_API.CONFIG.MEDAL_DANMU_CONTENT[danmuContentIndex];
await MY_API.MEDAL_DANMU.sendDanmu(danmuContent, roomid, medal_name);
danmuContentIndex++;
await sleep(MY_API.CONFIG.MEDAL_DANMU_INTERVAL * 1000);
}
MY_API.MEDAL_DANMU.isRunning = false;
window.toast('[粉丝牌打卡弹幕] 今日已完成', 'success');
MY_API.CACHE.Live_medalDanmu_TS = ts_ms();
MY_API.saveCache();
return runTomorrow(() => MY_API.MEDAL_DANMU.run(), 0, 2, '粉丝勋章打卡弹幕');
},
},
DANMU_MODIFY: {
maxLength: 0,
configJson: {
DANMU_MODIFY_REGEX: [],
DANMU_MODIFY_UID: [],
DANMU_MODIFY_POOL: [],
DANMU_MODIFY_SIZE: [],
DANMU_MODIFY_COLOR: [],
},
handleConfig: () => {
for (const i in MY_API.DANMU_MODIFY.configJson) {
MY_API.DANMU_MODIFY.configJson[i] = MY_API.CONFIG[i];
}
for (const i in MY_API.DANMU_MODIFY.configJson) {
if (MY_API.DANMU_MODIFY.configJson[i].length > MY_API.DANMU_MODIFY.maxLength) MY_API.DANMU_MODIFY.maxLength = MY_API.DANMU_MODIFY.configJson[i].length;
}
for (const i in MY_API.DANMU_MODIFY.configJson) {
if (MY_API.DANMU_MODIFY.configJson[i].length < MY_API.DANMU_MODIFY.maxLength) {
let lastIndex = MY_API.DANMU_MODIFY.configJson[i].length - 1;
for (let c = lastIndex; c < MY_API.DANMU_MODIFY.maxLength - 1; c++) {
MY_API.DANMU_MODIFY.configJson[i].push(MY_API.DANMU_MODIFY.configJson[i][lastIndex]);
}
}
}
},
check: (info) => {
for (let i = 0; i < MY_API.DANMU_MODIFY.maxLength; i++) {
let regex,
uid = info[2][0],
danmu = info[1];
try {
regex = eval(MY_API.DANMU_MODIFY.configJson.DANMU_MODIFY_REGEX[i]);
} catch (e) {
MYDEBUG('bliveproxy', `正则表达式出错 ${MY_API.DANMU_MODIFY.configJson.DANMU_MODIFY_REGEX[i]}`);
regex = /^【/;
}
if (regex.test(danmu) || MY_API.DANMU_MODIFY.configJson.DANMU_MODIFY_UID[i] == uid) return i;
}
return -1;
},
run: () => {
if (!SP_CONFIG.DANMU_MODIFY) return $.Deferred().resolve();
MY_API.DANMU_MODIFY.handleConfig();
// MYDEBUG('MY_API.DANMU_MODIFY.configJson', MY_API.DANMU_MODIFY.configJson);
W.bliveproxy.addCommandHandler('DANMU_MSG', (command) => {
if (!SP_CONFIG.DANMU_MODIFY) return $.Deferred().resolve();
let info = command.info;
MYDEBUG('bliveproxy DANMU_MSG', info);
let index = MY_API.DANMU_MODIFY.check(info);
if (index === -1) return $.Deferred().resolve();
// 显示模式
info[0][1] = MY_API.DANMU_MODIFY.configJson.DANMU_MODIFY_POOL[index];
// 尺寸
info[0][2] *= MY_API.DANMU_MODIFY.configJson.DANMU_MODIFY_SIZE[index];
// 颜色
info[0][3] = Number('0x' + MY_API.DANMU_MODIFY.configJson.DANMU_MODIFY_COLOR[index].replace('#', ''));
});
},
},
GET_PRIVILEGE: {
check_cache: () => {
if (ts_ms() >= MY_API.CACHE.NextVipPrivilege_TS) return true;
else return false;
},
save_cache: (expire_time) => {
const newTs = (expire_time + 2) * 1000;
if (newTs < MY_API.CACHE.NextVipPrivilege_TS || !MY_API.CACHE.NextVipPrivilege_TS) {
MY_API.CACHE.NextVipPrivilege_TS = newTs;
return MY_API.saveCache(false);
}
},
get_info: () => {
return BAPI.x.vip.privilege.my().then((response) => {
MYDEBUG(`API.x.vip.privilege.my response`, response);
if (response.code === 0) return response.data.list;
else window.toast(`[大会员] 获取权益状态失败 ${response.message}`, 'error');
return false;
});
},
receive: (type) => {
return BAPI.x.vip.privilege.receive(type).then((response) => {
MYDEBUG(`API.x.vip.privilege.receive response`, response);
if (response.code === 0) return true;
else window.toast(`[大会员] 领取权益失败(type=${type}) ${response.message}`, 'warning');
return false;
});
},
run: async () => {
if (!MY_API.CONFIG.GET_PRIVILEGE || otherScriptsRunning || Live_info.vipStatus === 0 || ts_ms() < MY_API.CACHE.NextVipPrivilege_TS) return $.Deferred().resolve();
let privilege_info = await MY_API.GET_PRIVILEGE.get_info();
if (!privilege_info) return $.Deferred().resolve();
let flag = false;
for (const i of privilege_info) {
if (i.state === 0) {
flag = await MY_API.GET_PRIVILEGE.receive(i.type, i.expire_time);
}
}
if (flag) window.toast('[大会员] 权益已领取', 'success');
if (privilege_info.every((obj) => obj.state === 1)) {
let min_expire_time = privilege_info[0].expire_time;
for (let i = 1; i < privilege_info.length; i++) {
if (privilege_info[i].expire_time < min_expire_time) min_expire_time = privilege_info[i].expire_time;
}
MY_API.GET_PRIVILEGE.save_cache(min_expire_time);
}
return $.Deferred().resolve();
},
},
AppUserTask: {
isRunning: false,
getRemainProgress: async () => {
return BAPI.xlive.app.getUserTaskProgress(userToken.access_token).then((response) => {
MYDEBUG('API.xlive.app.getUserTaskProgress', response);
if (response.code === 0) {
const is_surplus = response.data.is_surplus;
const target = response.data.target;
if (is_surplus === -1 || target === 0) return -1;
const progress = response.data.progress;
return target - progress;
} else {
MYERROR('[APP用户任务] 获取进度失败', response.message);
return -2;
}
});
},
getUserTaskRewards: async () => {
return BAPI.xlive.app.userTaskReceiveRewards(userToken.access_token).then((response) => {
MYDEBUG('API.xlive.app.userTaskReceiveRewards', response);
if (response.code === 0) {
return MY_API.chatLog(`[APP用户任务] 领取奖励成功<br>获得${response.data.num}个电池`, 'success');
} else {
return MY_API.chatLog(`[APP用户任务] 领取奖励失败<br>${response.message}`, 'error');
}
});
},
sendDanmu: async (remainProgress) => {
const content = remainProgress < MY_API.CONFIG.MEDAL_DANMU_CONTENT.length ? MY_API.CONFIG.MEDAL_DANMU_CONTENT[remainProgress] : '打卡' + remainProgress;
return BAPI.xlive.app.sendmsg(userToken.access_token, content, 22474988, Live_info.uid).then((response) => {
MYDEBUG(`API.sendLiveDanmu(弹幕 = ${content}, roomid = 22474988,)`, response);
if (response.code === 0) {
return MYDEBUG(`[APP用户任务] 弹幕发送内容【${content}】,房间号【22474988,】`, response);
} else {
return MY_API.chatLog(`[APP用户任务] 弹幕【${content}】(房间号【22474988,】)出错 ${response.message}`, 'error');
}
});
},
completeTask: async (remainProgress) => {
while (remainProgress > 0) {
await MY_API.AppUserTask.sendDanmu(remainProgress--);
await sleep(5000);
}
},
run: async () => {
if (!MY_API.CONFIG.APP_TASK || otherScriptsRunning) return $.Deferred().resolve();
if (!checkNewDay(MY_API.CACHE.AppTaskRewards)) return runMidnight(() => MY_API.LiveReward.WatchLive(), 'APP用户任务');
if (MY_API.MEDAL_DANMU.isRunning) {
// 【粉丝牌打卡】任务运行中
return setTimeout(() => MY_API.AppUserTask.run(), 3e3);
}
if ((await setToken()) === undefined) return;
MY_API.AppUserTask.isRunning = true;
MYDEBUG('appToken userToken.access_token.length', userToken.access_token.length);
let remainProgress = await MY_API.AppUserTask.getRemainProgress();
if (remainProgress >= 0) {
await MY_API.AppUserTask.completeTask(remainProgress);
await MY_API.AppUserTask.getUserTaskRewards();
} else if (remainProgress === -1) {
MY_API.chatLog('[APP用户任务] 你的账号可能无法参与该任务', 'warning');
} else {
MY_API.chatLog('[APP用户任务] 失败,尝试重置token<br>刷新页面后会再次重试该任务', 'info');
return await setToken(true);
}
MY_API.CACHE.AppTaskRewards = ts_ms();
MY_API.saveCache();
MY_API.AppUserTask.isRunning = false;
return runMidnight(() => MY_API.LiveReward.WatchLive(), 'APP用户任务');
},
},
};
MY_API.init().then(() => {
try {
let runNext = $.Deferred();
if (SP_CONFIG.showEula) {
const eula = GM_getResourceText('eula');
myopen({
title: `${GM_info.script.name}最终用户许可协议`,
btn: ['同意协议并继续', '不同意'],
closeBtn: 0,
area: [String($(window).width() * 0.618) + 'px', String($(window).height() * 0.8) + 'px'],
content: eula,
yes: function (index) {
SP_CONFIG.showEula = false;
saveSpConfig();
layer.close(index);
runNext.resolve();
},
btn2: function () {
mymsg('脚本已停止运行', {
time: 3000,
icon: 2,
});
window.toast('由于未同意最终用户许可协议,脚本已停止运行。', 'caution');
SP_CONFIG.showEula = true;
saveSpConfig();
runNext.reject();
},
});
} else runNext.resolve();
runNext.then(() => {
if (parseInt(Live_info.uid) === 0 || isNaN(parseInt(Live_info.uid))) return window.toast('未登录(不可用),请先登录(不可用)再使用脚本', 'caution');
// 新版本提示信息
if (MY_API.CONFIG.UPDATE_TIP) MY_API.newMessage(GM_info.script.version);
MYDEBUG('MY_API.CONFIG', MY_API.CONFIG);
main(MY_API);
});
} catch (e) {
MYERROR('初始化错误', e);
}
});
}
async function main(API) {
// 检查更新
// checkUpdate(GM_info.script.version);
// 修复版本更新产生的兼容性问题
fixVersionDifferences(API, GM_info.script.version);
runExactMidnight(() => clearStat(), '重置统计');
API.creatSetBox(); // 创建设置框
API.removeUnnecessary(); // 移除页面元素
API.DANMU_MODIFY.run(); // 弹幕修改
const taskList = [
// 每日任务
API.MEDAL_DANMU.run, // 粉丝牌打卡弹幕
API.GroupSign.run, // 应援团签到
API.DailyReward.run, // 每日任务
API.LiveReward.run, // 直播每日任务
API.AppUserTask.run, // APP用户任务(发5条弹幕领1电池)
API.Exchange.runS2C, // 银瓜子换硬币
API.Exchange.runC2S, // 硬币换银瓜子
// 其它任务
API.AUTO_DANMU.run, // 自动发弹幕
API.Gift.run, // 送礼物
API.GET_PRIVILEGE.run, // 领取大会员权益
API.AUTO_CHECK_DANMU.run, // 检查弹幕是否发送成功
];
otherScriptsRunningCheck.then(() => runAllTasks(5000, 200, taskList));
if (API.CONFIG.TIME_RELOAD) reset(API.CONFIG.TIME_RELOAD_MINUTE * 60000); // 刷新直播间
function reset(delay) {
let resetTimer = setTimeout(() => {
if (API.CONFIG.WatchLive && checkNewDay(API.CACHE.LiveReward_TS)) {
MYDEBUG('[刷新直播间]', '正在运行观看直播任务,10分钟后再次检查');
clearTimeout(resetTimer);
return reset(600e3);
}
const resetTime = sleepCheck(API.CONFIG);
if (resetTime) {
MYDEBUG('[刷新直播间]', `处于休眠时间段,将在${resetTime}毫秒后刷新直播间`);
clearTimeout(resetTimer);
return reset(resetTime);
}
W.location.reload();
}, delay);
}
}
function checkUpdate(version) {
if (!checkNewDay(noticeJson.lastCheckUpdateTs)) return;
const headers = {
Accept: `text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9`,
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7',
};
XHR({
GM: true,
anonymous: true,
method: 'GET',
url: 'https://andywang.top:3001/api/v1/notice',
headers: headers,
responseType: 'json',
}).then((response) => {
MYDEBUG('检查更新 checkUpdate', response);
if (!response || response.response.status !== 200) return MYERROR(`[检查更新] 获取notice.json出错`);
noticeJson = response.body.data;
noticeJson.lastCheckUpdateTs = ts_ms();
GM_setValue(`noticeJson`, noticeJson);
const scriptVersion = noticeJson.version;
const greasyforkOpenTabOptions = { active: true, insert: true, setParent: true };
if (versionStringCompare(version, scriptVersion) === -1) {
// version < scriptVersion
// 需要更新
let updateSource, updateURL;
if (GM_info.script.updateURL === null) {
updateSource = 'Greasy Fork镜像';
updateURL = 'https://gf.qytechs.cn/scripts/406048-b%E7%AB%99%E7%9B%B4%E6%92%AD%E9%97%B4%E6%8C%82%E6%9C%BA%E5%8A%A9%E6%89%8B';
} else {
updateSource = 'BLTH-server';
updateURL = 'https://andywang.top:3001/api/v1/BLTH.user.js';
}
let index = myconfirm(
`检测到新版本 <strong>${scriptVersion}</strong>。<br>是否从 ${updateSource} 更新脚本?`,
{
title: '更新脚本',
btn: ['是', '否'],
},
function () {
// 更新
if (updateSource === 'Greasy Fork镜像') {
layer.close(index);
GM_openInTab(updateURL, greasyforkOpenTabOptions);
} else {
updateBLTH(updateURL);
mymsg('正在更新...', { time: 2000 });
}
},
function () {
// 不更新
}
);
}
});
}
/**
* 获取粉丝勋章列表和真实直播间号
* @param {Number} page
* @returns
*/
async function getMedalList(page = 1) {
if (page === 1) medal_info = { status: $.Deferred(), medal_list: [] };
let end = false;
while (true) {
await BAPI.xlive.app.medal(page).then((response) => {
MYDEBUG('before init() getMedalList: API.i.medal', response);
if (response.code === 0) {
for (let i = 0; i < response.data.items.length; i++) {
if (response.data.items[i].roomid && response.data.items[i].roomid <= 100000) {
BAPI.room.get_info(response.data.items[i].roomid).then((res) => {
if (res.code === 0) {
response.data.items[i].real_roomid = res.data.room_id;
} else {
MYERROR(`获取直播间${response.data.items[i].roomid}的真实房间号出错`);
}
});
} else {
response.data.items[i].real_roomid = response.data.items[i].roomid;
}
}
medal_info.medal_list = medal_info.medal_list.concat(response.data.items);
if (response.data.page_info.cur_page < response.data.page_info.total_page) page++;
else {
medal_info.status.resolve();
end = true;
}
} else if (response.code === 1024) {
window.toast(`获取粉丝勋章列表超时 ${response.message} 部分功能将无法正常运行`, 'error');
delayCall(() => getMedalList(page));
end = true;
} else {
window.toast(`获取粉丝勋章列表失败 ${response.message} 部分功能将无法正常运行`, 'error');
delayCall(() => getMedalList(page));
end = true;
}
});
if (end) {
runTomorrow(() => getMedalList(), 0, 1, '获取粉丝勋章列表');
break;
}
await sleep(200);
}
}
/**
* 比较版本号大小
* @param {string} ver1
* @param {string} ver2
* @returns {boolean} 若 ver1 > ver2 返回 1, ver1 = ver2 返回 0, ver1 < ver2, 返回 -1
*/
function versionStringCompare(ver1 = '0', ver2 = '0') {
function changeVersion2Num(ver) {
return ver
.match(/\d.*/)[0]
.split('.')
.reduce((total, value, index) => total + 0.01 ** index * Number(value), 0);
}
const verNum1 = changeVersion2Num(ver1),
verNum2 = changeVersion2Num(ver2);
if (verNum1 > verNum2) return 1;
else if (verNum1 < verNum2) return -1;
else return 0;
}
/**
* 模拟鼠标移动
*/
function mouseMove() {
MYDEBUG('屏蔽挂机检测', '触发一次MouseEvent(mousemove)');
document.dispatchEvent(
new MouseEvent('mousemove', {
screenX: Math.floor(Math.random() * screen.availWidth),
screenY: Math.floor(Math.random() * screen.availHeight),
clientX: Math.floor(Math.random() * W.innerWidth),
clientY: Math.floor(Math.random() * W.innerHeight),
ctrlKey: Math.random() > 0.8,
shiftKey: Math.random() > 0.8,
altKey: Math.random() > 0.9,
metaKey: false,
button: 0,
buttons: 0,
relatedTarget: null,
region: null,
detail: 0,
view: W,
sourceCapabilities: W.InputDeviceCapabilities ? new W.InputDeviceCapabilities({ fireTouchEvents: false }) : null,
bubbles: true,
cancelable: true,
composed: true,
})
);
}
/**
* 执行所有任务
* @param {Number} sleep 休眠时间
* @param {Number} interval 任务间隔
* @param {list} task 任务
*/
function runAllTasks(sleep, interval, task) {
let num = 0;
setTimeout(() => {
for (const i of task) {
setTimeout(() => i(), interval * num++);
}
}, sleep);
}
/**
* 修复因版本差异造成的错误
* @param API MY_API
*/
function fixVersionDifferences(API, version) {
// 添加新的修复后需修改版本号
if (versionStringCompare(SP_CONFIG.storageLastFixVersion, '6.0.1') >= 0) return;
// 修复变量类型错误
const configFixList = ['AUTO_GIFT_ROOMID', 'COIN_UID'];
if (!configFixList.every((i) => Array.isArray(API.CONFIG[i]))) {
for (const i of configFixList) {
if (!Array.isArray(API.CONFIG[i])) {
API.CONFIG[i] = String(API.CONFIG[i]).split(',');
}
}
}
// 修复变量值差异
if (API.CONFIG.GIFT_SORT == 'high') API.CONFIG.GIFT_SORT = 'GIFT_SORT_HIGH';
else if (API.CONFIG.GIFT_SORT == 'low') API.CONFIG.GIFT_SORT = 'GIFT_SORT_LOW';
if (API.CONFIG.MEDAL_DANMU_ROOM) API.CONFIG.LIVE_TASKS_ROOM = API.CONFIG.MEDAL_DANMU_ROOM;
if (API.CONFIG.MEDAL_DANMU_METHOD) API.CONFIG.LIVE_TASKS_METHOD = 'LIVE_TASKS_' + API.CONFIG.MEDAL_DANMU_METHOD.split('_').pop();
// localStorage fix
localStorage.removeItem('im_deviceid_IGIFTMSG');
// save settings
SP_CONFIG.storageLastFixVersion = version;
API.saveConfig(false);
API.saveCache();
saveSpConfig();
}
/**
* 保存特殊设置
*/
function saveSpConfig(printLog = true) {
if (printLog) MYDEBUG('SP_CONFIG已保存', SP_CONFIG);
return GM_setValue(`SP_CONFIG`, SP_CONFIG);
}
/**
* layer动画
* @param {jqdom} jqdom
* @param {boolean} bool
*/
function animChange(jqdom, bool) {
if (bool) {
// show => hide
jqdom.removeClass('layer-anim');
jqdom.removeClass('layer-anim-00');
jqdom.addClass('layer-anim');
jqdom.addClass('layer-anim-close');
} else {
// hide => show
jqdom.removeClass('layer-anim');
jqdom.removeClass('layer-anim-close');
jqdom.addClass('layer-anim');
jqdom.addClass('layer-anim-00');
}
}
/**
* 合并两个对象,只合并 obj1 中不包含(或为undefined, null)但 obj2 中有的属性
* 删除 obj1 有但 obj2 中没有的属性
*/
function mergeObject(obj1, obj2) {
function combine(object1, object2) {
for (let i in object2) {
if (object1[i] === undefined || object1[i] === null) object1[i] = object2[i];
else if (!Array.isArray(object1[i]) && typeof object1[i] === 'object') combine(object1[i], object2[i]);
}
}
function del(object1, object2) {
for (let i in object1) {
if (object2[i] === undefined || object2[i] === null) delete object1[i];
else if (!Array.isArray(object1[i]) && typeof object1[i] === 'object') del(object1[i], object2[i]);
}
}
combine(obj1, obj2);
del(obj1, obj2);
}
/**
* 通过检查某些特性是否存在判断浏览器版本
* @returns {Array} 0: 提示字符串 1: 等级字符串
*/
function checkBrowserVersion() {
if (typeof Array.prototype.findIndex === 'undefined') return ['浏览器版本过低,挂机助手停止运行', 'error'];
else if (typeof String.prototype.replaceAll === 'undefined') return ['浏览器版本略低,挂机助手可以正常运行但建议升级浏览器到最新版', 'info'];
else return ['ok', 'info'];
}
/**
* 保存文件到本地
* @param fileName 文件名
* @param fileContent 文件内容
*/
function downFile(fileName, fileContent) {
let elementA = document.createElement('a');
elementA.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(fileContent)));
elementA.setAttribute('download', fileName);
elementA.style.display = 'none';
document.body.appendChild(elementA);
elementA.click();
document.body.removeChild(elementA);
}
/**
* 更新 BLTH 脚本
* @param {*} link 更新地址
*/
function updateBLTH(link) {
let elementA = document.createElement('a');
elementA.setAttribute('href', link);
elementA.style.display = 'none';
document.body.appendChild(elementA);
elementA.click();
document.body.removeChild(elementA);
}
/**
* 导出配置文件
* @param MY_API_CONFIG MY_API.CONFIG
* @param SP_CONFIG SP_CONFIG
*/
function exportConfig(MY_API_CONFIG, SP_CONFIG) {
const exportJson = {
VERSION: GM_info.script.version,
MY_API_CONFIG: MY_API_CONFIG,
SP_CONFIG: SP_CONFIG,
};
return downFile('BLTH_CONFIG.json', exportJson);
}
/**
* 导入配置文件
*/
function importConfig() {
let selectedFile = document.getElementById('BLTH_config_file').files[0];
let reader = new FileReader();
reader.onload = function () {
MYDEBUG('importConfig 文件读取结果:', this.result);
try {
readConfigArray[0] = JSON.parse(decodeURIComponent(this.result));
if (typeof readConfigArray[0] == 'object' && readConfigArray[0]) {
const list = ['VERSION', 'MY_API_CONFIG', 'SP_CONFIG'];
for (const i of list) {
if (!readConfigArray[0].hasOwnProperty(i)) return wrongFile();
}
if (versionStringCompare('5.6.6.3', readConfigArray[0]['VERSION']) === 1)
// 5.6.6.3 > VERSION
return wrongFile('该配置文件版本过低');
return readConfigArray[1].resolve();
} else {
return wrongFile();
}
} catch (e) {
MYDEBUG('importConfig error:', e);
return wrongFile();
}
};
reader.readAsText(selectedFile);
function wrongFile(msg = '文件格式错误') {
return mymsg(msg, {
time: 2500,
icon: 2,
});
}
}
/**
* (23,50) 获取与目标时间在一天内的间隔时间,24小时制(毫秒)
* @param hour 整数 小时
* @param minute 整数 分钟
* @param second 整数 秒(可不填)
* @returns {number} intervalTime
*/
function getIntervalTime(hour, minute, second) {
const myDate = new Date();
const h = myDate.getHours();
const m = myDate.getMinutes();
const s = myDate.getSeconds();
const TargetTime = hour * 3600 * 1e3 + minute * 60 * 1e3 + (!second ? 0 : second * 1e3);
const nowTime = h * 3600 * 1e3 + m * 60 * 1e3 + s * 1e3;
const intervalTime = TargetTime - nowTime;
MYDEBUG('[getIntervalTime]获取间隔时间', `${intervalTime}毫秒`);
if (intervalTime < 0) {
return 24 * 3600 * 1e3 + intervalTime;
} else {
return intervalTime;
}
}
/**
* (23,50) 当前时间是否为23:50
* @param hour 整数 小时
* @param minute 整数 分钟
* @param second 整数 秒(可不填)
* @returns {boolean}
*/
function isTime(hour, minute, second) {
let myDate = new Date();
let h = myDate.getHours();
let m = myDate.getMinutes();
let s = myDate.getSeconds();
if ((h == hour && m == minute && !second) || (h == hour && m == minute && s == second)) {
return true;
} else {
MYDEBUG('isTime 错误时间', `目标时间${hour}时${minute}分${second || 0}秒,当前时间${h}时${m}分${s}秒`);
return false;
}
}
/**
* (2,10,0,1) 当前是否在两点0分到十点1分之间
* @param sH 整数 起始小时
* @param eH 整数 终止小时
* @param sM 整数 起始分钟
* @param eM 整数 终止分钟
* @returns {boolean}
*/
function inTimeArea(sH, eH, sM, eM) {
if (sH > 23 || eH > 24 || sH < 0 || eH < 1 || sM > 59 || sM < 0 || eM > 59 || eM < 0) {
return false;
}
const hourMs = 3600000,
minMs = 60000,
myDate = new Date(),
nowHour = myDate.getHours(),
nowMin = myDate.getMinutes(),
nowTimeTs = nowHour * hourMs + nowMin * minMs,
targetStartTs = sH * hourMs + sM * minMs,
targetEndTs = eH * hourMs + eM * minMs;
if (targetStartTs < targetEndTs) {
if (nowTimeTs >= targetStartTs && nowTimeTs < targetEndTs) return true;
else return false;
} else {
if (nowTimeTs >= targetStartTs || nowTimeTs < targetEndTs) return true;
else return false;
}
}
/**
* 暂停
* @param millisecond
* @returns {Promise} resolve
*/
function sleep(millisecond) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, millisecond);
});
}
/**
* 检查是否为新一天
* 注:并无要求被检查时间戳大于当前时间戳
* @param ts 被检查的时间戳
* @param type 检查标准 北京时间(默认): Beijing 本地时间: local
* @returns {boolean}
*/
function checkNewDay(ts, type = 'Beijing') {
if (ts == 0) return true;
let t = new Date(ts);
let d = type === 'Beijing' ? getCHSdate() : new Date();
let td = t.getDate();
let dd = d.getDate();
let now_ts = d.getTime();
return dd !== td || now_ts - ts > 86400000;
}
/**
* 唯一运行检测
*/
function onlyScriptCheck() {
try {
let UNIQUE_CHECK_CACHE = localStorage.getItem('UNIQUE_CHECK_CACHE') || 0;
const t = ts_ms();
if (t - UNIQUE_CHECK_CACHE >= 0 && t - UNIQUE_CHECK_CACHE <= 11e3) {
// 其他脚本正在运行
window.toast('检测到其他直播间页面的挂机助手正在运行,无需重复运行的功能将停止运行', 'caution');
otherScriptsRunning = true;
return otherScriptsRunningCheck.resolve();
}
let timer_unique;
const uniqueMark = () => {
timer_unique = setTimeout(() => uniqueMark(), 10e3);
UNIQUE_CHECK_CACHE = ts_ms();
localStorage.setItem('UNIQUE_CHECK_CACHE', UNIQUE_CHECK_CACHE);
};
W.addEventListener('unload', () => {
clearTimeout(timer_unique);
localStorage.setItem('UNIQUE_CHECK_CACHE', 0);
});
uniqueMark();
return otherScriptsRunningCheck.resolve();
} catch (e) {
MYDEBUG('重复运行检测出错', e);
return otherScriptsRunningCheck.reject();
}
}
/**
* 防抖
* @param {function} func
* @param {number} wait
* @returns {function}
*/
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
};
}
/**
* 发起xmlhttpRequest请求(GM函数和浏览器原生)
* @param XHROptions
* @returns {object} resolve({response: res, body: res.response})
*/
function XHR(XHROptions) {
return new Promise((resolve) => {
const onerror = (error) => {
MYERROR('XHR出错', XHROptions, error);
resolve(undefined);
};
if (XHROptions.GM) {
if (XHROptions.method === 'POST') {
if (XHROptions.headers === undefined) XHROptions.headers = {};
if (XHROptions.headers['Content-Type'] === undefined) XHROptions.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
}
XHROptions.timeout = 30 * 1000;
XHROptions.onload = (res) => resolve({ response: res, body: res.response });
XHROptions.onerror = onerror;
XHROptions.ontimeout = onerror;
GM_xmlhttpRequest(XHROptions);
} else {
const xhr = new XMLHttpRequest();
xhr.open(XHROptions.method, XHROptions.url);
if (XHROptions.method === 'POST' && xhr.getResponseHeader('Content-Type') === null) xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
if (XHROptions.cookie) xhr.withCredentials = true;
if (XHROptions.responseType !== undefined) xhr.responseType = XHROptions.responseType;
xhr.timeout = 30 * 1000;
xhr.onload = (ev) => {
const res = ev.target;
resolve({ response: res, body: res.response });
};
xhr.onerror = onerror;
xhr.ontimeout = onerror;
xhr.send(XHROptions.data);
}
});
}
/**
* 包装layer的prompt方法
* @param obj
* @param func
* @returns index
*/
function myprompt(obj, func) {
if (SP_CONFIG.darkMode) {
if (obj.title) obj.title = '<span style="color:#a2a7ae;">' + obj.title + '</span>';
}
let index = layer.prompt(obj, func);
if (SP_CONFIG.darkMode) {
layer.style(index, {
'background-color': '#1c1c1c',
});
}
return index;
}
/**
* 包装layer的msg方法
* @param msg
* @param obj
* @returns index
*/
function mymsg(msg, obj) {
let index = layer.msg(msg, obj);
if (SP_CONFIG.darkMode) {
layer.style(index, {
'background-color': '#1c1c1c',
color: '#a2a7ae',
});
}
return index;
}
/**
* 包装layer的open方法
* @param obj
* @returns index
*/
function myopen(obj) {
if (SP_CONFIG.darkMode) {
if (obj.title) obj.title = '<span style="color:#a2a7ae;">' + obj.title + '</span>';
}
let index = layer.open(obj);
if (SP_CONFIG.darkMode) {
layer.style(index, {
'background-color': '#1c1c1c',
color: '#a2a7ae',
});
}
return index;
}
/**
* 包装layer的confirm方法
* @param msg
* @param obj
* @param func
* @returns index
*/
function myconfirm(msg, obj, ...func) {
if (SP_CONFIG.darkMode) {
if (obj.title) obj.title = '<span style="color:#a2a7ae;">' + obj.title + '</span>';
}
let index = layer.confirm(msg, obj, ...func);
if (SP_CONFIG.darkMode) {
layer.style(index, {
'background-color': '#1c1c1c',
color: '#a2a7ae',
});
}
return index;
}
/**
* 包装layer的tips方法
* @param content
* @param element
* @param obj
*/
function mytips(content, element, obj = {}) {
const contentCss = { 'border-radius': '20px', 'background-color': '#00c4f8' },
tipsGTCss = { 'border-right-color': '#00c4f8' };
function _successFn(dom, index) {
const layerContent = dom.children('.layui-layer-content'),
layerTipsGT = layerContent.children('.layui-layer-TipsG.layui-layer-TipsT');
layerContent.css(contentCss);
layerTipsGT.css(tipsGTCss);
}
if (!obj.success) obj.success = _successFn;
else
obj.success = function () {
obj.success();
_successFn();
};
layer.tips(content, element, obj);
}
})();