Skip to content

Instantly share code, notes, and snippets.

@shynome
Last active September 22, 2023 13:47
Show Gist options
  • Save shynome/8271f3170029d7c6fa2e4ceae5c18f49 to your computer and use it in GitHub Desktop.
Save shynome/8271f3170029d7c6fa2e4ceae5c18f49 to your computer and use it in GitHub Desktop.
bilibili B站 主播流水导出

how to use

将下方内容添加为书签, 点击添加数据导出按钮

javascript:(j=>fetch(j).then(r=>r.text()).then(b=>new Function(b)).then(b=>b()))(`data:application/javascript;base64,Ly8gQHRzLWNoZWNrCgpjb25zdCBiYXNlQXBpID0KCSdodHRwczovL2FwaS5saXZlLmJpbGliaWxpLmNvbS94bGl2ZS9yZXZlbnVlL3YxL2dpZnRTdHJlYW0vZ2V0UmVjZWl2ZWRHaWZ0U3RyZWFtTmV4dExpc3Q/bGltaXQ9MjAnCgovKioKICogQHR5cGVkZWYge3tjb2RlOm51bWJlcjttZXNzYWdlOnN0cmluZztkYXRhOlR9fSBCaWxpYmlsaVJlc3BvbnNlPFQ+CiAqIEB0ZW1wbGF0ZSB7YW55fSBUCiAqLwoKLyoqCiAqIEB0eXBlZGVmIHtvYmplY3R9IERhdGEKICogQHByb3Age0l0ZW1bXX0gbGlzdAogKiBAcHJvcCB7MHwxfSBoYXNfbW9yZQogKiBAdHlwZWRlZiB7b2JqZWN0fSBJdGVtCiAqIEBwcm9wIHtzdHJpbmd9IGlkCiAqLwoKLyoqCiAqIEBwYXJhbSB7c3RyaW5nfSBkYXkg5aaCOiAyMDIzLTA5LTAzCiAqLwphc3luYyBmdW5jdGlvbiBleHBvcnREYXkoZGF5KSB7CglsZXQgbGFzdF9pZAoJbGV0IGhhc19tb3JlID0gMQoJbGV0IGl0ZW1zID0gW10KCXdoaWxlIChoYXNfbW9yZSA9PT0gMSkgewoJCWxldCBkID0gYXdhaXQgZmV0Y2hMaXN0KGRheSwgbGFzdF9pZCkKCQloYXNfbW9yZSA9IGQuaGFzX21vcmUKCQlpdGVtcy5wdXNoKC4uLmQubGlzdCkKCQlpZiAoaGFzX21vcmUpIHsKCQkJbGFzdF9pZCA9IGQubGlzdC5zbGljZSgtMSlbMF0uaWQKCQl9CgkJYXdhaXQgbmV3IFByb21pc2UoKHJsKSA9PiBzZXRUaW1lb3V0KHJsLCA1MDApKSAvLyDpgb/lhY3niKzlj5bov4flv6vlr7zoh7TpmZDpgJ8KCX0KCXJldHVybiBpdGVtcwp9CgovKioKICogQHBhcmFtIHtzdHJpbmd9IGRheQogKiBAcGFyYW0ge3N0cmluZ30gW2xhc3RfaWRdCiAqLwphc3luYyBmdW5jdGlvbiBmZXRjaExpc3QoZGF5LCBsYXN0X2lkKSB7CglsZXQgbGluayA9IG5ldyBVUkwoYmFzZUFwaSkKCWxpbmsuc2VhcmNoUGFyYW1zLnNldCgnYmVnaW5fdGltZScsIGRheSkKCWlmIChsYXN0X2lkKSB7CgkJbGluay5zZWFyY2hQYXJhbXMuc2V0KCdsYXN0X2lkJywgbGFzdF9pZCkKCX0KCWxldCByID0gYXdhaXQgZmV0Y2gobGluaywgeyBjcmVkZW50aWFsczogJ2luY2x1ZGUnIH0pCgkvKipAdHlwZSB7QmlsaWJpbGlSZXNwb25zZTxEYXRhPn0gKi8KCWxldCByZXNwID0gYXdhaXQgci5qc29uKCkKCWlmIChyZXNwLmNvZGUgIT0gMCkgewoJCXRocm93IG5ldyBFcnJvcihyZXNwLm1lc3NhZ2UpCgl9CglyZXR1cm4gcmVzcC5kYXRhCn0KCmNvbnN0IGRhdGVGb3JtYXR0ZXIgPSBJbnRsLkRhdGVUaW1lRm9ybWF0KCd6aCcsIHsKCXllYXI6ICdudW1lcmljJywKCW1vbnRoOiAnMi1kaWdpdCcsCglkYXk6ICcyLWRpZ2l0JywKfSkKZnVuY3Rpb24gZm9ybWF0RGF0ZShkID0gbmV3IERhdGUoKSkgewoJcmV0dXJuIGRhdGVGb3JtYXR0ZXIuZm9ybWF0KGQpLnJlcGxhY2UoL1wvL2csICctJykKfQoKLyoqCiAqIEBwYXJhbSB7c3RyaW5nfSBsaW5rCiAqIEBwYXJhbSB7c3RyaW5nfSBmaWxlbmFtZQogKi8KZnVuY3Rpb24gZG93bmxvYWQoZmlsZW5hbWUsIGxpbmspIHsKCWxldCBhID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnYScpCglhLnN0eWxlLmRpc3BsYXkgPSAnbm9uZScKCWEuaHJlZiA9IGxpbmsKCWEuZG93bmxvYWQgPSBmaWxlbmFtZQoJZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChhKQoJYS5jbGljaygpCn0KClByb21pc2UucmVzb2x2ZSgpCgkudGhlbihhc3luYyAoKSA9PiB7CgkJY29uc3QgYmFyID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcignLnNlbGVjdC1iYXIgLml0ZW0udGltZScpCgkJaWYgKCFiYXIpIHsKCQkJdGhyb3cgbmV3IEVycm9yKCJjYW4ndCBmaW5kIC5zZWxlY3QtYmFyIC5pdGVtLnRpbWUiKQoJCX0KCgkJbGV0IGJ0biA9IC8qKkB0eXBlIHtIVE1MQnV0dG9uRWxlbWVudH0gKi8gKGJhci5xdWVyeVNlbGVjdG9yKCcuZXhwb3J0ZXInKSkKCQlpZiAoYnRuKSB7CgkJCXRocm93IG5ldyBFcnJvcign5a+85Ye65oyJ6ZKu5bey5re75YqgJykKCQl9CgkJLy8gQHRzLWlnbm9yZQoJCWJhci5zdHlsZS5wb3NpdGlvbiA9ICdyZWxhdGl2ZScKCQlidG4gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdidXR0b24nKQoJCWJ0bi50eXBlID0gJ2J1dHRvbicKCQlidG4uY2xhc3NOYW1lID0gJ2V4cG9ydGVyIGJsLWJ1dHRvbiBsaXZlLWJ0biBkZWZhdWx0IGJsLWJ1dHRvbi0tcHJpbWFyeSBibC1idXR0b24tLXNpemUnCgkJY29uc3QgYnRuVGV4dCA9ICflr7zlh7rlt7LpgInmi6nml6XmnJ/liLDnjrDlnKjnmoTmlbDmja4nCgkJYnRuLmlubmVyVGV4dCA9IGJ0blRleHQKCQlidG4uc3R5bGUuY3NzVGV4dCA9ICdwb3NpdGlvbjphYnNvbHV0ZTt0b3A6MTAwJTsgbGVmdDogMDsnCgkJbGV0IGNhbmNlbCA9IGZhbHNlCgkJYXN5bmMgZnVuY3Rpb24gaGFuZGxlRXhwb3J0KCkgewoJCQljb25zdCBkYXRlSW5wdXQgPSAvKipAdHlwZSB7SFRNTElucHV0RWxlbWVudH0gKi8gKAoJCQkJZG9jdW1lbnQucXVlcnlTZWxlY3RvcignLnNlbGVjdC1iYXIgLml0ZW0udGltZSAuZGF0ZS1zZWxlY3RvciBpbnB1dCcpCgkJCSkKCQkJbGV0IHN0YXJ0ID0gbmV3IERhdGUoZGF0ZUlucHV0LnZhbHVlKQoJCQlsZXQgY3Vyc29yID0gbmV3IERhdGUoZGF0ZUlucHV0LnZhbHVlKQoJCQlsZXQgZW5kID0gbmV3IERhdGUoZm9ybWF0RGF0ZSgpKQoJCQlsZXQgYWxsSXRlbXMgPSBbXQoJCQl3aGlsZSAoY3Vyc29yLmdldFRpbWUoKSA8PSBlbmQuZ2V0VGltZSgpKSB7CgkJCQlsZXQgcyA9IGZvcm1hdERhdGUoY3Vyc29yKQoJCQkJYnRuLmlubmVyVGV4dCA9IGAke3N9IOaVsOaNruivt+axguS4rSwg54K55Ye75o+Q5YmN5YGc5q2iYAoKCQkJCWxldCBpdGVtcyA9IGF3YWl0IGV4cG9ydERheShzKQoJCQkJYWxsSXRlbXMucHVzaCguLi5pdGVtcykKCQkJCWN1cnNvci5zZXREYXRlKGN1cnNvci5nZXREYXRlKCkgKyAxKQoJCQkJaWYgKGNhbmNlbCkgewoJCQkJCWJ0bi5pbm5lclRleHQgPSBg5bey5YGc5q2iLCDmraPlnKjlkIjlubbmlbDmja5gCgkJCQkJYnJlYWsKCQkJCX0KCQkJfQoJCQlsZXQgY29udGVudCA9IGFsbEl0ZW1zLm1hcCgodikgPT4gSlNPTi5zdHJpbmdpZnkodikpLmpvaW4oJ1xuJykKCQkJbGV0IGIgPSBuZXcgQmxvYihbY29udGVudF0sIHsgdHlwZTogJ3RleHQvcGxhaW4nIH0pCgkJCWxldCBibGluayA9IFVSTC5jcmVhdGVPYmplY3RVUkwoYikKCQkJbGV0IGZuYW1lID0gYOekvOeJqeaVsOaNriAke2Zvcm1hdERhdGUoc3RhcnQpfSB+ICR7Zm9ybWF0RGF0ZShjdXJzb3IpfS50eHRgCgkJCWRvd25sb2FkKGZuYW1lLCBibGluaykKCQkJVVJMLnJldm9rZU9iamVjdFVSTChibGluaykKCQl9CgkJbGV0IHBlbmRpbmcgPSBmYWxzZQoJCWJ0bi5vbmNsaWNrID0gKCkgPT4gewoJCQlpZiAocGVuZGluZykgewoJCQkJY2FuY2VsID0gdHJ1ZQoJCQkJcmV0dXJuCgkJCX0KCQkJcGVuZGluZyA9IHRydWUKCQkJY2FuY2VsID0gZmFsc2UKCQkJUHJvbWlzZS5yZXNvbHZlKCkKCQkJCS50aGVuKGhhbmRsZUV4cG9ydCkKCQkJCS50aGVuKGFzeW5jICgpID0+IHsKCQkJCQlidG4uaW5uZXJUZXh0ID0gJ+ivt+axguWujOaIkCcKCQkJCQlhd2FpdCBuZXcgUHJvbWlzZSgocmwpID0+IHNldFRpbWVvdXQocmwsIDJlMykpCgkJCQl9KQoJCQkJLmNhdGNoKChlcnIpID0+IHsKCQkJCQlsZXQgdGlwID0gZXJyPy5tZXNzYWdlID8/ICfmnKrnn6XplJnor68sIOivt+aMiUYxMuaJk+W8gOaOp+WItuWPsOafpeeci+mUmeivr+WOn+WboC4nCgkJCQkJYWxlcnQoYOWvvOWHuuWHuumUmSwg6ZSZ6K+vOiAke3RpcH0uYCkKCQkJCX0pCgkJCQkuZmluYWxseSgoKSA9PiB7CgkJCQkJYnRuLmlubmVyVGV4dCA9IGJ0blRleHQKCQkJCQlwZW5kaW5nID0gZmFsc2UKCQkJCX0pCgkJfQoJCWJhci5hcHBlbmQoYnRuKQoJfSkKCS50aGVuKCgpID0+IHsKCQlhbGVydCgn5pWw5o2u5a+85Ye65oyJ6ZKu5bey5re75Yqg5oiQ5YqfJykKCX0pCgkuY2F0Y2goKGVycikgPT4gewoJCWNvbnNvbGUuZXJyb3IoZXJyKQoJCWFsZXJ0KGDmt7vliqDlr7zlh7rmjInpkq7lpLHotKUsIOmUmeivrzogJHtlcnI/Lm1lc3NhZ2UgPz8gJ+acquefpemUmeivrywg6K+35oyJRjEy5omT5byA5o6n5Yi25Y+w5p+l55yL6ZSZ6K+v5Y6f5ZugLid9LmApCgl9KQo=`)
// @ts-check
const baseApi =
'https://api.live.bilibili.com/xlive/revenue/v1/giftStream/getReceivedGiftStreamNextList?limit=20'
/**
* @typedef {{code:number;message:string;data:T}} BilibiliResponse<T>
* @template {any} T
*/
/**
* @typedef {object} Data
* @prop {Item[]} list
* @prop {0|1} has_more
* @typedef {object} Item
* @prop {string} id
*/
/**
* @param {string} day 如: 2023-09-03
*/
async function exportDay(day) {
let last_id
let has_more = 1
let items = []
while (has_more === 1) {
let d = await fetchList(day, last_id)
has_more = d.has_more
items.push(...d.list)
if (has_more) {
last_id = d.list.slice(-1)[0].id
}
await new Promise((rl) => setTimeout(rl, 500)) // 避免爬取过快导致限速
}
return items
}
/**
* @param {string} day
* @param {string} [last_id]
*/
async function fetchList(day, last_id) {
let link = new URL(baseApi)
link.searchParams.set('begin_time', day)
if (last_id) {
link.searchParams.set('last_id', last_id)
}
let r = await fetch(link, { credentials: 'include' })
/**@type {BilibiliResponse<Data>} */
let resp = await r.json()
if (resp.code != 0) {
throw new Error(resp.message)
}
return resp.data
}
const dateFormatter = Intl.DateTimeFormat('zh', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
function formatDate(d = new Date()) {
return dateFormatter.format(d).replace(/\//g, '-')
}
/**
* @param {string} link
* @param {string} filename
*/
function download(filename, link) {
let a = document.createElement('a')
a.style.display = 'none'
a.href = link
a.download = filename
document.body.appendChild(a)
a.click()
}
Promise.resolve()
.then(async () => {
const bar = document.querySelector('.select-bar .item.time')
if (!bar) {
throw new Error("can't find .select-bar .item.time")
}
let btn = /**@type {HTMLButtonElement} */ (bar.querySelector('.exporter'))
if (btn) {
throw new Error('导出按钮已添加')
}
// @ts-ignore
bar.style.position = 'relative'
btn = document.createElement('button')
btn.type = 'button'
btn.className = 'exporter bl-button live-btn default bl-button--primary bl-button--size'
const btnText = '导出已选择日期到现在的数据'
btn.innerText = btnText
btn.style.cssText = 'position:absolute;top:100%; left: 0;'
let cancel = false
async function handleExport() {
const dateInput = /**@type {HTMLInputElement} */ (
document.querySelector('.select-bar .item.time .date-selector input')
)
let start = new Date(dateInput.value)
let cursor = new Date(dateInput.value)
let end = new Date(formatDate())
let allItems = []
while (cursor.getTime() <= end.getTime()) {
let s = formatDate(cursor)
btn.innerText = `${s} 数据请求中, 点击提前停止`
let items = await exportDay(s)
allItems.push(...items)
cursor.setDate(cursor.getDate() + 1)
if (cancel) {
btn.innerText = `已停止, 正在合并数据`
break
}
}
let content = allItems.map((v) => JSON.stringify(v)).join('\n')
let b = new Blob([content], { type: 'text/plain' })
let blink = URL.createObjectURL(b)
let fname = `礼物数据 ${formatDate(start)} ~ ${formatDate(cursor)}.txt`
download(fname, blink)
URL.revokeObjectURL(blink)
}
let pending = false
btn.onclick = () => {
if (pending) {
cancel = true
return
}
pending = true
cancel = false
Promise.resolve()
.then(handleExport)
.then(async () => {
btn.innerText = '请求完成'
await new Promise((rl) => setTimeout(rl, 2e3))
})
.catch((err) => {
let tip = err?.message ?? '未知错误, 请按F12打开控制台查看错误原因.'
alert(`导出出错, 错误: ${tip}.`)
})
.finally(() => {
btn.innerText = btnText
pending = false
})
}
bar.append(btn)
})
.then(() => {
alert('数据导出按钮已添加成功')
})
.catch((err) => {
console.error(err)
alert(`添加导出按钮失败, 错误: ${err?.message ?? '未知错误, 请按F12打开控制台查看错误原因.'}.`)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment