SoundCloud Feed Control

Filter by post, repost, and playlists for a better feed experience

  1. // ==UserScript==
  2. // @name SoundCloud Feed Control
  3. // @version 2.002
  4. // @license MIT
  5. // @author nov0id
  6. // @description Filter by post, repost, and playlists for a better feed experience
  7. // @match *://soundcloud.com/feed
  8. // @grant none
  9. // @namespace https://rainbowlabllc.com/
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Function to get setting for the current tab
  16. function getTabSetting(key, defaultValue) {
  17. return sessionStorage.getItem(key) !== null
  18. ? JSON.parse(sessionStorage.getItem(key))
  19. : defaultValue;
  20. }
  21.  
  22. // Function to set setting for the current tab
  23. function setTabSetting(key, value) {
  24. sessionStorage.setItem(key, JSON.stringify(value));
  25. }
  26.  
  27. // Default settings per tab
  28. const settings = {
  29. filterPosts: getTabSetting('filterPosts', false),
  30. filterPlaylists: getTabSetting('filterPlaylists', false),
  31. filterReposts: getTabSetting('filterReposts', false)
  32. };
  33.  
  34. function createUI() {
  35. let panel = document.createElement('div');
  36. panel.id = 'sc-filter-panel';
  37. panel.innerHTML = `
  38. <style>
  39. #sc-filter-panel {
  40. position: fixed;
  41. top: 50px;
  42. left: -200px;
  43. width: 200px;
  44. background: rgba(0, 0, 0, 0.6);
  45. padding: 15px;
  46. border-radius: 0 10px 10px 0;
  47. z-index: 9999;
  48. color: white;
  49. font-family: 'Segoe UI', sans-serif;
  50. font-size: 14px;
  51. transition: right 0.3s ease-in-out, background 0.2s ease-in-out;
  52. }
  53. #sc-filter-panel:hover {
  54. left: 0;
  55. background: rgba(0, 0, 0, 0.9);
  56. }
  57. .sc-filter-slider {
  58. display: flex;
  59. align-items: center;
  60. justify-content: space-between;
  61. margin-bottom: 10px;
  62. font-weight: normal;
  63. opacity: 0;
  64. transition: opacity 0.3s ease-in-out;
  65. }
  66. #sc-filter-panel:hover .sc-filter-slider {
  67. opacity: 1;
  68. }
  69. .sc-filter-slider input {
  70. width: 18px;
  71. height: 18px;
  72. }
  73. .sc-filter-title {
  74. text-align: center;
  75. font-size: 16px;
  76. margin-bottom: 10px;
  77. font-weight: bold;
  78. text-transform: uppercase;
  79. letter-spacing: 1px;
  80. opacity: 0;
  81. transition: opacity 0.3s ease-in-out;
  82. }
  83. #sc-filter-panel:hover .sc-filter-title {
  84. opacity: 1;
  85. }
  86. </style>
  87. <div class='sc-filter-title'>Feed Filter</div>
  88. <div class='sc-filter-slider'>
  89. <label>Filter Posts</label>
  90. <input type='checkbox' id='filterPosts' ${settings.filterPosts ? 'checked' : ''}>
  91. </div>
  92. <div class='sc-filter-slider'>
  93. <label>Filter Playlists</label>
  94. <input type='checkbox' id='filterPlaylists' ${settings.filterPlaylists ? 'checked' : ''}>
  95. </div>
  96. <div class='sc-filter-slider'>
  97. <label>Filter Reposts</label>
  98. <input type='checkbox' id='filterReposts' ${settings.filterReposts ? 'checked' : ''}>
  99. </div>
  100. `;
  101. document.body.appendChild(panel);
  102.  
  103. document.getElementById('filterPosts').addEventListener('change', () => toggleSetting('filterPosts'));
  104. document.getElementById('filterPlaylists').addEventListener('change', () => toggleSetting('filterPlaylists'));
  105. document.getElementById('filterReposts').addEventListener('change', () => toggleSetting('filterReposts'));
  106. }
  107.  
  108. function toggleSetting(key) {
  109. settings[key] = !settings[key];
  110. setTabSetting(key, settings[key]); // Store setting per tab
  111. filterFeed();
  112. }
  113.  
  114. function filterFeed() {
  115. document.querySelectorAll('.soundList__item').forEach((element) => {
  116. const context = element.querySelector('.sound.streamContext');
  117. if (context) {
  118. let label = context.getAttribute('aria-label')?.toLowerCase();
  119. if (label.includes('reposted') && settings.filterReposts) {
  120. element.remove();
  121. } else if (label.includes('playlist') && settings.filterPlaylists) {
  122. element.remove();
  123. } else if (!label.includes('playlist') && !label.includes('reposted') && settings.filterPosts) {
  124. element.remove();
  125. }
  126. }
  127. });
  128. }
  129.  
  130. // Initialize UI and filter logic
  131. createUI();
  132. filterFeed();
  133.  
  134. // Observe DOM for dynamically added elements
  135. const observer = new MutationObserver(filterFeed);
  136. observer.observe(document.body, { childList: true, subtree: true });
  137. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址