// ==UserScript==
// @name Bilibili Commnter Fetcher
// @namespace https://scripts.lirc572.com/
// @version 0.1.1
// @description fetch commenters for an opus (wth is that)
// @author lirc572
// @match https://www.bilibili.com/opus/*
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
/**
* Make delayInms larger if you encounter 412 errors
*/
/* https://stackoverflow.com/a/49813472 */
const delay = (delayInms = 800) => {
return new Promise(resolve => setTimeout(resolve, delayInms));
}
function getCookie() {
return document.cookie.split(';').map(x => x.trim().split('=')).reduce((m, x) => { m.set(x[0], x[1]); return m; }, new Map());
}
function getCsrfToken() {
return getCookie().get('bili_jct');
}
async function getParams() {
const pageUrl = window.location.href;
const html = await fetch(pageUrl).then(res => res.text());
const startIndex = html.indexOf('window.__INITIAL_STATE__=');
const endIndex = html.indexOf(';', startIndex);
const initialStateString = html.substring(startIndex + 'window.__INITIAL_STATE__='.length, endIndex);
const initialState = JSON.parse(initialStateString);
const oid = initialState.detail.basic.comment_id_str;
const type = initialState.detail.basic.comment_type; // 11
return { oid, type };
}
async function isFollowing(mid) {
const res = await fetch(`https://api.bilibili.com/x/web-interface/card?mid=${mid}`);
const json = await res.json();
await delay();
return json.data.following;
}
async function getComments(next = 0) {
const results = [];
let count = 0;
const csrfToken = getCsrfToken();
const { oid, type } = await getParams();
for (; ;) {
try {
/**
* `mode=3` and `plat=1` are magic numbers I don't understand...
* If the values here don't work, try to find them in the network tab of your browser
* by putting `/main` in the filter before checking the parameters in the request URL
*/
const res = await fetch(`https://api.bilibili.com/x/v2/reply/main?csrf=${csrfToken}&mode=3&next=${next}&oid=${oid}&plat=1&type=${type}`);
const json = await res.json();
const data = json.data;
const {
replies, // array of replies
} = data;
for (const reply of replies) {
const {
ctime, // UNIX timestamp of comment time
content, // comment content
member, // user info
} = reply;
const {
message, // comment message
} = content;
const {
is_contractor, // ?
is_senior_member, // da-member
mid, // user id
sex, // gender
uname, // username
} = member;
const is_following = await isFollowing(mid);
results.push({
mid,
uname,
is_following,
sex,
is_contractor,
is_senior_member,
ctime,
message,
});
count++;
console.log(`Fetched comment ${count}`);
}
if (data.cursor.is_end) {
break;
}
console.log(`Fetched comments for page ${next}!`);
next = data.cursor.next;
} catch (e) {
console.error(`Error while fetching comments for page ${next}:`, e);
throw new Error(`Error while fetching comments for page ${next}:`, e);
}
}
return results;
}
/* https://stackoverflow.com/a/18197341 */
function downloadTextFile(filename, text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
function downloadAsJson(comments, filename) {
downloadTextFile(filename, JSON.stringify(comments, null, 4));
}
/* Not completed, need to escape characters */
function downloadAsCsv(comments, filename) {
const csvHeader = [
'mid',
'uname',
'is_following',
'sex',
'is_contractor',
'is_senior_member',
'ctime',
'message',
].join(',');
const csvContent = comments.map(x => {
const {
mid,
uname,
is_following,
sex,
is_contractor,
is_senior_member,
ctime,
message,
} = x;
return [
mid,
uname,
is_following,
sex,
is_contractor,
is_senior_member,
ctime,
message,
].join(',');
}).join('\n');
downloadTextFile(filename, csvHeader + '\n' + csvContent);
}
(function () {
window.addEventListener('load', function () {
const btnNode = document.createElement('button');
btnNode.setAttribute('id', 'bcf-download-btn');
btnNode.innerHTML = 'BCF Download';
document.body.appendChild(btnNode);
document.getElementById('bcf-download-btn').addEventListener(
'click',
() => {
document.getElementById('bcf-download-btn').disabled = true;
getComments().then(comments => {
downloadAsJson(comments, 'comments.json');
// downloadAsCsv(comments, 'comments.csv');
document.getElementById('bcf-download-btn').disabled = false;
});
},
false,
);
});
})();
GM_addStyle(`
#bcf-download-btn {
position: fixed;
top: 4rem;
right: 0;
}
`);