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

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

轉(zhuǎn)載 感謝 盛百凡 寫的動態(tài)評論區(qū)抽獎腳本工具的源代碼

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

/**

?* 抽獎號的日常2: 如何專業(yè)地評論區(qū)開獎

?*

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

?* @version 1.5.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));


? // 清空控制臺

? await wait(0);

? console.clear();


? // 用戶配置

? const USER_CONFIG = {

? ? // 本地保存所有評論

? ? SAVE_COMMENTS: true,

? ? // 單用戶累計評論數(shù)上限

? ? MAX_REPEAT: 5,

? ? // 用戶等級權重

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

? ? // 勛章等級權重

? ? 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

? ? },

? ? // 會員類型權重

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

? };


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

? const SYS_CONFIG = {

? ? // API請求間隔(毫秒)

? ? API_INTERVAL: 250

? };


? // 控制臺顏色

? 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(用戶等級 認證類型 會員類型)

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


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

? 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;

? ? }

? });


? // 控制臺格式化

? 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 `格式錯誤 ${styles}`;

? ? ? }

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

? ? ? css.push('');

? ? }

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

? };


? // 圖標格式

? const STYLE = {

? ? // 用戶等級

? ? LEVEL: new Proxy([

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

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

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

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

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

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

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

? ? ], styleHandler(styleA)),

? ? // 勛章等級

? ? 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, '粉絲')),

? ? // 認證類型

? ? OFFICIAL: new Proxy([

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

? ? ? ['個人', '#F6C851'],

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

? ? ], styleHandler(styleA)),

? ? // 會員類型

? ? VIP: new Proxy([

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

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

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

? ? ], styleHandler(styleA))

? };


? // 日志

? 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;

? };


? // 格式化時間

? 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}`;

? };


? // 檢查整數(shù)

? const ckInt = value => {

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

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

? ? }

? ? return value;

? };


? // 檢查字符串

? const ckStr = value => {

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

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

? ? }

? ? return value;

? };


? // 檢查時間

? const ckUnix = unix => {

? ? ckInt(unix);

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

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

? ? }

? ? return unix;

? };


? // 檢查用戶等級

? const ckLevel = level => {

? ? ckInt(level);

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

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

? ? }

? ? return level;

? };


? // 檢查勛章等級

? const ckMedal = medal => {

? ? ckInt(medal);

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

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

? ? }

? ? return medal;

? };


? // 檢查認證

? const ckOfficial = official => {

? ? ckInt(official);

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

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

? ? }

? ? return official + 1;

? };


? // 檢查會員

? const ckVip = vip => {

? ? ckInt(vip.vipType);

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

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

? ? }

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

? };


? // 加入全局配置

? const globalConfig = () => {

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

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

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

? ? ? // 恢復控制臺函數(shù)

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

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

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

? ? ? ? }

? ? ? }

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

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

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

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

? ? ? ? }

? ? ? }

? ? }

? ? // 提示頁面關閉

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

? ? ? event.preventDefault();

? ? ? return event.returnValue = false;

? ? });

? };


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

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

? ? for (const func of functions) {

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

? ? ? ? throw '重復運行開獎腳本,請刷新當前網(wǎng)頁頁面后再次嘗試。';

? ? ? }

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

? ? }

? };


? // 計算頁面評論區(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 '無法獲取全局變量';

? ? ? }

? ? ? return prop;

? ? };

? ? let info;

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

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

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

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

? ? ? }

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

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

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

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

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

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

? ? ? ? info = {web: '活動', 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 '不支持當前頁面';

? ? }

? ? 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;

? };


? // 獲取當前登錄用戶

? const getUser = async () => {

? ? const userRes = await $.ajax('https://api.bilibili.com/x/web-interface/nav', {

? ? ? type: 'GET',

? ? ? xhrFields: {withCredentials: true}

? ? });

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

? ? ? return undefined;

? ? }

? ? return userRes.data.uname;

? };


? // 獲取用戶與當前登錄用戶關系

? const getRelation = async uid => {

? ? await wait(SYS_CONFIG.API_INTERVAL);

? ? const relationRes = await $.ajax('https://api.bilibili.com/x/space/acc/relation', {

? ? ? type: 'GET',

? ? ? data: {mid: uid},

? ? ? xhrFields: {withCredentials: true}

? ? });

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

? ? ? throw '無法獲取用戶關系';

? ? }

? ? const relation = relationRes.data.be_relation;

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

? };


? // 計算單項權重

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

? ? LOGGER.group(`${title}權重 (相對百分比)`);

? ? let max = 0;

? ? let weights = [];

? ? for (const tag of styles) {

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

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

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

? ? ? }

? ? ? weights.push(w);

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

? ? }

? ? if (max === 0) {

? ? ? throw `${title}權重全為零`;

? ? }

? ? 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;

? };


? // 獲取單頁評論

? 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 $.ajax('https://api.bilibili.com/x/v2/reply/main', {

? ? ? type: 'GET',

? ? ? data: {type: type, oid: oid, next: next, mode: 2}

? ? });

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

? ? ? throw '無法獲取評論';

? ? }

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

? };


? // 保存評論

? 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 msg = ckStr(c.content.message);

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

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

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

? ? ? ? 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} 跳過`);

? ? ? }

? ? }

? };


? // 加載評論

? 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);

? ? }

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

? ? [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 '不存在有抽獎資格用戶';

? ? }

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

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

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

? };


? // 用戶分布

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

? ? LOGGER.group(`${title}分布 (有資格數(shù), %c無資格數(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();

? };


? // 用戶映射

? const mapToUser = user => ({

? ? UID: user.uid,

? ? 用戶名: user.name,

? ? 用戶等級: user.level,

? ? 勛章等級: user.medal,

? ? 認證類型: user.official,

? ? 會員類型: user.vip,

? ? 累計評論數(shù): user.details.size

? });


? // 評論映射

? const mapToDetail = detail => ({

? ? 評論ID: detail.rpid,

? ? 評論時間: formatTime(detail.ts),

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

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

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

? });


? // 用戶列表

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

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

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

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

? ? } else {

? ? ? LOGGER.table(list.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(['權重', (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(`[是否關注%c${USER_NAME}%c] %c已關注 %c${formatTime(relation.ts)}`, `color:${COLOR.PINK};font-weight:bold`, '', `color:${COLOR.GREEN}`, '');

? ? ? } else {

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

? ? ? }

? ? }

? ? LOGGER.log(`[首條評論位置鏈接] ${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}]`;

? ? }

? };


? // 保存所有評論

? const saveComments = rows => {

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

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

? };


? // 加權隨機排序

? 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)計評論

? const countCommentMap = () => {

? ? // 創(chuàng)建統(tǒng)計數(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 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++;

? ? ? ? if (USER_CONFIG.SAVE_COMMENTS) {

? ? ? ? ? rows.push([detail.rpid, formatTime(detail.ts), 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);

? ? // 用戶分布

? ? showDistribution('用戶等級', STYLE.LEVEL, level);

? ? showDistribution('勛章等級', STYLE.MEDAL, medal, 4);

? ? showDistribution('認證類型', STYLE.OFFICIAL, official);

? ? showDistribution('會員類型', STYLE.VIP, vip);

? ? // 特殊列表

? ? overLimits.sort(comparator(user => -user.details.size, user => user.uid));

? ? showUsers('超出累計評論數(shù)上限', overLimits);

? ? zeroWeights.sort(comparator(user => user.uid));

? ? showUsers('無權重', zeroWeights);

? ? fans.sort(comparator(user => -user.medal, user => user.uid));

? ? showUsers('勛章', fans);

? ? companies.sort(comparator(user => user.uid));

? ? showUsers('企業(yè)認證', companies);

? ? // 詳細評論

? ? if (USER_CONFIG.SAVE_COMMENTS) {

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

? ? ? rows.unshift(['評論ID', '評論時間', '評論內(nèi)容', '被點贊數(shù)', '被評論數(shù)', 'UID', '用戶名', '用戶等級', '勛章等級', '認證類型', '會員類型', '累計評論數(shù)', '相對權重']);

? ? ? saveComments(rows);

? ? }

? };


? // 評論加載完成提示

? const commentPrompt = () => {

? ? if (G.done) {

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

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

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

? ? } else {

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

? ? }

? };


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

? const resume = async () => {

? ? await wait(0);

? ? if (!G.done) {

? ? ? // ---- 加載評論 ----

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

? ? ? try {

? ? ? ? await loadCommentMap();

? ? ? } catch (e) {

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

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

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

? ? ? ? } else {

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

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

? ? ? ? }

? ? ? ? throw e;

? ? ? } finally {

? ? ? ? console.groupEnd();

? ? ? }

? ? ? // ---- 統(tǒng)計評論 ----

? ? ? LOGGER.group('統(tǒ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('成功重新打亂用戶順序,控制臺輸入%c draw(n) %c(n為正整數(shù)) 并回車以重新隨機抽取n位用戶。', `color:${COLOR.ORANGE}`, '');

? ? } else {

? ? ? commentPrompt();

? ? }

? };


? // [隨機抽取用戶]

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

? ? await wait(0);

? ? if (G.done) {

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

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

? ? ? }

? ? ? // ---- 隨機抽取用戶 ----

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

? ? ? let count = num;

? ? ? try {

? ? ? ? while (count > 0) {

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

? ? ? ? ? ? throw '無剩余有資格用戶';

? ? ? ? ? }

? ? ? ? ? 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(`成功隨機抽取${num}位用戶,控制臺輸入%c draw(n) %c(n為正整數(shù)) 并回車以繼續(xù)隨機抽取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(`評論區(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};


? // ---- 當前頁面 ----

? LOGGER.group('當前頁面');

? const INFO = computeCommentInfo();

? const USER_NAME = await getUser();

? LOGGER.groupEnd();


? // ---- 運行配置 ----

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

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

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

? if (USER_NAME === undefined) {

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

? ? console.warn('登錄后可自動驗證中獎用戶是否關注當前登錄用戶(需刷新頁面并重新運行開獎腳本)。');

? } else {

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

? }

? if (USER_CONFIG.SAVE_COMMENTS) {

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

? } else {

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

? }

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

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

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

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

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

? LOGGER.groupEnd();


? // ---- 運行確認 ----

? console.group('運行確認');

? if (!confirm(`確認在 本頁面評論區(qū) (按照控制臺所示配置) 開獎?`)) {

? ? throw '已取消';

? }

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

? console.groupEnd();


? // ---- 加載與統(tǒng)計評論 ----

? await resume();

})();


轉(zhuǎn)載 感謝 盛百凡 寫的動態(tài)評論區(qū)抽獎腳本工具的源代碼的評論 (共 條)

分享到微博請遵守國家法律
资溪县| 红桥区| 灵丘县| 汶上县| 北安市| 梧州市| 资源县| 武穴市| 贡嘎县| 论坛| 峡江县| 沁水县| 临清市| 弋阳县| 合作市| 当雄县| 聊城市| 阳朔县| 双桥区| 眉山市| 南部县| 略阳县| 博白县| 柘荣县| 怀柔区| 普宁市| 长子县| 新密市| 沽源县| 罗江县| 深水埗区| 德钦县| 济源市| 辉县市| 新平| 扶余县| 应用必备| 辽中县| 逊克县| 喀喇沁旗| 凤凰县|