您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a button to the top of journal to generate and save a screenshot of the last N journal entries, optionally excluding non-hunts
// ==UserScript== // @name MH - Journal Screenshot Button // @namespace https://greasyfork.org/users/918578 // @description Adds a button to the top of journal to generate and save a screenshot of the last N journal entries, optionally excluding non-hunts // @author squash // @match https://www.mousehuntgame.com/* // @match http://www.mousehuntgame.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=mousehuntgame.com // @grant none // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/html2canvas.min.js // @version 0.3 // ==/UserScript== // TODO: Is type filtering accurate? Are active passive and linked the only hunt entry types? ;(function () { 'use strict' let css = ` #journalContainer.journal--privacy .journal__screenshot-preview .entry:not(.badge) a[href*="profile.php"], #journalContainer.journal--privacy .journal__screenshot-preview .entry.socialGift .journaltext a, #journalContainer.journal--privacy .journal__screenshot-preview .relicHunter_complete > .journalbody > .journaltext > b:nth-child(6), #journalContainer.journal--privacy .journal__screenshot-preview .wanted_poster-complete > .journalbody > .journaltext > b:nth-child(8), #journalContainer.journal--privacy .journal__screenshot-preview .journal__hunter-name { visibility: hidden !important; } #journalContainer .journal__screenshot-button { position: absolute; left: 10px; top: 60%; transform: translateY(-60%); font-size: 10px; line-height: 20px; padding: 0 5px 0 5px; border-radius: 5px; background: #ccc; border: 1px solid #333; text-decoration: none; display: none; } #journalContainer .journal__screenshot-button:hover { text-decoration: none; background-color: #eee; } #journalContainer .top:hover .journal__screenshot-button { display: block; } .journal__screenshot-preview { display: grid; grid-template-rows: repeat(12, auto); grid-gap: 0px; grid-auto-flow: column; overflow:visible; } .journal__screenshot-preview .entry { width: 349px; } ` const styleNode = document.createElement('style') styleNode.appendChild(document.createTextNode(css)) ;(document.querySelector('head') || document.documentElement).appendChild(styleNode) // Create screenshot button const button = document.createElement('a') button.innerText = 'Screenshot' button.href = '#' button.className = 'journal__screenshot-button' button.onclick = function (e) { e.preventDefault() createOptionsDialog() } function createOptionsDialog() { let dialog = new jsDialog() dialog.setTemplate('ajax') dialog.setIsModal(true) dialog.addToken('{*prefix*}', '<h2 class="title">Journal Screenshot</h2>') let content = `<form>` content += `<p><label>Include <input type="number" min="1" max="72" name="entries" value="5" step="1" /> entries starting from the top of Page ${parseInt( document.querySelector('.pagerView-section-currentPage').innerText )}. </label></p>` content += `<p><label>Exclude non-hunt entries? <input type="checkbox" name="exclude_nonhunts" value="1" checked /></label></p>` content += `<p><label>Hide hunter names? <input type="checkbox" name="censor" value="1" checked /></label></p>` content += '</form>' dialog.addToken('{*content*}', content) dialog.addToken('{*suffix*}', `<input class="jsDialogClose" type="button" value="Cancel" /> <input class="jsDialogClose journal__screenshot-save" type="button" value="Save Screenshot" />`) dialog.show() // Bind save screenshot event document.querySelector('#overlayPopup .journal__screenshot-save').addEventListener('click', async function (event) { let preview = await createPreviewElement() // Temporarily append preview to journal so it's styled correctly document.querySelector('#journalContainer .content').append(preview) // Wrap unselectable names in case we're censoring them findUnselectableNames() // Generate screenshot and remove preview again html2canvas(preview, { logging: false, width: preview.scrollWidth, height: preview.scrollHeight, backgroundColor: '#f1f1f1' }).then((canvas) => { let entryTime = preview.querySelector('.journaldate').innerText.split(' - ')[0].replace(':', '_') saveAs(canvas.toDataURL('image/png'), `screenshot-${new Date().toISOString().split('T')[0]}_${entryTime}.png`) preview.remove() document.querySelector('#journalContainer').classList.remove('journal--privacy') }) }) } async function createPreviewElement() { let config = Object.fromEntries(new FormData(document.querySelector('.jsDialog form')).entries()) config.entries = parseInt(config.entries) if (config.entries > 72) { config.entries == 72 } let preview = document.createElement('div') preview.classList = 'journal__screenshot-preview' let elements = document.querySelectorAll(config.exclude_nonhunts ? '#journalContainer .entry.active, #journalContainer .entry.passive, #journalContainer .entry.linked' : '.entry') let nextPage = getNextPage() let container = document.querySelector('#journalContainer') if (config.censor) { container.classList.add('journal--privacy') } else { container.classList.remove('journal--privacy') } // Fetch next journal page until we have all relevant entries or run out of pages while (elements.length < config.entries && nextPage <= 6) { console.log('Fetching Journal Page: ' + nextPage) let response = await fetch(callbackurl + 'managers/ajax/pages/journal.php', { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ page: nextPage, size: container.dataset.size, owner: container.dataset.owner }), }) let data = await response.json() let parser = new DOMParser() let journalPage = parser.parseFromString(data.journal_page.entries_string, 'text/html') elements = [...elements].concat([...journalPage.querySelectorAll(config.exclude_nonhunts ? '.entry.active, .entry.passive, .entry.linked' : '.entry')]) nextPage++ } // Append relevant number of journal entires to preview let i = 0 for (let element of elements) { i++ if (i > config.entries) { break } preview.appendChild(element.cloneNode(true)) } return preview } function getNextPage() { let page = parseInt(document.querySelector('.pagerView-section-currentPage').innerText) return page + 1 } function saveAs(uri, filename) { var link = document.createElement('a') if (typeof link.download === 'string') { link.href = uri link.download = filename // Firefox requires the link to be in the body document.body.appendChild(link) link.click() document.body.removeChild(link) } else { window.open(uri) } } function findUnselectableNames() { // Some map-related journal entries don't have selector-friendly names, so they need to be found and wrapped. const mapEntries = document.querySelectorAll('#journalContainer .entry.relicHunter_start .journaltext') if (mapEntries) { mapEntries.forEach(function (entry) { let index = entry.innerHTML.indexOf(' has joined the ') if (index === -1) { index = entry.innerHTML.indexOf(' has left the ') } if (index === -1) { index = entry.innerHTML.indexOf(' used Rare Map Dust') } if (index === -1) { index = entry.innerHTML.indexOf(', the map owner, has activated Consolation Mode') } if (index !== -1) { if (!entry.querySelector('.journal__hunter-name')) { entry.innerHTML = ['<span class="journal__hunter-name">', entry.innerHTML.slice(0, index), '</span>', entry.innerHTML.slice(index)].join('') } } }) } } function renderButton() { if (!document.querySelector('.journal__screenshot-button')) { const journalTop = document.querySelector('#journalContainer .top') if (journalTop) { journalTop.prepend(button) } } } renderButton() if (typeof eventRegistry !== 'undefined') { eventRegistry.addEventListener(hg.utils.PageUtil.EventSetPage, function (currentPage) { if (currentPage.type == 'PageCamp') { renderButton() } }) } })()