您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
fetch commenters for an opus (wth is that)
// ==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; } `);