// ==UserScript==
// @name Neopets: Outbox (Sent NeoMail)
// @namespace https://github.com/saahphire/NeopetsUserscripts
// @version 1.0.3
// @description Saves the last 100 sent neomails in an Outbox
// @author saahphire
// @homepageURL 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 https://update.greasyfork.icu/scripts/549260/Neopets%3A%20Outbox%20%28Sent%20NeoMail%29.user.js
// @updateURL https://update.greasyfork.icu/scripts/549260/Neopets%3A%20Outbox%20%28Sent%20NeoMail%29.meta.js
// ==/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 body = document.getElementById("message_body").value ?? document.getElementById("message_body").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", '');
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();
});
})();