最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

轉(zhuǎn)載 感謝 盛百凡 寫的動(dòng)態(tài)評(píng)論區(qū)抽獎(jiǎng)腳本工具源代碼 版本更新到1.7 可顯示Ip地址歸屬

2022-09-13 13:34 作者:善良使人健康快樂有錢  | 我要投稿

/**

?* 抽獎(jiǎng)號(hào)的日常2: 如何專業(yè)地評(píng)論區(qū)開獎(jiǎng)

?*

?* @author [盛百凡]{@link https://space.bilibili.com/14064125}

?* @version 1.7.0

?* @see [Weighted random sampling with a reservoir]{@link https://doi.org/10.1016/j.ipl.2005.11.003}

?*/

(async () => {

? 'use strict';

? // 暫停

? const wait = async delay => new Promise(resolve => setTimeout(resolve, delay));


? // 清空控制臺(tái)

? await wait(0);

? console.clear();


? // 用戶配置

? const USER_CONFIG = {

? ? // 本地保存所有評(píng)論

? ? SAVE_COMMENTS: true,

? ? // 獲取評(píng)論IP歸屬地

? ? GET_COMMENT_IP: true,

? ? // 單用戶累計(jì)評(píng)論數(shù)上限

? ? MAX_REPEAT: 5,

? ? // 用戶等級(jí)權(quán)重

? ? LEVEL_WEIGHT: {Lv0: 0, Lv1: 0, Lv2: 0, Lv3: 0.5, Lv4: 1, Lv5: 1, Lv6: 1},

? ? // 勛章等級(jí)權(quán)重

? ? MEDAL_WEIGHT: {

? ? ? Lv_0: 1,

? ? ? Lv_1: 1, Lv_2: 1, Lv_3: 1, Lv_4: 1, Lv_5: 1, Lv_6: 1, Lv_7: 1, Lv_8: 1, Lv_9: 1, Lv10: 1,

? ? ? Lv11: 1, Lv12: 1, Lv13: 1, Lv14: 1, Lv15: 1, Lv16: 1, Lv17: 1, Lv18: 1, Lv19: 1, Lv20: 1,

? ? ? Lv21: 1, Lv22: 1, Lv23: 1, Lv24: 1, Lv25: 1, Lv26: 1, Lv27: 1, Lv28: 1, Lv29: 1, Lv30: 1,

? ? ? Lv31: 1, Lv32: 1, Lv33: 1, Lv34: 1, Lv35: 1, Lv36: 1, Lv37: 1, Lv38: 1, Lv39: 1, Lv40: 1

? ? },

? ? // 會(huì)員類型權(quán)重

? ? VIP_WEIGHT: {普通: 1, 月度: 1, 年度: 1}

? };


? // 系統(tǒng)配置

? const SYS_CONFIG = {

? ? // API請(qǐng)求間隔(毫秒)

? ? API_INTERVAL: 250

? };


? // 控制臺(tái)顏色

? const COLOR = {

? ? RED: '#EE230D',

? ? PINK: '#FF8CC6',

? ? ORANGE: '#FF9201',

? ? GREEN: '#1DB100',

? ? BLUE: '#02A2FF',

? ? GRAY: '#D6D5D5'

? };


? // 格式百分比

? const stylePercent = (num, digits) => {

? ? if (num < 0 || num > 1) {

? ? ? throw `百分比[${num}]必須在[0, 1]之間`;

? ? }

? ? const maxLen = digits === 0 ? 3 : digits + 4;

? ? if (num === 0) {

? ? ? return {text: 'N/A'.padStart(maxLen, ' '), css: `color:${COLOR.GRAY}`};

? ? }

? ? return {text: (100 * num).toFixed(digits).padStart(maxLen, ' '), css: ''};

? };


? // 格式整數(shù)

? const styleInt = (num, maxLen, color) => ({text: num.toString().padStart(maxLen, ' '), css: color === undefined ? '' : `color:${color}`});


? // 格式A(用戶等級(jí) 認(rèn)證類型 會(huì)員類型)

? const styleA = (words, color, key) => ({key: key, text: words[0], css: `border-radius:3px;color:#FFFFFF;background:${color};padding:1px`});


? // 格式B(勛章等級(jí))

? const styleB = (words, color, key) => {

? ? const style = {

? ? ? key: key,

? ? ? prefix: {text: words[0], css: `border-radius:3px 0 0 3px;color:#FFFFFF;background:${color};padding:1px 0 1px 1px`},

? ? ? main: {text: words[1], css: `border-radius:0 3px 3px 0;border-style:solid;border-width:1px;border-color:${color};color:${color}`}

? ? };

? ? if (words[1].length === 1) {

? ? ? style.main.css += ';padding:0 0.5ch';

? ? }

? ? return style;

? };


? // 格式捕捉器

? const styleHandler = (style, prefix) => ({

? ? get(target, prop, receiver) {

? ? ? const origin = Reflect.get(target, prop, receiver);

? ? ? if (typeof prop === 'string' && Reflect.has(target, prop) && !Number.isNaN(+prop)) {

? ? ? ? if (prefix === undefined) {

? ? ? ? ? return style([origin[0]], origin[1], origin[0]);

? ? ? ? }

? ? ? ? return style([prefix, prop], origin[1], origin[0]);

? ? ? }

? ? ? return origin;

? ? }

? });


? // 控制臺(tái)格式化

? const consoleFormat = (styles, separator = '') => {

? ? // 轉(zhuǎn)義

? ? const escape = msg => {

? ? ? if (msg === null || msg === undefined) {

? ? ? ? return `%c${msg}`;

? ? ? }

? ? ? return '%c' + msg.toString().replaceAll('%', '%%');

? ? };

? ? const text = [];

? ? const css = [];

? ? for (const s of styles) {

? ? ? if (s === null || s === undefined || s === '') {

? ? ? ? continue;

? ? ? }

? ? ? if (typeof s === 'string') {

? ? ? ? text.push(escape(s));

? ? ? ? css.push('');

? ? ? } else if (s.main === undefined) {

? ? ? ? text.push(escape(s.text));

? ? ? ? css.push(s.css);

? ? ? } else if (s.prefix !== undefined) {

? ? ? ? text.push(escape(s.prefix.text) + escape(s.main.text));

? ? ? ? css.push(s.prefix.css, s.main.css);

? ? ? } else {

? ? ? ? throw `格式錯(cuò)誤 ${styles}`;

? ? ? }

? ? ? text.push(escape(separator));

? ? ? css.push('');

? ? }

? ? return {text: text.join(''), css: css};

? };


? // 圖標(biāo)格式

? const STYLE = {

? ? // 用戶等級(jí)

? ? LEVEL: new Proxy([

? ? ? ['Lv0', '#BFBFBF'],

? ? ? ['Lv1', '#BFBFBF'],

? ? ? ['Lv2', '#95DDB2'],

? ? ? ['Lv3', '#92D1E5'],

? ? ? ['Lv4', '#FFB37C'],

? ? ? ['Lv5', '#FF6C00'],

? ? ? ['Lv6', '#FF0000']

? ? ], styleHandler(styleA)),

? ? // 勛章等級(jí)

? ? MEDAL: new Proxy([

? ? ? ['Lv_0', '#BFBFBF'],

? ? ? ['Lv_1', '#5C968E'], ['Lv_2', '#5C968E'], ['Lv_3', '#5C968E'], ['Lv_4', '#5C968E'],

? ? ? ['Lv_5', '#5D7B9E'], ['Lv_6', '#5D7B9E'], ['Lv_7', '#5D7B9E'], ['Lv_8', '#5D7B9E'],

? ? ? ['Lv_9', '#8D7CA6'], ['Lv10', '#8D7CA6'], ['Lv11', '#8D7CA6'], ['Lv12', '#8D7CA6'],

? ? ? ['Lv13', '#BE6686'], ['Lv14', '#BE6686'], ['Lv15', '#BE6686'], ['Lv16', '#BE6686'],

? ? ? ['Lv17', '#C79D24'], ['Lv18', '#C79D24'], ['Lv19', '#C79D24'], ['Lv20', '#C79D24'],

? ? ? ['Lv21', '#1A544B'], ['Lv22', '#1A544B'], ['Lv23', '#1A544B'], ['Lv24', '#1A544B'],

? ? ? ['Lv25', '#06154C'], ['Lv26', '#06154C'], ['Lv27', '#06154C'], ['Lv28', '#06154C'],

? ? ? ['Lv29', '#2D0855'], ['Lv30', '#2D0855'], ['Lv31', '#2D0855'], ['Lv32', '#2D0855'],

? ? ? ['Lv33', '#7A0423'], ['Lv34', '#7A0423'], ['Lv35', '#7A0423'], ['Lv36', '#7A0423'],

? ? ? ['Lv37', '#FF610B'], ['Lv38', '#FF610B'], ['Lv39', '#FF610B'], ['Lv40', '#FF610B']

? ? ], styleHandler(styleB, '粉絲')),

? ? // 認(rèn)證類型

? ? OFFICIAL: new Proxy([

? ? ? ['普通', '#BFBFBF'],

? ? ? ['個(gè)人', '#F6C851'],

? ? ? ['企業(yè)', '#6FC4FA']

? ? ], styleHandler(styleA)),

? ? // 會(huì)員類型

? ? VIP: new Proxy([

? ? ? ['普通', '#BFBFBF'],

? ? ? ['月度', '#FDB8CC'],

? ? ? ['年度', '#FB7299']

? ? ], styleHandler(styleA))

? };


? // 原生請(qǐng)求

? const web = async (baseUrl, params, useCookie) => {

? ? const url = new URL(baseUrl);

? ? for (const [k, v] of Object.entries(params)) {

? ? ? url.searchParams.set(k, v);

? ? }

? ? const response = await fetch(url, {method: 'GET', credentials: useCookie ? 'include' : 'omit', referrerPolicy: 'no-referrer'});

? ? if (response.status !== 200) {

? ? ? throw `請(qǐng)求異常 ${response.status}`;

? ? }

? ? return await response.json();

? };


? // 日志

? const LOG = [];


? // 日志記錄器

? const LOGGER = {

? ? level: 0,

? ? plain_(msg) {

? ? ? if (msg === null || msg === undefined) {

? ? ? ? return `${msg}`;

? ? ? }

? ? ? return msg.toString().replaceAll('%c', '').replaceAll('%%', '%');

? ? },

? ? log_(msg) {

? ? ? LOG.push(this.plain_(msg));

? ? },

? ? group_(msg) {

? ? ? this.level++;

? ? ? LOG.push(`${'#'.repeat(this.level)} ${this.plain_(msg)}`);

? ? },

? ? log(msg, ...params) {

? ? ? this.log_(msg);

? ? ? console.log(msg, ...params);

? ? },

? ? table(data, properties) {

? ? ? this.log_('(暫不支持表格)');

? ? ? console.table(data, properties);

? ? },

? ? group(msg, ...params) {

? ? ? this.group_(msg);

? ? ? console.group(msg, ...params);

? ? },

? ? groupCollapsed(msg, ...params) {

? ? ? this.group_(msg);

? ? ? console.groupCollapsed(msg, ...params);

? ? },

? ? groupEnd() {

? ? ? if (this.level > 0) {

? ? ? ? this.level--;

? ? ? ? if (this.level === 0) {

? ? ? ? ? LOG.push('');

? ? ? ? }

? ? ? }

? ? ? console.groupEnd();

? ? }

? };


? // 比較數(shù)字

? const compareInt = (v1, v2) => v1 < v2 ? -1 : (v1 === v2 ? 0 : 1);


? // 比較器

? const comparator = (...extracts) => (c1, c2) => {

? ? for (const ext of extracts) {

? ? ? const res = compareInt(ext(c1), ext(c2));

? ? ? if (res !== 0) {

? ? ? ? return res;

? ? ? }

? ? }

? ? return 0;

? };


? // 格式化時(shí)間

? const formatTime = unix => {

? ? const d = new Date(unix * 1000);

? ? const year = d.getFullYear().toString();

? ? const month = (d.getMonth() + 1).toString().padStart(2, '0');

? ? const date = d.getDate().toString().padStart(2, '0');

? ? const hours = d.getHours().toString().padStart(2, '0');

? ? const minutes = d.getMinutes().toString().padStart(2, '0');

? ? const seconds = d.getSeconds().toString().padStart(2, '0');

? ? return `${year}-${month}-${date} ${hours}:${minutes}:${seconds}`;

? };


? // 格式化日期

? const formatDate = unix => {

? ? const d = new Date(unix * 1000);

? ? const year = d.getFullYear().toString();

? ? const month = (d.getMonth() + 1).toString().padStart(2, '0');

? ? const date = d.getDate().toString().padStart(2, '0');

? ? return `${year}-${month}-${date}`;

? };


? // 檢查整數(shù)

? const ckInt = value => {

? ? if (!Number.isInteger(value)) {

? ? ? throw `[${value}]非整數(shù)`;

? ? }

? ? return value;

? };


? // 檢查字符串

? const ckStr = value => {

? ? if (typeof value !== 'string') {

? ? ? throw `[${value}]非字符串`;

? ? }

? ? return value;

? };


? // 檢查IP

? const ckIp = location => {

? ? ckStr(location);

? ? return location.startsWith('IP屬地:') ? location.substring(5) : location;

? };


? // 檢查時(shí)間

? const ckUnix = unix => {

? ? ckInt(unix);

? ? if (unix.toString().length !== 10) {

? ? ? throw `時(shí)間[${unix}]必須為10位數(shù)`;

? ? }

? ? return unix;

? };


? // 檢查用戶等級(jí)

? const ckLevel = level => {

? ? ckInt(level);

? ? if (level < 0 || level > 6) {

? ? ? throw `用戶等級(jí)[${level}]必須在[0, 6]之間`;

? ? }

? ? return level;

? };


? // 檢查勛章等級(jí)

? const ckMedal = medal => {

? ? ckInt(medal);

? ? if (medal < 0 || medal > 40) {

? ? ? throw `勛章等級(jí)[${medal}]必須在[0, 40]之間`;

? ? }

? ? return medal;

? };


? // 檢查認(rèn)證

? const ckOfficial = official => {

? ? ckInt(official);

? ? if (official < -1 || official > 1) {

? ? ? throw `認(rèn)證類型[${official}]必須在[-1, 1]之間`;

? ? }

? ? return official + 1;

? };


? // 檢查會(huì)員

? const ckVip = vip => {

? ? ckInt(vip.vipType);

? ? if (vip.vipType < 0 || vip.vipType > 2) {

? ? ? throw `會(huì)員類型[${vip.vipType}]必須在[0, 2]之間`;

? ? }

? ? return vip.vipStatus === 0 ? 0 : vip.vipType;

? };


? // 加入全局配置

? const globalConfig = () => {

? ? // 關(guān)閉監(jiān)控

? ? if (window['Sentry']) {

? ? ? window['Sentry'].getCurrentHub().getClient().getOptions().enabled = false;

? ? ? // 恢復(fù)控制臺(tái)函數(shù)

? ? ? for (const name of ['debug', 'info', 'warn', 'error', 'log', 'assert']) {

? ? ? ? if (console[name].__sentry__) {

? ? ? ? ? console[name] = console[name].__sentry_original__;

? ? ? ? }

? ? ? }

? ? ? // 恢復(fù)全局函數(shù)

? ? ? for (const name of ['fetch', 'addEventListener', 'setTimeout']) {

? ? ? ? if (window[name].__sentry__) {

? ? ? ? ? window[name] = window[name].__sentry_original__;

? ? ? ? }

? ? ? }

? ? }

? ? // 提示頁(yè)面關(guān)閉

? ? addEventListener('beforeunload', event => {

? ? ? event.preventDefault();

? ? ? return event.returnValue = false;

? ? });

? };


? // 加入全局函數(shù)

? const globalFunctions = (...functions) => {

? ? for (const func of functions) {

? ? ? if (window[func.name] !== undefined) {

? ? ? ? throw '重復(fù)運(yùn)行開獎(jiǎng)腳本,請(qǐng)刷新當(dāng)前網(wǎng)頁(yè)頁(yè)面后再次嘗試。';

? ? ? }

? ? ? Object.defineProperty(window, func.name, {value: func});

? ? }

? };


? // 計(jì)算頁(yè)面評(píng)論區(qū)參數(shù)

? const computeCommentInfo = () => {

? ? const url = new URL(location);

? ? const host = url.hostname;

? ? const path = url.pathname;

? ? // 檢查路徑

? ? const ckPath = prefix => {

? ? ? return path.length > prefix.length && path.substring(0, prefix.length) === prefix;

? ? };

? ? // 提取ID

? ? const extId = prefix => {

? ? ? return ckInt(+path.substring(prefix.length));

? ? };

? ? // 檢查全局變量

? ? const ckW = prop => {

? ? ? if (prop === undefined || prop === null) {

? ? ? ? throw '無(wú)法獲取全局變量';

? ? ? }

? ? ? return prop;

? ? };

? ? let info;

? ? if (host === 't.bilibili.com';) {

? ? ? if (ckPath('/')) {

? ? ? ? const vue1 = document.querySelector('div.detail-card > div')?.__vue__?.cardData;

? ? ? ? if (vue1 !== undefined) {

? ? ? ? ? info = {web: '動(dòng)態(tài)', type: vue1.comment_id, oid: vue1.comment_oid};

? ? ? ? }

? ? ? ? const vue2 = document.querySelector('div.bili-dyn-item')?.__vue__?.data.basic;

? ? ? ? if (vue2 !== undefined) {

? ? ? ? ? info = {web: '動(dòng)態(tài)', type: vue2.comment_type, oid: vue2.comment_id_str};

? ? ? ? }

? ? ? }

? ? } else if (host === 'www.bilibili.com';) {

? ? ? if (ckPath('/video/')) {

? ? ? ? info = {web: '投稿視頻', type: 1, oid: ckW(window.aid ?? window.playerInfo?.aid)};

? ? ? } else if (ckPath('/bangumi/play/')) {

? ? ? ? info = {web: '版權(quán)視頻', type: 1, oid: ckW(window.aid ?? window.playerInfo?.aid)};

? ? ? } else if (ckPath('/blackboard/')) {

? ? ? ? info = {web: '活動(dòng)', type: 4, oid: ckW(window.activityId)};

? ? ? } else if (ckPath('/blackroom/ban/')) {

? ? ? ? info = {web: '小黑屋', type: 6, oid: extId('/blackroom/ban/')};

? ? ? } else if (ckPath('/read/cv')) {

? ? ? ? info = {web: '專欄', type: 12, oid: extId('/read/cv')};

? ? ? } else if (ckPath('/audio/au')) {

? ? ? ? info = {web: '音頻', type: 14, oid: extId('/audio/au')};

? ? ? } else if (ckPath('/audio/am')) {

? ? ? ? info = {web: '音頻列表', type: 19, oid: extId('/audio/am')};

? ? ? } else if (ckPath('/cheese/play/ep')) {

? ? ? ? info = {web: '課程', type: 33, oid: extId('/cheese/play/ep')};

? ? ? }

? ? } else if (host === 'h.bilibili.com';) {

? ? ? if (ckPath('/')) {

? ? ? ? info = {web: '相簿', type: 11, oid: extId('/')};

? ? ? }

? ? } else if (host === 'manga.bilibili.com';) {

? ? ? if (ckPath('/detail/mc')) {

? ? ? ? info = {web: '漫畫', type: 22, oid: extId('/detail/mc')};

? ? ? }

? ? }

? ? if (info === undefined) {

? ? ? throw '不支持當(dāng)前頁(yè)面';

? ? }

? ? url.hash = '';

? ? for (const [key, value] of new URL(url).searchParams) {

? ? ? if (key !== 'type') {

? ? ? ? url.searchParams.delete(key);

? ? ? }

? ? }

? ? info.url = url;

? ? LOGGER.log(`[類型] %c${info.web}`, `color:${COLOR.BLUE}`);

? ? LOGGER.log(`%c[url] ${info.url}`, `color:${COLOR.GRAY}`);

? ? LOGGER.log(`%c[type] ${info.type}`, `color:${COLOR.GRAY}`);

? ? LOGGER.log(`%c[oid] ${info.oid}`, `color:${COLOR.GRAY}`);

? ? return info;

? };


? // 獲取當(dāng)前登錄用戶

? const getUser = async () => {

? ? const userRes = await web('https://api.bilibili.com/x/web-interface/nav', {}, true);

? ? if (userRes.code !== 0) {

? ? ? return undefined;

? ? }

? ? return userRes.data.uname;

? };


? // 獲取用戶與當(dāng)前登錄用戶關(guān)系

? const getRelation = async uid => {

? ? await wait(SYS_CONFIG.API_INTERVAL);

? ? const relationRes = await web('https://api.bilibili.com/x/space/acc/relation', {mid: uid}, true);

? ? if (relationRes.code !== 0) {

? ? ? throw '無(wú)法獲取用戶關(guān)系';

? ? }

? ? const relation = relationRes.data.be_relation;

? ? return {followed: relation.attribute !== undefined && relation.attribute !== 0 && relation.attribute !== 128, ts: relation.mtime};

? };


? // 計(jì)算單項(xiàng)權(quán)重

? const computeWeights = (title, styles, weight, column = 1) => {

? ? LOGGER.group(`${title}權(quán)重 (相對(duì)百分比)`);

? ? let max = 0;

? ? let weights = [];

? ? for (const tag of styles) {

? ? ? const w = weight[tag.key];

? ? ? if (typeof w !== 'number' || w < 0) {

? ? ? ? throw `${title}權(quán)重[${tag.key}]必須為非負(fù)數(shù)`;

? ? ? }

? ? ? weights.push(w);

? ? ? max = Math.max(max, w);

? ? }

? ? if (max === 0) {

? ? ? throw `${title}權(quán)重全為零`;

? ? }

? ? weights = weights.map(w => w / max);

? ? let text = [];

? ? let css = [];

? ? for (let i = 0; i < weights.length; i++) {

? ? ? const format = consoleFormat([styles[i], stylePercent(weights[i], 1)], ' ');

? ? ? text.push(format.text);

? ? ? css.push(...format.css);

? ? ? if (i % column === 0) {

? ? ? ? LOGGER.log(text.join('? ?'), ...css);

? ? ? ? text = [];

? ? ? ? css = [];

? ? ? }

? ? }

? ? LOGGER.groupEnd();

? ? return weights;

? };


? // 獲取單頁(yè)評(píng)論

? const getPageComments = async (type, oid, next) => {

? ? await wait(SYS_CONFIG.API_INTERVAL);

? ? G.next = next;

? ? console.log(`%c[${G.call++}] ${next}`, `color:${COLOR.GRAY}`);

? ? const pageRes = await web('https://api.bilibili.com/x/v2/reply/main', {type: type, oid: oid, next: next, mode: 2}, USER_CONFIG.GET_COMMENT_IP);

? ? if (pageRes.code !== 0) {

? ? ? throw '無(wú)法獲取評(píng)論';

? ? }

? ? return [pageRes.data.cursor.is_end, pageRes.data.cursor.prev, pageRes.data.cursor.next, pageRes.data.replies ?? []];

? };


? // 保存評(píng)論

? const saveCommentsToMap = comments => {

? ? for (const [i, c] of comments.entries()) {

? ? ? try {

? ? ? ? const ts = ckUnix(c.ctime);

? ? ? ? if (ts > DRAW_TIME) {

? ? ? ? ? continue;

? ? ? ? }

? ? ? ? const rpid = ckInt(c.rpid);

? ? ? ? const ip = ckIp(c.reply_control?.location ?? '');

? ? ? ? const msg = ckStr(c.content.message);

? ? ? ? const like = ckInt(c.like);

? ? ? ? const reply = ckInt(c.count);

? ? ? ? const detail = {rpid, ts, ip, msg, like, reply};

? ? ? ? const uid = ckInt(c.mid);

? ? ? ? const user = G.uMap.get(uid);

? ? ? ? if (user === undefined) {

? ? ? ? ? const name = ckStr(c.member.uname);

? ? ? ? ? const level = ckLevel(c.member.level_info.current_level);

? ? ? ? ? const medal = ckMedal(c.member.fans_detail?.level ?? 0);

? ? ? ? ? const official = ckOfficial(c.member.official_verify.type);

? ? ? ? ? const vip = ckVip(c.member.vip);

? ? ? ? ? const weight = LEVEL_WEIGHTS[level] * MEDAL_WEIGHTS[medal] * VIP_WEIGHTS[vip];

? ? ? ? ? const details = new Map([[rpid, detail]]);

? ? ? ? ? G.uMap.set(uid, {uid, name, level, medal, official, vip, weight, details});

? ? ? ? } else {

? ? ? ? ? user.details.set(rpid, detail);

? ? ? ? }

? ? ? } catch (e) {

? ? ? ? console.warn(`[${G.call}] ${i} ${e} 跳過`);

? ? ? }

? ? }

? };


? // 加載評(píng)論

? const loadCommentMap = async () => {

? ? let [isEnd, prev, next, cs] = await getPageComments(INFO.type, INFO.oid, G.next);

? ? let mid = 0;

? ? while (!isEnd && prev !== 0 && next !== 0 && cs.length !== 0) {

? ? ? saveCommentsToMap(cs);

? ? ? mid = next + ((prev - next) >> 1);

? ? ? [isEnd, prev, next, cs] = await getPageComments(INFO.type, INFO.oid, next);

? ? }

? ? // 再次獲取最早評(píng)論 (B站BUG 最早評(píng)論有可能被遺漏)

? ? [isEnd, prev, next, cs] = await getPageComments(INFO.type, INFO.oid, mid);

? ? saveCommentsToMap(cs);

? };


? // 用戶概況

? const showSummary = total => {

? ? const pass = G.uList.length;

? ? const fail = G.uMap.size - pass;

? ? if (pass === 0) {

? ? ? throw '不存在有抽獎(jiǎng)資格用戶';

? ? }

? ? LOGGER.log(`[總評(píng)論數(shù)] ${total}`);

? ? LOGGER.log(`[有抽獎(jiǎng)資格用戶總數(shù)] %c${pass}`, `color:${COLOR.BLUE}`);

? ? LOGGER.log(`%c[無(wú)抽獎(jiǎng)資格用戶總數(shù)] ${fail}`, `color:${COLOR.GRAY}`);

? };


? // 用戶分布

? const showUserDistribution = (title, styles, summary, column = 1) => {

? ? LOGGER.group(`用戶分布 [${title}] (有資格數(shù), %c無(wú)資格數(shù)%c)`, `color:${COLOR.GRAY}`, '');

? ? const passLen = Math.max(...summary.passes).toString().length;

? ? const failLen = Math.max(...summary.fails).toString().length;

? ? let text = [];

? ? let css = [];

? ? for (let i = 0; i < summary.passes.length; i++) {

? ? ? const format = consoleFormat([styles[i], styleInt(summary.passes[i], passLen), styleInt(summary.fails[i], failLen, COLOR.GRAY)], ' ');

? ? ? text.push(format.text);

? ? ? css.push(...format.css);

? ? ? if (i % column === 0) {

? ? ? ? LOGGER.log(text.join('? ?'), ...css);

? ? ? ? text = [];

? ? ? ? css = [];

? ? ? }

? ? }

? ? LOGGER.groupEnd();

? };


? // 評(píng)論分布

? const showCommentDistribution = (title, keyName, map, compareFn) => {

? ? LOGGER.groupCollapsed(`評(píng)論分布 [${title}] (評(píng)論總數(shù), Lv0, Lv1, Lv2, Lv3, Lv4, Lv5, Lv6)`);

? ? const list = Array.from(map).map(([key, value]) => ({key, sum: value.reduce((a, b) => a + b, 0), value})).sort(compareFn);

? ? LOGGER.table(list.map(e => ({

? ? ? [keyName]: e.key,

? ? ? '評(píng)論總數(shù)': e.sum,

? ? ? 'Lv0': e.value[0],

? ? ? 'Lv1': e.value[1],

? ? ? 'Lv2': e.value[2],

? ? ? 'Lv3': e.value[3],

? ? ? 'Lv4': e.value[4],

? ? ? 'Lv5': e.value[5],

? ? ? 'Lv6': e.value[6]

? ? })));

? ? LOGGER.groupEnd();

? };


? // 用戶映射

? const mapToUser = user => ({

? ? UID: user.uid,

? ? 用戶名: user.name,

? ? 用戶等級(jí): user.level,

? ? 勛章等級(jí): user.medal,

? ? 認(rèn)證類型: user.official,

? ? 會(huì)員類型: user.vip,

? ? 累計(jì)評(píng)論數(shù): user.details.size

? });


? // 評(píng)論映射

? const mapToDetail = detail => ({

? ? 評(píng)論ID: detail.rpid,

? ? 評(píng)論時(shí)間: formatTime(detail.ts),

? ? 評(píng)論IP: detail.ip,

? ? 評(píng)論內(nèi)容: detail.msg,

? ? 被點(diǎn)贊數(shù): detail.like,

? ? 被評(píng)論數(shù): detail.reply

? });


? // 用戶列表

? const showUsers = (title, list, compareFn) => {

? ? LOGGER.groupCollapsed(`用戶列表 [${title}] (${list.length})`);

? ? if (list.length === 0) {

? ? ? LOGGER.log('%cN/A', `color:${COLOR.GRAY}`);

? ? } else {

? ? ? LOGGER.table(list.sort(compareFn).map(mapToUser));

? ? }

? ? LOGGER.groupEnd();

? };


? // 展示用戶

? const displayUser = async user => {

? ? const details = Array.from(user.details.values()).sort(comparator(detail => detail.ts, detail => detail.rpid));

? ? const replyUrl = new URL(INFO.url);

? ? replyUrl.hash = `reply${details[0].rpid}`;


? ? const level = STYLE.LEVEL[user.level];

? ? const medal = user.medal === 0 ? undefined : STYLE.MEDAL[user.medal];

? ? const official = user.official === 0 ? undefined : STYLE.OFFICIAL[user.official];

? ? const vip = user.vip === 0 ? undefined : STYLE.VIP[user.vip];

? ? const uid = styleB(['UID', user.uid], '#8A8A8A');

? ? const name = {text: user.name, css: 'font-weight:bold'};

? ? const weight = styleB(['權(quán)重', (100 * user.weight).toFixed(1)], '#FEFEFE');

? ? const prob = styleB(['概率', (100 * user.weight / G.uWeight).toFixed(6)], '#FEFEFE');

? ? const format = consoleFormat([level, medal, official, vip, uid, name, weight, prob], ' ');

? ? LOGGER.log(format.text, ...format.css);


? ? if (USER_NAME !== undefined) {

? ? ? const relation = await getRelation(user.uid);

? ? ? if (relation.followed) {

? ? ? ? LOGGER.log(`[是否關(guān)注%c${USER_NAME}%c] %c已關(guān)注 %c${formatTime(relation.ts)}`, `color:${COLOR.PINK};font-weight:bold`, '', `color:${COLOR.GREEN}`, '');

? ? ? } else {

? ? ? ? LOGGER.log(`[是否關(guān)注%c${USER_NAME}%c] %c未關(guān)注`, `color:${COLOR.PINK};font-weight:bold`, '', `color:${COLOR.RED}`);

? ? ? }

? ? }

? ? LOGGER.log(`[首條評(píng)論位置鏈接] ${replyUrl}`);

? ? LOGGER.table(details.map(mapToDetail));

? };


? // 保存文件

? const save = (data, fileName, fileType) => {

? ? const link = document.createElement('a');

? ? link.download = fileName;

? ? const blob = new Blob([data], {type: fileType});

? ? link.href = URL.createObjectURL(blob);

? ? link.click();

? ? URL.revokeObjectURL(link.href);

? };


? // CSV映射

? const mapToCSV = prop => {

? ? switch (typeof prop) {

? ? ? case 'undefined':

? ? ? ? return '';

? ? ? case 'number':

? ? ? ? return `"${prop}"`;

? ? ? case 'string':

? ? ? ? return `"${prop.replaceAll('"', '""').replaceAll('\r', '\u23CE').replaceAll('\n', '\u23CE')}"`;

? ? ? default:

? ? ? ? throw `不支持格式[${typeof prop}]`;

? ? }

? };


? // 保存所有評(píng)論

? const saveComments = rows => {

? ? const data = rows.map(row => row.map(mapToCSV).join(',')).join('\r\n') + '\r\n';

? ? save(data, `評(píng)論_${INFO.type}_${INFO.oid}_${DRAW_TIME}.csv`, 'text/csv');

? };


? // 加權(quán)隨機(jī)排序

? const weightedRandomShuffle = () => {

? ? const MAX = 0x4000;

? ? const ua = new Uint32Array(MAX);

? ? for (const [i, user] of G.uList.entries()) {

? ? ? const index = i % MAX;

? ? ? if (index === 0) {

? ? ? ? window.crypto.getRandomValues(ua);

? ? ? }

? ? ? user.key = -Math.log((ua[index] + 1) / 0x100000001) / user.weight;

? ? }

? ? G.uList.sort(comparator(user => user.key));

? };


? // 統(tǒng)計(jì)評(píng)論

? const countCommentMap = () => {

? ? // 創(chuàng)建統(tǒng)計(jì)數(shù)組

? ? const newSummary = length => ({passes: new Array(length).fill(0), fails: new Array(length).fill(0)});

? ? let total = 0;

? ? const level = newSummary(7);

? ? const medal = newSummary(41);

? ? const official = newSummary(3);

? ? const vip = newSummary(3);

? ? const dateMap = new Map();

? ? const ipMap = new Map();

? ? const overLimits = [];

? ? const zeroWeights = [];

? ? const fans = [];

? ? const companies = [];

? ? const rows = [];

? ? for (const [uid, user] of G.uMap) {

? ? ? if (user.medal > 0) {

? ? ? ? fans.push(user);

? ? ? }

? ? ? if (user.official === 2) {

? ? ? ? companies.push(user);

? ? ? }

? ? ? if (user.details.size > USER_CONFIG.MAX_REPEAT || user.weight === 0) {

? ? ? ? level.fails[user.level]++;

? ? ? ? medal.fails[user.medal]++;

? ? ? ? official.fails[user.official]++;

? ? ? ? vip.fails[user.vip]++;

? ? ? ? if (user.details.size > USER_CONFIG.MAX_REPEAT) {

? ? ? ? ? overLimits.push(user);

? ? ? ? }

? ? ? ? if (user.weight === 0) {

? ? ? ? ? zeroWeights.push(user);

? ? ? ? }

? ? ? } else {

? ? ? ? level.passes[user.level]++;

? ? ? ? medal.passes[user.medal]++;

? ? ? ? official.passes[user.official]++;

? ? ? ? vip.passes[user.vip]++;

? ? ? ? G.uWeight += user.weight;

? ? ? ? G.uList.push(user);

? ? ? }

? ? ? for (const [rpid, detail] of user.details) {

? ? ? ? total++;

? ? ? ? const date = formatDate(detail.ts);

? ? ? ? const dates = dateMap.get(date);

? ? ? ? if (dates === undefined) {

? ? ? ? ? dateMap.set(date, Array.from({length: 7}, (e, i) => user.level === i ? 1 : 0));

? ? ? ? } else {

? ? ? ? ? dates[user.level]++;

? ? ? ? }

? ? ? ? const ips = ipMap.get(detail.ip);

? ? ? ? if (ips === undefined) {

? ? ? ? ? ipMap.set(detail.ip, Array.from({length: 7}, (e, i) => user.level === i ? 1 : 0));

? ? ? ? } else {

? ? ? ? ? ips[user.level]++;

? ? ? ? }

? ? ? ? if (USER_CONFIG.SAVE_COMMENTS) {

? ? ? ? ? rows.push([detail.rpid, formatTime(detail.ts), detail.ip, detail.msg, detail.like, detail.reply, user.uid, user.name, user.level, user.medal, user.official, user.vip, user.details.size, user.weight]);

? ? ? ? }

? ? ? }

? ? }

? ? // 打亂用戶順序

? ? weightedRandomShuffle();

? ? // 參與人數(shù)

? ? showSummary(total);

? ? // 用戶分布

? ? showUserDistribution('用戶等級(jí)', STYLE.LEVEL, level);

? ? showUserDistribution('勛章等級(jí)', STYLE.MEDAL, medal, 4);

? ? showUserDistribution('認(rèn)證類型', STYLE.OFFICIAL, official);

? ? showUserDistribution('會(huì)員類型', STYLE.VIP, vip);

? ? // 評(píng)論分布

? ? showCommentDistribution('日期', '評(píng)論日期', dateMap, comparator(e => e.key));

? ? showCommentDistribution('IP歸屬地', '評(píng)論IP', ipMap, comparator(e => -e.sum, e => e.key));

? ? // 用戶列表

? ? showUsers('超出累計(jì)評(píng)論數(shù)上限', overLimits, comparator(user => -user.details.size, user => user.uid));

? ? showUsers('無(wú)權(quán)重', zeroWeights, comparator(user => user.uid));

? ? showUsers('勛章', fans, comparator(user => -user.medal, user => user.uid));

? ? showUsers('企業(yè)認(rèn)證', companies, comparator(user => user.uid));

? ? // 詳細(xì)評(píng)論

? ? if (USER_CONFIG.SAVE_COMMENTS) {

? ? ? rows.sort(comparator(row => row[1], row => row[0]));

? ? ? rows.unshift(['評(píng)論ID', '評(píng)論時(shí)間', '評(píng)論IP', '評(píng)論內(nèi)容', '被點(diǎn)贊數(shù)', '被評(píng)論數(shù)', 'UID', '用戶名', '用戶等級(jí)', '勛章等級(jí)', '認(rèn)證類型', '會(huì)員類型', '累計(jì)評(píng)論數(shù)', '相對(duì)權(quán)重']);

? ? ? saveComments(rows);

? ? }

? };


? // 評(píng)論加載完成提示

? const commentPrompt = () => {

? ? if (G.done) {

? ? ? console.warn('加載與統(tǒng)計(jì)評(píng)論已完成,控制臺(tái)輸入%c draw(n) %c(n為正整數(shù)) 并回車以隨機(jī)抽取n位用戶。', `color:${COLOR.ORANGE}`, '');

? ? ? console.warn('控制臺(tái)輸入%c shuffle() %c并回車以重新打亂用戶順序。', `color:${COLOR.ORANGE}`, '');

? ? ? console.warn('控制臺(tái)輸入%c saveLog() %c并回車以保存當(dāng)前控制臺(tái)所有顯示內(nèi)容。', `color:${COLOR.ORANGE}`, '');

? ? } else {

? ? ? console.warn('加載與統(tǒng)計(jì)評(píng)論未完成,請(qǐng)嘗試控制臺(tái)輸入%c resume() %c并回車以繼續(xù)加載評(píng)論。', `color:${COLOR.ORANGE}`, '');

? ? }

? };


? // [加載與統(tǒng)計(jì)評(píng)論]

? const resume = async () => {

? ? await wait(0);

? ? if (!G.done) {

? ? ? // ---- 加載評(píng)論 ----

? ? ? console.group('加載評(píng)論');

? ? ? try {

? ? ? ? await loadCommentMap();

? ? ? } catch (e) {

? ? ? ? if (e.status === 412) {

? ? ? ? ? console.error('觸發(fā)B站安全風(fēng)控策略,當(dāng)前IP被暫時(shí)屏蔽。%c請(qǐng)勿關(guān)閉或刷新本頁(yè)面%c,以防丟失加載進(jìn)度。', `color:${COLOR.ORANGE}`, '');

? ? ? ? ? console.error('請(qǐng)更換IP或等待1小時(shí)后,控制臺(tái)輸入%c resume() %c并回車以繼續(xù)加載評(píng)論。', `color:${COLOR.ORANGE}`, '');

? ? ? ? } else {

? ? ? ? ? console.error('發(fā)生未知錯(cuò)誤。%c請(qǐng)勿關(guān)閉或刷新本頁(yè)面%c,以防丟失加載進(jìn)度。', `color:${COLOR.ORANGE}`, '');

? ? ? ? ? console.error('控制臺(tái)輸入%c resume() %c并回車以繼續(xù)加載評(píng)論。', `color:${COLOR.ORANGE}`, '');

? ? ? ? }

? ? ? ? throw e;

? ? ? } finally {

? ? ? ? console.groupEnd();

? ? ? }

? ? ? // ---- 統(tǒng)計(jì)評(píng)論 ----

? ? ? LOGGER.group('統(tǒng)計(jì)評(píng)論');

? ? ? countCommentMap();

? ? ? LOGGER.groupEnd();

? ? ? G.done = true;

? ? }

? ? commentPrompt();

? };


? // [保存日志]

? const saveLog = () => {

? ? save(LOG.join('\r\n') + '\r\n', `日志_${INFO.type}_${INFO.oid}_${DRAW_TIME}.txt`, 'text/plain');

? };


? // [重新打亂用戶順序]

? const shuffle = () => {

? ? if (G.done) {

? ? ? G.dIndex = 0;

? ? ? weightedRandomShuffle();

? ? ? console.warn('成功重新打亂用戶順序,控制臺(tái)輸入%c draw(n) %c(n為正整數(shù)) 并回車以重新隨機(jī)抽取n位用戶。', `color:${COLOR.ORANGE}`, '');

? ? } else {

? ? ? commentPrompt();

? ? }

? };


? // [隨機(jī)抽取用戶]

? const draw = async (num = 1) => {

? ? await wait(0);

? ? if (G.done) {

? ? ? if (!Number.isInteger(num) || num <= 0) {

? ? ? ? throw `用戶數(shù)量[${num}]必須為正整數(shù)`;

? ? ? }

? ? ? // ---- 隨機(jī)抽取用戶 ----

? ? ? LOGGER.group(`隨機(jī)抽取用戶(${num})`);

? ? ? let count = num;

? ? ? try {

? ? ? ? while (count > 0) {

? ? ? ? ? if (G.dIndex === G.uList.length) {

? ? ? ? ? ? throw '無(wú)剩余有資格用戶';

? ? ? ? ? }

? ? ? ? ? LOGGER.group(`%c第${G.dIndex + 1}名`, `color:${COLOR.BLUE}`);

? ? ? ? ? const user = G.uList[G.dIndex];

? ? ? ? ? await displayUser(user);

? ? ? ? ? LOGGER.groupEnd();

? ? ? ? ? count--;

? ? ? ? ? G.dIndex++;

? ? ? ? }

? ? ? } finally {

? ? ? ? LOGGER.groupEnd();

? ? ? }

? ? ? console.warn(`成功隨機(jī)抽取${num}位用戶,控制臺(tái)輸入%c draw(n) %c(n為正整數(shù)) 并回車以繼續(xù)隨機(jī)抽取n位用戶。`, `color:${COLOR.ORANGE}`, '');

? ? } else {

? ? ? commentPrompt();

? ? }

? };


? // [查找用戶]

? const search = key => {

? ? if (G.done) {

? ? ? console.group(`查找用戶[${key}]`);

? ? ? let res = G.uMap.get(key);

? ? ? if (res === undefined) {

? ? ? ? for (const [uid, user] of G.uMap) {

? ? ? ? ? if (user.name === key) {

? ? ? ? ? ? res = user;

? ? ? ? ? ? break;

? ? ? ? ? }

? ? ? ? }

? ? ? }

? ? ? if (res === undefined) {

? ? ? ? console.warn(`評(píng)論區(qū)不存在用戶[${key}]`);

? ? ? } else {

? ? ? ? const details = Array.from(res.details.values()).sort(comparator(detail => detail.ts, detail => detail.rpid));

? ? ? ? console.table([res].map(mapToUser));

? ? ? ? console.table(details.map(mapToDetail));

? ? ? }

? ? ? console.groupEnd();

? ? } else {

? ? ? commentPrompt();

? ? }

? };


? // ---- 窗口管理 ----

? globalConfig();

? globalFunctions(resume, saveLog, shuffle, draw, search);

? const G = {call: 1, next: 0, done: false, uMap: new Map(), uWeight: 0, uList: [], dIndex: 0};


? // ---- 當(dāng)前頁(yè)面 ----

? LOGGER.group('當(dāng)前頁(yè)面');

? const INFO = computeCommentInfo();

? const USER_NAME = await getUser();

? LOGGER.groupEnd();


? // ---- 運(yùn)行配置 ----

? LOGGER.group('運(yùn)行配置');

? const DRAW_TIME = Math.trunc(Date.now() / 1000);

? LOGGER.log(`[開獎(jiǎng)時(shí)間] %c${formatTime(DRAW_TIME)}`, `color:${COLOR.BLUE}`);

? if (USER_NAME === undefined) {

? ? LOGGER.log('[當(dāng)前登錄用戶] %c未登錄', `color:${COLOR.GRAY};font-weight:bold`);

? ? console.warn('登錄后可自動(dòng)驗(yàn)證中獎(jiǎng)用戶是否關(guān)注當(dāng)前登錄用戶(需刷新頁(yè)面并重新運(yùn)行開獎(jiǎng)腳本)。');

? } else {

? ? LOGGER.log(`[當(dāng)前登錄用戶] %c${USER_NAME}`, `color:${COLOR.PINK};font-weight:bold`);

? }

? if (USER_CONFIG.SAVE_COMMENTS) {

? ? LOGGER.log('[本地保存所有評(píng)論] %c保存', `color:${COLOR.GREEN}`);

? } else {

? ? LOGGER.log('[本地保存所有評(píng)論] %c不保存', `color:${COLOR.RED}`);

? }

? if (USER_CONFIG.GET_COMMENT_IP) {

? ? LOGGER.log('[獲取評(píng)論IP歸屬地] %c獲取', `color:${COLOR.GREEN}`);

? ? if (USER_NAME === undefined) {

? ? ? console.warn('登錄后才能獲取評(píng)論IP歸屬地(需刷新頁(yè)面并重新運(yùn)行開獎(jiǎng)腳本)。');

? ? }

? } else {

? ? LOGGER.log('[獲取評(píng)論IP歸屬地] %c不獲取', `color:${COLOR.RED}`);

? }

? LOGGER.log(`[單用戶累計(jì)評(píng)論數(shù)上限] ${USER_CONFIG.MAX_REPEAT}`);

? LOGGER.log(`%c[API請(qǐng)求間隔] ${SYS_CONFIG.API_INTERVAL}毫秒`, `color:${COLOR.GRAY}`);

? const LEVEL_WEIGHTS = computeWeights('用戶等級(jí)', STYLE.LEVEL, USER_CONFIG.LEVEL_WEIGHT);

? const MEDAL_WEIGHTS = computeWeights('勛章等級(jí)', STYLE.MEDAL, USER_CONFIG.MEDAL_WEIGHT, 4);

? const VIP_WEIGHTS = computeWeights('會(huì)員類型', STYLE.VIP, USER_CONFIG.VIP_WEIGHT);

? LOGGER.groupEnd();


? // ---- 運(yùn)行確認(rèn) ----

? console.group('運(yùn)行確認(rèn)');

? if (!confirm(`確認(rèn)在 本頁(yè)面評(píng)論區(qū) (按照控制臺(tái)所示配置) 開獎(jiǎng)?`)) {

? ? throw '已取消';

? }

? console.log('%c已確認(rèn)', `color:${COLOR.GREEN}`);

? console.groupEnd();


? // ---- 加載與統(tǒng)計(jì)評(píng)論 ----

? await resume();

})();


轉(zhuǎn)載 感謝 盛百凡 寫的動(dòng)態(tài)評(píng)論區(qū)抽獎(jiǎng)腳本工具源代碼 版本更新到1.7 可顯示Ip地址歸屬的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
林周县| 沙田区| 安西县| 萍乡市| 东明县| 新田县| 宁都县| 五寨县| 会宁县| 白朗县| 乌拉特后旗| 奉化市| 沧源| 榆中县| 怀集县| 克东县| 华池县| 乌拉特中旗| 凉城县| 宁陕县| 建瓯市| 西安市| 新昌县| 施甸县| 灌南县| 石阡县| 扎鲁特旗| 防城港市| 鄂托克旗| 苏尼特左旗| 东城区| 利津县| 曲靖市| 五原县| 钟山县| 银川市| 霍林郭勒市| 汝阳县| 天等县| 武夷山市| 阳朔县|