Rllmuk Really Ignore Users

Really ignore ignored users, and ignore users in specific topics

目前为 2021-03-08 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Rllmuk Really Ignore Users
  3. // @description Really ignore ignored users, and ignore users in specific topics
  4. // @namespace https://github.com/insin/greasemonkey/
  5. // @version 8
  6. // @match https://rllmukforum.com/index.php*
  7. // @match https://www.rllmukforum.com/index.php*
  8. // ==/UserScript==
  9. function addStyle(css) {
  10. let $style = document.createElement('style')
  11. $style.appendChild(document.createTextNode(css))
  12. document.querySelector('head').appendChild($style)
  13. }
  14.  
  15. const USER_LINK_ID_RE = /profile\/(\d+)/
  16.  
  17. function TopicPage() {
  18. let topicId = document.body.dataset.pageid
  19. let ignoredUserIds = JSON.parse(localStorage.ignoredUserIds || '[]')
  20. let ignoredUsersInTopics = JSON.parse(localStorage.ignoredUsersInTopics || '{}')
  21. let topicIgnoredUserIds = []
  22. if (ignoredUsersInTopics[topicId]) {
  23. topicIgnoredUserIds = ignoredUsersInTopics[topicId].users.map(user => user.id)
  24. ignoredUserIds.push(...topicIgnoredUserIds)
  25. }
  26.  
  27. // Hide "You've chosen to ignore content by <ignored user>"
  28. addStyle(`
  29. .ipsComment_ignored {
  30. display: none;
  31. }
  32. `)
  33.  
  34. // Hide posts containing elements which have an ignored user id as a specified
  35. // data attribute.
  36. function hidePostsByDataAttribute(elements, dataAttribute) {
  37. elements.forEach(el => {
  38. if (!ignoredUserIds.includes(el.dataset[dataAttribute])) return
  39. let post = el.closest('article.ipsComment')
  40. if (post.style.display == 'none') return
  41. post.style.display = 'none'
  42. })
  43. }
  44.  
  45. // Hide posts which quote ignored users
  46. function processQuotes(context) {
  47. hidePostsByDataAttribute(
  48. context.querySelectorAll('[data-ipsquote-userid]'),
  49. 'ipsquoteUserid'
  50. )
  51. }
  52.  
  53. // Hide posts which @-mention ignored users
  54. function processMentions(context) {
  55. hidePostsByDataAttribute(
  56. context.querySelectorAll('[data-mentionid]'),
  57. 'mentionid'
  58. )
  59. }
  60.  
  61. // Hide posts by users ignored in this specific topic
  62. function processTopicIgnoredPosts(context = document) {
  63. if (topicIgnoredUserIds.length == 0) return
  64.  
  65. let postAvatarLinks = context.querySelectorAll('li.cAuthorPane_photo a')
  66. postAvatarLinks.forEach(el => {
  67. let userId = USER_LINK_ID_RE.exec(el.href)[1]
  68. if (!topicIgnoredUserIds.includes(userId)) return
  69. let post = el.closest('article.ipsComment')
  70. if (post.style.display == 'none') return
  71. post.style.display = 'none'
  72. })
  73. }
  74.  
  75. // Hide the unread comment separator if all subsequent posts are hidden
  76. function updateUnreadCommentSeparator() {
  77. let separator = document.querySelector('hr.ipsCommentUnreadSeperator')
  78. if (!separator) return
  79. let hasVisiblePost = false
  80. let sibling = separator.nextElementSibling
  81. while (sibling) {
  82. if (sibling.matches('article.ipsComment') &&
  83. !sibling.classList.contains('ipsHide') &&
  84. sibling.style.display != 'none') {
  85. hasVisiblePost = true
  86. break
  87. }
  88. sibling = sibling.nextElementSibling
  89. }
  90. separator.style.display = hasVisiblePost ? '' : 'none'
  91. }
  92.  
  93. // Process all posts on the current page
  94. function processPosts(context = document) {
  95. processQuotes(context)
  96. processMentions(context)
  97. processTopicIgnoredPosts(context)
  98. }
  99.  
  100. // Process initial posts
  101. processPosts()
  102. updateUnreadCommentSeparator()
  103.  
  104. // Add a new control to a user's hover card to ignore them in this topic
  105. function processHoverCard($el) {
  106. if (!$el.classList.contains('ipsHovercard')) return
  107.  
  108. // Create a new "Topic Ignore" control
  109. let $topicIgnoreItem = document.createElement('li')
  110. $topicIgnoreItem.innerHTML = `<a href="#">
  111. <i class="fa fa-times-circle"></i> Topic Ignore
  112. </a>`
  113. let $ignoreLink = $topicIgnoreItem.querySelector('a')
  114. $ignoreLink.addEventListener('click', (e) => {
  115. e.preventDefault()
  116.  
  117. let topicName = document.querySelector('.ipsType_pageTitle').innerText
  118. let user = {
  119. id: USER_LINK_ID_RE.exec($el.querySelector('a').href)[1],
  120. name: $el.querySelector('h2').innerText,
  121. avatar: $el.querySelector('img.ipsUserPhoto').src,
  122. }
  123.  
  124. // Add the user to the ignored users config for this topic
  125. let ignoredUsersInTopics = JSON.parse(localStorage.ignoredUsersInTopics || '{}')
  126. if (ignoredUsersInTopics[topicId] == undefined) {
  127. ignoredUsersInTopics[topicId] = {
  128. name: topicName,
  129. users: [],
  130. }
  131. }
  132. ignoredUsersInTopics[topicId].name = topicName
  133. ignoredUsersInTopics[topicId].users.push(user)
  134. localStorage.ignoredUsersInTopics = JSON.stringify(ignoredUsersInTopics)
  135.  
  136. // Apply the new ignored user settings
  137. ignoredUserIds.push(user.id)
  138. topicIgnoredUserIds.push(user.id)
  139. processPosts()
  140. updateUnreadCommentSeparator()
  141.  
  142. // Hide the hover card
  143. $el.style.display = 'none'
  144. })
  145.  
  146. // Insert the new control into the hover card
  147. let $findContentItem = $el.querySelector('ul.ipsList_inline li:last-child')
  148. $findContentItem.parentNode.insertBefore($topicIgnoreItem, $findContentItem)
  149. }
  150.  
  151. // Watch for posts being replaced when paging
  152. new MutationObserver(mutations =>
  153. mutations.forEach(mutation => {
  154. if (mutation.oldValue == 'true') {
  155. processPosts()
  156. updateUnreadCommentSeparator()
  157. }
  158. })
  159. ).observe(document.querySelector('div.cTopic'), {
  160. attributes: true,
  161. attributeFilter: ['animating'],
  162. attributeOldValue: true,
  163. })
  164.  
  165. // Watch for new posts being loaded into the current page
  166. new MutationObserver(mutations => {
  167. mutations.forEach(mutation =>
  168. mutation.addedNodes.forEach(processPosts)
  169. )
  170. updateUnreadCommentSeparator()
  171. }).observe(document.querySelector('#elPostFeed > form'), {
  172. childList: true,
  173. })
  174.  
  175. // Watch for user hover cards being added for display
  176. new MutationObserver(mutations => {
  177. mutations.forEach(mutation =>
  178. mutation.addedNodes.forEach(processHoverCard)
  179. )
  180. }).observe(document.body, {
  181. childList: true,
  182. })
  183. }
  184.  
  185. function IgnoredUsersPage() {
  186. // Sync ignored user ids
  187. localStorage.ignoredUserIds = JSON.stringify(
  188. Array.from(document.querySelectorAll('[data-ignoreuserid]')).map(el =>
  189. el.dataset.ignoreuserid
  190. )
  191. )
  192.  
  193. // Add a new section to manage users ignored in specific topics
  194. let $mainArea = document.querySelector('#ipsLayout_mainArea')
  195. $mainArea.appendChild(document.createElement('br'))
  196. let $div = document.createElement('div')
  197. $div.className = 'ipsBox'
  198.  
  199. function populateIgnoredUsersInTopics() {
  200. let ignoredUsersInTopics = JSON.parse(localStorage.ignoredUsersInTopics || '{}')
  201.  
  202. $div.innerHTML = `
  203. <h2 class="ipsType_sectionTitle ipsType_reset ipsClear">Users currently being ignored in specific topics</h2>
  204. <ol class="ipsDataList ipsGrid ipsGrid_collapsePhone ipsClear" data-role="tableRows">
  205. </ol>`
  206. let $ol = $div.querySelector('ol')
  207.  
  208. if (Object.keys(ignoredUsersInTopics).length == 0) {
  209. $ol.innerHTML = `<li class="ipsDataItem">
  210. <div class="ipsType_light ipsType_center ipsPad">
  211. <br>
  212. <br>
  213. You're not currently ignoring any users in specific topics.
  214. </div>
  215. </li>`
  216. }
  217.  
  218. for (let [topicId, topicConfig] of Object.entries(ignoredUsersInTopics)) {
  219. for (let user of topicConfig.users) {
  220. let $li = document.createElement('li')
  221. $li.className = 'ipsDataItem ipsGrid_span6 ipsFaded_withHover'
  222. $li.innerHTML = `
  223. <p class="ipsType_reset ipsDataItem_icon">
  224. <a href="https://${location.host}/index.php?/profile/${user.id}" class="ipsUserPhoto ipsUserPhoto_tiny">
  225. <img src="${user.avatar}" alt="${user.name}">
  226. </a>
  227. </p>
  228. <div class="ipsDataItem_main">
  229. <h4 class="ipsDataItem_title"><strong>${user.name}</strong></h4>
  230. <ul class="ipsList_inline">
  231. <li class="ipsType_light">
  232. in <a href="https://${location.host}/index.php?/topic/${topicId}">
  233. ${topicConfig.name}
  234. </a>
  235. </li>
  236. <li>
  237. <a href="#" class="unignore ipsPos_middle ipsType_blendLinks">
  238. <i class="fa fa-times-circle"></i> Stop ignoring
  239. </a>
  240. </li>
  241. </ul>
  242. </div>`
  243. $li.querySelector('a.unignore').addEventListener('click', (e) => {
  244. e.preventDefault()
  245. let ignoredUsersInTopics = JSON.parse(localStorage.ignoredUsersInTopics || '{}')
  246. if (!ignoredUsersInTopics[topicId]) return populateIgnoredUsersInTopics()
  247. let index = ignoredUsersInTopics[topicId].users.findIndex(u => u.id == user.id)
  248. if (index == -1) return populateIgnoredUsersInTopics()
  249. ignoredUsersInTopics[topicId].users.splice(index, 1)
  250. if (ignoredUsersInTopics[topicId].users.length == 0) {
  251. delete ignoredUsersInTopics[topicId]
  252. }
  253. localStorage.ignoredUsersInTopics = JSON.stringify(ignoredUsersInTopics)
  254. populateIgnoredUsersInTopics()
  255. })
  256. $ol.appendChild($li)
  257. }
  258. }
  259.  
  260. $mainArea.appendChild($div)
  261. }
  262.  
  263. populateIgnoredUsersInTopics()
  264. }
  265.  
  266. function UnreadContentPage() {
  267. let ignoredUserIds = JSON.parse(localStorage.ignoredUserIds || '[]')
  268. let view
  269.  
  270. function getView() {
  271. let $activeViewButton = document.querySelector('a.ipsButton_primary[data-action="switchView"]')
  272. return $activeViewButton ? $activeViewButton.textContent.trim() : null
  273. }
  274.  
  275. function processTopic($topic) {
  276. let $user = Array.from($topic.querySelectorAll('.ipsStreamItem_status a[href*="/profile/"]')).pop()
  277. if (!$user) return
  278. let userId = USER_LINK_ID_RE.exec($user.href)[1]
  279. if (ignoredUserIds.includes(userId)) {
  280. $topic.remove()
  281. }
  282. }
  283.  
  284. /**
  285. * Process topics within a topic container and watch for a new topic container being added.
  286. * When you click "Load more activity", a new <div> is added to the end of the topic container.
  287. */
  288. function processTopicContainer($el) {
  289. Array.from($el.querySelectorAll(':scope > li.ipsStreamItem'), processTopic)
  290.  
  291. new MutationObserver((mutations) => {
  292. mutations.forEach((mutation) => {
  293. if (view != getView()) {
  294. processView()
  295. }
  296. else if (mutation.addedNodes[0].tagName === 'DIV') {
  297. processTopicContainer(mutation.addedNodes[0])
  298. }
  299. })
  300. }).observe($el, {childList: true})
  301. }
  302.  
  303. /**
  304. * Process topics when the view changes between Condensed and Expanded.
  305. */
  306. function processView() {
  307. view = getView()
  308. processTopicContainer(document.querySelector('ol.ipsStream'))
  309. }
  310.  
  311. processView()
  312. }
  313.  
  314. function ForumPage() {
  315. let ignoredUserIds = JSON.parse(localStorage.ignoredUserIds || '[]')
  316.  
  317. function processTopic($topic) {
  318. let $user = $topic.querySelector('.ipsDataItem_meta a')
  319. if (!$user) return
  320. let userId = USER_LINK_ID_RE.exec($user.href)[1]
  321. if (ignoredUserIds.includes(userId)) {
  322. $topic.remove()
  323. }
  324. }
  325.  
  326. // Initial list of topics
  327. Array.from(document.querySelectorAll('ol.cTopicList > li.ipsDataItem[data-rowid]'), processTopic)
  328.  
  329. // Watch for topics being replaced when paging
  330. new MutationObserver(mutations =>
  331. mutations.forEach(mutation =>
  332. Array.from(mutation.addedNodes).filter(node => node.nodeType === Node.ELEMENT_NODE).map(processTopic)
  333. )
  334. ).observe(document.querySelector('ol.cTopicList'), {childList: true})
  335. }
  336.  
  337. let page
  338. if (location.href.includes('index.php?/topic/')) {
  339. page = TopicPage
  340. }
  341. else if (location.href.includes('index.php?/ignore/')) {
  342. page = IgnoredUsersPage
  343. }
  344. else if (location.href.includes('index.php?/discover/unread')) {
  345. page = UnreadContentPage
  346. }
  347. else if (location.href.includes('index.php?/forum/')) {
  348. page = ForumPage
  349. }
  350.  
  351. if (page) {
  352. page()
  353. }