您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Sort Lemmy posts, comments, communities & search. Reload the webpage after changing the sort type in menu to take effect. To make this script runnable, the CSP for the website must be disabled/modified/removed using an addon.
// ==UserScript== // @name [Lemmy] Sort Posts, Comments, Communities & Search // @match https://aussie.zone/* // @match https://beehaw.org/* // @match https://discuss.tchncs.de/* // @match https://feddit.nl/* // @match https://feddit.org/* // @match https://feddit.uk/* // @match https://hexbear.net/* // @match https://infosec.pub/* // @match https://jlai.lu/* // @match https://lemmy.blahaj.zone/* // @match https://lemmy.ca/* // @match https://lemmy.dbzer0.com/* // @match https://lemmy.ml/* // @match https://lemmy.today/* // @match https://lemmy.world/* // @match https://lemmy.zip/* // @match https://lemmybefree.net/* // @match https://lemmygrad.ml/* // @match https://midwest.social/* // @match https://programming.dev/* // @match https://reddthat.com/* // @match https://sh.itjust.works/* // @match https://slrpnk.net/* // @match https://sopuli.xyz/* // @noframes // @run-at document-start // @inject-into page // @grant GM_deleteValue // @grant GM_getValues // @grant GM_registerMenuCommand // @grant GM_setValue // @grant unsafeWindow // @namespace Violentmonkey Scripts // @author SedapnyaTidur // @version 1.0.0 // @license MIT // @revision 8/18/2025, 11:39:49 PM // @description Sort Lemmy posts, comments, communities & search. Reload the webpage after changing the sort type in menu to take effect. To make this script runnable, the CSP for the website must be disabled/modified/removed using an addon. // ==/UserScript== (function() { 'use strict'; const posts = { Hot: 'Hot', Active: 'Active', Scaled: 'Scaled', Controversial: 'Controversial', New: 'New', Old: 'Old', MostComments: 'Most Comments', NewComments: 'New Comments', TopHour: 'Top Hour', TopSixHour: 'Top 6 Hours', TopTwelveHour: 'Top 12 Hours', TopDay: 'Top Day', TopWeek: 'Top Week', TopMonth: 'Top Month', TopThreeMonths: 'Top 3 Months', TopSixMonths: 'Top 6 Months', TopNineMonths: 'Top 9 Months', TopYear: 'Top Year', TopAll: 'Top All Time' }; const comments = ['Hot', 'Top', 'Controversial', 'New', 'Old']; // Based on the keys of "posts" above and not the values for posts, communities & search. const defaults = { posts: 'Active', comments: 'Hot', communities: 'TopMonth', search: 'TopAll' }; let { postsSortBy, commentsSortBy, communitiesSortBy, searchSortBy, reload } = GM_getValues({ postsSortBy: defaults.posts, commentsSortBy: defaults.comments, communitiesSortBy: defaults.communities, searchSortBy: defaults.search, reload: false }); const window = unsafeWindow; let attrObserver, currURL = window.location.href, first = true; let searchInterval, searchTimeout, startInterval, startTimeout; const configs = [{ path: /^(?:\/$|\/c\/)/, defaultSort: defaults.posts, sort: postsSortBy }, { path: /^\/post\//, defaultSort: defaults.comments, sort: commentsSortBy }, { path: /^\/communities/, defaultSort: defaults.communities, sort: communitiesSortBy }, { path: /^\/search/, defaultSort: defaults.search, sort: searchSortBy }]; if (reload) GM_deleteValue('reload'); const location = window.location; // For URL like this "https://lemmy.ca/search?" remove the "?" at the end and window.location.search is empty. const href = location.href.replace(/([^=])\?+$/, '$1'); const path = location.pathname; const search = location.search; // May redirect to a different URL for the first visit. for (const config of configs) { if (config.path.test(path)) { if (!search) { //window.location.search is empty. if (config.sort !== config.defaultSort) { window.stop(); location.replace(href + `?sort=${config.sort}`); return; } } else if (/[?&]sort=[^&]+/.test(search)) { if (!reload && !search.includes(`sort=${config.sort}`)) { window.stop(); location.replace(href.replace(/(.)sort=[^&]+/, `$1sort=${config.sort}`)); return; } } else { window.stop(); location.replace(href + `&sort=${config.sort}`); return; } } } // "inject-into page" is a must for this to work. const pushState = window.History.prototype.pushState; window.History.prototype.pushState = function() { const location = new URL(arguments[2], window.location.href); const href = location.href; const path = location.pathname; const search = location.search; for (const config of configs) { if (config.path.test(path)) { if (!search) { // Consume if window.location.search is empty. if (config.sort !== config.defaultSort) { arguments[2] = href + `?sort=${config.sort}`; } } else if (!/[?&]sort=[^&]+/.test(search)) { // So that users can change to different sort types. arguments[2] = href + `&sort=${config.sort}`; } // Avoid duplicate URLs in history when clicking the top-left icon/label. if(arguments[2] === window.location.href && window.location.pathname === '/') return window.history.go(0); break; } } return pushState.apply(this, arguments); }; // Dirty trick to make the visited posts highlighted via a style a:visited. const changeLinks = function() { searchTimeout = setTimeout(() => { clearInterval(searchInterval); }, 5000); searchInterval = setInterval(() => { const targets = document.body.querySelectorAll('a[href^="/post/"]'); if (targets.length < 6) return; clearInterval(searchInterval); clearTimeout(searchTimeout); searchInterval = 0; searchTimeout = 0; for (const anchor of targets) { const href = anchor.href; // Complete URL. const search = href.replace(/^[^?]+/, ''); if (!search) { if (commentsSortBy !== defaults.comments) { anchor.href = href + `?sort=${commentsSortBy}`; } } else if (!/[?&]sort=[^&]+/.test(search)) { anchor.href = href + `&sort=${commentsSortBy}`; } if (first) { // May get overriden by Lemmy. first = false; attrObserver = new MutationObserver(changeLinks); attrObserver.observe(anchor, { attributes: true }); } } }, 500); }; const reset = function() { clearInterval(searchInterval); clearTimeout(searchTimeout); if (attrObserver) { attrObserver.disconnect(); attrObserver = undefined; } searchInterval = 0; searchTimeout = 0; first = true; }; const start = function() { startTimeout = setTimeout(() => { clearInterval(startInterval); }, 5000); startInterval = setInterval(() => { if (!document.body) return; clearInterval(startInterval); clearTimeout(startTimeout); new MutationObserver(() => { if (window.location.href === currURL) return; currURL = window.location.href; reset(); if (/^(?:\/$|\/c\/|\/search)/.test(window.location.pathname)) { changeLinks(); return; } }).observe(document.body, { childList: true, subtree: true }); }, 500); }; // Change visited links for the first time or reload. if (/^(?:\/$|\/c\/|\/search)/.test(window.location.pathname)) changeLinks(); start(); window.addEventListener('beforeunload', () => { GM_setValue('reload', true); }, false); const next = function(array, startIndex, sortBy) { for (let i = startIndex; i < array.length; ++i) { if (array[i] === sortBy) { if (i === array.length - 1) return array[startIndex]; return array[i + 1]; } } }; const clickPosts = function() { postsSortBy = next(Object.keys(posts), 0, postsSortBy); GM_setValue('postsSortBy', postsSortBy); GM_registerMenuCommand(`Posts: 《${posts[postsSortBy]}》`, clickPosts, { id: '0', autoClose: false, title: 'Click to change the posts sort type.' }); }; const clickComments = function() { commentsSortBy = next(comments, 0, commentsSortBy); GM_setValue('commentsSortBy', commentsSortBy); GM_registerMenuCommand(`Comments: 《${commentsSortBy}》`, clickComments, { id: '1', autoClose: false, title: 'Click to change the comments sort type.' }); }; const clickCommunities = function() { communitiesSortBy = next(Object.keys(posts), 0, communitiesSortBy); GM_setValue('communitiesSortBy', communitiesSortBy); GM_registerMenuCommand(`Communities: 《${posts[communitiesSortBy]}》`, clickCommunities, { id: '2', autoClose: false, title: 'Click to change the communities sort type.' }); }; const clickSearch = function() { searchSortBy = next(Object.keys(posts), 3, searchSortBy); GM_setValue('searchSortBy', searchSortBy); GM_registerMenuCommand(`Search: 《${posts[searchSortBy]}》`, clickSearch, { id: '3', autoClose: false, title: 'Click to change the search sort type.' }); }; GM_registerMenuCommand(`Posts:《${posts[postsSortBy]}》`, clickPosts, { id: '0', autoClose: false, title: 'Click to change the posts sort type.' }); GM_registerMenuCommand(`Comments:《${commentsSortBy}》`, clickComments, { id: '1', autoClose: false, title: 'Click to change the comments sort type.' }); GM_registerMenuCommand(`Communities: 《${posts[communitiesSortBy]}》`, clickCommunities, { id: '2', autoClose: false, title: 'Click to change the communities sort type.' }); GM_registerMenuCommand(`Search: 《${posts[searchSortBy]}》`, clickSearch, { id: '3', autoClose: false, title: 'Click to change the search sort type.' }); })();