// ==UserScript== // @name 水源显示回复可见 // @namespace CCCC_David // @version 0.1.0 // @description 可在水源论坛显示“仅回复可见”的回帖内容(UI极其简陋的初版本) // @author CCCC_David // @match https://shuiyuan.sjtu.edu.cn/* // @grant none // @downloadURL none // ==/UserScript== (async () => { 'use strict'; const supportsTrustedTypes = window.trustedTypes?.createPolicy; const allowedPolicy = supportsTrustedTypes ? trustedTypes.createPolicy('allowedPolicy', { createHTML: x => x }) : null; const createTrustedHTML = (html) => supportsTrustedTypes ? allowedPolicy.createHTML(html) : html; const topicId = parseInt(window.location.pathname.match(/^\/t\/topic\/(\d+)$/)?.[1], 10); if (Number.isNaN(topicId)) { // Not a topic page. return; } const topicInfoResponse = await fetch(`/t/${topicId}.json`, { method: 'GET', headers: { 'Discourse-Present': 'true', 'Discourse-Logged-In': 'true', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRF-Token': document.querySelector('meta[name=csrf-token]').getAttribute('content'), }, mode: 'same-origin', credentials: 'include', }); const topicInfo = await topicInfoResponse.json(); const isPrivateReply = topicInfo.private_replies; const numReplies = topicInfo.posts_count; if (!isPrivateReply) { // Not a private reply page. return; } const fetchReply = async (replyId) => { const replyURL = `/t/topic/${topicId}/${replyId}`; const response = await fetch(`/onebox?url=${encodeURIComponent(replyURL)}`, { method: 'GET', headers: { 'Discourse-Present': 'true', 'Discourse-Logged-In': 'true', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRF-Token': document.querySelector('meta[name=csrf-token]').getAttribute('content'), }, mode: 'same-origin', credentials: 'include', }); return response.text(); }; const addViewer = (parent) => { if (document.getElementById('show-private-reply-id')) { return; } const viewerContainer = document.createElement('div'); viewerContainer.innerHTML = createTrustedHTML(`
0 `); parent.appendChild(viewerContainer); const replyIdElement = document.getElementById('show-private-reply-id'); const replyContentElement = document.getElementById('show-private-reply-content'); document.getElementById('show-private-reply-inc-id').addEventListener('click', async () => { let replyId = parseInt(replyIdElement.textContent, 10); if (replyId < numReplies) { replyId += 1; } replyIdElement.textContent = replyId.toString(); const reply = await fetchReply(replyId); replyContentElement.innerHTML = createTrustedHTML(reply); }); document.getElementById('show-private-reply-dec-id').addEventListener('click', async () => { let replyId = parseInt(replyIdElement.textContent, 10); if (replyId > 1) { replyId -= 1; } replyIdElement.textContent = replyId.toString(); const reply = await fetchReply(replyId); replyContentElement.innerHTML = createTrustedHTML(reply); }); }; const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'childList') { for (const el of mutation.addedNodes) { if (el.classList?.contains('post-stream')) { addViewer(el); } } } else if (mutation.type === 'attributes') { if (!mutation.oldValue?.match(/(?:^|\s)post-stream(?:\s|$)/) && mutation.target.classList?.contains('post-stream')) { addViewer(mutation.target); } } } }); observer.observe(document.body, { subtree: true, childList: true, attributeFilter: ['class'], attributeOldValue: true, }); const postStreamContainer = document.getElementsByClassName('post-stream')[0]; if (postStreamContainer) { addViewer(postStreamContainer); } })();