知乎答主MCN信息显示

获取并显示答主MCN数据

目前为 2024-10-27 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name 知乎答主MCN信息显示
  3. // @namespace https://github.com/ByronLeeeee/zhihu-mcn-data/
  4. // @version 1.0
  5. // @description 获取并显示答主MCN数据
  6. // @author ByronLeeeee
  7. // @match *://www.zhihu.com/question/*
  8. // @match *://www.zhihu.com/people/*
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_listValues
  12. // @grant GM_openInTab
  13. // @grant GM_deleteValue
  14. // @grant GM_xmlhttpRequest
  15. // @grant GM_addStyle
  16. // ==/UserScript==
  17.  
  18. (function () {
  19. "use strict";
  20.  
  21. // GitHub配置
  22. const GITHUB_CONFIG = {
  23. owner: "ByronLeeeee",
  24. repo: "zhihu-mcn-data",
  25. branch: "main",
  26. path: "mcn-data.json",
  27. };
  28.  
  29. // 添加样式
  30. GM_addStyle(`
  31. .mcn-button {
  32. margin-left: 8px;
  33. padding: 2px 8px;
  34. font-size: 12px;
  35. color: #8590a6;
  36. background: none;
  37. border: 1px solid #8590a6;
  38. border-radius: 3px;
  39. cursor: pointer;
  40. }
  41. .mcn-button:hover {
  42. color: #76839b;
  43. border-color: #76839b;
  44. }
  45. .mcn-info {
  46. color: #999;
  47. font-size: 14px;
  48. margin-left: 5px;
  49. }
  50. .mcn-main-button {
  51. position: fixed;
  52. bottom: 20px;
  53. right: 20px;
  54. width: 50px;
  55. height: 50px;
  56. background: #1772f6;
  57. color: white;
  58. border: none;
  59. border-radius: 50%;
  60. cursor: pointer;
  61. z-index: 1000;
  62. display: flex;
  63. align-items: center;
  64. justify-content: center;
  65. font-size: 18px;
  66. }
  67. .mcn-main-button:hover {
  68. background: #0077e6;
  69. }
  70. .mcn-sub-buttons {
  71. position: fixed;
  72. bottom: 80px;
  73. right: 20px;
  74. display: flex;
  75. flex-direction: column;
  76. align-items: flex-end;
  77. z-index: 999;
  78. }
  79. .mcn-sub-button {
  80. margin-bottom: 10px;
  81. padding: 8px 16px;
  82. background: #1772f6;
  83. color: white;
  84. border: none;
  85. border-radius: 4px;
  86. cursor: pointer;
  87. display: none;
  88. }
  89. .mcn-sub-button:hover {
  90. background: #389e0d;
  91. }
  92. .status-message {
  93. position: fixed;
  94. bottom: 70px;
  95. right: 20px;
  96. padding: 8px;
  97. background: rgba(0, 0, 0, 0.7);
  98. color: white;
  99. border-radius: 4px;
  100. font-size: 12px;
  101. z-index: 1000;
  102. display: none;
  103. }
  104. `);
  105.  
  106. // 存储正在处理的用户ID,防止重复获取
  107. const processingUsers = new Set();
  108.  
  109. // 数据管理器
  110. const DataManager = {
  111. statusElement: null,
  112. mcnData: null,
  113.  
  114. createStatusElement() {
  115. if (!this.statusElement) {
  116. this.statusElement = document.createElement("div");
  117. this.statusElement.className = "status-message";
  118. document.body.appendChild(this.statusElement);
  119. }
  120. },
  121.  
  122. showStatus(message, duration = 3000) {
  123. this.statusElement.textContent = message;
  124. this.statusElement.style.display = "block";
  125. setTimeout(() => {
  126. this.statusElement.style.display = "none";
  127. }, duration);
  128. },
  129.  
  130. // 添加主按钮
  131. addMainButton() {
  132. const mainButton = document.createElement("button");
  133. mainButton.className = "mcn-main-button";
  134. mainButton.textContent = "MCN";
  135.  
  136. const subButtonsContainer = document.createElement("div");
  137. subButtonsContainer.className = "mcn-sub-buttons";
  138.  
  139. const downloadButton = document.createElement("button");
  140. downloadButton.className = "mcn-sub-button";
  141. downloadButton.textContent = "更新MCN数据库";
  142. downloadButton.onclick = async () => {
  143. downloadButton.disabled = true;
  144. downloadButton.textContent = "更新中...";
  145. await this.updateMCNData();
  146. downloadButton.disabled = false;
  147. downloadButton.textContent = "更新MCN数据库";
  148. updateAllMCNDisplays();
  149. };
  150.  
  151. const exportButton = document.createElement("button");
  152. exportButton.className = "mcn-sub-button";
  153. exportButton.textContent = "导出MCN数据";
  154. exportButton.onclick = () => {
  155. try {
  156. this.exportData();
  157. } catch (error) {
  158. console.error("导出按钮点击处理失败:", error);
  159. this.showStatus("导出操作失败");
  160. }
  161. };
  162.  
  163. subButtonsContainer.appendChild(downloadButton);
  164. subButtonsContainer.appendChild(exportButton);
  165.  
  166. mainButton.onclick = () => {
  167. if (
  168. subButtonsContainer.style.display === "none" ||
  169. subButtonsContainer.style.display === ""
  170. ) {
  171. subButtonsContainer.style.display = "flex";
  172. downloadButton.style.display = "block";
  173. exportButton.style.display = "block";
  174. } else {
  175. subButtonsContainer.style.display = "none";
  176. downloadButton.style.display = "none";
  177. exportButton.style.display = "none";
  178. }
  179. };
  180.  
  181. document.body.appendChild(mainButton);
  182. document.body.appendChild(subButtonsContainer);
  183. },
  184.  
  185. // 获取所有本地存储的MCN数据
  186. getAllLocalData() {
  187. const localData = {};
  188.  
  189. // 从本地存储获取手动记录的数据
  190. if (typeof GM_listValues === "function") {
  191. const keys = GM_listValues();
  192. keys.forEach((key) => {
  193. const value = GM_getValue(key);
  194. if (value) {
  195. localData[key] = value;
  196. }
  197. });
  198. }
  199.  
  200. console.log("导出数据统计:", {
  201. 本地数据条数: Object.keys(localData).length,
  202. });
  203.  
  204. return localData;
  205. },
  206.  
  207. // 导出数据为JSON文件
  208. exportData() {
  209. const data = this.getAllLocalData();
  210. const dataCount = Object.keys(data).length;
  211.  
  212. if (dataCount === 0) {
  213. this.showStatus("没有找到可导出的数据");
  214. return;
  215. }
  216.  
  217. const blob = new Blob([JSON.stringify(data, null, 2)], {
  218. type: "application/json",
  219. });
  220. const url = URL.createObjectURL(blob);
  221. const timestamp = new Date()
  222. .toISOString()
  223. .slice(0, 19)
  224. .replace(/[T:]/g, "-");
  225.  
  226. const a = document.createElement("a");
  227. a.href = url;
  228. a.download = `zhihu-mcn-data-${timestamp}.json`;
  229. document.body.appendChild(a);
  230. a.click();
  231.  
  232. setTimeout(() => {
  233. document.body.removeChild(a);
  234. URL.revokeObjectURL(url);
  235. }, 100);
  236.  
  237. this.showStatus(`已导出 ${dataCount} MCN数据`);
  238. },
  239.  
  240. // 更新MCN数据
  241. async updateMCNData() {
  242. try {
  243. // 从GitHub获取新数据
  244. const data = await this.fetchFromGitHub();
  245.  
  246. // 获取本地存储的键
  247. const keys = GM_listValues ? GM_listValues() : [];
  248.  
  249. // 优先使用本地数据并保存
  250. for (const key of Object.keys(data)) {
  251. const localValue = keys.includes(key) ? GM_getValue(key) : undefined;
  252. const valueToSave = localValue !== undefined ? localValue : data[key];
  253. GM_setValue(key, valueToSave);
  254. }
  255.  
  256. // 处理本地数据中未在data中出现的键
  257. for (const key of keys) {
  258. if (!data.hasOwnProperty(key)) {
  259. GM_setValue(key, GM_getValue(key)); // 重新保存本地值
  260. }
  261. }
  262.  
  263. this.showStatus("已更新MCN数据");
  264. return true;
  265. } catch (error) {
  266. console.error("更新MCN数据失败:", error);
  267. this.showStatus("更新MCN数据失败");
  268. return false;
  269. }
  270. },
  271. // 从GitHub获取数据
  272. async fetchFromGitHub() {
  273. const rawUrl = `https://raw.githubusercontent.com/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/${GITHUB_CONFIG.branch}/${GITHUB_CONFIG.path}`;
  274.  
  275. try {
  276. console.log("Fetching from URL:", rawUrl); // 调试信息
  277. const response = await fetch(rawUrl);
  278.  
  279. if (!response.ok) {
  280. throw new Error(`HTTP ${response.status}`);
  281. }
  282.  
  283. const data = await response.json();
  284. console.log("Fetched data:", data); // 调试信息
  285. return data;
  286. } catch (error) {
  287. console.error("Fetch failed:", error); // 调试信息
  288. throw error;
  289. }
  290. },
  291.  
  292. // 获取MCN信息
  293. getMCNInfo(userId) {
  294. // 优先从本地存储获取
  295. const localInfo = GM_getValue(userId);
  296. if (localInfo) return localInfo;
  297. },
  298. };
  299.  
  300. // 获取MCN信息的函数(手动模式)
  301. async function fetchMCNInfo(userId, mcnButton) {
  302. if (processingUsers.has(userId)) {
  303. return;
  304. }
  305.  
  306. processingUsers.add(userId);
  307. mcnButton.textContent = "获取中...";
  308. mcnButton.disabled = true;
  309.  
  310. const tab = GM_openInTab(
  311. `https://www.zhihu.com/people/${userId}?autoOpened=true`,
  312. {
  313. active: false,
  314. insert: true,
  315. }
  316. );
  317.  
  318. const checkInterval = setInterval(() => {
  319. const mcnInfo = GM_getValue(userId);
  320. if (mcnInfo !== undefined) {
  321. clearInterval(checkInterval);
  322. processingUsers.delete(userId);
  323. mcnButton.textContent = "记录MCN";
  324. mcnButton.disabled = false;
  325. updateAllMCNDisplays();
  326. }
  327. }, 500);
  328.  
  329. setTimeout(() => {
  330. clearInterval(checkInterval);
  331. processingUsers.delete(userId);
  332. mcnButton.textContent = "记录MCN";
  333. mcnButton.disabled = false;
  334. }, 10000);
  335. }
  336.  
  337. // 更新所有MCN显示
  338. function updateAllMCNDisplays() {
  339. const answers = document.querySelectorAll(".List-item");
  340. answers.forEach((answer) => {
  341. const urlMeta = answer.querySelector('meta[itemprop="url"]');
  342. if (!urlMeta) return;
  343.  
  344. const userId = urlMeta.content.split("/").pop();
  345. const nameElement = answer.querySelector(".AuthorInfo-name");
  346. if (!nameElement) return;
  347.  
  348. // 移除旧的MCN信息
  349. const oldMcnInfo = nameElement.querySelector(".mcn-info");
  350. if (oldMcnInfo) {
  351. oldMcnInfo.remove();
  352. }
  353.  
  354. // 添加新的MCN信息
  355. const mcnInfo = DataManager.getMCNInfo(userId);
  356. if (mcnInfo) {
  357. const mcnElement = document.createElement("span");
  358. mcnElement.className = "mcn-info";
  359. mcnElement.textContent = `(MCN: ${mcnInfo})`;
  360. nameElement.appendChild(mcnElement);
  361. }
  362. });
  363. }
  364.  
  365. // 处理用户页面(用于手动获取MCN信息)
  366. function handlePeoplePage() {
  367. if (!window.location.pathname.startsWith("/people/")) {
  368. return;
  369. }
  370.  
  371. const userId = window.location.pathname.split("/").pop();
  372. const urlParams = new URLSearchParams(window.location.search);
  373. const isAutoOpened = urlParams.get("autoOpened") === "true";
  374.  
  375. setTimeout(async () => {
  376. const expandButton = document.querySelector(
  377. ".ProfileHeader-expandButton"
  378. );
  379. if (expandButton) {
  380. expandButton.click();
  381. }
  382.  
  383. setTimeout(() => {
  384. const mcnElements = document.querySelectorAll(
  385. ".ProfileHeader-detailItem"
  386. );
  387. let mcnInfo = "";
  388.  
  389. for (const element of mcnElements) {
  390. if (element.textContent.includes("MCN 机构")) {
  391. const mcnValue = element.querySelector(
  392. ".ProfileHeader-detailValue"
  393. );
  394. if (mcnValue) {
  395. mcnInfo = mcnValue.textContent.trim();
  396. // 存储到本地
  397. GM_setValue(userId, mcnInfo);
  398. console.log("已保存MCN信息:", userId, mcnInfo);
  399. break;
  400. }
  401. }
  402. }
  403.  
  404. if (isAutoOpened) {
  405. window.close();
  406. }
  407. }, 1000);
  408. }, 1000);
  409. }
  410.  
  411. // 处理问题页面
  412. function handleQuestionPage() {
  413. if (!window.location.pathname.startsWith("/question/")) {
  414. return;
  415. }
  416.  
  417. function processAnswer(answer) {
  418. if (answer.classList.contains("processed-mcn")) {
  419. return;
  420. }
  421.  
  422. const authorInfo = answer.querySelector(".AuthorInfo");
  423. if (!authorInfo) return;
  424.  
  425. const urlMeta = authorInfo.querySelector('meta[itemprop="url"]');
  426. if (!urlMeta) return;
  427.  
  428. const userId = urlMeta.content.split("/").pop();
  429. answer.classList.add("processed-mcn");
  430.  
  431. const nameElement = authorInfo.querySelector(".AuthorInfo-name");
  432. if (nameElement && !nameElement.querySelector(".mcn-button")) {
  433. // 创建MCN按钮
  434. const mcnButton = document.createElement("button");
  435. mcnButton.className = "mcn-button";
  436. mcnButton.textContent = "记录MCN";
  437. mcnButton.onclick = () => fetchMCNInfo(userId, mcnButton);
  438. nameElement.appendChild(mcnButton);
  439.  
  440. // 显示MCN信息
  441. const mcnInfo = DataManager.getMCNInfo(userId);
  442. if (mcnInfo) {
  443. const mcnElement = document.createElement("span");
  444. mcnElement.className = "mcn-info";
  445. mcnElement.textContent = `(MCN: ${mcnInfo})`;
  446. nameElement.appendChild(mcnElement);
  447. }
  448. }
  449. }
  450.  
  451. const observer = new MutationObserver((mutations) => {
  452. const answers = document.querySelectorAll(
  453. ".List-item:not(.processed-mcn)"
  454. );
  455. answers.forEach(processAnswer);
  456. });
  457.  
  458. observer.observe(document.querySelector(".List") || document.body, {
  459. childList: true,
  460. subtree: true,
  461. });
  462.  
  463. // 处理已有的回答
  464. const initialAnswers = document.querySelectorAll(".List-item");
  465. initialAnswers.forEach(processAnswer);
  466. }
  467.  
  468. // 初始化
  469. async function initialize() {
  470. DataManager.createStatusElement();
  471. DataManager.addMainButton();
  472.  
  473. if (window.location.pathname.startsWith("/people/")) {
  474. handlePeoplePage();
  475. } else if (window.location.pathname.startsWith("/question/")) {
  476. handleQuestionPage();
  477. }
  478. }
  479.  
  480. // 页面加载完成后初始化
  481. window.addEventListener("load", initialize);
  482. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址