// ==UserScript==
// @name Bili兑换码抢购
// @namespace Violentmonkey Scripts
// @license Mit
// @match https://www.bilibili.com/blackboard/activity-award-exchange.html?task_id=*
// @grant none
// @version 1.6.1
// @author -
// @icon https://i0.hdslb.com/bfs/activity-plat/static/b9vgSxGaAg.png
// @description 🔥功能介绍🔥:🎉 1、支持B站所有激励计划,是否成功取决于b站接口是否更新,与游戏版本无关;🎉 2、打开对应一个兑换码页面自动运行,F12控制台查看运行信息;
// ==/UserScript==
(function(){
const Time = 100; //请求频率,可修改
// 比如100表示每1秒抢10次,关闭兑换码页面停止执行)
// 1000表示1秒抢1次
document.addEventListener('visibilitychange', function(event) {
event.stopImmediatePropagation(); // 阻断b站设置的visibilitychange监听器
}, true)
//输出响应信息
async function readReadableStream(readableStream) {
const reader = readableStream.getReader(); // 获取读取器
const decoder = new TextDecoder();
let chunks = '';
let info;
try {
let result;
while ((result = await reader.read())) { // 循环直到流结束
// 'result.value' 是从 ReadableStream 中读取的 Uint8Array 字节数据
if (result.done) {
const decodedText = JSON.parse(chunks);
// 返回receiveId还是响应message
if (decodedText.message === '0') {
info = {
code: 0,
rid: decodedText.data.task_info.receive_id,
tid: decodedText.data.task_info.id,
aid: decodedText.data.task_info.act_id
}
} else if (decodedText.code === 202100) {
// 弹验证码则走一次网页自身逻辑
info = decodedText.message
console.log(decodedText.code + ':' + info)
document.querySelector('#app > div > div.home-wrap.select-disable > section.tool-wrap > div').click()
} else {
info = decodedText.message
console.log(decodedText.code + ':' + info)
}
break
}
chunks = chunks + decoder.decode((result.value));
}
} catch (error) {
console.error("Error reading the stream:", error);
} finally {
// 关闭读取器释放资源(虽然在现代浏览器中通常是自动完成的)
reader.releaseLock();
}
return info
}
// 发送请求
async function fetchWrapper(url, options = {}) {
// 合并用户自定义headers与默认headers
const { method = 'GET', headers = {}, body = null, credentials = 'include' } = options
const defaultHeaders = {
'Accept': 'application/json, text/plain, */*',
};
const mergedHeaders = {...defaultHeaders, ...headers };
// 对于 GET 请求,确保不携带 body
const requestInit = {
method,
headers: mergedHeaders,
credentials,
};
// 根据method决定是否需要序列化body,如果为post且传了值且type为url
if (method.toUpperCase() !== 'GET' && body !== null) {
// 将JSON对象转化为URLSearchParams实例
const formData = new URLSearchParams();
for (const [key, value] of Object.entries(body)) {
formData.append(key, value);
}
requestInit.body = formData
} else {
let params = new URLSearchParams({...body });
// 将参数添加到URL
url = `${url}?${params.toString()}`;
}
// 发起fetch请求
return await fetch(url, {
method,
...requestInit
})
.then((response) => {
// 处理响应体
return readReadableStream(response.body)
}).then(result => {
return result
})
.catch((error) => {
throw new Error('An error occurred during the fetch operation!', error);
});
}
// 截取cookie
function getCookie(name) {
// 获取所有cookie并以"; "分割
const cookies = document.cookie.split('; ');
for (let i = 0; i < cookies.length; i++) {
// 分割键值对
const cookie = cookies[i].split('=');
// 删除cookie名两边的空白字符
const cookieName = cookie[0].trim();
// 如果找到了所需的cookie键
if (cookieName === name) {
// 返回对应的cookie值(去掉值两边的空白字符)
return decodeURIComponent(cookie[1].trim());
}
}
// 如果找不到指定的cookie,返回null或空字符串
return null;
}
// 拿到csrf和id
function getCsrfAndId() {
const id = new URLSearchParams(window.location.search).get('task_id')
const csrf = getCookie('bili_jct')
return { id, csrf }
}
// 拿到receiveId和task_id和act_id
async function getReceiveID() {
let receive_id;
let task_id;
let act_id;
await fetchWrapper('https://api.bilibili.com/x/activity/mission/single_task', {
method: 'GET',
body: getCsrfAndId()
}).then((info) => {
receive_id = info.rid
task_id = info.tid
act_id = info.aid
})
return { receive_id, task_id, act_id }
}
/******** Boundary *******/
const { csrf } = getCsrfAndId();
let receive_id;
let task_id;
let act_id;
// commonJs无法使用顶层await
// const { receive_id, task_id } = await getReceiveID()
// 避免请求频繁,加个定时器
// (async() => {
// const info = await getReceiveID()
// receive_id = info.receive_id
// task_id = info.task_id
// })();
// 拿到receive_id、task_id再请求接口、receive_id是完成任务的凭证,task_id用于区分活动
// 开始执行并重复无限次数
setTimeout(function() {
document.querySelector('#app > div > div.home-wrap.select-disable > section.tool-wrap > div').click();
(async() => {
const info = await getReceiveID()
receive_id = info.receive_id
task_id = info.task_id
act_id = info.act_id
})();
setInterval(() => {
fetchWrapper('https://api.bilibili.com/x/activity/mission/task/reward/receive', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: {
csrf,
act_id,
task_id,
"group_id": 0,
receive_id,
"receive_from": "missionPage",
"act_name": "崩坏",
"task_name": "星穹铁道",
"reward_name": "激励计划",
"gaia_vtoken": ""
}
});
}, Time); //请求频率
// 如果用户没抢码资格,则持续更新资格直到获取到资格
// 请求频率过高时避免undefined
let setReceiveTimer = setInterval(() => {
fetchWrapper('https://api.bilibili.com/x/activity/mission/single_task', {
method: 'GET',
body: getCsrfAndId()
}).then((info) => {
(info.code === 0 && (receive_id = info.rid)) ||
(receive_id !== 0 && receive_id !== undefined && clearInterval(setReceiveTimer));
})
}, 1100)
}, 1000) // 该定时器防止频繁请求导致获取信息失败
})()