zhihusb

try to take over the world!

  1. // ==UserScript==
  2. // @name zhihusb
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.3
  5. // @description try to take over the world!
  6. // @author You
  7. // @match https://www.zhihu.com/question/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. (function (root, factory) {
  15. window.lscache = factory();
  16. }(this, function () {
  17.  
  18. // Prefix for all lscache keys
  19. var CACHE_PREFIX = 'lscache-';
  20.  
  21. // Suffix for the key name on the expiration items in localStorage
  22. var CACHE_SUFFIX = '-cacheexpiration';
  23.  
  24. // expiration date radix (set to Base-36 for most space savings)
  25. var EXPIRY_RADIX = 10;
  26.  
  27. // time resolution in minutes
  28. var EXPIRY_UNITS = 60 * 1000;
  29.  
  30. // ECMAScript max Date (epoch + 1e8 days)
  31. var MAX_DATE = Math.floor(8.64e15/EXPIRY_UNITS);
  32.  
  33. var cachedStorage;
  34. var cachedJSON;
  35. var cacheBucket = '';
  36. var warnings = false;
  37.  
  38. // Determines if localStorage is supported in the browser;
  39. // result is cached for better performance instead of being run each time.
  40. // Feature detection is based on how Modernizr does it;
  41. // it's not straightforward due to FF4 issues.
  42. // It's not run at parse-time as it takes 200ms in Android.
  43. function supportsStorage() {
  44. var key = '__lscachetest__';
  45. var value = key;
  46.  
  47. if (cachedStorage !== undefined) {
  48. return cachedStorage;
  49. }
  50.  
  51. // some browsers will throw an error if you try to access local storage (e.g. brave browser)
  52. // hence check is inside a try/catch
  53. try {
  54. if (!localStorage) {
  55. return false;
  56. }
  57. } catch (ex) {
  58. return false;
  59. }
  60.  
  61. try {
  62. setItem(key, value);
  63. removeItem(key);
  64. cachedStorage = true;
  65. } catch (e) {
  66. // If we hit the limit, and we don't have an empty localStorage then it means we have support
  67. if (isOutOfSpace(e) && localStorage.length) {
  68. cachedStorage = true; // just maxed it out and even the set test failed.
  69. } else {
  70. cachedStorage = false;
  71. }
  72. }
  73. return cachedStorage;
  74. }
  75.  
  76. // Check to set if the error is us dealing with being out of space
  77. function isOutOfSpace(e) {
  78. if (e && e.name === 'QUOTA_EXCEEDED_ERR' ||
  79. e.name === 'NS_ERROR_DOM_QUOTA_REACHED' ||
  80. e.name === 'QuotaExceededError') {
  81. return true;
  82. }
  83. return false;
  84. }
  85.  
  86. // Determines if native JSON (de-)serialization is supported in the browser.
  87. function supportsJSON() {
  88. /*jshint eqnull:true */
  89. if (cachedJSON === undefined) {
  90. cachedJSON = (window.JSON != null);
  91. }
  92. return cachedJSON;
  93. }
  94.  
  95. /**
  96. * Returns a string where all RegExp special characters are escaped with a \.
  97. * @param {String} text
  98. * @return {string}
  99. */
  100. function escapeRegExpSpecialCharacters(text) {
  101. return text.replace(/[[\]{}()*+?.\\^$|]/g, '\\$&');
  102. }
  103.  
  104. /**
  105. * Returns the full string for the localStorage expiration item.
  106. * @param {String} key
  107. * @return {string}
  108. */
  109. function expirationKey(key) {
  110. return key + CACHE_SUFFIX;
  111. }
  112.  
  113. /**
  114. * Returns the number of minutes since the epoch.
  115. * @return {number}
  116. */
  117. function currentTime() {
  118. return Math.floor((new Date().getTime())/EXPIRY_UNITS);
  119. }
  120.  
  121. /**
  122. * Wrapper functions for localStorage methods
  123. */
  124.  
  125. function getItem(key) {
  126. return localStorage.getItem(CACHE_PREFIX + cacheBucket + key);
  127. }
  128.  
  129. function setItem(key, value) {
  130. // Fix for iPad issue - sometimes throws QUOTA_EXCEEDED_ERR on setItem.
  131. localStorage.removeItem(CACHE_PREFIX + cacheBucket + key);
  132. localStorage.setItem(CACHE_PREFIX + cacheBucket + key, value);
  133. }
  134.  
  135. function removeItem(key) {
  136. localStorage.removeItem(CACHE_PREFIX + cacheBucket + key);
  137. }
  138.  
  139. function eachKey(fn) {
  140. var prefixRegExp = new RegExp('^' + CACHE_PREFIX + escapeRegExpSpecialCharacters(cacheBucket) + '(.*)');
  141. // Loop in reverse as removing items will change indices of tail
  142. for (var i = localStorage.length-1; i >= 0 ; --i) {
  143. var key = localStorage.key(i);
  144. key = key && key.match(prefixRegExp);
  145. key = key && key[1];
  146. if (key && key.indexOf(CACHE_SUFFIX) < 0) {
  147. fn(key, expirationKey(key));
  148. }
  149. }
  150. }
  151.  
  152. function flushItem(key) {
  153. var exprKey = expirationKey(key);
  154.  
  155. removeItem(key);
  156. removeItem(exprKey);
  157. }
  158.  
  159. function flushExpiredItem(key) {
  160. var exprKey = expirationKey(key);
  161. var expr = getItem(exprKey);
  162.  
  163. if (expr) {
  164. var expirationTime = parseInt(expr, EXPIRY_RADIX);
  165.  
  166. // Check if we should actually kick item out of storage
  167. if (currentTime() >= expirationTime) {
  168. removeItem(key);
  169. removeItem(exprKey);
  170. return true;
  171. }
  172. }
  173. }
  174.  
  175. function warn(message, err) {
  176. if (!warnings) return;
  177. if (!('console' in window) || typeof window.console.warn !== 'function') return;
  178. window.console.warn("lscache - " + message);
  179. if (err) window.console.warn("lscache - The error was: " + err.message);
  180. }
  181.  
  182. var lscache = {
  183. /**
  184. * Stores the value in localStorage. Expires after specified number of minutes.
  185. * @param {string} key
  186. * @param {Object|string} value
  187. * @param {number} time
  188. */
  189. set: function(key, value, time) {
  190. if (!supportsStorage()) return;
  191.  
  192. // If we don't get a string value, try to stringify
  193. // In future, localStorage may properly support storing non-strings
  194. // and this can be removed.
  195.  
  196. if (!supportsJSON()) return;
  197. try {
  198. value = JSON.stringify(value);
  199. } catch (e) {
  200. // Sometimes we can't stringify due to circular refs
  201. // in complex objects, so we won't bother storing then.
  202. return;
  203. }
  204.  
  205. try {
  206. setItem(key, value);
  207. } catch (e) {
  208. if (isOutOfSpace(e)) {
  209. // If we exceeded the quota, then we will sort
  210. // by the expire time, and then remove the N oldest
  211. var storedKeys = [];
  212. var storedKey;
  213. eachKey(function(key, exprKey) {
  214. var expiration = getItem(exprKey);
  215. if (expiration) {
  216. expiration = parseInt(expiration, EXPIRY_RADIX);
  217. } else {
  218. // TODO: Store date added for non-expiring items for smarter removal
  219. expiration = MAX_DATE;
  220. }
  221. storedKeys.push({
  222. key: key,
  223. size: (getItem(key) || '').length,
  224. expiration: expiration
  225. });
  226. });
  227. // Sorts the keys with oldest expiration time last
  228. storedKeys.sort(function(a, b) { return (b.expiration-a.expiration); });
  229.  
  230. var targetSize = (value||'').length;
  231. while (storedKeys.length && targetSize > 0) {
  232. storedKey = storedKeys.pop();
  233. warn("Cache is full, removing item with key '" + key + "'");
  234. flushItem(storedKey.key);
  235. targetSize -= storedKey.size;
  236. }
  237. try {
  238. setItem(key, value);
  239. } catch (e) {
  240. // value may be larger than total quota
  241. warn("Could not add item with key '" + key + "', perhaps it's too big?", e);
  242. return;
  243. }
  244. } else {
  245. // If it was some other error, just give up.
  246. warn("Could not add item with key '" + key + "'", e);
  247. return;
  248. }
  249. }
  250.  
  251. // If a time is specified, store expiration info in localStorage
  252. if (time) {
  253. setItem(expirationKey(key), (currentTime() + time).toString(EXPIRY_RADIX));
  254. } else {
  255. // In case they previously set a time, remove that info from localStorage.
  256. removeItem(expirationKey(key));
  257. }
  258. },
  259.  
  260. /**
  261. * Retrieves specified value from localStorage, if not expired.
  262. * @param {string} key
  263. * @return {string|Object}
  264. */
  265. get: function(key) {
  266. if (!supportsStorage()) return null;
  267.  
  268. // Return the de-serialized item if not expired
  269. if (flushExpiredItem(key)) { return null; }
  270.  
  271. // Tries to de-serialize stored value if its an object, and returns the normal value otherwise.
  272. var value = getItem(key);
  273. if (!value || !supportsJSON()) {
  274. return value;
  275. }
  276.  
  277. try {
  278. // We can't tell if its JSON or a string, so we try to parse
  279. return JSON.parse(value);
  280. } catch (e) {
  281. // If we can't parse, it's probably because it isn't an object
  282. return value;
  283. }
  284. },
  285.  
  286. /**
  287. * Removes a value from localStorage.
  288. * Equivalent to 'delete' in memcache, but that's a keyword in JS.
  289. * @param {string} key
  290. */
  291. remove: function(key) {
  292. if (!supportsStorage()) return;
  293.  
  294. flushItem(key);
  295. },
  296.  
  297. /**
  298. * Returns whether local storage is supported.
  299. * Currently exposed for testing purposes.
  300. * @return {boolean}
  301. */
  302. supported: function() {
  303. return supportsStorage();
  304. },
  305.  
  306. /**
  307. * Flushes all lscache items and expiry markers without affecting rest of localStorage
  308. */
  309. flush: function() {
  310. if (!supportsStorage()) return;
  311.  
  312. eachKey(function(key) {
  313. flushItem(key);
  314. });
  315. },
  316.  
  317. /**
  318. * Flushes expired lscache items and expiry markers without affecting rest of localStorage
  319. */
  320. flushExpired: function() {
  321. if (!supportsStorage()) return;
  322.  
  323. eachKey(function(key) {
  324. flushExpiredItem(key);
  325. });
  326. },
  327.  
  328. /**
  329. * Appends CACHE_PREFIX so lscache will partition data in to different buckets.
  330. * @param {string} bucket
  331. */
  332. setBucket: function(bucket) {
  333. cacheBucket = bucket;
  334. },
  335.  
  336. /**
  337. * Resets the string being appended to CACHE_PREFIX so lscache will use the default storage behavior.
  338. */
  339. resetBucket: function() {
  340. cacheBucket = '';
  341. },
  342.  
  343. /**
  344. * Sets whether to display warnings when an item is removed from the cache or not.
  345. */
  346. enableWarnings: function(enabled) {
  347. warnings = enabled;
  348. }
  349. };
  350.  
  351. // Return the module
  352. return lscache;
  353. }));
  354.  
  355. var c = document.querySelector("#QuestionAnswers-answers [data-zop-feedlist]");
  356.  
  357. if (c === null) {
  358. c = document.querySelector(".Question-mainColumn .Card+.Card");
  359. }
  360.  
  361. console.dir(c);
  362.  
  363. var userinfos = {};
  364. var userids = {};
  365. var userextends = {};
  366. var components = {};
  367.  
  368. var createStopComponent = function(v) {
  369. var b = document.createElement("button");
  370. b.id = "item" + Date.now() + "-" + Math.random();
  371. b.innerHTML = "给我滚";
  372. b.style = 'position: absolute; right: 0; top: 0;';
  373. b.onclick = function() {
  374. var id = userids[b.id];
  375. var sbid = lscache.get("sbid");
  376. var info = {id: id, info: userinfos[b.id], extend: userextends[b.id], lastupdatetime: Date.now()};
  377. if (!sbid) {
  378. sbid = {};
  379. }
  380. if (!sbid[id]) {
  381. info.num = 1;
  382. } else {
  383. info.num = sbid[id].num + 1;
  384. }
  385. sbid[id] = info;
  386. lscache.set("sbid", sbid);
  387. if (v.querySelector(".AuthorInfo-name .gankuaigun .num")) {
  388. v.querySelector(".AuthorInfo-name .gankuaigun .num").innerHTML = "" + info.num;
  389. } else {
  390. v.querySelector(".AuthorInfo-name").insertAdjacentHTML('beforeend', '<span class="gankuaigun" style="color: #f00;"> 危险分子 +<span class="num">' + info.num + '</span> </span>');
  391. }
  392. };
  393. return b;
  394. };
  395.  
  396. setTimeout(function() {
  397. var items = Array.prototype.slice.call(c.children);
  398. var sbid = lscache.get("sbid");
  399. var daodishinagesb = {};
  400.  
  401. items.forEach(function(v, k) {
  402. v.style = "position: relative";
  403.  
  404. var component = createStopComponent(v);
  405. var info = JSON.parse(v.querySelector(".ContentItem").dataset.zaModuleInfo);
  406. var zhegesbid = info.card.content.author_member_hash_id;
  407.  
  408. if (zhegesbid) {
  409. if (sbid && sbid[zhegesbid]) {
  410. console.log("sb gei wo gun " + sbid[zhegesbid].extend.zop.authorName);
  411. //v.style.display = "none";
  412. v.querySelector(".AuthorInfo-name").insertAdjacentHTML('beforeend', '<span class="gankuaigun" style="color: #f00;"> 危险分子 +<span class="num">' + sbid[zhegesbid].num + '</span> </span>');
  413. }
  414.  
  415. components[component.id] = component;
  416. v.appendChild(component);
  417. userinfos[component.id] = info;
  418. userids[component.id] = zhegesbid;
  419. userextends[component.id] = {
  420. zop: JSON.parse(v.querySelector(".ContentItem").dataset.zop)
  421. };
  422. }
  423.  
  424. });
  425.  
  426. console.dir(userids);
  427. }, 3000);
  428. })();