Cook'd and Bomb'd Really Ignore Users

Really ignores ignored users

  1. // ==UserScript==
  2. // @name Cook'd and Bomb'd Really Ignore Users
  3. // @description Really ignores ignored users
  4. // @namespace https://github.com/insin/greasemonkey/
  5. // @version 3
  6. // @match https://www.cookdandbombd.co.uk/forums/index.php?board*
  7. // @match https://www.cookdandbombd.co.uk/forums/index.php?topic*
  8. // @match https://www.cookdandbombd.co.uk/forums/index.php?action=post*
  9. // @match https://www.cookdandbombd.co.uk/forums/index.php?action=profile;area=lists;sa=ignore*
  10. // @match https://www.cookdandbombd.co.uk/forums/index.php?action=unread*
  11. // @grant GM.registerMenuCommand
  12. // ==/UserScript==
  13.  
  14. const IGNORED_USERS_STORAGE = 'cab_ignoredUsers'
  15.  
  16. let config = {
  17. addIgnoreUserControlToPosts: true,
  18. hidePostsQuotingIgnoredUsers: true,
  19. hideTopicsCreatedByIgnoredUsers: true,
  20. showIgnoredPosts: false,
  21. }
  22.  
  23. let posts = []
  24.  
  25. function addStyle(css) {
  26. let $style = document.createElement('style')
  27. $style.appendChild(document.createTextNode(css))
  28. document.head.appendChild($style)
  29. }
  30.  
  31. function addIgnoredPostsStyle() {
  32. addStyle(`
  33. .cab_ignoredPost {
  34. display: none;
  35. }
  36. .cab_ignoredPost.cab_show {
  37. display: block;
  38. background-color: #ddd !important;
  39. }
  40. `)
  41. }
  42.  
  43. function getIgnoredUsers() {
  44. return JSON.parse(localStorage[IGNORED_USERS_STORAGE] || '[]')
  45. }
  46.  
  47. function storeIgnoredUsers(ignoredUsers) {
  48. localStorage[IGNORED_USERS_STORAGE] = JSON.stringify(ignoredUsers)
  49. }
  50.  
  51. function toggleShowIgnoredPosts(showIgnoredPosts) {
  52. config.showIgnoredPosts = showIgnoredPosts
  53. posts.forEach(post => post.updateClassNames())
  54. }
  55.  
  56. /**
  57. * Topics being hidden breaks the CSS nth-of-type striping.
  58. */
  59. function reStripePosts() {
  60. let odd = true
  61. posts.forEach(post => {
  62. if (!post.isIgnored()) {
  63. post.$el.classList.toggle('odd', odd)
  64. post.$el.classList.toggle('even', !odd)
  65. odd = !odd
  66. } else {
  67. post.$el.classList.remove('odd')
  68. post.$el.classList.remove('even')
  69. }
  70. })
  71. }
  72.  
  73. function TopicPage() {
  74. addIgnoredPostsStyle()
  75.  
  76. let isLoggedIn = document.querySelector('#profile_menu_top') != null
  77.  
  78. let ignoredUsers
  79. let ignoredUserIds
  80. let ignoredUserNames
  81.  
  82. function setIgnoredUsers(ignoreList) {
  83. ignoredUsers = ignoreList
  84. ignoredUserIds = ignoredUsers.map(user => user.id)
  85. ignoredUserNames = ignoredUsers.map(user => user.name)
  86. }
  87.  
  88. function configureIgnoreControl({$a, $span, userId, userName}) {
  89. let isUserIgnored = ignoredUserIds.includes(userId)
  90. $a.href = `https://www.cookdandbombd.co.uk/forums/index.php?action=profile;area=lists;sa=ignore&${isUserIgnored ? `unignore=${userId}` : `ignore=${userName}`}`
  91. $a.title = `${isUserIgnored ? 'Remove from' : 'Add to'} ignore list`
  92. $span.classList.toggle('delete', isUserIgnored)
  93. $span.classList.toggle('ignore', !isUserIgnored)
  94. }
  95.  
  96. function Post($wrapper) {
  97. let $userLink = $wrapper.querySelector('div.poster h4 a')
  98.  
  99. let userId = $userLink.href.match(/;u=(\d+)/)[1]
  100. let userName = $userLink.textContent
  101. let quotedUserNames = Array.from($wrapper.querySelectorAll('.post blockquote cite a')).map(
  102. $a => $a.textContent.match(/Quote from: (.+) on /)?.[1] || $a.textContent.match(/Quote from: (.+)/)?.[1]
  103. ).filter(Boolean)
  104.  
  105. let api = {
  106. $el: $wrapper,
  107. isIgnored() {
  108. let isUserIgnored = ignoredUserIds.includes(userId)
  109. let quotesIgnoredUser = config.hidePostsQuotingIgnoredUsers && quotedUserNames.some(userName => ignoredUserNames.includes(userName))
  110. return isUserIgnored || quotesIgnoredUser
  111. },
  112. updateClassNames() {
  113. let isPostIgnored = api.isIgnored()
  114. $wrapper.classList.toggle('cab_ignoredPost', isPostIgnored)
  115. $wrapper.classList.toggle('cab_show', config.showIgnoredPosts && isPostIgnored)
  116. }
  117. }
  118.  
  119. // Add an ignore/unignore link to user profiles in posts
  120. if (config.addIgnoreUserControlToPosts) {
  121. let $a = document.createElement('a')
  122. let $span = document.createElement('span')
  123. $span.className = 'main_icons centericon'
  124. $a.appendChild($span)
  125. let $li = document.createElement('li')
  126. $li.appendChild($a)
  127. configureIgnoreControl({$a, $span, userId, userName})
  128. let $profileIcons = $wrapper.querySelector('div.poster ol.profile_icons')
  129.  
  130. // Logged-out users don't get a profile list item, so we'll add our own
  131. if (!$profileIcons) {
  132. let $insertProfileAfter =
  133. $wrapper.querySelector('div.poster .im_icons') ||
  134. $wrapper.querySelector('div.poster .blurb') ||
  135. $wrapper.querySelector('div.poster .icons')
  136. let $profile = document.createElement('li')
  137. $profile.className = 'profile'
  138. $profileIcons = document.createElement('ol')
  139. $profileIcons.className = 'profile'
  140. $profile.appendChild($profileIcons)
  141. $insertProfileAfter.insertAdjacentElement('afterend', $profile)
  142. }
  143.  
  144. $profileIcons.appendChild($li)
  145.  
  146. // For logged-out users, manage the ignore list independently
  147. if (!isLoggedIn) {
  148. $a.addEventListener('click', (e) => {
  149. e.preventDefault()
  150. // Get a fresh copy in case it's been changed in another tab
  151. let ignoredUsers = getIgnoredUsers()
  152. let index = ignoredUsers.findIndex(user => user.id === userId)
  153. if (index != -1) {
  154. ignoredUsers.splice(index, 1)
  155. }
  156. else {
  157. ignoredUsers.push({id: userId, name: userName})
  158. }
  159. setIgnoredUsers(ignoredUsers)
  160. storeIgnoredUsers(ignoredUsers)
  161. configureIgnoreControl({$a, $span, userId, userName})
  162. posts.forEach(post => post.updateClassNames())
  163. reStripePosts()
  164. })
  165. }
  166. }
  167.  
  168. api.updateClassNames()
  169. return api
  170. }
  171.  
  172. let postElements = Array.from(document.querySelectorAll('#forumposts > form > div.windowbg'))
  173. let oddBg = postElements[0] ? getComputedStyle(postElements[0]).backgroundColor : null
  174. let evenBg = postElements[1] ? getComputedStyle(postElements[1]).backgroundColor : null
  175.  
  176. addStyle(`
  177. .cab_ignoredPost {
  178. display: none;
  179. }
  180. .cab_ignoredPost.cab_show {
  181. display: block;
  182. background-color: #ddd !important;
  183. }
  184. ${oddBg ? `#forumposts .windowbg.odd {
  185. background-color: ${oddBg};
  186. }` : ''}
  187. ${evenBg ? `#forumposts .windowbg.even {
  188. background-color: ${evenBg};
  189. }` : ''}
  190. `)
  191.  
  192. setIgnoredUsers(getIgnoredUsers())
  193. posts = postElements.map(Post)
  194. reStripePosts()
  195. document.body.classList.add('cab_reallyIgnoreUsers')
  196. }
  197.  
  198. function PostReplyPage() {
  199. let ignoredUserNames = getIgnoredUsers().map(user => user.name)
  200.  
  201. function Post($wrapper) {
  202. let $userHeader = $wrapper.querySelector('h5')
  203.  
  204. let userName = $userHeader?.textContent.match(/Posted by (.+)/)?.[1]
  205. let quotedUserNames = Array.from($wrapper.querySelectorAll('blockquote cite a')).map(
  206. $a => $a.textContent.match(/Quote from: (.+) on /)?.[1] || $a.textContent.match(/Quote from: (.+)/)?.[1]
  207. ).filter(Boolean)
  208.  
  209. let api = {
  210. $el: $wrapper,
  211. isIgnored() {
  212. let isUserIgnored = ignoredUserNames.includes(userName)
  213. let quotesIgnoredUser = config.hidePostsQuotingIgnoredUsers && quotedUserNames.some(userName => ignoredUserNames.includes(userName))
  214. return isUserIgnored || quotesIgnoredUser
  215. },
  216. updateClassNames() {
  217. let isPostIgnored = api.isIgnored()
  218. $wrapper.classList.toggle('cab_ignoredPost', isPostIgnored)
  219. $wrapper.classList.toggle('cab_show', config.showIgnoredPosts && isPostIgnored)
  220. }
  221. }
  222.  
  223. api.updateClassNames()
  224. return api
  225. }
  226.  
  227. let postElements = Array.from(document.querySelectorAll('#recent div.windowbg'))
  228. let oddBg = postElements[0] ? getComputedStyle(postElements[0]).backgroundColor : null
  229. let evenBg = postElements[1] ? getComputedStyle(postElements[1]).backgroundColor : null
  230.  
  231. addStyle(`
  232. .cab_ignoredPost {
  233. display: none;
  234. }
  235. .cab_ignoredPost.cab_show {
  236. display: block;
  237. background-color: #ddd !important;
  238. }
  239. ${oddBg ? `#recent .windowbg.odd {
  240. background-color: ${oddBg};
  241. }` : ''}
  242. ${evenBg ? `#recent .windowbg.even {
  243. background-color: ${evenBg};
  244. }` : ''}
  245. `)
  246.  
  247. posts = postElements.map(Post)
  248. reStripePosts()
  249. document.body.classList.add('cab_reallyIgnoreUsers')
  250. }
  251.  
  252. function IgnoreListPage() {
  253. let params = new URLSearchParams(location.search)
  254.  
  255. // Automatically ignore a user if ignore=name is provided in the URL
  256. if (params.has('ignore')) {
  257. let $newIgnoreInput = document.querySelector('#new_ignore')
  258. $newIgnoreInput.value = params.get('ignore')
  259. $newIgnoreInput.form.submit()
  260. return
  261. }
  262.  
  263. // Automatically unignore a user if unignore=id is provided in the URL
  264. if (params.has('unignore')) {
  265. let $removeLink = Array.from(document.querySelectorAll('.table_grid tr td:last-child a')).find(
  266. $a => $a.href.includes(`remove=${params.get('unignore')}`)
  267. )
  268. if ($removeLink) {
  269. $removeLink.click()
  270. return
  271. }
  272. }
  273.  
  274. // Otherwise sync with the ignore list
  275. let ignoredUsers = Array.from(document.querySelectorAll('.table_grid tr td:first-child a')).map($a => ({
  276. id: $a.href.match(/;u=(\d+)/)[1],
  277. name: $a.textContent,
  278. }))
  279. storeIgnoredUsers(ignoredUsers)
  280. }
  281.  
  282. function ForumPage() {
  283. addStyle(`
  284. #topic_container .windowbg.cab_ignoredUser {
  285. display: none;
  286. }
  287. `)
  288.  
  289. let ignoredUserIds = getIgnoredUsers().map(user => user.id)
  290.  
  291. for (let $topicRow of document.querySelectorAll('#topic_container > div')) {
  292. let $userLink = $topicRow.querySelector('.info .floatleft a')
  293. let userId = $userLink?.href.match(/;u=(\d+)/)?.[1]
  294. if (ignoredUserIds.includes(userId)) {
  295. $topicRow.classList.add('cab_ignoredUser')
  296. }
  297. }
  298. }
  299.  
  300. if (location.search.includes('?action=profile;area=lists;sa=ignore')) {
  301. IgnoreListPage()
  302. }
  303. else if (location.search.includes('?action=unread') || location.search.includes('?board')) {
  304. if (config.hideTopicsCreatedByIgnoredUsers) {
  305. ForumPage()
  306. }
  307. }
  308. else if (!document.body.classList.contains('cab_reallyIgnoreUsers')) {
  309. let page = location.search.includes('?action=post') ? PostReplyPage : TopicPage
  310. if (typeof GM != 'undefined') {
  311. page()
  312. GM.registerMenuCommand('Toggle ignored post display', () => {
  313. toggleShowIgnoredPosts(!config.showIgnoredPosts)
  314. })
  315. }
  316. else {
  317. chrome.storage.local.get((storedConfig) => {
  318. Object.assign(config, storedConfig)
  319. page()
  320. })
  321. chrome.storage.onChanged.addListener((changes) => {
  322. if ('showIgnoredPosts' in changes) {
  323. toggleShowIgnoredPosts(changes['showIgnoredPosts'].newValue)
  324. }
  325. })
  326. }
  327. }