V2EX Ignore

在 V2EX 的每个帖子标题旁添加一个“忽略”按钮,单击即可忽略该主题。

目前为 2023-07-24 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name V2EX Ignore
  3. // @license GNU GPLv3
  4. // @namespace https://www.example.com/
  5. // @version 1.9
  6. // @description Adds an "Ignore" button next to each post's title on V2EX that allows you to ignore the topic with a single click.
  7. // @description:zh-CN 在 V2EX 的每个帖子标题旁添加一个“忽略”按钮,单击即可忽略该主题。
  8. // @description:zh-TW 在 V2EX 的每個帖子標題旁添加一個“忽略”按鈕,單擊即可忽略該主題。
  9. // @description:ja 在 V2EX の各投稿タイトルの横に、「無視する」ボタンを追加し、クリックするだけでそのトピックを無視できます。
  10. // @author Arryboom
  11. // @match https://*.v2ex.com/*
  12. // @match https://v2ex.com/*
  13. // @grant GM_xmlhttpRequest
  14. // @run-at document-end
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict';
  19. const debug = true;
  20. const debugLog = debug ? console.log.bind(console) : function() {};
  21.  
  22. debugLog("Ignore plugin running");
  23.  
  24. // Function to ignore a topic
  25. function ignoreTopic(event) {
  26. event.preventDefault();
  27. const topicLink = event.target.parentNode.querySelector('.item_title a');
  28. if (topicLink) {
  29. const topicUrl = topicLink.href;
  30. const topicId = getTopicIdFromUrl(topicUrl);
  31. if (topicId) {
  32. getOnceValue().then(onceValue => {
  33. const ignoreUrl = `https://${window.location.hostname}/ignore/topic/${topicId}?once=${onceValue}`;
  34. GM_xmlhttpRequest({
  35. method: 'GET',
  36. url: ignoreUrl,
  37. onload: function(response) {
  38. debugLog(`Topic ${topicId} ignored.`);
  39. const post = event.target.closest('.cell.item');
  40. if (post) {
  41. post.remove();
  42. debugLog(`Post ${topicId} removed.`);
  43. showToast(`Topic ${topicId} ignored.`, function() {
  44. window.location.reload();
  45. });
  46. }
  47. },
  48. onerror: function(error) {
  49. debugLog(`Error ignoring topic ${topicId}: ${error}`);
  50. }
  51. });
  52. }).catch(error => {
  53. debugLog(`Error fetching once value: ${error}`);
  54. });
  55. }
  56. }
  57. }
  58.  
  59. // Function to extract the topic ID from a topic URL
  60. function getTopicIdFromUrl(topicUrl) {
  61. const match = topicUrl.match(/\/t\/(\d+)/);
  62. if (match) {
  63. return match[1];
  64. }
  65. return null;
  66. }
  67.  
  68. // Function to fetch the current once value from the V2EX server
  69. function getOnceValue() {
  70. return new Promise((resolve, reject) => {
  71. const tops = document.querySelectorAll('a.top');
  72. const top = Array.from(tops).find(a => a.getAttribute('onclick')?.includes('once='));
  73. if (top) {
  74. const onclick = top.getAttribute('onclick');
  75. const match = onclick.match(/\?once=(\d+)/);
  76. if (match) {
  77. const onceValue = match[1].replace(/\D/g, '');
  78. debugLog(`Current once value is ${onceValue}`);
  79. resolve(onceValue);
  80. return;
  81. }
  82. }
  83. const error = new Error('Not logged in.');
  84. error.isAuthenticationError = true;
  85. reject(error);
  86. });
  87. }
  88.  
  89. // Function to display a toast message
  90. function showToast(message, callback) {
  91. const toast = document.createElement('div');
  92. toast.textContent = message;
  93. toast.style.position = 'fixed';
  94. toast.style.bottom = '20px';
  95. toast.style.right = '20px';
  96. toast.style.padding = '10px';
  97. toast.style.backgroundColor = '#333';
  98. toast.style.color = '#fff';
  99. toast.style.borderRadius = '5px';
  100. document.body.appendChild(toast);
  101. setTimeout(() => {
  102. toast.remove();
  103. if (callback) {
  104. callback();
  105. }
  106. }, 300);
  107. }
  108.  
  109. // Find all post titles on the page and add an "Ignore" button next to each one
  110. const postTitles = document.querySelectorAll('.cell.item .item_title');
  111. postTitles.forEach(postTitle => {
  112. const ignoreButton = document.createElement('a');
  113. ignoreButton.textContent = 'Ignore';
  114. ignoreButton.href = '#';
  115. ignoreButton.classList.add('tag');
  116. ignoreButton.addEventListener('click', ignoreTopic);
  117. postTitle.parentNode.insertBefore(ignoreButton, postTitle.nextSibling);
  118. });
  119. })();