X/Twitter 1 Click report spam and block

Report spam and block profiles with 1 click

  1. // ==UserScript==
  2. // @name X/Twitter 1 Click report spam and block
  3. // @description Report spam and block profiles with 1 click
  4. // @version 1.4
  5. // @grant none
  6. // @match https://twitter.com/*
  7. // @match https://x.com/*
  8. // @author incognico
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=x.com
  10. // @require https://cdn.jsdelivr.net/npm/@violentmonkey/dom@2
  11. // @license GPL v3
  12. // @author incognico
  13. // @namespace https://greasyfork.org/users/931787
  14. //
  15. // ==/UserScript==
  16.  
  17.  
  18. const accountMenuIconSelector = '[data-testid="primaryColumn"] svg:has([d="M3 12c0-1.1.9-2 2-2s2 .9 2 2-.9 2-2 2-2-.9-2-2zm9 2c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm7 0c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"])';
  19. const ourIconId = 'oneclickreportbtn';
  20. const reportIconSelector = '[data-testid="Dropdown"] svg:has([d="M3 2h18.61l-3.5 7 3.5 7H5v6H3V2zm2 12h13.38l-2.5-5 2.5-5H5v10z"])';
  21. const spamItemSelector = '[data-viewportview="true"] [role="radiogroup"] label:nth-child(6)';
  22. const nextButtonSelector = '[data-testid="ChoiceSelectionNextButton"]';
  23. const modalButtonsSelector = '[data-viewportview="true"] button';
  24.  
  25. const wait = (ms) => {
  26. return new Promise(resolve => setTimeout(resolve, ms));
  27. };
  28.  
  29. const doReport = async (event) => {
  30. if (!event.shiftKey) {
  31. alert('Press shift to confirm reporting this account as spam and blocking');
  32. return;
  33. }
  34. // open account overflow menu
  35. document.querySelector(accountMenuIconSelector).parentElement.click();
  36. await wait(100);
  37. // press report
  38. document.querySelector(reportIconSelector).parentElement.parentElement.click();
  39. await wait(850);
  40. // select spam and press next
  41. document.querySelector(spamItemSelector).click();
  42. document.querySelector(nextButtonSelector).click();
  43. await wait(500);
  44. // second button is report
  45. document.querySelectorAll(modalButtonsSelector)[1].click();
  46. };
  47.  
  48.  
  49. const destroyIcon = () => {
  50. const icon = document.getElementById(ourIconId);
  51. if (icon) {
  52. icon.remove();
  53. }
  54. };
  55.  
  56.  
  57. const createIconIfNotExists = () => {
  58. const iconExists = document.getElementById(ourIconId);
  59. const accountMenuIcon = document.querySelector(accountMenuIconSelector);
  60. if (!iconExists && accountMenuIcon) {
  61. const icon = document.createElement('button');
  62. icon.id = ourIconId;
  63. icon.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h5.697" /><path d="M18 14v4h4" /><path d="M18 11v-4a2 2 0 0 0 -2 -2h-2" /><path d="M8 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M18 18m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M8 11h4" /><path d="M8 15h3" /></svg>`;
  64. icon.title = '1-Click spam report & block';
  65. icon.onclick = doReport;
  66.  
  67. // assign style to the button
  68. const style = {
  69. color: 'black',
  70. textAlign: 'center',
  71. fontWeight: 'bold',
  72. alignSelf: 'baseline',
  73. borderRadius: '50%',
  74. borderColor: 'rgba(0, 0, 0, 0)',
  75. marginLeft: '5px',
  76. backgroundColor: 'rgb(239, 243, 244)',
  77. fontFamily: 'TwitterChirp, sans-serif',
  78. cursor: 'pointer',
  79. };
  80. Object.assign(icon.style, style);
  81.  
  82. // append icon to the profile icon list
  83. const header = accountMenuIcon.parentElement?.parentElement?.parentElement;
  84. if (header) {
  85. header.appendChild(icon);
  86. }
  87. }
  88. };
  89.  
  90. const disconnect = VM.observe(document.body, () => {
  91. // append our icon on profile pages
  92. const profilePage = document.querySelector('[data-testid="UserName"]');
  93. if (profilePage) {
  94. createIconIfNotExists();
  95. } else {
  96. destroyIcon();
  97. }
  98. });