// ==UserScript== // @name 查看Luogu中的文章、剪贴板、个人介绍 // @namespace http://tampermonkey.net/ // @version 0.3.0 // @description 查看Luogu中的文章、剪贴板、个人介绍 // @author Andy_hpy // @match *://www.luogu.com.cn/* // @match *://luogu.com.cn/* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_getResourceText // @resource sweetalert2css https://cdn.jsdelivr.net/npm/sweetalert2@11.26.25/dist/sweetalert2.min.css // @require https://cdn.jsdelivr.net/npm/sweetalert2@11.26.25/dist/sweetalert2.min.js // @require https://cdn.jsdelivr.net/npm/luogu-renderer@latest/dist/luogu-new-renderer.iife.min.js // @run-at document-start // @connect api.luogu.me // @connect .luogu.com.cn // @connect .luogu.com // @license AGPL-3.0 // ==/UserScript== async function init() { window.dataOfVote = JSON.parse(GM_getValue('dataOfVote', '{}')); window.dataOfFavor = JSON.parse(GM_getValue('dataOfFavor', '{}')); setInterval(() => { GM_setValue('dataOfVote', JSON.stringify(window.dataOfVote)); GM_setValue('dataOfFavor', JSON.stringify(window.dataOfFavor)); }, 60000); document.addEventListener('beforeunload', () => { GM_setValue('dataOfVote', JSON.stringify(window.dataOfVote)); GM_setValue('dataOfFavor', JSON.stringify(window.dataOfFavor)); }); } async function articlePage() { const override = setInterval(async function () { if (document.querySelector('body > div')) { clearInterval(override); document.querySelector('body > div').style.visibility = 'hidden'; } }, 10); const id = location.href.split('/').pop(); async function deal404() { const wait = setInterval(async function () { if (document.querySelector('html')) { clearInterval(wait); if (document.querySelector('body > div > div.card > pre#url')) { Swal.fire({ title: '内容未找到', text: '该内容尚未被保存站收录,是否立即保存?', icon: 'warning', showCancelButton: true, confirmButtonText: '保存', cancelButtonText: '返回', confirmButtonColor: "#3085d6", cancelButtonColor: "#d33", allowEscapeKey: false, allowOutsideClick: false }).then(async (result) => { if (result.isConfirmed) { Swal.mixin({ toast: true, position: "top-end", showConfirmButton: false, timer: 3000, timerProgressBar: true, didOpen: (toast) => { toast.onmouseenter = Swal.stopTimer; toast.onmouseleave = Swal.resumeTimer; } }).fire({ icon: "success", title: "保存请求已提交" }); const response = await saveArticle(id); const workId = response.data.workflowId; if (response.code === 200) { const wait = setInterval(async () => { if ((await getWorkflow(workId)).code === 200) { clearInterval(wait); Swal.fire({ title: '保存成功', icon: 'success', confirmButtonText: '确定' }).then(() => { location.reload() }); } }, 500); } else { Swal.fire({ title: '保存失败', text: '保存失败,请稍后再试', icon: 'error', confirmButtonText: '确定' }).then(() => { document.querySelector('body > div').style.visibility = ''; }); } } else if (result.dismiss === Swal.DismissReason.cancel) { history.back(); } }); } else { document.querySelector('body > div').style.display = ''; } } }, 500); return; } let articleInfo, user, theme, metaHtml, replyCount; try { [articleInfo, { user, theme, metaHtml }, replyCount] = await Promise.all([ getDataOnLuoguMe(`https://api.luogu.me/article/query/${id}`), getTheme_MyUserInfo_AndMetaHtml(), getReplyCount(id) ]); } catch (error) { deal404(); return; } if (articleInfo.code === 404) { deal404(); return; } (async () => { document.querySelector('head').insertAdjacentHTML('beforeend', metaHtml); })(); const info = articleInfo.data; const [authorInfo, upvote] = await Promise.all([ getUserInfo(info.authorId), (async () => { const response = await fetch(`https://www.luogu.com.cn/article/${id}/vote?vote=${Number(window.dataOfVote[id] || 0)}`, { method: 'POST' }); const data = await response.json(); if (!response.ok) { return -1; } return data.upvotes; })() ]); const finalData = { instance: "main", template: "article.show", status: 200, locale: "zh-CN", data: { article: { lid: id, title: info.title, category: info.category, time: Math.floor(new Date(info.createdAt).getTime() / 1000), author: { uid: authorInfo.uid, avatar: authorInfo.avatar, name: authorInfo.name, slogan: authorInfo.slogan, badge: authorInfo.badge, isAdmin: authorInfo.isAdmin, isBanned: authorInfo.isBanned, color: authorInfo.color, ccfLevel: authorInfo.ccfLevel, xcpcLevel: authorInfo.xcpcLevel, background: authorInfo.background, verified: true }, upvote: upvote, favorCount: Math.max(window.dataOfFavor[id], info.favorCount), solutionFor: info.solutionForPid == null ? null : { pid: info.solutionForPid }, content: info.content, canReply: true, canEdit: false, status: 1, replyCount: replyCount, voted: Number(window.dataOfVote[id] || 0) }, canReply: true, canEdit: false, favored: window.dataOfFavor[id] || 0 }, user: user, theme: theme } console.debug(finalData); function write() { document.open(); document.write(` ${metaHtml} ${info.title} - 洛谷专栏
`); document.close(); } const wait = setInterval(async function () { if (document.querySelector('html')) { if (document.querySelector('body > div > div.card > pre#url')) { write(); } else { document.querySelector('body > div').style.visibility = ''; } clearInterval(wait); } }, 500); setInterval(async function () { document.querySelectorAll('div.actions').forEach(async div => { if (!div.querySelector('div.button-2line[data-update-btn]') || div.querySelector('div.button-2line[data-update-btn]').dataset.init !== 'true') { if (div.querySelector('div.button-2line[data-update-btn]')) { div.querySelector('div.button-2line[data-update-btn]').dataset.init = 'true'; } else { div.insertAdjacentHTML('beforeend', `
更新
`); if (div.classList.contains('left-mode')) { if (document.querySelectorAll('div.actions.left-mode.hasDrawer > *').length === 4) { document.querySelector('div.actions.left-mode.hasDrawer').style.top = 'calc(50vh - 300px/2)'; } else { document.querySelector('div.actions.left-mode.hasDrawer').style.top = 'calc(50vh - 380px/2)'; } } } const btn = div.querySelector('div.button-2line[data-update-btn]'); if (btn) btn.dataset.init = 'true'; btn.addEventListener('click', async function (e) { if (btn.dataset.disabled === 'true') return; btn.dataset.disabled = 'true'; Swal.mixin({ toast: true, position: "top-end", showConfirmButton: false, timer: 3000, timerProgressBar: true, didOpen: (toast) => { toast.onmouseenter = Swal.stopTimer; toast.onmouseleave = Swal.resumeTimer; } }).fire({ icon: "success", title: "更新请求已提交" }); const response = await saveArticle(id); const workId = response.data.workflowId; if (response.code === 200) { const wait = setInterval(async () => { if ((await getWorkflow(workId)).code === 200) { clearInterval(wait); Swal.fire({ title: '更新成功', icon: 'success', confirmButtonColor: "#3085d6", confirmButtonText: '确定' }).then(() => { location.reload() }); btn.dataset.disabled = 'false'; } }, 500); } else { Swal.fire({ title: '更新失败', text: '更新失败,请稍后再试', icon: 'error', confirmButtonColor: "#d33", confirmButtonText: '确定' }); btn.dataset.disabled = 'false'; } }); } }); }, 500); } async function getReplyCount(id) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: `https://api.luogu.me/article/comments/${id}`, redirect: "follow", onload: function (res) { try { const json = JSON.parse(res.responseText); if (json.data && Array.isArray(json.data.comments)) { resolve(json.data.comments.length); } else { reject(new Error('无效的评论数据')) } } catch (e) { reject(e); } }, onerror: reject }); }); } async function getTheme_MyUserInfo_AndMetaHtml() { const response = await fetch('https://www.luogu.com.cn/article/'); const html = await response.text(); const match = html.match(/
`); document.close(); } const wait = setInterval(async function () { if (document.querySelector('html')) { if (document.querySelector('body > div > div.card > pre#url')) { write(); } else { document.querySelector('body > div').style.visibility = ''; } clearInterval(wait); } }, 500); setInterval(async function () { if (document.querySelector('div.card.padding-default > div.content-card-top') && (!document.querySelector('button#update-btn') || document.querySelector('button#update-btn').dataset.init !== 'true')) { if (document.querySelector('button#update-btn')) { document.querySelector('button#update-btn').dataset.init = 'true'; document.querySelector('button#update-btn').disabled = false; } else if (document.querySelector('div.card.padding-default > div.content-card-top > div.actions')) { document.querySelector('div.card.padding-default > div.content-card-top > div.actions').insertAdjacentHTML('beforeend', ` `); } else { document.querySelector('div.card.padding-default > div.content-card-top').insertAdjacentHTML('beforeend', `
`); } const btn = document.querySelector('button#update-btn'); btn.addEventListener('mouseover', async function (e) { btn.style.backgroundColor = 'rgba(239, 114, 4, 0.75)'; btn.style.borderColor = 'rgba(239, 114, 4, 0.75)'; }); btn.addEventListener('mouseout', async function (e) { btn.style.backgroundColor = 'rgb(239, 114, 4)'; btn.style.borderColor = 'rgb(239, 114, 4)'; }); btn.addEventListener('click', async function (e) { btn.disabled = true; Swal.mixin({ toast: true, position: "top-end", showConfirmButton: false, timer: 3000, timerProgressBar: true, didOpen: (toast) => { toast.onmouseenter = Swal.stopTimer; toast.onmouseleave = Swal.resumeTimer; } }).fire({ icon: "success", title: "更新请求已提交" }); const response = await savePaste(id); const workId = response.data.workflowId; if (response.code === 200) { const wait = setInterval(async () => { if ((await getWorkflow(workId)).code === 200) { clearInterval(wait); Swal.fire({ title: '更新成功', icon: 'success', confirmButtonText: '确定', confirmButtonColor: "#3085d6" }).then(() => { location.reload() }); btn.disabled = false; } }, 500); } else { Swal.fire({ title: '更新失败', text: '更新失败,请稍后再试', icon: 'error', confirmButtonColor: "#d33", confirmButtonText: '确定' }); btn.disabled = false; } }); } }, 500); } async function addcss() { GM_addStyle(GM_getResourceText('sweetalert2css')); } async function getUserInfo(uid) { const response = await fetch(`https://www.luogu.com.cn/api/user/info/${uid}`); const data = await response.json(); return data.user; } async function getDataOnLuoguMe(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'json', redirect: "follow", onload: function (response) { resolve(response.response); }, onerror: function (error) { reject(error); } }); }); } async function needWaitMain() { if (/^http[s]?:\/\/(www\.)?luogu\.com\.cn\/user\/\d+[^\/]*[^\/]*$/.test(location.href)) { if (!document.querySelector('main > div.main > div.sidebar-container.reverse > div.main > div.l-card:nth-child(3)')) { let data = await getUserInfo(location.href.split('/').pop()); console.debug(data.introduction); if (data.introduction === "") data.introduction = '这个人很懒,什么都没写'; data = await LuoguNewRenderer.render(data.introduction); document.querySelector('main > div.main > div.sidebar-container.reverse > div.main').insertAdjacentHTML('beforeend', `

个人介绍

${data}
`); } } } async function fetchFeInjection(url) { const response = await fetch(url, { credentials: 'include' }); const html = await response.text(); const match = html.match(/window\._feInjection\s*=\s*JSON\.parse\(decodeURIComponent\("([^"]+)"\)\)/); if (match) { return JSON.parse(decodeURIComponent(match[1])); } throw new Error('无法解析 _feInjection'); } async function getWorkflow(id) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: `https://api.luogu.me/workflow/query/${id}`, redirect: "follow", onload: function (response) { resolve(JSON.parse(response.response)); }, onerror: reject }); }); } async function savePaste(id) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: 'https://api.luogu.me/workflow/create/template/paste-save-pipeline', redirect: "follow", headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ targetId: id }), onload: function (response) { resolve(JSON.parse(response.response)); }, onerror: function (error) { reject(error); } }); }); } async function saveArticle(id) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: 'https://api.luogu.me/workflow/create/template/article-save-pipeline', redirect: "follow", headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ targetId: id }), onload: function (response) { resolve(JSON.parse(response.response)); }, onerror: function (error) { reject(error); } }); }); } async function notNeedWaitMain() { init(); if (/^http[s]?:\/\/(www\.)?luogu\.com\.cn\/paste\/[0-9a-zA-Z]{8}[^\/]*$/.test(location.href)) { pastePage(); } else if (/^http[s]?:\/\/(www\.)?luogu\.com\.cn\/article\/[0-9a-zA-Z]{8}[^\/]*$/.test(location.href)) { articlePage(); } } notNeedWaitMain(); addcss(); const wait = setInterval(async function () { if (document.querySelector('html') && LuoguNewRenderer) { needWaitMain(); clearInterval(wait); } }, 500);