🏠 Home 

让MCN闪耀,让荣誉的勋章更容易看到!

发现更大的世界和更多的软广!


Install this script?
  1. // ==UserScript==
  2. // @name 让MCN闪耀,让荣誉的勋章更容易看到!
  3. // @namespace mcn.is.very.very.good
  4. // @version 1.0.2
  5. // @description 发现更大的世界和更多的软广!
  6. // @author Dislike soft AD
  7. // @license MIT
  8. // @match https://www.zhihu.com/*
  9. // @grant unsafeWindow
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_unregisterMenuCommand
  12. // @grant GM_setClipboard
  13. // ==/UserScript==
  14. (async () => {
  15. 'use strict';
  16. // 请不要将这里改为 true,否则将会在机构前增加一个emoji表情,这可能会增加攻击性。
  17. const useIcon = false;
  18. const setCache = (name, data) => {
  19. const cache = JSON.parse(localStorage.getItem('mcnCache')) || {};
  20. cache[name] = data;
  21. localStorage.setItem('mcnCache', JSON.stringify(cache));
  22. updateMenu();
  23. }
  24. const getCache = (name) => {
  25. const cache = JSON.parse(localStorage.getItem('mcnCache')) || {};
  26. return cache[name];
  27. }
  28. const setNoMcn = (name) => {
  29. const noMcn = new Set(JSON.parse(localStorage.getItem('noMcn')) || []);
  30. noMcn.add(name);
  31. localStorage.setItem('noMcn', JSON.stringify(Array.from(noMcn)));
  32. }
  33. const isNoMcn = (name) => {
  34. const noMcn = new Set(JSON.parse(localStorage.getItem('noMcn')) || []);
  35. return noMcn.has(name);
  36. }
  37. const promiseMap = {};
  38. const getAuthorMcn = async (token, manual = false) => {
  39. const cache = getCache(token);
  40. if (cache?.mcn) return cache.mcn;
  41. if (isNoMcn(token)) return false; // false is no mcn, null is no record
  42. if (promiseMap[token]) {
  43. return promiseMap[token];
  44. }
  45. const autoLoad = localStorage.getItem('mcnAutoLoad') || false;
  46. if (!autoLoad && !manual) {
  47. return null;
  48. }
  49. promiseMap[token] = new Promise(async (resolve) => {
  50. const url = `https://www.zhihu.com/people/${token}`;
  51. const html = await fetch(url)
  52. .then(response => response.text());
  53. const parser = new DOMParser();
  54. const doc = parser.parseFromString(html, 'text/html');
  55. const initialData = doc.querySelector('#js-initialData');
  56. const json = initialData.textContent;
  57. const data = JSON.parse(json);
  58. console.log(token);
  59. const mcn = data?.initialState?.entities?.users?.[token]?.mcnCompany;
  60. if (mcn) {
  61. setCache(token, {
  62. mcn,
  63. nickname: data?.initialState?.entities?.users?.[token]?.name ?? token,
  64. });
  65. } else {
  66. setNoMcn(token);
  67. }
  68. resolve(mcn || false);
  69. promiseMap[token] = null;
  70. });
  71. return promiseMap[token];
  72. }
  73. const clearBadge = (mcnBadge) => {
  74. mcnBadge.onclick = null;
  75. mcnBadge.style.cursor = null;
  76. mcnBadge.style.border = null;
  77. mcnBadge.style.backgroundColor = null;
  78. mcnBadge.className = 'mcn-badge';
  79. mcnBadge.textContent = 'Loading...';
  80. mcnBadge.style.marginLeft = '5px';
  81. mcnBadge.style.display = 'inline-block';
  82. mcnBadge.style.color = '#DDD';
  83. mcnBadge.style.fontSize = '12px';
  84. mcnBadge.style.padding = '2px';
  85. mcnBadge.style.borderRadius = '3px';
  86. }
  87. const updateBadge = (mcnBadge, token, mcn) => {
  88. clearBadge(mcnBadge);
  89. if (mcn) {
  90. mcnBadge.textContent = mcn;
  91. mcnBadge.style.marginLeft = '5px';
  92. mcnBadge.style.display = 'inline-block';
  93. mcnBadge.style.color = '#FFFFFF';
  94. mcnBadge.style.backgroundColor = '#FF0000';
  95. if (useIcon) {
  96. mcnBadge.textContent = '🐕‍🦺' + mcn;
  97. mcnBadge.style.backgroundColor = null;
  98. mcnBadge.style.color = "#FF0000";
  99. }
  100. mcnBadge.title = "如果是被包养了,就不要谈独立人格"
  101. } else if (mcn === false) {
  102. mcnBadge.textContent = "No MCN";
  103. } else {
  104. mcnBadge.textContent = "点击加载MCN";
  105. mcnBadge.style.cursor = 'pointer';
  106. mcnBadge.style.color = '#aaa';
  107. mcnBadge.style.backgroundColor = '#FFFFFF';
  108. mcnBadge.style.border = '1px solid #aaa';
  109. mcnBadge.style.padding = '1px';
  110. mcnBadge.onclick = async () => {
  111. clearBadge(mcnBadge);
  112. const mcn = await getAuthorMcn(token, true);
  113. updateBadge(mcnBadge, token, mcn);
  114. };
  115. }
  116. }
  117. const addMcnBadge = async (authorDom) => {
  118. if (authorDom.querySelector('.mcn-badge')) return;
  119. const box = authorDom.querySelector('.AuthorInfo-head');
  120. const mcnBadge = document.createElement('span');
  121. box.appendChild(mcnBadge);
  122. const userLink = authorDom.querySelector('.UserLink-link');
  123. if (!userLink) return;
  124. const link = userLink.getAttribute('href');
  125. const token = link.split('/').pop();
  126. clearBadge(mcnBadge);
  127. const mcn = await getAuthorMcn(token);
  128. updateBadge(mcnBadge, token, mcn);
  129. }
  130. const addQuestionerMcnBadge = async (dom) => {
  131. console.log('addQuestionerMcnBadge', dom);
  132. if (!dom) return;
  133. if(dom.querySelector('.mcn-badge')) return;
  134. const link = dom.querySelector('.BrandQuestionSymbol-brandLink');
  135. if (!link) return;
  136. const token = link.getAttribute('href').split('/').pop();
  137. console.log('token', token);
  138. const mcnBadge = document.createElement('span');
  139. // push BrandQuestionSymbol-brandLink next
  140. dom.insertBefore(mcnBadge, link.nextSibling);
  141. clearBadge(mcnBadge, token, null);
  142. const mcn = await getAuthorMcn(token);
  143. updateBadge(mcnBadge, token, mcn);
  144. }
  145. const blockAuthor = async (name) => {
  146. const url = `https://www.zhihu.com/api/v4/members/${name}/actions/block`;
  147. const response = await fetch(url, {
  148. method: 'POST',
  149. credentials: 'include',
  150. });
  151. console.log(response);
  152. return response.status === 204;
  153. }
  154. const getMcnAuthorMap = () => {
  155. const cache = JSON.parse(localStorage.getItem('mcnCache')) || {};
  156. const m###ap = {};
  157. for (const [name, data] of Object.entries(cache)) {
  158. if (!m###ap[data.mcn]) {
  159. m###ap[data.mcn] = [];
  160. }
  161. m###ap[data.mcn].push({ token: name, ...data });
  162. }
  163. return m###ap;
  164. }
  165. if (typeof unsafeWindow === 'undefined') {
  166. window.unsafeWindow = window;
  167. }
  168. const mcn = unsafeWindow.mcn = () => {
  169. const m###ap = getMcnAuthorMap();
  170. console.group('MCN Map');
  171. for (const [mcn, list] of Object.entries(m###ap)) {
  172. console.groupCollapsed(mcn + ' (' + list.length + ')');
  173. for (const data of list) {
  174. console.log(data.token, "\t", data.nickname, "\t", `https://www.zhihu.com/people/${data.token}`);
  175. }
  176. console.groupEnd();
  177. }
  178. console.groupEnd();
  179. }
  180. const blockMcn = unsafeWindow.blockMcn = async (name) => {
  181. const m###ap = getMcnAuthorMap();
  182. const authors = m###ap[name] || [];
  183. if (!authors.length) {
  184. console.error('没有找到已记录的MCN作者 ' + name);
  185. return;
  186. }
  187. for (const author of authors) {
  188. const r###lt = await blockAuthor(author.token);
  189. if (r###lt) {
  190. console.log(`已屏蔽 ${author.token} ${author.nickname}`);
  191. } else {
  192. console.error(`屏蔽失败 ${author.token} ${author.nickname}`);
  193. }
  194. };
  195. console.log("全部完成");
  196. alert("全部完成");
  197. }
  198. const headDom = document.querySelector('head');
  199. const hiddenContent = unsafeWindow.hiddenContent = () => {
  200. const style = document.createElement('style');
  201. style.id = 'hiddenContent';
  202. style.textContent = `
  203. .RichContent {
  204. display: none !important;
  205. }
  206. .LabelContainer-wrapper {
  207. display: none !important;
  208. }
  209. `;
  210. headDom.appendChild(style);
  211. }
  212. const showContent = unsafeWindow.showContent = () => {
  213. const style = document.querySelector('style#hiddenContent');
  214. if (style) {
  215. style.remove();
  216. }
  217. }
  218. let observer;
  219. function runObserver() {
  220. if (observer) observer.disconnect();
  221. const handle = (node) => {
  222. if (node.classList?.contains('AuthorInfo')) {
  223. setTimeout(() => {
  224. addMcnBadge(node);
  225. });
  226. }
  227. if (node.classList?.contains('BrandQuestionSymbol')) {
  228. setTimeout(() => {
  229. addQuestionerMcnBadge(node);
  230. });
  231. }
  232. }
  233. // MutationObserver
  234. observer = new MutationObserver((mutationsList, observer) => {
  235. for (let mutation of mutationsList) {
  236. if (mutation.type === "attributes") {
  237. handle(mutation.target);
  238. } else {
  239. for (const node of mutation.addedNodes) {
  240. handle(node);
  241. if (node.childNodes) {
  242. const nodeIterator = document.createNodeIterator(node);
  243. let childNode = nodeIterator.nextNode();
  244. while (childNode) {
  245. handle(childNode);
  246. childNode = nodeIterator.nextNode();
  247. }
  248. }
  249. }
  250. }
  251. }
  252. });
  253. const targetNode = window.document.documentElement;
  254. observer.observe(targetNode, { childList: true, subtree: true });
  255. }
  256. const m###enuId = [];
  257. let showMcnList = false;
  258. const updateMenu = () => {
  259. if (typeof GM_registerMenuCommand === 'undefined') {
  260. return;
  261. }
  262. try {
  263. m###enuId.forEach(id => {
  264. GM_unregisterMenuCommand(id);
  265. });
  266. const isAuto = localStorage.getItem('mcnAutoLoad') || false;
  267. const autoLoadId = GM_registerMenuCommand(`自动加载MCN(当前:${isAuto ? '自动': '手动'})`, function (event) {
  268. localStorage.setItem('mcnAutoLoad', isAuto ? '' : '1');
  269. updateMenu();
  270. });
  271. const hasHCstyle = document.querySelector('style#hiddenContent');
  272. if (hasHCstyle) {
  273. m###enuId.push(GM_registerMenuCommand("显示回答正文(当前:隐藏)", function (event) {
  274. showContent();
  275. updateMenu();
  276. }));
  277. } else {
  278. m###enuId.push(GM_registerMenuCommand("隐藏回答正文(当前:显示)", function (event) {
  279. hiddenContent();
  280. updateMenu();
  281. }));
  282. }
  283. m###enuId.push(GM_registerMenuCommand("复制表格", function (event) {
  284. const m###ap = getMcnAuthorMap();
  285. const textList = [];
  286. for (const [mcn, list] of Object.entries(m###ap)) {
  287. for (const data of list) {
  288. textList.push(`${mcn}\t${data.token}\t${data.nickname}\thttps://www.zhihu.com/people/${data.token}`);
  289. }
  290. }
  291. GM_setClipboard(textList.join('\n'));
  292. }));
  293. m###enuId.push(GM_registerMenuCommand("清理缓存", function (event) {
  294. if (!confirm('确定要清理缓存吗?')) return;
  295. localStorage.removeItem('mcnCache');
  296. localStorage.removeItem('noMcn');
  297. updateMenu();
  298. }));
  299. m###enuId.push(GM_registerMenuCommand(`${showMcnList ? '▼' : '▶'} 显示MCN列表${showMcnList ? '' : ' (需再次打开菜单)'}`, function (event) {
  300. showMcnList = !showMcnList;
  301. updateMenu();
  302. }, { autoClose: true }));
  303. if (showMcnList) {
  304. const m###ap = getMcnAuthorMap();
  305. for (const [mcn, list] of Object.entries(m###ap)) {
  306. const id = GM_registerMenuCommand(`拉黑 ${mcn} (${list.length})`, async () => {
  307. if (!confirm(`确定要拉黑 ${mcn} (${list.length}) 吗?\n${list.map(v => v.nickname).join(', ')}`)) {
  308. return;
  309. }
  310. await blockMcn(mcn);
  311. });
  312. m###enuId.push(id);
  313. }
  314. }
  315. } catch (error) {
  316. console.error(error);
  317. }
  318. }
  319. if (typeof GM_registerMenuCommand !== 'undefined') {
  320. updateMenu();
  321. }
  322. runObserver();
  323. addQuestionerMcnBadge();
  324. const authorDomList = document.querySelectorAll('.AuthorInfo');
  325. for (const authorDom of authorDomList) {
  326. await addMcnBadge(authorDom);
  327. }
  328. })();