// ==UserScript== // @name Neopets: Outbox (Sent NeoMail) // @namespace https://github.com/saahphire/NeopetsUserscripts // @version 1.0.5 // @description Saves the last 100 sent neomails in an Outbox // @author saahphire // @homepageURL https://github.com/saahphire/NeopetsUserscripts // @homepage https://github.com/saahphire/NeopetsUserscripts // @match *://*.neopets.com/neomessages.phtml* // @icon https://www.google.com/s2/favicons?sz=64&domain=neopets.com // @license The Unlicense // @grant GM.setValue // @grant GM.getValue // @downloadURL none // ==/UserScript== const savedMessageLimit = 100; const properties = ["timestamp", "nickname", "username", "subject", "body", "reply"]; class Outbox { constructor(rawMessages) { const parsedMessages = JSON.parse(rawMessages); this.messages = parsedMessages.map(msg => new Message(msg.t, msg.n, msg.u, msg.s, msg.b, msg.r)); } async save() { this.messages = this.messages.slice(savedMessageLimit * -1); GM.setValue("neopets-outbox", JSON.stringify(this.messages.map(msg => msg.minify()))); } async add() { const timestamp = new Date().getTime(); const nickname = document.querySelector('input[name="recipient"] ~ span')?.textContent; const username = document.querySelector('input[name="recipient"]').value; const subject = document.querySelector('input[name="subject"]').value; const bodyTextArea = document.getElementById("message_body") ?? document.querySelector('textarea[name="message_body"]'); const body = bodyTextArea.value ?? bodyTextArea.contentDocument.body.innerText.replaceAll(/\n\n/g, "\n"); const replyElement = document.querySelector('td[bgcolor="#DEDEDE"]')?.cloneNode(true); if (replyElement) { replyElement.querySelector("input").remove(); const reply = replyElement.innerHTML; this.messages.push(new Message(timestamp, nickname, username, subject, body, reply)); } else this.messages.push(new Message(timestamp, nickname, username, subject, body)); this.save(); } } class Message { constructor(timestamp, nickname, username, subject, body, reply) { this.timestamp = timestamp; this.nickname = nickname; this.username = username; this.subject = subject; this.body = body; this.reply = reply; } minify() { return Object.fromEntries(properties.map(p => [p[0], this[p]])); } toTitle() { const tr = document.createElement("tr"); tr.dataset.timestamp = this.timestamp; tr.innerHTML = ` ${(new Date(this.timestamp)).toLocaleString(navigator.language, {dateStyle: "short", timeStyle: "short"})} ${this.nickname ? this.nickname + '
[' : ''}${this.username}${this.nickname ? ']' : ''} ${this.subject} `; tr.onclick = () => this.toggle(tr); return tr; } toHTML() { const table = document.createElement("table"); table.classList.add("outbox--full", "inactive"); setTimeout(() => table.classList.remove("inactive"), 10); const tbody = document.createElement("tbody"); table.appendChild(tbody); tbody.innerHTML = ` To:[${this.username}] ${this.nickname ?? ''} Sent:${(new Date(this.timestamp)).toLocaleString(navigator.language, {dateStyle: "short", timeStyle: "short"})} Subject:${this.subject} ${this.reply ? '' + this.username + ' wrote:' + this.reply + '' : ''} Message:${this.body.replaceAll("\n", "
")} `; return table; } toggle(tr) { tr.classList.toggle("active"); if(tr.classList.contains("active")) this.expand(tr); else this.collapse(tr); } expand(tr) { const row = document.createElement("tr"); tr.insertAdjacentElement("afterEnd", row); const cell = document.createElement("td"); row.appendChild(cell); cell.colSpan = 3; cell.appendChild(this.toHTML()); } collapse(tr) { const row = tr.nextElementSibling; row.getElementsByClassName("outbox--full")[0].classList.add("inactive"); setTimeout(() => row.remove(), 250); } } class UI { constructor(outbox) { this.anchor = document.createElement("a"); this.anchor.href = "#"; this.anchor.onclick = () => this.open(outbox); this.anchor.textContent = "Outbox"; } appendAnchor() { const lastLink = document.querySelector("div.medText a:first-child"); lastLink.insertAdjacentElement("afterEnd", this.anchor); lastLink.insertAdjacentText("afterEnd", " | "); } makeMessageList(outbox) { const table = document.createElement("table"); table.classList.add("outbox--list"); const tbody = document.createElement("tbody"); table.appendChild(tbody); outbox.messages.forEach(msg => tbody.prepend(msg.toTitle())); tbody.insertAdjacentHTML("afterBegin", 'Date SentToSubject'); return table; } open(outbox) { const links = document.querySelector("div.medText"); document.getElementsByClassName("content")[0].style.display = "none"; const parent = document.createElement("td"); document.getElementsByClassName("content")[0].insertAdjacentElement("beforeBegin", parent); parent.classList.add("content"); parent.appendChild(links); const messageList = this.makeMessageList(outbox); parent.appendChild(messageList); } } const css = ``; (async function() { 'use strict'; document.head.insertAdjacentHTML("beforeEnd", css); const outbox = new Outbox(await GM.getValue("neopets-outbox", "[]")); const ui = new UI(outbox); ui.appendAnchor(); document.querySelector(".content input[type='submit']")?.addEventListener("click", () => { outbox.add(); }); })();