您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Hide older articles that have both the same title and URL within a category
当前为
- // ==UserScript==
- // @name FreshRSS Duplicate Filter
- // @namespace https://github.com/hiroki-miya
- // @version 1.0.0
- // @description Hide older articles that have both the same title and URL within a category
- // @author hiroki-miya
- // @license MIT
- // @match https://freshrss.example.net
- // @grant GM_addStyle
- // @grant GM_getValue
- // @grant GM_registerMenuCommand
- // @grant GM_setValue
- // @grant GM_xmlhttpRequest
- // @run-at document-idle
- // ==/UserScript==
- (function() {
- 'use strict';
- // Default settings
- const DEFAULT_CATEGORY_LIST = [];
- const DEFAULT_CHECK_LIMIT = 100;
- // Load saved settings
- let selectedCategories = GM_getValue('selectedCategories', DEFAULT_CATEGORY_LIST);
- let checkLimit = GM_getValue('checkLimit', DEFAULT_CHECK_LIMIT);
- // Add styles
- GM_addStyle(`
- #freshrss-duplicate-filter {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- z-index: 10000;
- background-color: white;
- border: 1px solid black;
- padding:10px;
- }
- #freshrss-duplicate-filter > h2 {
- box-shadow: inset 0 0 0 0.5px black;
- padding: 5px 10px;
- text-align: center;
- }
- #freshrss-duplicate-filter > h4 {
- margin-top: 0;
- }
- `);
- // Normalize category name: remove spaces and everything after newlines
- function normalizeCategoryName(categoryName) {
- return categoryName.replace(/[ \r\n].*/g, '');
- }
- // Settings screen
- function showSettings() {
- const categories = Array.from(document.querySelectorAll('.tree-folder.category')).map(cat => normalizeCategoryName(cat.innerText));
- const selected = selectedCategories || [];
- let categoryOptions = categories.map(cat => {
- const checked = selected.includes(cat) ? 'checked' : '';
- return `<label><input type="checkbox" value="${cat}" ${checked}> ${cat}</label>`;
- }).join('');
- const limitInput = `<label>Check Limit: <input type="number" id="checkLimit" value="${checkLimit}" min="1"></label>`;
- const settingsHTML = `
- <h2>Duplicate Filter Settings</h2>
- <h4>Select Categories</h4>
- ${categoryOptions}
- ${limitInput}
- <br>
- <button id="fdfs-save">Save</button>
- <button id="fdfs-close">Close</button>
- `;
- const settingsDiv = document.createElement('div');
- settingsDiv.id = "freshrss-duplicate-filter";
- settingsDiv.innerHTML = settingsHTML;
- document.body.appendChild(settingsDiv);
- // Function to display the tooltip
- function showTooltip(message) {
- // Create the tooltip element
- const tooltip = document.createElement('div');
- tooltip.textContent = message;
- tooltip.style.position = 'fixed';
- tooltip.style.top = '50%';
- tooltip.style.left = '50%';
- tooltip.style.transform = 'translate(-50%, -50%)';
- tooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
- tooltip.style.color = 'white';
- tooltip.style.padding = '10px 20px';
- tooltip.style.borderRadius = '5px';
- tooltip.style.zIndex = '10000';
- tooltip.style.fontSize = '16px';
- tooltip.style.textAlign = 'center';
- // Add the tooltip to the page
- document.body.appendChild(tooltip);
- // Automatically remove the tooltip after 1 second
- setTimeout(() => {
- document.body.removeChild(tooltip);
- }, 1000);
- }
- // Save button event
- document.getElementById('fdfs-save').addEventListener('click', () => {
- const selectedCheckboxes = Array.from(document.querySelectorAll('input[type="checkbox"]:checked')).map(el => el.value);
- const newLimit = parseInt(document.getElementById('checkLimit').value, 10);
- GM_setValue('selectedCategories', selectedCheckboxes);
- GM_setValue('checkLimit', newLimit);
- // Show tooltip instead of alert
- showTooltip('Saved');
- // Mark duplicates as read after saving
- markDuplicatesAsRead();
- });
- // Close button event
- document.getElementById('fdfs-close').addEventListener('click', () => {
- document.body.removeChild(settingsDiv);
- });
- }
- // Register settings screen
- GM_registerMenuCommand('Settings', showSettings);
- // Function to add "duplicate" class to articles
- function markAsDuplicate(articleElement) {
- if (!articleElement) return;
- articleElement.classList.add('duplicate');
- articleElement.style.display = 'none';
- }
- // Check for duplicate articles and mark older ones as read
- function markDuplicatesAsRead() {
- const articles = Array.from(document.querySelectorAll('#stream > .flux:not(:has(.duplicate))'));
- const articleMap = new Map();
- articles.slice(-checkLimit).forEach(article => {
- const titleElement = article.querySelector('a.item-element.title');
- if (!titleElement) return;
- const title = titleElement.innerText;
- const url = titleElement.href;
- const timeElement = article.querySelector('.date > time');
- if (!timeElement) return;
- const articleData = { element: article, timestamp: new Date(timeElement.datetime).getTime() };
- // Duplicate check
- if (articleMap.has(title)) {
- const existingArticle = articleMap.get(title);
- if (existingArticle.url === url) {
- const older = existingArticle.timestamp <= articleData.timestamp ? existingArticle : articleData;
- // Mark older articles as duplicates
- markAsDuplicate(older.element);
- }
- } else {
- articleMap.set(title, { ...articleData, url });
- }
- });
- }
- // Get the current category
- function getCurrentCategory() {
- const categoryElement = document.querySelector('.category.active > a > span.title');
- return categoryElement ? normalizeCategoryName(categoryElement.innerText) : null;
- }
- // Setup MutationObserver
- function setupObserver() {
- const targetNode = document.querySelector('#stream');
- if (targetNode) {
- const observer = new MutationObserver(() => {
- const currentCategory = getCurrentCategory();
- if (currentCategory && selectedCategories.includes(currentCategory)) {
- markDuplicatesAsRead();
- }
- });
- observer.observe(targetNode, { childList: true, subtree: true });
- // Initial run
- const currentCategory = getCurrentCategory();
- if (currentCategory && selectedCategories.includes(currentCategory)) {
- markDuplicatesAsRead();
- }
- } else {
- // Retry if #stream is not found
- setTimeout(setupObserver, 1000);
- }
- }
- // Start setupObserver when the script starts
- setupObserver();
- })();