RSS+ Enhancer

Show all RSS/Atom/JSON feeds with UI, smart guessing, copy features, feed counter, and draggable floating icon.

  1. // ==UserScript==
  2. // @name RSS+ Enhancer
  3. // @namespace Eliminater74
  4. // @version 2.6
  5. // @description Show all RSS/Atom/JSON feeds with UI, smart guessing, copy features, feed counter, and draggable floating icon.
  6. // @author Eliminater74
  7. // @license MIT
  8. // @match *://*/*
  9. // @grant GM_setClipboard
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. const foundFeeds = [];
  16.  
  17. function isValidFeed(link) {
  18. const types = [
  19. "application/rss+xml",
  20. "application/atom+xml",
  21. "application/json",
  22. "application/feed+json"
  23. ];
  24. return types.includes(link.type);
  25. }
  26.  
  27. function getFeeds() {
  28. const links = [...document.querySelectorAll('link')];
  29. const guesses = [
  30. '/feed', '/rss', '/rss.xml', '/atom.xml', '/feeds/posts/default', '/?feed=rss2'
  31. ];
  32.  
  33. links.forEach(link => {
  34. if (isValidFeed(link)) {
  35. foundFeeds.push({ title: link.title || link.href, href: link.href });
  36. }
  37. });
  38.  
  39. guesses.forEach(path => {
  40. try {
  41. const testURL = new URL(path, location.href).href;
  42. fetch(testURL, { method: 'GET' }).then(resp => {
  43. if (resp.ok) {
  44. resp.text().then(txt => {
  45. if (txt.includes("<rss") || txt.includes("<feed") || txt.includes("application/json")) {
  46. if (!foundFeeds.find(f => f.href === testURL)) {
  47. foundFeeds.push({ title: testURL, href: testURL });
  48. updatePanel();
  49. }
  50. }
  51. });
  52. }
  53. }).catch(() => {});
  54. } catch {}
  55. });
  56. }
  57.  
  58. function createUI() {
  59. const icon = document.createElement('div');
  60. icon.id = 'rssPlusIcon';
  61. icon.textContent = '📡';
  62. icon.style.cssText = `
  63. position: fixed;
  64. bottom: 20px;
  65. right: 20px;
  66. font-size: 24px;
  67. background: #222;
  68. color: #fff;
  69. padding: 10px 14px;
  70. border-radius: 50%;
  71. z-index: 999999;
  72. cursor: grab;
  73. box-shadow: 0 0 10px #000;
  74. user-select: none;
  75. `;
  76.  
  77. const badge = document.createElement('span');
  78. badge.id = 'rssCount';
  79. badge.style.cssText = `
  80. position: absolute;
  81. top: -6px;
  82. right: -6px;
  83. background: red;
  84. color: white;
  85. font-size: 12px;
  86. border-radius: 50%;
  87. padding: 2px 5px;
  88. display: none;
  89. `;
  90. icon.appendChild(badge);
  91.  
  92. // Drag handling
  93. let isDragging = false;
  94. let offsetX, offsetY;
  95.  
  96. icon.addEventListener('mousedown', (e) => {
  97. isDragging = true;
  98. offsetX = e.clientX - icon.getBoundingClientRect().left;
  99. offsetY = e.clientY - icon.getBoundingClientRect().top;
  100. icon.style.cursor = 'grabbing';
  101. e.preventDefault();
  102. });
  103.  
  104. document.addEventListener('mousemove', (e) => {
  105. if (!isDragging) return;
  106. icon.style.left = 'unset';
  107. icon.style.top = e.clientY - offsetY + 'px';
  108. icon.style.right = 'unset';
  109. icon.style.left = e.clientX - offsetX + 'px';
  110. icon.style.bottom = 'unset';
  111. });
  112.  
  113. document.addEventListener('mouseup', () => {
  114. if (isDragging) {
  115. isDragging = false;
  116. icon.style.cursor = 'grab';
  117. }
  118. });
  119.  
  120. icon.onclick = () => {
  121. const panel = document.getElementById('rssPanel');
  122. panel.style.display = (panel.style.display === 'none') ? 'block' : 'none';
  123. };
  124.  
  125. document.body.appendChild(icon);
  126.  
  127. const panel = document.createElement('div');
  128. panel.id = 'rssPanel';
  129. panel.style.cssText = `
  130. position: fixed;
  131. top: 60px;
  132. right: 20px;
  133. background: #2c2c2c;
  134. color: white;
  135. font-family: sans-serif;
  136. padding: 10px;
  137. border-radius: 6px;
  138. box-shadow: 0 0 15px #000;
  139. z-index: 999998;
  140. max-width: 400px;
  141. min-width: 250px;
  142. display: none;
  143. `;
  144.  
  145. panel.innerHTML = `<div style="font-weight:bold; margin-bottom: 6px;">RSS Feeds Found:</div><div id="rssFeedList"></div>
  146. <div style="margin-top: 10px;">
  147. <button id="rssCopyAll">Copy All</button>
  148. <button id="rssToggleTheme">Toggle Theme</button>
  149. <button id="rssHide">Hide</button>
  150. </div>`;
  151.  
  152. document.body.appendChild(panel);
  153.  
  154. document.getElementById('rssCopyAll').onclick = () => {
  155. const text = foundFeeds.map(f => f.href).join('\n');
  156. GM_setClipboard(text);
  157. alert("All RSS feed URLs copied to clipboard!");
  158. };
  159.  
  160. document.getElementById('rssToggleTheme').onclick = () => {
  161. const dark = panel.style.background === 'white';
  162. panel.style.background = dark ? '#2c2c2c' : 'white';
  163. panel.style.color = dark ? 'white' : 'black';
  164. };
  165.  
  166. document.getElementById('rssHide').onclick = () => {
  167. panel.style.display = 'none';
  168. };
  169. }
  170.  
  171. function updatePanel() {
  172. const list = document.getElementById('rssFeedList');
  173. const badge = document.getElementById('rssCount');
  174. if (!list || !badge) return;
  175.  
  176. list.innerHTML = '';
  177. foundFeeds.forEach(f => {
  178. const row = document.createElement('div');
  179. row.style.marginBottom = '6px';
  180.  
  181. const link = document.createElement('a');
  182. link.href = f.href;
  183. link.target = '_blank';
  184. link.textContent = f.title.length > 60 ? f.title.slice(0, 60) + '…' : f.title;
  185. link.style.cssText = `color: #4FC3F7; word-break: break-all;`;
  186.  
  187. const copyBtn = document.createElement('button');
  188. copyBtn.textContent = '📋';
  189. copyBtn.style.cssText = `margin-left: 5px;`;
  190. copyBtn.onclick = () => GM_setClipboard(f.href);
  191.  
  192. row.appendChild(link);
  193. row.appendChild(copyBtn);
  194. list.appendChild(row);
  195. });
  196.  
  197. badge.style.display = foundFeeds.length ? 'block' : 'none';
  198. badge.textContent = foundFeeds.length;
  199. }
  200.  
  201. window.addEventListener('load', () => {
  202. createUI();
  203. getFeeds();
  204. setTimeout(updatePanel, 2500);
  205. });
  206. })();