Greasy Fork is available in English.
Saves the last 100 sent neomails in an Outbox
// ==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
// ==/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 = `
<td class="outbox--timestamp">${(new Date(this.timestamp)).toLocaleString(navigator.language, {dateStyle: "short", timeStyle: "short"})}</td>
<td class="outbox--recipient">${this.nickname ? this.nickname + '<br>[' : ''}<a href="https://www.neopets.com/userlookup.phtml?user=${this.username}">${this.username}</a>${this.nickname ? ']' : ''}</td>
<td class="outbox--subject">${this.subject}</td>
`;
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 = `
<tr><td>To:</td><td>[<a href="https://www.neopets.com/userlookup.phtml?user=${this.username}">${this.username}</a>] ${this.nickname ?? ''}</td></tr>
<tr><td>Sent:</td><td>${(new Date(this.timestamp)).toLocaleString(navigator.language, {dateStyle: "short", timeStyle: "short"})}</td></tr>
<tr><td>Subject:</td><td>${this.subject}</td>
${this.reply ? '<tr><td>' + this.username + ' wrote:</td><td>' + this.reply + '</td></tr>' : ''}
<tr><td>Message:</td><td>${this.body.replaceAll("\n", "<br>")}</td></tr>
`;
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", '<tr class="outbox--list-header"><th class="neomail-outbox-date">Date Sent</th><th class="neomail-outbox-recipient">To</th><th class="neomail-outbox-subject">Subject</th></tr>');
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 = `<style>
.outbox--list {
width: 100%;
border: 1px solid #000000;
border-collapse: collapse;
margin-top: 50px;
}
.outbox--list-header th {
font-weight: bolder;
background-color: #687DAA;
color: #FFFFFF;
height: 22px;
font-size: 9pt;
font-weight: bold;
text-align: left;
padding-left: 3px;
}
.outbox--list td, .outbox--list th {
padding: 3px;
}
.outbox--timestamp {
line-height: 2.5;
}
.neomail-outbox-date {
width: 130px;
}
.neomail-outbox-recipient {
width: 200px;
}
.outbox--list tr {
border: 1px solid black;
cursor: pointer;
}
.outbox--list tr:nth-child(2n+1) {
background: #EDEDED;
}
.outbox--list td {
font-size: 8pt;
}
.outbox--full {
width: 100%;
transform: scaleY(100%);
transform-origin: top;
transition: transform 250ms ease-in-out;
}
.outbox--full.inactive {
transform: scaleY(0);
transition: transform 250ms ease-in-out;
}
.outbox--full, .outbox--full tr:nth-child(2n+1), .outbox--full tr:nth-child(2n) {
background: white;
}
.outbox--full td {
padding: 6px;
font-size: 9pt;
}
.outbox--full td:first-child {
width: 100px;
background-color: #C8E3FF;
font-size: 8pt;
font-weight: bolder;
}
</style>`;
(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();
});
})();