- // ==UserScript==
- // @name 百度贴吧点赞数据显示
- // @namespace http://tampermonkey.net/
- // @version 0.1
- // @description 在百度贴吧显示主题帖、楼层的点赞数据
- // @author noahacgn
- // @match *://tieba.baidu.com/p/*
- // @grant GM_xmlhttpRequest
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_registerMenuCommand
- // @connect tiebac.baidu.com
- // @license MIT
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // 配置信息
- const config = {
- BDUSS: '', // 用户手动填写BDUSS
- CLIENT_INFO: {
- _client_version: "12.64.1.1",
- _client_type: "2"
- },
- BASE_URL: "http://tiebac.baidu.com",
- lastUrl: location.href, // 记录上一次处理的URL
- postDataCache: {}, // 缓存帖子数据
- isProcessing: false // 是否正在处理数据
- };
-
- // 初始化
- function init() {
- console.log("百度贴吧点赞数据显示脚本初始化...");
- // 从GM存储中获取BDUSS
- config.BDUSS = GM_getValue('BDUSS', '');
-
- // 如果没有BDUSS,则提示用户设置
- if (!config.BDUSS) {
- showBDUSSPrompt();
- } else {
- // 获取当前帖子ID
- const tid = getTidFromUrl();
- if (tid) {
- console.log("获取到帖子ID:", tid);
- // 获取帖子点赞数据
- getPostData(tid);
- } else {
- console.error("无法从URL获取帖子ID");
- }
- }
- }
-
- // 显示BDUSS设置提示
- function showBDUSSPrompt() {
- const userBDUSS = prompt("请输入您的BDUSS值以获取点赞数据:", "");
- if (userBDUSS) {
- config.BDUSS = userBDUSS;
- GM_setValue('BDUSS', userBDUSS);
-
- // 获取当前帖子ID
- const tid = getTidFromUrl();
- if (tid) {
- // 获取帖子点赞数据
- getPostData(tid);
- }
- }
- }
-
- // 从URL获取帖子ID
- function getTidFromUrl() {
- const match = window.location.href.match(/p\/(\d+)/);
- return match ? match[1] : null;
- }
-
- // 获取当前页码
- function getCurrentPage() {
- const match = window.location.href.match(/pn=(\d+)/);
- return match ? parseInt(match[1]) : 1;
- }
-
- // 加密签名函数
- function generateSign(params) {
- // 先排序参数
- const sortedParams = new URLSearchParams(params);
- sortedParams.sort();
-
- // 构建签名字符串
- const signStr = Array.from(sortedParams.entries())
- .map(entry => entry.join('='))
- .join('') + "tiebaclient!!!";
-
- // 计算MD5
- return md5(signStr).toUpperCase();
- }
-
- // 打包请求参数,添加签名
- function packRequest(params) {
- const reqParams = { ...params, ...config.CLIENT_INFO };
-
- // 添加BDUSS
- if (!reqParams.BDUSS) {
- reqParams.BDUSS = config.BDUSS;
- }
-
- // 生成签名
- const sign = generateSign(reqParams);
- reqParams.sign = sign;
-
- return new URLSearchParams(reqParams).toString();
- }
-
- // 使用GM_xmlhttpRequest调用百度贴吧API
- function fetchAPI(url, data, method = "GET") {
- return new Promise((resolve, reject) => {
- let apiUrl = config.BASE_URL + url;
- const fetchOptions = {
- method: method,
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- },
- onload: function(response) {
- if (response.status >= 200 && response.status < 300) {
- try {
- const responseData = JSON.parse(response.responseText);
- resolve(responseData);
- } catch (error) {
- reject(new Error("API返回数据解析失败: " + error.message));
- }
- } else {
- reject(new Error(`HTTP错误! 状态: ${response.status}`));
- }
- },
- onerror: function(error) {
- reject(new Error("API请求失败: " + error.message));
- }
- };
-
- // 根据方法添加不同的参数
- if (method === "GET") {
- if (data) {
- apiUrl += `?${data}`;
- }
- } else if (method === "POST") {
- fetchOptions.data = data;
- }
-
- GM_xmlhttpRequest({
- ...fetchOptions,
- url: apiUrl
- });
- });
- }
-
- // 获取帖子数据(包含点赞信息)
- async function getPostData(tid) {
- try {
- if (config.isProcessing) {
- console.log("正在处理数据,稍后再试");
- return;
- }
-
- config.isProcessing = true;
- const currentPage = getCurrentPage();
- console.log(`开始获取帖子数据,帖子ID: ${tid},页码: ${currentPage}`);
-
- // 构造请求参数
- const params = {
- kz: tid,
- pn: currentPage,
- rn: 30, // 每页回复数量
- r: 3, // 排序方式: 3为时间正序
- BDUSS: config.BDUSS
- };
-
- // 发起请求
- const data = await fetchAPI("/c/f/pb/page", packRequest(params), "POST");
-
- console.log("获取到帖子数据:", data);
-
- if (!data || data.error) {
- throw new Error(`获取帖子数据失败: ${data?.error_msg || '未知错误'}`);
- }
-
- // 缓存帖子数据
- const cacheKey = `${tid}_${currentPage}`;
- config.postDataCache[cacheKey] = data;
-
- // 使用智能延迟处理显示
- setTimeout(() => {
- processPostData(tid, currentPage);
- }, 500);
-
- return data;
- } catch (error) {
- console.error("获取帖子点赞数据失败:", error);
- config.isProcessing = false;
- return null;
- }
- }
-
- // 处理帖子数据并显示点赞信息
- function processPostData(tid, page) {
- const cacheKey = `${tid}_${page}`;
- const data = config.postDataCache[cacheKey];
-
- if (!data) {
- console.error("缓存中找不到帖子数据:", cacheKey);
- config.isProcessing = false;
- return;
- }
-
- try {
- // 只在第一页显示主题帖点赞数据
- if (page === 1 && data.thread) {
- displayThreadLikeCount(data.thread);
- }
-
- // 检查页面是否已经加载完成
- const postsLoaded = checkPostsLoaded(data);
-
- if (postsLoaded) {
- console.log("页面已加载完成,显示点赞数据");
- // 显示回复的点赞数据
- if (data.post_list && Array.isArray(data.post_list) && data.post_list.length > 0) {
- data.post_list.forEach(post => {
- if (!post) return;
- displayPostLikeCount(post);
- });
- }
-
- // 处理完成
- config.isProcessing = false;
- } else {
- console.log("页面尚未加载完成,500ms后重试");
- // 如果页面未加载完,延迟重试
- setTimeout(() => {
- processPostData(tid, page);
- }, 500);
- }
- } catch (error) {
- console.error("处理帖子数据出错:", error);
- config.isProcessing = false;
- }
- }
-
- // 检查页面是否加载完成
- function checkPostsLoaded(data) {
- if (!data.post_list || !Array.isArray(data.post_list) || data.post_list.length === 0) {
- return true; // 没有回复,认为加载完成
- }
-
- // 检查页面是否已加载数据中的第一个和最后一个帖子
- const firstPost = data.post_list[0];
- const lastPost = data.post_list[data.post_list.length - 1];
-
- if (!firstPost || !lastPost || !firstPost.id || !lastPost.id) {
- return false;
- }
-
- const firstPostElement = document.querySelector(`.l_post[data-pid="${firstPost.id}"]`);
- const lastPostElement = document.querySelector(`.l_post[data-pid="${lastPost.id}"]`);
-
- return firstPostElement && lastPostElement;
- }
-
- // 显示主题帖点赞数
- function displayThreadLikeCount(thread) {
- const titleElement = document.querySelector('.core_title_txt.pull-left.text-overflow');
- if (titleElement) {
- const likeCount = thread.agree?.agree_num || 0;
- // 检查是否已经添加了点赞计数
- const existingLikeElement = document.querySelector('.thread-like-count');
- if (existingLikeElement) {
- return;
- }
-
- const likeElement = document.createElement('span');
- likeElement.className = 'thread-like-count';
- likeElement.innerHTML = `<span style="color: #E74C3C; margin-left: 10px;"><i style="font-size: 14px; margin-right: 3px;">❤</i>${likeCount}</span>`;
- titleElement.after(likeElement);
-
- console.log("已显示主题帖点赞数:", likeCount);
- }
- }
-
- // 显示楼层点赞数
- function displayPostLikeCount(post) {
- if (!post || !post.id) {
- console.warn("无效的回复数据:", post);
- return;
- }
-
- const postElement = document.querySelector(`.l_post[data-pid="${post.id}"]`);
- if (postElement) {
- const tailElement = postElement.querySelector('.core_reply_tail');
- if (tailElement) {
- // 检查是否已经添加了点赞计数
- const existingLikeElement = tailElement.querySelector('.post-like-count');
- if (existingLikeElement) {
- return;
- }
-
- const likeCount = post.agree?.agree_num || 0;
- const likeElement = document.createElement('span');
- likeElement.className = 'post-like-count';
- likeElement.innerHTML = `<span class="tail-info" style="color: #E74C3C;"><i style="font-size: 12px; margin-right: 3px;">❤</i>${likeCount}</span>`;
-
- const timeElement = tailElement.querySelector('.tail-info:last-of-type');
- if (timeElement) {
- timeElement.after(likeElement);
- } else {
- const postTailWrap = tailElement.querySelector('.post-tail-wrap');
- if (postTailWrap) {
- postTailWrap.appendChild(likeElement);
- }
- }
-
- console.log("已显示楼层点赞数,回复ID:", post.id, "点赞数:", likeCount);
- }
- } else {
- console.warn("未找到对应的楼层元素,回复ID:", post.id);
- }
- }
-
- // 检查URL变化
- function checkUrlChange() {
- if (config.lastUrl !== location.href) {
- console.log("URL已变化,从", config.lastUrl, "变为", location.href);
- config.lastUrl = location.href;
-
- // 清除处理中状态
- config.isProcessing = false;
-
- // 获取当前帖子ID
- const tid = getTidFromUrl();
- if (tid) {
- console.log("URL变化后重新获取点赞数据,帖子ID:", tid);
- // 给页面一些时间加载
- setTimeout(() => {
- // 获取帖子点赞数据
- getPostData(tid);
- }, 1000);
- }
- }
- }
-
- // MD5算法实现
- function md5(string) {
- function RotateLeft(lValue, iShiftBits) {
- return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
- }
-
- function AddUnsigned(lX, lY) {
- var lX4, lY4, lX8, lY8, lResult;
- lX8 = (lX & 0x80000000);
- lY8 = (lY & 0x80000000);
- lX4 = (lX & 0x40000000);
- lY4 = (lY & 0x40000000);
- lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
- if (lX4 & lY4) {
- return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
- }
- if (lX4 | lY4) {
- if (lResult & 0x40000000) {
- return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
- } else {
- return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
- }
- } else {
- return (lResult ^ lX8 ^ lY8);
- }
- }
-
- function F(x, y, z) { return (x & y) | ((~x) & z); }
- function G(x, y, z) { return (x & z) | (y & (~z)); }
- function H(x, y, z) { return (x ^ y ^ z); }
- function I(x, y, z) { return (y ^ (x | (~z))); }
-
- function FF(a, b, c, d, x, s, ac) {
- a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
- return AddUnsigned(RotateLeft(a, s), b);
- }
-
- function GG(a, b, c, d, x, s, ac) {
- a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
- return AddUnsigned(RotateLeft(a, s), b);
- }
-
- function HH(a, b, c, d, x, s, ac) {
- a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
- return AddUnsigned(RotateLeft(a, s), b);
- }
-
- function II(a, b, c, d, x, s, ac) {
- a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
- return AddUnsigned(RotateLeft(a, s), b);
- }
-
- function ConvertToWordArray(string) {
- var lWordCount;
- var lMessageLength = string.length;
- var lNumberOfWords_temp1 = lMessageLength + 8;
- var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
- var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
- var lWordArray = Array(lNumberOfWords - 1);
- var lBytePosition = 0;
- var lByteCount = 0;
- while (lByteCount < lMessageLength) {
- lWordCount = (lByteCount - (lByteCount % 4)) / 4;
- lBytePosition = (lByteCount % 4) * 8;
- lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
- lByteCount++;
- }
- lWordCount = (lByteCount - (lByteCount % 4)) / 4;
- lBytePosition = (lByteCount % 4) * 8;
- lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
- lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
- lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
- return lWordArray;
- }
-
- function WordToHex(lValue) {
- var WordToHexValue = "", WordToHexValue_temp = "", lByte, lCount;
- for (lCount = 0; lCount <= 3; lCount++) {
- lByte = (lValue >>> (lCount * 8)) & 255;
- WordToHexValue_temp = "0" + lByte.toString(16);
- WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
- }
- return WordToHexValue;
- }
-
- function Utf8Encode(string) {
- string = string.replace(/\r\n/g, "\n");
- var utftext = "";
-
- for (var n = 0; n < string.length; n++) {
- var c = string.charCodeAt(n);
-
- if (c < 128) {
- utftext += String.fromCharCode(c);
- }
- else if ((c > 127) && (c < 2048)) {
- utftext += String.fromCharCode((c >> 6) | 192);
- utftext += String.fromCharCode((c & 63) | 128);
- }
- else {
- utftext += String.fromCharCode((c >> 12) | 224);
- utftext += String.fromCharCode(((c >> 6) & 63) | 128);
- utftext += String.fromCharCode((c & 63) | 128);
- }
- }
-
- return utftext;
- }
-
- var x = Array();
- var k, AA, BB, CC, DD, a, b, c, d;
- var S11 = 7, S12 = 12, S13 = 17, S14 = 22;
- var S21 = 5, S22 = 9, S23 = 14, S24 = 20;
- var S31 = 4, S32 = 11, S33 = 16, S34 = 23;
- var S41 = 6, S42 = 10, S43 = 15, S44 = 21;
-
- string = Utf8Encode(string);
-
- x = ConvertToWordArray(string);
-
- a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
-
- for (k = 0; k < x.length; k += 16) {
- AA = a; BB = b; CC = c; DD = d;
- a = FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
- d = FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
- c = FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
- b = FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
- a = FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
- d = FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
- c = FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
- b = FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
- a = FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
- d = FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
- c = FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
- b = FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
- a = FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
- d = FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
- c = FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
- b = FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
- a = GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
- d = GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
- c = GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
- b = GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
- a = GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
- d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
- c = GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
- b = GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
- a = GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
- d = GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
- c = GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
- b = GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
- a = GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
- d = GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
- c = GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
- b = GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
- a = HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
- d = HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
- c = HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
- b = HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
- a = HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
- d = HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
- c = HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
- b = HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
- a = HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
- d = HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
- c = HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
- b = HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
- a = HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
- d = HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
- c = HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
- b = HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
- a = II(a, b, c, d, x[k + 0], S41, 0xF4292244);
- d = II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
- c = II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
- b = II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
- a = II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
- d = II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
- c = II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
- b = II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
- a = II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
- d = II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
- c = II(c, d, a, b, x[k + 6], S43, 0xA3014314);
- b = II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
- a = II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
- d = II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
- c = II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
- b = II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
- a = AddUnsigned(a, AA);
- b = AddUnsigned(b, BB);
- c = AddUnsigned(c, CC);
- d = AddUnsigned(d, DD);
- }
-
- var temp = WordToHex(a) + WordToHex(b) + WordToHex(c) + WordToHex(d);
-
- return temp.toLowerCase();
- }
-
- // 添加样式
- function addCustomStyles() {
- const style = document.createElement('style');
- style.textContent = `
- .thread-like-count, .post-like-count {
- display: inline-block;
- animation: fadeIn 0.5s;
- }
- @keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
- }
- `;
- document.head.appendChild(style);
- }
-
- // 注册(不可用)油猴菜单
- GM_registerMenuCommand("设置BDUSS", () => {
- const currentBDUSS = GM_getValue('BDUSS', '');
- const userBDUSS = prompt("请输入您的BDUSS值以获取点赞数据:", currentBDUSS || "");
- if (userBDUSS) {
- config.BDUSS = userBDUSS;
- GM_setValue('BDUSS', userBDUSS);
-
- // 刷新点赞数据
- const tid = getTidFromUrl();
- if (tid) getPostData(tid);
- }
- });
-
- // 初始化函数
- function runScript() {
- addCustomStyles();
- init();
-
- // 由于贴吧页面有时会动态加载内容,需要监听DOM变化
- const observer = new MutationObserver(function(mutations) {
- mutations.forEach(function(mutation) {
- if (mutation.addedNodes && mutation.addedNodes.length > 0) {
- // 如果有新的楼层添加,尝试显示点赞数据
- for (let i = 0; i < mutation.addedNodes.length; i++) {
- const node = mutation.addedNodes[i];
- if (node.nodeType === 1 && node.classList.contains('l_post')) {
- const tid = getTidFromUrl();
- const currentPage = getCurrentPage();
- const cacheKey = `${tid}_${currentPage}`;
-
- // 如果有缓存数据,尝试直接使用缓存的数据为新加载的楼层添加点赞数
- if (config.postDataCache[cacheKey] && !config.isProcessing) {
- const cachedData = config.postDataCache[cacheKey];
- const postId = node.getAttribute('data-pid');
-
- if (postId && cachedData.post_list) {
- const post = cachedData.post_list.find(p => p && p.id === postId);
- if (post) {
- displayPostLikeCount(post);
- }
- }
- } else if (tid && !config.isProcessing) {
- // 如果没有缓存数据或正在处理,则重新获取
- getPostData(tid);
- }
- break;
- }
- }
- }
- });
- });
-
- observer.observe(document.body, { childList: true, subtree: true });
-
- // 监听URL变化(针对翻页)
- // 方法1:使用事件监听(适用于单页应用)
- window.addEventListener('popstate', () => {
- checkUrlChange();
- });
-
- // 方法2:周期性检查URL(适用于任何情况)
- setInterval(checkUrlChange, 1000);
-
- // 方法3:监听贴吧的翻页事件(适用于贴吧特定实现)
- document.addEventListener('click', (e) => {
- // 检查是否点击了翻页链接
- if (e.target && (e.target.classList.contains('pagination-item') ||
- e.target.closest('.pagination-item'))) {
- // 给点击翻页一些时间来改变URL和加载内容
- setTimeout(checkUrlChange, 500);
- }
- });
- }
-
- // 确保脚本在页面加载后运行
- if (document.readyState === "loading") {
- window.addEventListener('DOMContentLoaded', runScript);
- } else {
- runScript();
- }
- })();